@dom-expressions/runtime 0.50.0-next.15
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/CHANGELOG.md +348 -0
- package/LICENSE +21 -0
- package/README.md +106 -0
- package/package.json +36 -0
- package/src/client.d.ts +117 -0
- package/src/client.js +893 -0
- package/src/constants.d.ts +23 -0
- package/src/constants.js +517 -0
- package/src/jsx-customize.mjs +58 -0
- package/src/jsx-h.d.ts +4148 -0
- package/src/jsx-properties.d.ts +93 -0
- package/src/jsx-update.mjs +96 -0
- package/src/jsx.d.ts +4134 -0
- package/src/reconcile.d.ts +1 -0
- package/src/reconcile.js +149 -0
- package/src/serializer.d.ts +3 -0
- package/src/serializer.js +54 -0
- package/src/server.d.ts +193 -0
- package/src/server.js +1302 -0
- package/src/universal.d.ts +38 -0
- package/src/universal.js +352 -0
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
export interface RendererOptions<NodeType> {
|
|
2
|
+
createElement(tag: string): NodeType;
|
|
3
|
+
createTextNode(value: string): NodeType;
|
|
4
|
+
createSentinel?(): NodeType;
|
|
5
|
+
replaceText(textNode: NodeType, value: string): void;
|
|
6
|
+
isTextNode(node: NodeType): boolean;
|
|
7
|
+
setProperty<T>(node: NodeType, name: string, value: T, prev?: T): void;
|
|
8
|
+
insertNode(parent: NodeType, node: NodeType, anchor?: NodeType): void;
|
|
9
|
+
removeNode(parent: NodeType, node: NodeType): void;
|
|
10
|
+
cleanupNodes?(parent: NodeType, nodes: NodeType[]): void;
|
|
11
|
+
getParentNode(node: NodeType): NodeType | undefined;
|
|
12
|
+
getFirstChild(node: NodeType): NodeType | undefined;
|
|
13
|
+
getNextSibling(node: NodeType): NodeType | undefined;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface Renderer<NodeType> {
|
|
17
|
+
render(code: () => NodeType, node: NodeType): () => void;
|
|
18
|
+
effect<T>(fn: (prev?: T) => T, effect: (value: T, prev?: T) => void): void;
|
|
19
|
+
memo<T>(fn: () => T, equal: boolean): () => T;
|
|
20
|
+
createComponent<T>(Comp: (props: T) => NodeType, props: T): NodeType;
|
|
21
|
+
createElement(tag: string): NodeType;
|
|
22
|
+
createTextNode(value: string): NodeType;
|
|
23
|
+
insertNode(parent: NodeType, node: NodeType, anchor?: NodeType): void;
|
|
24
|
+
insert<T>(parent: any, accessor: (() => T) | T, marker?: any | null, initial?: any): NodeType;
|
|
25
|
+
spread<T extends object>(node: any, props: T, skipChildren?: boolean): void;
|
|
26
|
+
setProp<T>(node: NodeType, name: string, value: T, prev?: T): T;
|
|
27
|
+
mergeProps(...sources: unknown[]): unknown;
|
|
28
|
+
applyRef(
|
|
29
|
+
r: ((element: NodeType) => void) | ((element: NodeType) => void)[],
|
|
30
|
+
element: NodeType
|
|
31
|
+
): void;
|
|
32
|
+
ref(
|
|
33
|
+
fn: () => ((element: NodeType) => void) | ((element: NodeType) => void)[],
|
|
34
|
+
element: NodeType
|
|
35
|
+
): void;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function createRenderer<NodeType>(options: RendererOptions<NodeType>): Renderer<NodeType>;
|
package/src/universal.js
ADDED
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
import {
|
|
2
|
+
root,
|
|
3
|
+
effect,
|
|
4
|
+
memo,
|
|
5
|
+
createComponent,
|
|
6
|
+
untrack,
|
|
7
|
+
runWithOwner,
|
|
8
|
+
mergeProps,
|
|
9
|
+
flatten
|
|
10
|
+
} from "rxcore";
|
|
11
|
+
|
|
12
|
+
const INNER_OWNED = {};
|
|
13
|
+
|
|
14
|
+
export function createRenderer({
|
|
15
|
+
createElement,
|
|
16
|
+
createTextNode,
|
|
17
|
+
createSentinel = () => createTextNode(""),
|
|
18
|
+
isTextNode,
|
|
19
|
+
replaceText,
|
|
20
|
+
insertNode,
|
|
21
|
+
removeNode,
|
|
22
|
+
cleanupNodes,
|
|
23
|
+
setProperty,
|
|
24
|
+
getParentNode,
|
|
25
|
+
getFirstChild,
|
|
26
|
+
getNextSibling
|
|
27
|
+
}) {
|
|
28
|
+
function insert(parent, accessor, marker, initial, options) {
|
|
29
|
+
const onUpdate = options && options.onUpdate;
|
|
30
|
+
let effectOptions = options;
|
|
31
|
+
if (onUpdate) {
|
|
32
|
+
const { onUpdate, ...rest } = options;
|
|
33
|
+
effectOptions = rest;
|
|
34
|
+
}
|
|
35
|
+
const multi = marker !== undefined;
|
|
36
|
+
if (multi && !initial) initial = [];
|
|
37
|
+
if (typeof accessor !== "function") {
|
|
38
|
+
accessor = normalize(accessor, multi, true);
|
|
39
|
+
if (typeof accessor !== "function") {
|
|
40
|
+
insertExpression(parent, accessor, initial, marker);
|
|
41
|
+
onUpdate && onUpdate(accessor);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
if (multi && initial.length === 0) {
|
|
46
|
+
const sentinel = createSentinel();
|
|
47
|
+
insertNode(parent, sentinel, marker);
|
|
48
|
+
initial = [sentinel];
|
|
49
|
+
}
|
|
50
|
+
let current = initial;
|
|
51
|
+
effect(
|
|
52
|
+
prev => {
|
|
53
|
+
const value = normalize(accessor(), multi, true);
|
|
54
|
+
if (typeof value !== "function") return value;
|
|
55
|
+
effect(
|
|
56
|
+
() => normalize(value, multi),
|
|
57
|
+
inner => {
|
|
58
|
+
insertExpression(parent, inner, current, marker);
|
|
59
|
+
current = inner;
|
|
60
|
+
onUpdate && onUpdate(current);
|
|
61
|
+
},
|
|
62
|
+
prev !== undefined && !(options && options.schedule)
|
|
63
|
+
? { ...effectOptions, schedule: true }
|
|
64
|
+
: effectOptions
|
|
65
|
+
);
|
|
66
|
+
return INNER_OWNED;
|
|
67
|
+
},
|
|
68
|
+
value => {
|
|
69
|
+
if (value === INNER_OWNED) return;
|
|
70
|
+
insertExpression(parent, value, current, marker);
|
|
71
|
+
current = value;
|
|
72
|
+
onUpdate && onUpdate(current);
|
|
73
|
+
},
|
|
74
|
+
effectOptions
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function insertExpression(parent, value, current, marker) {
|
|
79
|
+
if (value === current) return;
|
|
80
|
+
const t = typeof value,
|
|
81
|
+
multi = marker !== undefined;
|
|
82
|
+
|
|
83
|
+
if (t === "string" || t === "number") {
|
|
84
|
+
const tc = typeof current;
|
|
85
|
+
if (tc === "string" || tc === "number") {
|
|
86
|
+
replaceText(getFirstChild(parent), value);
|
|
87
|
+
} else {
|
|
88
|
+
cleanChildren(parent, current, marker, createTextNode(value));
|
|
89
|
+
}
|
|
90
|
+
} else if (value == null) {
|
|
91
|
+
cleanChildren(parent, current, marker);
|
|
92
|
+
} else if (Array.isArray(value)) {
|
|
93
|
+
if (value.length === 0) {
|
|
94
|
+
cleanChildren(parent, current, marker);
|
|
95
|
+
} else {
|
|
96
|
+
if (Array.isArray(current)) {
|
|
97
|
+
if (current.length === 0) {
|
|
98
|
+
appendNodes(parent, value, marker);
|
|
99
|
+
} else reconcileArrays(parent, current, value);
|
|
100
|
+
} else if (current == null) {
|
|
101
|
+
appendNodes(parent, value);
|
|
102
|
+
} else {
|
|
103
|
+
reconcileArrays(parent, (multi && current) || [getFirstChild(parent)], value);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
} else {
|
|
107
|
+
if (Array.isArray(current)) {
|
|
108
|
+
cleanChildren(parent, current, multi ? marker : null, value);
|
|
109
|
+
} else if (current == null || !getFirstChild(parent)) {
|
|
110
|
+
insertNode(parent, value);
|
|
111
|
+
} else replaceNode(parent, value, getFirstChild(parent));
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function normalize(value, multi, doNotUnwrap) {
|
|
116
|
+
value = flatten(value, { skipNonRendered: true, doNotUnwrap });
|
|
117
|
+
if (doNotUnwrap && typeof value === "function") return value;
|
|
118
|
+
if (multi && !Array.isArray(value)) value = [value != null ? value : ""];
|
|
119
|
+
if (Array.isArray(value)) {
|
|
120
|
+
for (let i = 0, len = value.length; i < len; i++) {
|
|
121
|
+
const item = value[i],
|
|
122
|
+
t = typeof item;
|
|
123
|
+
if (t === "string" || t === "number") value[i] = createTextNode(item);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return value;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function reconcileArrays(parentNode, a, b) {
|
|
130
|
+
let bLength = b.length,
|
|
131
|
+
aEnd = a.length,
|
|
132
|
+
bEnd = bLength,
|
|
133
|
+
aStart = 0,
|
|
134
|
+
bStart = 0,
|
|
135
|
+
after = getNextSibling(a[aEnd - 1]),
|
|
136
|
+
map = null;
|
|
137
|
+
|
|
138
|
+
while (aStart < aEnd || bStart < bEnd) {
|
|
139
|
+
// common prefix
|
|
140
|
+
if (a[aStart] === b[bStart]) {
|
|
141
|
+
aStart++;
|
|
142
|
+
bStart++;
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
// common suffix
|
|
146
|
+
while (a[aEnd - 1] === b[bEnd - 1]) {
|
|
147
|
+
aEnd--;
|
|
148
|
+
bEnd--;
|
|
149
|
+
}
|
|
150
|
+
// append
|
|
151
|
+
if (aEnd === aStart) {
|
|
152
|
+
const node =
|
|
153
|
+
bEnd < bLength ? (bStart ? getNextSibling(b[bStart - 1]) : b[bEnd - bStart]) : after;
|
|
154
|
+
|
|
155
|
+
while (bStart < bEnd) insertNode(parentNode, b[bStart++], node);
|
|
156
|
+
// remove
|
|
157
|
+
} else if (bEnd === bStart) {
|
|
158
|
+
while (aStart < aEnd) {
|
|
159
|
+
if (!map || !map.has(a[aStart])) removeNode(parentNode, a[aStart]);
|
|
160
|
+
aStart++;
|
|
161
|
+
}
|
|
162
|
+
// swap backward — symmetric end-swap detected. Walk inward with a single
|
|
163
|
+
// stable front anchor (a[aStart]); each move targets the same host
|
|
164
|
+
// position and avoids cross-anchored inserts on reorder-heavy patterns.
|
|
165
|
+
} else if (a[aStart] === b[bEnd - 1] && b[bStart] === a[aEnd - 1]) {
|
|
166
|
+
const anchor = a[aStart];
|
|
167
|
+
do {
|
|
168
|
+
insertNode(parentNode, a[--aEnd], anchor);
|
|
169
|
+
bStart++;
|
|
170
|
+
if (aStart >= aEnd - 1 || bStart >= bEnd) break;
|
|
171
|
+
} while (a[aStart] === b[bEnd - 1] && b[bStart] === a[aEnd - 1]);
|
|
172
|
+
// fallback to map
|
|
173
|
+
} else {
|
|
174
|
+
if (!map) {
|
|
175
|
+
map = new Map();
|
|
176
|
+
let i = bStart;
|
|
177
|
+
|
|
178
|
+
while (i < bEnd) map.set(b[i], i++);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const index = map.get(a[aStart]);
|
|
182
|
+
if (index != null) {
|
|
183
|
+
if (bStart < index && index < bEnd) {
|
|
184
|
+
let i = aStart,
|
|
185
|
+
sequence = 1,
|
|
186
|
+
t;
|
|
187
|
+
|
|
188
|
+
while (++i < aEnd && i < bEnd) {
|
|
189
|
+
if ((t = map.get(a[i])) == null || t !== index + sequence) break;
|
|
190
|
+
sequence++;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (sequence > index - bStart) {
|
|
194
|
+
const node = a[aStart];
|
|
195
|
+
while (bStart < index) insertNode(parentNode, b[bStart++], node);
|
|
196
|
+
} else replaceNode(parentNode, b[bStart++], a[aStart++]);
|
|
197
|
+
} else aStart++;
|
|
198
|
+
} else removeNode(parentNode, a[aStart++]);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function cleanChildren(parent, current, marker, replacement) {
|
|
204
|
+
if (marker === undefined) {
|
|
205
|
+
let removed;
|
|
206
|
+
while ((removed = getFirstChild(parent))) removeNode(parent, removed);
|
|
207
|
+
replacement && insertNode(parent, replacement);
|
|
208
|
+
return "";
|
|
209
|
+
}
|
|
210
|
+
if (current.length) {
|
|
211
|
+
let inserted = false;
|
|
212
|
+
for (let i = current.length - 1; i >= 0; i--) {
|
|
213
|
+
const el = current[i];
|
|
214
|
+
if (replacement !== el) {
|
|
215
|
+
const isParent = getParentNode(el) === parent;
|
|
216
|
+
if (replacement && !inserted && !i)
|
|
217
|
+
isParent
|
|
218
|
+
? replaceNode(parent, replacement, el)
|
|
219
|
+
: insertNode(parent, replacement, marker);
|
|
220
|
+
else isParent && removeNode(parent, el);
|
|
221
|
+
} else inserted = true;
|
|
222
|
+
}
|
|
223
|
+
} else if (replacement) insertNode(parent, replacement, marker);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function appendNodes(parent, array, marker) {
|
|
227
|
+
for (let i = 0, len = array.length; i < len; i++) insertNode(parent, array[i], marker);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function replaceNode(parent, newNode, oldNode) {
|
|
231
|
+
insertNode(parent, newNode, oldNode);
|
|
232
|
+
removeNode(parent, oldNode);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function collectNodes(value, nodes) {
|
|
236
|
+
if (Array.isArray(value)) {
|
|
237
|
+
for (let i = 0, len = value.length; i < len; i++) collectNodes(value[i], nodes);
|
|
238
|
+
} else if (value != null && typeof value !== "string" && typeof value !== "number") {
|
|
239
|
+
nodes.push(value);
|
|
240
|
+
}
|
|
241
|
+
return nodes;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function collectMounted(parent, value) {
|
|
245
|
+
const nodes = collectNodes(value, []);
|
|
246
|
+
if (!nodes.length && (typeof value === "string" || typeof value === "number")) {
|
|
247
|
+
const node = getFirstChild(parent);
|
|
248
|
+
if (node) nodes.push(node);
|
|
249
|
+
}
|
|
250
|
+
return nodes;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function defaultCleanupNodes(parent, nodes) {
|
|
254
|
+
for (let i = 0, len = nodes.length; i < len; i++) {
|
|
255
|
+
const node = nodes[i];
|
|
256
|
+
if (getParentNode(node) === parent) removeNode(parent, node);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// TODO: make this better
|
|
261
|
+
function spread(node, props, skipChildren) {
|
|
262
|
+
const prevProps = {};
|
|
263
|
+
props || (props = {});
|
|
264
|
+
if (!skipChildren) insert(node, () => props.children);
|
|
265
|
+
effect(
|
|
266
|
+
() => {
|
|
267
|
+
const r = props.ref;
|
|
268
|
+
(typeof r === "function" || Array.isArray(r)) && ref(() => r, node);
|
|
269
|
+
},
|
|
270
|
+
() => {}
|
|
271
|
+
);
|
|
272
|
+
effect(
|
|
273
|
+
() => {
|
|
274
|
+
const newProps = {};
|
|
275
|
+
for (const prop in props) {
|
|
276
|
+
if (prop === "children" || prop === "ref") continue;
|
|
277
|
+
newProps[prop] = props[prop];
|
|
278
|
+
}
|
|
279
|
+
return newProps;
|
|
280
|
+
},
|
|
281
|
+
props => {
|
|
282
|
+
for (const prop in prevProps) {
|
|
283
|
+
if (!(prop in props)) {
|
|
284
|
+
setProperty(node, prop, undefined, prevProps[prop]);
|
|
285
|
+
delete prevProps[prop];
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
for (const prop in props) {
|
|
289
|
+
const value = props[prop];
|
|
290
|
+
if (value === prevProps[prop]) continue;
|
|
291
|
+
setProperty(node, prop, value, prevProps[prop]);
|
|
292
|
+
prevProps[prop] = value;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
);
|
|
296
|
+
return prevProps;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
function applyRef(r, element) {
|
|
300
|
+
Array.isArray(r) ? r.flat(Infinity).forEach(f => f && f(element)) : r(element);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
function ref(fn, element) {
|
|
304
|
+
const resolved = untrack(fn);
|
|
305
|
+
runWithOwner(null, () => applyRef(resolved, element));
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
return {
|
|
309
|
+
render(code, element) {
|
|
310
|
+
let disposer,
|
|
311
|
+
disposed = false,
|
|
312
|
+
mounted = [];
|
|
313
|
+
const cleanup = cleanupNodes || defaultCleanupNodes;
|
|
314
|
+
try {
|
|
315
|
+
root(dispose => {
|
|
316
|
+
disposer = dispose;
|
|
317
|
+
insert(element, code(), undefined, undefined, {
|
|
318
|
+
onUpdate(value) {
|
|
319
|
+
mounted = collectMounted(element, value);
|
|
320
|
+
}
|
|
321
|
+
});
|
|
322
|
+
});
|
|
323
|
+
} catch (err) {
|
|
324
|
+
if (disposer) disposer();
|
|
325
|
+
cleanup(element, mounted);
|
|
326
|
+
throw err;
|
|
327
|
+
}
|
|
328
|
+
return () => {
|
|
329
|
+
if (disposed) return;
|
|
330
|
+
disposed = true;
|
|
331
|
+
disposer();
|
|
332
|
+
cleanup(element, mounted);
|
|
333
|
+
mounted = [];
|
|
334
|
+
};
|
|
335
|
+
},
|
|
336
|
+
insert,
|
|
337
|
+
spread,
|
|
338
|
+
createElement,
|
|
339
|
+
createTextNode,
|
|
340
|
+
insertNode,
|
|
341
|
+
setProp(node, name, value, prev) {
|
|
342
|
+
setProperty(node, name, value, prev);
|
|
343
|
+
return value;
|
|
344
|
+
},
|
|
345
|
+
mergeProps,
|
|
346
|
+
effect,
|
|
347
|
+
memo,
|
|
348
|
+
createComponent,
|
|
349
|
+
applyRef,
|
|
350
|
+
ref
|
|
351
|
+
};
|
|
352
|
+
}
|