@esportsplus/template 0.15.19 → 0.16.1

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