@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/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.2",
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.15",
17
+ "version": "0.15.17",
18
18
  "scripts": {
19
19
  "build": "tsc && tsc-alias",
20
20
  "-": "-"
package/src/attributes.ts CHANGED
@@ -1,17 +1,41 @@
1
1
  import { effect } from '@esportsplus/reactivity';
2
2
  import { isArray, isFunction, isObject, isString } from '@esportsplus/utilities';
3
+ import { ATTRIBUTE_STORE } from '~/constants';
3
4
  import { ondisconnect } from './slot';
4
5
  import { Attributes, Element } from './types';
5
6
  import { className, raf, removeAttribute, setAttribute } from './utilities';
7
+ import q from '@esportsplus/queue';
6
8
  import event from './event';
7
9
 
8
10
 
9
- let attributes: Record<string, unknown> = {},
10
- delimiters: Record<string, string> = {
11
+ const EFFECT_KEY = Symbol();
12
+
13
+ const UPDATES_KEY = Symbol();
14
+
15
+ const STATE_HYDRATING = 0;
16
+
17
+ const STATE_NONE = 1;
18
+
19
+ const STATE_WAITING = 2;
20
+
21
+
22
+ type Context = {
23
+ element: Element;
24
+ store: Record<PropertyKey, unknown>
25
+ updates: Record<PropertyKey, unknown>;
26
+ updating: boolean;
27
+ };
28
+
29
+ type State = typeof STATE_HYDRATING | typeof STATE_NONE | typeof STATE_WAITING;
30
+
31
+
32
+ let delimiters: Record<string, string> = {
11
33
  class: ' ',
12
34
  style: ';'
13
35
  },
14
- key = Symbol();
36
+ hydrating: Record<string, unknown> = {},
37
+ queue = q<Context>(64),
38
+ scheduled = false;
15
39
 
16
40
 
17
41
  function attribute(element: Element, name: string, value: unknown) {
@@ -29,73 +53,115 @@ function attribute(element: Element, name: string, value: unknown) {
29
53
  }
30
54
  }
31
55
 
32
- function set(element: Element, value: unknown, name: string, wait = false) {
56
+ function schedule() {
57
+ if (scheduled) {
58
+ return;
59
+ }
60
+
61
+ scheduled = true;
62
+ raf.add(task);
63
+ }
64
+
65
+ function set(context: Context, name: string, value: unknown, state: State) {
33
66
  if (isArray(value)) {
34
67
  for (let i = 0, n = value.length; i < n; i++) {
35
- set(element, value[i], name, wait);
68
+ set(context, name, value[i], state);
36
69
  }
37
70
  }
38
71
  else if (isFunction(value)) {
39
72
  if (name.startsWith('on')) {
40
- event(element, name as `on${string}`, value);
73
+ event(context.element, name as `on${string}`, value);
41
74
  }
42
75
  else {
43
- let id = ('e' + store(element)[key]++);
76
+ context.store[EFFECT_KEY] ??= 0;
77
+
78
+ let id = (context.store[EFFECT_KEY] as number)++;
44
79
 
45
80
  ondisconnect(
46
- element,
81
+ context.element,
47
82
  effect(() => {
48
- let v = (value as Function)(element);
83
+ let v = (value as Function)(context.element);
49
84
 
50
85
  if (isArray(v)) {
51
86
  let last = v.length - 1;
52
87
 
53
88
  for (let i = 0, n = v.length; i < n; i++) {
54
- update(element, id, name, v[i], wait || i !== last);
89
+ update(
90
+ context,
91
+ id,
92
+ name,
93
+ v[i],
94
+ state === STATE_HYDRATING
95
+ ? state
96
+ : i !== last ? STATE_WAITING : state
97
+ );
55
98
  }
56
99
  }
57
100
  else {
58
- update(element, id, name, v, wait);
101
+ update(context, id, name, v, state);
59
102
  }
60
103
  })
61
104
  );
62
105
 
63
- wait = false;
106
+ state = STATE_NONE;
64
107
  }
65
108
  }
66
109
  else {
67
- update(element, null, name, value, wait);
110
+ update(context, null, name, value, state);
68
111
  }
69
112
  }
70
113
 
71
- function store(element: Element) {
72
- return (
73
- element[key] || (element[key] = { [key]: 0 })
74
- ) as Attributes & { [key]: number };
114
+ function task() {
115
+ let context,
116
+ n = queue.length;
117
+
118
+ while ((context = queue.next()) && n--) {
119
+ let { element, updates } = context;
120
+
121
+ for (let name in updates) {
122
+ attribute(element, name, updates[name]);
123
+ delete updates[name];
124
+ }
125
+
126
+ context.updating = false;
127
+ }
128
+
129
+ if (queue.length) {
130
+ raf.add(task);
131
+ }
132
+ else {
133
+ scheduled = false;
134
+ }
75
135
  }
76
136
 
77
- function update(element: Element, id: null | string, name: string, value: unknown, wait = false) {
137
+ function update(
138
+ context: Context,
139
+ id: null | number,
140
+ name: string,
141
+ value: unknown,
142
+ state: State
143
+ ) {
78
144
  if (value === false || value == null) {
79
145
  value = '';
80
146
  }
81
147
 
82
- let data = store(element);
148
+ let store = context.store;
83
149
 
84
150
  if (name in delimiters) {
85
151
  let cache = name + '.static',
86
152
  delimiter = delimiters[name],
87
- dynamic = data[name] as Attributes | undefined;
153
+ dynamic = store[name] as Attributes | undefined;
88
154
 
89
155
  if (dynamic === undefined) {
90
- let value = (element.getAttribute(name) || '').trim();
156
+ let value = (context.element.getAttribute(name) || '').trim();
91
157
 
92
- data[cache] = value.endsWith(delimiter) ? value.slice(0, -1) : value;
93
- data[name] = dynamic = {};
158
+ store[cache] = value;
159
+ store[name] = dynamic = {};
94
160
  }
95
161
 
96
162
  if (id === null) {
97
163
  if (value && isString(value)) {
98
- data[cache] += (data[cache] ? delimiter : '') + value;
164
+ store[cache] += (store[cache] ? delimiter : '') + value;
99
165
  }
100
166
  }
101
167
  else {
@@ -117,7 +183,7 @@ function update(element: Element, id: null | string, name: string, value: unknow
117
183
  }
118
184
  }
119
185
 
120
- let cold = data[id] as Attributes | undefined;
186
+ let cold = store[id] as Attributes | undefined;
121
187
 
122
188
  if (cold !== undefined) {
123
189
  for (let part in cold) {
@@ -129,61 +195,74 @@ function update(element: Element, id: null | string, name: string, value: unknow
129
195
  }
130
196
  }
131
197
 
132
- data[id] = hot;
198
+ store[id] = hot;
133
199
  }
134
200
 
135
- value = data[cache];
201
+ value = store[cache];
136
202
 
137
203
  for (let key in dynamic) {
138
204
  value += (value ? delimiter : '') + key;
139
205
  }
140
206
  }
141
- else if (isString(id)) {
142
- if (data[name] === value) {
207
+ else if (id !== null) {
208
+ if (store[name] === value) {
143
209
  return;
144
210
  }
145
211
 
146
- data[name] = value as string;
212
+ store[name] = value as string;
147
213
  }
148
214
 
149
- if (wait) {
150
- if (id === null) {
151
- attributes[name] = value;
152
- }
215
+ if (state === STATE_HYDRATING) {
216
+ hydrating[name] = value;
153
217
  }
154
218
  else {
155
- raf.add(() => {
156
- attribute(element, name, value);
157
- });
219
+ context.updates[name] = value;
220
+
221
+ if (state === STATE_NONE && !context.updating) {
222
+ context.updating = true;
223
+ queue.add(context);
224
+ }
225
+
226
+ if (!scheduled) {
227
+ schedule();
228
+ }
158
229
  }
159
230
  }
160
231
 
161
232
 
162
233
  const apply = (element: Element) => {
163
- for (let key in attributes) {
164
- attribute(element, key, attributes[key]);
234
+ for (let key in hydrating) {
235
+ attribute(element, key, hydrating[key]);
165
236
  }
166
237
 
167
- attributes = {};
238
+ hydrating = {};
168
239
  };
169
240
 
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];
241
+ const spread = function (element: Element, value: Attributes | Attributes[]) {
242
+ let cache = element[ATTRIBUTE_STORE] ??= { [UPDATES_KEY]: {} },
243
+ context = {
244
+ element,
245
+ store: cache,
246
+ updates: cache[UPDATES_KEY] as Record<PropertyKey, unknown>,
247
+ updating: false
248
+ };
179
249
 
180
- for (let name in attrs) {
181
- set(element, attrs[name], name, true);
250
+ if (isArray(value)) {
251
+ for (let i = 0, n = value.length; i < n; i++) {
252
+ let v = value[i];
253
+
254
+ for (let name in v) {
255
+ set(context, name, v[name], STATE_HYDRATING);
182
256
  }
183
257
  }
184
258
  }
259
+ else if (isObject(value)) {
260
+ for (let name in value) {
261
+ set(context, name, value[name], STATE_HYDRATING);
262
+ }
263
+ }
185
264
  };
186
265
 
187
266
 
188
267
  export default { apply, spread };
189
- export { apply, spread }
268
+ export { apply, spread };
package/src/constants.ts CHANGED
@@ -1,3 +1,6 @@
1
+ const ATTRIBUTE_STORE = Symbol();
2
+
3
+
1
4
  const NODE_CLOSING = 1;
2
5
 
3
6
  const NODE_COMMENT = 2;
@@ -53,18 +56,9 @@ const SLOT_MARKER_LENGTH = SLOT_MARKER.length;
53
56
 
54
57
 
55
58
  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
59
+ ATTRIBUTE_STORE,
60
+ NODE_CLOSING, NODE_ELEMENT, NODE_SLOT, NODE_VOID, NODE_WHITELIST,
61
+ REGEX_EMPTY_TEXT_NODES, REGEX_SLOT_NODES,
62
+ RENDERABLE, RENDERABLE_REACTIVE, RENDERABLE_TEMPLATE,
63
+ SLOT_CLEANUP, SLOT_HTML, SLOT_MARKER, SLOT_MARKER_LENGTH
70
64
  };
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, Renderable, RenderableReactive, RenderableTemplate, RenderedGroup, Template } from '~/types';
3
3
  import { Slot } from '~/slot';
4
4
  import { cloneNode, firstChild, nextSibling } from '~/utilities';
5
5
  import { apply } from '~/attributes';
@@ -10,23 +10,38 @@ 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(
14
+ root(() => array.map(template))
15
+ );
15
16
  },
16
- render = (i: number, n?: number) => {
17
- return root(() => template(array, factory, i, n));
17
+ renderer = (i: number, n?: number) => {
18
+ return root(() => array.map(template, i, n)) as RenderedGroup[];
18
19
  },
19
- renderables = array.map(factory);
20
-
21
- array.on('pop', () => slot.pop());
22
- array.on('push', ({ items }) => slot.push(...render(array.length - items.length)));
20
+ template = function(data, i) {
21
+ let renderable = factory.call(this, data, i);
22
+
23
+ return render(renderable, cache.get(renderable));
24
+ } as Parameters<typeof array['map']>[0];
25
+
26
+ array.on('pop', () => {
27
+ slot.pop();
28
+ });
29
+ array.on('push', ({ items }) => {
30
+ slot.push(...renderer(array.length - items.length));
31
+ });
23
32
  array.on('reverse', refresh);
24
- array.on('shift', () => slot.shift());
33
+ array.on('shift', () => {
34
+ slot.shift();
35
+ });
25
36
  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);
37
+ array.on('splice', ({ deleteCount: d, items: i, start: s }) => {
38
+ slot.splice(s, d, ...renderer(s, i.length));
39
+ });
40
+ array.on('unshift', ({ items }) => {
41
+ slot.unshift(...renderer(0, items.length));
42
+ });
43
+
44
+ return array.map(template) as RenderedGroup[];
30
45
  }
31
46
 
32
47
  function render<T>(renderable: Renderable<T>, template: Template) {
@@ -42,8 +57,7 @@ function render<T>(renderable: Renderable<T>, template: Template) {
42
57
  for (let i = slots.length - 1; i >= 0; i--) {
43
58
  let { fn, path, slot } = slots[i];
44
59
 
45
- if (path === previous) {}
46
- else {
60
+ if (path !== previous) {
47
61
  apply(node);
48
62
 
49
63
  node = fragment;
@@ -55,7 +69,7 @@ function render<T>(renderable: Renderable<T>, template: Template) {
55
69
  }
56
70
 
57
71
  // @ts-ignore
58
- fn(node, values[slot], name);
72
+ fn(node, values[slot]);
59
73
  }
60
74
 
61
75
  apply(node);
@@ -65,22 +79,7 @@ function render<T>(renderable: Renderable<T>, template: Template) {
65
79
  elements.push(element);
66
80
  }
67
81
 
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;
82
+ return { elements, fragment };
84
83
  }
85
84
 
86
85