@esportsplus/template 0.13.0 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of @esportsplus/template might be problematic. Click here for more details.
- package/build/attributes.js +9 -3
- package/build/constants.d.ts +2 -1
- package/build/constants.js +2 -1
- package/build/event.d.ts +1 -1
- package/build/event.js +53 -28
- package/build/slot.d.ts +4 -3
- package/build/slot.js +37 -37
- package/build/types.d.ts +4 -2
- package/build/types.js +1 -1
- package/package.json +2 -2
- package/src/attributes.ts +24 -15
- package/src/constants.ts +3 -0
- package/src/event.ts +76 -37
- package/src/slot.ts +61 -61
- package/src/types.ts +2 -2
package/build/attributes.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { effect, root } from '@esportsplus/reactivity';
|
|
2
|
+
import { oncleanup } from './slot.js';
|
|
2
3
|
import { className, isArray, isObject, raf, removeAttribute, setAttribute } from './utilities.js';
|
|
3
4
|
import event from './event.js';
|
|
4
5
|
let attributes = {}, delimiters = {
|
|
@@ -24,19 +25,24 @@ function attribute(element, name, value) {
|
|
|
24
25
|
}
|
|
25
26
|
function reactive(element, id, name, value, wait = false) {
|
|
26
27
|
if (typeof value === 'function') {
|
|
27
|
-
effect(() => {
|
|
28
|
+
let instance = effect(() => {
|
|
28
29
|
let v = value(element);
|
|
29
30
|
if (typeof v === 'function') {
|
|
30
|
-
root(() => {
|
|
31
|
+
root((root) => {
|
|
32
|
+
instance.on('cleanup', () => root.dispose());
|
|
31
33
|
reactive(element, id, name, v(element), wait);
|
|
32
34
|
});
|
|
33
35
|
}
|
|
36
|
+
else if (isArray(v) || isObject(v)) {
|
|
37
|
+
spread(element, v);
|
|
38
|
+
}
|
|
34
39
|
else {
|
|
35
40
|
raf.add(() => {
|
|
36
41
|
update(element, id, name, v, wait);
|
|
37
42
|
});
|
|
38
43
|
}
|
|
39
44
|
});
|
|
45
|
+
oncleanup(element, () => instance.dispose());
|
|
40
46
|
wait = false;
|
|
41
47
|
}
|
|
42
48
|
else {
|
|
@@ -144,7 +150,7 @@ const spread = function (element, attributes) {
|
|
|
144
150
|
for (let i = 0, n = attributes.length; i < n; i++) {
|
|
145
151
|
let attrs = attributes[i];
|
|
146
152
|
if (!isObject(attrs)) {
|
|
147
|
-
|
|
153
|
+
throw new Error('@esportsplus/template: attributes must be of type `Attributes` or `Attributes[]`; Received ' + JSON.stringify(attributes));
|
|
148
154
|
}
|
|
149
155
|
for (let name in attrs) {
|
|
150
156
|
set(element, attrs[name], name);
|
package/build/constants.d.ts
CHANGED
|
@@ -9,7 +9,8 @@ declare const RENDERABLE: unique symbol;
|
|
|
9
9
|
declare const RENDERABLE_REACTIVE: unique symbol;
|
|
10
10
|
declare const RENDERABLE_TEMPLATE: unique symbol;
|
|
11
11
|
declare const SLOT: unique symbol;
|
|
12
|
+
declare const SLOT_CLEANUP: unique symbol;
|
|
12
13
|
declare const SLOT_HTML = "<!--$-->";
|
|
13
14
|
declare const SLOT_MARKER = "{{$}}";
|
|
14
15
|
declare const SLOT_MARKER_LENGTH: number;
|
|
15
|
-
export { NODE_CLOSING, NODE_ELEMENT, NODE_SLOT, NODE_VOID, NODE_WHITELIST, REGEX_EMPTY_TEXT_NODES, REGEX_SLOT_NODES, RENDERABLE, RENDERABLE_REACTIVE, RENDERABLE_TEMPLATE, SLOT, SLOT_HTML, SLOT_MARKER, SLOT_MARKER_LENGTH };
|
|
16
|
+
export { NODE_CLOSING, NODE_ELEMENT, NODE_SLOT, NODE_VOID, NODE_WHITELIST, REGEX_EMPTY_TEXT_NODES, REGEX_SLOT_NODES, RENDERABLE, RENDERABLE_REACTIVE, RENDERABLE_TEMPLATE, SLOT, SLOT_CLEANUP, SLOT_HTML, SLOT_MARKER, SLOT_MARKER_LENGTH };
|
package/build/constants.js
CHANGED
|
@@ -29,7 +29,8 @@ const RENDERABLE = Symbol();
|
|
|
29
29
|
const RENDERABLE_REACTIVE = Symbol();
|
|
30
30
|
const RENDERABLE_TEMPLATE = Symbol();
|
|
31
31
|
const SLOT = Symbol();
|
|
32
|
+
const SLOT_CLEANUP = Symbol();
|
|
32
33
|
const SLOT_HTML = '<!--$-->';
|
|
33
34
|
const SLOT_MARKER = '{{$}}';
|
|
34
35
|
const SLOT_MARKER_LENGTH = SLOT_MARKER.length;
|
|
35
|
-
export { NODE_CLOSING, NODE_ELEMENT, NODE_SLOT, NODE_VOID, NODE_WHITELIST, REGEX_EMPTY_TEXT_NODES, REGEX_SLOT_NODES, RENDERABLE, RENDERABLE_REACTIVE, RENDERABLE_TEMPLATE, SLOT, SLOT_HTML, SLOT_MARKER, SLOT_MARKER_LENGTH };
|
|
36
|
+
export { NODE_CLOSING, NODE_ELEMENT, NODE_SLOT, NODE_VOID, NODE_WHITELIST, REGEX_EMPTY_TEXT_NODES, REGEX_SLOT_NODES, RENDERABLE, RENDERABLE_REACTIVE, RENDERABLE_TEMPLATE, SLOT, SLOT_CLEANUP, SLOT_HTML, SLOT_MARKER, SLOT_MARKER_LENGTH };
|
package/build/event.d.ts
CHANGED
package/build/event.js
CHANGED
|
@@ -1,33 +1,15 @@
|
|
|
1
1
|
import { root } from '@esportsplus/reactivity';
|
|
2
|
+
import { oncleanup } from './slot.js';
|
|
2
3
|
import { addEventListener, defineProperty, parentElement } from './utilities.js';
|
|
3
|
-
let capture = new Set(['
|
|
4
|
-
'
|
|
5
|
-
'
|
|
6
|
-
'
|
|
7
|
-
'
|
|
4
|
+
let capture = new Set(['onblur', 'onfocus', 'onscroll']), controllers = new Map(), keys = {}, passive = new Set([
|
|
5
|
+
'onmousedown', 'onmouseenter', 'onmouseleave', 'onmousemove', 'onmouseout', 'onmouseover', 'onmouseup', 'onmousewheel',
|
|
6
|
+
'onscroll',
|
|
7
|
+
'ontouchcancel', 'ontouchend', 'ontouchleave', 'ontouchmove', 'ontouchstart',
|
|
8
|
+
'onwheel'
|
|
8
9
|
]);
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
let node = e.target;
|
|
13
|
-
defineProperty(e, 'currentTarget', {
|
|
14
|
-
configurable: true,
|
|
15
|
-
get() {
|
|
16
|
-
return node || window.document;
|
|
17
|
-
}
|
|
18
|
-
});
|
|
19
|
-
while (node) {
|
|
20
|
-
if (key in node) {
|
|
21
|
-
return node[key].call(node, e);
|
|
22
|
-
}
|
|
23
|
-
node = parentElement.call(node);
|
|
24
|
-
}
|
|
25
|
-
}, {
|
|
26
|
-
capture: capture.has(type),
|
|
27
|
-
passive: passive.has(type)
|
|
28
|
-
});
|
|
29
|
-
return key;
|
|
30
|
-
}
|
|
10
|
+
['onmousemove', 'onmousewheel', 'onscroll', 'ontouchend', 'ontouchmove', 'ontouchstart', 'onwheel'].map(event => {
|
|
11
|
+
controllers.set(event, null);
|
|
12
|
+
});
|
|
31
13
|
export default (element, event, listener) => {
|
|
32
14
|
if (event === 'onmount') {
|
|
33
15
|
let interval = setInterval(() => {
|
|
@@ -45,5 +27,48 @@ export default (element, event, listener) => {
|
|
|
45
27
|
else if (event === 'onrender') {
|
|
46
28
|
return root(() => listener(element));
|
|
47
29
|
}
|
|
48
|
-
|
|
30
|
+
let controller = controllers.get(event), signal;
|
|
31
|
+
if (controller === null) {
|
|
32
|
+
let { abort, signal } = new AbortController();
|
|
33
|
+
controllers.set(event, controller = {
|
|
34
|
+
abort,
|
|
35
|
+
signal,
|
|
36
|
+
listeners: 0,
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
if (controller) {
|
|
40
|
+
controller.listeners++;
|
|
41
|
+
oncleanup(element, () => {
|
|
42
|
+
if (--controller.listeners) {
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
controller.abort();
|
|
46
|
+
controllers.set(event, null);
|
|
47
|
+
});
|
|
48
|
+
signal = controller.signal;
|
|
49
|
+
}
|
|
50
|
+
let key = keys[event];
|
|
51
|
+
if (!key) {
|
|
52
|
+
key = keys[event] = Symbol();
|
|
53
|
+
addEventListener.call(window.document, event.slice(2), (e) => {
|
|
54
|
+
let node = e.target;
|
|
55
|
+
defineProperty(e, 'currentTarget', {
|
|
56
|
+
configurable: true,
|
|
57
|
+
get() {
|
|
58
|
+
return node || window.document;
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
while (node) {
|
|
62
|
+
if (key in node) {
|
|
63
|
+
return node[key].call(node, e);
|
|
64
|
+
}
|
|
65
|
+
node = parentElement.call(node);
|
|
66
|
+
}
|
|
67
|
+
}, {
|
|
68
|
+
capture: capture.has(event),
|
|
69
|
+
passive: passive.has(event),
|
|
70
|
+
signal
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
element[key] = listener;
|
|
49
74
|
};
|
package/build/slot.d.ts
CHANGED
|
@@ -10,13 +10,14 @@ declare class Slot {
|
|
|
10
10
|
set length(n: number);
|
|
11
11
|
anchor(index?: number): Element;
|
|
12
12
|
clear(): void;
|
|
13
|
-
pop(): Elements | undefined;
|
|
13
|
+
pop(): Elements[] | undefined;
|
|
14
14
|
push(...groups: Elements[]): number;
|
|
15
15
|
render(input: unknown): this;
|
|
16
|
-
shift(): Elements | undefined;
|
|
16
|
+
shift(): Elements[] | undefined;
|
|
17
17
|
splice(start: number, stop?: number, ...groups: Elements[]): Elements[];
|
|
18
18
|
unshift(...groups: Elements[]): number;
|
|
19
19
|
}
|
|
20
|
+
declare const oncleanup: (element: Element, fn: VoidFunction) => void;
|
|
20
21
|
declare const _default: (marker: Element, value: unknown) => Slot;
|
|
21
22
|
export default _default;
|
|
22
|
-
export { Slot };
|
|
23
|
+
export { oncleanup, Slot };
|
package/build/slot.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { effect, root, DIRTY } from '@esportsplus/reactivity';
|
|
2
|
-
import { RENDERABLE, RENDERABLE_REACTIVE, SLOT } from './constants.js';
|
|
2
|
+
import { RENDERABLE, RENDERABLE_REACTIVE, SLOT, SLOT_CLEANUP } from './constants.js';
|
|
3
3
|
import { hydrate } from './html/index.js';
|
|
4
4
|
import { firstChild, isArray, isObject, nextSibling, nodeValue, raf, text } from './utilities.js';
|
|
5
|
-
let cleanup = [],
|
|
6
|
-
function
|
|
5
|
+
let cleanup = [], scheduled = false;
|
|
6
|
+
function after(anchor, groups) {
|
|
7
7
|
for (let i = 0, n = groups.length; i < n; i++) {
|
|
8
8
|
let group = groups[i];
|
|
9
9
|
if (group.length) {
|
|
@@ -13,29 +13,13 @@ function afterGroups(anchor, groups) {
|
|
|
13
13
|
}
|
|
14
14
|
return groups;
|
|
15
15
|
}
|
|
16
|
-
function
|
|
17
|
-
if (group === undefined) {
|
|
18
|
-
return group;
|
|
19
|
-
}
|
|
20
|
-
for (let i = 0, n = group.length; i < n; i++) {
|
|
21
|
-
let item = group[i];
|
|
22
|
-
if (key in item) {
|
|
23
|
-
cleanup.push(item[key]);
|
|
24
|
-
}
|
|
25
|
-
item.remove();
|
|
26
|
-
}
|
|
27
|
-
if (!scheduled && cleanup.length) {
|
|
28
|
-
schedule();
|
|
29
|
-
}
|
|
30
|
-
return group;
|
|
31
|
-
}
|
|
32
|
-
function removeGroups(groups) {
|
|
16
|
+
function remove(...groups) {
|
|
33
17
|
for (let i = 0, n = groups.length; i < n; i++) {
|
|
34
18
|
let group = groups[i];
|
|
35
19
|
for (let j = 0, o = group.length; j < o; j++) {
|
|
36
20
|
let item = group[j];
|
|
37
|
-
if (
|
|
38
|
-
cleanup.push(item[
|
|
21
|
+
if (item[SLOT_CLEANUP]) {
|
|
22
|
+
cleanup.push(...item[SLOT_CLEANUP]);
|
|
39
23
|
}
|
|
40
24
|
item.remove();
|
|
41
25
|
}
|
|
@@ -55,12 +39,12 @@ function render(anchor, input, slot) {
|
|
|
55
39
|
for (let i = 0, n = input.length; i < n; i++) {
|
|
56
40
|
groups.push(render(null, input[i]));
|
|
57
41
|
}
|
|
58
|
-
return anchor ?
|
|
42
|
+
return anchor ? after(anchor, groups) : groups;
|
|
59
43
|
}
|
|
60
44
|
let nodes = [];
|
|
61
45
|
if (RENDERABLE in input) {
|
|
62
46
|
if (input[RENDERABLE] === RENDERABLE_REACTIVE) {
|
|
63
|
-
return
|
|
47
|
+
return after(anchor, hydrate.reactive(input, slot));
|
|
64
48
|
}
|
|
65
49
|
else {
|
|
66
50
|
nodes = hydrate.static(input);
|
|
@@ -98,13 +82,16 @@ function schedule() {
|
|
|
98
82
|
scheduled = true;
|
|
99
83
|
raf.add(() => {
|
|
100
84
|
try {
|
|
101
|
-
let
|
|
102
|
-
while (
|
|
103
|
-
|
|
85
|
+
let fn;
|
|
86
|
+
while (fn = cleanup.pop()) {
|
|
87
|
+
fn();
|
|
104
88
|
}
|
|
105
89
|
}
|
|
106
90
|
catch (e) { }
|
|
107
91
|
scheduled = false;
|
|
92
|
+
if (cleanup.length) {
|
|
93
|
+
schedule();
|
|
94
|
+
}
|
|
108
95
|
});
|
|
109
96
|
}
|
|
110
97
|
class Slot {
|
|
@@ -113,7 +100,7 @@ class Slot {
|
|
|
113
100
|
nodes;
|
|
114
101
|
text = null;
|
|
115
102
|
constructor(marker) {
|
|
116
|
-
marker
|
|
103
|
+
oncleanup(marker, () => this.clear());
|
|
117
104
|
this.marker = marker;
|
|
118
105
|
this.nodes = [];
|
|
119
106
|
}
|
|
@@ -131,14 +118,18 @@ class Slot {
|
|
|
131
118
|
return node || this.marker;
|
|
132
119
|
}
|
|
133
120
|
clear() {
|
|
134
|
-
|
|
121
|
+
remove(...this.nodes);
|
|
135
122
|
this.text = null;
|
|
136
123
|
}
|
|
137
124
|
pop() {
|
|
138
|
-
|
|
125
|
+
let group = this.nodes.pop();
|
|
126
|
+
if (!group) {
|
|
127
|
+
return undefined;
|
|
128
|
+
}
|
|
129
|
+
return remove(group);
|
|
139
130
|
}
|
|
140
131
|
push(...groups) {
|
|
141
|
-
|
|
132
|
+
after(this.anchor(), groups);
|
|
142
133
|
for (let i = 0, n = groups.length; i < n; i++) {
|
|
143
134
|
this.nodes.push(groups[i]);
|
|
144
135
|
}
|
|
@@ -146,10 +137,11 @@ class Slot {
|
|
|
146
137
|
}
|
|
147
138
|
render(input) {
|
|
148
139
|
if (typeof input === 'function') {
|
|
149
|
-
effect((self) => {
|
|
140
|
+
let instance = effect((self) => {
|
|
150
141
|
let v = input();
|
|
151
142
|
if (typeof v === 'function') {
|
|
152
|
-
root(() => {
|
|
143
|
+
root((root) => {
|
|
144
|
+
instance.on('cleanup', () => root.dispose());
|
|
153
145
|
this.render(v());
|
|
154
146
|
});
|
|
155
147
|
}
|
|
@@ -162,6 +154,7 @@ class Slot {
|
|
|
162
154
|
});
|
|
163
155
|
}
|
|
164
156
|
});
|
|
157
|
+
oncleanup(this.marker, () => instance.dispose());
|
|
165
158
|
return this;
|
|
166
159
|
}
|
|
167
160
|
if (this.text) {
|
|
@@ -182,16 +175,23 @@ class Slot {
|
|
|
182
175
|
return this;
|
|
183
176
|
}
|
|
184
177
|
shift() {
|
|
185
|
-
|
|
178
|
+
let group = this.nodes.shift();
|
|
179
|
+
if (!group) {
|
|
180
|
+
return undefined;
|
|
181
|
+
}
|
|
182
|
+
return remove(group);
|
|
186
183
|
}
|
|
187
184
|
splice(start, stop = this.nodes.length, ...groups) {
|
|
188
|
-
return
|
|
185
|
+
return remove(...this.nodes.splice(start, stop, ...after(this.anchor(start), groups)));
|
|
189
186
|
}
|
|
190
187
|
unshift(...groups) {
|
|
191
|
-
return this.nodes.unshift(...
|
|
188
|
+
return this.nodes.unshift(...after(this.marker, groups));
|
|
192
189
|
}
|
|
193
190
|
}
|
|
191
|
+
const oncleanup = (element, fn) => {
|
|
192
|
+
(element[SLOT_CLEANUP] ??= []).push(fn);
|
|
193
|
+
};
|
|
194
194
|
export default (marker, value) => {
|
|
195
195
|
return new Slot(marker).render(value);
|
|
196
196
|
};
|
|
197
|
-
export { Slot };
|
|
197
|
+
export { oncleanup, Slot };
|
package/build/types.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ReactiveArray } from '@esportsplus/reactivity';
|
|
2
|
-
import { RENDERABLE, RENDERABLE_REACTIVE, RENDERABLE_TEMPLATE } from './constants.js';
|
|
2
|
+
import { RENDERABLE, RENDERABLE_REACTIVE, RENDERABLE_TEMPLATE, SLOT_CLEANUP } from './constants.js';
|
|
3
3
|
import { firstChild } from './utilities.js';
|
|
4
4
|
import attributes from './attributes.js';
|
|
5
5
|
import event from './event.js';
|
|
@@ -17,7 +17,9 @@ type Attributes = {
|
|
|
17
17
|
} & Record<PropertyKey, unknown>;
|
|
18
18
|
type Effect<T> = () => EffectResponse<T>;
|
|
19
19
|
type EffectResponse<T> = T extends [] ? EffectResponse<T[number]>[] : Primitive | Renderable<T>;
|
|
20
|
-
type Element = HTMLElement & Attributes &
|
|
20
|
+
type Element = HTMLElement & Attributes & {
|
|
21
|
+
[SLOT_CLEANUP]?: VoidFunction[];
|
|
22
|
+
} & Record<PropertyKey, unknown>;
|
|
21
23
|
type Elements = Element[];
|
|
22
24
|
type Primitive = bigint | boolean | null | number | string | undefined;
|
|
23
25
|
type Renderable<T = unknown> = RenderableReactive<T> | RenderableTemplate<T>;
|
package/build/types.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import { RENDERABLE } from './constants.js';
|
|
1
|
+
import { RENDERABLE, SLOT_CLEANUP } from './constants.js';
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"author": "ICJR",
|
|
3
3
|
"dependencies": {
|
|
4
|
-
"@esportsplus/reactivity": "^0.4.
|
|
4
|
+
"@esportsplus/reactivity": "^0.4.7",
|
|
5
5
|
"@esportsplus/tasks": "^0.1.9",
|
|
6
6
|
"@esportsplus/utilities": "^0.19.0"
|
|
7
7
|
},
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"private": false,
|
|
14
14
|
"type": "module",
|
|
15
15
|
"types": "./build/index.d.ts",
|
|
16
|
-
"version": "0.
|
|
16
|
+
"version": "0.15.0",
|
|
17
17
|
"scripts": {
|
|
18
18
|
"build": "tsc && tsc-alias",
|
|
19
19
|
"-": "-"
|
package/src/attributes.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { effect, root } from '@esportsplus/reactivity';
|
|
2
|
+
import { oncleanup } from './slot';
|
|
2
3
|
import { Attributes, Element } from './types';
|
|
3
4
|
import { className, isArray, isObject, raf, removeAttribute, setAttribute } from './utilities';
|
|
4
5
|
import event from './event';
|
|
@@ -33,20 +34,27 @@ function attribute(element: Element, name: string, value: unknown) {
|
|
|
33
34
|
|
|
34
35
|
function reactive(element: Element, id: string, name: string, value: unknown, wait = false) {
|
|
35
36
|
if (typeof value === 'function') {
|
|
36
|
-
effect(() => {
|
|
37
|
-
|
|
37
|
+
let instance = effect(() => {
|
|
38
|
+
let v = (value as Function)(element);
|
|
39
|
+
|
|
40
|
+
if (typeof v === 'function') {
|
|
41
|
+
root((root) => {
|
|
42
|
+
instance.on('cleanup', () => root.dispose());
|
|
43
|
+
reactive(element, id, name, v(element), wait);
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
else if (isArray(v) || isObject(v)) {
|
|
47
|
+
spread(element, v as Attributes | Attributes[]);
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
raf.add(() => {
|
|
51
|
+
update(element, id, name, v, wait);
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
oncleanup(element, () => instance.dispose());
|
|
38
57
|
|
|
39
|
-
if (typeof v === 'function') {
|
|
40
|
-
root(() => {
|
|
41
|
-
reactive(element, id, name, v(element), wait);
|
|
42
|
-
});
|
|
43
|
-
}
|
|
44
|
-
else {
|
|
45
|
-
raf.add(() => {
|
|
46
|
-
update(element, id, name, v, wait);
|
|
47
|
-
});
|
|
48
|
-
}
|
|
49
|
-
});
|
|
50
58
|
wait = false;
|
|
51
59
|
}
|
|
52
60
|
else {
|
|
@@ -67,7 +75,7 @@ function set(element: Element, value: unknown, name: string) {
|
|
|
67
75
|
}
|
|
68
76
|
else if (typeof value === 'function') {
|
|
69
77
|
if (name.startsWith('on')) {
|
|
70
|
-
event(element, name
|
|
78
|
+
event(element, name as `on${string}`, value);
|
|
71
79
|
}
|
|
72
80
|
else {
|
|
73
81
|
reactive(element, ('e' + store(element)[key]++), name, value, true);
|
|
@@ -184,7 +192,7 @@ const spread = function (element: Element, attributes: Attributes | Attributes[]
|
|
|
184
192
|
let attrs = attributes[i];
|
|
185
193
|
|
|
186
194
|
if (!isObject(attrs)) {
|
|
187
|
-
|
|
195
|
+
throw new Error('@esportsplus/template: attributes must be of type `Attributes` or `Attributes[]`; Received ' + JSON.stringify(attributes));
|
|
188
196
|
}
|
|
189
197
|
|
|
190
198
|
for (let name in attrs) {
|
|
@@ -197,5 +205,6 @@ const spread = function (element: Element, attributes: Attributes | Attributes[]
|
|
|
197
205
|
}
|
|
198
206
|
};
|
|
199
207
|
|
|
208
|
+
|
|
200
209
|
export default { apply, spread };
|
|
201
210
|
export { apply, spread }
|
package/src/constants.ts
CHANGED
|
@@ -45,6 +45,8 @@ const RENDERABLE_TEMPLATE = Symbol();
|
|
|
45
45
|
|
|
46
46
|
const SLOT = Symbol();
|
|
47
47
|
|
|
48
|
+
const SLOT_CLEANUP = Symbol();
|
|
49
|
+
|
|
48
50
|
const SLOT_HTML = '<!--$-->';
|
|
49
51
|
|
|
50
52
|
const SLOT_MARKER = '{{$}}';
|
|
@@ -64,6 +66,7 @@ export {
|
|
|
64
66
|
RENDERABLE_REACTIVE,
|
|
65
67
|
RENDERABLE_TEMPLATE,
|
|
66
68
|
SLOT,
|
|
69
|
+
SLOT_CLEANUP,
|
|
67
70
|
SLOT_HTML,
|
|
68
71
|
SLOT_MARKER,
|
|
69
72
|
SLOT_MARKER_LENGTH
|
package/src/event.ts
CHANGED
|
@@ -1,51 +1,31 @@
|
|
|
1
1
|
import { root } from '@esportsplus/reactivity';
|
|
2
|
+
import { oncleanup } from './slot';
|
|
2
3
|
import { Element } from './types';
|
|
3
4
|
import { addEventListener, defineProperty, parentElement } from './utilities';
|
|
4
5
|
|
|
5
6
|
|
|
6
|
-
let capture = new Set(['
|
|
7
|
+
let capture = new Set<`on${string}`>(['onblur', 'onfocus', 'onscroll']),
|
|
8
|
+
controllers = new Map<
|
|
9
|
+
`on${string}`,
|
|
10
|
+
(AbortController & { listeners: number }) | null
|
|
11
|
+
>(),
|
|
7
12
|
keys: Record<string, symbol> = {},
|
|
8
|
-
passive = new Set([
|
|
9
|
-
'
|
|
10
|
-
'
|
|
11
|
-
'
|
|
12
|
-
'
|
|
13
|
+
passive = new Set<`on${string}`>([
|
|
14
|
+
'onmousedown', 'onmouseenter', 'onmouseleave', 'onmousemove', 'onmouseout', 'onmouseover', 'onmouseup', 'onmousewheel',
|
|
15
|
+
'onscroll',
|
|
16
|
+
'ontouchcancel', 'ontouchend', 'ontouchleave', 'ontouchmove', 'ontouchstart',
|
|
17
|
+
'onwheel'
|
|
13
18
|
]);
|
|
14
19
|
|
|
15
20
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
21
|
+
(['onmousemove', 'onmousewheel', 'onscroll', 'ontouchend', 'ontouchmove', 'ontouchstart', 'onwheel'] as `on${string}`[]).map(event => {
|
|
22
|
+
controllers.set(event, null);
|
|
23
|
+
});
|
|
19
24
|
|
|
20
|
-
addEventListener.call(window.document, type, (e) => {
|
|
21
|
-
let node = e.target as Element | null;
|
|
22
25
|
|
|
23
|
-
|
|
24
|
-
configurable: true,
|
|
25
|
-
get() {
|
|
26
|
-
return node || window.document;
|
|
27
|
-
}
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
while (node) {
|
|
31
|
-
if (key in node) {
|
|
32
|
-
return (node[key] as Function).call(node, e);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
node = parentElement.call(node);
|
|
36
|
-
}
|
|
37
|
-
}, {
|
|
38
|
-
capture: capture.has(type),
|
|
39
|
-
passive: passive.has(type)
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
return key;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
export default (element: Element, event: string, listener: Function): void => {
|
|
26
|
+
export default (element: Element, event: `on${string}`, listener: Function) => {
|
|
47
27
|
if (event === 'onmount') {
|
|
48
|
-
let interval
|
|
28
|
+
let interval = setInterval(() => {
|
|
49
29
|
retry--;
|
|
50
30
|
|
|
51
31
|
if (element.isConnected) {
|
|
@@ -66,5 +46,64 @@ export default (element: Element, event: string, listener: Function): void => {
|
|
|
66
46
|
return root(() => listener(element));
|
|
67
47
|
}
|
|
68
48
|
|
|
69
|
-
|
|
49
|
+
let controller = controllers.get(event),
|
|
50
|
+
signal: AbortController['signal'] | undefined;
|
|
51
|
+
|
|
52
|
+
if (controller === null) {
|
|
53
|
+
let { abort, signal } = new AbortController();
|
|
54
|
+
|
|
55
|
+
controllers.set(
|
|
56
|
+
event,
|
|
57
|
+
controller = {
|
|
58
|
+
abort,
|
|
59
|
+
signal,
|
|
60
|
+
listeners: 0,
|
|
61
|
+
}
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (controller) {
|
|
66
|
+
controller.listeners++;
|
|
67
|
+
|
|
68
|
+
oncleanup(element, () => {
|
|
69
|
+
if (--controller.listeners) {
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
controller.abort();
|
|
74
|
+
controllers.set(event, null);
|
|
75
|
+
});
|
|
76
|
+
signal = controller.signal;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
let key = keys[event];
|
|
80
|
+
|
|
81
|
+
if (!key) {
|
|
82
|
+
key = keys[event] = Symbol();
|
|
83
|
+
|
|
84
|
+
addEventListener.call(window.document, event.slice(2), (e) => {
|
|
85
|
+
let node = e.target as Element | null;
|
|
86
|
+
|
|
87
|
+
defineProperty(e, 'currentTarget', {
|
|
88
|
+
configurable: true,
|
|
89
|
+
get() {
|
|
90
|
+
return node || window.document;
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
while (node) {
|
|
95
|
+
if (key in node) {
|
|
96
|
+
return (node[key] as Function).call(node, e);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
node = parentElement.call(node);
|
|
100
|
+
}
|
|
101
|
+
}, {
|
|
102
|
+
capture: capture.has(event),
|
|
103
|
+
passive: passive.has(event),
|
|
104
|
+
signal
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
element[key] = listener;
|
|
70
109
|
};
|
package/src/slot.ts
CHANGED
|
@@ -1,17 +1,15 @@
|
|
|
1
1
|
import { effect, root, DIRTY } from '@esportsplus/reactivity';
|
|
2
|
-
import { RENDERABLE, RENDERABLE_REACTIVE, SLOT } from './constants';
|
|
2
|
+
import { RENDERABLE, RENDERABLE_REACTIVE, SLOT, SLOT_CLEANUP } from './constants';
|
|
3
3
|
import { hydrate } from './html';
|
|
4
4
|
import { Element, Elements, RenderableReactive, RenderableTemplate } from './types';
|
|
5
5
|
import { firstChild, isArray, isObject, nextSibling, nodeValue, raf, text } from './utilities'
|
|
6
6
|
|
|
7
7
|
|
|
8
|
-
let cleanup:
|
|
9
|
-
// Using a private symbol since 'SLOT' is used as a different flag in 'render.ts'
|
|
10
|
-
key = Symbol(),
|
|
8
|
+
let cleanup: VoidFunction[] = [],
|
|
11
9
|
scheduled = false;
|
|
12
10
|
|
|
13
11
|
|
|
14
|
-
function
|
|
12
|
+
function after(anchor: Element, groups: Elements[]) {
|
|
15
13
|
for (let i = 0, n = groups.length; i < n; i++) {
|
|
16
14
|
let group = groups[i];
|
|
17
15
|
|
|
@@ -24,37 +22,15 @@ function afterGroups(anchor: Element, groups: Elements[]) {
|
|
|
24
22
|
return groups;
|
|
25
23
|
}
|
|
26
24
|
|
|
27
|
-
function
|
|
28
|
-
if (group === undefined) {
|
|
29
|
-
return group;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
for (let i = 0, n = group.length; i < n; i++) {
|
|
33
|
-
let item = group[i];
|
|
34
|
-
|
|
35
|
-
if (key in item) {
|
|
36
|
-
cleanup.push(item[key] as Slot);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
item.remove();
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
if (!scheduled && cleanup.length) {
|
|
43
|
-
schedule();
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
return group;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
function removeGroups(groups: Elements[]) {
|
|
25
|
+
function remove(...groups: Elements[]) {
|
|
50
26
|
for (let i = 0, n = groups.length; i < n; i++) {
|
|
51
27
|
let group = groups[i];
|
|
52
28
|
|
|
53
29
|
for (let j = 0, o = group.length; j < o; j++) {
|
|
54
30
|
let item = group[j];
|
|
55
31
|
|
|
56
|
-
if (
|
|
57
|
-
cleanup.push(item[
|
|
32
|
+
if (item[SLOT_CLEANUP]) {
|
|
33
|
+
cleanup.push(...item[SLOT_CLEANUP]);
|
|
58
34
|
}
|
|
59
35
|
|
|
60
36
|
item.remove();
|
|
@@ -80,14 +56,14 @@ function render(anchor: Element | null, input: unknown, slot?: Slot): Elements |
|
|
|
80
56
|
groups.push( render(null, input[i]) as Elements );
|
|
81
57
|
}
|
|
82
58
|
|
|
83
|
-
return anchor ?
|
|
59
|
+
return anchor ? after(anchor, groups) : groups;
|
|
84
60
|
}
|
|
85
61
|
|
|
86
62
|
let nodes: Elements = [];
|
|
87
63
|
|
|
88
64
|
if (RENDERABLE in input) {
|
|
89
65
|
if (input[RENDERABLE] === RENDERABLE_REACTIVE) {
|
|
90
|
-
return
|
|
66
|
+
return after(anchor!, hydrate.reactive(input as RenderableReactive, slot!));
|
|
91
67
|
}
|
|
92
68
|
else {
|
|
93
69
|
nodes = hydrate.static(input as RenderableTemplate<unknown>);
|
|
@@ -135,15 +111,19 @@ function schedule() {
|
|
|
135
111
|
|
|
136
112
|
raf.add(() => {
|
|
137
113
|
try {
|
|
138
|
-
let
|
|
114
|
+
let fn;
|
|
139
115
|
|
|
140
|
-
while (
|
|
141
|
-
|
|
116
|
+
while (fn = cleanup.pop()) {
|
|
117
|
+
fn();
|
|
142
118
|
}
|
|
143
119
|
}
|
|
144
|
-
catch(e) {}
|
|
120
|
+
catch(e) { }
|
|
145
121
|
|
|
146
122
|
scheduled = false;
|
|
123
|
+
|
|
124
|
+
if (cleanup.length) {
|
|
125
|
+
schedule();
|
|
126
|
+
}
|
|
147
127
|
});
|
|
148
128
|
}
|
|
149
129
|
|
|
@@ -157,7 +137,7 @@ class Slot {
|
|
|
157
137
|
|
|
158
138
|
|
|
159
139
|
constructor(marker: Element) {
|
|
160
|
-
marker
|
|
140
|
+
oncleanup(marker, () => this.clear());
|
|
161
141
|
|
|
162
142
|
this.marker = marker;
|
|
163
143
|
this.nodes = [];
|
|
@@ -185,16 +165,22 @@ class Slot {
|
|
|
185
165
|
}
|
|
186
166
|
|
|
187
167
|
clear() {
|
|
188
|
-
|
|
168
|
+
remove(...this.nodes);
|
|
189
169
|
this.text = null;
|
|
190
170
|
}
|
|
191
171
|
|
|
192
172
|
pop() {
|
|
193
|
-
|
|
173
|
+
let group = this.nodes.pop();
|
|
174
|
+
|
|
175
|
+
if (!group) {
|
|
176
|
+
return undefined;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return remove(group);
|
|
194
180
|
}
|
|
195
181
|
|
|
196
182
|
push(...groups: Elements[]) {
|
|
197
|
-
|
|
183
|
+
after(this.anchor(), groups);
|
|
198
184
|
|
|
199
185
|
for (let i = 0, n = groups.length; i < n; i++) {
|
|
200
186
|
this.nodes.push(groups[i]);
|
|
@@ -205,23 +191,26 @@ class Slot {
|
|
|
205
191
|
|
|
206
192
|
render(input: unknown) {
|
|
207
193
|
if (typeof input === 'function') {
|
|
208
|
-
effect((self) => {
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
}
|
|
219
|
-
else {
|
|
220
|
-
raf.add(() => {
|
|
194
|
+
let instance = effect((self) => {
|
|
195
|
+
let v = (input as Function)();
|
|
196
|
+
|
|
197
|
+
if (typeof v === 'function') {
|
|
198
|
+
root((root) => {
|
|
199
|
+
instance.on('cleanup', () => root.dispose());
|
|
200
|
+
this.render(v());
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
else if (self.state === DIRTY) {
|
|
221
204
|
this.render(v);
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
|
|
205
|
+
}
|
|
206
|
+
else {
|
|
207
|
+
raf.add(() => {
|
|
208
|
+
this.render(v);
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
oncleanup(this.marker, () => instance.dispose());
|
|
225
214
|
|
|
226
215
|
return this;
|
|
227
216
|
}
|
|
@@ -252,22 +241,33 @@ class Slot {
|
|
|
252
241
|
}
|
|
253
242
|
|
|
254
243
|
shift() {
|
|
255
|
-
|
|
244
|
+
let group = this.nodes.shift();
|
|
245
|
+
|
|
246
|
+
if (!group) {
|
|
247
|
+
return undefined;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
return remove(group);
|
|
256
251
|
}
|
|
257
252
|
|
|
258
253
|
splice(start: number, stop: number = this.nodes.length, ...groups: Elements[]) {
|
|
259
|
-
return
|
|
260
|
-
this.nodes.splice(start, stop, ...
|
|
254
|
+
return remove(
|
|
255
|
+
...this.nodes.splice(start, stop, ...after(this.anchor(start), groups))
|
|
261
256
|
);
|
|
262
257
|
}
|
|
263
258
|
|
|
264
259
|
unshift(...groups: Elements[]) {
|
|
265
|
-
return this.nodes.unshift(...
|
|
260
|
+
return this.nodes.unshift(...after(this.marker, groups));
|
|
266
261
|
}
|
|
267
262
|
}
|
|
268
263
|
|
|
269
264
|
|
|
265
|
+
const oncleanup = (element: Element, fn: VoidFunction) => {
|
|
266
|
+
( element[SLOT_CLEANUP] ??= [] ).push(fn);
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
|
|
270
270
|
export default (marker: Element, value: unknown) => {
|
|
271
271
|
return new Slot(marker).render(value);
|
|
272
272
|
};
|
|
273
|
-
export { Slot };
|
|
273
|
+
export { oncleanup, Slot };
|
package/src/types.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ReactiveArray } from '@esportsplus/reactivity';
|
|
2
|
-
import { RENDERABLE, RENDERABLE_REACTIVE, RENDERABLE_TEMPLATE } from './constants';
|
|
2
|
+
import { RENDERABLE, RENDERABLE_REACTIVE, RENDERABLE_TEMPLATE, SLOT_CLEANUP } from './constants';
|
|
3
3
|
import { firstChild } from './utilities';
|
|
4
4
|
import attributes from './attributes';
|
|
5
5
|
import event from './event';
|
|
@@ -26,7 +26,7 @@ type Effect<T> = () => EffectResponse<T>;
|
|
|
26
26
|
|
|
27
27
|
type EffectResponse<T> = T extends [] ? EffectResponse<T[number]>[] : Primitive | Renderable<T>;
|
|
28
28
|
|
|
29
|
-
type Element = HTMLElement & Attributes & Record<PropertyKey, unknown>;
|
|
29
|
+
type Element = HTMLElement & Attributes & { [SLOT_CLEANUP]?: VoidFunction[] } & Record<PropertyKey, unknown>;
|
|
30
30
|
|
|
31
31
|
type Elements = Element[];
|
|
32
32
|
|