@esportsplus/template 0.15.15 → 0.15.17

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
@@ -2,26 +2,44 @@ import { effect, root } from '@esportsplus/reactivity';
2
2
  import { isArray, isFunction, isInstanceOf, isObject } from '@esportsplus/utilities';
3
3
  import { RENDERABLE, RENDERABLE_REACTIVE, SLOT_CLEANUP } 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, RenderableReactive, RenderableTemplate, RenderedGroup } from './types';
6
+ import { append, 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),
10
+ let cleanup = queue<VoidFunction[]>(64),
11
+ fallback = fragment(''),
11
12
  scheduled = false;
12
13
 
13
14
 
14
- function after(anchor: Element, groups: Elements[]) {
15
- for (let i = 0, n = groups.length; i < n; i++) {
16
- let group = groups[i];
15
+ function after(anchor: Element, groups: RenderedGroup[]) {
16
+ let elements: Elements[] = [],
17
+ n = groups.length;
18
+
19
+ if (n) {
20
+ let fragment = groups[0].fragment || fallback;
21
+
22
+ if (n === 1) {
23
+ elements.push( groups[0].elements );
24
+ }
25
+ else {
26
+ for (let i = 1; i < n; i++) {
27
+ let group = groups[i];
28
+
29
+ if (group.fragment) {
30
+ append.call(fragment, group.fragment);
31
+ group.fragment = null;
32
+ }
17
33
 
18
- if (group.length) {
19
- anchor.after(anchor, ...group);
20
- anchor = group.at(-1)!;
34
+ elements.push(group.elements);
35
+ }
21
36
  }
37
+
38
+ anchor.after(fragment);
39
+ groups[0].fragment = null;
22
40
  }
23
41
 
24
- return groups;
42
+ return elements;
25
43
  }
26
44
 
27
45
  function remove(...groups: Elements[]) {
@@ -32,7 +50,7 @@ function remove(...groups: Elements[]) {
32
50
  let item = group[j];
33
51
 
34
52
  if (item[SLOT_CLEANUP]) {
35
- cleanup.add( item[SLOT_CLEANUP] );
53
+ cleanup.add(item[SLOT_CLEANUP]);
36
54
  }
37
55
 
38
56
  item.remove();
@@ -46,62 +64,57 @@ 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(groups: RenderedGroup[], input: unknown, slot?: Slot) {
68
+ if (input === false || input == null || input === '') {
69
+ return groups;
52
70
  }
53
- else if (isObject(input)) {
54
- if (isArray(input)) {
55
- let groups: Elements[] = [];
56
71
 
57
- for (let i = 0, n = input.length; i < n; i++) {
58
- groups.push( render(null, input[i]) as Elements );
59
- }
60
-
61
- return anchor ? after(anchor, groups) : groups;
72
+ if (isArray(input)) {
73
+ for (let i = 0, n = input.length; i < n; i++) {
74
+ render(groups, input[i]);
62
75
  }
63
-
64
- let nodes: Elements = [];
65
-
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);
77
- }
76
+ }
77
+ else if (isObject(input) && RENDERABLE in input) {
78
+ if (input[RENDERABLE] === RENDERABLE_REACTIVE) {
79
+ groups.push(
80
+ ...hydrate.reactive(input as RenderableReactive, slot!)
81
+ );
78
82
  }
79
- else if (isInstanceOf(input, Node)) {
80
- nodes = [input] as Elements;
83
+ else {
84
+ groups.push(
85
+ hydrate.static(input as RenderableTemplate<unknown>)
86
+ );
81
87
  }
88
+ }
89
+ else if (isInstanceOf(input, NodeList)) {
90
+ let elements: Elements = [];
82
91
 
83
- if (anchor) {
84
- anchor.after(...nodes);
92
+ for (let node = firstChild.call(input); node; node = nextSibling.call(node)) {
93
+ elements.push(node);
85
94
  }
86
95
 
87
- return nodes;
96
+ groups.push({ elements, fragment: null });
88
97
  }
89
-
90
- if (input === '') {
91
- return [];
98
+ else if (isInstanceOf(input, Node)) {
99
+ groups.push({
100
+ elements: [ input as Element ],
101
+ fragment: input as RenderedGroup['fragment']
102
+ });
92
103
  }
93
-
94
- let node = text(input as string);
95
-
96
- if (anchor) {
97
- anchor.after(node);
104
+ else {
105
+ let element = text( typeof input === 'string' ? input : String(input) );
98
106
 
99
107
  if (slot) {
100
- slot.text = node;
108
+ slot.text = element;
101
109
  }
110
+
111
+ groups.push({
112
+ elements: [ element ],
113
+ fragment: element
114
+ });
102
115
  }
103
116
 
104
- return [ node ];
117
+ return groups;
105
118
  }
106
119
 
107
120
  function schedule() {
@@ -110,20 +123,19 @@ function schedule() {
110
123
  }
111
124
 
112
125
  scheduled = true;
113
-
114
126
  microtask.add(task);
115
127
  }
116
128
 
117
129
  function task() {
118
- let fns;
130
+ let fn, fns;
119
131
 
120
132
  while (fns = cleanup.next()) {
121
- for (let i = 0, n = fns.length; i < n; i++) {
122
- try {
123
- fns[i]();
133
+ try {
134
+ while (fn = fns.pop()) {
135
+ fn();
124
136
  }
125
- catch { }
126
137
  }
138
+ catch { }
127
139
  }
128
140
 
129
141
  scheduled = false;
@@ -170,6 +182,7 @@ class Slot {
170
182
 
171
183
  clear() {
172
184
  remove(...this.nodes);
185
+ this.nodes.length = 0;
173
186
  this.text = null;
174
187
  }
175
188
 
@@ -183,14 +196,8 @@ class Slot {
183
196
  return remove(group);
184
197
  }
185
198
 
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;
199
+ push(...groups: RenderedGroup[]) {
200
+ return this.nodes.push( ...after(this.anchor(), groups) );
194
201
  }
195
202
 
196
203
  render(input: unknown) {
@@ -198,7 +205,7 @@ class Slot {
198
205
  ondisconnect(
199
206
  this.marker,
200
207
  effect(() => {
201
- let v = (input as Function)();
208
+ let v = input();
202
209
 
203
210
  if (isFunction(v)) {
204
211
  root(() => this.render(v()));
@@ -217,7 +224,8 @@ class Slot {
217
224
  if (this.text) {
218
225
  let type = typeof input;
219
226
 
220
- if (type === 'object' && input !== null) {}
227
+ if (type === 'object' && input !== null) {
228
+ }
221
229
  else if (this.text.isConnected) {
222
230
  nodeValue.call(
223
231
  this.text,
@@ -228,13 +236,7 @@ class Slot {
228
236
  }
229
237
 
230
238
  this.clear();
231
-
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
- }
239
+ this.nodes = after(this.marker, render([], input, this));
238
240
 
239
241
  return this;
240
242
  }
@@ -249,14 +251,18 @@ class Slot {
249
251
  return remove(group);
250
252
  }
251
253
 
252
- splice(start: number, stop: number = this.nodes.length, ...groups: Elements[]) {
254
+ splice(start: number, stop: number = this.nodes.length, ...groups: RenderedGroup[]) {
253
255
  return remove(
254
- ...this.nodes.splice(start, stop, ...after(this.anchor(start), groups))
256
+ ...this.nodes.splice(
257
+ start,
258
+ stop,
259
+ ...after(this.anchor(start), groups)
260
+ )
255
261
  );
256
262
  }
257
263
 
258
- unshift(...groups: Elements[]) {
259
- return this.nodes.unshift(...after(this.marker, groups));
264
+ unshift(...groups: RenderedGroup[]) {
265
+ return this.nodes.unshift( ...after(this.marker, groups) );
260
266
  }
261
267
  }
262
268
 
package/src/types.ts CHANGED
@@ -1,8 +1,11 @@
1
1
  import { ReactiveArray } from '@esportsplus/reactivity';
2
- import { RENDERABLE, RENDERABLE_REACTIVE, RENDERABLE_TEMPLATE, SLOT_CLEANUP } from './constants';
2
+ import {
3
+ ATTRIBUTE_STORE,
4
+ RENDERABLE, RENDERABLE_REACTIVE, RENDERABLE_TEMPLATE,
5
+ SLOT_CLEANUP
6
+ } from './constants';
3
7
  import { firstChild } from './utilities';
4
8
  import attributes from './attributes';
5
- import event from './event';
6
9
  import slot from './slot';
7
10
 
8
11
 
@@ -27,7 +30,10 @@ type Effect<T> = () => EffectResponse<T>;
27
30
 
28
31
  type EffectResponse<T> = T extends [] ? EffectResponse<T[number]>[] : Primitive | Renderable<T>;
29
32
 
30
- type Element = HTMLElement & Attributes & { [SLOT_CLEANUP]?: VoidFunction[] } & Record<PropertyKey, unknown>;
33
+ type Element = HTMLElement & Attributes & {
34
+ [ATTRIBUTE_STORE]?: Record<PropertyKey, unknown>;
35
+ [SLOT_CLEANUP]?: VoidFunction[]
36
+ } & Record<PropertyKey, unknown>;
31
37
 
32
38
  type Elements = Element[];
33
39
 
@@ -56,12 +62,14 @@ type RenderableTemplate<T> = {
56
62
 
57
63
  type RenderableValue<T = unknown> = Attributes | Readonly<Attributes> | Readonly<Attributes[]> | Effect<T> | Primitive | Renderable;
58
64
 
65
+ type RenderedGroup = { elements: Elements, fragment: DocumentFragment | Node | null };
66
+
59
67
  type Template = {
60
68
  fragment: DocumentFragment;
61
69
  html: string;
62
70
  literals: TemplateStringsArray;
63
71
  slots: {
64
- fn: typeof attributes.spread | typeof event | typeof slot;
72
+ fn: typeof attributes.spread | typeof slot;
65
73
  path: typeof firstChild[];
66
74
  slot: number;
67
75
  }[] | null;
@@ -71,6 +79,6 @@ type Template = {
71
79
  export type {
72
80
  Attributes,
73
81
  Effect, Element, Elements,
74
- Renderable, RenderableReactive, RenderableTemplate, RenderableValue,
82
+ Renderable, RenderableReactive, RenderableTemplate, RenderableValue, RenderedGroup,
75
83
  Template
76
84
  };
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,