@shgysk8zer0/polyfills 0.4.5 → 0.4.7

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,10 @@
1
1
  import { aria } from './aom.js';
2
2
  import { polyfillGetterSetter, polyfillMethod, overwriteMethod } from './utils.js';
3
- import { setHTMLUnsafe } from './methods/dom.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);
7
8
 
8
9
  if ('CustomElementRegistry' in globalThis && ! (CustomElementRegistry.prototype.getName instanceof Function)) {
9
10
  const registry = new Map();
@@ -47,7 +48,7 @@ export function initPopover(target = document.body) {
47
48
  .forEach(el => el.addEventListener('click', handlePopover));
48
49
  }
49
50
 
50
- if ((globalThis.ToggleEvent instanceof Function)) {
51
+ if (! (globalThis.ToggleEvent instanceof Function)) {
51
52
  class ToggleEvent extends Event {
52
53
  #newState;
53
54
  #oldState;
@@ -268,26 +269,77 @@ if (! (HTMLImageElement.prototype.decode instanceof Function)) {
268
269
  }
269
270
 
270
271
  if (! HTMLTemplateElement.prototype.hasOwnProperty('shadowRootMode')) {
271
- Object.defineProperty(HTMLTemplateElement.prototype, 'shadowRootMode', {
272
- get: function() {
272
+ const attachedShadows = new WeakMap();
273
+
274
+ polyfillGetterSetter(HTMLTemplateElement.prototype, 'shadowRootMode', {
275
+ get() {
273
276
  return this.getAttribute('shadowrootmode');
274
277
  },
275
- set: function(val) {
278
+ set(val) {
276
279
  this.setAttribute('shadowrootmode', val);
280
+ }
281
+ });
282
+
283
+ polyfillGetterSetter(HTMLTemplateElement.prototype, 'shadowRootDelegatesFocus', {
284
+ get() {
285
+ return this.hasAttribute('shadowrootdelegatesfocus');
286
+ },
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');
277
304
  },
278
- enumerable: true,
279
- configurable: true,
305
+ set(val) {
306
+ this.toggleAttribute('shadowrootserializable', val);
307
+ }
280
308
  });
281
309
 
282
310
  const attachShadows = (base = document) => {
283
311
  base.querySelectorAll('template[shadowrootmode]').forEach(tmp => {
284
- 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
+
285
319
  shadow.append(tmp.content);
286
320
  tmp.remove();
287
321
  attachShadows(shadow);
322
+ attachedShadows.set(shadow.host, shadow);
288
323
  });
289
324
  };
290
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
+
291
343
  if (document.readyState === 'loading') {
292
344
  document.addEventListener('readystatechange', () => attachShadows(document), { once: true });
293
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.5",
3
+ "version": "0.4.7",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "A collection of JavaScript polyfills",
@@ -92,7 +92,7 @@
92
92
  "urlpattern-polyfill": "^10.0.0"
93
93
  },
94
94
  "dependencies": {
95
- "@aegisjsproject/sanitizer": "^0.1.0",
95
+ "@aegisjsproject/sanitizer": "^0.1.3",
96
96
  "@aegisjsproject/trusted-types": "^1.0.1"
97
97
  }
98
98
  }