@esportsplus/template 0.15.20 → 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/build/constants.d.ts +2 -1
- package/build/constants.js +3 -1
- package/build/html/cache.d.ts +2 -2
- package/build/html/cache.js +23 -15
- package/build/html/hydrate.d.ts +5 -3
- package/build/html/hydrate.js +63 -36
- package/build/render.d.ts +1 -1
- package/build/slot.d.ts +8 -8
- package/build/slot.js +88 -84
- package/build/types.d.ts +8 -8
- package/package.json +2 -2
- package/src/constants.ts +7 -0
- package/src/html/cache.ts +28 -18
- package/src/html/hydrate.ts +82 -40
- package/src/slot.ts +101 -111
- package/src/types.ts +8 -5
package/build/constants.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
declare const EMPTY_FRAGMENT: DocumentFragment;
|
|
1
2
|
declare const NODE_CLOSING = 1;
|
|
2
3
|
declare const NODE_ELEMENT = 3;
|
|
3
4
|
declare const NODE_SLOT = 4;
|
|
@@ -11,4 +12,4 @@ declare const RENDERABLE_TEMPLATE: unique symbol;
|
|
|
11
12
|
declare const SLOT_HTML = "<!--$-->";
|
|
12
13
|
declare const SLOT_MARKER = "{{$}}";
|
|
13
14
|
declare const SLOT_MARKER_LENGTH: number;
|
|
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 };
|
|
15
|
+
export { EMPTY_FRAGMENT, 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,3 +1,5 @@
|
|
|
1
|
+
import { fragment } from './utilities.js';
|
|
2
|
+
const EMPTY_FRAGMENT = fragment('');
|
|
1
3
|
const NODE_CLOSING = 1;
|
|
2
4
|
const NODE_COMMENT = 2;
|
|
3
5
|
const NODE_ELEMENT = 3;
|
|
@@ -31,4 +33,4 @@ const RENDERABLE_TEMPLATE = Symbol();
|
|
|
31
33
|
const SLOT_HTML = '<!--$-->';
|
|
32
34
|
const SLOT_MARKER = '{{$}}';
|
|
33
35
|
const SLOT_MARKER_LENGTH = SLOT_MARKER.length;
|
|
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 };
|
|
36
|
+
export { EMPTY_FRAGMENT, 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/cache.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Template } from '../types.js';
|
|
2
2
|
declare const _default: {
|
|
3
|
-
get:
|
|
3
|
+
get: (literals: TemplateStringsArray) => Template;
|
|
4
4
|
};
|
|
5
5
|
export default _default;
|
package/build/html/cache.js
CHANGED
|
@@ -3,8 +3,9 @@ import { firstChild, firstElementChild, fragment, nextElementSibling, nextSiblin
|
|
|
3
3
|
import { spread } from '../attributes.js';
|
|
4
4
|
import s from '../slot.js';
|
|
5
5
|
let cache = new WeakMap();
|
|
6
|
-
function build(literals
|
|
7
|
-
|
|
6
|
+
function build(literals) {
|
|
7
|
+
let n = literals.length - 1;
|
|
8
|
+
if (n === 0) {
|
|
8
9
|
return set(literals, literals[0]);
|
|
9
10
|
}
|
|
10
11
|
let buffer = '', html = literals.join(SLOT_MARKER)
|
|
@@ -13,21 +14,26 @@ function build(literals, values) {
|
|
|
13
14
|
children: 0,
|
|
14
15
|
elements: 0,
|
|
15
16
|
path: []
|
|
16
|
-
}], parsed = html.split(SLOT_MARKER), slot = 0, slots = []
|
|
17
|
+
}], parsed = html.split(SLOT_MARKER), slot = 0, slots = [];
|
|
17
18
|
for (let match of html.matchAll(REGEX_SLOT_NODES)) {
|
|
18
19
|
let parent = levels[level], type = match[1] === undefined ? NODE_SLOT : (NODE_WHITELIST[match[1].toLowerCase()] || NODE_ELEMENT);
|
|
19
20
|
if ((match.index || 1) - 1 > index) {
|
|
20
21
|
parent.children++;
|
|
21
22
|
}
|
|
22
23
|
if (type === NODE_ELEMENT || type === NODE_VOID) {
|
|
23
|
-
let attr = match[2]
|
|
24
|
+
let attr = match[2], path = parent.path.length
|
|
25
|
+
? methods(parent.elements, parent.path, firstElementChild, nextElementSibling)
|
|
26
|
+
: methods(parent.children, [], firstChild, nextSibling);
|
|
24
27
|
if (attr) {
|
|
25
|
-
let i = attr.indexOf(SLOT_MARKER),
|
|
28
|
+
let i = attr.indexOf(SLOT_MARKER), p = {
|
|
29
|
+
absolute: path,
|
|
30
|
+
parent: parent.path,
|
|
31
|
+
relative: path.slice(parent.path.length)
|
|
32
|
+
};
|
|
26
33
|
while (i !== -1) {
|
|
27
34
|
slots.push({
|
|
28
35
|
fn: spread,
|
|
29
|
-
|
|
30
|
-
path,
|
|
36
|
+
path: p,
|
|
31
37
|
slot
|
|
32
38
|
});
|
|
33
39
|
buffer += parsed[slot++];
|
|
@@ -38,23 +44,25 @@ function build(literals, values) {
|
|
|
38
44
|
levels[++level] = {
|
|
39
45
|
children: 0,
|
|
40
46
|
elements: 0,
|
|
41
|
-
path
|
|
42
|
-
? methods(parent.elements, parent.path, firstElementChild, nextElementSibling)
|
|
43
|
-
: methods(parent.children, [], firstChild, nextSibling)
|
|
47
|
+
path
|
|
44
48
|
};
|
|
45
49
|
}
|
|
46
50
|
parent.elements++;
|
|
47
51
|
}
|
|
48
52
|
else if (type === NODE_SLOT) {
|
|
53
|
+
let relative = methods(parent.children, [], firstChild, nextSibling);
|
|
49
54
|
buffer += parsed[slot] + SLOT_HTML;
|
|
50
55
|
slots.push({
|
|
51
56
|
fn: s,
|
|
52
|
-
|
|
53
|
-
|
|
57
|
+
path: {
|
|
58
|
+
absolute: [...parent.path, ...relative],
|
|
59
|
+
parent: parent.path,
|
|
60
|
+
relative
|
|
61
|
+
},
|
|
54
62
|
slot: slot++
|
|
55
63
|
});
|
|
56
64
|
}
|
|
57
|
-
if (slot ===
|
|
65
|
+
if (slot === n) {
|
|
58
66
|
buffer += parsed[slot];
|
|
59
67
|
break;
|
|
60
68
|
}
|
|
@@ -86,7 +94,7 @@ function set(literals, html, slots = null) {
|
|
|
86
94
|
cache.set(literals, template);
|
|
87
95
|
return template;
|
|
88
96
|
}
|
|
89
|
-
const get = (
|
|
90
|
-
return cache.get(literals) || build(literals
|
|
97
|
+
const get = (literals) => {
|
|
98
|
+
return cache.get(literals) || build(literals);
|
|
91
99
|
};
|
|
92
100
|
export default { get };
|
package/build/html/hydrate.d.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Elements, Fragment, RenderableReactive, RenderableTemplate } from '../types.js';
|
|
2
2
|
import { Slot } from '../slot.js';
|
|
3
|
+
declare function reactive<T>(elements: Elements[], fragment: Fragment, renderable: RenderableReactive<T>, slot: Slot): void;
|
|
4
|
+
declare function hydrate<T>(elements: Elements[] | null, fragment: Fragment, renderable: RenderableTemplate<T>): void;
|
|
3
5
|
declare const _default: {
|
|
4
|
-
reactive:
|
|
5
|
-
static:
|
|
6
|
+
reactive: typeof reactive;
|
|
7
|
+
static: typeof hydrate;
|
|
6
8
|
};
|
|
7
9
|
export default _default;
|
package/build/html/hydrate.js
CHANGED
|
@@ -1,58 +1,85 @@
|
|
|
1
1
|
import { root } from '@esportsplus/reactivity';
|
|
2
|
-
import {
|
|
2
|
+
import { EMPTY_FRAGMENT } from '../constants.js';
|
|
3
|
+
import { cloneNode } from '../utilities.js';
|
|
3
4
|
import cache from './cache.js';
|
|
4
|
-
function reactive(renderable, slot) {
|
|
5
|
+
function reactive(elements, fragment, renderable, slot) {
|
|
5
6
|
let array = renderable.values, factory = renderable.template, refresh = () => {
|
|
6
|
-
|
|
7
|
+
root(() => array.map(template));
|
|
8
|
+
slot.clear();
|
|
9
|
+
slot.anchor().after(fragment);
|
|
10
|
+
slot.nodes = elements;
|
|
11
|
+
reset();
|
|
12
|
+
}, reset = () => {
|
|
13
|
+
elements = [];
|
|
14
|
+
fragment = cloneNode.call(EMPTY_FRAGMENT);
|
|
7
15
|
}, template = function (data, i) {
|
|
8
|
-
|
|
9
|
-
return hydrate(renderable, cache.get(renderable));
|
|
16
|
+
hydrate(elements, fragment, factory.call(this, data, i));
|
|
10
17
|
};
|
|
11
|
-
array.on('
|
|
12
|
-
|
|
13
|
-
});
|
|
14
|
-
array.on('push', ({ items }) => {
|
|
15
|
-
slot.push(...root(() => array.map(template, array.length - items.length)));
|
|
16
|
-
});
|
|
18
|
+
array.on('clear', () => slot.clear());
|
|
19
|
+
array.on('pop', () => slot.pop());
|
|
17
20
|
array.on('reverse', refresh);
|
|
18
|
-
array.on('shift', () =>
|
|
19
|
-
slot.shift();
|
|
20
|
-
});
|
|
21
|
+
array.on('shift', () => slot.shift());
|
|
21
22
|
array.on('sort', refresh);
|
|
23
|
+
array.on('push', ({ items }) => {
|
|
24
|
+
let anchor = slot.anchor();
|
|
25
|
+
elements = slot.nodes;
|
|
26
|
+
root(() => array.map(template, array.length - items.length));
|
|
27
|
+
anchor.after(fragment);
|
|
28
|
+
reset();
|
|
29
|
+
});
|
|
22
30
|
array.on('splice', ({ deleteCount: d, items: i, start: s }) => {
|
|
23
|
-
|
|
31
|
+
if (array.length === 0) {
|
|
32
|
+
slot.clear();
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
root(() => array.map(template, s, i.length));
|
|
36
|
+
slot.splice(s, d, fragment, ...elements);
|
|
37
|
+
reset();
|
|
24
38
|
});
|
|
25
39
|
array.on('unshift', ({ items }) => {
|
|
26
|
-
|
|
40
|
+
root(() => array.map(template, 0, items.length));
|
|
41
|
+
slot.unshift(fragment, ...elements);
|
|
42
|
+
reset();
|
|
27
43
|
});
|
|
28
|
-
|
|
44
|
+
root(() => array.map(template));
|
|
45
|
+
reset();
|
|
29
46
|
}
|
|
30
|
-
function hydrate(
|
|
31
|
-
let
|
|
47
|
+
function hydrate(elements, fragment, renderable) {
|
|
48
|
+
let { fragment: frag, slots } = cache.get(renderable.literals), clone = cloneNode.call(frag, true);
|
|
32
49
|
if (slots !== null) {
|
|
33
|
-
let node,
|
|
34
|
-
for (let i = slots.length
|
|
35
|
-
let { fn, path, slot } = slots[i];
|
|
36
|
-
if (
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
50
|
+
let node, nodePath, parent, parentPath, values = renderable.values;
|
|
51
|
+
for (let i = 0, n = slots.length; i < n; i++) {
|
|
52
|
+
let { fn, path, slot } = slots[i], pp = path.parent, pr = path.relative;
|
|
53
|
+
if (pp !== parentPath) {
|
|
54
|
+
if (pp === nodePath) {
|
|
55
|
+
parent = node;
|
|
56
|
+
parentPath = nodePath;
|
|
57
|
+
nodePath = undefined;
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
parent = clone;
|
|
61
|
+
parentPath = pp;
|
|
62
|
+
for (let o = 0, j = pp.length; o < j; o++) {
|
|
63
|
+
parent = pp[o].call(parent);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
if (pr !== nodePath) {
|
|
68
|
+
node = parent;
|
|
69
|
+
nodePath = path.absolute;
|
|
70
|
+
for (let o = 0, j = pr.length; o < j; o++) {
|
|
71
|
+
node = pr[o].call(node);
|
|
41
72
|
}
|
|
42
73
|
}
|
|
43
74
|
fn(node, values[slot]);
|
|
44
75
|
}
|
|
45
76
|
}
|
|
46
|
-
|
|
47
|
-
elements.push(
|
|
77
|
+
if (elements) {
|
|
78
|
+
elements.push([...clone.childNodes]);
|
|
48
79
|
}
|
|
49
|
-
|
|
80
|
+
fragment.appendChild(clone);
|
|
50
81
|
}
|
|
51
82
|
export default {
|
|
52
|
-
reactive
|
|
53
|
-
|
|
54
|
-
},
|
|
55
|
-
static: (renderable) => {
|
|
56
|
-
return hydrate(renderable, renderable.template || (renderable.template = cache.get(renderable)));
|
|
57
|
-
}
|
|
83
|
+
reactive,
|
|
84
|
+
static: hydrate
|
|
58
85
|
};
|
package/build/render.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import { Slot } from './slot.js';
|
|
2
2
|
import { Renderable } from './types.js';
|
|
3
|
-
declare const _default: (renderable: Renderable, parent: HTMLElement | Slot) => Slot;
|
|
3
|
+
declare const _default: (renderable: Renderable, parent: HTMLElement | Slot) => void | Slot;
|
|
4
4
|
export default _default;
|
package/build/slot.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Element, Elements,
|
|
1
|
+
import { Element, Elements, Fragment } from './types.js';
|
|
2
2
|
declare class Slot {
|
|
3
3
|
marker: Element;
|
|
4
4
|
nodes: Elements[];
|
|
@@ -7,15 +7,15 @@ declare class Slot {
|
|
|
7
7
|
get length(): number;
|
|
8
8
|
set length(n: number);
|
|
9
9
|
anchor(index?: number): Element;
|
|
10
|
-
clear():
|
|
11
|
-
pop():
|
|
12
|
-
push(...
|
|
10
|
+
clear(): this;
|
|
11
|
+
pop(): this;
|
|
12
|
+
push(fragment: Fragment, ...nodes: Elements[]): this;
|
|
13
13
|
render(input: unknown, state?: number): this;
|
|
14
|
-
shift():
|
|
15
|
-
splice(start: number, stop?: number, ...
|
|
16
|
-
unshift(...
|
|
14
|
+
shift(): this;
|
|
15
|
+
splice(start: number, stop?: number, fragment?: Fragment, ...nodes: Elements[]): this;
|
|
16
|
+
unshift(fragment: Fragment, ...nodes: Elements[]): this;
|
|
17
17
|
}
|
|
18
18
|
declare const ondisconnect: (element: Element, fn: VoidFunction) => void;
|
|
19
|
-
declare const _default: (marker: Element, value: unknown) =>
|
|
19
|
+
declare const _default: (marker: Element, value: unknown) => void;
|
|
20
20
|
export default _default;
|
|
21
21
|
export { ondisconnect, Slot };
|
package/build/slot.js
CHANGED
|
@@ -1,34 +1,20 @@
|
|
|
1
|
-
import { effect
|
|
2
|
-
import { isArray,
|
|
3
|
-
import { RENDERABLE, RENDERABLE_REACTIVE } from './constants.js';
|
|
1
|
+
import { effect } from '@esportsplus/reactivity';
|
|
2
|
+
import { isArray, isInstanceOf } from '@esportsplus/utilities';
|
|
3
|
+
import { EMPTY_FRAGMENT, RENDERABLE, RENDERABLE_REACTIVE } from './constants.js';
|
|
4
4
|
import { hydrate } from './html/index.js';
|
|
5
|
-
import { append, cloneNode,
|
|
5
|
+
import { append, cloneNode, microtask, nodeValue, raf, text } from './utilities.js';
|
|
6
6
|
import queue from '@esportsplus/queue';
|
|
7
7
|
const CLEANUP_KEY = Symbol();
|
|
8
8
|
const CONNECTED = 0;
|
|
9
9
|
const HYDRATING = 1;
|
|
10
|
-
let cleanup = queue(64), scheduled = false
|
|
11
|
-
function after(anchor, groups) {
|
|
12
|
-
let n = groups.length;
|
|
13
|
-
if (n === 0) {
|
|
14
|
-
return [];
|
|
15
|
-
}
|
|
16
|
-
let fragment = cloneNode.call(template), elements = new Array(n);
|
|
17
|
-
for (let i = 0; i < n; i++) {
|
|
18
|
-
let { elements: e, fragment: f } = groups[i];
|
|
19
|
-
append.call(fragment, f);
|
|
20
|
-
elements[i] = e;
|
|
21
|
-
}
|
|
22
|
-
anchor.after(fragment);
|
|
23
|
-
return elements;
|
|
24
|
-
}
|
|
10
|
+
let cleanup = queue(64), scheduled = false;
|
|
25
11
|
function remove(groups) {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
let item = group[j];
|
|
12
|
+
let group, item;
|
|
13
|
+
while (group = groups.pop()) {
|
|
14
|
+
while (item = group.pop()) {
|
|
30
15
|
if (CLEANUP_KEY in item) {
|
|
31
16
|
cleanup.add(item[CLEANUP_KEY]);
|
|
17
|
+
item[CLEANUP_KEY] = null;
|
|
32
18
|
}
|
|
33
19
|
item.remove();
|
|
34
20
|
}
|
|
@@ -36,52 +22,47 @@ function remove(groups) {
|
|
|
36
22
|
if (!scheduled && cleanup.length) {
|
|
37
23
|
schedule();
|
|
38
24
|
}
|
|
39
|
-
return groups;
|
|
40
25
|
}
|
|
41
|
-
function render(elements, fragment, input, slot) {
|
|
26
|
+
function render(anchor, elements, fragment, input, slot) {
|
|
42
27
|
if (input === false || input == null || input === '') {
|
|
43
28
|
return;
|
|
44
29
|
}
|
|
45
30
|
let type = typeof input;
|
|
46
|
-
if (type === '
|
|
31
|
+
if (type === 'object' && RENDERABLE in input) {
|
|
32
|
+
if (input[RENDERABLE] === RENDERABLE_REACTIVE) {
|
|
33
|
+
slot ??= new Slot(anchor);
|
|
34
|
+
hydrate.reactive(slot.nodes, fragment, input, slot);
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
hydrate.static(elements, fragment, input);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
else if (type === 'string' || type === 'number') {
|
|
47
41
|
let element = text(type === 'string' ? input : String(input));
|
|
48
42
|
if (slot) {
|
|
49
43
|
slot.text = element;
|
|
50
44
|
}
|
|
51
45
|
append.call(fragment, element);
|
|
52
|
-
elements
|
|
46
|
+
if (elements) {
|
|
47
|
+
elements.push([element]);
|
|
48
|
+
}
|
|
53
49
|
}
|
|
54
50
|
else if (isArray(input)) {
|
|
55
51
|
for (let i = 0, n = input.length; i < n; i++) {
|
|
56
|
-
render(elements, fragment, input[i]);
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
else if (isObject(input) && RENDERABLE in input) {
|
|
60
|
-
if (input[RENDERABLE] === RENDERABLE_REACTIVE) {
|
|
61
|
-
let response = hydrate.reactive(input, slot);
|
|
62
|
-
for (let i = 0, n = response.length; i < n; i++) {
|
|
63
|
-
let { elements: e, fragment: f } = response[i];
|
|
64
|
-
append.call(fragment, f);
|
|
65
|
-
elements.push(e);
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
else {
|
|
69
|
-
let { elements: e, fragment: f } = hydrate.static(input);
|
|
70
|
-
append.call(fragment, f);
|
|
71
|
-
elements.push(e);
|
|
52
|
+
render(anchor, elements, fragment, input[i], slot);
|
|
72
53
|
}
|
|
73
54
|
}
|
|
74
55
|
else if (isInstanceOf(input, NodeList)) {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
56
|
+
append.call(fragment, ...input);
|
|
57
|
+
if (elements) {
|
|
58
|
+
elements.push([...input]);
|
|
78
59
|
}
|
|
79
|
-
append.call(fragment, ...e);
|
|
80
|
-
elements.push(e);
|
|
81
60
|
}
|
|
82
61
|
else if (isInstanceOf(input, Node)) {
|
|
83
62
|
append.call(fragment, input);
|
|
84
|
-
elements
|
|
63
|
+
if (elements) {
|
|
64
|
+
elements.push([input]);
|
|
65
|
+
}
|
|
85
66
|
}
|
|
86
67
|
}
|
|
87
68
|
function schedule() {
|
|
@@ -119,36 +100,46 @@ class Slot {
|
|
|
119
100
|
return this.nodes.length;
|
|
120
101
|
}
|
|
121
102
|
set length(n) {
|
|
122
|
-
this.
|
|
103
|
+
if (n >= this.nodes.length) {
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
else if (n === 0) {
|
|
107
|
+
this.clear();
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
this.splice(n);
|
|
111
|
+
}
|
|
123
112
|
}
|
|
124
113
|
anchor(index = this.nodes.length - 1) {
|
|
125
114
|
let nodes = this.nodes[index];
|
|
126
115
|
return nodes ? nodes[nodes.length - 1] : this.marker;
|
|
127
116
|
}
|
|
128
117
|
clear() {
|
|
118
|
+
if (this.text) {
|
|
119
|
+
this.nodes.push([this.text]);
|
|
120
|
+
this.text = null;
|
|
121
|
+
}
|
|
129
122
|
remove(this.nodes);
|
|
130
|
-
this
|
|
131
|
-
this.text = null;
|
|
123
|
+
return this;
|
|
132
124
|
}
|
|
133
125
|
pop() {
|
|
134
126
|
let group = this.nodes.pop();
|
|
135
|
-
if (
|
|
136
|
-
|
|
127
|
+
if (group) {
|
|
128
|
+
remove([group]);
|
|
137
129
|
}
|
|
138
|
-
return
|
|
130
|
+
return this;
|
|
139
131
|
}
|
|
140
|
-
push(...
|
|
141
|
-
|
|
132
|
+
push(fragment, ...nodes) {
|
|
133
|
+
this.anchor().after(fragment);
|
|
134
|
+
this.nodes.push(...nodes);
|
|
135
|
+
return this;
|
|
142
136
|
}
|
|
143
137
|
render(input, state = HYDRATING) {
|
|
144
|
-
|
|
138
|
+
let type = typeof input;
|
|
139
|
+
if (type === 'function') {
|
|
145
140
|
ondisconnect(this.marker, effect(() => {
|
|
146
141
|
let v = input();
|
|
147
|
-
if (
|
|
148
|
-
root(() => this.render(v(), state));
|
|
149
|
-
state = CONNECTED;
|
|
150
|
-
}
|
|
151
|
-
else if (state === HYDRATING) {
|
|
142
|
+
if (state === HYDRATING) {
|
|
152
143
|
this.render(v, state);
|
|
153
144
|
state = CONNECTED;
|
|
154
145
|
}
|
|
@@ -160,41 +151,54 @@ class Slot {
|
|
|
160
151
|
}));
|
|
161
152
|
return this;
|
|
162
153
|
}
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
154
|
+
let text = this.text;
|
|
155
|
+
if (text && text.isConnected && (input == null || type !== 'object')) {
|
|
156
|
+
nodeValue.call(text, (type === 'number' || type === 'string') ? input : '');
|
|
157
|
+
}
|
|
158
|
+
else {
|
|
159
|
+
this.clear();
|
|
160
|
+
let fragment = cloneNode.call(EMPTY_FRAGMENT);
|
|
161
|
+
render(this.marker, this.nodes, fragment, input, this);
|
|
162
|
+
this.marker.after(fragment);
|
|
169
163
|
}
|
|
170
|
-
this.clear();
|
|
171
|
-
let fragment = cloneNode.call(template), nodes = [];
|
|
172
|
-
render(nodes, fragment, input, this);
|
|
173
|
-
this.marker.after(fragment);
|
|
174
|
-
this.nodes = nodes;
|
|
175
164
|
return this;
|
|
176
165
|
}
|
|
177
166
|
shift() {
|
|
178
167
|
let group = this.nodes.shift();
|
|
179
|
-
if (
|
|
180
|
-
|
|
168
|
+
if (group) {
|
|
169
|
+
remove([group]);
|
|
181
170
|
}
|
|
182
|
-
return
|
|
171
|
+
return this;
|
|
183
172
|
}
|
|
184
|
-
splice(start, stop = this.nodes.length, ...
|
|
185
|
-
if (!
|
|
186
|
-
|
|
173
|
+
splice(start, stop = this.nodes.length, fragment, ...nodes) {
|
|
174
|
+
if (!fragment) {
|
|
175
|
+
remove(this.nodes.splice(start, stop));
|
|
187
176
|
}
|
|
188
|
-
|
|
177
|
+
else {
|
|
178
|
+
this.anchor(start).after(fragment);
|
|
179
|
+
remove(this.nodes.splice(start, stop, ...nodes));
|
|
180
|
+
}
|
|
181
|
+
return this;
|
|
189
182
|
}
|
|
190
|
-
unshift(...
|
|
191
|
-
|
|
183
|
+
unshift(fragment, ...nodes) {
|
|
184
|
+
this.marker.after(fragment);
|
|
185
|
+
this.nodes.unshift(...nodes);
|
|
186
|
+
return this;
|
|
192
187
|
}
|
|
193
188
|
}
|
|
194
189
|
const ondisconnect = (element, fn) => {
|
|
195
190
|
(element[CLEANUP_KEY] ??= []).push(fn);
|
|
196
191
|
};
|
|
197
192
|
export default (marker, value) => {
|
|
198
|
-
|
|
193
|
+
let type = typeof value;
|
|
194
|
+
if (type === 'function') {
|
|
195
|
+
new Slot(marker).render(value);
|
|
196
|
+
}
|
|
197
|
+
else {
|
|
198
|
+
let fragment = cloneNode.call(EMPTY_FRAGMENT);
|
|
199
|
+
render(marker, null, fragment, value);
|
|
200
|
+
marker.after(fragment);
|
|
201
|
+
}
|
|
202
|
+
;
|
|
199
203
|
};
|
|
200
204
|
export { ondisconnect, Slot };
|
package/build/types.d.ts
CHANGED
|
@@ -20,10 +20,7 @@ type Effect<T> = () => EffectResponse<T>;
|
|
|
20
20
|
type EffectResponse<T> = T extends [] ? EffectResponse<T[number]>[] : Primitive | Renderable<T>;
|
|
21
21
|
type Element = HTMLElement & Attributes & Record<PropertyKey, unknown>;
|
|
22
22
|
type Elements = Element[];
|
|
23
|
-
type
|
|
24
|
-
elements: Elements;
|
|
25
|
-
fragment: DocumentFragment | Node;
|
|
26
|
-
};
|
|
23
|
+
type Fragment = DocumentFragment | Node;
|
|
27
24
|
type Primitive = bigint | boolean | null | number | string | undefined;
|
|
28
25
|
type Renderable<T = unknown> = RenderableReactive<T> | RenderableTemplate<T>;
|
|
29
26
|
type RenderableReactive<T = unknown> = Readonly<{
|
|
@@ -32,7 +29,7 @@ type RenderableReactive<T = unknown> = Readonly<{
|
|
|
32
29
|
template: (this: ThisParameterType<Parameters<ReactiveArray<T>['map']>[0]>, ...args: Parameters<Parameters<ReactiveArray<T>['map']>[0]>) => RenderableTemplate<T>;
|
|
33
30
|
values: ReactiveArray<T>;
|
|
34
31
|
}>;
|
|
35
|
-
type RenderableTemplate<T> = {
|
|
32
|
+
type RenderableTemplate<T = unknown> = {
|
|
36
33
|
[RENDERABLE]: typeof RENDERABLE_TEMPLATE;
|
|
37
34
|
literals: TemplateStringsArray;
|
|
38
35
|
template: Template | null;
|
|
@@ -45,9 +42,12 @@ type Template = {
|
|
|
45
42
|
literals: TemplateStringsArray;
|
|
46
43
|
slots: {
|
|
47
44
|
fn: typeof attributes.spread | typeof slot;
|
|
48
|
-
|
|
49
|
-
|
|
45
|
+
path: {
|
|
46
|
+
absolute: typeof firstChild[];
|
|
47
|
+
parent: typeof firstChild[];
|
|
48
|
+
relative: typeof firstChild[];
|
|
49
|
+
};
|
|
50
50
|
slot: number;
|
|
51
51
|
}[] | null;
|
|
52
52
|
};
|
|
53
|
-
export type { Attributes, Effect, Element, Elements,
|
|
53
|
+
export type { Attributes, Effect, Element, Elements, Fragment, Renderable, RenderableReactive, RenderableTemplate, RenderableValue, Template };
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"author": "ICJR",
|
|
3
3
|
"dependencies": {
|
|
4
4
|
"@esportsplus/queue": "^0.1.0",
|
|
5
|
-
"@esportsplus/reactivity": "^0.
|
|
5
|
+
"@esportsplus/reactivity": "^0.13.2",
|
|
6
6
|
"@esportsplus/tasks": "^0.2.1",
|
|
7
7
|
"@esportsplus/utilities": "^0.22.1"
|
|
8
8
|
},
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
"private": false,
|
|
15
15
|
"type": "module",
|
|
16
16
|
"types": "./build/index.d.ts",
|
|
17
|
-
"version": "0.
|
|
17
|
+
"version": "0.16.1",
|
|
18
18
|
"scripts": {
|
|
19
19
|
"build": "tsc && tsc-alias",
|
|
20
20
|
"-": "-"
|
package/src/constants.ts
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
import { fragment } from './utilities';
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
const EMPTY_FRAGMENT = fragment('');
|
|
5
|
+
|
|
6
|
+
|
|
1
7
|
const NODE_CLOSING = 1;
|
|
2
8
|
|
|
3
9
|
const NODE_COMMENT = 2;
|
|
@@ -51,6 +57,7 @@ const SLOT_MARKER_LENGTH = SLOT_MARKER.length;
|
|
|
51
57
|
|
|
52
58
|
|
|
53
59
|
export {
|
|
60
|
+
EMPTY_FRAGMENT,
|
|
54
61
|
NODE_CLOSING, NODE_ELEMENT, NODE_SLOT, NODE_VOID, NODE_WHITELIST,
|
|
55
62
|
REGEX_EMPTY_TEXT_NODES, REGEX_SLOT_NODES,
|
|
56
63
|
RENDERABLE, RENDERABLE_REACTIVE, RENDERABLE_TEMPLATE,
|
package/src/html/cache.ts
CHANGED
|
@@ -2,7 +2,7 @@ import {
|
|
|
2
2
|
NODE_CLOSING, NODE_ELEMENT, NODE_SLOT, NODE_VOID, NODE_WHITELIST, REGEX_EMPTY_TEXT_NODES,
|
|
3
3
|
REGEX_SLOT_NODES, SLOT_HTML, SLOT_MARKER, SLOT_MARKER_LENGTH
|
|
4
4
|
} from '~/constants';
|
|
5
|
-
import {
|
|
5
|
+
import { Template } from '~/types';
|
|
6
6
|
import { firstChild, firstElementChild, fragment, nextElementSibling, nextSibling } from '~/utilities';
|
|
7
7
|
import { spread } from '~/attributes';
|
|
8
8
|
import s from '~/slot';
|
|
@@ -11,8 +11,10 @@ import s from '~/slot';
|
|
|
11
11
|
let cache = new WeakMap<TemplateStringsArray, Template>();
|
|
12
12
|
|
|
13
13
|
|
|
14
|
-
function build(literals: TemplateStringsArray
|
|
15
|
-
|
|
14
|
+
function build(literals: TemplateStringsArray) {
|
|
15
|
+
let n = literals.length - 1;
|
|
16
|
+
|
|
17
|
+
if (n === 0) {
|
|
16
18
|
return set(literals, literals[0]);
|
|
17
19
|
}
|
|
18
20
|
|
|
@@ -25,12 +27,11 @@ function build(literals: TemplateStringsArray, values: unknown[]) {
|
|
|
25
27
|
levels = [{
|
|
26
28
|
children: 0,
|
|
27
29
|
elements: 0,
|
|
28
|
-
path: [] as NonNullable<Template['slots']>[
|
|
30
|
+
path: [] as NonNullable<Template['slots']>[number]['path']['parent']
|
|
29
31
|
}],
|
|
30
32
|
parsed = html.split(SLOT_MARKER),
|
|
31
33
|
slot = 0,
|
|
32
|
-
slots: Template['slots'] = []
|
|
33
|
-
total = values.length;
|
|
34
|
+
slots: Template['slots'] = [];
|
|
34
35
|
|
|
35
36
|
for (let match of html.matchAll(REGEX_SLOT_NODES)) {
|
|
36
37
|
let parent = levels[level],
|
|
@@ -42,17 +43,23 @@ function build(literals: TemplateStringsArray, values: unknown[]) {
|
|
|
42
43
|
}
|
|
43
44
|
|
|
44
45
|
if (type === NODE_ELEMENT || type === NODE_VOID) {
|
|
45
|
-
let attr = match[2]
|
|
46
|
+
let attr = match[2],
|
|
47
|
+
path = parent.path.length
|
|
48
|
+
? methods(parent.elements, parent.path, firstElementChild, nextElementSibling)
|
|
49
|
+
: methods(parent.children, [], firstChild, nextSibling);
|
|
46
50
|
|
|
47
51
|
if (attr) {
|
|
48
52
|
let i = attr.indexOf(SLOT_MARKER),
|
|
49
|
-
|
|
53
|
+
p = {
|
|
54
|
+
absolute: path,
|
|
55
|
+
parent: parent.path,
|
|
56
|
+
relative: path.slice(parent.path.length)
|
|
57
|
+
};
|
|
50
58
|
|
|
51
59
|
while (i !== -1) {
|
|
52
60
|
slots.push({
|
|
53
61
|
fn: spread,
|
|
54
|
-
|
|
55
|
-
path,
|
|
62
|
+
path: p,
|
|
56
63
|
slot
|
|
57
64
|
});
|
|
58
65
|
|
|
@@ -65,25 +72,28 @@ function build(literals: TemplateStringsArray, values: unknown[]) {
|
|
|
65
72
|
levels[++level] = {
|
|
66
73
|
children: 0,
|
|
67
74
|
elements: 0,
|
|
68
|
-
path
|
|
69
|
-
? methods(parent.elements, parent.path, firstElementChild, nextElementSibling)
|
|
70
|
-
: methods(parent.children, [], firstChild, nextSibling)
|
|
75
|
+
path
|
|
71
76
|
};
|
|
72
77
|
}
|
|
73
78
|
|
|
74
79
|
parent.elements++;
|
|
75
80
|
}
|
|
76
81
|
else if (type === NODE_SLOT) {
|
|
82
|
+
let relative = methods(parent.children, [], firstChild, nextSibling);
|
|
83
|
+
|
|
77
84
|
buffer += parsed[slot] + SLOT_HTML;
|
|
78
85
|
slots.push({
|
|
79
86
|
fn: s,
|
|
80
|
-
|
|
81
|
-
|
|
87
|
+
path: {
|
|
88
|
+
absolute: [...parent.path, ...relative],
|
|
89
|
+
parent: parent.path,
|
|
90
|
+
relative
|
|
91
|
+
},
|
|
82
92
|
slot: slot++
|
|
83
93
|
});
|
|
84
94
|
}
|
|
85
95
|
|
|
86
|
-
if (slot ===
|
|
96
|
+
if (slot === n) {
|
|
87
97
|
buffer += parsed[slot];
|
|
88
98
|
break;
|
|
89
99
|
}
|
|
@@ -127,8 +137,8 @@ function set(literals: TemplateStringsArray, html: string, slots: Template['slot
|
|
|
127
137
|
}
|
|
128
138
|
|
|
129
139
|
|
|
130
|
-
const get =
|
|
131
|
-
return cache.get(literals) || build(literals
|
|
140
|
+
const get = (literals: TemplateStringsArray) => {
|
|
141
|
+
return cache.get(literals) || build(literals);
|
|
132
142
|
};
|
|
133
143
|
|
|
134
144
|
|
package/src/html/hydrate.ts
CHANGED
|
@@ -1,62 +1,108 @@
|
|
|
1
1
|
import { root } from '@esportsplus/reactivity';
|
|
2
|
-
import {
|
|
2
|
+
import { EMPTY_FRAGMENT } from '~/constants';
|
|
3
|
+
import { Elements, Fragment, RenderableReactive, RenderableTemplate } from '~/types';
|
|
3
4
|
import { Slot } from '~/slot';
|
|
4
|
-
import { cloneNode
|
|
5
|
+
import { cloneNode } from '~/utilities';
|
|
5
6
|
import cache from './cache';
|
|
6
7
|
|
|
7
8
|
|
|
8
|
-
function reactive<T>(renderable: RenderableReactive<T>, slot: Slot) {
|
|
9
|
+
function reactive<T>(elements: Elements[], fragment: Fragment, renderable: RenderableReactive<T>, slot: Slot) {
|
|
9
10
|
let array = renderable.values,
|
|
10
11
|
factory = renderable.template,
|
|
11
12
|
refresh = () => {
|
|
12
|
-
|
|
13
|
+
root(() => array.map(template));
|
|
14
|
+
|
|
15
|
+
slot.clear();
|
|
16
|
+
slot.anchor().after(fragment);
|
|
17
|
+
slot.nodes = elements;
|
|
18
|
+
|
|
19
|
+
reset();
|
|
20
|
+
},
|
|
21
|
+
reset = () => {
|
|
22
|
+
elements = [];
|
|
23
|
+
fragment = cloneNode.call(EMPTY_FRAGMENT);
|
|
13
24
|
},
|
|
14
25
|
template = function(data, i) {
|
|
15
|
-
|
|
26
|
+
hydrate(elements, fragment, factory.call(this, data, i));
|
|
27
|
+
} as (this: typeof array, ...args: Parameters<Parameters<typeof array['map']>[0]>) => void;
|
|
16
28
|
|
|
17
|
-
|
|
18
|
-
|
|
29
|
+
array.on('clear', () => slot.clear());
|
|
30
|
+
array.on('pop', () => slot.pop());
|
|
31
|
+
array.on('reverse', refresh);
|
|
32
|
+
array.on('shift', () => slot.shift());
|
|
33
|
+
array.on('sort', refresh);
|
|
19
34
|
|
|
20
|
-
array.on('pop', () => {
|
|
21
|
-
slot.pop();
|
|
22
|
-
});
|
|
23
35
|
array.on('push', ({ items }) => {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
36
|
+
let anchor = slot.anchor();
|
|
37
|
+
|
|
38
|
+
elements = slot.nodes;
|
|
39
|
+
|
|
40
|
+
root(() => array.map(template, array.length - items.length));
|
|
41
|
+
|
|
42
|
+
anchor.after(fragment);
|
|
43
|
+
reset();
|
|
29
44
|
});
|
|
30
|
-
array.on('sort', refresh);
|
|
31
45
|
array.on('splice', ({ deleteCount: d, items: i, start: s }) => {
|
|
32
|
-
|
|
46
|
+
if (array.length === 0) {
|
|
47
|
+
slot.clear();
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
root(() => array.map(template, s, i.length))
|
|
52
|
+
|
|
53
|
+
slot.splice(s, d, fragment, ...elements);
|
|
54
|
+
reset();
|
|
33
55
|
});
|
|
34
56
|
array.on('unshift', ({ items }) => {
|
|
35
|
-
|
|
57
|
+
root(() => array.map(template, 0, items.length))
|
|
58
|
+
|
|
59
|
+
slot.unshift(fragment, ...elements);
|
|
60
|
+
reset();
|
|
36
61
|
});
|
|
37
62
|
|
|
38
|
-
|
|
63
|
+
root(() => array.map(template));
|
|
64
|
+
reset();
|
|
39
65
|
}
|
|
40
66
|
|
|
41
|
-
function hydrate<T>(
|
|
42
|
-
let
|
|
43
|
-
|
|
44
|
-
slots = template.slots;
|
|
67
|
+
function hydrate<T>(elements: Elements[] | null, fragment: Fragment, renderable: RenderableTemplate<T>) {
|
|
68
|
+
let { fragment: frag, slots } = cache.get(renderable.literals),
|
|
69
|
+
clone = cloneNode.call(frag, true);
|
|
45
70
|
|
|
46
71
|
if (slots !== null) {
|
|
47
72
|
let node,
|
|
48
|
-
|
|
73
|
+
nodePath,
|
|
74
|
+
parent,
|
|
75
|
+
parentPath,
|
|
49
76
|
values = renderable.values;
|
|
50
77
|
|
|
51
|
-
for (let i = slots.length
|
|
52
|
-
let { fn, path, slot } = slots[i]
|
|
78
|
+
for (let i = 0, n = slots.length; i < n; i++) {
|
|
79
|
+
let { fn, path, slot } = slots[i],
|
|
80
|
+
pp = path.parent,
|
|
81
|
+
pr = path.relative;
|
|
82
|
+
|
|
83
|
+
if (pp !== parentPath) {
|
|
84
|
+
if (pp === nodePath) {
|
|
85
|
+
parent = node;
|
|
86
|
+
parentPath = nodePath;
|
|
53
87
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
88
|
+
nodePath = undefined;
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
parent = clone;
|
|
92
|
+
parentPath = pp;
|
|
57
93
|
|
|
58
|
-
|
|
59
|
-
|
|
94
|
+
for (let o = 0, j = pp.length; o < j; o++) {
|
|
95
|
+
parent = pp[o].call(parent);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (pr !== nodePath) {
|
|
101
|
+
node = parent;
|
|
102
|
+
nodePath = path.absolute;
|
|
103
|
+
|
|
104
|
+
for (let o = 0, j = pr.length; o < j; o++) {
|
|
105
|
+
node = pr[o].call(node);
|
|
60
106
|
}
|
|
61
107
|
}
|
|
62
108
|
|
|
@@ -65,19 +111,15 @@ function hydrate<T>(renderable: Renderable<T>, template: Template): HydrateResul
|
|
|
65
111
|
}
|
|
66
112
|
}
|
|
67
113
|
|
|
68
|
-
|
|
69
|
-
elements.push(
|
|
114
|
+
if (elements) {
|
|
115
|
+
elements.push([...clone.childNodes] as Elements);
|
|
70
116
|
}
|
|
71
117
|
|
|
72
|
-
|
|
118
|
+
fragment.appendChild(clone)
|
|
73
119
|
}
|
|
74
120
|
|
|
75
121
|
|
|
76
122
|
export default {
|
|
77
|
-
reactive
|
|
78
|
-
|
|
79
|
-
},
|
|
80
|
-
static: <T>(renderable: RenderableTemplate<T>) => {
|
|
81
|
-
return hydrate(renderable, renderable.template || (renderable.template = cache.get(renderable)));
|
|
82
|
-
}
|
|
123
|
+
reactive,
|
|
124
|
+
static: hydrate
|
|
83
125
|
};
|
package/src/slot.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { effect
|
|
2
|
-
import { isArray,
|
|
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,
|
|
6
|
-
import { append, cloneNode,
|
|
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
|
|
|
@@ -15,41 +15,18 @@ const HYDRATING = 1;
|
|
|
15
15
|
|
|
16
16
|
|
|
17
17
|
let cleanup = queue<VoidFunction[]>(64),
|
|
18
|
-
scheduled = false
|
|
19
|
-
template = fragment('');
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
function after(anchor: Element, groups: HydrateResult[]) {
|
|
23
|
-
let n = groups.length;
|
|
24
|
-
|
|
25
|
-
if (n === 0) {
|
|
26
|
-
return [];
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
let fragment = cloneNode.call(template),
|
|
30
|
-
elements = new Array(n);
|
|
31
|
-
|
|
32
|
-
for (let i = 0; i < n; i++) {
|
|
33
|
-
let { elements: e, fragment: f } = groups[i];
|
|
34
|
-
|
|
35
|
-
append.call(fragment, f);
|
|
36
|
-
elements[i] = e;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
anchor.after(fragment);
|
|
18
|
+
scheduled = false;
|
|
40
19
|
|
|
41
|
-
return elements;
|
|
42
|
-
}
|
|
43
20
|
|
|
44
21
|
function remove(groups: Elements[]) {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
for (let j = 0, o = group.length; j < o; j++) {
|
|
49
|
-
let item = group[j];
|
|
22
|
+
let group,
|
|
23
|
+
item;
|
|
50
24
|
|
|
25
|
+
while (group = groups.pop()) {
|
|
26
|
+
while (item = group.pop()) {
|
|
51
27
|
if (CLEANUP_KEY in item) {
|
|
52
28
|
cleanup.add(item[CLEANUP_KEY] as VoidFunction[]);
|
|
29
|
+
item[CLEANUP_KEY] = null;
|
|
53
30
|
}
|
|
54
31
|
|
|
55
32
|
item.remove();
|
|
@@ -59,64 +36,55 @@ function remove(groups: Elements[]) {
|
|
|
59
36
|
if (!scheduled && cleanup.length) {
|
|
60
37
|
schedule();
|
|
61
38
|
}
|
|
62
|
-
|
|
63
|
-
return groups;
|
|
64
39
|
}
|
|
65
40
|
|
|
66
|
-
function render(elements: Elements[], fragment:
|
|
41
|
+
function render(anchor: Element, elements: Elements[] | null, fragment: Fragment, input: unknown, slot?: Slot) {
|
|
67
42
|
if (input === false || input == null || input === '') {
|
|
68
43
|
return;
|
|
69
44
|
}
|
|
70
45
|
|
|
71
46
|
let type = typeof input;
|
|
72
47
|
|
|
73
|
-
if (type === '
|
|
74
|
-
|
|
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);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
else if (type === 'string' || type === 'number') {
|
|
58
|
+
let element = text( type === 'string' ? input as string : String(input) );
|
|
75
59
|
|
|
76
60
|
if (slot) {
|
|
77
61
|
slot.text = element;
|
|
78
62
|
}
|
|
79
63
|
|
|
80
64
|
append.call(fragment, element);
|
|
81
|
-
|
|
65
|
+
|
|
66
|
+
if (elements) {
|
|
67
|
+
elements.push([element]);
|
|
68
|
+
}
|
|
82
69
|
}
|
|
83
70
|
else if (isArray(input)) {
|
|
84
71
|
for (let i = 0, n = input.length; i < n; i++) {
|
|
85
|
-
render(elements, fragment, input[i]);
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
else if (isObject(input) && RENDERABLE in input) {
|
|
89
|
-
if (input[RENDERABLE] === RENDERABLE_REACTIVE) {
|
|
90
|
-
let response = hydrate.reactive(input as RenderableReactive, slot!);
|
|
91
|
-
|
|
92
|
-
for (let i = 0, n = response.length; i < n; i++) {
|
|
93
|
-
let { elements: e, fragment: f } = response[i];
|
|
94
|
-
|
|
95
|
-
append.call(fragment, f);
|
|
96
|
-
elements.push(e);
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
else {
|
|
100
|
-
let { elements: e, fragment: f } = hydrate.static(input as RenderableTemplate<unknown>);
|
|
101
|
-
|
|
102
|
-
append.call(fragment, f);
|
|
103
|
-
elements.push(e);
|
|
72
|
+
render(anchor, elements, fragment, input[i], slot);
|
|
104
73
|
}
|
|
105
74
|
}
|
|
106
75
|
else if (isInstanceOf(input, NodeList)) {
|
|
107
|
-
|
|
108
|
-
i = 0;
|
|
76
|
+
append.call(fragment, ...input);
|
|
109
77
|
|
|
110
|
-
|
|
111
|
-
|
|
78
|
+
if (elements) {
|
|
79
|
+
elements.push([...input] as Elements);
|
|
112
80
|
}
|
|
113
|
-
|
|
114
|
-
append.call(fragment, ...e);
|
|
115
|
-
elements.push(e);
|
|
116
81
|
}
|
|
117
82
|
else if (isInstanceOf(input, Node)) {
|
|
118
83
|
append.call(fragment, input);
|
|
119
|
-
|
|
84
|
+
|
|
85
|
+
if (elements) {
|
|
86
|
+
elements.push([ input as Element ]);
|
|
87
|
+
}
|
|
120
88
|
}
|
|
121
89
|
}
|
|
122
90
|
|
|
@@ -168,7 +136,15 @@ class Slot {
|
|
|
168
136
|
}
|
|
169
137
|
|
|
170
138
|
set length(n: number) {
|
|
171
|
-
this.
|
|
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
|
+
}
|
|
172
148
|
}
|
|
173
149
|
|
|
174
150
|
|
|
@@ -179,37 +155,43 @@ class Slot {
|
|
|
179
155
|
}
|
|
180
156
|
|
|
181
157
|
clear() {
|
|
158
|
+
if (this.text) {
|
|
159
|
+
this.nodes.push([this.text]);
|
|
160
|
+
this.text = null;
|
|
161
|
+
}
|
|
162
|
+
|
|
182
163
|
remove(this.nodes);
|
|
183
|
-
|
|
184
|
-
this
|
|
164
|
+
|
|
165
|
+
return this;
|
|
185
166
|
}
|
|
186
167
|
|
|
187
168
|
pop() {
|
|
188
169
|
let group = this.nodes.pop();
|
|
189
170
|
|
|
190
|
-
if (
|
|
191
|
-
|
|
171
|
+
if (group) {
|
|
172
|
+
remove([group]);
|
|
192
173
|
}
|
|
193
174
|
|
|
194
|
-
return
|
|
175
|
+
return this;
|
|
195
176
|
}
|
|
196
177
|
|
|
197
|
-
push(...
|
|
198
|
-
|
|
178
|
+
push(fragment: Fragment, ...nodes: Elements[]) {
|
|
179
|
+
this.anchor().after(fragment);
|
|
180
|
+
this.nodes.push( ...nodes );
|
|
181
|
+
|
|
182
|
+
return this;
|
|
199
183
|
}
|
|
200
184
|
|
|
201
185
|
render(input: unknown, state = HYDRATING) {
|
|
202
|
-
|
|
186
|
+
let type = typeof input;
|
|
187
|
+
|
|
188
|
+
if (type === 'function') {
|
|
203
189
|
ondisconnect(
|
|
204
190
|
this.marker,
|
|
205
191
|
effect(() => {
|
|
206
|
-
let v = input();
|
|
192
|
+
let v = (input as Function)();
|
|
207
193
|
|
|
208
|
-
if (
|
|
209
|
-
root(() => this.render(v(), state));
|
|
210
|
-
state = CONNECTED;
|
|
211
|
-
}
|
|
212
|
-
else if (state === HYDRATING) {
|
|
194
|
+
if (state === HYDRATING) {
|
|
213
195
|
this.render(v, state);
|
|
214
196
|
state = CONNECTED;
|
|
215
197
|
}
|
|
@@ -224,24 +206,20 @@ class Slot {
|
|
|
224
206
|
return this;
|
|
225
207
|
}
|
|
226
208
|
|
|
227
|
-
|
|
228
|
-
let type = typeof input;
|
|
209
|
+
let text = this.text;
|
|
229
210
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
return this;
|
|
233
|
-
}
|
|
211
|
+
if (text && text.isConnected && (input == null || type !== 'object')) {
|
|
212
|
+
nodeValue.call(text, (type === 'number' || type === 'string') ? input : '');
|
|
234
213
|
}
|
|
214
|
+
else {
|
|
215
|
+
this.clear();
|
|
235
216
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
let fragment = cloneNode.call(template),
|
|
239
|
-
nodes: Elements[] = [];
|
|
217
|
+
let fragment = cloneNode.call(EMPTY_FRAGMENT);
|
|
240
218
|
|
|
241
|
-
|
|
219
|
+
render(this.marker, this.nodes, fragment, input, this);
|
|
242
220
|
|
|
243
|
-
|
|
244
|
-
|
|
221
|
+
this.marker.after(fragment);
|
|
222
|
+
}
|
|
245
223
|
|
|
246
224
|
return this;
|
|
247
225
|
}
|
|
@@ -249,29 +227,30 @@ class Slot {
|
|
|
249
227
|
shift() {
|
|
250
228
|
let group = this.nodes.shift();
|
|
251
229
|
|
|
252
|
-
if (
|
|
253
|
-
|
|
230
|
+
if (group) {
|
|
231
|
+
remove([group]);
|
|
254
232
|
}
|
|
255
233
|
|
|
256
|
-
return
|
|
234
|
+
return this;
|
|
257
235
|
}
|
|
258
236
|
|
|
259
|
-
splice(start: number, stop: number = this.nodes.length, ...
|
|
260
|
-
if (!
|
|
261
|
-
|
|
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) )
|
|
262
244
|
}
|
|
263
245
|
|
|
264
|
-
return
|
|
265
|
-
this.nodes.splice(
|
|
266
|
-
start,
|
|
267
|
-
stop,
|
|
268
|
-
...after(this.anchor(start), groups)
|
|
269
|
-
)
|
|
270
|
-
);
|
|
246
|
+
return this;
|
|
271
247
|
}
|
|
272
248
|
|
|
273
|
-
unshift(...
|
|
274
|
-
|
|
249
|
+
unshift(fragment: Fragment, ...nodes: Elements[]) {
|
|
250
|
+
this.marker.after(fragment);
|
|
251
|
+
this.nodes.unshift( ...nodes );
|
|
252
|
+
|
|
253
|
+
return this;
|
|
275
254
|
}
|
|
276
255
|
}
|
|
277
256
|
|
|
@@ -282,6 +261,17 @@ const ondisconnect = (element: Element, fn: VoidFunction) => {
|
|
|
282
261
|
|
|
283
262
|
|
|
284
263
|
export default (marker: Element, value: unknown) => {
|
|
285
|
-
|
|
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
|
+
};
|
|
286
276
|
};
|
|
287
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
|
|
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,8 +63,11 @@ type Template = {
|
|
|
63
63
|
literals: TemplateStringsArray;
|
|
64
64
|
slots: {
|
|
65
65
|
fn: typeof attributes.spread | typeof slot;
|
|
66
|
-
|
|
67
|
-
|
|
66
|
+
path: {
|
|
67
|
+
absolute: typeof firstChild[],
|
|
68
|
+
parent: typeof firstChild[],
|
|
69
|
+
relative: typeof firstChild[]
|
|
70
|
+
};
|
|
68
71
|
slot: number;
|
|
69
72
|
}[] | null;
|
|
70
73
|
};
|
|
@@ -73,7 +76,7 @@ type Template = {
|
|
|
73
76
|
export type {
|
|
74
77
|
Attributes,
|
|
75
78
|
Effect, Element, Elements,
|
|
76
|
-
|
|
79
|
+
Fragment,
|
|
77
80
|
Renderable, RenderableReactive, RenderableTemplate, RenderableValue,
|
|
78
81
|
Template
|
|
79
82
|
};
|