{"id":13826503,"url":"https://github.com/WebReflection/wicked-elements","last_synced_at":"2025-07-09T00:33:44.710Z","repository":{"id":55635406,"uuid":"158247919","full_name":"WebReflection/wicked-elements","owner":"WebReflection","description":"Components for the DOM as you've never seen before","archived":false,"fork":false,"pushed_at":"2021-05-05T08:38:04.000Z","size":313,"stargazers_count":243,"open_issues_count":0,"forks_count":12,"subscribers_count":18,"default_branch":"master","last_synced_at":"2024-04-24T21:20:45.542Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"https://medium.com/@WebReflection/a-wicked-custom-elements-alternative-6d1504b5857f","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"isc","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/WebReflection.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null},"funding":{"github":null,"patreon":null,"open_collective":"hyperHTML","ko_fi":null,"tidelift":null,"custom":"https://www.patreon.com/webreflection"}},"created_at":"2018-11-19T15:33:49.000Z","updated_at":"2024-04-16T09:56:19.000Z","dependencies_parsed_at":"2022-08-15T05:01:05.287Z","dependency_job_id":null,"html_url":"https://github.com/WebReflection/wicked-elements","commit_stats":null,"previous_names":[],"tags_count":62,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/WebReflection%2Fwicked-elements","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/WebReflection%2Fwicked-elements/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/WebReflection%2Fwicked-elements/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/WebReflection%2Fwicked-elements/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/WebReflection","download_url":"https://codeload.github.com/WebReflection/wicked-elements/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":225476383,"owners_count":17480215,"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":[],"created_at":"2024-08-04T09:01:39.174Z","updated_at":"2024-11-20T05:30:48.524Z","avatar_url":"https://github.com/WebReflection.png","language":"JavaScript","readme":"# wickedElements 🧙\n\n\u003csup\u003e**Social Media Photo by [Jonatan Pie](https://unsplash.com/@r3dmax) on [Unsplash](https://unsplash.com/)**\u003c/sup\u003e\n\n### 📣 Community Announcement\n\nPlease ask questions in the [dedicated discussions repository](https://github.com/WebReflection/discussions), to help the community around this project grow ♥\n\n---\n\nAn _all inclusive_ ~1.3K library to handle any element as if it was a Custom Element.\n\n```js\nimport {define, defineAsync, get, upgrade, whenDefined} from 'wicked-elements';\nconst {define, defineAsync, get, upgrade, whenDefined} = require('wicked-elements');\n```\n\n```html\n\u003cscript src=\"https://unpkg.com/wicked-elements\"\u003e\n  // as global variable\n  wickedElements.{define, get, upgrade, whenDefined};\n\u003c/script\u003e\n```\n\n### All versions changes\n\nPlease read [VERSIONS.md](./VERSIONS.md) to know more about historical changes, including the breaking one.\n\n\n## API\n\nExact same [customElements API](https://html.spec.whatwg.org/multipage/custom-elements.html#dom-window-customelements), with the following differences:\n\n  * `wickedElements.get(CSS)` returns the component definition, which should be an object literal, or a combination of definitions (i.e. `Object.assign({}, BaseDefinition, OverwritesDefinition)`)\n  * `wickedElements.define(CSS, definition)` accepts any _CSS_ selector, where the more specific it is, the better.\n  \nThe `definition` is a literal object with optional helpers/utilities described as such:\n\n```js\nwickedElements.define(\n  // a unique, specific, CSS selector that will become wicked\n  '[data-wicked=\"my-component\"]',\n  {\n    // a one-off, `constructor` like, initialization,\n    // the right place to populate node content, add listeners, or setup components\n    init() {\n      // the element that is related to this wicked instance will be always\n      // reachable through `this.element`, even without an `init()` method\n      this.element;\n      // always points, in every other method, to the DOM element related\n      // to this wicked element/component\n    },\n\n    // Custom Elements like callbacks, without the redundant `Callback` suffix\n    connected() {},\n    disconnected() {},\n\n    // invokes `attributeChanged` if any of these attributes is changed, set, removed\n    observedAttributes: ['data-thing', 'value'],\n    attributeChanged(name, oldValue, newValue) {},\n\n    // zero, one, or more, listeners, automatically setup per each component\n    // the context of each method-listener will be the wicked instance,\n    // not the element itself, but you can reach event.currentTarget or this.element\n    // at any time within the code\n    onClick(event) {},\n    onCustomEvent(event) {}\n    // if defined camelCase, events will be attached both lowercase\n    // and also camelCase, so that element.dispatchEvent(new CustomEvent('customEvent'))\n    // or element.dispatchEvent(new CustomEvent('customevent')) will both work.\n    // the `event.type` will be the one dispatched, i.e. `click` or `customEvent`\n    // or even `customevent`.\n\n    // any property with an `Options` suffix, will be used to add the listener,\n    // so that special cases like `{once: true}`, `true` to capture, and others,\n    // can be easily addressed through the definition. By default, options is `false`.\n    onClickOptions: {once: true}\n  }\n);\n```\n\n## F.A.Q.\n\n\u003cdetails\u003e\n  \u003csummary\u003e\n    \u003cstrong\u003eCan I use 3rd parts libraries to render content?\u003c/strong\u003e\n  \u003c/summary\u003e\n  \u003cdiv\u003e\n\n  Sure thing! Following a \u003ca href=\"https://github.com/WebReflection/lighterhtml#readme\"\u003elighterhtml\u003c/a\u003e integration example, also \u003ca href=\"https://codepen.io/WebReflection/pen/qBdOzWj?editors=0010\"\u003elive in CodePen\u003c/a\u003e:\n\n```js\nimport {render, html, svg} from 'lighterhtml';\nconst LighterHTML = {\n  html() { return render(this.element, html.apply(null, arguments)); },\n  svg() { return render(this.element, svg.apply(null, arguments)); }\n};\n\nimport {define} from 'wicked-elements';\ndefine('.my-component', {\n  ...LighterHTML,\n  init() { this.render(); },\n  render() {\n    this.html`\u003ch3\u003eHello 👋\u003c/h3\u003e`;\n  }\n});\n```\n  \u003c/div\u003e\n\u003c/details\u003e\n\n\u003cdetails\u003e\n  \u003csummary\u003e\n    \u003cstrong\u003eCan I haz \u003cem\u003ehooks\u003c/em\u003e too?\u003c/strong\u003e\n  \u003c/summary\u003e\n  \u003cdiv\u003e\n\n  You can either check **[hookedElements](https://github.com/WebReflection/hooked-elements#readme)** for an out-of-the-box solution, or you could use \u003ca href=\"https://github.com/WebReflection/augmentor#readme\"\u003eaugmentor\u003c/a\u003e, which is just perfect for this use case 😉, which is indeed exactly what _hookedElements_ use (it's just automatically integrated).\n\n  Test it \u003ca href=\"https://codepen.io/WebReflection/pen/poJjXPg?editors=0010\"\u003elive on CodePen\u003c/a\u003e.\n\n```js\nimport {augmentor, useState} from 'augmentor';\nimport {define} from 'wicked-elements';\ndefine('button.counter', {\n  init() {\n    // augment once any method, and that's it 🦄\n    this.render = augmentor(this.render.bind(this));\n    this.render();\n  },\n  render() {\n    const [counter, update] = useState(0);\n    const {element} = this;\n    element.onclick = () =\u003e update(counter + 1);\n    element.textContent = `${counter} clicks`;\n  }\n});\n```\n\n  \u003c/div\u003e\n\u003c/details\u003e\n\n\u003cdetails\u003e\n  \u003csummary\u003e\n    \u003cstrong\u003eAny basic example to play with?\u003c/strong\u003e\n  \u003c/summary\u003e\n  \u003cdiv\u003e\n\n  This is a classic one, the \u003ca href=\"https://webcomponents.dev/edit/kfZrGZ2SZwBu0opTJqL9\"\u003eWebComponents.dev click counter\u003c/a\u003e, also in \u003ca href=\"https://codepen.io/WebReflection/pen/JjdYyxj\"\u003ein CodePen\u003c/a\u003e.\n\n  \u003c/div\u003e\n\u003c/details\u003e\n\n\u003cdetails\u003e\n  \u003csummary\u003e\n    \u003cstrong\u003eAny other example?\u003c/strong\u003e\n  \u003c/summary\u003e\n  \u003cdiv\u003e\n\nSure. Here any element with a `disabled` class will effectively become disabled.\n\n```js\nwickedElements.define('.disabled', {\n  init() {\n    const {element} = this;\n\n    // if the element has its native way to be disabled, return\n    if ('disabled' in element)\n      return;\n\n    // otherwise define the behavior\n    Object.defineProperty(element, 'disabled', {\n      get: () =\u003e element.hasAttribute('disabled'),\n      set: value =\u003e {\n        if (value) {\n          element.style.cssText = this.disabled;\n          element.setAttribute('disabled', '');\n        }\n        else {\n          element.style.cssText = '';\n          element.removeAttribute('disabled');\n        }\n      }\n    });\n\n    // if the element was live, just trigger/ensure its status\n    element.disabled = element.disabled;\n  },\n  // the style to attach to disabled elements\n  disabled: `\n    pointer-events: none;\n    opacity: 0.5;\n  `\n});\n```\n\nOnce a definition is known, even same DOM nodes can be handled by multiple definitions/behaviors.\n\nAs example, here we are addressing all elements that will eventually have a `[disabled]` attribute.\n\n```js\nwickedElements.define('[disabled]', {\n  onMouseOver() {\n    const {element} = this;\n    // as elements can be promoted but never come back,\n    // which is the same that happens to Custom Elements definitions,\n    // we can check these elements are still disabled, per each mouseover event\n    if (element.disabled) {\n      element.style.visibility = 'hidden';\n    }\n  },\n  onMouseOut() {\n    this.element.style.visibility = 'visible';\n  }\n});\n```\n\nEach definition/behavior will provide a new instance of such definition (definition as prototype), meaning there are no conflicts between definitions, and each wicked instance deals with what its prototype object had at the time of definition.\n\n  \u003c/div\u003e\n\u003c/details\u003e\n\n\u003cdetails\u003e\n  \u003csummary\u003e\n    \u003cstrong\u003eAny caveat/hint to consider?\u003c/strong\u003e\n  \u003c/summary\u003e\n  \u003cdiv\u003e\n\nSame as Custom Elements suffer name-clashing, so that you can have only one `custom-element` definition per page, wicked definitions also could clash if the name is too generic.\n\nIt is a good practice to ensure, somehow, your definitions are namespaced, or unique enough, if you're after portability.\n\n```js\nwickedElements.define('[data-wicked=\"my-proj-name-table\"]', {\n  // unique(ish) definition what will likely not clash with others\n});\n```\n\nUsing `data-wicked=\"...\"` is convenient to also be sure a single element would represent the definition and nothing else, as you cannot have multiple values within an `element.dataset.wicked`, or better, you can serve these components via Server Side Rendering and reflect their special powers via JS once their definition lands on the client, which can be at any given time.\n\nUsing a runtime unique class/attribute name also grants behaviors and definitions won't clash, but portability of each wicked behavior could be compromised.\n\n  \u003c/div\u003e\n\u003c/details\u003e\n\n\u003cdetails\u003e\n  \u003csummary\u003e\n    \u003cstrong\u003eMy element doesn't become wicked, what should I do?\u003c/strong\u003e\n  \u003c/summary\u003e\n  \u003cdiv\u003e\n\n  There are cases where an element might not become \u003cem\u003ewicked\u003c/em\u003e, such as when the element class changes at runtime, and after the definition occurs.\n\n```js\nwickedElements.define('.is-wicked', {\n  init() {\n    this.element.classList.remove('not-wicked-yet');\n    console.log(this.element, 'is now wicked 🎉');\n  }\n});\n\nconst later = document.querySelector('.not-wicked-yet');\nlater.classList.add('is-wicked');\n// ... nothing happens ...\n```\n\nFor obvious performance reasons, the `MutationObserver` doesn't trigger per each possible class change in the DOM, but fear not, like it is for \u003ca href=\"https://html.spec.whatwg.org/multipage/custom-elements.html#dom-customelementregistry-upgrade\"\u003ecustomElements.upgrade(element)\u003c/a\u003e, you can always upgrade one or more elements via `wickedElements.upgrade(element)`.\n\n```js\nwickedElements.upgrade(later);\n// console.log ...\n// \u003cdiv class=\"is-wicked\"\u003e\u003c/div\u003e is now wicked 🎉\n```\n\nIf you'd like to upgrade many elements at once, you can always pass their top-most container, and let the library do the rest.\n\n```js\n// upgrade all wicked definitions at once 👍\nwickedElements.upgrade(document.documentElement);\n```\n\nDon't worry though, elements that were already wicked won't be affected by an upgrade, so that each `init()` is still granted to execute only once per fresh new element, and never again.\n\n  \u003c/div\u003e\n\u003c/details\u003e\n","funding_links":["https://opencollective.com/hyperHTML","https://www.patreon.com/webreflection"],"categories":["JavaScript"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FWebReflection%2Fwicked-elements","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FWebReflection%2Fwicked-elements","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FWebReflection%2Fwicked-elements/lists"}