@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.

@@ -3,6 +3,7 @@ declare const addEventListener: {
3
3
  <K extends keyof ElementEventMap>(type: K, listener: (this: Element, ev: ElementEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void;
4
4
  (type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void;
5
5
  };
6
+ declare const append: (...nodes: (Node | string)[]) => void;
6
7
  declare const removeEventListener: {
7
8
  <K extends keyof ElementEventMap>(type: K, listener: (this: Element, ev: ElementEventMap[K]) => any, options?: boolean | EventListenerOptions): void;
8
9
  (type: string, listener: EventListenerOrEventListenerObject, options?: boolean | EventListenerOptions): void;
@@ -24,4 +25,4 @@ declare const fragment: (html: string) => DocumentFragment;
24
25
  declare const microtask: import("@esportsplus/tasks/build/factory").Scheduler;
25
26
  declare const raf: import("@esportsplus/tasks/build/factory").Scheduler;
26
27
  declare const text: (value: string) => E;
27
- export { addEventListener, className, cloneNode, firstChild, firstElementChild, fragment, innerHTML, microtask, nextElementSibling, nextSibling, nodeValue, parentElement, parentNode, prepend, raf, removeAttribute, removeEventListener, setAttribute, text };
28
+ export { addEventListener, append, className, cloneNode, firstChild, firstElementChild, fragment, innerHTML, microtask, nextElementSibling, nextSibling, nodeValue, parentElement, parentNode, prepend, raf, removeAttribute, removeEventListener, setAttribute, text };
@@ -2,6 +2,7 @@ import { micro as m, raf as r } from '@esportsplus/tasks';
2
2
  let prototype, template = document.createElement('template'), t = document.createTextNode('');
3
3
  prototype = Element.prototype;
4
4
  const addEventListener = prototype.addEventListener;
5
+ const append = prototype.append;
5
6
  const removeEventListener = prototype.removeEventListener;
6
7
  const className = Object.getOwnPropertyDescriptor(prototype, 'className').set;
7
8
  const innerHTML = Object.getOwnPropertyDescriptor(prototype, 'innerHTML').set;
@@ -32,4 +33,4 @@ const text = (value) => {
32
33
  }
33
34
  return element;
34
35
  };
35
- export { addEventListener, className, cloneNode, firstChild, firstElementChild, fragment, innerHTML, microtask, nextElementSibling, nextSibling, nodeValue, parentElement, parentNode, prepend, raf, removeAttribute, removeEventListener, setAttribute, text };
36
+ export { addEventListener, append, className, cloneNode, firstChild, firstElementChild, fragment, innerHTML, microtask, nextElementSibling, nextSibling, nodeValue, parentElement, parentNode, prepend, raf, removeAttribute, removeEventListener, setAttribute, text };
package/package.json CHANGED
@@ -2,9 +2,9 @@
2
2
  "author": "ICJR",
3
3
  "dependencies": {
4
4
  "@esportsplus/queue": "^0.1.0",
5
- "@esportsplus/reactivity": "^0.12.3",
5
+ "@esportsplus/reactivity": "^0.12.4",
6
6
  "@esportsplus/tasks": "^0.2.1",
7
- "@esportsplus/utilities": "^0.21.1"
7
+ "@esportsplus/utilities": "^0.22.1"
8
8
  },
9
9
  "devDependencies": {
10
10
  "@esportsplus/typescript": "^0.9.2"
@@ -14,7 +14,7 @@
14
14
  "private": false,
15
15
  "type": "module",
16
16
  "types": "./build/index.d.ts",
17
- "version": "0.15.16",
17
+ "version": "0.15.18",
18
18
  "scripts": {
19
19
  "build": "tsc && tsc-alias",
20
20
  "-": "-"
package/src/attributes.ts CHANGED
@@ -3,15 +3,42 @@ import { isArray, isFunction, isObject, isString } from '@esportsplus/utilities'
3
3
  import { ondisconnect } from './slot';
4
4
  import { Attributes, Element } from './types';
5
5
  import { className, raf, removeAttribute, setAttribute } from './utilities';
6
+ import q from '@esportsplus/queue';
6
7
  import event from './event';
7
8
 
8
9
 
9
- let attributes: Record<string, unknown> = {},
10
- delimiters: Record<string, string> = {
10
+ const EFFECT_KEY = Symbol();
11
+
12
+ const HYDRATE_KEY = Symbol();
13
+
14
+ const STORE_KEY = Symbol();
15
+
16
+ const UPDATES_KEY = Symbol();
17
+
18
+
19
+ const STATE_HYDRATING = 0;
20
+
21
+ const STATE_NONE = 1;
22
+
23
+ const STATE_WAITING = 2;
24
+
25
+
26
+ type Context = {
27
+ element: Element;
28
+ store: Record<PropertyKey, unknown>
29
+ updates: Record<PropertyKey, unknown>;
30
+ updating: boolean;
31
+ };
32
+
33
+ type State = typeof STATE_HYDRATING | typeof STATE_NONE | typeof STATE_WAITING;
34
+
35
+
36
+ let delimiters: Record<string, string> = {
11
37
  class: ' ',
12
38
  style: ';'
13
39
  },
14
- key = Symbol();
40
+ queue = q<Context>(64),
41
+ scheduled = false;
15
42
 
16
43
 
17
44
  function attribute(element: Element, name: string, value: unknown) {
@@ -29,73 +56,115 @@ function attribute(element: Element, name: string, value: unknown) {
29
56
  }
30
57
  }
31
58
 
32
- function set(element: Element, value: unknown, name: string, wait = false) {
59
+ function schedule() {
60
+ if (scheduled) {
61
+ return;
62
+ }
63
+
64
+ scheduled = true;
65
+ raf.add(task);
66
+ }
67
+
68
+ function set(context: Context, name: string, value: unknown, state: State) {
33
69
  if (isArray(value)) {
34
70
  for (let i = 0, n = value.length; i < n; i++) {
35
- set(element, value[i], name, wait);
71
+ set(context, name, value[i], state);
36
72
  }
37
73
  }
38
74
  else if (isFunction(value)) {
39
75
  if (name.startsWith('on')) {
40
- event(element, name as `on${string}`, value);
76
+ event(context.element, name as `on${string}`, value);
41
77
  }
42
78
  else {
43
- let id = ('e' + store(element)[key]++);
79
+ context.store[EFFECT_KEY] ??= 0;
80
+
81
+ let id = (context.store[EFFECT_KEY] as number)++;
44
82
 
45
83
  ondisconnect(
46
- element,
84
+ context.element,
47
85
  effect(() => {
48
- let v = (value as Function)(element);
86
+ let v = (value as Function)(context.element);
49
87
 
50
88
  if (isArray(v)) {
51
89
  let last = v.length - 1;
52
90
 
53
91
  for (let i = 0, n = v.length; i < n; i++) {
54
- update(element, id, name, v[i], wait || i !== last);
92
+ update(
93
+ context,
94
+ id,
95
+ name,
96
+ v[i],
97
+ state === STATE_HYDRATING
98
+ ? state
99
+ : i !== last ? STATE_WAITING : state
100
+ );
55
101
  }
56
102
  }
57
103
  else {
58
- update(element, id, name, v, wait);
104
+ update(context, id, name, v, state);
59
105
  }
60
106
  })
61
107
  );
62
108
 
63
- wait = false;
109
+ state = STATE_NONE;
64
110
  }
65
111
  }
66
112
  else {
67
- update(element, null, name, value, wait);
113
+ update(context, null, name, value, state);
68
114
  }
69
115
  }
70
116
 
71
- function store(element: Element) {
72
- return (
73
- element[key] || (element[key] = { [key]: 0 })
74
- ) as Attributes & { [key]: number };
117
+ function task() {
118
+ let context,
119
+ n = queue.length;
120
+
121
+ while ((context = queue.next()) && n--) {
122
+ let { element, updates } = context;
123
+
124
+ for (let name in updates) {
125
+ attribute(element, name, updates[name]);
126
+ delete updates[name];
127
+ }
128
+
129
+ context.updating = false;
130
+ }
131
+
132
+ if (queue.length) {
133
+ raf.add(task);
134
+ }
135
+ else {
136
+ scheduled = false;
137
+ }
75
138
  }
76
139
 
77
- function update(element: Element, id: null | string, name: string, value: unknown, wait = false) {
140
+ function update(
141
+ context: Context,
142
+ id: null | number,
143
+ name: string,
144
+ value: unknown,
145
+ state: State
146
+ ) {
78
147
  if (value === false || value == null) {
79
148
  value = '';
80
149
  }
81
150
 
82
- let data = store(element);
151
+ let store = context.store;
83
152
 
84
153
  if (name in delimiters) {
85
154
  let cache = name + '.static',
86
155
  delimiter = delimiters[name],
87
- dynamic = data[name] as Attributes | undefined;
156
+ dynamic = store[name] as Attributes | undefined;
88
157
 
89
158
  if (dynamic === undefined) {
90
- let value = (element.getAttribute(name) || '').trim();
159
+ let value = (context.element.getAttribute(name) || '').trim();
91
160
 
92
- data[cache] = value.endsWith(delimiter) ? value.slice(0, -1) : value;
93
- data[name] = dynamic = {};
161
+ store[cache] = value;
162
+ store[name] = dynamic = {};
94
163
  }
95
164
 
96
165
  if (id === null) {
97
166
  if (value && isString(value)) {
98
- data[cache] += (data[cache] ? delimiter : '') + value;
167
+ store[cache] += (store[cache] ? delimiter : '') + value;
99
168
  }
100
169
  }
101
170
  else {
@@ -117,7 +186,7 @@ function update(element: Element, id: null | string, name: string, value: unknow
117
186
  }
118
187
  }
119
188
 
120
- let cold = data[id] as Attributes | undefined;
189
+ let cold = store[id] as Attributes | undefined;
121
190
 
122
191
  if (cold !== undefined) {
123
192
  for (let part in cold) {
@@ -129,61 +198,80 @@ function update(element: Element, id: null | string, name: string, value: unknow
129
198
  }
130
199
  }
131
200
 
132
- data[id] = hot;
201
+ store[id] = hot;
133
202
  }
134
203
 
135
- value = data[cache];
204
+ value = store[cache];
136
205
 
137
206
  for (let key in dynamic) {
138
207
  value += (value ? delimiter : '') + key;
139
208
  }
140
209
  }
141
- else if (isString(id)) {
142
- if (data[name] === value) {
210
+ else if (id !== null) {
211
+ if (store[name] === value) {
143
212
  return;
144
213
  }
145
214
 
146
- data[name] = value as string;
215
+ store[name] = value as string;
147
216
  }
148
217
 
149
- if (wait) {
150
- if (id === null) {
151
- attributes[name] = value;
152
- }
218
+ if (state === STATE_HYDRATING) {
219
+ ((context.element[HYDRATE_KEY] ??= {}) as Record<PropertyKey, unknown>)[name] = value;
153
220
  }
154
221
  else {
155
- raf.add(() => {
156
- attribute(element, name, value);
157
- });
222
+ context.updates[name] = value;
223
+
224
+ if (state === STATE_NONE && !context.updating) {
225
+ context.updating = true;
226
+ queue.add(context);
227
+ }
228
+
229
+ if (!scheduled) {
230
+ schedule();
231
+ }
158
232
  }
159
233
  }
160
234
 
161
235
 
162
236
  const apply = (element: Element) => {
237
+ let attributes = element[HYDRATE_KEY] as Record<PropertyKey, unknown> | undefined;
238
+
239
+ if (!attributes) {
240
+ return;
241
+ }
242
+
163
243
  for (let key in attributes) {
164
244
  attribute(element, key, attributes[key]);
165
245
  }
166
246
 
167
- attributes = {};
247
+ delete element[HYDRATE_KEY];
168
248
  };
169
249
 
170
- const spread = function (element: Element, attributes: Attributes | Attributes[]) {
171
- if (isObject(attributes)) {
172
- for (let name in attributes) {
173
- set(element, attributes[name], name, true);
174
- }
175
- }
176
- else if (isArray(attributes)) {
177
- for (let i = 0, n = attributes.length; i < n; i++) {
178
- let attrs = attributes[i];
250
+ const spread = function (element: Element, value: Attributes | Attributes[]) {
251
+ let cache = (element[STORE_KEY] ??= { [UPDATES_KEY]: {} }) as Record<PropertyKey, unknown>,
252
+ context = {
253
+ element,
254
+ store: cache,
255
+ updates: cache[UPDATES_KEY] as Record<PropertyKey, unknown>,
256
+ updating: false
257
+ };
179
258
 
180
- for (let name in attrs) {
181
- set(element, attrs[name], name, true);
259
+ if (isArray(value)) {
260
+ for (let i = 0, n = value.length; i < n; i++) {
261
+ let v = value[i];
262
+
263
+ for (let name in v) {
264
+ set(context, name, v[name], STATE_HYDRATING);
182
265
  }
183
266
  }
184
267
  }
268
+ else if (isObject(value)) {
269
+ for (let name in value) {
270
+ set(context, name, value[name], STATE_HYDRATING);
271
+ }
272
+ }
185
273
  };
186
274
 
187
275
 
188
276
  export default { apply, spread };
189
- export { apply, spread }
277
+ export { apply, spread };
package/src/constants.ts CHANGED
@@ -43,8 +43,6 @@ const RENDERABLE_REACTIVE = Symbol();
43
43
  const RENDERABLE_TEMPLATE = Symbol();
44
44
 
45
45
 
46
- const SLOT_CLEANUP = Symbol();
47
-
48
46
  const SLOT_HTML = '<!--$-->';
49
47
 
50
48
  const SLOT_MARKER = '{{$}}';
@@ -53,18 +51,8 @@ const SLOT_MARKER_LENGTH = SLOT_MARKER.length;
53
51
 
54
52
 
55
53
  export {
56
- NODE_CLOSING,
57
- NODE_ELEMENT,
58
- NODE_SLOT,
59
- NODE_VOID,
60
- NODE_WHITELIST,
61
- REGEX_EMPTY_TEXT_NODES,
62
- REGEX_SLOT_NODES,
63
- RENDERABLE,
64
- RENDERABLE_REACTIVE,
65
- RENDERABLE_TEMPLATE,
66
- SLOT_CLEANUP,
67
- SLOT_HTML,
68
- SLOT_MARKER,
69
- SLOT_MARKER_LENGTH
54
+ NODE_CLOSING, NODE_ELEMENT, NODE_SLOT, NODE_VOID, NODE_WHITELIST,
55
+ REGEX_EMPTY_TEXT_NODES, REGEX_SLOT_NODES,
56
+ RENDERABLE, RENDERABLE_REACTIVE, RENDERABLE_TEMPLATE,
57
+ SLOT_HTML, SLOT_MARKER, SLOT_MARKER_LENGTH
70
58
  };
package/src/event.ts CHANGED
@@ -2,7 +2,7 @@ import { root } from '@esportsplus/reactivity';
2
2
  import { defineProperty } from '@esportsplus/utilities';
3
3
  import { ondisconnect } from './slot';
4
4
  import { Element } from './types';
5
- import { addEventListener, parentElement } from './utilities';
5
+ import { addEventListener, parentElement, raf } from './utilities';
6
6
 
7
7
 
8
8
  let capture = new Set<`on${string}`>(['onblur', 'onfocus', 'onscroll']),
@@ -26,7 +26,8 @@ let capture = new Set<`on${string}`>(['onblur', 'onfocus', 'onscroll']),
26
26
 
27
27
  export default (element: Element, event: `on${string}`, listener: Function): void => {
28
28
  if (event === 'onconnect') {
29
- let interval = setInterval(() => {
29
+ let retry = 60,
30
+ task = () => {
30
31
  retry--;
31
32
 
32
33
  if (element.isConnected) {
@@ -34,11 +35,12 @@ export default (element: Element, event: `on${string}`, listener: Function): voi
34
35
  root(() => listener(element));
35
36
  }
36
37
 
37
- if (!retry) {
38
- clearInterval(interval);
38
+ if (retry) {
39
+ raf.add(task);
39
40
  }
40
- }, 1000 / 60),
41
- retry = 60;
41
+ };
42
+
43
+ raf.add(task);
42
44
 
43
45
  return;
44
46
  }
@@ -1,5 +1,5 @@
1
- import { root, ReactiveArray } from '@esportsplus/reactivity';
2
- import { Element, Elements, Renderable, RenderableReactive, RenderableTemplate, Template } from '~/types';
1
+ import { root } from '@esportsplus/reactivity';
2
+ import { Element, Elements, HydrateResult, Renderable, RenderableReactive, RenderableTemplate, Template } from '~/types';
3
3
  import { Slot } from '~/slot';
4
4
  import { cloneNode, firstChild, nextSibling } from '~/utilities';
5
5
  import { apply } from '~/attributes';
@@ -10,26 +10,36 @@ function reactive<T>(renderable: RenderableReactive<T>, slot: Slot) {
10
10
  let array = renderable.values,
11
11
  factory = renderable.template,
12
12
  refresh = () => {
13
- slot.length = 0;
14
- reactive(renderable, slot);
13
+ slot.render( root(() => array.map(template)) );
15
14
  },
16
- render = (i: number, n?: number) => {
17
- return root(() => template(array, factory, i, n));
18
- },
19
- renderables = array.map(factory);
20
-
21
- array.on('pop', () => slot.pop());
22
- array.on('push', ({ items }) => slot.push(...render(array.length - items.length)));
15
+ template = function(data, i) {
16
+ let renderable = factory.call(this, data, i);
17
+
18
+ return hydrate<T>(renderable, cache.get(renderable));
19
+ } as (this: typeof array, ...args: Parameters<Parameters<typeof array['map']>[0]>) => HydrateResult;
20
+
21
+ array.on('pop', () => {
22
+ slot.pop();
23
+ });
24
+ array.on('push', ({ items }) => {
25
+ slot.push( ...root(() => array.map(template, array.length - items.length)) );
26
+ });
23
27
  array.on('reverse', refresh);
24
- array.on('shift', () => slot.shift());
28
+ array.on('shift', () => {
29
+ slot.shift();
30
+ });
25
31
  array.on('sort', refresh);
26
- array.on('splice', ({ deleteCount: d, items: i, start: s }) => slot.splice(s, d, ...render(s, i.length)));
27
- array.on('unshift', ({ items }) => slot.unshift(...render(0, items.length)));
28
-
29
- return template(array, factory, 0, renderables.length);
32
+ array.on('splice', ({ deleteCount: d, items: i, start: s }) => {
33
+ slot.splice(s, d, ...root(() => array.map(template, s, i.length)));
34
+ });
35
+ array.on('unshift', ({ items }) => {
36
+ slot.unshift( ...root(() => array.map(template, 0, items.length)) );
37
+ });
38
+
39
+ return array.map(template);
30
40
  }
31
41
 
32
- function render<T>(renderable: Renderable<T>, template: Template) {
42
+ function hydrate<T>(renderable: Renderable<T>, template: Template): HydrateResult {
33
43
  let elements: Elements = [],
34
44
  fragment = cloneNode.call(template.fragment, true),
35
45
  slots = template.slots;
@@ -42,8 +52,7 @@ function render<T>(renderable: Renderable<T>, template: Template) {
42
52
  for (let i = slots.length - 1; i >= 0; i--) {
43
53
  let { fn, path, slot } = slots[i];
44
54
 
45
- if (path === previous) {}
46
- else {
55
+ if (path !== previous) {
47
56
  apply(node);
48
57
 
49
58
  node = fragment;
@@ -55,7 +64,7 @@ function render<T>(renderable: Renderable<T>, template: Template) {
55
64
  }
56
65
 
57
66
  // @ts-ignore
58
- fn(node, values[slot], name);
67
+ fn(node, values[slot]);
59
68
  }
60
69
 
61
70
  apply(node);
@@ -65,22 +74,7 @@ function render<T>(renderable: Renderable<T>, template: Template) {
65
74
  elements.push(element);
66
75
  }
67
76
 
68
- return elements;
69
- }
70
-
71
- function template<T>(array: ReactiveArray<T>, template: RenderableReactive<T>['template'], i: number, n?: number) {
72
- let groups: Elements[] = [],
73
- renderables = array.map< RenderableTemplate<T> >(template, i, n);
74
-
75
- for (let i = 0, n = renderables.length; i < n; i++) {
76
- let renderable = renderables[i];
77
-
78
- groups.push(
79
- render(renderable, cache.get(renderable))
80
- );
81
- }
82
-
83
- return groups;
77
+ return { elements, fragment };
84
78
  }
85
79
 
86
80
 
@@ -89,6 +83,6 @@ export default {
89
83
  return reactive(renderable, slot);
90
84
  },
91
85
  static: <T>(renderable: RenderableTemplate<T>) => {
92
- return render(renderable, renderable.template || (renderable.template = cache.get(renderable)));
86
+ return hydrate(renderable, renderable.template || (renderable.template = cache.get(renderable)));
93
87
  }
94
88
  };