diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 642896c..927f363 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -8,9 +8,7 @@ jobs: strategy: matrix: - # TODO get tests working in Windows too - # windows-latest - operating-system: [ubuntu-latest, macos-latest] + operating-system: [ubuntu-latest, windows-latest, macos-latest] steps: - uses: actions/checkout@v1 @@ -18,9 +16,23 @@ jobs: uses: actions/setup-node@v3 with: node-version: latest - - name: npm install, build, and test + - name: install run: | npm i + - name: check formatting + run: | + npm run prettier:check + - name: build + run: | + npm run clean + npm run build + - name: test + run: | npm test + - name: check repo is clean + # skip this check in windows for now, as the build outputs may get slightly modified in Windows, which we want to fix. + if: runner.os != 'Windows' + run: | + git add . && git diff --quiet && git diff --cached --quiet env: CI: true diff --git a/README.md b/README.md index eb13978..e061721 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[![Build Status](https://travis-ci.org/matthewp/custom-attributes.svg?branch=master)](https://travis-ci.org/matthewp/custom-attributes) +[![Build status](https://github.com/lume/custom-attributes/actions/workflows/tests.yml/badge.svg)](https://github.com/lume/custom-attributes/actions/workflows/tests.yml) [![npm version](https://badge.fury.io/js/custom-attributes.svg)](http://badge.fury.io/js/custom-attributes) # custom-attributes diff --git a/dist/CustomAttributeRegistry.d.ts b/dist/CustomAttributeRegistry.d.ts index 884e61b..a41f936 100644 --- a/dist/CustomAttributeRegistry.d.ts +++ b/dist/CustomAttributeRegistry.d.ts @@ -1,20 +1,10 @@ -import type { Constructor } from 'lowclass'; +import type { Constructor } from 'lowclass/dist/Constructor.js'; export declare class CustomAttributeRegistry { + #private; ownerDocument: Document | ShadowRoot; - private _attrMap; - private _elementMap; - private _observer; constructor(ownerDocument: Document | ShadowRoot); define(attrName: string, Class: Constructor): void; get(element: Element, attrName: string): CustomAttribute | undefined; - private _getConstructor; - private _observe; - private _unobserve; - private _reobserve; - private _upgradeAttr; - private _elementConnected; - private _elementDisconnected; - private _handleChange; } export interface CustomAttribute { ownerElement: Element; diff --git a/dist/CustomAttributeRegistry.d.ts.map b/dist/CustomAttributeRegistry.d.ts.map index 7139a91..f52c6d1 100644 --- a/dist/CustomAttributeRegistry.d.ts.map +++ b/dist/CustomAttributeRegistry.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"CustomAttributeRegistry.d.ts","sourceRoot":"","sources":["../src/CustomAttributeRegistry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,UAAU,CAAA;AAIzC,qBAAa,uBAAuB;IAmBhB,aAAa,EAAE,QAAQ,GAAG,UAAU;IAlBvD,OAAO,CAAC,QAAQ,CAAiC;IACjD,OAAO,CAAC,WAAW,CAAuD;IAE1E,OAAO,CAAC,SAAS,CAaf;gBAEiB,aAAa,EAAE,QAAQ,GAAG,UAAU;IAIvD,MAAM,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,WAAW;IAM3C,GAAG,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM;IAMtC,OAAO,CAAC,eAAe;IAIvB,OAAO,CAAC,QAAQ;IAYhB,OAAO,CAAC,UAAU;IAIlB,OAAO,CAAC,UAAU;IAKlB,OAAO,CAAC,YAAY;IAQpB,OAAO,CAAC,iBAAiB,CAWxB;IAED,OAAO,CAAC,oBAAoB,CAO3B;IAED,OAAO,CAAC,aAAa;CAiCrB;AAGD,MAAM,WAAW,eAAe;IAC/B,YAAY,EAAE,OAAO,CAAA;IACrB,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,MAAM,CAAA;IACb,iBAAiB,CAAC,IAAI,IAAI,CAAA;IAC1B,oBAAoB,CAAC,IAAI,IAAI,CAAA;IAC7B,eAAe,CAAC,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;CAC1D"} \ No newline at end of file +{"version":3,"file":"CustomAttributeRegistry.d.ts","sourceRoot":"","sources":["../src/CustomAttributeRegistry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,8BAA8B,CAAA;AAI7D,qBAAa,uBAAuB;;IAmBhB,aAAa,EAAE,QAAQ,GAAG,UAAU;gBAApC,aAAa,EAAE,QAAQ,GAAG,UAAU;IAIvD,MAAM,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,WAAW;IAM3C,GAAG,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM;CA8FtC;AAGD,MAAM,WAAW,eAAe;IAC/B,YAAY,EAAE,OAAO,CAAA;IACrB,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,MAAM,CAAA;IACb,iBAAiB,CAAC,IAAI,IAAI,CAAA;IAC1B,oBAAoB,CAAC,IAAI,IAAI,CAAA;IAC7B,eAAe,CAAC,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;CAC1D"} \ No newline at end of file diff --git a/dist/CustomAttributeRegistry.js b/dist/CustomAttributeRegistry.js index 04194f2..af4033d 100644 --- a/dist/CustomAttributeRegistry.js +++ b/dist/CustomAttributeRegistry.js @@ -1,85 +1,93 @@ -var _a; const forEach = Array.prototype.forEach; export class CustomAttributeRegistry { + ownerDocument; + #attrMap = new Map(); + #elementMap = new WeakMap(); + #observer = new MutationObserver(mutations => { + forEach.call(mutations, (m) => { + if (m.type === 'attributes') { + const attr = this.#getConstructor(m.attributeName); + if (attr) + this.#handleChange(m.attributeName, m.target, m.oldValue); + } + // chlidList + else { + forEach.call(m.removedNodes, this.#elementDisconnected); + forEach.call(m.addedNodes, this.#elementConnected); + } + }); + }); constructor(ownerDocument) { this.ownerDocument = ownerDocument; - this._attrMap = new Map(); - this._elementMap = new WeakMap(); - this._observer = new MutationObserver(mutations => { - forEach.call(mutations, (m) => { - if (m.type === 'attributes') { - const attr = this._getConstructor(m.attributeName); - if (attr) - this._handleChange(m.attributeName, m.target, m.oldValue); - } - else { - forEach.call(m.removedNodes, this._elementDisconnected); - forEach.call(m.addedNodes, this._elementConnected); - } - }); - }); - this._elementConnected = (element) => { - if (element.nodeType !== 1) - return; - forEach.call(element.attributes, (attr) => { - if (this._getConstructor(attr.name)) - this._handleChange(attr.name, element, null); - }); - this._attrMap.forEach((_constructor, attr) => this._upgradeAttr(attr, element)); - }; - this._elementDisconnected = (element) => { - const map = this._elementMap.get(element); - if (!map) - return; - map.forEach(inst => { var _a; return (_a = inst.disconnectedCallback) === null || _a === void 0 ? void 0 : _a.call(inst); }, this); - this._elementMap.delete(element); - }; if (!ownerDocument) throw new Error('Must be given a document'); } define(attrName, Class) { - this._attrMap.set(attrName, Class); - this._upgradeAttr(attrName); - this._reobserve(); + this.#attrMap.set(attrName, Class); + this.#upgradeAttr(attrName); + this.#reobserve(); } get(element, attrName) { - const map = this._elementMap.get(element); + const map = this.#elementMap.get(element); if (!map) return; return map.get(attrName); } - _getConstructor(attrName) { - return this._attrMap.get(attrName); + #getConstructor(attrName) { + return this.#attrMap.get(attrName); } - _observe() { - this._observer.observe(this.ownerDocument, { + #observe() { + this.#observer.observe(this.ownerDocument, { childList: true, subtree: true, attributes: true, attributeOldValue: true, - attributeFilter: Array.from(this._attrMap.keys()), + attributeFilter: Array.from(this.#attrMap.keys()), + // attributeFilter: [...this.#attrMap.keys()], // Broken in Oculus + // attributeFilter: this.#attrMap.keys(), // This works in Chrome, but TS complains, and not clear if it should work in all browsers yet: https://github.com/whatwg/dom/issues/1092 }); } - _unobserve() { - this._observer.disconnect(); + #unobserve() { + this.#observer.disconnect(); } - _reobserve() { - this._unobserve(); - this._observe(); + #reobserve() { + this.#unobserve(); + this.#observe(); } - _upgradeAttr(attrName, node = this.ownerDocument) { + #upgradeAttr(attrName, node = this.ownerDocument) { const matches = node.querySelectorAll('[' + attrName + ']'); - forEach.call(matches, (element) => this._handleChange(attrName, element, null)); + // Possibly create custom attributes that may be in the given 'node' tree. + // Use a forEach as Edge doesn't support for...of on a NodeList + forEach.call(matches, (element) => this.#handleChange(attrName, element, null)); } - _handleChange(attrName, el, oldVal) { - var _a, _b, _c; - let map = this._elementMap.get(el); + #elementConnected = (element) => { + if (element.nodeType !== 1) + return; + // For each of the connected element's attribute, possibly instantiate the custom attributes. + // Use a forEach as Safari 10 doesn't support for...of on NamedNodeMap (attributes) + forEach.call(element.attributes, (attr) => { + if (this.#getConstructor(attr.name)) + this.#handleChange(attr.name, element, null); + }); + // Possibly instantiate custom attributes that may be in the subtree of the connected element. + this.#attrMap.forEach((_constructor, attr) => this.#upgradeAttr(attr, element)); + }; + #elementDisconnected = (element) => { + const map = this.#elementMap.get(element); + if (!map) + return; + map.forEach(inst => inst.disconnectedCallback?.(), this); + this.#elementMap.delete(element); + }; + #handleChange(attrName, el, oldVal) { + let map = this.#elementMap.get(el); if (!map) - this._elementMap.set(el, (map = new Map())); + this.#elementMap.set(el, (map = new Map())); let inst = map.get(attrName); const newVal = el.getAttribute(attrName); + // Attribute is being created if (!inst) { - const Constructor = this._getConstructor(attrName); + const Constructor = this.#getConstructor(attrName); inst = new Constructor(); map.set(attrName, inst); inst.ownerElement = el; @@ -87,25 +95,28 @@ export class CustomAttributeRegistry { if (newVal == null) throw new Error('Not possible!'); inst.value = newVal; - (_a = inst.connectedCallback) === null || _a === void 0 ? void 0 : _a.call(inst); + inst.connectedCallback?.(); return; } + // Attribute was removed if (newVal == null) { - (_b = inst.disconnectedCallback) === null || _b === void 0 ? void 0 : _b.call(inst); + inst.disconnectedCallback?.(); map.delete(attrName); } + // Attribute changed else if (newVal !== inst.value) { inst.value = newVal; if (oldVal == null) throw new Error('Not possible!'); - (_c = inst.changedCallback) === null || _c === void 0 ? void 0 : _c.call(inst, oldVal, newVal); + inst.changedCallback?.(oldVal, newVal); } } } -if ((_a = globalThis.window) === null || _a === void 0 ? void 0 : _a.document) { - const _attachShadow = Element.prototype.attachShadow; +// Avoid errors trying to use DOM APIs in non-DOM environments (f.e. server-side rendering). +if (globalThis.window?.document) { + const original = Element.prototype.attachShadow; Element.prototype.attachShadow = function attachShadow(options) { - const root = _attachShadow.call(this, options); + const root = original.call(this, options); if (!root.customAttributes) root.customAttributes = new CustomAttributeRegistry(root); return root; diff --git a/dist/CustomAttributeRegistry.js.map b/dist/CustomAttributeRegistry.js.map index 3beace2..e450257 100644 --- a/dist/CustomAttributeRegistry.js.map +++ b/dist/CustomAttributeRegistry.js.map @@ -1 +1 @@ -{"version":3,"file":"CustomAttributeRegistry.js","sourceRoot":"","sources":["../src/CustomAttributeRegistry.ts"],"names":[],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"CustomAttributeRegistry.js","sourceRoot":"","sources":["../src/CustomAttributeRegistry.ts"],"names":[],"mappings":"AAEA,MAAM,OAAO,GAAG,KAAK,CAAC,SAAS,CAAC,OAAO,CAAA;AAEvC,MAAM,OAAO,uBAAuB;IAmBhB;IAlBnB,QAAQ,GAAG,IAAI,GAAG,EAAuB,CAAA;IACzC,WAAW,GAAG,IAAI,OAAO,EAAyC,CAAA;IAElE,SAAS,GAAqB,IAAI,gBAAgB,CAAC,SAAS,CAAC,EAAE;QAC9D,OAAO,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC,CAAiB,EAAE,EAAE;YAC7C,IAAI,CAAC,CAAC,IAAI,KAAK,YAAY,EAAE,CAAC;gBAC7B,MAAM,IAAI,GAAG,IAAI,CAAC,eAAe,CAAC,CAAC,CAAC,aAAc,CAAC,CAAA;gBACnD,IAAI,IAAI;oBAAE,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,aAAc,EAAE,CAAC,CAAC,MAAiB,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAA;YAChF,CAAC;YAED,YAAY;iBACP,CAAC;gBACL,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,YAAY,EAAE,IAAI,CAAC,oBAAoB,CAAC,CAAA;gBACvD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,UAAU,EAAE,IAAI,CAAC,iBAAiB,CAAC,CAAA;YACnD,CAAC;QACF,CAAC,CAAC,CAAA;IACH,CAAC,CAAC,CAAA;IAEF,YAAmB,aAAoC;QAApC,kBAAa,GAAb,aAAa,CAAuB;QACtD,IAAI,CAAC,aAAa;YAAE,MAAM,IAAI,KAAK,CAAC,0BAA0B,CAAC,CAAA;IAChE,CAAC;IAED,MAAM,CAAC,QAAgB,EAAE,KAAkB;QAC1C,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAA;QAClC,IAAI,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAA;QAC3B,IAAI,CAAC,UAAU,EAAE,CAAA;IAClB,CAAC;IAED,GAAG,CAAC,OAAgB,EAAE,QAAgB;QACrC,MAAM,GAAG,GAAG,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,OAAO,CAAC,CAAA;QACzC,IAAI,CAAC,GAAG;YAAE,OAAM;QAChB,OAAO,GAAG,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAA;IACzB,CAAC;IAED,eAAe,CAAC,QAAgB;QAC/B,OAAO,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAA;IACnC,CAAC;IAED,QAAQ;QACP,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,aAAa,EAAE;YAC1C,SAAS,EAAE,IAAI;YACf,OAAO,EAAE,IAAI;YACb,UAAU,EAAE,IAAI;YAChB,iBAAiB,EAAE,IAAI;YACvB,eAAe,EAAE,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC;YACjD,kEAAkE;YAClE,mLAAmL;SACnL,CAAC,CAAA;IACH,CAAC;IAED,UAAU;QACT,IAAI,CAAC,SAAS,CAAC,UAAU,EAAE,CAAA;IAC5B,CAAC;IAED,UAAU;QACT,IAAI,CAAC,UAAU,EAAE,CAAA;QACjB,IAAI,CAAC,QAAQ,EAAE,CAAA;IAChB,CAAC;IAED,YAAY,CAAC,QAAgB,EAAE,OAAwC,IAAI,CAAC,aAAa;QACxF,MAAM,OAAO,GAAG,IAAI,CAAC,gBAAgB,CAAC,GAAG,GAAG,QAAQ,GAAG,GAAG,CAAC,CAAA;QAE3D,0EAA0E;QAC1E,+DAA+D;QAC/D,OAAO,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,OAAgB,EAAE,EAAE,CAAC,IAAI,CAAC,aAAa,CAAC,QAAQ,EAAE,OAAO,EAAE,IAAI,CAAC,CAAC,CAAA;IACzF,CAAC;IAED,iBAAiB,GAAG,CAAC,OAAgB,EAAE,EAAE;QACxC,IAAI,OAAO,CAAC,QAAQ,KAAK,CAAC;YAAE,OAAM;QAElC,6FAA6F;QAC7F,mFAAmF;QACnF,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,UAAU,EAAE,CAAC,IAAU,EAAE,EAAE;YAC/C,IAAI,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC;gBAAE,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,IAAI,EAAE,OAAO,EAAE,IAAI,CAAC,CAAA;QAClF,CAAC,CAAC,CAAA;QAEF,8FAA8F;QAC9F,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,YAAY,EAAE,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,YAAY,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,CAAA;IAChF,CAAC,CAAA;IAED,oBAAoB,GAAG,CAAC,OAAgB,EAAE,EAAE;QAC3C,MAAM,GAAG,GAAG,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,OAAO,CAAC,CAAA;QACzC,IAAI,CAAC,GAAG;YAAE,OAAM;QAEhB,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,oBAAoB,EAAE,EAAE,EAAE,IAAI,CAAC,CAAA;QAExD,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC,OAAO,CAAC,CAAA;IACjC,CAAC,CAAA;IAED,aAAa,CAAC,QAAgB,EAAE,EAAW,EAAE,MAAqB;QACjE,IAAI,GAAG,GAAG,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,EAAE,CAAC,CAAA;QAClC,IAAI,CAAC,GAAG;YAAE,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC,GAAG,GAAG,IAAI,GAAG,EAAE,CAAC,CAAC,CAAA;QAErD,IAAI,IAAI,GAAG,GAAG,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAA;QAC5B,MAAM,MAAM,GAAG,EAAE,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAA;QAExC,6BAA6B;QAC7B,IAAI,CAAC,IAAI,EAAE,CAAC;YACX,MAAM,WAAW,GAAG,IAAI,CAAC,eAAe,CAAC,QAAQ,CAAE,CAAA;YACnD,IAAI,GAAG,IAAI,WAAW,EAAqB,CAAA;YAC3C,GAAG,CAAC,GAAG,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAA;YACvB,IAAI,CAAC,YAAY,GAAG,EAAE,CAAA;YACtB,IAAI,CAAC,IAAI,GAAG,QAAQ,CAAA;YACpB,IAAI,MAAM,IAAI,IAAI;gBAAE,MAAM,IAAI,KAAK,CAAC,eAAe,CAAC,CAAA;YACpD,IAAI,CAAC,KAAK,GAAG,MAAM,CAAA;YACnB,IAAI,CAAC,iBAAiB,EAAE,EAAE,CAAA;YAC1B,OAAM;QACP,CAAC;QAED,wBAAwB;QACxB,IAAI,MAAM,IAAI,IAAI,EAAE,CAAC;YACpB,IAAI,CAAC,oBAAoB,EAAE,EAAE,CAAA;YAC7B,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAA;QACrB,CAAC;QAED,oBAAoB;aACf,IAAI,MAAM,KAAK,IAAI,CAAC,KAAK,EAAE,CAAC;YAChC,IAAI,CAAC,KAAK,GAAG,MAAM,CAAA;YACnB,IAAI,MAAM,IAAI,IAAI;gBAAE,MAAM,IAAI,KAAK,CAAC,eAAe,CAAC,CAAA;YACpD,IAAI,CAAC,eAAe,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;QACvC,CAAC;IACF,CAAC;CACD;AAYD,4FAA4F;AAC5F,IAAI,UAAU,CAAC,MAAM,EAAE,QAAQ,EAAE,CAAC;IACjC,MAAM,QAAQ,GAAG,OAAO,CAAC,SAAS,CAAC,YAAY,CAAA;IAE/C,OAAO,CAAC,SAAS,CAAC,YAAY,GAAG,SAAS,YAAY,CAAC,OAAO;QAC7D,MAAM,IAAI,GAAG,QAAQ,CAAC,IAAI,CAAC,IAAI,EAAE,OAAO,CAAC,CAAA;QAEzC,IAAI,CAAC,IAAI,CAAC,gBAAgB;YAAE,IAAI,CAAC,gBAAgB,GAAG,IAAI,uBAAuB,CAAC,IAAI,CAAC,CAAA;QAErF,OAAO,IAAI,CAAA;IACZ,CAAC,CAAA;AACF,CAAC"} \ No newline at end of file diff --git a/dist/index.d.ts b/dist/index.d.ts index 43449f0..afd94ab 100644 --- a/dist/index.d.ts +++ b/dist/index.d.ts @@ -10,5 +10,5 @@ declare global { customAttributes: CustomAttributeRegistry; } } -export declare const version = "0.2.0"; +export declare const version = "0.2.4"; //# sourceMappingURL=index.d.ts.map \ No newline at end of file diff --git a/dist/index.js b/dist/index.js index a783354..d2b1e36 100644 --- a/dist/index.js +++ b/dist/index.js @@ -1,8 +1,11 @@ -var _a; +// TODO We don't know when a ShadowRoot is no longer referenced, hence we cannot +// unobserve them. Verify that MOs are cleaned up once ShadowRoots are no longer +// referenced. import { CustomAttributeRegistry } from './CustomAttributeRegistry.js'; export * from './CustomAttributeRegistry.js'; export let customAttributes; -if ((_a = globalThis.window) === null || _a === void 0 ? void 0 : _a.document) +// Avoid errors trying to use DOM APIs in non-DOM environments (f.e. server-side rendering). +if (globalThis.window?.document) customAttributes = globalThis.customAttributes = new CustomAttributeRegistry(document); -export const version = '0.2.0'; +export const version = '0.2.4'; //# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/dist/index.js.map b/dist/index.js.map index 1ae9217..2a47348 100644 --- a/dist/index.js.map +++ b/dist/index.js.map @@ -1 +1 @@ -{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAIA,OAAO,EAAC,uBAAuB,EAAC,MAAM,8BAA8B,CAAA;AAEpE,cAAc,8BAA8B,CAAA;AAE5C,MAAM,CAAC,IAAI,gBAAyC,CAAA;AAGpD,IAAI,MAAA,UAAU,CAAC,MAAM,0CAAE,QAAQ;IAAE,gBAAgB,GAAG,UAAU,CAAC,gBAAgB,GAAG,IAAI,uBAAuB,CAAC,QAAQ,CAAC,CAAA;AAmBvH,MAAM,CAAC,MAAM,OAAO,GAAG,OAAO,CAAA"} \ No newline at end of file +{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,gFAAgF;AAChF,gFAAgF;AAChF,cAAc;AAEd,OAAO,EAAC,uBAAuB,EAAC,MAAM,8BAA8B,CAAA;AAEpE,cAAc,8BAA8B,CAAA;AAE5C,MAAM,CAAC,IAAI,gBAAyC,CAAA;AAEpD,4FAA4F;AAC5F,IAAI,UAAU,CAAC,MAAM,EAAE,QAAQ;IAAE,gBAAgB,GAAG,UAAU,CAAC,gBAAgB,GAAG,IAAI,uBAAuB,CAAC,QAAQ,CAAC,CAAA;AAmBvH,MAAM,CAAC,MAAM,OAAO,GAAG,OAAO,CAAA"} \ No newline at end of file diff --git a/dist/test/change.test.d.ts b/dist/test/change.test.d.ts new file mode 100644 index 0000000..67a7576 --- /dev/null +++ b/dist/test/change.test.d.ts @@ -0,0 +1,2 @@ +import '../index.js'; +//# sourceMappingURL=change.test.d.ts.map \ No newline at end of file diff --git a/dist/test/change.test.d.ts.map b/dist/test/change.test.d.ts.map new file mode 100644 index 0000000..361162b --- /dev/null +++ b/dist/test/change.test.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"change.test.d.ts","sourceRoot":"","sources":["../../src/test/change.test.ts"],"names":[],"mappings":"AAAA,OAAO,aAAa,CAAA"} \ No newline at end of file diff --git a/dist/test/change.test.js b/dist/test/change.test.js new file mode 100644 index 0000000..ba7b8b5 --- /dev/null +++ b/dist/test/change.test.js @@ -0,0 +1,37 @@ +import '../index.js'; +describe('changedCallback()', function () { + async function later() { + await new Promise(r => setTimeout(r, 4)); + } + it('Is called when an attribute changes value', async function () { + const { resolve, promise: changedPromise } = Promise.withResolvers(); + class SomeAttr { + changedCallback() { + resolve(); + } + } + customAttributes.define('some-attr', SomeAttr); + const el = document.createElement('article'); + el.setAttribute('some-attr', 'foo'); + document.body.append(el); + queueMicrotask(() => el.setAttribute('some-attr', 'bar')); + await changedPromise; + el.remove(); + }); + it("Is not called when an attribute's value remains the same", async function () { + let count = 0; + class AnotherAttr { + changedCallback() { + count++; + } + } + customAttributes.define('another-attr', AnotherAttr); + const el = document.createElement('span'); + el.setAttribute('another-attr', 'foo'); + document.body.append(el); + el.setAttribute('another-attr', 'foo'); + await later(); + expect(count).toBe(0); + }); +}); +//# sourceMappingURL=change.test.js.map \ No newline at end of file diff --git a/dist/test/change.test.js.map b/dist/test/change.test.js.map new file mode 100644 index 0000000..4cc3d48 --- /dev/null +++ b/dist/test/change.test.js.map @@ -0,0 +1 @@ +{"version":3,"file":"change.test.js","sourceRoot":"","sources":["../../src/test/change.test.ts"],"names":[],"mappings":"AAAA,OAAO,aAAa,CAAA;AAEpB,QAAQ,CAAC,mBAAmB,EAAE;IAC7B,KAAK,UAAU,KAAK;QACnB,MAAM,IAAI,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,UAAU,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAA;IACzC,CAAC;IAED,EAAE,CAAC,2CAA2C,EAAE,KAAK;QACpD,MAAM,EAAC,OAAO,EAAE,OAAO,EAAE,cAAc,EAAC,GAAG,OAAO,CAAC,aAAa,EAAQ,CAAA;QAExE,MAAM,QAAQ;YACb,eAAe;gBACd,OAAO,EAAE,CAAA;YACV,CAAC;SACD;QAED,gBAAgB,CAAC,MAAM,CAAC,WAAW,EAAE,QAAQ,CAAC,CAAA;QAE9C,MAAM,EAAE,GAAG,QAAQ,CAAC,aAAa,CAAC,SAAS,CAAC,CAAA;QAC5C,EAAE,CAAC,YAAY,CAAC,WAAW,EAAE,KAAK,CAAC,CAAA;QACnC,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,CAAA;QAExB,cAAc,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,YAAY,CAAC,WAAW,EAAE,KAAK,CAAC,CAAC,CAAA;QAEzD,MAAM,cAAc,CAAA;QAEpB,EAAE,CAAC,MAAM,EAAE,CAAA;IACZ,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,0DAA0D,EAAE,KAAK;QACnE,IAAI,KAAK,GAAG,CAAC,CAAA;QAEb,MAAM,WAAW;YAChB,eAAe;gBACd,KAAK,EAAE,CAAA;YACR,CAAC;SACD;QAED,gBAAgB,CAAC,MAAM,CAAC,cAAc,EAAE,WAAW,CAAC,CAAA;QAEpD,MAAM,EAAE,GAAG,QAAQ,CAAC,aAAa,CAAC,MAAM,CAAC,CAAA;QACzC,EAAE,CAAC,YAAY,CAAC,cAAc,EAAE,KAAK,CAAC,CAAA;QACtC,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,CAAA;QAExB,EAAE,CAAC,YAAY,CAAC,cAAc,EAAE,KAAK,CAAC,CAAA;QAEtC,MAAM,KAAK,EAAE,CAAA;QAEb,MAAM,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;IACtB,CAAC,CAAC,CAAA;AACH,CAAC,CAAC,CAAA"} \ No newline at end of file diff --git a/dist/test/connect.test.d.ts b/dist/test/connect.test.d.ts new file mode 100644 index 0000000..dc81e37 --- /dev/null +++ b/dist/test/connect.test.d.ts @@ -0,0 +1,5 @@ +import '../index.js'; +declare global { + function expect(...args: any[]): any; +} +//# sourceMappingURL=connect.test.d.ts.map \ No newline at end of file diff --git a/dist/test/connect.test.d.ts.map b/dist/test/connect.test.d.ts.map new file mode 100644 index 0000000..3be0049 --- /dev/null +++ b/dist/test/connect.test.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"connect.test.d.ts","sourceRoot":"","sources":["../../src/test/connect.test.ts"],"names":[],"mappings":"AAAA,OAAO,aAAa,CAAA;AAEpB,OAAO,CAAC,MAAM,CAAC;IACd,SAAS,MAAM,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,GAAG,GAAG,CAAA;CACpC"} \ No newline at end of file diff --git a/dist/test/connect.test.js b/dist/test/connect.test.js new file mode 100644 index 0000000..b708d64 --- /dev/null +++ b/dist/test/connect.test.js @@ -0,0 +1,47 @@ +import '../index.js'; +class BgColorAttr { + connectedCallback() { + this.setColor(); + } + changedCallback(_oldValue, _newValue) { + this.setColor(); + } + setColor() { + const color = this.value || ''; + this.ownerElement.style.backgroundColor = color; + } +} +document.body.insertAdjacentHTML('beforeend', +/*html*/ ` +
+

This article is static

+
+`); +customAttributes.define('bg-color', BgColorAttr); +describe('connectedCallback()', function () { + it('Is called when element is already in the DOM', function () { + const article = document.querySelector('article'); + expect(article.style.backgroundColor).toBe('red'); + article.remove(); + }); + it('Is called when an attribute is dynamically created', async function () { + const article = document.createElement('article'); + article.textContent = 'hello world'; + document.body.append(article); + article.setAttribute('bg-color', 'blue'); + await new Promise(r => setTimeout(r, 4)); + expect(article.style.backgroundColor).toBe('blue'); + article.remove(); + }); + it('Is called on nested elements when root element is added to the DOM', async function () { + const article = document.createElement('article'); + const header = document.createElement('header'); + header.setAttribute('bg-color', 'blue'); + article.append(header); + document.body.append(article); + await new Promise(r => setTimeout(r, 4)); + expect(header.style.backgroundColor).toBe('blue'); + article.remove(); + }); +}); +//# sourceMappingURL=connect.test.js.map \ No newline at end of file diff --git a/dist/test/connect.test.js.map b/dist/test/connect.test.js.map new file mode 100644 index 0000000..df26525 --- /dev/null +++ b/dist/test/connect.test.js.map @@ -0,0 +1 @@ +{"version":3,"file":"connect.test.js","sourceRoot":"","sources":["../../src/test/connect.test.ts"],"names":[],"mappings":"AAAA,OAAO,aAAa,CAAA;AAMpB,MAAM,WAAW;IAIhB,iBAAiB;QAChB,IAAI,CAAC,QAAQ,EAAE,CAAA;IAChB,CAAC;IAED,eAAe,CAAC,SAAc,EAAE,SAAc;QAC7C,IAAI,CAAC,QAAQ,EAAE,CAAA;IAChB,CAAC;IAED,QAAQ;QACP,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,IAAI,EAAE,CAAA;QAC9B,IAAI,CAAC,YAAY,CAAC,KAAK,CAAC,eAAe,GAAG,KAAK,CAAA;IAChD,CAAC;CACD;AAED,QAAQ,CAAC,IAAI,CAAC,kBAAkB,CAC/B,WAAW;AACX,QAAQ,CAAC;;;;CAIT,CACA,CAAA;AAED,gBAAgB,CAAC,MAAM,CAAC,UAAU,EAAE,WAAW,CAAC,CAAA;AAEhD,QAAQ,CAAC,qBAAqB,EAAE;IAC/B,EAAE,CAAC,8CAA8C,EAAE;QAClD,MAAM,OAAO,GAAG,QAAQ,CAAC,aAAa,CAAC,SAAS,CAAE,CAAA;QAClD,MAAM,CAAC,OAAQ,CAAC,KAAK,CAAC,eAAe,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;QAClD,OAAO,CAAC,MAAM,EAAE,CAAA;IACjB,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,oDAAoD,EAAE,KAAK;QAC7D,MAAM,OAAO,GAAG,QAAQ,CAAC,aAAa,CAAC,SAAS,CAAC,CAAA;QACjD,OAAO,CAAC,WAAW,GAAG,aAAa,CAAA;QACnC,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,CAAA;QAE7B,OAAO,CAAC,YAAY,CAAC,UAAU,EAAE,MAAM,CAAC,CAAA;QAExC,MAAM,IAAI,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,UAAU,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAA;QAExC,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,eAAe,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;QAClD,OAAO,CAAC,MAAM,EAAE,CAAA;IACjB,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,oEAAoE,EAAE,KAAK;QAC7E,MAAM,OAAO,GAAG,QAAQ,CAAC,aAAa,CAAC,SAAS,CAAC,CAAA;QACjD,MAAM,MAAM,GAAG,QAAQ,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAA;QAE/C,MAAM,CAAC,YAAY,CAAC,UAAU,EAAE,MAAM,CAAC,CAAA;QACvC,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,CAAA;QAEtB,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,CAAA;QAE7B,MAAM,IAAI,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,UAAU,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAA;QAExC,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,eAAe,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;QACjD,OAAO,CAAC,MAAM,EAAE,CAAA;IACjB,CAAC,CAAC,CAAA;AACH,CAAC,CAAC,CAAA"} \ No newline at end of file diff --git a/dist/test/disconnect.test.d.ts b/dist/test/disconnect.test.d.ts new file mode 100644 index 0000000..1cd5e91 --- /dev/null +++ b/dist/test/disconnect.test.d.ts @@ -0,0 +1,2 @@ +import '../index.js'; +//# sourceMappingURL=disconnect.test.d.ts.map \ No newline at end of file diff --git a/dist/test/disconnect.test.d.ts.map b/dist/test/disconnect.test.d.ts.map new file mode 100644 index 0000000..5b603d8 --- /dev/null +++ b/dist/test/disconnect.test.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"disconnect.test.d.ts","sourceRoot":"","sources":["../../src/test/disconnect.test.ts"],"names":[],"mappings":"AAAA,OAAO,aAAa,CAAA"} \ No newline at end of file diff --git a/dist/test/disconnect.test.js b/dist/test/disconnect.test.js new file mode 100644 index 0000000..f38108b --- /dev/null +++ b/dist/test/disconnect.test.js @@ -0,0 +1,45 @@ +import '../index.js'; +describe('disconnectedCallback()', function () { + async function later() { + await new Promise(r => setTimeout(r, 4)); + } + it('Is called when removeAttribute() is used', async function () { + const { resolve, promise: disconnectedPromise } = Promise.withResolvers(); + let count = 0; + class MyAttr { + async connectedCallback() { + await later().then(() => el.removeAttribute('my-attr')); + } + changedCallback() { + count++; + } + disconnectedCallback() { + resolve(); + } + } + customAttributes.define('my-attr', MyAttr); + const el = document.createElement('span'); + document.body.append(el); + queueMicrotask(() => el.setAttribute('my-attr', 'test')); + await disconnectedPromise; + expect(count).toBe(0); + }); + it('Is called when the element is removed', async function () { + const { resolve, promise: disconnectedPromise } = Promise.withResolvers(); + class SomeAttr { + async connectedCallback() { + await later(); + el.remove(); + } + disconnectedCallback() { + resolve(); + } + } + customAttributes.define('some-attr', SomeAttr); + const el = document.createElement('span'); + document.body.append(el); + queueMicrotask(() => el.setAttribute('some-attr', 'test')); + await disconnectedPromise; + }); +}); +//# sourceMappingURL=disconnect.test.js.map \ No newline at end of file diff --git a/dist/test/disconnect.test.js.map b/dist/test/disconnect.test.js.map new file mode 100644 index 0000000..987ea8e --- /dev/null +++ b/dist/test/disconnect.test.js.map @@ -0,0 +1 @@ +{"version":3,"file":"disconnect.test.js","sourceRoot":"","sources":["../../src/test/disconnect.test.ts"],"names":[],"mappings":"AAAA,OAAO,aAAa,CAAA;AAEpB,QAAQ,CAAC,wBAAwB,EAAE;IAClC,KAAK,UAAU,KAAK;QACnB,MAAM,IAAI,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,UAAU,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAA;IACzC,CAAC;IAED,EAAE,CAAC,0CAA0C,EAAE,KAAK;QACnD,MAAM,EAAC,OAAO,EAAE,OAAO,EAAE,mBAAmB,EAAC,GAAG,OAAO,CAAC,aAAa,EAAQ,CAAA;QAE7E,IAAI,KAAK,GAAG,CAAC,CAAA;QAEb,MAAM,MAAM;YACX,KAAK,CAAC,iBAAiB;gBACtB,MAAM,KAAK,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,eAAe,CAAC,SAAS,CAAC,CAAC,CAAA;YACxD,CAAC;YAED,eAAe;gBACd,KAAK,EAAE,CAAA;YACR,CAAC;YAED,oBAAoB;gBACnB,OAAO,EAAE,CAAA;YACV,CAAC;SACD;QAED,gBAAgB,CAAC,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC,CAAA;QAE1C,MAAM,EAAE,GAAG,QAAQ,CAAC,aAAa,CAAC,MAAM,CAAC,CAAA;QACzC,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,CAAA;QAExB,cAAc,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,YAAY,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC,CAAA;QAExD,MAAM,mBAAmB,CAAA;QAEzB,MAAM,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;IACtB,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,uCAAuC,EAAE,KAAK;QAChD,MAAM,EAAC,OAAO,EAAE,OAAO,EAAE,mBAAmB,EAAC,GAAG,OAAO,CAAC,aAAa,EAAQ,CAAA;QAE7E,MAAM,QAAQ;YACb,KAAK,CAAC,iBAAiB;gBACtB,MAAM,KAAK,EAAE,CAAA;gBAEb,EAAE,CAAC,MAAM,EAAE,CAAA;YACZ,CAAC;YAED,oBAAoB;gBACnB,OAAO,EAAE,CAAA;YACV,CAAC;SACD;QAED,gBAAgB,CAAC,MAAM,CAAC,WAAW,EAAE,QAAQ,CAAC,CAAA;QAE9C,MAAM,EAAE,GAAG,QAAQ,CAAC,aAAa,CAAC,MAAM,CAAC,CAAA;QACzC,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,CAAA;QAExB,cAAc,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,YAAY,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC,CAAA;QAE1D,MAAM,mBAAmB,CAAA;IAC1B,CAAC,CAAC,CAAA;AACH,CAAC,CAAC,CAAA"} \ No newline at end of file diff --git a/dist/test/get.test.d.ts b/dist/test/get.test.d.ts new file mode 100644 index 0000000..1c06818 --- /dev/null +++ b/dist/test/get.test.d.ts @@ -0,0 +1,2 @@ +import '../index.js'; +//# sourceMappingURL=get.test.d.ts.map \ No newline at end of file diff --git a/dist/test/get.test.d.ts.map b/dist/test/get.test.d.ts.map new file mode 100644 index 0000000..132c6d0 --- /dev/null +++ b/dist/test/get.test.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"get.test.d.ts","sourceRoot":"","sources":["../../src/test/get.test.ts"],"names":[],"mappings":"AAAA,OAAO,aAAa,CAAA"} \ No newline at end of file diff --git a/dist/test/get.test.js b/dist/test/get.test.js new file mode 100644 index 0000000..925937f --- /dev/null +++ b/dist/test/get.test.js @@ -0,0 +1,37 @@ +import '../index.js'; +describe('get()', function () { + async function later() { + await new Promise(r => setTimeout(r, 4)); + } + it('gets the attribute instance', async function () { + class Foobar { + } + customAttributes.define('foo-bar', Foobar); + const el = document.createElement('span'); + el.setAttribute('foo-bar', 'baz'); + document.body.append(el); + await later(); + const fooBar = customAttributes.get(el, 'foo-bar'); + expect(fooBar).toBeInstanceOf(Foobar); + }); + it('allows passing more complex data types', async function () { + const { resolve, promise: bazSetPromise } = Promise.withResolvers(); + class Foobar { + set baz(val) { + expect(val).toBe('hello world'); + resolve(); + } + } + customAttributes.define('foo-bar', Foobar); + const el = document.createElement('span'); + el.setAttribute('foo-bar', 'bam'); + document.body.append(el); + await later(); + const fooBar = customAttributes.get(el, 'foo-bar'); + queueMicrotask(() => + // @ts-expect-error TODO make a CustomAttributes map for registering global attribute types. + (fooBar.baz = 'hello world')); + await bazSetPromise; + }); +}); +//# sourceMappingURL=get.test.js.map \ No newline at end of file diff --git a/dist/test/get.test.js.map b/dist/test/get.test.js.map new file mode 100644 index 0000000..7bf7fd3 --- /dev/null +++ b/dist/test/get.test.js.map @@ -0,0 +1 @@ +{"version":3,"file":"get.test.js","sourceRoot":"","sources":["../../src/test/get.test.ts"],"names":[],"mappings":"AAAA,OAAO,aAAa,CAAA;AAEpB,QAAQ,CAAC,OAAO,EAAE;IACjB,KAAK,UAAU,KAAK;QACnB,MAAM,IAAI,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,UAAU,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAA;IACzC,CAAC;IAED,EAAE,CAAC,6BAA6B,EAAE,KAAK;QACtC,MAAM,MAAM;SAAG;QACf,gBAAgB,CAAC,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC,CAAA;QAE1C,MAAM,EAAE,GAAG,QAAQ,CAAC,aAAa,CAAC,MAAM,CAAC,CAAA;QACzC,EAAE,CAAC,YAAY,CAAC,SAAS,EAAE,KAAK,CAAC,CAAA;QACjC,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,CAAA;QAExB,MAAM,KAAK,EAAE,CAAA;QAEb,MAAM,MAAM,GAAG,gBAAgB,CAAC,GAAG,CAAC,EAAE,EAAE,SAAS,CAAC,CAAA;QAClD,MAAM,CAAC,MAAM,CAAC,CAAC,cAAc,CAAC,MAAM,CAAC,CAAA;IACtC,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,wCAAwC,EAAE,KAAK;QACjD,MAAM,EAAC,OAAO,EAAE,OAAO,EAAE,aAAa,EAAC,GAAG,OAAO,CAAC,aAAa,EAAQ,CAAA;QAEvE,MAAM,MAAM;YACX,IAAI,GAAG,CAAC,GAAQ;gBACf,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAA;gBAC/B,OAAO,EAAE,CAAA;YACV,CAAC;SACD;QAED,gBAAgB,CAAC,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC,CAAA;QAE1C,MAAM,EAAE,GAAG,QAAQ,CAAC,aAAa,CAAC,MAAM,CAAC,CAAA;QACzC,EAAE,CAAC,YAAY,CAAC,SAAS,EAAE,KAAK,CAAC,CAAA;QACjC,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,CAAA;QAExB,MAAM,KAAK,EAAE,CAAA;QAEb,MAAM,MAAM,GAAG,gBAAgB,CAAC,GAAG,CAAC,EAAE,EAAE,SAAS,CAAC,CAAA;QAElD,cAAc,CACb,GAAG,EAAE;QACJ,4FAA4F;QAC5F,CAAC,MAAO,CAAC,GAAG,GAAG,aAAa,CAAC,CAC9B,CAAA;QAED,MAAM,aAAa,CAAA;IACpB,CAAC,CAAC,CAAA;AACH,CAAC,CAAC,CAAA"} \ No newline at end of file diff --git a/package.json b/package.json index 42b4bd3..6276cde 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@lume/custom-attributes", - "version": "0.2.0", + "version": "0.2.4", "description": "Custom attributes: like custom elements, but for attributes", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -12,8 +12,8 @@ "dev": "lume dev", "typecheck": "lume typecheck", "typecheck:watch": "lume typecheckWatch", - "test": "npm run prettier:check && npm run build && echo TODO use 'lume test'", - "test:debug": "lume testDebug", + "test": "lume test", + "test:watch": "lume test --watch", "prettier": "lume prettier", "prettier:check": "lume prettierCheck", "release:patch": "lume releasePatch", @@ -37,12 +37,12 @@ }, "homepage": "https://github.com/matthewp/custom-attributes#readme", "devDependencies": { - "@lume/cli": "^0.9.0", + "@lume/cli": "^0.13.0", "prettier": "3.0.3", "rollup": "^0.41.4", - "typescript": "4.4.3" + "typescript": "^5.0.0" }, "dependencies": { - "lowclass": "^5.0.0" + "lowclass": "^8.0.0" } } diff --git a/src/CustomAttributeRegistry.ts b/src/CustomAttributeRegistry.ts index 4408571..05644c1 100644 --- a/src/CustomAttributeRegistry.ts +++ b/src/CustomAttributeRegistry.ts @@ -1,22 +1,22 @@ -import type {Constructor} from 'lowclass' +import type {Constructor} from 'lowclass/dist/Constructor.js' const forEach = Array.prototype.forEach export class CustomAttributeRegistry { - private _attrMap = new Map() - private _elementMap = new WeakMap>() + #attrMap = new Map() + #elementMap = new WeakMap>() - private _observer: MutationObserver = new MutationObserver(mutations => { + #observer: MutationObserver = new MutationObserver(mutations => { forEach.call(mutations, (m: MutationRecord) => { if (m.type === 'attributes') { - const attr = this._getConstructor(m.attributeName!) - if (attr) this._handleChange(m.attributeName!, m.target as Element, m.oldValue) + const attr = this.#getConstructor(m.attributeName!) + if (attr) this.#handleChange(m.attributeName!, m.target as Element, m.oldValue) } // chlidList else { - forEach.call(m.removedNodes, this._elementDisconnected) - forEach.call(m.addedNodes, this._elementConnected) + forEach.call(m.removedNodes, this.#elementDisconnected) + forEach.call(m.addedNodes, this.#elementConnected) } }) }) @@ -26,82 +26,82 @@ export class CustomAttributeRegistry { } define(attrName: string, Class: Constructor) { - this._attrMap.set(attrName, Class) - this._upgradeAttr(attrName) - this._reobserve() + this.#attrMap.set(attrName, Class) + this.#upgradeAttr(attrName) + this.#reobserve() } get(element: Element, attrName: string) { - const map = this._elementMap.get(element) + const map = this.#elementMap.get(element) if (!map) return return map.get(attrName) } - private _getConstructor(attrName: string) { - return this._attrMap.get(attrName) + #getConstructor(attrName: string) { + return this.#attrMap.get(attrName) } - private _observe() { - this._observer.observe(this.ownerDocument, { + #observe() { + this.#observer.observe(this.ownerDocument, { childList: true, subtree: true, attributes: true, attributeOldValue: true, - attributeFilter: Array.from(this._attrMap.keys()), - // attributeFilter: [...this._attrMap.keys()], // Broken in Oculus - // attributeFilter: this._attrMap.keys(), // This works in Chrome, but TS complains, and not clear if it should work in all browsers yet: https://github.com/whatwg/dom/issues/1092 + attributeFilter: Array.from(this.#attrMap.keys()), + // attributeFilter: [...this.#attrMap.keys()], // Broken in Oculus + // attributeFilter: this.#attrMap.keys(), // This works in Chrome, but TS complains, and not clear if it should work in all browsers yet: https://github.com/whatwg/dom/issues/1092 }) } - private _unobserve() { - this._observer.disconnect() + #unobserve() { + this.#observer.disconnect() } - private _reobserve() { - this._unobserve() - this._observe() + #reobserve() { + this.#unobserve() + this.#observe() } - private _upgradeAttr(attrName: string, node: Element | Document | ShadowRoot = this.ownerDocument) { + #upgradeAttr(attrName: string, node: Element | Document | ShadowRoot = this.ownerDocument) { const matches = node.querySelectorAll('[' + attrName + ']') // Possibly create custom attributes that may be in the given 'node' tree. // Use a forEach as Edge doesn't support for...of on a NodeList - forEach.call(matches, (element: Element) => this._handleChange(attrName, element, null)) + forEach.call(matches, (element: Element) => this.#handleChange(attrName, element, null)) } - private _elementConnected = (element: Element) => { + #elementConnected = (element: Element) => { if (element.nodeType !== 1) return // For each of the connected element's attribute, possibly instantiate the custom attributes. // Use a forEach as Safari 10 doesn't support for...of on NamedNodeMap (attributes) forEach.call(element.attributes, (attr: Attr) => { - if (this._getConstructor(attr.name)) this._handleChange(attr.name, element, null) + if (this.#getConstructor(attr.name)) this.#handleChange(attr.name, element, null) }) // Possibly instantiate custom attributes that may be in the subtree of the connected element. - this._attrMap.forEach((_constructor, attr) => this._upgradeAttr(attr, element)) + this.#attrMap.forEach((_constructor, attr) => this.#upgradeAttr(attr, element)) } - private _elementDisconnected = (element: Element) => { - const map = this._elementMap.get(element) + #elementDisconnected = (element: Element) => { + const map = this.#elementMap.get(element) if (!map) return map.forEach(inst => inst.disconnectedCallback?.(), this) - this._elementMap.delete(element) + this.#elementMap.delete(element) } - private _handleChange(attrName: string, el: Element, oldVal: string | null) { - let map = this._elementMap.get(el) - if (!map) this._elementMap.set(el, (map = new Map())) + #handleChange(attrName: string, el: Element, oldVal: string | null) { + let map = this.#elementMap.get(el) + if (!map) this.#elementMap.set(el, (map = new Map())) let inst = map.get(attrName) const newVal = el.getAttribute(attrName) // Attribute is being created if (!inst) { - const Constructor = this._getConstructor(attrName)! + const Constructor = this.#getConstructor(attrName)! inst = new Constructor() as CustomAttribute map.set(attrName, inst) inst.ownerElement = el @@ -139,10 +139,10 @@ export interface CustomAttribute { // Avoid errors trying to use DOM APIs in non-DOM environments (f.e. server-side rendering). if (globalThis.window?.document) { - const _attachShadow = Element.prototype.attachShadow + const original = Element.prototype.attachShadow Element.prototype.attachShadow = function attachShadow(options) { - const root = _attachShadow.call(this, options) + const root = original.call(this, options) if (!root.customAttributes) root.customAttributes = new CustomAttributeRegistry(root) diff --git a/src/index.ts b/src/index.ts index 9accf31..5d766f1 100644 --- a/src/index.ts +++ b/src/index.ts @@ -28,4 +28,4 @@ declare global { } } -export const version = '0.2.0' +export const version = '0.2.4' diff --git a/src/test/change.test.ts b/src/test/change.test.ts new file mode 100644 index 0000000..d30b024 --- /dev/null +++ b/src/test/change.test.ts @@ -0,0 +1,51 @@ +import '../index.js' + +describe('changedCallback()', function () { + async function later() { + await new Promise(r => setTimeout(r, 4)) + } + + it('Is called when an attribute changes value', async function () { + const {resolve, promise: changedPromise} = Promise.withResolvers() + + class SomeAttr { + changedCallback() { + resolve() + } + } + + customAttributes.define('some-attr', SomeAttr) + + const el = document.createElement('article') + el.setAttribute('some-attr', 'foo') + document.body.append(el) + + queueMicrotask(() => el.setAttribute('some-attr', 'bar')) + + await changedPromise + + el.remove() + }) + + it("Is not called when an attribute's value remains the same", async function () { + let count = 0 + + class AnotherAttr { + changedCallback() { + count++ + } + } + + customAttributes.define('another-attr', AnotherAttr) + + const el = document.createElement('span') + el.setAttribute('another-attr', 'foo') + document.body.append(el) + + el.setAttribute('another-attr', 'foo') + + await later() + + expect(count).toBe(0) + }) +}) diff --git a/src/test/connect.test.ts b/src/test/connect.test.ts new file mode 100644 index 0000000..23b0f7d --- /dev/null +++ b/src/test/connect.test.ts @@ -0,0 +1,70 @@ +import '../index.js' + +declare global { + function expect(...args: any[]): any +} + +class BgColorAttr { + declare value: any + declare ownerElement: any + + connectedCallback() { + this.setColor() + } + + changedCallback(_oldValue: any, _newValue: any) { + this.setColor() + } + + setColor() { + const color = this.value || '' + this.ownerElement.style.backgroundColor = color + } +} + +document.body.insertAdjacentHTML( + 'beforeend', + /*html*/ ` +
+

This article is static

+
+`, +) + +customAttributes.define('bg-color', BgColorAttr) + +describe('connectedCallback()', function () { + it('Is called when element is already in the DOM', function () { + const article = document.querySelector('article')! + expect(article!.style.backgroundColor).toBe('red') + article.remove() + }) + + it('Is called when an attribute is dynamically created', async function () { + const article = document.createElement('article') + article.textContent = 'hello world' + document.body.append(article) + + article.setAttribute('bg-color', 'blue') + + await new Promise(r => setTimeout(r, 4)) + + expect(article.style.backgroundColor).toBe('blue') + article.remove() + }) + + it('Is called on nested elements when root element is added to the DOM', async function () { + const article = document.createElement('article') + const header = document.createElement('header') + + header.setAttribute('bg-color', 'blue') + article.append(header) + + document.body.append(article) + + await new Promise(r => setTimeout(r, 4)) + + expect(header.style.backgroundColor).toBe('blue') + article.remove() + }) +}) diff --git a/src/test/disconnect.test.ts b/src/test/disconnect.test.ts new file mode 100644 index 0000000..56977aa --- /dev/null +++ b/src/test/disconnect.test.ts @@ -0,0 +1,63 @@ +import '../index.js' + +describe('disconnectedCallback()', function () { + async function later() { + await new Promise(r => setTimeout(r, 4)) + } + + it('Is called when removeAttribute() is used', async function () { + const {resolve, promise: disconnectedPromise} = Promise.withResolvers() + + let count = 0 + + class MyAttr { + async connectedCallback() { + await later().then(() => el.removeAttribute('my-attr')) + } + + changedCallback() { + count++ + } + + disconnectedCallback() { + resolve() + } + } + + customAttributes.define('my-attr', MyAttr) + + const el = document.createElement('span') + document.body.append(el) + + queueMicrotask(() => el.setAttribute('my-attr', 'test')) + + await disconnectedPromise + + expect(count).toBe(0) + }) + + it('Is called when the element is removed', async function () { + const {resolve, promise: disconnectedPromise} = Promise.withResolvers() + + class SomeAttr { + async connectedCallback() { + await later() + + el.remove() + } + + disconnectedCallback() { + resolve() + } + } + + customAttributes.define('some-attr', SomeAttr) + + const el = document.createElement('span') + document.body.append(el) + + queueMicrotask(() => el.setAttribute('some-attr', 'test')) + + await disconnectedPromise + }) +}) diff --git a/src/test/get.test.ts b/src/test/get.test.ts new file mode 100644 index 0000000..6889291 --- /dev/null +++ b/src/test/get.test.ts @@ -0,0 +1,50 @@ +import '../index.js' + +describe('get()', function () { + async function later() { + await new Promise(r => setTimeout(r, 4)) + } + + it('gets the attribute instance', async function () { + class Foobar {} + customAttributes.define('foo-bar', Foobar) + + const el = document.createElement('span') + el.setAttribute('foo-bar', 'baz') + document.body.append(el) + + await later() + + const fooBar = customAttributes.get(el, 'foo-bar') + expect(fooBar).toBeInstanceOf(Foobar) + }) + + it('allows passing more complex data types', async function () { + const {resolve, promise: bazSetPromise} = Promise.withResolvers() + + class Foobar { + set baz(val: any) { + expect(val).toBe('hello world') + resolve() + } + } + + customAttributes.define('foo-bar', Foobar) + + const el = document.createElement('span') + el.setAttribute('foo-bar', 'bam') + document.body.append(el) + + await later() + + const fooBar = customAttributes.get(el, 'foo-bar') + + queueMicrotask( + () => + // @ts-expect-error TODO make a CustomAttributes map for registering global attribute types. + (fooBar!.baz = 'hello world'), + ) + + await bazSetPromise + }) +}) diff --git a/test/all.html b/test/all.html deleted file mode 100644 index 59da15f..0000000 --- a/test/all.html +++ /dev/null @@ -1,31 +0,0 @@ - - - - Codestin Search App - - - -
- - -
-
- - -
- - diff --git a/test/change.html b/test/change.html deleted file mode 100644 index 751033d..0000000 --- a/test/change.html +++ /dev/null @@ -1,66 +0,0 @@ - - - - Codestin Search App - - - - - - -
- - - - - diff --git a/test/connect.html b/test/connect.html deleted file mode 100644 index f35f8cd..0000000 --- a/test/connect.html +++ /dev/null @@ -1,87 +0,0 @@ - - - - Codestin Search App - - - - - - -
-

This article is static

-
- -
- - - - - - - - diff --git a/test/disconnect.html b/test/disconnect.html deleted file mode 100644 index e455cf6..0000000 --- a/test/disconnect.html +++ /dev/null @@ -1,77 +0,0 @@ - - - - Codestin Search App - - - - - - -
- - - - - - diff --git a/test/get.html b/test/get.html deleted file mode 100644 index 5d1e39c..0000000 --- a/test/get.html +++ /dev/null @@ -1,62 +0,0 @@ - - - - Codestin Search App - - - - - - -
- - - - - diff --git a/tsconfig.json b/tsconfig.json index 9838b4b..d7b73b0 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,5 +1,3 @@ { - "extends": "./node_modules/@lume/cli/config/ts.config.json", - "include": ["./src/**/*"], - "exclude": ["./src/globals.d.ts"] + "extends": "./node_modules/@lume/cli/config/ts.config.json" }