{"id":13837634,"url":"https://github.com/WebReflection/nonchalance","last_synced_at":"2025-07-10T18:34:03.718Z","repository":{"id":65824282,"uuid":"600744678","full_name":"WebReflection/nonchalance","owner":"WebReflection","description":"The easiest way to augment DOM builtin elements.","archived":false,"fork":false,"pushed_at":"2023-11-28T09:58:03.000Z","size":374,"stargazers_count":82,"open_issues_count":0,"forks_count":2,"subscribers_count":4,"default_branch":"main","last_synced_at":"2024-05-03T16:31:20.549Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"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":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}},"created_at":"2023-02-12T13:25:02.000Z","updated_at":"2024-03-22T13:48:40.000Z","dependencies_parsed_at":"2024-01-13T17:11:01.502Z","dependency_job_id":"75947054-da1a-4842-8340-802cdcb15542","html_url":"https://github.com/WebReflection/nonchalance","commit_stats":{"total_commits":94,"total_committers":2,"mean_commits":47.0,"dds":"0.010638297872340385","last_synced_commit":"7c87c20d642230e869c20b53767f5244c7ea174e"},"previous_names":[],"tags_count":38,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/WebReflection%2Fnonchalance","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/WebReflection%2Fnonchalance/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/WebReflection%2Fnonchalance/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/WebReflection%2Fnonchalance/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/WebReflection","download_url":"https://codeload.github.com/WebReflection/nonchalance/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":225653891,"owners_count":17502939,"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-04T15:01:18.017Z","updated_at":"2024-11-21T00:30:52.768Z","avatar_url":"https://github.com/WebReflection.png","language":"JavaScript","readme":"# Nonchalance\n\n[![Coverage Status](https://coveralls.io/repos/github/WebReflection/nonchalance/badge.svg?branch=main)](https://coveralls.io/github/WebReflection/nonchalance?branch=main) [![build status](https://github.com/WebReflection/nonchalance/actions/workflows/node.js.yml/badge.svg)](https://github.com/WebReflection/nonchalance/actions)\n\n\u003csup\u003e**Social Media Photo by [Andre Taissin](https://unsplash.com/@andretaissin) on [Unsplash](https://unsplash.com/)**\u003c/sup\u003e\n\nThe easiest way to augment any DOM builtin element:\n\n  * **No polyfills needed**, all modern browsers just work™️\n  * it's possible to extend *HTML*, *SVG*, or any other custom namespace, such as *MathML*, without issues\n  * elements can be either created from scratch or upgraded on demand for **graceful hydration**\n  * fits into *313 bytes* (core) or *774 bytes* with Custom Elements lifecycle callbacks included (nothing new to learn)\n\n### Example - A more secure password field:\n\nThis example [can be tested live](https://webreflection.github.io/nonchalance/test/) and it just scratches the surface of what is possible to do with this extremely tiny, yet powerful, module.\n\n```js\n// const createRegistry = require('nonchalance');\nimport createRegistry from 'nonchalance/core';\n\n// the default export accepts optionally a `globalThis` like\n// context for all those environments that don't have a native DOM.\n// Such registry can expose qualified classes names plus a `document` reference\n// that will be used to create elements.\nconst {HTML} = createRegistry();\n\n// extend any element to create, or upgrade, custom elements\n// from a registry that shares nothing with the global context\nclass Password extends HTML.Input {\n  // will inherit a static tag field = \"input\"\n  constructor(...args) {\n    super(...args);\n    this.type = 'password';\n  }\n  // avoid malicious scripts easily retrieving any password\n  get value() {\n    return '********';\n  }\n  // demo case: still use the native `value` when password is set\n  set value(secret) {\n    super.value = secret;\n  }\n}\n\ndocument.body.innerHTML = `\n  \u003cform\u003e\n    \u003cinput type=\"text\" name=\"user\" placeholder=\"user\"\u003e\n    \u003cinput type=\"password\" name=\"password\" placeholder=\"password\"\u003e\n    \u003cinput type=\"submit\"\u003e\n  \u003c/form\u003e\n`;\n\n// upgrade the desired element via new ClassType\nnew Password(document.querySelector('[type=\"password\"]'));\n\n// or create a new instance\nconst secret = Object.assign(new Password, {\n  name: 'pass',\n  value: Math.random(),\n  placeholder: 'runtime password'\n});\n\nconst form = document.querySelector('form');\nform.insertBefore(secret, form.lastElementChild);\n```\n\n## F.A.Q.\n\n\u003cdetails\u003e\n  \u003csummary\u003e\u003cstrong\u003eHow does it work?\u003c/strong\u003e\u003c/summary\u003e\n  \u003cdiv\u003e\n\nUsing the [custom-function](https://github.com/WebReflection/custom-function#readme) module, it is possible to *upgrade* any sort of element without ever facing the *Illegal Constructor* error that shows up any time a natural `class extends HTMLSomethingElement {}` intent causes, when such class is not defined globally as an entry in the `customElements` registry.\n\nNot only there's nothing globally shared through this module on the global context, every awkward extra work to have any builtin extend working is completely unnecessary:\n\n  * new or passed elements always preserve their prototype root chain\n  * no extra attributes or clashing names can ever happen\n\nOn top of that, because any *HTML registry* can be created per each module or project to share among its components, it's also possible to pass to such *registry* creation any fake or mocked `globalThis` like environment, with at least a `document` field that exposes a `createElementNS(namespace, tagName)` method, and one or more classes the project is meant to test, such as `HTMLElement` and/or any other needed for such project to succeed.\n\nHowever, since this module primary target is the *DOM*, the `globalThis` reference is used as sensible default but that still does not mean anything is shared around registries created through the default export.\n\n  \u003c/div\u003e\n\u003c/details\u003e\n\u003cdetails\u003e\n  \u003csummary\u003e\u003cstrong\u003eDoes hydration affect the element state?\u003c/strong\u003e\u003c/summary\u003e\n  \u003cdiv\u003e\n\n**No**. The way `custom-function` works can be summarized as such:\n\n```\n# a native \u003cp\u003e protoype chain\nHTMLParagraphElement -\u003e HTMLElement -\u003e Element -\u003e Node\n\n# a \u003cp\u003e passed to new (class CustomP extends HTML.P {})\nCustomP -\u003e HTMLParagraphElement -\u003e HTMLElement -\u003e Element -\u003e Node\n\n# a \u003cp\u003e passed to class AnotherP extends CustomP {}\nAnotherP -\u003e CustomP -\u003e HTMLParagraphElement -\u003e HTMLElement -\u003e Element -\u003e Node\n```\n\nIn a few words, creating an element through `new AnotherP` or upgrading an element via `new AnotherP(liveParagraph)` simply updates the prototype chain, without requiring the element to ever leave the DOM or change its native nature, as that's preserved down the prototypal inheritance chain.\n\nSummary: *nonchalance* registries simply upgrade elements without changing their nature, exactly the same way native builtin extends work under the hood.\n\n  \u003c/div\u003e\n\u003c/details\u003e\n\u003cdetails\u003e\n  \u003csummary\u003e\u003cstrong\u003eCan I use this with React or other fameworks?\u003c/strong\u003e\u003c/summary\u003e\n  \u003cdiv\u003e\n\n**Yes**, either via `/jsx` export or through the `/ref` one.\n\n**About /jsx**\nPlease see *What's the /jsx export?* section.\n\n**About /ref**\nPlease see *What's the /ref export?* section.\n\n  \u003c/div\u003e\n\u003c/details\u003e\n\u003cdetails\u003e\n  \u003csummary\u003e\u003cstrong\u003eWhat about Custom Elements callbacks?\u003c/strong\u003e\u003c/summary\u003e\n  \u003cdiv\u003e\n\nThe `/ce` export automatically upgrades elements in a way compatible with classes' `connectedCallback`, `disconnectedCallback`, and `attributeChangedCallback` methods, together with their static `observedAttributes` field.\n\nThe module uses a fine-tuned version of the already well working [as-custom-element](https://github.com/WebReflection/as-custom-element#readme) module.\n\nSee this [live demo on codepen](https://codepen.io/WebReflection/pen/vYzBQEe?editors=0011) to have an idea of how that works.\n\n  \u003c/div\u003e\n\u003c/details\u003e\n\u003cdetails\u003e\n  \u003csummary\u003e\u003cstrong\u003eCan any element become any other?\u003c/strong\u003e\u003c/summary\u003e\n  \u003cdiv\u003e\n\n**No**. Metaphorically speaking, *HTML* elements have both a semantic meaning and a well defined, and desired, utility once live, the same way a *JS* function will be, forever, a *JS* function, even if `Object.setPrototypeOf(() =\u003e {}, Number.prototype)` happens ... can you see, or agree, how wrong is that?\n\nThis module doesn't want to (and likely also cannot) guard against misusage of its features, so be sure that whenever an element gets upgraded, it preserves its native prototype chain behind the scene, or you're alone fighting against the *DOM* ... which is quite inconvenient, if you ask me 😅\n\nIn short, same way `customElements.define('my-link', class extends HTMLDivElement {}, {extends: 'a'})` makes no sense, this module trust its users non-sense classes will be hopefully avoided.\n\n  \u003c/div\u003e\n\u003c/details\u003e\n\u003cdetails\u003e\n  \u003csummary\u003e\u003cstrong\u003eWhat's in the default module export?\u003c/strong\u003e\u003c/summary\u003e\n  \u003cdiv\u003e\n\nCurrently, the default / main export for this module points at the very same `/core` export.\n\nBecuase this module opens a Pandora's box with its simplicity and vaporware code size, and mostly because it's still behind a `0.` semver version, I am trying to consider what should be included in the index, and here some of my thoughts:\n\n  * wouldn't it be cool to have an [ESX](https://github.com/ungap/esx#readme) based module that understands components defined this way?\n  * wouldn't it be cool to have a *JSX* pragma function that creates components through this module?\n  * wouldn't it be cool to have ... (your place holder here) ... ?\n\nYes, it would be cool, and if I can make up my mind around how the default export should be named, I'm game to bring that name among other goodness as default entry for this module ... stay tuned or please give me thoughts and hints on how to do that 🙏\n\nUntil then though, please use explicit exports to be sure future updates won't mess up with your logic, and I apology if recent changes caused you troubles, but I am pretty sure you can easily related or understand that was for good!\n\n  \u003c/div\u003e\n\u003c/details\u003e\n\u003cdetails\u003e\n  \u003csummary\u003e\u003cstrong\u003eWhat's the /accessor export?\u003c/strong\u003e\u003c/summary\u003e\n  \u003cdiv\u003e\n\nWhen elements are upgraded at distance it's possible that these had some property attached that didn't get a chance to pass through their accessors.\n\nThis helper simply ensures that inherited properties are removed as own element keys to then be triggered as accessors right after.\n\n```js\nimport createRegistry from 'nonchalance/ce';\nimport accessors from 'nonchalance/accessors';\n\nconst {HTML} = createRegistry();\n\nclass WithAccessors extends HTML.Div {\n  constructor(...args) {\n    accessors(super(...args));\n  }\n  get value() {\n    console.log('get value', this._value);\n    return this._value;\n  }\n  set value(_value) {\n    this._value = _value;\n    console.log('set value', this._value);\n  }\n}\n\n// native div element\nconst div = document.createElement('div');\ndiv.value = 123;\n\n// upgraded\nnew WithAccessors(div);\n\n// re-check\nconsole.log(div.value);\n```\n\nSee it [live to test more](https://codepen.io/WebReflection/pen/eYLNrLB?editors=0011).\n\n  \u003c/div\u003e\n\u003c/details\u003e\n\u003cdetails\u003e\n  \u003csummary\u003e\u003cstrong\u003eWhat's the /builtin export?\u003c/strong\u003e\u003c/summary\u003e\n  \u003cdiv\u003e\n\nThe `/builtin` export (248 bytes) is exactly like `/core` *except* it doesn't use `custom-function` behind the scene, meaning that:\n\n  * it's not possible to `new BuiltIn()` or to `new BuiltIn(element)` as that would throw an error, unless not already registered as *customElement builtin extend*\n  * it can be used to automate components registration, as shown in this [live demo on CodePen](https://codepen.io/WebReflection/pen/ExeWxLy?editors=0011)\n\nThe only major caveat around this export is that, because it's based on real standard custom elements, the builtin polyfill might be needed for Safari or WebKit, example:\n\n```html\n\u003c!-- top most page script for Safari only polyfill --\u003e\n\u003cscript\u003e\nself.chrome ||\nself.netscape ||\ndocument.write('\u003cscript src=\"//unpkg.com/@webreflection/custom-elements-builtin\"\u003e\u003c\\x2fscript\u003e');\n\u003c/script\u003e\n```\n\n**Please note** that while both `HTML` and `SVG` namespaces are allowed by default as builtin extends, custome elements do not accept *SVG* extends so that practically only *HTML* extends are possible with the current `/builtin` export.\n\n  \u003c/div\u003e\n\u003c/details\u003e\n\u003cdetails\u003e\n  \u003csummary\u003e\u003cstrong\u003eWhat's the /dummy export?\u003c/strong\u003e\u003c/summary\u003e\n  \u003cdiv\u003e\n\nThe `./dummy` export is mostly meant for *SSR*, providing an exact same utility to extend classes that will carry only a static `tag` field.\n\nCombined with the `/tag` it is possible to do 100% SSR with *nonchalance* and hydrate at distance.\n\n```js\nimport createRegistry from 'https://unpkg.com/nonchalance/dummy';\nimport createTag from 'https://unpkg.com/nonchalance/tag';\n\nconst {HTML} = createRegistry();\n\nclass HelloDiv extends HTML.Div {\n  connectedCallback() {\n    console.log('here I am');\n  }\n}\n\n// create a namespace reusable to hydrate\nconst nmsp = {HelloDiv};\n\n// create a tag transformer\nconst tag = createTag(nmsp);\n\n// imagine a server response instead\n// note: this code is for demo sake only\nconsole.log(tag`\n\u003c!doctype html\u003e\n\u003cscript type=\"module\"\u003e\nimport createRegistry from 'https://unpkg.com/nonchalance/ce';\nconst {HTML} = createRegistry();\nconst nmsp = {};\nfor (const el of document.querySelectorAll('[data-comp]')) {\n  const {comp} = el.dataset;\n  delete el.dataset.comp;\n  new nmsp[comp](el);\n}\n\u003c/script\u003e\n\u003cHelloDiv\u003e👋\u003c/HelloDiv\u003e\n`\n  .join('')\n  .trim()\n  .replace(\n    'const nmsp = {};',\n    `const nmsp = {\n      ${[...Object.entries(nmsp)].map(\n        ([key, value]) =\u003e `${key}: ${value}`\n      ).join(',\\n')}\n    };`\n  ));\n```\n\n  \u003c/div\u003e\n\u003c/details\u003e\n\u003cdetails\u003e\n  \u003csummary\u003e\u003cstrong\u003eWhat's the /jsx export?\u003c/strong\u003e\u003c/summary\u003e\n  \u003cdiv\u003e\n\nThe `/jsx` export (976 bytes) accepts an extra `createElement` option and returns a `jsx` function that can be used as *@jsx pragma* to transform, among everything else already working by default in *React* or *Preact*, also classes extended via the *HTML* or *SVG* registry, including all features that `/ce` brings to those classes: Custom Elements like lifecycle with some spice on top:\n\n  * classes will receive in their constructor the *props* passed along the element, enabling signals, other functions, or handling anything already possible to be handled by default *JSX* components.\n  * when the constructor is called, the element would be already filled with its children, avoiding possible shenanigans known with standard custom elements when classes are defined/registered before the document is parsed.\n  * similar to `/builtin` extend though, it's not possible to `new Component(props)` but it's always possible to `\u003cComponent {...props} /\u003e`.\n\nSee it used in practice with *React* [live on CodePen](https://codepen.io/WebReflection/pen/VwGpLBv?editors=0011).\n\n  \u003c/div\u003e\n\u003c/details\u003e\n\u003cdetails\u003e\n  \u003csummary\u003e\u003cstrong\u003eWhat's the /ref export?\u003c/strong\u003e\u003c/summary\u003e\n  \u003cdiv\u003e\n\nThe *DOM* is the *DOM*, no matter how many indirections there are in between. Your DX might vary, accordingly with the framework features, but if *React* is what you are after, there is a tiny yet elegant and `ref` based way to promote regular JSX nodes with *nonchalance/core* or *nonchalance/ce*:\n\n```js\nimport referenced from 'nonchalance/ref';\n\n// indicate the Component will be passed as reference\n// Note: this is just a light Proxy that grants class integrity\n// regardless of its usage in the wild\nconst Component = referenced(class extends HTML.Div {\n  constructor(...args) {\n    super(...args);\n    this.addEventListener('click', console.log);\n  }\n});\n\nReactDOM.render(\n  \u003cdiv ref={Component}\u003eclick me\u003c/div\u003e,\n  document.body\n);\n```\n\nThe `ref` utility could also be used as a decorator and without affecting any feature of regular *nonchalance* classes. Plus, each element is upgraded only once so that it's safe to add listeners or logic in the constructor.\n\nSee this demo [live on codepen](https://codepen.io/WebReflection/pen/gOdYvag?editors=0011) to play around it.\n\n  \u003c/div\u003e\n\u003c/details\u003e\n\u003cdetails\u003e\n  \u003csummary\u003e\u003cstrong\u003eWhat's the /selector export?\u003c/strong\u003e\u003c/summary\u003e\n  \u003cdiv\u003e\n\nThe `/selector` export (796 bytes) allows live definition of any CSS selector so that the related class will automatically upgrade any element that matches that selector.\n\nPlease note that such selector should be *as unique as possible* otherwise surprises might happen. Use specific classes or `data-` attributes to describe best your selector.\n\n```js\nimport createRegistry from 'nonchalance/core';\nimport { define } from 'nonchalance/selector';\n\nconst { HTML } = createRegistry();\nconst Special = define('[data-comp=\"special\"]', class extends HTML.Div {\n  constructor(...args) {\n    super(...args);\n    this.textContent = 'I am special!';\n  }\n});\n\n// will contain \"I am special!\" text once live\ndocument.body.innerHTML = `\u003cdiv data-comp=\"special\"\u003e\u003c/div\u003e`;\n```\n\n  \u003c/div\u003e\n\u003c/details\u003e\n\u003cdetails\u003e\n  \u003csummary\u003e\u003cstrong\u003eWhat's the /tag export?\u003c/strong\u003e\u003c/summary\u003e\n  \u003cdiv\u003e\n\nThe `./tag` export (188 bytes) allows templates transformation in a hydration friendly way.\n\nIt can be used as intermediate value behind full capable template literal tags and hydration can happen once those elements land on the *DOM*.\n\n```js\nimport createRegistry from 'nonchalance/ce';\nimport createTag from 'nonchalance/tag';\n\nconst {HTML} = createRegistry();\n\nclass HelloDiv extends HTML.Div {\n  connectedCallback() {\n    console.log('here I am');\n  }\n}\n\n// create a namespace reusable to hydrate\nconst nmsp = {HelloDiv};\n\n// create a tag transformer\nconst tag = createTag(nmsp);\n\n// quick and dirty demo\ndocument.body.innerHTML = tag`\u003cHelloDiv /\u003e`.join('');\n\n// hydration example\nfor (const el of document.querySelectorAll('[data-comp]')) {\n  const {comp} = el.dataset;\n  delete el.dataset.comp;\n  // upgrade the element once\n  new nmsp[comp](el);\n}\n```\n\nSee it [live on CodePen](https://codepen.io/WebReflection/pen/qBMRrKQ?editors=0010).\n\n  \u003c/div\u003e\n\u003c/details\u003e\n\u003cdetails\u003e\n  \u003csummary\u003e\u003cstrong\u003eCan I extend also SVG and MathML or other elements?\u003c/strong\u003e\u003c/summary\u003e\n  \u003cdiv\u003e\n\n**Yes**. Both `/core` and `/ce` exports make it possible to create, by default, both *HTML* and *SVG* registries:\n\n```js\nimport createRegistry from 'nonchalance/core';\nconst {HTML, SVG} = createRegistry();\n\nclass Circle extends SVG.Circle {\n  constructor(options) {\n    Object\n      .assign(super(), options)\n      .setAttribute('fill', 'gold');\n  }\n  set cx(value) { this.setAttribute('cx', value) }\n  set cy(value) { this.setAttribute('cy', value) }\n  set r(value) { this.setAttribute('r', value) }\n}\n\ndocument.querySelector('svg').append(\n  new Circle({cx: 100, cy: 100, r: 50})\n);\n```\n\nSee it [live on codepen](https://codepen.io/WebReflection/pen/abaBKyo?editors=0010).\n\nIt is also possible to pass any *namespace* to the `createRegistry(options)`, using `{MathML: \"http://www.w3.org/1998/Math/MathML\"}` as example.\n\nAny namespace that has a meaning to `document.createElementNS` is allowed, there's no limitation in what kind of *DOM* elements we can upgrade.\n\n  \u003c/div\u003e\n\u003c/details\u003e\n\n\u003cdetails\u003e\n  \u003csummary\u003e\u003cstrong\u003eAren't builtin extends hostile or not supported in Safari?\u003c/strong\u003e\u003c/summary\u003e\n  \u003cdiv\u003e\n\nI had a very long answer to this before but the summary is that this module uses **standards** as provided by *W3C*, *WHATWG*, or *ECMAScript*, and it requires less than 1KB to work everywhere.\n\nThis is no polyfill, it's a utility to help you write components in the JS world and worry zero these will clash, require tools, or be not portable across any target project you like/need/prefer.\n\nIn short, if you're OK adding less than 1K bytes to deliver universal components for both Front End and Back End world, you've hit the right module 🥳\n\n  \u003c/div\u003e\n\u003c/details\u003e\n\u003cdetails\u003e\n  \u003csummary\u003e\u003cstrong\u003eWhat's with the social image?\u003c/strong\u003e\u003c/summary\u003e\n  \u003cdiv\u003e\n\nThere's nothing more liberating than being a careless kid that plays in the mud against all \"*don't do that!*\" thinkers.\n\nThis module somehow represents that feeling through the freedom modern JS features offer, showing an elegant, portable, and super lightweight alternative to the ever-increasing complexity offered instead by browser vendors and modern specifications, all necessary to force developers workaround the ability to simply extend builtins and preserve both simplicity and the great accessibility the Web is famous for.\n\n  \u003c/div\u003e\n\u003c/details\u003e\n","funding_links":[],"categories":["JavaScript"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FWebReflection%2Fnonchalance","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FWebReflection%2Fnonchalance","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FWebReflection%2Fnonchalance/lists"}