@esportsplus/template 0.15.17 → 0.15.19
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/build/attributes.js +12 -7
- package/build/constants.d.ts +1 -3
- package/build/constants.js +1 -3
- package/build/html/hydrate.d.ts +3 -6
- package/build/html/hydrate.js +9 -9
- package/build/slot.d.ts +4 -4
- package/build/slot.js +46 -42
- package/build/types.d.ts +7 -10
- package/build/types.js +1 -1
- package/build/utilities.d.ts +1 -1
- package/build/utilities.js +2 -1
- package/package.json +1 -1
- package/src/attributes.ts +16 -7
- package/src/constants.ts +1 -7
- package/src/html/hydrate.ts +13 -16
- package/src/slot.ts +63 -55
- package/src/types.ts +6 -12
- package/src/utilities.ts +4 -2
package/build/attributes.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { effect } from '@esportsplus/reactivity';
|
|
2
2
|
import { isArray, isFunction, isObject, isString } from '@esportsplus/utilities';
|
|
3
|
-
import { ATTRIBUTE_STORE } from './constants.js';
|
|
4
3
|
import { ondisconnect } from './slot.js';
|
|
5
4
|
import { className, raf, removeAttribute, setAttribute } from './utilities.js';
|
|
6
5
|
import q from '@esportsplus/queue';
|
|
7
6
|
import event from './event.js';
|
|
8
7
|
const EFFECT_KEY = Symbol();
|
|
8
|
+
const HYDRATE_KEY = Symbol();
|
|
9
|
+
const STORE_KEY = Symbol();
|
|
9
10
|
const UPDATES_KEY = Symbol();
|
|
10
11
|
const STATE_HYDRATING = 0;
|
|
11
12
|
const STATE_NONE = 1;
|
|
@@ -13,7 +14,7 @@ const STATE_WAITING = 2;
|
|
|
13
14
|
let delimiters = {
|
|
14
15
|
class: ' ',
|
|
15
16
|
style: ';'
|
|
16
|
-
},
|
|
17
|
+
}, queue = q(64), scheduled = false;
|
|
17
18
|
function attribute(element, name, value) {
|
|
18
19
|
if (value === '' || value === false || value == null) {
|
|
19
20
|
removeAttribute.call(element, name);
|
|
@@ -139,7 +140,7 @@ function update(context, id, name, value, state) {
|
|
|
139
140
|
store[name] = value;
|
|
140
141
|
}
|
|
141
142
|
if (state === STATE_HYDRATING) {
|
|
142
|
-
|
|
143
|
+
(context.element[HYDRATE_KEY] ??= {})[name] = value;
|
|
143
144
|
}
|
|
144
145
|
else {
|
|
145
146
|
context.updates[name] = value;
|
|
@@ -153,13 +154,17 @@ function update(context, id, name, value, state) {
|
|
|
153
154
|
}
|
|
154
155
|
}
|
|
155
156
|
const apply = (element) => {
|
|
156
|
-
|
|
157
|
-
|
|
157
|
+
let attributes = element[HYDRATE_KEY];
|
|
158
|
+
if (!attributes) {
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
for (let key in attributes) {
|
|
162
|
+
attribute(element, key, attributes[key]);
|
|
158
163
|
}
|
|
159
|
-
|
|
164
|
+
delete element[HYDRATE_KEY];
|
|
160
165
|
};
|
|
161
166
|
const spread = function (element, value) {
|
|
162
|
-
let cache = element[
|
|
167
|
+
let cache = (element[STORE_KEY] ??= { [UPDATES_KEY]: {} }), context = {
|
|
163
168
|
element,
|
|
164
169
|
store: cache,
|
|
165
170
|
updates: cache[UPDATES_KEY],
|
package/build/constants.d.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
declare const ATTRIBUTE_STORE: unique symbol;
|
|
2
1
|
declare const NODE_CLOSING = 1;
|
|
3
2
|
declare const NODE_ELEMENT = 3;
|
|
4
3
|
declare const NODE_SLOT = 4;
|
|
@@ -9,8 +8,7 @@ declare const REGEX_SLOT_NODES: RegExp;
|
|
|
9
8
|
declare const RENDERABLE: unique symbol;
|
|
10
9
|
declare const RENDERABLE_REACTIVE: unique symbol;
|
|
11
10
|
declare const RENDERABLE_TEMPLATE: unique symbol;
|
|
12
|
-
declare const SLOT_CLEANUP: unique symbol;
|
|
13
11
|
declare const SLOT_HTML = "<!--$-->";
|
|
14
12
|
declare const SLOT_MARKER = "{{$}}";
|
|
15
13
|
declare const SLOT_MARKER_LENGTH: number;
|
|
16
|
-
export {
|
|
14
|
+
export { NODE_CLOSING, NODE_ELEMENT, NODE_SLOT, NODE_VOID, NODE_WHITELIST, REGEX_EMPTY_TEXT_NODES, REGEX_SLOT_NODES, RENDERABLE, RENDERABLE_REACTIVE, RENDERABLE_TEMPLATE, SLOT_HTML, SLOT_MARKER, SLOT_MARKER_LENGTH };
|
package/build/constants.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
const ATTRIBUTE_STORE = Symbol();
|
|
2
1
|
const NODE_CLOSING = 1;
|
|
3
2
|
const NODE_COMMENT = 2;
|
|
4
3
|
const NODE_ELEMENT = 3;
|
|
@@ -29,8 +28,7 @@ const REGEX_SLOT_NODES = /<([\w-]+|[\/!])(?:([^><]*{{\$}}[^><]*)|(?:[^><]*))?>|{
|
|
|
29
28
|
const RENDERABLE = Symbol();
|
|
30
29
|
const RENDERABLE_REACTIVE = Symbol();
|
|
31
30
|
const RENDERABLE_TEMPLATE = Symbol();
|
|
32
|
-
const SLOT_CLEANUP = Symbol();
|
|
33
31
|
const SLOT_HTML = '<!--$-->';
|
|
34
32
|
const SLOT_MARKER = '{{$}}';
|
|
35
33
|
const SLOT_MARKER_LENGTH = SLOT_MARKER.length;
|
|
36
|
-
export {
|
|
34
|
+
export { NODE_CLOSING, NODE_ELEMENT, NODE_SLOT, NODE_VOID, NODE_WHITELIST, REGEX_EMPTY_TEXT_NODES, REGEX_SLOT_NODES, RENDERABLE, RENDERABLE_REACTIVE, RENDERABLE_TEMPLATE, SLOT_HTML, SLOT_MARKER, SLOT_MARKER_LENGTH };
|
package/build/html/hydrate.d.ts
CHANGED
|
@@ -1,10 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { HydrateResult, RenderableReactive, RenderableTemplate } from '../types.js';
|
|
2
2
|
import { Slot } from '../slot.js';
|
|
3
3
|
declare const _default: {
|
|
4
|
-
reactive: <T>(renderable: RenderableReactive<T>, slot: Slot) =>
|
|
5
|
-
static: <T>(renderable: RenderableTemplate<T>) =>
|
|
6
|
-
elements: Elements;
|
|
7
|
-
fragment: Node;
|
|
8
|
-
};
|
|
4
|
+
reactive: <T>(renderable: RenderableReactive<T>, slot: Slot) => HydrateResult[];
|
|
5
|
+
static: <T>(renderable: RenderableTemplate<T>) => HydrateResult;
|
|
9
6
|
};
|
|
10
7
|
export default _default;
|
package/build/html/hydrate.js
CHANGED
|
@@ -5,17 +5,15 @@ import cache from './cache.js';
|
|
|
5
5
|
function reactive(renderable, slot) {
|
|
6
6
|
let array = renderable.values, factory = renderable.template, refresh = () => {
|
|
7
7
|
slot.render(root(() => array.map(template)));
|
|
8
|
-
}, renderer = (i, n) => {
|
|
9
|
-
return root(() => array.map(template, i, n));
|
|
10
8
|
}, template = function (data, i) {
|
|
11
9
|
let renderable = factory.call(this, data, i);
|
|
12
|
-
return
|
|
10
|
+
return hydrate(renderable, cache.get(renderable));
|
|
13
11
|
};
|
|
14
12
|
array.on('pop', () => {
|
|
15
13
|
slot.pop();
|
|
16
14
|
});
|
|
17
15
|
array.on('push', ({ items }) => {
|
|
18
|
-
slot.push(...
|
|
16
|
+
slot.push(...root(() => array.map(template, array.length - items.length)));
|
|
19
17
|
});
|
|
20
18
|
array.on('reverse', refresh);
|
|
21
19
|
array.on('shift', () => {
|
|
@@ -23,21 +21,23 @@ function reactive(renderable, slot) {
|
|
|
23
21
|
});
|
|
24
22
|
array.on('sort', refresh);
|
|
25
23
|
array.on('splice', ({ deleteCount: d, items: i, start: s }) => {
|
|
26
|
-
slot.splice(s, d, ...
|
|
24
|
+
slot.splice(s, d, ...root(() => array.map(template, s, i.length)));
|
|
27
25
|
});
|
|
28
26
|
array.on('unshift', ({ items }) => {
|
|
29
|
-
slot.unshift(...
|
|
27
|
+
slot.unshift(...root(() => array.map(template, 0, items.length)));
|
|
30
28
|
});
|
|
31
29
|
return array.map(template);
|
|
32
30
|
}
|
|
33
|
-
function
|
|
31
|
+
function hydrate(renderable, template) {
|
|
34
32
|
let elements = [], fragment = cloneNode.call(template.fragment, true), slots = template.slots;
|
|
35
33
|
if (slots !== null) {
|
|
36
34
|
let node, previous, values = renderable.values;
|
|
37
35
|
for (let i = slots.length - 1; i >= 0; i--) {
|
|
38
36
|
let { fn, path, slot } = slots[i];
|
|
39
37
|
if (path !== previous) {
|
|
40
|
-
|
|
38
|
+
if (node) {
|
|
39
|
+
apply(node);
|
|
40
|
+
}
|
|
41
41
|
node = fragment;
|
|
42
42
|
previous = path;
|
|
43
43
|
for (let o = 0, j = path.length; o < j; o++) {
|
|
@@ -58,6 +58,6 @@ export default {
|
|
|
58
58
|
return reactive(renderable, slot);
|
|
59
59
|
},
|
|
60
60
|
static: (renderable) => {
|
|
61
|
-
return
|
|
61
|
+
return hydrate(renderable, renderable.template || (renderable.template = cache.get(renderable)));
|
|
62
62
|
}
|
|
63
63
|
};
|
package/build/slot.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Element, Elements,
|
|
1
|
+
import { Element, Elements, HydrateResult } from './types.js';
|
|
2
2
|
declare class Slot {
|
|
3
3
|
marker: Element;
|
|
4
4
|
nodes: Elements[];
|
|
@@ -9,11 +9,11 @@ declare class Slot {
|
|
|
9
9
|
anchor(index?: number): Element;
|
|
10
10
|
clear(): void;
|
|
11
11
|
pop(): Elements[] | undefined;
|
|
12
|
-
push(...groups:
|
|
12
|
+
push(...groups: HydrateResult[]): number;
|
|
13
13
|
render(input: unknown): this;
|
|
14
14
|
shift(): Elements[] | undefined;
|
|
15
|
-
splice(start: number, stop?: number, ...groups:
|
|
16
|
-
unshift(...groups:
|
|
15
|
+
splice(start: number, stop?: number, ...groups: HydrateResult[]): Elements[];
|
|
16
|
+
unshift(...groups: HydrateResult[]): number;
|
|
17
17
|
}
|
|
18
18
|
declare const ondisconnect: (element: Element, fn: VoidFunction) => void;
|
|
19
19
|
declare const _default: (marker: Element, value: unknown) => Slot;
|
package/build/slot.js
CHANGED
|
@@ -1,30 +1,28 @@
|
|
|
1
1
|
import { effect, root } from '@esportsplus/reactivity';
|
|
2
2
|
import { isArray, isFunction, isInstanceOf, isObject } from '@esportsplus/utilities';
|
|
3
|
-
import { RENDERABLE, RENDERABLE_REACTIVE
|
|
3
|
+
import { RENDERABLE, RENDERABLE_REACTIVE } from './constants.js';
|
|
4
4
|
import { hydrate } from './html/index.js';
|
|
5
|
-
import { append, firstChild, fragment, microtask, nextSibling, nodeValue, raf, text } from './utilities.js';
|
|
5
|
+
import { append, cloneNode, firstChild, fragment, microtask, nextSibling, nodeValue, raf, text } from './utilities.js';
|
|
6
6
|
import queue from '@esportsplus/queue';
|
|
7
|
-
|
|
7
|
+
const CLEANUP_KEY = Symbol();
|
|
8
|
+
let cleanup = queue(64), scheduled = false, template = fragment('');
|
|
8
9
|
function after(anchor, groups) {
|
|
9
|
-
let
|
|
10
|
-
if (n) {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
elements.push(group.elements);
|
|
23
|
-
}
|
|
10
|
+
let n = groups.length;
|
|
11
|
+
if (n === 0) {
|
|
12
|
+
return [];
|
|
13
|
+
}
|
|
14
|
+
let elements = [], fragment = groups[0].fragment || cloneNode.call(template);
|
|
15
|
+
if (n === 1) {
|
|
16
|
+
elements.push(groups[0].elements);
|
|
17
|
+
}
|
|
18
|
+
else {
|
|
19
|
+
for (let i = 1; i < n; i++) {
|
|
20
|
+
let { elements: e, fragment: f } = groups[i];
|
|
21
|
+
append.call(fragment, f);
|
|
22
|
+
elements.push(e);
|
|
24
23
|
}
|
|
25
|
-
anchor.after(fragment);
|
|
26
|
-
groups[0].fragment = null;
|
|
27
24
|
}
|
|
25
|
+
anchor.after(fragment);
|
|
28
26
|
return elements;
|
|
29
27
|
}
|
|
30
28
|
function remove(...groups) {
|
|
@@ -32,8 +30,8 @@ function remove(...groups) {
|
|
|
32
30
|
let group = groups[i];
|
|
33
31
|
for (let j = 0, o = group.length; j < o; j++) {
|
|
34
32
|
let item = group[j];
|
|
35
|
-
if (item
|
|
36
|
-
cleanup.add(item[
|
|
33
|
+
if (CLEANUP_KEY in item) {
|
|
34
|
+
cleanup.add(item[CLEANUP_KEY]);
|
|
37
35
|
}
|
|
38
36
|
item.remove();
|
|
39
37
|
}
|
|
@@ -43,47 +41,50 @@ function remove(...groups) {
|
|
|
43
41
|
}
|
|
44
42
|
return groups;
|
|
45
43
|
}
|
|
46
|
-
function render(
|
|
44
|
+
function render(elements, fragment, input, slot) {
|
|
47
45
|
if (input === false || input == null || input === '') {
|
|
48
|
-
return
|
|
46
|
+
return;
|
|
49
47
|
}
|
|
50
48
|
if (isArray(input)) {
|
|
51
49
|
for (let i = 0, n = input.length; i < n; i++) {
|
|
52
|
-
render(
|
|
50
|
+
render(elements, fragment, input[i]);
|
|
53
51
|
}
|
|
54
52
|
}
|
|
55
53
|
else if (isObject(input) && RENDERABLE in input) {
|
|
56
54
|
if (input[RENDERABLE] === RENDERABLE_REACTIVE) {
|
|
57
|
-
|
|
55
|
+
let response = hydrate.reactive(input, slot);
|
|
56
|
+
for (let i = 0, n = response.length; i < n; i++) {
|
|
57
|
+
let { elements: e, fragment: f } = response[i];
|
|
58
|
+
append.call(fragment, f);
|
|
59
|
+
elements.push(e);
|
|
60
|
+
}
|
|
58
61
|
}
|
|
59
62
|
else {
|
|
60
|
-
|
|
63
|
+
let { elements: e, fragment: f } = hydrate.static(input);
|
|
64
|
+
append.call(fragment, f);
|
|
65
|
+
elements.push(e);
|
|
61
66
|
}
|
|
62
67
|
}
|
|
63
68
|
else if (isInstanceOf(input, NodeList)) {
|
|
64
|
-
let
|
|
69
|
+
let e = [];
|
|
65
70
|
for (let node = firstChild.call(input); node; node = nextSibling.call(node)) {
|
|
66
|
-
|
|
71
|
+
e.push(node);
|
|
67
72
|
}
|
|
68
|
-
|
|
73
|
+
append.call(fragment, ...e);
|
|
74
|
+
elements.push(e);
|
|
69
75
|
}
|
|
70
76
|
else if (isInstanceOf(input, Node)) {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
fragment: input
|
|
74
|
-
});
|
|
77
|
+
append.call(fragment, input);
|
|
78
|
+
elements.push([input]);
|
|
75
79
|
}
|
|
76
80
|
else {
|
|
77
81
|
let element = text(typeof input === 'string' ? input : String(input));
|
|
78
82
|
if (slot) {
|
|
79
83
|
slot.text = element;
|
|
80
84
|
}
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
fragment: element
|
|
84
|
-
});
|
|
85
|
+
append.call(fragment, element);
|
|
86
|
+
elements.push([element]);
|
|
85
87
|
}
|
|
86
|
-
return groups;
|
|
87
88
|
}
|
|
88
89
|
function schedule() {
|
|
89
90
|
if (scheduled) {
|
|
@@ -125,7 +126,7 @@ class Slot {
|
|
|
125
126
|
anchor(index = this.nodes.length - 1) {
|
|
126
127
|
let node, nodes = this.nodes[index];
|
|
127
128
|
if (nodes) {
|
|
128
|
-
node = nodes.
|
|
129
|
+
node = nodes[nodes.length - 1];
|
|
129
130
|
}
|
|
130
131
|
return node || this.marker;
|
|
131
132
|
}
|
|
@@ -169,7 +170,10 @@ class Slot {
|
|
|
169
170
|
}
|
|
170
171
|
}
|
|
171
172
|
this.clear();
|
|
172
|
-
|
|
173
|
+
let fragment = cloneNode.call(template), nodes = [];
|
|
174
|
+
render(nodes, fragment, input, this);
|
|
175
|
+
this.marker.after(fragment);
|
|
176
|
+
this.nodes = nodes;
|
|
173
177
|
return this;
|
|
174
178
|
}
|
|
175
179
|
shift() {
|
|
@@ -187,7 +191,7 @@ class Slot {
|
|
|
187
191
|
}
|
|
188
192
|
}
|
|
189
193
|
const ondisconnect = (element, fn) => {
|
|
190
|
-
(element[
|
|
194
|
+
(element[CLEANUP_KEY] ??= []).push(fn);
|
|
191
195
|
};
|
|
192
196
|
export default (marker, value) => {
|
|
193
197
|
return new Slot(marker).render(value);
|
package/build/types.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ReactiveArray } from '@esportsplus/reactivity';
|
|
2
|
-
import {
|
|
2
|
+
import { RENDERABLE, RENDERABLE_REACTIVE, RENDERABLE_TEMPLATE } from './constants.js';
|
|
3
3
|
import { firstChild } from './utilities.js';
|
|
4
4
|
import attributes from './attributes.js';
|
|
5
5
|
import slot from './slot.js';
|
|
@@ -18,11 +18,12 @@ type Attributes = {
|
|
|
18
18
|
} & Record<PropertyKey, unknown>;
|
|
19
19
|
type Effect<T> = () => EffectResponse<T>;
|
|
20
20
|
type EffectResponse<T> = T extends [] ? EffectResponse<T[number]>[] : Primitive | Renderable<T>;
|
|
21
|
-
type Element = HTMLElement & Attributes &
|
|
22
|
-
[ATTRIBUTE_STORE]?: Record<PropertyKey, unknown>;
|
|
23
|
-
[SLOT_CLEANUP]?: VoidFunction[];
|
|
24
|
-
} & Record<PropertyKey, unknown>;
|
|
21
|
+
type Element = HTMLElement & Attributes & Record<PropertyKey, unknown>;
|
|
25
22
|
type Elements = Element[];
|
|
23
|
+
type HydrateResult = {
|
|
24
|
+
elements: Elements;
|
|
25
|
+
fragment: DocumentFragment | Node;
|
|
26
|
+
};
|
|
26
27
|
type Primitive = bigint | boolean | null | number | string | undefined;
|
|
27
28
|
type Renderable<T = unknown> = RenderableReactive<T> | RenderableTemplate<T>;
|
|
28
29
|
type RenderableReactive<T = unknown> = Readonly<{
|
|
@@ -38,10 +39,6 @@ type RenderableTemplate<T> = {
|
|
|
38
39
|
values: (RenderableValue<T> | RenderableValue<T>[])[];
|
|
39
40
|
};
|
|
40
41
|
type RenderableValue<T = unknown> = Attributes | Readonly<Attributes> | Readonly<Attributes[]> | Effect<T> | Primitive | Renderable;
|
|
41
|
-
type RenderedGroup = {
|
|
42
|
-
elements: Elements;
|
|
43
|
-
fragment: DocumentFragment | Node | null;
|
|
44
|
-
};
|
|
45
42
|
type Template = {
|
|
46
43
|
fragment: DocumentFragment;
|
|
47
44
|
html: string;
|
|
@@ -52,4 +49,4 @@ type Template = {
|
|
|
52
49
|
slot: number;
|
|
53
50
|
}[] | null;
|
|
54
51
|
};
|
|
55
|
-
export type { Attributes, Effect, Element, Elements, Renderable, RenderableReactive, RenderableTemplate, RenderableValue,
|
|
52
|
+
export type { Attributes, Effect, Element, Elements, HydrateResult, Renderable, RenderableReactive, RenderableTemplate, RenderableValue, Template };
|
package/build/types.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { RENDERABLE } from './constants.js';
|
package/build/utilities.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { Element as E } from './types.js';
|
|
2
|
+
declare const append: (...nodes: (Node | string)[]) => void;
|
|
2
3
|
declare const addEventListener: {
|
|
3
4
|
<K extends keyof ElementEventMap>(type: K, listener: (this: Element, ev: ElementEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void;
|
|
4
5
|
(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void;
|
|
5
6
|
};
|
|
6
|
-
declare const append: (...nodes: (Node | string)[]) => void;
|
|
7
7
|
declare const removeEventListener: {
|
|
8
8
|
<K extends keyof ElementEventMap>(type: K, listener: (this: Element, ev: ElementEventMap[K]) => any, options?: boolean | EventListenerOptions): void;
|
|
9
9
|
(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | EventListenerOptions): void;
|
package/build/utilities.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { micro as m, raf as r } from '@esportsplus/tasks';
|
|
2
2
|
let prototype, template = document.createElement('template'), t = document.createTextNode('');
|
|
3
|
+
prototype = DocumentFragment.prototype;
|
|
4
|
+
const append = prototype.append;
|
|
3
5
|
prototype = Element.prototype;
|
|
4
6
|
const addEventListener = prototype.addEventListener;
|
|
5
|
-
const append = prototype.append;
|
|
6
7
|
const removeEventListener = prototype.removeEventListener;
|
|
7
8
|
const className = Object.getOwnPropertyDescriptor(prototype, 'className').set;
|
|
8
9
|
const innerHTML = Object.getOwnPropertyDescriptor(prototype, 'innerHTML').set;
|
package/package.json
CHANGED
package/src/attributes.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { effect } from '@esportsplus/reactivity';
|
|
2
2
|
import { isArray, isFunction, isObject, isString } from '@esportsplus/utilities';
|
|
3
|
-
import { ATTRIBUTE_STORE } from '~/constants';
|
|
4
3
|
import { ondisconnect } from './slot';
|
|
5
4
|
import { Attributes, Element } from './types';
|
|
6
5
|
import { className, raf, removeAttribute, setAttribute } from './utilities';
|
|
@@ -10,8 +9,13 @@ import event from './event';
|
|
|
10
9
|
|
|
11
10
|
const EFFECT_KEY = Symbol();
|
|
12
11
|
|
|
12
|
+
const HYDRATE_KEY = Symbol();
|
|
13
|
+
|
|
14
|
+
const STORE_KEY = Symbol();
|
|
15
|
+
|
|
13
16
|
const UPDATES_KEY = Symbol();
|
|
14
17
|
|
|
18
|
+
|
|
15
19
|
const STATE_HYDRATING = 0;
|
|
16
20
|
|
|
17
21
|
const STATE_NONE = 1;
|
|
@@ -33,7 +37,6 @@ let delimiters: Record<string, string> = {
|
|
|
33
37
|
class: ' ',
|
|
34
38
|
style: ';'
|
|
35
39
|
},
|
|
36
|
-
hydrating: Record<string, unknown> = {},
|
|
37
40
|
queue = q<Context>(64),
|
|
38
41
|
scheduled = false;
|
|
39
42
|
|
|
@@ -213,7 +216,7 @@ function update(
|
|
|
213
216
|
}
|
|
214
217
|
|
|
215
218
|
if (state === STATE_HYDRATING) {
|
|
216
|
-
|
|
219
|
+
((context.element[HYDRATE_KEY] ??= {}) as Record<PropertyKey, unknown>)[name] = value;
|
|
217
220
|
}
|
|
218
221
|
else {
|
|
219
222
|
context.updates[name] = value;
|
|
@@ -231,15 +234,21 @@ function update(
|
|
|
231
234
|
|
|
232
235
|
|
|
233
236
|
const apply = (element: Element) => {
|
|
234
|
-
|
|
235
|
-
|
|
237
|
+
let attributes = element[HYDRATE_KEY] as Record<PropertyKey, unknown> | undefined;
|
|
238
|
+
|
|
239
|
+
if (!attributes) {
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
for (let key in attributes) {
|
|
244
|
+
attribute(element, key, attributes[key]);
|
|
236
245
|
}
|
|
237
246
|
|
|
238
|
-
|
|
247
|
+
delete element[HYDRATE_KEY];
|
|
239
248
|
};
|
|
240
249
|
|
|
241
250
|
const spread = function (element: Element, value: Attributes | Attributes[]) {
|
|
242
|
-
let cache = element[
|
|
251
|
+
let cache = (element[STORE_KEY] ??= { [UPDATES_KEY]: {} }) as Record<PropertyKey, unknown>,
|
|
243
252
|
context = {
|
|
244
253
|
element,
|
|
245
254
|
store: cache,
|
package/src/constants.ts
CHANGED
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
const ATTRIBUTE_STORE = Symbol();
|
|
2
|
-
|
|
3
|
-
|
|
4
1
|
const NODE_CLOSING = 1;
|
|
5
2
|
|
|
6
3
|
const NODE_COMMENT = 2;
|
|
@@ -46,8 +43,6 @@ const RENDERABLE_REACTIVE = Symbol();
|
|
|
46
43
|
const RENDERABLE_TEMPLATE = Symbol();
|
|
47
44
|
|
|
48
45
|
|
|
49
|
-
const SLOT_CLEANUP = Symbol();
|
|
50
|
-
|
|
51
46
|
const SLOT_HTML = '<!--$-->';
|
|
52
47
|
|
|
53
48
|
const SLOT_MARKER = '{{$}}';
|
|
@@ -56,9 +51,8 @@ const SLOT_MARKER_LENGTH = SLOT_MARKER.length;
|
|
|
56
51
|
|
|
57
52
|
|
|
58
53
|
export {
|
|
59
|
-
ATTRIBUTE_STORE,
|
|
60
54
|
NODE_CLOSING, NODE_ELEMENT, NODE_SLOT, NODE_VOID, NODE_WHITELIST,
|
|
61
55
|
REGEX_EMPTY_TEXT_NODES, REGEX_SLOT_NODES,
|
|
62
56
|
RENDERABLE, RENDERABLE_REACTIVE, RENDERABLE_TEMPLATE,
|
|
63
|
-
|
|
57
|
+
SLOT_HTML, SLOT_MARKER, SLOT_MARKER_LENGTH
|
|
64
58
|
};
|
package/src/html/hydrate.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { root } from '@esportsplus/reactivity';
|
|
2
|
-
import { Element, Elements, Renderable, RenderableReactive, RenderableTemplate,
|
|
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,24 +10,19 @@ function reactive<T>(renderable: RenderableReactive<T>, slot: Slot) {
|
|
|
10
10
|
let array = renderable.values,
|
|
11
11
|
factory = renderable.template,
|
|
12
12
|
refresh = () => {
|
|
13
|
-
slot.render(
|
|
14
|
-
root(() => array.map(template))
|
|
15
|
-
);
|
|
16
|
-
},
|
|
17
|
-
renderer = (i: number, n?: number) => {
|
|
18
|
-
return root(() => array.map(template, i, n)) as RenderedGroup[];
|
|
13
|
+
slot.render( root(() => array.map(template)) );
|
|
19
14
|
},
|
|
20
15
|
template = function(data, i) {
|
|
21
16
|
let renderable = factory.call(this, data, i);
|
|
22
17
|
|
|
23
|
-
return
|
|
24
|
-
} as Parameters<typeof array['map']>[0];
|
|
18
|
+
return hydrate<T>(renderable, cache.get(renderable));
|
|
19
|
+
} as (this: typeof array, ...args: Parameters<Parameters<typeof array['map']>[0]>) => HydrateResult;
|
|
25
20
|
|
|
26
21
|
array.on('pop', () => {
|
|
27
22
|
slot.pop();
|
|
28
23
|
});
|
|
29
24
|
array.on('push', ({ items }) => {
|
|
30
|
-
slot.push(...
|
|
25
|
+
slot.push( ...root(() => array.map(template, array.length - items.length)) );
|
|
31
26
|
});
|
|
32
27
|
array.on('reverse', refresh);
|
|
33
28
|
array.on('shift', () => {
|
|
@@ -35,16 +30,16 @@ function reactive<T>(renderable: RenderableReactive<T>, slot: Slot) {
|
|
|
35
30
|
});
|
|
36
31
|
array.on('sort', refresh);
|
|
37
32
|
array.on('splice', ({ deleteCount: d, items: i, start: s }) => {
|
|
38
|
-
slot.splice(s, d, ...
|
|
33
|
+
slot.splice(s, d, ...root(() => array.map(template, s, i.length)));
|
|
39
34
|
});
|
|
40
35
|
array.on('unshift', ({ items }) => {
|
|
41
|
-
slot.unshift(...
|
|
36
|
+
slot.unshift( ...root(() => array.map(template, 0, items.length)) );
|
|
42
37
|
});
|
|
43
38
|
|
|
44
|
-
return array.map(template)
|
|
39
|
+
return array.map(template);
|
|
45
40
|
}
|
|
46
41
|
|
|
47
|
-
function
|
|
42
|
+
function hydrate<T>(renderable: Renderable<T>, template: Template): HydrateResult {
|
|
48
43
|
let elements: Elements = [],
|
|
49
44
|
fragment = cloneNode.call(template.fragment, true),
|
|
50
45
|
slots = template.slots;
|
|
@@ -58,7 +53,9 @@ function render<T>(renderable: Renderable<T>, template: Template) {
|
|
|
58
53
|
let { fn, path, slot } = slots[i];
|
|
59
54
|
|
|
60
55
|
if (path !== previous) {
|
|
61
|
-
|
|
56
|
+
if (node) {
|
|
57
|
+
apply(node);
|
|
58
|
+
}
|
|
62
59
|
|
|
63
60
|
node = fragment;
|
|
64
61
|
previous = path;
|
|
@@ -88,6 +85,6 @@ export default {
|
|
|
88
85
|
return reactive(renderable, slot);
|
|
89
86
|
},
|
|
90
87
|
static: <T>(renderable: RenderableTemplate<T>) => {
|
|
91
|
-
return
|
|
88
|
+
return hydrate(renderable, renderable.template || (renderable.template = cache.get(renderable)));
|
|
92
89
|
}
|
|
93
90
|
};
|
package/src/slot.ts
CHANGED
|
@@ -1,44 +1,44 @@
|
|
|
1
1
|
import { effect, root } from '@esportsplus/reactivity';
|
|
2
2
|
import { isArray, isFunction, isInstanceOf, isObject } from '@esportsplus/utilities';
|
|
3
|
-
import { RENDERABLE, RENDERABLE_REACTIVE
|
|
3
|
+
import { RENDERABLE, RENDERABLE_REACTIVE } from './constants';
|
|
4
4
|
import { hydrate } from './html';
|
|
5
|
-
import { Element, Elements, RenderableReactive, RenderableTemplate
|
|
6
|
-
import { append, firstChild, fragment, 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
|
+
const CLEANUP_KEY = Symbol();
|
|
11
|
+
|
|
12
|
+
|
|
10
13
|
let cleanup = queue<VoidFunction[]>(64),
|
|
11
|
-
|
|
12
|
-
|
|
14
|
+
scheduled = false,
|
|
15
|
+
template = fragment('');
|
|
13
16
|
|
|
14
17
|
|
|
15
|
-
function after(anchor: Element, groups:
|
|
16
|
-
let
|
|
17
|
-
n = groups.length;
|
|
18
|
+
function after(anchor: Element, groups: HydrateResult[]) {
|
|
19
|
+
let n = groups.length;
|
|
18
20
|
|
|
19
|
-
if (n) {
|
|
20
|
-
|
|
21
|
+
if (n === 0) {
|
|
22
|
+
return [];
|
|
23
|
+
}
|
|
21
24
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
}
|
|
25
|
-
else {
|
|
26
|
-
for (let i = 1; i < n; i++) {
|
|
27
|
-
let group = groups[i];
|
|
25
|
+
let elements: Elements[] = [],
|
|
26
|
+
fragment = groups[0].fragment || cloneNode.call(template);
|
|
28
27
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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];
|
|
33
34
|
|
|
34
|
-
|
|
35
|
-
|
|
35
|
+
append.call(fragment, f);
|
|
36
|
+
elements.push(e);
|
|
36
37
|
}
|
|
37
|
-
|
|
38
|
-
anchor.after(fragment);
|
|
39
|
-
groups[0].fragment = null;
|
|
40
38
|
}
|
|
41
39
|
|
|
40
|
+
anchor.after(fragment);
|
|
41
|
+
|
|
42
42
|
return elements;
|
|
43
43
|
}
|
|
44
44
|
|
|
@@ -49,8 +49,8 @@ function remove(...groups: Elements[]) {
|
|
|
49
49
|
for (let j = 0, o = group.length; j < o; j++) {
|
|
50
50
|
let item = group[j];
|
|
51
51
|
|
|
52
|
-
if (item
|
|
53
|
-
cleanup.add(item[
|
|
52
|
+
if (CLEANUP_KEY in item) {
|
|
53
|
+
cleanup.add(item[CLEANUP_KEY] as VoidFunction[]);
|
|
54
54
|
}
|
|
55
55
|
|
|
56
56
|
item.remove();
|
|
@@ -64,42 +64,47 @@ function remove(...groups: Elements[]) {
|
|
|
64
64
|
return groups;
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
-
function render(
|
|
67
|
+
function render(elements: Elements[], fragment: DocumentFragment | Node, input: unknown, slot?: Slot) {
|
|
68
68
|
if (input === false || input == null || input === '') {
|
|
69
|
-
return
|
|
69
|
+
return;
|
|
70
70
|
}
|
|
71
71
|
|
|
72
72
|
if (isArray(input)) {
|
|
73
73
|
for (let i = 0, n = input.length; i < n; i++) {
|
|
74
|
-
render(
|
|
74
|
+
render(elements, fragment, input[i]);
|
|
75
75
|
}
|
|
76
76
|
}
|
|
77
77
|
else if (isObject(input) && RENDERABLE in input) {
|
|
78
78
|
if (input[RENDERABLE] === RENDERABLE_REACTIVE) {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
)
|
|
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];
|
|
83
|
+
|
|
84
|
+
append.call(fragment, f);
|
|
85
|
+
elements.push(e);
|
|
86
|
+
}
|
|
82
87
|
}
|
|
83
88
|
else {
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
);
|
|
89
|
+
let { elements: e, fragment: f } = hydrate.static(input as RenderableTemplate<unknown>);
|
|
90
|
+
|
|
91
|
+
append.call(fragment, f);
|
|
92
|
+
elements.push(e);
|
|
87
93
|
}
|
|
88
94
|
}
|
|
89
95
|
else if (isInstanceOf(input, NodeList)) {
|
|
90
|
-
let
|
|
96
|
+
let e: Elements = [];
|
|
91
97
|
|
|
92
98
|
for (let node = firstChild.call(input); node; node = nextSibling.call(node)) {
|
|
93
|
-
|
|
99
|
+
e.push(node);
|
|
94
100
|
}
|
|
95
101
|
|
|
96
|
-
|
|
102
|
+
append.call(fragment, ...e);
|
|
103
|
+
elements.push(e);
|
|
97
104
|
}
|
|
98
105
|
else if (isInstanceOf(input, Node)) {
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
fragment: input as RenderedGroup['fragment']
|
|
102
|
-
});
|
|
106
|
+
append.call(fragment, input);
|
|
107
|
+
elements.push([ input as Element ]);
|
|
103
108
|
}
|
|
104
109
|
else {
|
|
105
110
|
let element = text( typeof input === 'string' ? input : String(input) );
|
|
@@ -108,13 +113,9 @@ function render(groups: RenderedGroup[], input: unknown, slot?: Slot) {
|
|
|
108
113
|
slot.text = element;
|
|
109
114
|
}
|
|
110
115
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
fragment: element
|
|
114
|
-
});
|
|
116
|
+
append.call(fragment, element);
|
|
117
|
+
elements.push([ element ]);
|
|
115
118
|
}
|
|
116
|
-
|
|
117
|
-
return groups;
|
|
118
119
|
}
|
|
119
120
|
|
|
120
121
|
function schedule() {
|
|
@@ -174,7 +175,7 @@ class Slot {
|
|
|
174
175
|
nodes = this.nodes[index];
|
|
175
176
|
|
|
176
177
|
if (nodes) {
|
|
177
|
-
node = nodes.
|
|
178
|
+
node = nodes[nodes.length - 1];
|
|
178
179
|
}
|
|
179
180
|
|
|
180
181
|
return node || this.marker;
|
|
@@ -196,7 +197,7 @@ class Slot {
|
|
|
196
197
|
return remove(group);
|
|
197
198
|
}
|
|
198
199
|
|
|
199
|
-
push(...groups:
|
|
200
|
+
push(...groups: HydrateResult[]) {
|
|
200
201
|
return this.nodes.push( ...after(this.anchor(), groups) );
|
|
201
202
|
}
|
|
202
203
|
|
|
@@ -236,7 +237,14 @@ class Slot {
|
|
|
236
237
|
}
|
|
237
238
|
|
|
238
239
|
this.clear();
|
|
239
|
-
|
|
240
|
+
|
|
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;
|
|
240
248
|
|
|
241
249
|
return this;
|
|
242
250
|
}
|
|
@@ -251,7 +259,7 @@ class Slot {
|
|
|
251
259
|
return remove(group);
|
|
252
260
|
}
|
|
253
261
|
|
|
254
|
-
splice(start: number, stop: number = this.nodes.length, ...groups:
|
|
262
|
+
splice(start: number, stop: number = this.nodes.length, ...groups: HydrateResult[]) {
|
|
255
263
|
return remove(
|
|
256
264
|
...this.nodes.splice(
|
|
257
265
|
start,
|
|
@@ -261,14 +269,14 @@ class Slot {
|
|
|
261
269
|
);
|
|
262
270
|
}
|
|
263
271
|
|
|
264
|
-
unshift(...groups:
|
|
272
|
+
unshift(...groups: HydrateResult[]) {
|
|
265
273
|
return this.nodes.unshift( ...after(this.marker, groups) );
|
|
266
274
|
}
|
|
267
275
|
}
|
|
268
276
|
|
|
269
277
|
|
|
270
278
|
const ondisconnect = (element: Element, fn: VoidFunction) => {
|
|
271
|
-
(
|
|
279
|
+
((element[CLEANUP_KEY] ??= []) as VoidFunction[]).push(fn);
|
|
272
280
|
};
|
|
273
281
|
|
|
274
282
|
|
package/src/types.ts
CHANGED
|
@@ -1,9 +1,5 @@
|
|
|
1
1
|
import { ReactiveArray } from '@esportsplus/reactivity';
|
|
2
|
-
import {
|
|
3
|
-
ATTRIBUTE_STORE,
|
|
4
|
-
RENDERABLE, RENDERABLE_REACTIVE, RENDERABLE_TEMPLATE,
|
|
5
|
-
SLOT_CLEANUP
|
|
6
|
-
} from './constants';
|
|
2
|
+
import { RENDERABLE, RENDERABLE_REACTIVE, RENDERABLE_TEMPLATE } from './constants';
|
|
7
3
|
import { firstChild } from './utilities';
|
|
8
4
|
import attributes from './attributes';
|
|
9
5
|
import slot from './slot';
|
|
@@ -30,13 +26,12 @@ type Effect<T> = () => EffectResponse<T>;
|
|
|
30
26
|
|
|
31
27
|
type EffectResponse<T> = T extends [] ? EffectResponse<T[number]>[] : Primitive | Renderable<T>;
|
|
32
28
|
|
|
33
|
-
type Element = HTMLElement & Attributes &
|
|
34
|
-
[ATTRIBUTE_STORE]?: Record<PropertyKey, unknown>;
|
|
35
|
-
[SLOT_CLEANUP]?: VoidFunction[]
|
|
36
|
-
} & Record<PropertyKey, unknown>;
|
|
29
|
+
type Element = HTMLElement & Attributes & Record<PropertyKey, unknown>;
|
|
37
30
|
|
|
38
31
|
type Elements = Element[];
|
|
39
32
|
|
|
33
|
+
type HydrateResult = { elements: Elements, fragment: DocumentFragment | Node };
|
|
34
|
+
|
|
40
35
|
// Copied from '@esportsplus/utilities'
|
|
41
36
|
// - Importing from ^ causes 'cannot be named without a reference to...' error
|
|
42
37
|
type Primitive = bigint | boolean | null | number | string | undefined;
|
|
@@ -62,8 +57,6 @@ type RenderableTemplate<T> = {
|
|
|
62
57
|
|
|
63
58
|
type RenderableValue<T = unknown> = Attributes | Readonly<Attributes> | Readonly<Attributes[]> | Effect<T> | Primitive | Renderable;
|
|
64
59
|
|
|
65
|
-
type RenderedGroup = { elements: Elements, fragment: DocumentFragment | Node | null };
|
|
66
|
-
|
|
67
60
|
type Template = {
|
|
68
61
|
fragment: DocumentFragment;
|
|
69
62
|
html: string;
|
|
@@ -79,6 +72,7 @@ type Template = {
|
|
|
79
72
|
export type {
|
|
80
73
|
Attributes,
|
|
81
74
|
Effect, Element, Elements,
|
|
82
|
-
|
|
75
|
+
HydrateResult,
|
|
76
|
+
Renderable, RenderableReactive, RenderableTemplate, RenderableValue,
|
|
83
77
|
Template
|
|
84
78
|
};
|
package/src/utilities.ts
CHANGED
|
@@ -7,13 +7,15 @@ let prototype,
|
|
|
7
7
|
t = document.createTextNode('');
|
|
8
8
|
|
|
9
9
|
|
|
10
|
+
prototype = DocumentFragment.prototype;
|
|
11
|
+
|
|
12
|
+
const append = prototype.append
|
|
13
|
+
|
|
10
14
|
// https://github.com/localvoid/ivi/blob/master/packages/ivi/src/client/core.ts#L38
|
|
11
15
|
prototype = Element.prototype;
|
|
12
16
|
|
|
13
17
|
const addEventListener = prototype.addEventListener;
|
|
14
18
|
|
|
15
|
-
const append = prototype.append;
|
|
16
|
-
|
|
17
19
|
const removeEventListener = prototype.removeEventListener;
|
|
18
20
|
|
|
19
21
|
const className = Object.getOwnPropertyDescriptor(prototype, 'className')!.set!;
|