@shgysk8zer0/polyfills 0.4.4 → 0.4.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,41 @@
1
+ if (! (globalThis.CloseWatcher instanceof Function)) {
2
+ globalThis.CloseWatcher = class CloseWatcher extends EventTarget {
3
+ #controller = new AbortController();
4
+ #cancelable = true;
5
+
6
+ constructor() {
7
+ super();
8
+
9
+ document.addEventListener('keydown', event => {
10
+ if (event.key === 'Escape') {
11
+ this.requestClose();
12
+ }
13
+ }, { signal: this.#controller.signal });
14
+ }
15
+
16
+ requestClose() {
17
+ if (! this.#controller.signal.aborted) {
18
+ const event = new Event('cancel', { cancelable: this.#cancelable });
19
+ this.dispatchEvent(event);
20
+ this.#cancelable = false;
21
+
22
+ if (! event.defaultPrevented) {
23
+ this.close();
24
+ }
25
+ }
26
+ }
27
+
28
+ close() {
29
+ if (! this.#controller.signal.aborted) {
30
+ this.dispatchEvent(new Event('close'));
31
+ this.destroy();
32
+ }
33
+ }
34
+
35
+ destroy() {
36
+ if (! this.#controller.signal.aborted) {
37
+ this.#controller.abort();
38
+ }
39
+ }
40
+ };
41
+ }
package/element.js CHANGED
@@ -1,9 +1,32 @@
1
1
  import { aria } from './aom.js';
2
- import { polyfillGetterSetter, polyfillMethod } from './utils.js';
3
- import { setHTMLUnsafe } from './methods/dom.js';
2
+ import { polyfillGetterSetter, polyfillMethod, overwriteMethod } from './utils.js';
3
+ import { getHTML, setHTMLUnsafe } from './methods/dom.js';
4
4
  import './sanitizer.js';
5
5
 
6
6
  polyfillMethod(Element.prototype, 'setHTMLUnsafe', setHTMLUnsafe);
7
+ polyfillMethod(Element.prototype, 'getHTML', getHTML);
8
+
9
+ if ('CustomElementRegistry' in globalThis && ! (CustomElementRegistry.prototype.getName instanceof Function)) {
10
+ const registry = new Map();
11
+
12
+ overwriteMethod(CustomElementRegistry.prototype, 'define', original => {
13
+ return (tag, proto, opts) => {
14
+ original.call(customElements, tag,proto, opts);
15
+ registry.set(proto, typeof opts.extends === 'string' ? opts.extends : tag);
16
+ };
17
+ });
18
+
19
+ polyfillMethod(CustomElementRegistry.prototype, 'getName', proto => {
20
+ if (typeof proto === 'function') {
21
+ return registry.get(proto) ?? null;
22
+ } else if (typeof proto === 'object' && proto !== null) {
23
+ // Unnecessary, but just to be sure the same errors are thrown.
24
+ throw new TypeError('CustomElementRegistry.getName: Argument 1 is not callable.');
25
+ } else {
26
+ throw new TypeError('CustomElementRegistry.getName: Argument 1 is not an object.');
27
+ }
28
+ });
29
+ }
7
30
 
8
31
  function handlePopover({ currentTarget }) {
9
32
  switch(currentTarget.popoverTargetAction) {
@@ -25,7 +48,7 @@ export function initPopover(target = document.body) {
25
48
  .forEach(el => el.addEventListener('click', handlePopover));
26
49
  }
27
50
 
28
- if ((globalThis.ToggleEvent instanceof Function)) {
51
+ if (! (globalThis.ToggleEvent instanceof Function)) {
29
52
  class ToggleEvent extends Event {
30
53
  #newState;
31
54
  #oldState;
@@ -246,26 +269,77 @@ if (! (HTMLImageElement.prototype.decode instanceof Function)) {
246
269
  }
247
270
 
248
271
  if (! HTMLTemplateElement.prototype.hasOwnProperty('shadowRootMode')) {
249
- Object.defineProperty(HTMLTemplateElement.prototype, 'shadowRootMode', {
250
- get: function() {
272
+ const attachedShadows = new WeakMap();
273
+
274
+ polyfillGetterSetter(HTMLTemplateElement.prototype, 'shadowRootMode', {
275
+ get() {
251
276
  return this.getAttribute('shadowrootmode');
252
277
  },
253
- set: function(val) {
278
+ set(val) {
254
279
  this.setAttribute('shadowrootmode', val);
280
+ }
281
+ });
282
+
283
+ polyfillGetterSetter(HTMLTemplateElement.prototype, 'shadowRootDelegatesFocus', {
284
+ get() {
285
+ return this.hasAttribute('shadowrootdelegatesfocus');
255
286
  },
256
- enumerable: true,
257
- configurable: true,
287
+ set(val) {
288
+ this.toggleAttribute('shadowrootdelegatesfocus', val);
289
+ }
290
+ });
291
+
292
+ polyfillGetterSetter(HTMLTemplateElement.prototype, 'shadowRootClonable', {
293
+ get() {
294
+ return this.hasAttribute('shadowrootclonable');
295
+ },
296
+ set(val) {
297
+ this.toggleAttribute('shadowrootclonable', val);
298
+ }
299
+ });
300
+
301
+ polyfillGetterSetter(HTMLTemplateElement.prototype, 'shadowRootSerializable', {
302
+ get() {
303
+ return this.hasAttribute('shadowrootserializable');
304
+ },
305
+ set(val) {
306
+ this.toggleAttribute('shadowrootserializable', val);
307
+ }
258
308
  });
259
309
 
260
310
  const attachShadows = (base = document) => {
261
311
  base.querySelectorAll('template[shadowrootmode]').forEach(tmp => {
262
- const shadow = tmp.parentElement.attachShadow({ mode: tmp.shadowRootMode });
312
+ const shadow = tmp.parentElement.attachShadow({
313
+ mode: tmp.shadowRootMode,
314
+ clonable: tmp.shadowRootClonable,
315
+ delegatesFocus: tmp.shadowRootDelegatesFocus,
316
+ serializable: tmp.shadowRootSerializable,
317
+ });
318
+
263
319
  shadow.append(tmp.content);
264
320
  tmp.remove();
265
321
  attachShadows(shadow);
322
+ attachedShadows.set(shadow.host, shadow);
266
323
  });
267
324
  };
268
325
 
326
+ overwriteMethod(HTMLElement.prototype, 'attachShadow', function(attach) {
327
+ return ({ mode, clonable = false, delegatesFocus = false, serializable = false, slotAssignment = 'auto' }) => {
328
+ if (! attachedShadows.has(this)) {
329
+ return attach.call(this, { mode, clonable, delegatesFocus, serializable, slotAssignment });
330
+ } else {
331
+ const shadow = attachedShadows.get(this);
332
+
333
+ if (mode === shadow.shadowRootMode) {
334
+ attachShadows.remove(this);
335
+ return shadow;
336
+ } else {
337
+ throw new DOMException('Element.attachShadow: Unable to re-attach to existing ShadowDOM');
338
+ }
339
+ }
340
+ };
341
+ });
342
+
269
343
  if (document.readyState === 'loading') {
270
344
  document.addEventListener('readystatechange', () => attachShadows(document), { once: true });
271
345
  } else {
package/methods/dom.js CHANGED
@@ -1,3 +1,11 @@
1
+ const HOSTS_SYMBOL = Symbol('shadow:hosts');
2
+
3
+ function getTagHTML(el) {
4
+ const outerHTML = el.cloneNode().outerHTML;
5
+ const closeStart = outerHTML.indexOf('/') - 1;
6
+ return closeStart > 0 ? [outerHTML.substring(0, closeStart), outerHTML.substring(closeStart)] : [outerHTML, ''];
7
+ }
8
+
1
9
  function attachShadow(template){
2
10
  if (template instanceof HTMLTemplateElement && template.parentElement instanceof HTMLElement) {
3
11
  const shadow = template.parentElement.attachShadow({
@@ -12,6 +20,45 @@ function attachShadow(template){
12
20
  }
13
21
  }
14
22
 
23
+ function serializeChildNodes(node, { serializableShadowRoots = false, shadowRoots = [], [HOSTS_SYMBOL]: hosts = [] } = {}) {
24
+ return Array.from(
25
+ node.childNodes,
26
+ child => {
27
+ if (child.nodeType === Node.ELEMENT_NODE) {
28
+ const [open, close] = getTagHTML(child);
29
+ return open + child.getHTML({ serializableShadowRoots, shadowRoots, [HOSTS_SYMBOL]: hosts }) + close;
30
+ } else {
31
+ return child.textContent;
32
+ }
33
+ }
34
+ ).join('');
35
+ }
36
+
37
+ function serializeShadowRoot(shadow, { serializableShadowRoots, shadowRoots, [HOSTS_SYMBOL]: hosts = [] } = {}) {
38
+ if (shadow.serializable) {
39
+ const openTag = `<template shadowrootmode="${shadow.mode}"`;
40
+ const attrs = [];
41
+
42
+ if (shadow.clonable) {
43
+ attrs.push(' shadowrootclonable=""');
44
+ }
45
+
46
+ if (shadow.serializable) {
47
+ attrs.push(' shadowrootserializable=""');
48
+ }
49
+
50
+ if (shadow.delegatesFocus) {
51
+ attrs.push(' shadowrootdelegatesfocus=""');
52
+ }
53
+
54
+ const tag = attrs.length === 0 ? openTag + '>' : openTag + ' ' + attrs.join('') + '>';
55
+
56
+ return tag + serializeChildNodes(shadow, { serializableShadowRoots, shadowRoots, [HOSTS_SYMBOL]: hosts }) + '</template>';
57
+ } else {
58
+ return serializeChildNodes(shadow, { serializableShadowRoots, shadowRoots, [HOSTS_SYMBOL]: hosts });
59
+ }
60
+ }
61
+
15
62
  export function parseHTMLUnsafe(input){
16
63
  const parser = new DOMParser();
17
64
  // Ensures `URL` is "about:blank"
@@ -20,7 +67,7 @@ export function parseHTMLUnsafe(input){
20
67
  const parsed = parser.parseFromString(input, 'text/html');
21
68
  doc.head.append(...parsed.head.childNodes);
22
69
  doc.body.append(...parsed.body.childNodes);
23
- doc.querySelectorAll('template[shadowrootmode]').forEach(attachShadow);
70
+ doc.querySelectorAll('* > template[shadowrootmode]').forEach(attachShadow);
24
71
  return doc;
25
72
  }
26
73
 
@@ -30,10 +77,43 @@ export function setHTMLUnsafe(input) {
30
77
  const parsed = parser.parseFromString(input, 'text/html');
31
78
  const frag = document.createDocumentFragment();
32
79
  frag.append(...parsed.body.childNodes);
33
- attachShadow(frag.querySelector('template[shadowroootmode]'));
80
+ frag.querySelectorAll('* > template[shadowrootmode]').forEach(attachShadow);
34
81
  this.replaceChildren(frag);
35
82
  }
36
83
 
37
- export function getHTML() {
38
- return this.innerHTML;
84
+ export function getHTML({ serializableShadowRoots = false, shadowRoots = [], [HOSTS_SYMBOL]: hosts = [] } = {}) {
85
+ if (serializableShadowRoots && hosts.length === 0 && shadowRoots.length !== 0) {
86
+ hosts.push(...shadowRoots.map(shadow => shadow.host));
87
+ }
88
+
89
+ switch(this.nodeType) {
90
+ case Node.ELEMENT_NODE:
91
+ if (
92
+ serializableShadowRoots
93
+ && this.shadowRoot instanceof ShadowRoot
94
+ && (this.shadowRoot.serializable || shadowRoots.includes(this.shadowRoot))
95
+ ) {
96
+ return this.shadowRoot.getHTML({ serializableShadowRoots, shadowRoots, [HOSTS_SYMBOL]: hosts })
97
+ + serializeChildNodes(this,{ serializableShadowRoots, shadowRoots, [HOSTS_SYMBOL]: hosts } );
98
+ } else if (serializableShadowRoots && hosts.includes(this)) {
99
+ const shadow = shadowRoots.find(shadow => shadow.host.isSameNode(this));
100
+
101
+ return serializeShadowRoot(shadow, { serializableShadowRoots, shadowRoots, [HOSTS_SYMBOL]: hosts })
102
+ + serializeChildNodes(this, { serializableShadowRoots, shadowRoots, [HOSTS_SYMBOL]: hosts } );
103
+ } else {
104
+ return this.innerHTML;
105
+ }
106
+
107
+ case Node.DOCUMENT_FRAGMENT_NODE:
108
+ if (! (this instanceof ShadowRoot)) {
109
+ return '';
110
+ } else if (serializableShadowRoots && (this.serializable || shadowRoots.includes(this))) {
111
+ return serializeShadowRoot(this, { serializableShadowRoots, shadowRoots, [HOSTS_SYMBOL]: hosts });
112
+ } else {
113
+ return serializeChildNodes(this, { serializableShadowRoots, shadowRoots, [HOSTS_SYMBOL]: hosts });
114
+ }
115
+
116
+ default:
117
+ throw new TypeError('\'getHTML\' called on an object that does not implement interface Element.');
118
+ }
39
119
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shgysk8zer0/polyfills",
3
- "version": "0.4.4",
3
+ "version": "0.4.6",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "A collection of JavaScript polyfills",