sinho 0.2.2 → 0.3.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.
- package/dist/array_mutation.js +1 -1
- package/dist/array_mutation.js.map +1 -1
- package/dist/bundle.d.ts +16 -5
- package/dist/bundle.js +99 -80
- package/dist/bundle.min.js +1 -1
- package/dist/component.js +4 -1
- package/dist/component.js.map +1 -1
- package/dist/intrinsic/Dynamic.d.ts +33 -0
- package/dist/intrinsic/Dynamic.js +53 -0
- package/dist/intrinsic/Dynamic.js.map +1 -0
- package/dist/intrinsic/ErrorBoundary.d.ts +14 -0
- package/dist/intrinsic/ErrorBoundary.js +36 -0
- package/dist/intrinsic/ErrorBoundary.js.map +1 -0
- package/dist/intrinsic/For.js +21 -36
- package/dist/intrinsic/For.js.map +1 -1
- package/dist/intrinsic/Fragment.d.ts +1 -1
- package/dist/intrinsic/Fragment.js +5 -3
- package/dist/intrinsic/Fragment.js.map +1 -1
- package/dist/intrinsic/If.js +11 -7
- package/dist/intrinsic/If.js.map +1 -1
- package/dist/intrinsic/Portal.js +4 -4
- package/dist/intrinsic/Portal.js.map +1 -1
- package/dist/intrinsic/TagComponent.js +3 -3
- package/dist/intrinsic/TagComponent.js.map +1 -1
- package/dist/mod.d.ts +2 -2
- package/dist/mod.js +1 -0
- package/dist/mod.js.map +1 -1
- package/dist/scope.d.ts +9 -3
- package/dist/scope.js +32 -22
- package/dist/scope.js.map +1 -1
- package/dist/template.d.ts +7 -2
- package/dist/template.js +16 -0
- package/dist/template.js.map +1 -1
- package/package.json +1 -1
- package/src/array_mutation.ts +7 -4
- package/src/component.ts +4 -2
- package/src/intrinsic/For.ts +26 -43
- package/src/intrinsic/Fragment.ts +6 -4
- package/src/intrinsic/If.ts +22 -8
- package/src/intrinsic/Portal.ts +6 -4
- package/src/intrinsic/TagComponent.ts +4 -3
- package/src/mod.ts +2 -2
- package/src/scope.ts +52 -28
- package/src/template.ts +31 -2
- package/tsconfig.json +2 -1
- package/web/dist/shingo.min.d.ts +1131 -0
- package/web/dist/shingo.min.js +1 -0
- package/web/static/dist/bundle.d.ts +1126 -0
- package/web/static/dist/bundle.min.js +1 -0
package/src/intrinsic/For.ts
CHANGED
|
@@ -4,14 +4,15 @@ import {
|
|
|
4
4
|
Signal,
|
|
5
5
|
SignalLike,
|
|
6
6
|
useEffect,
|
|
7
|
+
useMemo,
|
|
7
8
|
useSignal,
|
|
8
9
|
useSubscope,
|
|
9
10
|
} from "../scope.js";
|
|
10
11
|
import { useRenderer } from "../renderer.js";
|
|
11
|
-
import { createTemplate, Template } from "../template.js";
|
|
12
|
+
import { createTemplate, Template, TemplateNodes } from "../template.js";
|
|
12
13
|
|
|
13
14
|
interface KeyMeta {
|
|
14
|
-
_subnodes:
|
|
15
|
+
_subnodes: TemplateNodes;
|
|
15
16
|
_destroy: () => void;
|
|
16
17
|
}
|
|
17
18
|
|
|
@@ -32,38 +33,27 @@ export const For = <T>(props: {
|
|
|
32
33
|
const items = MaybeSignal.upgrade(props.each ?? []);
|
|
33
34
|
const anchor = renderer._node(() => document.createComment(""));
|
|
34
35
|
const keyFn = props.key ?? ((_, i) => i);
|
|
35
|
-
const nodes:
|
|
36
|
+
const nodes: [Comment, TemplateNodes[]] = [anchor, []];
|
|
36
37
|
const keyMap = new Map<unknown, KeyMeta>();
|
|
37
38
|
const mutationResult = useArrayMutation(items, keyFn);
|
|
38
39
|
|
|
39
|
-
const lookForAnchor = (index: number): Node =>
|
|
40
|
-
|
|
41
|
-
const key = keyFn(items()[index - 1], index - 1);
|
|
42
|
-
const nodes = keyMap.get(key)?._subnodes ?? [];
|
|
43
|
-
|
|
44
|
-
if (nodes.length > 0) {
|
|
45
|
-
return nodes[nodes.length - 1];
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
return anchor;
|
|
50
|
-
};
|
|
40
|
+
const lookForAnchor = (index: number): Node =>
|
|
41
|
+
TemplateNodes.last(nodes[1], index - 1) ?? anchor;
|
|
51
42
|
|
|
52
43
|
useEffect(() => {
|
|
53
44
|
for (const mutation of mutationResult()._mutations) {
|
|
54
45
|
if (mutation._type == "r") {
|
|
55
|
-
const { _subnodes
|
|
46
|
+
const { _subnodes, _destroy } = keyMap.get(mutation._key) ?? {};
|
|
56
47
|
_destroy?.();
|
|
57
48
|
|
|
58
|
-
|
|
59
|
-
if (index > 0) {
|
|
60
|
-
nodes.splice(index, _subnodes.length);
|
|
61
|
-
}
|
|
49
|
+
nodes[1].splice(mutation._index, 1);
|
|
62
50
|
|
|
63
|
-
|
|
51
|
+
TemplateNodes.forEach(_subnodes ?? [], (node) =>
|
|
52
|
+
node.parentNode?.removeChild(node),
|
|
53
|
+
);
|
|
64
54
|
keyMap.delete(mutation._key);
|
|
65
55
|
} else if (mutation._type == "a") {
|
|
66
|
-
let _subnodes
|
|
56
|
+
let _subnodes!: TemplateNodes;
|
|
67
57
|
|
|
68
58
|
const [, destroy] = useSubscope(() => {
|
|
69
59
|
const [index, setIndex] = useSignal(mutation._index);
|
|
@@ -84,36 +74,29 @@ export const For = <T>(props: {
|
|
|
84
74
|
});
|
|
85
75
|
|
|
86
76
|
_subnodes = props.children?.(item, index, items).build() ?? [];
|
|
77
|
+
nodes[1].splice(mutation._index, 0, _subnodes);
|
|
87
78
|
|
|
88
|
-
|
|
89
|
-
const anchorIndex = nodes.indexOf(itemAnchor);
|
|
90
|
-
if (anchorIndex >= 0) {
|
|
91
|
-
nodes.splice(anchorIndex + 1, 0, ..._subnodes);
|
|
92
|
-
}
|
|
79
|
+
let itemAnchor = lookForAnchor(mutation._index);
|
|
93
80
|
|
|
94
|
-
|
|
95
|
-
itemAnchor.parentNode?.insertBefore(node, itemAnchor.nextSibling)
|
|
96
|
-
|
|
81
|
+
TemplateNodes.forEach(_subnodes, (node) => {
|
|
82
|
+
itemAnchor.parentNode?.insertBefore(node, itemAnchor.nextSibling);
|
|
83
|
+
itemAnchor = node;
|
|
84
|
+
});
|
|
97
85
|
});
|
|
98
86
|
|
|
99
87
|
keyMap.set(mutation._key, { _subnodes, _destroy: destroy });
|
|
100
88
|
} else if (mutation._type == "m") {
|
|
101
|
-
const { _subnodes
|
|
89
|
+
const { _subnodes } = keyMap.get(mutation._key) ?? {};
|
|
102
90
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
nodes.splice(index, _subnodes.length);
|
|
106
|
-
}
|
|
91
|
+
nodes[1].splice(mutation._from, 1);
|
|
92
|
+
nodes[1].splice(mutation._to, 0, _subnodes ?? []);
|
|
107
93
|
|
|
108
|
-
|
|
109
|
-
const anchorIndex = nodes.indexOf(itemAnchor);
|
|
110
|
-
if (anchorIndex >= 0) {
|
|
111
|
-
nodes.splice(anchorIndex + 1, 0, ..._subnodes);
|
|
112
|
-
}
|
|
94
|
+
let itemAnchor = lookForAnchor(mutation._to);
|
|
113
95
|
|
|
114
|
-
|
|
115
|
-
itemAnchor.parentNode?.insertBefore(node, itemAnchor.nextSibling)
|
|
116
|
-
|
|
96
|
+
TemplateNodes.forEach(_subnodes ?? [], (node) => {
|
|
97
|
+
itemAnchor.parentNode?.insertBefore(node, itemAnchor.nextSibling);
|
|
98
|
+
itemAnchor = node;
|
|
99
|
+
});
|
|
117
100
|
}
|
|
118
101
|
}
|
|
119
102
|
}, [mutationResult]);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Text } from "./Text.js";
|
|
2
2
|
import { FunctionalComponent } from "../component.js";
|
|
3
3
|
import { createTemplate, Template } from "../template.js";
|
|
4
|
-
import type
|
|
4
|
+
import { useMemo, type MaybeSignal } from "../scope.js";
|
|
5
5
|
|
|
6
6
|
export type Children =
|
|
7
7
|
| Template
|
|
@@ -31,8 +31,10 @@ export const Fragment: FunctionalComponent<{
|
|
|
31
31
|
return !Array.isArray(children)
|
|
32
32
|
? children == null
|
|
33
33
|
? []
|
|
34
|
-
:
|
|
35
|
-
|
|
36
|
-
|
|
34
|
+
: [
|
|
35
|
+
typeof children == "object"
|
|
36
|
+
? children.build()
|
|
37
|
+
: Text({ text: children }).build(),
|
|
38
|
+
]
|
|
37
39
|
: children.flatMap((children) => Fragment({ children }).build());
|
|
38
40
|
});
|
package/src/intrinsic/If.ts
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
import type { FunctionalComponent } from "../component.js";
|
|
2
|
-
import { createTemplate, type Template } from "../template.js";
|
|
3
|
-
import {
|
|
2
|
+
import { TemplateNodes, createTemplate, type Template } from "../template.js";
|
|
3
|
+
import {
|
|
4
|
+
MaybeSignal,
|
|
5
|
+
SignalLike,
|
|
6
|
+
useEffect,
|
|
7
|
+
useMemo,
|
|
8
|
+
useSignal,
|
|
9
|
+
useSubscope,
|
|
10
|
+
} from "../scope.js";
|
|
4
11
|
import { runWithRenderer, useRenderer } from "../renderer.js";
|
|
5
12
|
import { Children, Fragment } from "./Fragment.js";
|
|
6
13
|
|
|
@@ -38,21 +45,28 @@ export const ElseIf: FunctionalComponent<{
|
|
|
38
45
|
return runWithRenderer({ _ifConditions: [] }, () =>
|
|
39
46
|
createTemplate(() => {
|
|
40
47
|
const anchor = renderer._node(() => document.createComment(""));
|
|
41
|
-
const nodes:
|
|
48
|
+
const nodes: [Comment, TemplateNodes] = [anchor, []];
|
|
42
49
|
const template = useMemo(() =>
|
|
43
50
|
condition() ? Fragment({ children: props.children }) : null,
|
|
44
51
|
);
|
|
45
52
|
|
|
46
|
-
let subnodes:
|
|
53
|
+
let subnodes: TemplateNodes = [];
|
|
47
54
|
|
|
48
55
|
useEffect(() => {
|
|
49
|
-
|
|
50
|
-
|
|
56
|
+
TemplateNodes.forEach(subnodes, (node) =>
|
|
57
|
+
node.parentNode?.removeChild(node),
|
|
58
|
+
);
|
|
59
|
+
nodes[1] = [];
|
|
51
60
|
|
|
52
61
|
const [, destroy] = useSubscope(() => {
|
|
53
62
|
subnodes = template()?.build() ?? [];
|
|
54
|
-
|
|
55
|
-
|
|
63
|
+
nodes[1] = subnodes;
|
|
64
|
+
|
|
65
|
+
let before: Node = anchor;
|
|
66
|
+
TemplateNodes.forEach(subnodes, (node) => {
|
|
67
|
+
before.parentNode?.insertBefore(node, before.nextSibling);
|
|
68
|
+
before = node;
|
|
69
|
+
});
|
|
56
70
|
});
|
|
57
71
|
|
|
58
72
|
return destroy;
|
package/src/intrinsic/Portal.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { FunctionalComponent } from "../component.js";
|
|
2
2
|
import { runWithRenderer } from "../renderer.js";
|
|
3
3
|
import { useEffect } from "../scope.js";
|
|
4
|
-
import { createTemplate } from "../template.js";
|
|
4
|
+
import { TemplateNodes, createTemplate } from "../template.js";
|
|
5
5
|
import { Children, Fragment } from "./Fragment.js";
|
|
6
6
|
|
|
7
7
|
export const Portal: FunctionalComponent<{
|
|
@@ -13,12 +13,14 @@ export const Portal: FunctionalComponent<{
|
|
|
13
13
|
const nodes = Fragment({ children }).build();
|
|
14
14
|
|
|
15
15
|
useEffect(() => {
|
|
16
|
-
|
|
16
|
+
TemplateNodes.forEach(nodes, (node) => mount.appendChild(node));
|
|
17
17
|
|
|
18
18
|
return () => {
|
|
19
|
-
|
|
19
|
+
TemplateNodes.forEach(nodes, (node) =>
|
|
20
|
+
node.parentNode?.removeChild(node),
|
|
21
|
+
);
|
|
20
22
|
};
|
|
21
|
-
});
|
|
23
|
+
}, []);
|
|
22
24
|
|
|
23
25
|
return [];
|
|
24
26
|
}),
|
|
@@ -3,7 +3,7 @@ import { jsxPropNameToEventName } from "../utils.js";
|
|
|
3
3
|
import { MaybeSignal, useBatch, useEffect, useScope } from "../scope.js";
|
|
4
4
|
import { Fragment } from "./Fragment.js";
|
|
5
5
|
import { runWithRenderer, useRenderer } from "../renderer.js";
|
|
6
|
-
import { createTemplate, Template } from "../template.js";
|
|
6
|
+
import { createTemplate, Template, TemplateNodes } from "../template.js";
|
|
7
7
|
|
|
8
8
|
export const hydrateElement = <E extends HTMLElement | SVGElement>(
|
|
9
9
|
node: E,
|
|
@@ -66,14 +66,15 @@ export const hydrateElement = <E extends HTMLElement | SVGElement>(
|
|
|
66
66
|
}
|
|
67
67
|
|
|
68
68
|
if (props.children != null) {
|
|
69
|
-
|
|
70
|
-
|
|
69
|
+
TemplateNodes.forEach(
|
|
70
|
+
runWithRenderer(
|
|
71
71
|
{
|
|
72
72
|
_svg: svg,
|
|
73
73
|
_nodes: node.childNodes.values(),
|
|
74
74
|
},
|
|
75
75
|
() => Fragment({ children: props.children }).build(),
|
|
76
76
|
),
|
|
77
|
+
(subnode) => node.append(subnode),
|
|
77
78
|
);
|
|
78
79
|
}
|
|
79
80
|
|
package/src/mod.ts
CHANGED
|
@@ -15,8 +15,8 @@ export {
|
|
|
15
15
|
} from "./component.js";
|
|
16
16
|
export { type Context, createContext, useContext } from "./context.js";
|
|
17
17
|
export { createElement, h } from "./create_element.js";
|
|
18
|
-
export { DangerousHtml, Styles } from "./dom.js";
|
|
19
|
-
export { type Template } from "./template.js";
|
|
18
|
+
export { type DangerousHtml, type Styles } from "./dom.js";
|
|
19
|
+
export { type Template, TemplateNodes } from "./template.js";
|
|
20
20
|
export {
|
|
21
21
|
type Cleanup,
|
|
22
22
|
MaybeSignal,
|
package/src/scope.ts
CHANGED
|
@@ -21,6 +21,13 @@ export interface Signal<out T> extends SignalLike<T> {
|
|
|
21
21
|
peek(): T;
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
+
export interface SignalOptions<T> extends SetSignalOptions {
|
|
25
|
+
/**
|
|
26
|
+
* A custom equality function to compare the new value with the old value.
|
|
27
|
+
*/
|
|
28
|
+
equals?: (a: T, b: T) => boolean;
|
|
29
|
+
}
|
|
30
|
+
|
|
24
31
|
export interface SetSignalOptions {
|
|
25
32
|
/**
|
|
26
33
|
* Whether to force the update of the signal even if the new value has the
|
|
@@ -116,6 +123,7 @@ let currBatch:
|
|
|
116
123
|
| {
|
|
117
124
|
_setters: (() => void)[];
|
|
118
125
|
_effects: Set<Effect>;
|
|
126
|
+
_pureEffects: Set<Effect>;
|
|
119
127
|
}
|
|
120
128
|
| undefined;
|
|
121
129
|
|
|
@@ -128,14 +136,14 @@ export const useScope = <T = {}>(): Scope<T> => currScope as Scope<T>;
|
|
|
128
136
|
*/
|
|
129
137
|
export const useSignal: (<T>(
|
|
130
138
|
value: T,
|
|
131
|
-
opts?:
|
|
139
|
+
opts?: SignalOptions<T>,
|
|
132
140
|
) => readonly [Signal<T>, SignalSetter<T>]) &
|
|
133
141
|
(<T>(
|
|
134
142
|
value?: T,
|
|
135
|
-
opts?:
|
|
143
|
+
opts?: SignalOptions<T | undefined>,
|
|
136
144
|
) => readonly [Signal<T | undefined>, SignalSetter<T | undefined>]) = <T>(
|
|
137
145
|
value: T,
|
|
138
|
-
opts?:
|
|
146
|
+
opts?: SignalOptions<T>,
|
|
139
147
|
): readonly [Signal<T>, SignalSetter<T>] => {
|
|
140
148
|
const signal: Signal<T> = () => {
|
|
141
149
|
if (!currUntracked && currEffect) {
|
|
@@ -150,7 +158,8 @@ export const useSignal: (<T>(
|
|
|
150
158
|
signal.peek = () => value;
|
|
151
159
|
|
|
152
160
|
const setter = (arg: T | ((value: T) => T), innerOpts?: SetSignalOptions) => {
|
|
153
|
-
|
|
161
|
+
const allOpts = { ...opts, ...innerOpts };
|
|
162
|
+
allOpts.equals ??= (a, b) => a === b;
|
|
154
163
|
|
|
155
164
|
if (currBatch) {
|
|
156
165
|
const newValue =
|
|
@@ -158,19 +167,25 @@ export const useSignal: (<T>(
|
|
|
158
167
|
? (arg as (value: T) => T)(signal.peek())
|
|
159
168
|
: arg;
|
|
160
169
|
|
|
161
|
-
if (
|
|
162
|
-
if (
|
|
170
|
+
if (allOpts?.force || !allOpts.equals(newValue, signal.peek())) {
|
|
171
|
+
if (allOpts?.force) {
|
|
163
172
|
value = newValue;
|
|
164
173
|
} else {
|
|
165
174
|
currBatch._setters.push(() => (value = newValue));
|
|
166
175
|
}
|
|
167
176
|
|
|
168
|
-
if (!
|
|
169
|
-
signal._effects.forEach((effect) =>
|
|
177
|
+
if (!allOpts?.silent) {
|
|
178
|
+
signal._effects.forEach((effect) => {
|
|
179
|
+
if (effect._pure) {
|
|
180
|
+
currBatch!._pureEffects.add(effect);
|
|
181
|
+
} else {
|
|
182
|
+
currBatch!._effects.add(effect);
|
|
183
|
+
}
|
|
184
|
+
});
|
|
170
185
|
}
|
|
171
186
|
}
|
|
172
187
|
} else {
|
|
173
|
-
useBatch(() => setter(arg,
|
|
188
|
+
useBatch(() => setter(arg, allOpts));
|
|
174
189
|
}
|
|
175
190
|
};
|
|
176
191
|
|
|
@@ -186,7 +201,11 @@ export const useSignal: (<T>(
|
|
|
186
201
|
export const useBatch = <T>(fn: () => T): T => {
|
|
187
202
|
if (currBatch) return fn();
|
|
188
203
|
|
|
189
|
-
currBatch = {
|
|
204
|
+
currBatch = {
|
|
205
|
+
_setters: [],
|
|
206
|
+
_effects: new Set(),
|
|
207
|
+
_pureEffects: new Set(),
|
|
208
|
+
};
|
|
190
209
|
|
|
191
210
|
try {
|
|
192
211
|
const result = fn();
|
|
@@ -198,9 +217,12 @@ export const useBatch = <T>(fn: () => T): T => {
|
|
|
198
217
|
};
|
|
199
218
|
|
|
200
219
|
export const flushBatch = (): void => {
|
|
201
|
-
|
|
220
|
+
while (
|
|
202
221
|
currBatch &&
|
|
203
|
-
|
|
222
|
+
currBatch._setters.length +
|
|
223
|
+
currBatch._effects.size +
|
|
224
|
+
currBatch._pureEffects.size >
|
|
225
|
+
0
|
|
204
226
|
) {
|
|
205
227
|
// Clean effect subscope
|
|
206
228
|
|
|
@@ -213,18 +235,14 @@ export const flushBatch = (): void => {
|
|
|
213
235
|
|
|
214
236
|
// Run next effect
|
|
215
237
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
currBatch._effects.delete(effect);
|
|
220
|
-
continue a;
|
|
221
|
-
}
|
|
222
|
-
}
|
|
238
|
+
const effect: Effect | undefined =
|
|
239
|
+
currBatch._pureEffects.values().next().value ??
|
|
240
|
+
currBatch._effects.values().next().value;
|
|
223
241
|
|
|
224
|
-
|
|
242
|
+
if (effect) {
|
|
225
243
|
effect._run();
|
|
244
|
+
currBatch._pureEffects.delete(effect);
|
|
226
245
|
currBatch._effects.delete(effect);
|
|
227
|
-
break;
|
|
228
246
|
}
|
|
229
247
|
}
|
|
230
248
|
};
|
|
@@ -305,15 +323,18 @@ export const useEffect = (
|
|
|
305
323
|
*
|
|
306
324
|
* @param fn The computation function.
|
|
307
325
|
*/
|
|
308
|
-
export const useMemo = <T>(fn: () => T, opts?:
|
|
309
|
-
const [memo, setMemo] = useSignal<T>(
|
|
326
|
+
export const useMemo = <T>(fn: () => T, opts?: SignalOptions<T>): Signal<T> => {
|
|
327
|
+
const [memo, setMemo] = useSignal<T>(
|
|
328
|
+
undefined,
|
|
329
|
+
opts as SignalOptions<T | undefined>,
|
|
330
|
+
);
|
|
310
331
|
|
|
311
332
|
let firstTime = true;
|
|
312
333
|
pureEffectFlag = true;
|
|
313
334
|
|
|
314
335
|
try {
|
|
315
336
|
useEffect(() => {
|
|
316
|
-
setMemo(fn, firstTime ? {
|
|
337
|
+
setMemo(fn, firstTime ? { force: true } : {});
|
|
317
338
|
|
|
318
339
|
firstTime = false;
|
|
319
340
|
});
|
|
@@ -384,15 +405,18 @@ export interface RefSignalSetter<in T> {
|
|
|
384
405
|
/**
|
|
385
406
|
* Creates a new signal with write capabilities.
|
|
386
407
|
*/
|
|
387
|
-
export const useRef: (<T>(value: T, opts?:
|
|
388
|
-
(<T>(
|
|
408
|
+
export const useRef: (<T>(value: T, opts?: SignalOptions<T>) => RefSignal<T>) &
|
|
409
|
+
(<T>(
|
|
410
|
+
value?: T,
|
|
411
|
+
opts?: SignalOptions<T | undefined>,
|
|
412
|
+
) => RefSignal<T | undefined>) = (<T>(
|
|
389
413
|
value?: T,
|
|
390
|
-
opts?:
|
|
414
|
+
opts?: SignalOptions<T | undefined>,
|
|
391
415
|
): RefSignal<T> & RefSignal<T | undefined> => {
|
|
392
416
|
const [signal, setter] = useSignal(value, opts);
|
|
393
417
|
(signal as RefSignal<T | undefined>).set = setter;
|
|
394
418
|
return signal as RefSignal<T> & RefSignal<T | undefined>;
|
|
395
|
-
};
|
|
419
|
+
}) as any;
|
|
396
420
|
|
|
397
421
|
/**
|
|
398
422
|
* Represents a value that can be a signal or a constant value.
|
package/src/template.ts
CHANGED
|
@@ -1,3 +1,30 @@
|
|
|
1
|
+
export type TemplateNodes = (Node | TemplateNodes)[];
|
|
2
|
+
|
|
3
|
+
export namespace TemplateNodes {
|
|
4
|
+
export const forEach = (
|
|
5
|
+
nodes: TemplateNodes,
|
|
6
|
+
fn: (node: Node) => void,
|
|
7
|
+
): void =>
|
|
8
|
+
nodes.forEach((node) =>
|
|
9
|
+
Array.isArray(node) ? TemplateNodes.forEach(node, fn) : fn(node),
|
|
10
|
+
);
|
|
11
|
+
|
|
12
|
+
export const last = (
|
|
13
|
+
nodes: TemplateNodes,
|
|
14
|
+
lastIndex: number = nodes.length - 1,
|
|
15
|
+
): Node | undefined => {
|
|
16
|
+
if (!nodes.length) return;
|
|
17
|
+
|
|
18
|
+
for (let i = lastIndex; i >= 0; i--) {
|
|
19
|
+
const last = nodes[i];
|
|
20
|
+
if (!Array.isArray(last)) return last;
|
|
21
|
+
|
|
22
|
+
const lastNode = TemplateNodes.last(last);
|
|
23
|
+
if (lastNode) return lastNode;
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
1
28
|
/**
|
|
2
29
|
* Represents a render result of a component.
|
|
3
30
|
*/
|
|
@@ -5,10 +32,12 @@ export interface Template {
|
|
|
5
32
|
/**
|
|
6
33
|
* Build the DOM elements represented by this template.
|
|
7
34
|
*/
|
|
8
|
-
build():
|
|
35
|
+
build(): TemplateNodes;
|
|
9
36
|
}
|
|
10
37
|
|
|
11
|
-
export const createTemplate = (
|
|
38
|
+
export const createTemplate = (
|
|
39
|
+
build: () => Template | TemplateNodes,
|
|
40
|
+
): Template => ({
|
|
12
41
|
build() {
|
|
13
42
|
const nodes = build();
|
|
14
43
|
return (nodes as Template).build?.() ?? nodes;
|