{"id":14990085,"url":"https://github.com/mfcc64/wc-helpers","last_synced_at":"2025-07-09T18:32:17.162Z","repository":{"id":249381515,"uuid":"831380284","full_name":"mfcc64/wc-helpers","owner":"mfcc64","description":"Helpers for Web Components","archived":false,"fork":false,"pushed_at":"2024-08-09T23:32:22.000Z","size":26,"stargazers_count":65,"open_issues_count":0,"forks_count":1,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-07-03T01:15:05.549Z","etag":null,"topics":["custom-elements","web-components"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","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/mfcc64.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":"2024-07-20T11:37:56.000Z","updated_at":"2025-02-16T21:59:51.000Z","dependencies_parsed_at":"2024-07-20T11:47:56.158Z","dependency_job_id":"d4cef98c-081a-400f-89af-7061711e1267","html_url":"https://github.com/mfcc64/wc-helpers","commit_stats":{"total_commits":17,"total_committers":1,"mean_commits":17.0,"dds":0.0,"last_synced_commit":"4aacea91a850d81310f90c94e36f973b76ebf743"},"previous_names":["mfcc64/wc-helpers"],"tags_count":5,"template":false,"template_full_name":null,"purl":"pkg:github/mfcc64/wc-helpers","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mfcc64%2Fwc-helpers","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mfcc64%2Fwc-helpers/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mfcc64%2Fwc-helpers/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mfcc64%2Fwc-helpers/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mfcc64","download_url":"https://codeload.github.com/mfcc64/wc-helpers/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mfcc64%2Fwc-helpers/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":264502387,"owners_count":23618587,"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","web-components"],"created_at":"2024-09-24T14:19:26.305Z","updated_at":"2025-07-09T18:32:17.129Z","avatar_url":"https://github.com/mfcc64.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# `wc-helpers v2`\n\n`wc-helpers v2` help you create your custom elements.\n\n## Install\n\n### NPM\n\n```\nnpm i wc-helpers\n```\n\n```js\n// example of named imports\nimport { WCHelpers, anonymousBase, html } from \"wc-helpers\";\n// example of importing all\nimport * as _ from \"wc-helpers\";\n```\n\n### CDN\n\n```js\n// jsdlivr\nimport * as _ from \"https://cdn.jsdelivr.net/npm/wc-helpers@2/wc-helpers.mjs\";\n// unpkg\nimport * as _ from \"https://unpkg.com/wc-helpers@2/wc-helpers.mjs\";\n// relative CDN\nimport * as _ from \"../wc-helpers@2/wc-helpers.mjs\";\n```\n\n## API\n\n### `Anonymous`\n\n`Anonymous` mixin creates anonymous custom element that is registered automatically when constructed or\nwhen `toString` method is called. It won't be anonymous if manual registration is called before\nconstruction or `toString`.\n\n#### `anonymousBase`\n\nAn optional static field symbol for `Anonymous` mixin as basename for anonymous name.\n\nExample:\n\n```js\nimport { Anonymous, anonymousBase } from \"wc-helpers\";\nimport { LitElement } from \"lit\";\nimport { html, unsafeStatic } from \"lit/static-html.js\";\n\nclass ElementA extends Anonymous(HTMLElement) { }\nclass ElementB extends Anonymous(LitElement) {\n    render() {\n        return html`\u003c${unsafeStatic(ElementA)}\u003e\u003c/${unsafeStatic(ElementA)}\u003e`;\n    }\n}\nclass ElementC extends Anonymous(HTMLElement) { }\ncustomElements.define(\"element-c\", ElementC);\n\nclass ElementD extends Anonymous(HTMLElement) {\n    static [anonymousBase] = \"element-a\";\n}\n\nconst b = new ElementB;\ndocument.body.appendChild(b);\nconsole.log(b);\nconsole.log(new ElementC);\nconsole.log(`${ElementD}`);\n```\n\n### `Shadow`\n\n`Shadow` mixin creates automatic `shadowRoot`.\n\n#### `shadowOptions`\n\nAn optional static field symbol for `Shadow` mixin as options for `attachShadow`.\nNote that the default is `{ mode: \"open\" }`.\n\n#### `shadowHTML`\n\nAn optional static field symbol for `Shadow` mixin as HTML template for `shadowRoot`.\nIt must be `html` literal or `HTMLTemplateElement` instance.\n\n#### `shadowCSS`\n\nAn optional static field symbol for `Shadow` mixin as array of CSS stylesheet for `shadowRoot`.\nIt must be an array of `css` literal or `CSSStyleSheet` instance.\n\n#### `shadowRoot`\n\nA symbol for `Shadow` mixin instance to access `shadowRoot`. It is useful when `mode` is closed.\n\n#### `shadowElements`\n\nA symbol for `Shadow` mixin instance as object to access elements that have `id` attribute.\n\nExample:\n\n```js\nimport {\n    Shadow, Anonymous, chain,\n    shadowOptions, shadowHTML, shadowCSS,\n    shadowRoot, shadowElements,\n    html, css, cssDisplayContents\n} from \"wc-helpers\";\n\nclass ElementA extends chain(Anonymous, Shadow, HTMLElement) {\n    static [shadowOptions] = { mode: \"closed\" };\n    static [shadowHTML] = html`\u003cp id=\"paragraph\"\u003e\u003cspan id=\"span\"\u003eHello\u003c/span\u003e \u003cspan\u003eWorld\u003c/span\u003e\u003c/p\u003e`;\n    static [shadowCSS] = [ cssDisplayContents, css`p { background-color: #ffff00; }` ];\n\n    constructor() {\n        super();\n        console.log(this[shadowRoot]);\n        console.log(this[shadowElements]);\n    }\n}\n\ndocument.body.appendChild(new ElementA);\n```\n\n### `State`\n\nA simple state management class.\n\n#### `new State(initialValue, optionalName)`\n\nConstuctor for `State` instance.\n\n#### `State.value instance field`\n\nCurrent value of `State` instance.\n\n#### `State.name instance field`\n\nOptional `name` for `State` instance.\n\n#### `State.prototype.set(value)`\n\nSet current value of state and call listeners.\n\n#### `State.prototype.cset(value)`\n\nSet current value of state. It doesn't call listeners when `value` equal to `oldValue`.\nIt returns `true` when listeners are called, otherwise `false`.\n\n#### `State function listener(value, oldValue) callback`\n\nCallback for `listen` method.\n\n#### `State.prototype.listen(listener)`\n\nRegister `listener` to listen state change and call it for initialization.\n\n#### `State.prototype.listenNoInit(listener)`\n\nRegister `listener` to listen state change but do not call it for initialization.\n\n#### `State.prototype.unlisten(listener)`\n\nUnregister `listener` from state instance.\n\n#### `State.prototype.unlistenAll()`\n\nUnregister all listeners from state instance.\n\n#### `State.listen(state0, ... , stateN, listener)`\n\nRegister `listener` to listen state change of `state0, ... , stateN` and call it once for initialization.\n\n#### `State.listenNoInit(state0, ... , stateN, listener)`\n\nRegister `listener` to listen state change of `state0, ... , stateN` but do not call it for initialization.\n\n#### `State.listenInitAll(state0, ... , stateN, listener)`\n\nRegister `listener` to listen state change of `state0, ... , stateN` and call it for initialization\nfor every `state0, ... , stateN`.\n\n#### `State.unlisten(listener, state0, ... , stateN)`\n\nUnregister `listener` from `state0, ... , stateN`.\n\n#### `State.unlistenAll(state0, ... , stateN)`\n\nUnregister all listeners from `state0, ... , stateN`.\n\nExample:\n\n```js\nimport { State } from \"wc-helpers\";\n\nconst stateA = new State(\"hello\");\nconst stateB = new State(10);\nconst stateC = new State(null, \"stateC\");\n\nstateA.listen((value, oldValue) =\u003e console.log(value, oldValue));\nstateB.listenNoInit(() =\u003e console.log(stateB.value));\nstateC.listen(function(value) { console.log(this.name, this.value); });\n\nState.listen(stateA, stateB, stateC, () =\u003e console.log(stateA.value, stateB.value, stateC.value));\nState.listenInitAll(stateA, stateB, stateC, (value) =\u003e console.log(value));\n\nstateA.set(\"world\");\nstateB.cset(10);\nstateC.set(\"stateC\");\n\nState.unlistenAll(stateA, stateB, stateC);\n```\n\n### `Attribute`\n\n`Attribute` mixin automatically creates `State` instances from\n`observedAttributes` that can be listened.\n\n#### `attributeState`\n\nA symbol to access attribute `State` instances.\n\nExample:\n\n```js\nimport { State, Attribute, attributeState, Anonymous } from \"wc-helpers\";\n\nclass ElementA extends Anonymous(Attribute(HTMLElement)) {\n    static observedAttributes = [ \"data-src\", \"data-active\", \"data-message\" ];\n\n    constructor() {\n        super();\n        const at = this[attributeState];\n        State.listen(...Object.values(at), function() { console.log(this.name, this.value); });\n        at[\"data-src\"].listen(src =\u003e console.log(src));\n    }\n}\n\nconst a = new ElementA;\na.setAttribute(\"data-src\", \"/home/\");\na.setAttribute(\"data-active\", \"\");\na.setAttribute(\"data-message\", \"Hello World\");\na.removeAttribute(\"data-active\"); // send null\n```\n\n### Utility\n\n#### `abstract`\n\nAn optional static field symbol for `Anonymous` mixin to prevent registration.\n\n```js\nimport { abstract, Anonymous } from \"wc-helpers\";\n\nclass ElementA extends Anonymous(HTMLElement) {\n    static [abstract];\n}\n\nconsole.log(`${ElementA}`); // do not throw\nnew ElementA; // throw\n```\n\n#### `once`\n\nRun function once when argument is equal. Only support single object parameter.\n\n#### `chain`\n\nFlatten mixin.\n\n```js\nimport { chain, Anonymous, Attribute, Shadow } from \"wc-helpers\";\n\nconsole.log(Anonymous(Attribute(Shadow(HTMLElement))) == chain(Anonymous, Attribute, Shadow, HTMLElement));\n```\n\n#### `html`\n\n`html` tag wraps string literals into object. It escape `\u0026\u003c\u003e'\"=` characters from literals' arguments\nunless that argument is also another `html` tag or result of `htmlUnsafeString` function. When an\nargument is `Array`, its item will be evaluated according to the escape rules and they will be concancenated.\n\n#### `htmlUnsafeString`\n`htmlUnsafeString` allows string to be included in `html` tag argument without escaping.\n\n#### `css`\n\n`css` tag wraps string literals into object. No escaping performed.\n\n#### `htmlCache`\n\nGet `HTMLTemplateElement` instance from `html` tag.\n\n#### `cssCache`\n\nGet `CSSStyleSheet` instance from `css` tag.\n\n#### `cssDisplayBlock`\n\n#### `cssDisplayInline`\n\n#### `cssDisplayInlineBlock`\n\n#### `cssDisplayNone`\n\n#### `cssDisplayContents`\n\nDefault `css` tag for `:host` element `display` set to `block`, `inline`,\n`inline-block`, `none`, or `contents` respectively.\n\n#### `htmlFragment`\n\nCreate `DocumentFragment` instance from `html` tag or `HTMLTemplateElement` instance.\n\nExample:\n\n```js\nimport * as _ from \"wc-helpers\";\n\nclass ElementA extends _.chain(_.Anonymous, _.Shadow, HTMLElement) {\n    static [_.shadowHTML] = _.html`\u003cslot\u003e\u003c/slot\u003e`;\n    static [_.shadowCSS] = [ _.cssDisplayBlock, _.css`:host { color: #00ff00; }` ];\n}\n\nconst message = \"\u003cHello World\u003e 'Hello World' \\\"Hello World\\\" \u0026=\";\nconst htmlA = _.html `\u003c${ElementA}\u003e${message}\u003c/${ElementA}\u003e`;\nconst templateA = _.htmlCache(htmlA);\ndocument.body.appendChild(_.htmlFragment(htmlA));\ndocument.body.appendChild(_.htmlFragment(templateA));\ndocument.body.insertAdjacentHTML(\"beforeend\", htmlA);\n```\n\n#### `getGlobalCSS`\n\n`getGlobalCSS` gets constructed `CSSStyleSheet` from `link` and `style` elements which have\n`data-wc-global-css` attribute.\n\n#### `updateGlobalCSS`\n\n`updateGlobalCSS` update the constructed `CSSStyleSheet` for `getGlobalCSS` dynamically.\n\nExample:\n\n```html\n\u003c!DOCTYPE html\u003e\n\u003chtml\u003e\n    \u003chead\u003e\n        \u003ctitle\u003eGlobal CSS\u003c/title\u003e\n        \u003cscript type=\"importmap\"\u003e{\n            \"imports\": {\n                \"wc-helpers\": \"https://cdn.jsdelivr.net/npm/wc-helpers@2/wc-helpers.mjs\"\n            }\n        }\u003c/script\u003e\n        \u003cscript type=\"module\"\u003e\n            import { getGlobalCSS, updateGlobalCSS, WCHelpers, shadowCSS, shadowHTML, html, css } from \"wc-helpers\";\n\n            class MyElement extends WCHelpers(HTMLElement) {\n                static [shadowCSS] = [ getGlobalCSS() ];\n                static [shadowHTML] = html`\u003cp\u003eInside shadow DOM\u003c/p\u003e`;\n            }\n            customElements.define(\"my-element\", MyElement);\n\n            setTimeout(() =\u003e {\n                const style = document.createElement(\"style\");\n                style.textContent = css`p { color: green; }`;\n                style.setAttribute(\"data-wc-global-css\", \"\");\n                style.addEventListener(\"load\", () =\u003e updateGlobalCSS());\n                document.head.appendChild(style);\n            }, 3000);\n\n        \u003c/script\u003e\n        \u003cstyle data-wc-global-css\u003e\n            p { color: red; }\n        \u003c/style\u003e\n    \u003c/head\u003e\n    \u003cbody\u003e\n        \u003cp\u003eOutside shadow DOM\u003c/p\u003e\n        \u003cmy-element\u003e\u003c/my-element\u003e\n    \u003c/body\u003e\n\u003c/html\u003e\n```\n\n### `WCHelpers`\n\n`Anonymous`, `Attribute`, and `Shadow` mixin chained together.\n\nHere is an example adopted from [react.dev](https://react.dev/learn/scaling-up-with-reducer-and-context),\nwith approximately 50% reduction of lines.\n\n```html\n\u003c!DOCTYPE html\u003e\n\u003chtml\u003e\n    \u003chead\u003e\n        \u003ctitle\u003eWCHelpers\u003c/title\u003e\n        \u003cscript type=\"importmap\"\u003e{\n            \"imports\": {\n                \"wc-helpers\": \"https://cdn.jsdelivr.net/npm/wc-helpers@2/wc-helpers.mjs\"\n            }\n        }\u003c/script\u003e\n        \u003cscript type=\"module\"\u003e\n// start\n// example using importmap\nimport * as _ from \"wc-helpers\";\n\nconst { html, css } = _;\n\nclass TaskItem extends _.WCHelpers(HTMLElement) {\n    static observedAttributes = [ \"data-done\" ];\n    static [_.shadowCSS] = [ css`:host { display: list-item; }`, _.getGlobalCSS() ];\n    static [_.shadowHTML] = html`\n\u003cinput type=\"checkbox\" id=\"checkbox\"/\u003e\n\u003cspan id=\"mode\"\u003e\n    \u003cspan id=\"editor\"\u003e\n        \u003cinput type=\"text\" id=\"input\"/\u003e\n        \u003cbutton id=\"save\"\u003eSave\u003c/button\u003e\n    \u003c/span\u003e\n    \u003cspan id=\"viewer\"\u003e\n        \u003cspan part=\"content\"\u003e\u003cslot\u003e\u003c/slot\u003e\u003c/span\u003e\n        \u003cbutton id=\"edit\"\u003eEdit\u003c/button\u003e\n    \u003c/span\u003e\n\u003c/span\u003e\n\u003cbutton id=\"delete\"\u003eDelete\u003c/button\u003e`;\n\n    constructor() {\n        super();\n        const internals = this.attachInternals();\n        internals.role = \"listitem\";\n\n        const id = this[_.shadowElements], at = this[_.attributeState];\n\n        at[\"data-done\"].listen((value) =\u003e id.checkbox.checked = value != null);\n        id.checkbox.addEventListener(\"change\", () =\u003e void(this.toggleAttribute(\"data-done\", id.checkbox.checked)));\n\n        const _edited = new _.State(false);\n\n        _edited.listen((edited) =\u003e {\n            id.mode.textContent = \"\";\n            id.mode.appendChild(edited ? id.editor : id.viewer);\n        });\n\n        _edited.listenNoInit((edited) =\u003e edited ? (id.input.value = this.textContent) : (this.textContent = id.input.value));\n\n        id.edit.addEventListener(\"click\", () =\u003e void (_edited.cset(true)));\n        id.save.addEventListener(\"click\", () =\u003e void (_edited.cset(false)));\n        id.delete.addEventListener(\"click\", () =\u003e void (this.parentNode?.removeChild(this)));\n    }\n}\ncustomElements.define(\"task-item\", TaskItem);\n\nclass MyTask extends _.WCHelpers(HTMLElement) {\n    static observedAttributes = [ \"data-title\" ];\n    static [_.shadowCSS] = [ _.cssDisplayBlock, _.getGlobalCSS() ];\n    static [_.shadowHTML] = html`\n\u003ch3 id=\"title\"\u003e\u003c/h3\u003e\n\u003cinput type=\"text\" placeholder=\"Add task\" id=\"input\"/\u003e\n\u003cbutton id=\"add\"\u003eAdd\u003c/button\u003e\n\u003cul\u003e\u003cslot\u003e\u003c/slot\u003e\u003c/ul\u003e`;\n\n    constructor() {\n        super();\n        const id = this[_.shadowElements];\n        const at = this[_.attributeState];\n        at[\"data-title\"].listen((value) =\u003e id.title.textContent = value ?? \"\");\n        id.add.addEventListener(\"click\", () =\u003e {\n            this.insertAdjacentHTML(\"beforeend\", html`\u003ctask-item\u003e${id.input.value}\u003c/task-item\u003e`);\n            id.input.value = \"\";\n        });\n    }\n}\ncustomElements.define(\"my-task\", MyTask);\n\n// end\n        \u003c/script\u003e\n        \u003cstyle\u003e\n            task-item[data-done]::part(content) {\n                text-decoration: line-through;\n            }\n        \u003c/style\u003e\n        \u003cstyle data-wc-global-css\u003e\n            ul, ol { padding-inline-start: 25px; }\n        \u003c/style\u003e\n    \u003chead\u003e\n    \u003cbody\u003e\n        \u003cmy-task data-title=\"Day off in Kyoto\"\u003e\n            \u003ctask-item data-done\u003ePhilosopher's Path\u003c/task-item\u003e\n            \u003ctask-item\u003eVisit the temple\u003c/task-item\u003e\n            \u003ctask-item\u003eDrink matcha\u003c/task-item\u003e\n        \u003c/my-task\u003e\n    \u003c/body\u003e\n\u003c/html\u003e\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmfcc64%2Fwc-helpers","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmfcc64%2Fwc-helpers","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmfcc64%2Fwc-helpers/lists"}