{"id":18941769,"url":"https://github.com/bahrus/xtal-decor","last_synced_at":"2026-04-25T23:35:24.150Z","repository":{"id":55818388,"uuid":"189253249","full_name":"bahrus/xtal-decor","owner":"bahrus","description":"Enables attaching ES6 proxies onto other DOM elements.","archived":false,"fork":false,"pushed_at":"2023-03-04T04:34:13.000Z","size":277,"stargazers_count":1,"open_issues_count":1,"forks_count":0,"subscribers_count":3,"default_branch":"baseline","last_synced_at":"2025-02-01T00:47:15.810Z","etag":null,"topics":["custom-elements","es6-proxy","web-component","web-components","webcomponents"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/bahrus.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2019-05-29T15:34:32.000Z","updated_at":"2021-10-17T15:31:56.000Z","dependencies_parsed_at":"2024-11-08T12:41:12.403Z","dependency_job_id":null,"html_url":"https://github.com/bahrus/xtal-decor","commit_stats":{"total_commits":179,"total_committers":2,"mean_commits":89.5,"dds":"0.011173184357541888","last_synced_commit":"a9456b07a33c4fd53d90d63b9210d9bdd1f191eb"},"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bahrus%2Fxtal-decor","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bahrus%2Fxtal-decor/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bahrus%2Fxtal-decor/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bahrus%2Fxtal-decor/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/bahrus","download_url":"https://codeload.github.com/bahrus/xtal-decor/tar.gz/refs/heads/baseline","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":239942757,"owners_count":19722330,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["custom-elements","es6-proxy","web-component","web-components","webcomponents"],"created_at":"2024-11-08T12:29:23.688Z","updated_at":"2026-03-23T23:30:16.669Z","avatar_url":"https://github.com/bahrus.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# xtal-decor [deprecated]\n\nxtal-decor is now deprecated.  [be-decorated](https://github.com/bahrus/be-decorated) replaces it.\n\n[![Published on webcomponents.org](https://img.shields.io/badge/webcomponents.org-published-blue.svg)](https://www.webcomponents.org/element/xtal-decor)\n\n\u003ca href=\"https://nodei.co/npm/xtal-decor/\"\u003e\u003cimg src=\"https://nodei.co/npm/xtal-decor.png\"\u003e\u003c/a\u003e\n\n\u003cimg src=\"https://badgen.net/bundlephobia/minzip/xtal-decor\"\u003e\n\n## Syntax\n\nxtal-decor provides a base class which enables attaching ES6 proxies onto other \"Shadow DOM peer citizens\" -- native DOM or custom elements in the same Shadow DOM realm.\n\nxtal-decor provides a much more \"conservative\" alternative approach to enhancing existing DOM elements, in place of the controversial \"is\"-based customized built-in element [standard-ish](https://bkardell.com/blog/TheWalrus.html).\n\nLike [xtal-deco](https://github.com/bahrus/xtal-deco), properties \"init\", \"on\", \"actions\" and \"finale\" allow us to define the behavior of the ES6 proxy with a minimum of fuss.  And the property \"virtualProps\" is also supported, which allows us to define properties that aren't already part of the native DOM element or custom element we are enhancing.  \n\nUse of virtualProps is critical if you want to be guaranteed that your component doesn't break, should the native DOM element or custom element be enhanced with a new property with the same name.\n\nxtal-decor provides the base class and web component.  Like xtal-deco, we can avoid having to inherit from this class, instead \"informally\" defining a new behavior by using an inline script via something like [nomodule](https://github.com/bahrus/nomodule):\n\n```html\n\u003cxtal-decor upgrade=button if-wants-to-be=a-butterbeer-counter virtual-props='[\"count\"]'\u003e\u003cscript nomodule=ish\u003e\n    const decoProps = {\n        actions: [\n            ({count}) =\u003e {\n                window.alert(count + \" butterbeers sold\");\n            }\n        ],\n        on: {\n            click: ({self}, event) =\u003e {\n                self.count++;\n            }\n        },\n        init: ({self}) =\u003e{\n            self.count = 0;\n        },\n        finale: ({self}, elementBeingRemoved) =\u003e {\n            console.log({self, elementBeingRemoved});\n        }\n    }\n    Object.assign(selfish.parentElement, decoProps);\n\u003c/script\u003e\u003c/xtal-decor\u003e\n\n\u003cbutton id=myButton be-a-butterbeer-counter='{\"count\": 1000}' disabled\u003eClick me to Order Your Drink\u003c/button\u003e\n\n\u003cbutton onclick=\"setCount()\"\u003eSet count to 2000\u003c/button\u003e\n\u003cscript\u003e\n    function setCount(){\n        butterBeerCounter.setAttribute('be-a-butterbeer-counter', '{\"count\": 2000}');\n    }\n\u003c/script\u003e\n```\n\n\nA more \"formal\" way of defining new behavior is to extend the base class XtalDecor, and to set the \"virtualProps\", \"actions\", \"on\" and/or \"init\" properties during field initialization or in the constructor.  You can then define a custom element with any name you want using your extended class.  \n\nAn instance of your custom element needs to be added somewhere in the shadowDOM realm where you want it to affect behavior (or outside any Shadow DOM Realm to affect elements outside any Shadow DOM).\n\nThe attributes of your instance tag needs to define what element (optional - use * for all elements) and attribute (required) to look for.\n\nFor example:\n\n```html\n#shadow-root (open)\n    \u003cbe-on-the-next-level upgrade=blacked-eyed-peas if-wants-to-be=on-the-next-level\u003e\u003c/be-on-the-next-level\u003e\n    \u003cbe-rocking-over-that-bass-tremble upgrade=black-eyed-peas if-wants-to-be=rocking-over-that-bass-tremble\u003e\u003c/be-rocking-over-that-bass-tremble\u003e\n    \u003cbe-chilling-with-my-motherfuckin-crew upgrade=blacked-eyed-peas if-wants-to-be=chilling-with-my-motherfuckin-crew\u003e\u003c/be-chilling-with-my-motherfuckin-crew\u003e\n    ...\n\n\n\n    \u003cblack-eyed-peas \n        be-on-the-next-level='{\"level\":\"level 11\"}' \n        be-rocking-over-that-bass-tremble\n        be-chilling-with-my-motherfuckin-crew\n    \u003e\u003c/black-eyed-peas\u003e\n\n    \u003c!-- Becomes, after upgrading --\u003e\n    \u003cblack-eyed-peas \n        is-on-the-next-level='{\"level\":\"level 11\"}'\n        is-rocking-over-that-bass-tremble\n        is-chilling-with-my-motherfuckin-crew\n    \u003e\u003c/black-eyed-peas\u003e\n```\n\n\n## Setting properties of the proxy externally\n\nJust as we need to be able to pass property values to custom elements, we need a way to do this with xtal-decor.  But how?\n\nThe tricky thing about proxies is they're great if you have access to them, useless if you don't.  \n\n###  Approach I.  Programmatically (Ugly)\n\nIf you need to modify a property of a proxy, you can do that via the xtal-decor element, or the element extending xtal-decor:\n\n```html\n\u003cxtal-decor id=decor upgrade=button if-wants-to-be=a-butterbeer-counter virtual-props='[\"count\"]'\u003e\n    ...\n\u003c/xtal-decor\u003e\n\u003cbutton id=butterBeerCounter be-a-butterbeer-counter='{\"count\": 1000}' disabled\u003eClick me to Order Your Drink\u003c/button\u003e\n...\n\u003cscript\u003e\n    function setCount(newCount){\n        if(decor.targetToProxyMap === undefined || !decor.targetToProxyMap.has(butterBeerCounter)){\n            setTimeout(() =\u003e setCount(newCount), 50);\n            return;\n        }\n        const proxy = decor.targetToProxyMap.get(butterBeerCounter);\n        proxy.count = newCount;\n    }\n\u003c/script\u003e\n```\n\n###  Approach II. Setting properties via the controlling attribute:\n\nA more elegant solution, perhaps, which xtal-decor supports, is to pass in properties via its custom attribute:\n\n```html\n\u003clist-sorter upgrade=* if-wants-to-be=sorted\u003e\u003c/list-sorter\u003e\n\n...\n\n\u003cul be-sorted='{\"direction\":\"asc\",\"nodeSelectorToSortOn\":\"span\"}'\u003e\n    \u003cli\u003e\n        \u003cspan\u003eZorse\u003c/span\u003e\n    \u003c/li\u003e\n    \u003cli\u003e\n        \u003cspan\u003eAardvark\u003c/span\u003e\n    \u003c/li\u003e\n\u003c/ul\u003e\n\n```\n\nAfter list-sorter does its thing, the attribute \"be-sorted\" switches to \"is-sorted\":\n\n```html\n\n\u003cul is-sorted='{\"direction\":\"asc\",\"nodeSelectorToSortOn\":\"span\"}'\u003e\n    \u003cli\u003e\n        \u003cspan\u003eAardvark\u003c/span\u003e\n    \u003c/li\u003e\n    \u003cli\u003e\n        \u003cspan\u003eZorse\u003c/span\u003e\n    \u003c/li\u003e\n\u003c/ul\u003e\n\n```\n\nYou cannot pass in new values by using the is-sorted attribute.  Instead, you need to continue to use the be-sorted attribute:\n\n\n```html\n\n\u003cul id=list is-sorted='{\"direction\":\"asc\",\"nodeSelectorToSortOn\":\"span\"}'\u003e\n    \u003cli\u003e\n        \u003cspan\u003eAardvark\u003c/span\u003e\n    \u003c/li\u003e\n    \u003cli\u003e\n        \u003cspan\u003eZorse\u003c/span\u003e\n    \u003c/li\u003e\n\u003c/ul\u003e\n\n\u003cscript\u003e\n    list.setAttribute('be-sorted', JSON.stringify({direction: 'desc'}))\n\u003c/script\u003e\n\n```\n\nA [vscode plug-in](https://marketplace.visualstudio.com/items?itemName=andersonbruceb.json-in-html) is available that makes editing JSON attributes like these much less susceptible to human fallibility.\n\n## Approach III.  Proxy Forwarding with a Light Touch\n\nA reusable component, [https://github.com/bahrus/proxy-decor](proxy-decor) serves as a useful companion to xtal-decor. Whereas xtal-decor can have specialized logic (either via prop setting or class extension), proxy-decor is very light-weight and generic.  Think of it as a very [thin client](https://www.dell.com/premier/us/en/RC1378895?gacd=9684689-1077-5763017-265940558-0\u0026dgc=st\u0026gclid=9f0071f121cb1a930be2117f5bd9e116\u0026gclsrc=3p.ds\u0026msclkid=9f0071f121cb1a930be2117f5bd9e116#/systems/cloud-client-computing) that easily connects to / switch between different remote, fully loaded desktop/server/VMs, sitting in some well-ventilated server room.\n\nproxy-decor not only allows properties to be passed in to the proxy, it also raises custom events after any property of the proxy changes.\n\nproxy-decor uses a \"for\" attribute, similar to the \"for\" attribute for a label.\n\nSample syntax:\n\n```html\n\u003cxtal-decor upgrade=button if-wants-to-be=a-butterbeer-counter virtual-props='[\"count\"]'\u003e\u003cscript nomodule=ish\u003e\n    const decoProps = {\n        actions: [\n            ({count, self}) =\u003e {\n                window.alert(count + \" butterbeers sold\");\n            }\n        ],\n        on: {\n            'click': ({self}) =\u003e {\n                console.log(self);\n                self.count++;\n            }\n        },\n        init: ({self}) =\u003e{\n            self.count = 0;\n        }\n    }\n    Object.assign(selfish.parentElement, decoProps);\n\u003c/script\u003e\u003c/xtal-decor\u003e\n\n\n\u003cbutton id=butterbeerCounter be-a-butterbeer-counter='{\"count\": 1000}' disabled\u003e\n    Click Me to Order Your Drink\n\u003c/button\u003e\n\u003cproxy-decor id=proxyDecor for=butterbeerCounter\u003e\u003c/proxy-decor\u003e\n\u003cpass-down\n    on=\"a-butterbeer-counter:count-changed\" \n    to=[-text-content] \n    m=1 \n    val-from-target=aButterBeerCounter.count\n    init-event=a-butterbeer-counter:initialized\u003e\n\u003c/pass-down\u003e\n\u003cspan -text-content\u003e\u003c/span\u003e drinks sold.\n\n\u003cbutton onclick=\"setCount()\"\u003eSet count to 2000\u003c/button\u003e\n\u003cscript\u003e\n    function setCount(){\n        const bbc = (proxyDecor.aButterbeerCounter ??= {});\n        bbc.count = 2000;\n    }\n\u003c/script\u003e\n```\n\nproxy-decor:\n\n1.  Does an id search within the shadow dom realm (like label for).\n2.  Multiple proxies can be \"fronted\" by a single proxy-decor tag.\n3.  Event names are namespaced only for virtual properties.\n\n\n\n\n\n\n## Approach IV.  Integrate with other decorators -- binding decorators -- that hide the complexity\n\n[WIP](https://github.com/bahrus/be-observant#inserting-dynamic-settings-todo)\n\n## [Demo](https://codepen.io/bahrus/pen/XWpvmZr)\n\n\n\n## API\n\nThis web component base class builds on the provided api:\n\n```JavaScript\nimport { upgrade } from 'xtal-decor/upgrade.js';\nupgrade({\n    shadowDOMPeer: ... //Apply trait to all elements within the same ShadowDOM realm as this node.\n    upgrade: ... //CSS query to monitor for matching elements within ShadowDOM Realm.\n    ifWantsToBe: // monitor for attributes that start with be-[ifWantsToBe], \n}, callback);\n```\n\nAPI example:\n\n```JavaScript\nimport {upgrade} from 'xtal-decor/upgrade.js';\nupgrade({\n    shadowDOMPeer: document.body,\n    upgrade: 'black-eyed-peas',\n    ifWantsToBe: 'on-the-next-level',\n}, target =\u003e {\n    ...\n});\n```\n\nThe API by itself is much more open ended, as you will need to entirely define what to do in your callback.  In other words, the api provides no built-in support for creating a proxy.\n\n## For the sticklers\n\nIf you are concerned about using attributes that are prefixed with the non standard be-, use data-be instead:\n\n\n```html\n\u003clist-sorter upgrade=* if-wants-to-be=sorted\u003e\u003c/list-sorter\u003e\n\n...\n\n\u003cul data-be-sorted='{\"direction\":\"asc\",\"nodeSelectorToSortOn\":\"span\"}'\u003e\n    \u003cli\u003e\n        \u003cspan\u003eZorse\u003c/span\u003e\n    \u003c/li\u003e\n    \u003cli\u003e\n        \u003cspan\u003eAardvark\u003c/span\u003e\n    \u003c/li\u003e\n\u003c/ul\u003e\n\n```\n\n\n\n## Viewing example from git clone or github fork:\n\nInstall node.js.  Then, from a command prompt from the folder of your git clone or github fork:\n\n```\n$ npm install\n$ npm run serve\n\nOpen http://localhost:3030/demo/dev.html\n```\n\n\n\n\n\n\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbahrus%2Fxtal-decor","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbahrus%2Fxtal-decor","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbahrus%2Fxtal-decor/lists"}