@esportsplus/template 0.15.16 → 0.15.18

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.

Potentially problematic release.


This version of @esportsplus/template might be problematic. Click here for more details.

package/src/slot.ts CHANGED
@@ -1,27 +1,45 @@
1
1
  import { effect, root } from '@esportsplus/reactivity';
2
2
  import { isArray, isFunction, isInstanceOf, isObject } from '@esportsplus/utilities';
3
- import { RENDERABLE, RENDERABLE_REACTIVE, SLOT_CLEANUP } from './constants';
3
+ import { RENDERABLE, RENDERABLE_REACTIVE } from './constants';
4
4
  import { hydrate } from './html';
5
- import { Element, Elements, RenderableReactive, RenderableTemplate } from './types';
6
- import { firstChild, microtask, nextSibling, nodeValue, raf, text } from './utilities'
5
+ import { Element, Elements, HydrateResult, RenderableReactive, RenderableTemplate } from './types';
6
+ import { append, cloneNode, firstChild, fragment, microtask, nextSibling, nodeValue, raf, text } from './utilities'
7
7
  import queue from '@esportsplus/queue';
8
8
 
9
9
 
10
- let cleanup = queue<VoidFunction[]>(1024),
11
- scheduled = false;
10
+ const CLEANUP_KEY = Symbol();
12
11
 
13
12
 
14
- function after(anchor: Element, groups: Elements[]) {
15
- for (let i = 0, n = groups.length; i < n; i++) {
16
- let group = groups[i];
13
+ let cleanup = queue<VoidFunction[]>(64),
14
+ scheduled = false,
15
+ template = fragment('');
16
+
17
+
18
+ function after(anchor: Element, groups: HydrateResult[]) {
19
+ let n = groups.length;
20
+
21
+ if (n === 0) {
22
+ return [];
23
+ }
24
+
25
+ let elements: Elements[] = [],
26
+ fragment = groups[0].fragment || cloneNode.call(template);
27
+
28
+ if (n === 1) {
29
+ elements.push( groups[0].elements );
30
+ }
31
+ else {
32
+ for (let i = 1; i < n; i++) {
33
+ let { elements: e, fragment: f } = groups[i];
17
34
 
18
- if (group.length) {
19
- anchor.after(anchor, ...group);
20
- anchor = group.at(-1)!;
35
+ append.call(fragment, f);
36
+ elements.push(e);
21
37
  }
22
38
  }
23
39
 
24
- return groups;
40
+ anchor.after(fragment);
41
+
42
+ return elements;
25
43
  }
26
44
 
27
45
  function remove(...groups: Elements[]) {
@@ -31,8 +49,8 @@ function remove(...groups: Elements[]) {
31
49
  for (let j = 0, o = group.length; j < o; j++) {
32
50
  let item = group[j];
33
51
 
34
- if (item[SLOT_CLEANUP]) {
35
- cleanup.add( item[SLOT_CLEANUP] );
52
+ if (CLEANUP_KEY in item) {
53
+ cleanup.add(item[CLEANUP_KEY] as VoidFunction[]);
36
54
  }
37
55
 
38
56
  item.remove();
@@ -46,62 +64,58 @@ function remove(...groups: Elements[]) {
46
64
  return groups;
47
65
  }
48
66
 
49
- function render(anchor: Element | null, input: unknown, slot?: Slot): Elements | Elements[] {
50
- if (input === false || input == null) {
51
- input = '';
67
+ function render(elements: Elements[], fragment: DocumentFragment | Node, input: unknown, slot?: Slot) {
68
+ if (input === false || input == null || input === '') {
69
+ return;
52
70
  }
53
- else if (isObject(input)) {
54
- if (isArray(input)) {
55
- let groups: Elements[] = [];
56
-
57
- for (let i = 0, n = input.length; i < n; i++) {
58
- groups.push( render(null, input[i]) as Elements );
59
- }
60
71
 
61
- return anchor ? after(anchor, groups) : groups;
72
+ if (isArray(input)) {
73
+ for (let i = 0, n = input.length; i < n; i++) {
74
+ render(elements, fragment, input[i]);
62
75
  }
76
+ }
77
+ else if (isObject(input) && RENDERABLE in input) {
78
+ if (input[RENDERABLE] === RENDERABLE_REACTIVE) {
79
+ let response = hydrate.reactive(input as RenderableReactive, slot!);
63
80
 
64
- let nodes: Elements = [];
81
+ for (let i = 0, n = response.length; i < n; i++) {
82
+ let { elements: e, fragment: f } = response[i];
65
83
 
66
- if (RENDERABLE in input) {
67
- if (input[RENDERABLE] === RENDERABLE_REACTIVE) {
68
- return after(anchor!, hydrate.reactive(input as RenderableReactive, slot!));
69
- }
70
- else {
71
- nodes = hydrate.static(input as RenderableTemplate<unknown>);
72
- }
73
- }
74
- else if (isInstanceOf(input, NodeList)) {
75
- for (let node = firstChild.call(input); node; node = nextSibling.call(node)) {
76
- nodes.push(node);
84
+ append.call(fragment, f);
85
+ elements.push(e);
77
86
  }
78
87
  }
79
- else if (isInstanceOf(input, Node)) {
80
- nodes = [input] as Elements;
88
+ else {
89
+ let { elements: e, fragment: f } = hydrate.static(input as RenderableTemplate<unknown>);
90
+
91
+ append.call(fragment, f);
92
+ elements.push(e);
81
93
  }
94
+ }
95
+ else if (isInstanceOf(input, NodeList)) {
96
+ let e: Elements = [];
82
97
 
83
- if (anchor) {
84
- anchor.after(...nodes);
98
+ for (let node = firstChild.call(input); node; node = nextSibling.call(node)) {
99
+ e.push(node);
85
100
  }
86
101
 
87
- return nodes;
102
+ append.call(fragment, ...e);
103
+ elements.push(e);
88
104
  }
89
-
90
- if (input === '') {
91
- return [];
105
+ else if (isInstanceOf(input, Node)) {
106
+ append.call(fragment, input);
107
+ elements.push([ input as Element ]);
92
108
  }
93
-
94
- let node = text(input as string);
95
-
96
- if (anchor) {
97
- anchor.after(node);
109
+ else {
110
+ let element = text( typeof input === 'string' ? input : String(input) );
98
111
 
99
112
  if (slot) {
100
- slot.text = node;
113
+ slot.text = element;
101
114
  }
102
- }
103
115
 
104
- return [ node ];
116
+ append.call(fragment, element);
117
+ elements.push([ element ]);
118
+ }
105
119
  }
106
120
 
107
121
  function schedule() {
@@ -110,20 +124,19 @@ function schedule() {
110
124
  }
111
125
 
112
126
  scheduled = true;
113
-
114
127
  microtask.add(task);
115
128
  }
116
129
 
117
130
  function task() {
118
- let fns;
131
+ let fn, fns;
119
132
 
120
133
  while (fns = cleanup.next()) {
121
- for (let i = 0, n = fns.length; i < n; i++) {
122
- try {
123
- fns[i]();
134
+ try {
135
+ while (fn = fns.pop()) {
136
+ fn();
124
137
  }
125
- catch { }
126
138
  }
139
+ catch { }
127
140
  }
128
141
 
129
142
  scheduled = false;
@@ -162,7 +175,7 @@ class Slot {
162
175
  nodes = this.nodes[index];
163
176
 
164
177
  if (nodes) {
165
- node = nodes.at(-1);
178
+ node = nodes[nodes.length - 1];
166
179
  }
167
180
 
168
181
  return node || this.marker;
@@ -170,6 +183,7 @@ class Slot {
170
183
 
171
184
  clear() {
172
185
  remove(...this.nodes);
186
+ this.nodes.length = 0;
173
187
  this.text = null;
174
188
  }
175
189
 
@@ -183,14 +197,8 @@ class Slot {
183
197
  return remove(group);
184
198
  }
185
199
 
186
- push(...groups: Elements[]) {
187
- after(this.anchor(), groups);
188
-
189
- for (let i = 0, n = groups.length; i < n; i++) {
190
- this.nodes.push(groups[i]);
191
- }
192
-
193
- return this.nodes.length;
200
+ push(...groups: HydrateResult[]) {
201
+ return this.nodes.push( ...after(this.anchor(), groups) );
194
202
  }
195
203
 
196
204
  render(input: unknown) {
@@ -198,7 +206,7 @@ class Slot {
198
206
  ondisconnect(
199
207
  this.marker,
200
208
  effect(() => {
201
- let v = (input as Function)();
209
+ let v = input();
202
210
 
203
211
  if (isFunction(v)) {
204
212
  root(() => this.render(v()));
@@ -217,7 +225,8 @@ class Slot {
217
225
  if (this.text) {
218
226
  let type = typeof input;
219
227
 
220
- if (type === 'object' && input !== null) {}
228
+ if (type === 'object' && input !== null) {
229
+ }
221
230
  else if (this.text.isConnected) {
222
231
  nodeValue.call(
223
232
  this.text,
@@ -229,12 +238,13 @@ class Slot {
229
238
 
230
239
  this.clear();
231
240
 
232
- if (isArray(input) || (isObject(input) && input[RENDERABLE] === RENDERABLE_REACTIVE)) {
233
- this.nodes = render(this.marker, input, this) as Elements[];
234
- }
235
- else {
236
- this.nodes = [ render(this.marker, input, this) as Elements ];
237
- }
241
+ let fragment = cloneNode.call(template),
242
+ nodes: Elements[] = [];
243
+
244
+ render(nodes, fragment, input, this);
245
+
246
+ this.marker.after(fragment);
247
+ this.nodes = nodes;
238
248
 
239
249
  return this;
240
250
  }
@@ -249,20 +259,24 @@ class Slot {
249
259
  return remove(group);
250
260
  }
251
261
 
252
- splice(start: number, stop: number = this.nodes.length, ...groups: Elements[]) {
262
+ splice(start: number, stop: number = this.nodes.length, ...groups: HydrateResult[]) {
253
263
  return remove(
254
- ...this.nodes.splice(start, stop, ...after(this.anchor(start), groups))
264
+ ...this.nodes.splice(
265
+ start,
266
+ stop,
267
+ ...after(this.anchor(start), groups)
268
+ )
255
269
  );
256
270
  }
257
271
 
258
- unshift(...groups: Elements[]) {
259
- return this.nodes.unshift(...after(this.marker, groups));
272
+ unshift(...groups: HydrateResult[]) {
273
+ return this.nodes.unshift( ...after(this.marker, groups) );
260
274
  }
261
275
  }
262
276
 
263
277
 
264
278
  const ondisconnect = (element: Element, fn: VoidFunction) => {
265
- ( element[SLOT_CLEANUP] ??= [] ).push(fn);
279
+ ((element[CLEANUP_KEY] ??= []) as VoidFunction[]).push(fn);
266
280
  };
267
281
 
268
282
 
package/src/types.ts CHANGED
@@ -1,8 +1,7 @@
1
1
  import { ReactiveArray } from '@esportsplus/reactivity';
2
- import { RENDERABLE, RENDERABLE_REACTIVE, RENDERABLE_TEMPLATE, SLOT_CLEANUP } from './constants';
2
+ import { RENDERABLE, RENDERABLE_REACTIVE, RENDERABLE_TEMPLATE } from './constants';
3
3
  import { firstChild } from './utilities';
4
4
  import attributes from './attributes';
5
- import event from './event';
6
5
  import slot from './slot';
7
6
 
8
7
 
@@ -27,10 +26,12 @@ type Effect<T> = () => EffectResponse<T>;
27
26
 
28
27
  type EffectResponse<T> = T extends [] ? EffectResponse<T[number]>[] : Primitive | Renderable<T>;
29
28
 
30
- type Element = HTMLElement & Attributes & { [SLOT_CLEANUP]?: VoidFunction[] } & Record<PropertyKey, unknown>;
29
+ type Element = HTMLElement & Attributes & Record<PropertyKey, unknown>;
31
30
 
32
31
  type Elements = Element[];
33
32
 
33
+ type HydrateResult = { elements: Elements, fragment: DocumentFragment | Node };
34
+
34
35
  // Copied from '@esportsplus/utilities'
35
36
  // - Importing from ^ causes 'cannot be named without a reference to...' error
36
37
  type Primitive = bigint | boolean | null | number | string | undefined;
@@ -61,7 +62,7 @@ type Template = {
61
62
  html: string;
62
63
  literals: TemplateStringsArray;
63
64
  slots: {
64
- fn: typeof attributes.spread | typeof event | typeof slot;
65
+ fn: typeof attributes.spread | typeof slot;
65
66
  path: typeof firstChild[];
66
67
  slot: number;
67
68
  }[] | null;
@@ -71,6 +72,7 @@ type Template = {
71
72
  export type {
72
73
  Attributes,
73
74
  Effect, Element, Elements,
75
+ HydrateResult,
74
76
  Renderable, RenderableReactive, RenderableTemplate, RenderableValue,
75
77
  Template
76
78
  };
package/src/utilities.ts CHANGED
@@ -12,6 +12,8 @@ prototype = Element.prototype;
12
12
 
13
13
  const addEventListener = prototype.addEventListener;
14
14
 
15
+ const append = prototype.append;
16
+
15
17
  const removeEventListener = prototype.removeEventListener;
16
18
 
17
19
  const className = Object.getOwnPropertyDescriptor(prototype, 'className')!.set!;
@@ -70,7 +72,7 @@ const text = (value: string) => {
70
72
 
71
73
 
72
74
  export {
73
- addEventListener,
75
+ addEventListener, append,
74
76
  className, cloneNode,
75
77
  firstChild, firstElementChild, fragment,
76
78
  innerHTML,