@esportsplus/template 0.16.1 → 0.18.0

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.
Files changed (60) hide show
  1. package/build/attributes.js +29 -24
  2. package/build/constants.d.ts +13 -3
  3. package/build/constants.js +14 -4
  4. package/build/event.js +48 -46
  5. package/build/html/hydrate.d.ts +2 -8
  6. package/build/html/hydrate.js +28 -79
  7. package/build/html/index.d.ts +2106 -5
  8. package/build/html/index.js +12 -4
  9. package/build/html/{cache.d.ts → parser.d.ts} +1 -1
  10. package/build/html/{cache.js → parser.js} +6 -4
  11. package/build/render.d.ts +1 -2
  12. package/build/render.js +7 -10
  13. package/build/slot/cleanup.d.ts +4 -0
  14. package/build/slot/cleanup.js +52 -0
  15. package/build/slot/effect.d.ts +3 -0
  16. package/build/slot/effect.js +56 -0
  17. package/build/slot/index.d.ts +3 -0
  18. package/build/slot/index.js +8 -0
  19. package/build/slot/reactive.d.ts +3 -0
  20. package/build/slot/reactive.js +125 -0
  21. package/build/slot/render.d.ts +2 -0
  22. package/build/slot/render.js +81 -0
  23. package/build/svg.d.ts +1 -2
  24. package/build/types.d.ts +21 -17
  25. package/build/utilities/element.d.ts +11 -0
  26. package/build/utilities/element.js +9 -0
  27. package/build/utilities/fragment.d.ts +3 -0
  28. package/build/utilities/fragment.js +11 -0
  29. package/build/utilities/node.d.ts +8 -0
  30. package/build/utilities/node.js +9 -0
  31. package/build/utilities/queue.d.ts +3 -0
  32. package/build/utilities/queue.js +4 -0
  33. package/build/utilities/text.d.ts +3 -0
  34. package/build/utilities/text.js +9 -0
  35. package/package.json +2 -2
  36. package/src/attributes.ts +32 -29
  37. package/src/constants.ts +32 -5
  38. package/src/event.ts +55 -52
  39. package/src/html/hydrate.ts +36 -108
  40. package/src/html/index.ts +16 -8
  41. package/src/html/{cache.ts → parser.ts} +5 -3
  42. package/src/render.ts +8 -12
  43. package/src/slot/cleanup.ts +74 -0
  44. package/src/slot/effect.ts +74 -0
  45. package/src/slot/index.ts +12 -0
  46. package/src/slot/reactive.ts +177 -0
  47. package/src/slot/render.ts +117 -0
  48. package/src/svg.ts +1 -2
  49. package/src/types.ts +25 -18
  50. package/src/utilities/element.ts +28 -0
  51. package/src/utilities/fragment.ts +22 -0
  52. package/src/utilities/node.ts +26 -0
  53. package/src/utilities/queue.ts +9 -0
  54. package/src/utilities/text.ts +16 -0
  55. package/build/slot.d.ts +0 -21
  56. package/build/slot.js +0 -204
  57. package/build/utilities.d.ts +0 -28
  58. package/build/utilities.js +0 -37
  59. package/src/slot.ts +0 -277
  60. package/src/utilities.ts +0 -87
package/src/attributes.ts CHANGED
@@ -1,24 +1,19 @@
1
1
  import { effect } from '@esportsplus/reactivity';
2
- import { isArray, isFunction, isObject, isString } from '@esportsplus/utilities';
3
- import { ondisconnect } from './slot';
2
+ import { isArray, isObject, isString } from '@esportsplus/utilities';
3
+ import { ondisconnect } from './slot/cleanup';
4
+ import { STATE_HYDRATING, STATE_NONE, STATE_WAITING } from './constants';
4
5
  import { Attributes, Element } from './types';
5
- import { className, raf, removeAttribute, setAttribute } from './utilities';
6
+ import { className, removeAttribute, setAttribute } from './utilities/element';
7
+ import { raf } from './utilities/queue';
6
8
  import q from '@esportsplus/queue';
7
9
  import event from './event';
8
10
 
9
11
 
10
- const EFFECT_KEY = Symbol();
12
+ const EFFECT = Symbol();
11
13
 
12
- const STORE_KEY = Symbol();
14
+ const STORE = Symbol();
13
15
 
14
- const UPDATES_KEY = Symbol();
15
-
16
-
17
- const STATE_HYDRATING = 0;
18
-
19
- const STATE_NONE = 1;
20
-
21
- const STATE_WAITING = 2;
16
+ const UPDATES = Symbol();
22
17
 
23
18
 
24
19
  type Context = {
@@ -64,19 +59,20 @@ function schedule() {
64
59
  }
65
60
 
66
61
  function set(context: Context, name: string, value: unknown, state: State) {
67
- if (isArray(value)) {
68
- for (let i = 0, n = value.length; i < n; i++) {
69
- set(context, name, value[i], state);
70
- }
62
+ if (value === false || value == null) {
63
+ value = '';
71
64
  }
72
- else if (isFunction(value)) {
65
+
66
+ let type = typeof value;
67
+
68
+ if (type === 'function') {
73
69
  if (name.startsWith('on')) {
74
- event(context.element, name as `on${string}`, value);
70
+ event(context.element, name as `on${string}`, value as Function);
75
71
  }
76
72
  else {
77
- context.store[EFFECT_KEY] ??= 0;
73
+ context.store[EFFECT] ??= 0;
78
74
 
79
- let id = (context.store[EFFECT_KEY] as number)++;
75
+ let id = (context.store[EFFECT] as number)++;
80
76
 
81
77
  ondisconnect(
82
78
  context.element,
@@ -107,6 +103,13 @@ function set(context: Context, name: string, value: unknown, state: State) {
107
103
  state = STATE_NONE;
108
104
  }
109
105
  }
106
+ else if (type === 'object') {
107
+ if (isArray(value)) {
108
+ for (let i = 0, n = value.length; i < n; i++) {
109
+ set(context, name, value[i], state);
110
+ }
111
+ }
112
+ }
110
113
  else {
111
114
  update(context, null, name, value, state);
112
115
  }
@@ -232,15 +235,20 @@ function update(
232
235
 
233
236
 
234
237
  const spread = function (element: Element, value: Attributes | Attributes[]) {
235
- let cache = (element[STORE_KEY] ??= { [UPDATES_KEY]: {} }) as Record<PropertyKey, unknown>,
238
+ let cache = (element[STORE] ??= { [UPDATES]: {} }) as Record<PropertyKey, unknown>,
236
239
  context = {
237
240
  element,
238
241
  store: cache,
239
- updates: cache[UPDATES_KEY] as Record<PropertyKey, unknown>,
242
+ updates: cache[UPDATES] as Record<PropertyKey, unknown>,
240
243
  updating: false
241
244
  };
242
245
 
243
- if (isArray(value)) {
246
+ if (isObject(value)) {
247
+ for (let name in value) {
248
+ set(context, name, value[name], STATE_HYDRATING);
249
+ }
250
+ }
251
+ else if (isArray(value)) {
244
252
  for (let i = 0, n = value.length; i < n; i++) {
245
253
  let v = value[i];
246
254
 
@@ -249,11 +257,6 @@ const spread = function (element: Element, value: Attributes | Attributes[]) {
249
257
  }
250
258
  }
251
259
  }
252
- else if (isObject(value)) {
253
- for (let name in value) {
254
- set(context, name, value[name], STATE_HYDRATING);
255
- }
256
- }
257
260
  };
258
261
 
259
262
 
package/src/constants.ts CHANGED
@@ -1,4 +1,7 @@
1
- import { fragment } from './utilities';
1
+ import { fragment } from './utilities/fragment';
2
+
3
+
4
+ const CLEANUP = Symbol();
2
5
 
3
6
 
4
7
  const EMPTY_FRAGMENT = fragment('');
@@ -44,9 +47,21 @@ const REGEX_SLOT_NODES = /<([\w-]+|[\/!])(?:([^><]*{{\$}}[^><]*)|(?:[^><]*))?>|{
44
47
 
45
48
  const RENDERABLE = Symbol();
46
49
 
47
- const RENDERABLE_REACTIVE = Symbol();
50
+ const RENDERABLE_ARRAY = 0;
51
+
52
+ const RENDERABLE_FRAGMENT = 1;
53
+
54
+ const RENDERABLE_HTML_FRAGMENT = 2;
55
+
56
+ const RENDERABLE_HTML_REACTIVE_ARRAY = 3;
57
+
58
+ const RENDERABLE_NODE = 4;
48
59
 
49
- const RENDERABLE_TEMPLATE = Symbol();
60
+ const RENDERABLE_NODE_LIST = 5;
61
+
62
+ const RENDERABLE_TEXT = 6;
63
+
64
+ const RENDERABLE_VOID = 7;
50
65
 
51
66
 
52
67
  const SLOT_HTML = '<!--$-->';
@@ -56,10 +71,22 @@ const SLOT_MARKER = '{{$}}';
56
71
  const SLOT_MARKER_LENGTH = SLOT_MARKER.length;
57
72
 
58
73
 
74
+ const STATE_HYDRATING = 0;
75
+
76
+ const STATE_NONE = 1;
77
+
78
+ const STATE_WAITING = 2;
79
+
80
+
59
81
  export {
82
+ CLEANUP,
60
83
  EMPTY_FRAGMENT,
61
84
  NODE_CLOSING, NODE_ELEMENT, NODE_SLOT, NODE_VOID, NODE_WHITELIST,
62
85
  REGEX_EMPTY_TEXT_NODES, REGEX_SLOT_NODES,
63
- RENDERABLE, RENDERABLE_REACTIVE, RENDERABLE_TEMPLATE,
64
- SLOT_HTML, SLOT_MARKER, SLOT_MARKER_LENGTH
86
+ RENDERABLE, RENDERABLE_ARRAY, RENDERABLE_FRAGMENT,
87
+ RENDERABLE_HTML_FRAGMENT, RENDERABLE_HTML_REACTIVE_ARRAY,
88
+ RENDERABLE_NODE, RENDERABLE_NODE_LIST, RENDERABLE_TEXT,
89
+ RENDERABLE_VOID,
90
+ SLOT_HTML, SLOT_MARKER, SLOT_MARKER_LENGTH,
91
+ STATE_HYDRATING, STATE_NONE, STATE_WAITING
65
92
  };
package/src/event.ts CHANGED
@@ -1,8 +1,10 @@
1
1
  import { root } from '@esportsplus/reactivity';
2
2
  import { defineProperty } from '@esportsplus/utilities';
3
- import { ondisconnect } from './slot';
4
3
  import { Element } from './types';
5
- import { addEventListener, parentElement, raf } from './utilities';
4
+ import { addEventListener } from './utilities/element';
5
+ import { parentElement } from './utilities/node';
6
+ import { raf } from './utilities/queue';
7
+ import { ondisconnect } from './slot/cleanup';
6
8
 
7
9
 
8
10
  let capture = new Set<`on${string}`>(['onblur', 'onfocus', 'onscroll']),
@@ -24,35 +26,7 @@ let capture = new Set<`on${string}`>(['onblur', 'onfocus', 'onscroll']),
24
26
  });
25
27
 
26
28
 
27
- export default (element: Element, event: `on${string}`, listener: Function): void => {
28
- if (event === 'onconnect') {
29
- let retry = 60,
30
- task = () => {
31
- retry--;
32
-
33
- if (element.isConnected) {
34
- retry = 0;
35
- root(() => listener(element));
36
- }
37
-
38
- if (retry) {
39
- raf.add(task);
40
- }
41
- };
42
-
43
- raf.add(task);
44
-
45
- return;
46
- }
47
- else if (event === 'ondisconnect') {
48
- ondisconnect(element, () => listener(element));
49
- return;
50
- }
51
- else if (event === 'onrender') {
52
- root(() => listener(element));
53
- return;
54
- }
55
-
29
+ function register(element: Element, event: `on${string}`) {
56
30
  let controller = controllers.get(event),
57
31
  signal: AbortController['signal'] | undefined;
58
32
 
@@ -83,34 +57,63 @@ export default (element: Element, event: `on${string}`, listener: Function): voi
83
57
  signal = controller.signal;
84
58
  }
85
59
 
86
- let key = keys[event];
60
+ let key = keys[event] = Symbol();
87
61
 
88
- if (!key) {
89
- key = keys[event] = Symbol();
62
+ addEventListener.call(window.document, event.slice(2), (e) => {
63
+ let node = e.target as Element | null;
90
64
 
91
- addEventListener.call(window.document, event.slice(2), (e) => {
92
- let node = e.target as Element | null;
65
+ while (node) {
66
+ if (key in node) {
67
+ defineProperty(e, 'currentTarget', {
68
+ configurable: true,
69
+ get() {
70
+ return node || window.document;
71
+ }
72
+ });
93
73
 
94
- defineProperty(e, 'currentTarget', {
95
- configurable: true,
96
- get() {
97
- return node || window.document;
74
+ return (node[key] as Function).call(node, e);
75
+ }
76
+
77
+ node = parentElement.call(node);
78
+ }
79
+ }, {
80
+ capture: capture.has(event),
81
+ passive: passive.has(event),
82
+ signal
83
+ });
84
+
85
+ return key;
86
+ }
87
+
88
+
89
+ export default (element: Element, event: `on${string}`, listener: Function): void => {
90
+ if (event === 'onconnect') {
91
+ let retry = 60,
92
+ task = () => {
93
+ retry--;
94
+
95
+ if (element.isConnected) {
96
+ retry = 0;
97
+ root(() => listener(element));
98
98
  }
99
- });
100
99
 
101
- while (node) {
102
- if (key in node) {
103
- return (node[key] as Function).call(node, e);
100
+ if (retry) {
101
+ raf.add(task);
104
102
  }
103
+ };
105
104
 
106
- node = parentElement.call(node);
107
- }
108
- }, {
109
- capture: capture.has(event),
110
- passive: passive.has(event),
111
- signal
112
- });
105
+ raf.add(task);
106
+
107
+ return;
108
+ }
109
+ else if (event === 'ondisconnect') {
110
+ ondisconnect(element, () => listener(element));
111
+ return;
112
+ }
113
+ else if (event === 'onrender') {
114
+ root(() => listener(element));
115
+ return;
113
116
  }
114
117
 
115
- element[key] = listener;
118
+ element[ keys[event] || register(element, event) ] = listener;
116
119
  };
@@ -1,125 +1,53 @@
1
- import { root } from '@esportsplus/reactivity';
2
- import { EMPTY_FRAGMENT } from '~/constants';
3
- import { Elements, Fragment, RenderableReactive, RenderableTemplate } from '~/types';
4
- import { Slot } from '~/slot';
5
- import { cloneNode } from '~/utilities';
6
- import cache from './cache';
1
+ import { Fragment, RenderableValues, Template } from '~/types';
2
+ import { cloneNode } from '~/utilities/node';
7
3
 
8
4
 
9
- function reactive<T>(elements: Elements[], fragment: Fragment, renderable: RenderableReactive<T>, slot: Slot) {
10
- let array = renderable.values,
11
- factory = renderable.template,
12
- refresh = () => {
13
- root(() => array.map(template));
5
+ export default ({ fragment, slots }: Template, values: RenderableValues[]): Fragment => {
6
+ let clone = cloneNode.call(fragment, true);
14
7
 
15
- slot.clear();
16
- slot.anchor().after(fragment);
17
- slot.nodes = elements;
18
-
19
- reset();
20
- },
21
- reset = () => {
22
- elements = [];
23
- fragment = cloneNode.call(EMPTY_FRAGMENT);
24
- },
25
- template = function(data, i) {
26
- hydrate(elements, fragment, factory.call(this, data, i));
27
- } as (this: typeof array, ...args: Parameters<Parameters<typeof array['map']>[0]>) => void;
28
-
29
- array.on('clear', () => slot.clear());
30
- array.on('pop', () => slot.pop());
31
- array.on('reverse', refresh);
32
- array.on('shift', () => slot.shift());
33
- array.on('sort', refresh);
34
-
35
- array.on('push', ({ items }) => {
36
- let anchor = slot.anchor();
37
-
38
- elements = slot.nodes;
39
-
40
- root(() => array.map(template, array.length - items.length));
41
-
42
- anchor.after(fragment);
43
- reset();
44
- });
45
- array.on('splice', ({ deleteCount: d, items: i, start: s }) => {
46
- if (array.length === 0) {
47
- slot.clear();
48
- return;
49
- }
50
-
51
- root(() => array.map(template, s, i.length))
52
-
53
- slot.splice(s, d, fragment, ...elements);
54
- reset();
55
- });
56
- array.on('unshift', ({ items }) => {
57
- root(() => array.map(template, 0, items.length))
58
-
59
- slot.unshift(fragment, ...elements);
60
- reset();
61
- });
62
-
63
- root(() => array.map(template));
64
- reset();
65
- }
66
-
67
- function hydrate<T>(elements: Elements[] | null, fragment: Fragment, renderable: RenderableTemplate<T>) {
68
- let { fragment: frag, slots } = cache.get(renderable.literals),
69
- clone = cloneNode.call(frag, true);
8
+ if (slots === null) {
9
+ return clone as Fragment;
10
+ }
70
11
 
71
- if (slots !== null) {
72
- let node,
73
- nodePath,
74
- parent,
75
- parentPath,
76
- values = renderable.values;
12
+ let node,
13
+ nodePath,
14
+ parent,
15
+ parentPath;
77
16
 
78
- for (let i = 0, n = slots.length; i < n; i++) {
79
- let { fn, path, slot } = slots[i],
80
- pp = path.parent,
81
- pr = path.relative;
17
+ for (let i = 0, n = slots.length; i < n; i++) {
18
+ let { fn, path, slot } = slots[i],
19
+ pp = path.parent,
20
+ pr = path.relative;
82
21
 
83
- if (pp !== parentPath) {
84
- if (pp === nodePath) {
85
- parent = node;
86
- parentPath = nodePath;
22
+ if (pp !== parentPath) {
23
+ if (pp === nodePath) {
24
+ parent = node;
25
+ parentPath = nodePath;
87
26
 
88
- nodePath = undefined;
89
- }
90
- else {
91
- parent = clone;
92
- parentPath = pp;
27
+ nodePath = undefined;
28
+ }
29
+ else {
30
+ parent = clone;
31
+ parentPath = pp;
93
32
 
94
- for (let o = 0, j = pp.length; o < j; o++) {
95
- parent = pp[o].call(parent);
96
- }
33
+ for (let i = 0, n = pp.length; i < n; i++) {
34
+ parent = pp[i].call(parent);
97
35
  }
98
36
  }
37
+ }
99
38
 
100
- if (pr !== nodePath) {
101
- node = parent;
102
- nodePath = path.absolute;
39
+ if (pr !== nodePath) {
40
+ node = parent;
41
+ nodePath = path.absolute;
103
42
 
104
- for (let o = 0, j = pr.length; o < j; o++) {
105
- node = pr[o].call(node);
106
- }
43
+ for (let i = 0, n = pr.length; i < n; i++) {
44
+ node = pr[i].call(node);
107
45
  }
108
-
109
- // @ts-ignore
110
- fn(node, values[slot]);
111
46
  }
112
- }
113
47
 
114
- if (elements) {
115
- elements.push([...clone.childNodes] as Elements);
48
+ // @ts-ignore
49
+ fn(node, values[slot]);
116
50
  }
117
51
 
118
- fragment.appendChild(clone)
119
- }
120
-
121
-
122
- export default {
123
- reactive,
124
- static: hydrate
125
- };
52
+ return clone as Fragment;
53
+ }
package/src/html/index.ts CHANGED
@@ -1,17 +1,25 @@
1
1
  import { ReactiveArray } from '@esportsplus/reactivity';
2
- import { RENDERABLE, RENDERABLE_REACTIVE, RENDERABLE_TEMPLATE } from '~/constants';
3
- import { RenderableReactive, RenderableTemplate } from '~/types';
2
+ import { RENDERABLE, RENDERABLE_HTML_FRAGMENT, RENDERABLE_HTML_REACTIVE_ARRAY } from '~/constants';
3
+ import { RenderableReactive, RenderableTemplate, RenderableValues } from '~/types';
4
4
  import hydrate from './hydrate';
5
+ import parser from './parser';
5
6
 
6
7
 
7
- const html = <T>(literals: TemplateStringsArray, ...values: RenderableTemplate<T>['values']): RenderableTemplate<T> => {
8
- return { [RENDERABLE]: RENDERABLE_TEMPLATE, literals, template: null, values };
8
+ const html = (literals: TemplateStringsArray, ...values: RenderableValues[]): RenderableTemplate => {
9
+ return {
10
+ [RENDERABLE]: RENDERABLE_HTML_FRAGMENT,
11
+ fragment: hydrate(parser.parse(literals), values),
12
+ literals
13
+ };
9
14
  };
10
15
 
11
- html.reactive = <T>(array: ReactiveArray<T>, template: RenderableReactive<T>['template']): RenderableReactive<T> => {
12
- return { [RENDERABLE]: RENDERABLE_REACTIVE, literals: null, template, values: array };
16
+ html.reactive = <T>(array: ReactiveArray<T[]>, template: RenderableReactive['template']) => {
17
+ return {
18
+ [RENDERABLE]: RENDERABLE_HTML_REACTIVE_ARRAY,
19
+ array,
20
+ template
21
+ };
13
22
  };
14
23
 
15
24
 
16
- export default html;
17
- export { hydrate };
25
+ export default html;
@@ -3,7 +3,9 @@ import {
3
3
  REGEX_SLOT_NODES, SLOT_HTML, SLOT_MARKER, SLOT_MARKER_LENGTH
4
4
  } from '~/constants';
5
5
  import { Template } from '~/types';
6
- import { firstChild, firstElementChild, fragment, nextElementSibling, nextSibling } from '~/utilities';
6
+ import { firstElementChild, nextElementSibling } from '~/utilities/element';
7
+ import { fragment } from '~/utilities/fragment';
8
+ import { firstChild, nextSibling } from '~/utilities/node';
7
9
  import { spread } from '~/attributes';
8
10
  import s from '~/slot';
9
11
 
@@ -137,9 +139,9 @@ function set(literals: TemplateStringsArray, html: string, slots: Template['slot
137
139
  }
138
140
 
139
141
 
140
- const get = (literals: TemplateStringsArray) => {
142
+ const parse = (literals: TemplateStringsArray) => {
141
143
  return cache.get(literals) || build(literals);
142
144
  };
143
145
 
144
146
 
145
- export default { get };
147
+ export default { parse };
package/src/render.ts CHANGED
@@ -1,21 +1,17 @@
1
- import { isInstanceOf } from '@esportsplus/utilities';
2
1
  import { SLOT_HTML } from './constants';
3
- import slot, { Slot } from './slot';
4
2
  import { Renderable } from './types';
5
- import { firstChild, fragment, nodeValue, prepend } from './utilities';
3
+ import { fragment } from './utilities/fragment';
4
+ import { firstChild, nodeValue } from './utilities/node';
5
+ import slot from './slot';
6
6
 
7
7
 
8
- let marker = firstChild.call( fragment(SLOT_HTML) ),
9
- node;
8
+ let anchor,
9
+ marker = firstChild.call( fragment(SLOT_HTML) );
10
10
 
11
11
 
12
- export default (renderable: Renderable, parent: HTMLElement | Slot) => {
13
- if (isInstanceOf(parent, Slot)) {
14
- return parent.render(renderable);
15
- }
16
-
12
+ export default (parent: HTMLElement, renderable: Renderable) => {
17
13
  nodeValue.call(parent, '');
18
- prepend.call(parent, node = marker.cloneNode());
14
+ parent.append(anchor = marker.cloneNode());
19
15
 
20
- return slot(node, renderable);
16
+ slot(anchor, renderable);
21
17
  };
@@ -0,0 +1,74 @@
1
+ import queue from '@esportsplus/queue';
2
+ import { CLEANUP } from '~/constants';
3
+ import { microtask } from '~/utilities/queue';
4
+ import { previousSibling } from '~/utilities/node';
5
+ import { SlotGroup } from '~/types';
6
+
7
+
8
+ let cleanup = queue<VoidFunction[]>(64),
9
+ scheduled = false;
10
+
11
+
12
+ function schedule() {
13
+ if (!cleanup.length || scheduled) {
14
+ return;
15
+ }
16
+
17
+ scheduled = true;
18
+ microtask.add(task);
19
+ }
20
+
21
+ function task() {
22
+ try {
23
+ let fns,
24
+ fn,
25
+ n = cleanup.length;
26
+
27
+ while ((fns = cleanup.next()) && n--) {
28
+ while (fn = fns.pop()) {
29
+ fn();
30
+ }
31
+ }
32
+ }
33
+ catch { }
34
+
35
+ if (cleanup.length) {
36
+ microtask.add(task);
37
+ }
38
+ else {
39
+ scheduled = false;
40
+ }
41
+ }
42
+
43
+
44
+ const ondisconnect = (element: Element, fn: VoidFunction) => {
45
+ ((element as any)[CLEANUP] ??= []).push(fn);
46
+ };
47
+
48
+ const remove = (groups: SlotGroup[]) => {
49
+ let group, head, tail;
50
+
51
+ while (group = groups.pop()) {
52
+ head = group.head;
53
+ tail = group.tail || head;
54
+
55
+ for (let node = tail; node; node = previousSibling.call(node)) {
56
+ if (CLEANUP in node) {
57
+ cleanup.add( node[CLEANUP] as VoidFunction[] );
58
+ }
59
+
60
+ node.remove();
61
+
62
+ if (head === node) {
63
+ break;
64
+ }
65
+ }
66
+ }
67
+
68
+ if (!scheduled && cleanup.length) {
69
+ schedule();
70
+ }
71
+ };
72
+
73
+
74
+ export { ondisconnect, remove };