custom-attributes-polyfill
v0.0.1
Published
A polyfill for Custom Attributes (`customAttributes.define()` and `Attr` subclasses)
Readme
Custom Attributes Polyfill
This polyfills Custom Attributes. While this proposal's specification and web platform tests are pending, this polyfill aims to implement what will be specified.
The polyfill will reach 1.0 when the specification has been written and
merged. Until then expect breaking changes.
This polyfill works by overriding Attr, allowing sub classing, and introducing
the CustomAttributeRegistry object. Attrs registered with
customAttributes.define() will be upgraded in place with the same lifecycle
callbacks as custom elements.
Installation
With npm
import "custom-attributes-polyfill";This will automatically apply the polyfill if required.
To apply the polyfill manually, import the isSupported and apply functions
from ./custom-attributes.js, which is mapped to /fn:
import { isSupported, apply } from "custom-attributes-polyfill/fn";
if (!isSupported()) apply();An isPolyfilled function is also available:
import {
isSupported,
isPolyfilled,
apply,
} from "custom-attributes-polyfill/fn";
if (!isSupported() && !isPolyfilled()) apply();Without a package manager, use the unpkg script:
<script
type="module"
async
src="https://unpkg.com/custom-attributes-polyfill@latest/custom-attributes.min.js"
></script>Usage
class PersistValue extends Attr {
connectedCallback() {
const stored = localStorage.getItem(this.value);
if (stored !== null) this.ownerElement.value = stored;
this.ownerElement.addEventListener("input", this);
}
disconnectedCallback() {
this.ownerElement.removeEventListener("input", this);
}
attributeChangedCallback(oldValue, newValue) {
if (oldValue !== null) localStorage.removeItem(oldValue);
}
handleEvent() {
localStorage.setItem(this.value, this.ownerElement.value);
}
}
customAttributes.define("persist-value", PersistValue);<input name="email" persist-value="email-draft" />el.getAttributeNode("persist-value") and el.attributes["persist-value"]
return the PersistValue instance.
Polyfilled surface
window.customAttributeswhich is aCustomAttributeRegistryCustomAttributeRegistrymethodsdefine,get,getName,whenDefined,upgrade,initialize.- The
Attrconstructor:new MyAttr()for defined classes,new Attr()throws. document.createAttribute()constructs defined attributes synchronously.customAttributeRegistryonElement,DocumentandShadowRoot, and thecustomAttributeRegistryoption oncreateElement(),createElementNS(),importNode(),attachShadow(),setHTML()andsetHTMLUnsafe().connectedCallback,disconnectedCallback,connectedMoveCallback,adoptedCallbackandattributeChangedCallback(oldValue, newValue).
Limitations
The polyfill observes the DOM with a MutationObserver, so it cannot match the
specification exactly:
- Reactions run at the next microtask checkpoint rather than synchronously at
the end of the DOM operation.
define(),upgrade(),initialize()andcreateAttribute()are synchronous. - An element removed and re-inserted within the same task keeps its custom
attributes connected; no
disconnectedCallback/connectedCallbackpair fires.moveBefore()firesconnectedMoveCallbacksynchronously. - Only
document, shadow roots, and documents passed toinitialize()are observed. Attributes upgraded elsewhere (for example inside an<iframe>) do not receive callbacks until they are adopted into an observed document. - An element's registry is fixed the first time the polyfill needs it rather
than at creation.
createElement(),importNode(),attachShadow(),setHTMLUnsafe()andinitialize()pin it immediately;cloneNode()does not copy it. - Attributes that fail to construct keep the prototype
super()gave them, as native custom elements do, but never receive callbacks.
