@async/framework 0.3.0 → 0.5.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/CHANGELOG.md +28 -0
- package/README.md +254 -34
- package/examples/components/main.js +10 -10
- package/examples/ssr/main.js +1 -1
- package/examples/streaming/main.js +1 -1
- package/framework.js +4133 -0
- package/package.json +9 -5
- package/src/async-signal.js +12 -1
- package/src/attributes.js +2 -0
- package/src/cache.js +5 -0
- package/src/component.js +149 -17
- package/src/handlers.js +12 -4
- package/src/html.js +99 -6
- package/src/loader.js +311 -18
- package/src/partials.js +20 -7
- package/src/registry-store.js +4 -0
- package/src/router.js +9 -0
- package/src/server.js +26 -6
- package/src/signals.js +16 -3
package/src/loader.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { renderComponent } from "./component.js";
|
|
2
2
|
import { createHandlerRegistry } from "./handlers.js";
|
|
3
|
-
import { createSignalRegistry } from "./signals.js";
|
|
3
|
+
import { createSignalRegistry, isSignalRef } from "./signals.js";
|
|
4
4
|
import { matchAttribute, normalizeAttributeConfig, readAttribute } from "./attributes.js";
|
|
5
5
|
|
|
6
|
+
const inlineBindingPrefix = "__async:inline:";
|
|
7
|
+
|
|
6
8
|
export function AsyncLoader({ root, signals, handlers, server, router, cache, attributes } = {}) {
|
|
7
9
|
const documentRef = root?.ownerDocument ?? root ?? globalThis.document;
|
|
8
10
|
const rootNode = root ?? documentRef;
|
|
@@ -16,6 +18,9 @@ export function AsyncLoader({ root, signals, handlers, server, router, cache, at
|
|
|
16
18
|
const visibleElements = new WeakSet();
|
|
17
19
|
const boundaryState = new WeakMap();
|
|
18
20
|
const renderingBoundaries = new WeakSet();
|
|
21
|
+
const inlineBindings = new Map();
|
|
22
|
+
const scopedCleanups = new WeakMap();
|
|
23
|
+
let inlineBindingCounter = 0;
|
|
19
24
|
let destroyed = false;
|
|
20
25
|
|
|
21
26
|
const api = {
|
|
@@ -36,6 +41,7 @@ export function AsyncLoader({ root, signals, handlers, server, router, cache, at
|
|
|
36
41
|
scan(rootOrFragment = rootNode) {
|
|
37
42
|
assertActive();
|
|
38
43
|
bindSignalAttributes(rootOrFragment);
|
|
44
|
+
bindClassAttributes(rootOrFragment);
|
|
39
45
|
bindEventAttributes(rootOrFragment);
|
|
40
46
|
bindBoundaries(rootOrFragment);
|
|
41
47
|
runPseudoEvents(rootOrFragment);
|
|
@@ -48,6 +54,7 @@ export function AsyncLoader({ root, signals, handlers, server, router, cache, at
|
|
|
48
54
|
if (!boundary) {
|
|
49
55
|
throw new Error(`Boundary "${boundaryId}" was not found.`);
|
|
50
56
|
}
|
|
57
|
+
cleanupChildren(boundary);
|
|
51
58
|
boundary.replaceChildren(toFragment(fragmentOrTemplate, documentRef));
|
|
52
59
|
api.scan(boundary);
|
|
53
60
|
return boundary;
|
|
@@ -64,11 +71,12 @@ export function AsyncLoader({ root, signals, handlers, server, router, cache, at
|
|
|
64
71
|
cache: api.cache,
|
|
65
72
|
attributes: attributeConfig
|
|
66
73
|
});
|
|
74
|
+
cleanupChildren(target);
|
|
67
75
|
target.replaceChildren(toFragment(rendered.html, target.ownerDocument));
|
|
68
76
|
api.scan(target);
|
|
69
77
|
rendered.mount(target);
|
|
70
78
|
rendered.visible(target, api._observeVisible);
|
|
71
|
-
|
|
79
|
+
addCleanup(rendered.cleanup, target, "children");
|
|
72
80
|
return rendered;
|
|
73
81
|
},
|
|
74
82
|
|
|
@@ -78,17 +86,34 @@ export function AsyncLoader({ root, signals, handlers, server, router, cache, at
|
|
|
78
86
|
}
|
|
79
87
|
destroyed = true;
|
|
80
88
|
for (const cleanup of [...cleanups]) {
|
|
81
|
-
cleanup
|
|
89
|
+
runCleanup(cleanup);
|
|
82
90
|
}
|
|
83
91
|
cleanups.clear();
|
|
84
92
|
},
|
|
85
93
|
|
|
86
94
|
_observeVisible(target, fn) {
|
|
87
95
|
return observeVisible(target, fn);
|
|
96
|
+
},
|
|
97
|
+
|
|
98
|
+
_registerBinding(value) {
|
|
99
|
+
const id = `${inlineBindingPrefix}${++inlineBindingCounter}`;
|
|
100
|
+
inlineBindings.set(id, value);
|
|
101
|
+
return id;
|
|
102
|
+
},
|
|
103
|
+
|
|
104
|
+
_releaseBinding(id) {
|
|
105
|
+
inlineBindings.delete(id);
|
|
88
106
|
}
|
|
89
107
|
};
|
|
90
108
|
|
|
91
109
|
signalRegistry._setContext?.({ server: api.server, router: api.router, loader: api, cache: api.cache });
|
|
110
|
+
api.server?._setContext?.({
|
|
111
|
+
signals: signalRegistry,
|
|
112
|
+
handlers: handlerRegistry,
|
|
113
|
+
loader: api,
|
|
114
|
+
router: api.router,
|
|
115
|
+
cache: api.cache
|
|
116
|
+
});
|
|
92
117
|
|
|
93
118
|
function bindEventAttributes(scope) {
|
|
94
119
|
for (const element of elementsIn(scope)) {
|
|
@@ -100,7 +125,7 @@ export function AsyncLoader({ root, signals, handlers, server, router, cache, at
|
|
|
100
125
|
if (!eventName) {
|
|
101
126
|
continue;
|
|
102
127
|
}
|
|
103
|
-
if (eventName === "mount" || eventName === "visible") {
|
|
128
|
+
if (eventName === "attach" || eventName === "mount" || eventName === "visible") {
|
|
104
129
|
continue;
|
|
105
130
|
}
|
|
106
131
|
bindEvent(element, eventName, element.getAttribute(name));
|
|
@@ -137,7 +162,7 @@ export function AsyncLoader({ root, signals, handlers, server, router, cache, at
|
|
|
137
162
|
};
|
|
138
163
|
|
|
139
164
|
element.addEventListener(eventName, listener);
|
|
140
|
-
|
|
165
|
+
addCleanup(() => element.removeEventListener(eventName, listener), element);
|
|
141
166
|
}
|
|
142
167
|
|
|
143
168
|
function bindSignalAttributes(scope) {
|
|
@@ -172,18 +197,71 @@ export function AsyncLoader({ root, signals, handlers, server, router, cache, at
|
|
|
172
197
|
bindSignal(element, `attr:${attr}:${path}`, path, (value) => updateAttribute(element, attr, value));
|
|
173
198
|
continue;
|
|
174
199
|
}
|
|
200
|
+
if (signalName.startsWith("prop:")) {
|
|
201
|
+
const prop = signalName.slice("prop:".length);
|
|
202
|
+
const path = element.getAttribute(name);
|
|
203
|
+
bindSignal(element, `prop:${prop}:${path}`, path, (value) => updateProperty(element, prop, value));
|
|
204
|
+
continue;
|
|
205
|
+
}
|
|
175
206
|
if (signalName.startsWith("class:")) {
|
|
176
207
|
const className = signalName.slice("class:".length);
|
|
177
208
|
const path = element.getAttribute(name);
|
|
178
|
-
|
|
179
|
-
element
|
|
180
|
-
}
|
|
209
|
+
if (className === "" || className === "{}") {
|
|
210
|
+
bindClass(element, className, path);
|
|
211
|
+
} else {
|
|
212
|
+
bindSignal(element, `class:${className}:${path}`, path, (value) => {
|
|
213
|
+
element.classList.toggle(className, Boolean(value));
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
continue;
|
|
217
|
+
}
|
|
218
|
+
if (signalName === "class") {
|
|
219
|
+
const path = element.getAttribute(name);
|
|
220
|
+
bindClass(element, "{}", path);
|
|
181
221
|
}
|
|
182
222
|
}
|
|
183
223
|
}
|
|
184
224
|
}
|
|
185
225
|
|
|
186
|
-
function
|
|
226
|
+
function bindClassAttributes(scope) {
|
|
227
|
+
for (const element of elementsIn(scope)) {
|
|
228
|
+
for (const name of element.getAttributeNames?.() ?? []) {
|
|
229
|
+
const className = matchAttribute(name, attributeConfig, "class");
|
|
230
|
+
if (className == null) {
|
|
231
|
+
continue;
|
|
232
|
+
}
|
|
233
|
+
bindClass(element, className, element.getAttribute(name));
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function bindClass(element, className, path) {
|
|
239
|
+
if (className === "" || className === "{}") {
|
|
240
|
+
const staticClasses = readClassTokens(element);
|
|
241
|
+
let previous = new Set();
|
|
242
|
+
bindSignal(element, `class:{}:${path}`, path, (value) => {
|
|
243
|
+
const next = normalizeClassTokens(value);
|
|
244
|
+
const current = readClassTokens(element);
|
|
245
|
+
for (const token of previous) {
|
|
246
|
+
if (!next.has(token) && !staticClasses.has(token)) {
|
|
247
|
+
current.delete(token);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
for (const token of next) {
|
|
251
|
+
current.add(token);
|
|
252
|
+
}
|
|
253
|
+
writeClassTokens(element, current);
|
|
254
|
+
previous = next;
|
|
255
|
+
}, { rawInline: true });
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
bindSignal(element, `class:${className}:${path}`, path, (value) => {
|
|
260
|
+
updateClassToken(element, className, Boolean(value));
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function bindSignal(element, key, path, apply, options = {}) {
|
|
187
265
|
const bound = signalBindings.get(element) ?? new Set();
|
|
188
266
|
if (bound.has(key)) {
|
|
189
267
|
return;
|
|
@@ -191,8 +269,9 @@ export function AsyncLoader({ root, signals, handlers, server, router, cache, at
|
|
|
191
269
|
bound.add(key);
|
|
192
270
|
signalBindings.set(element, bound);
|
|
193
271
|
|
|
194
|
-
|
|
195
|
-
|
|
272
|
+
const read = () => readBinding(path, options);
|
|
273
|
+
apply(read());
|
|
274
|
+
addCleanup(subscribeBinding(path, () => apply(read())), element);
|
|
196
275
|
}
|
|
197
276
|
|
|
198
277
|
function bindValueWriter(element, path) {
|
|
@@ -200,11 +279,42 @@ export function AsyncLoader({ root, signals, handlers, server, router, cache, at
|
|
|
200
279
|
bindEvent(element, "change", `__async:set:${path}`);
|
|
201
280
|
if (!handlerRegistry.resolve(`__async:set:${path}`)) {
|
|
202
281
|
handlerRegistry.register(`__async:set:${path}`, function writeValue({ element }) {
|
|
203
|
-
|
|
282
|
+
writeBinding(path, element.value);
|
|
204
283
|
});
|
|
205
284
|
}
|
|
206
285
|
}
|
|
207
286
|
|
|
287
|
+
function readBinding(path, options = {}) {
|
|
288
|
+
if (isInlineBinding(path)) {
|
|
289
|
+
const value = inlineBindings.get(path);
|
|
290
|
+
return options.rawInline ? value : resolveInlineValue(value);
|
|
291
|
+
}
|
|
292
|
+
return signalRegistry.get(path);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
function writeBinding(path, value) {
|
|
296
|
+
if (!isInlineBinding(path)) {
|
|
297
|
+
return signalRegistry.set(path, value);
|
|
298
|
+
}
|
|
299
|
+
const binding = inlineBindings.get(path);
|
|
300
|
+
if (isSignalRef(binding)) {
|
|
301
|
+
return binding.set(value);
|
|
302
|
+
}
|
|
303
|
+
throw new Error(`Inline binding "${path}" is not writable.`);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
function subscribeBinding(path, fn) {
|
|
307
|
+
if (!isInlineBinding(path)) {
|
|
308
|
+
return signalRegistry.subscribe(path, fn);
|
|
309
|
+
}
|
|
310
|
+
const cleanups = collectSignalRefs(inlineBindings.get(path)).map((ref) => ref.subscribe(fn));
|
|
311
|
+
return () => {
|
|
312
|
+
for (const cleanup of cleanups) {
|
|
313
|
+
cleanup();
|
|
314
|
+
}
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
|
|
208
318
|
function bindBoundaries(scope) {
|
|
209
319
|
for (const boundary of elementsIn(scope)) {
|
|
210
320
|
if (renderingBoundaries.has(boundary)) {
|
|
@@ -225,7 +335,7 @@ export function AsyncLoader({ root, signals, handlers, server, router, cache, at
|
|
|
225
335
|
cleanup: signalRegistry.subscribe(`${id}.$status`, () => renderBoundary(boundary))
|
|
226
336
|
};
|
|
227
337
|
boundaryState.set(boundary, state);
|
|
228
|
-
|
|
338
|
+
addCleanup(state.cleanup, boundary);
|
|
229
339
|
}
|
|
230
340
|
renderBoundary(boundary);
|
|
231
341
|
}
|
|
@@ -241,6 +351,7 @@ export function AsyncLoader({ root, signals, handlers, server, router, cache, at
|
|
|
241
351
|
if (!template) {
|
|
242
352
|
return;
|
|
243
353
|
}
|
|
354
|
+
cleanupChildren(boundary);
|
|
244
355
|
boundary.replaceChildren(template.content.cloneNode(true));
|
|
245
356
|
renderingBoundaries.add(boundary);
|
|
246
357
|
try {
|
|
@@ -252,15 +363,17 @@ export function AsyncLoader({ root, signals, handlers, server, router, cache, at
|
|
|
252
363
|
|
|
253
364
|
function runPseudoEvents(scope) {
|
|
254
365
|
for (const element of elementsIn(scope)) {
|
|
255
|
-
const
|
|
256
|
-
if (
|
|
366
|
+
const refs = readPseudoRefs(element, ["attach", "mount"]);
|
|
367
|
+
if (refs.length === 0) {
|
|
257
368
|
continue;
|
|
258
369
|
}
|
|
259
370
|
if (mountedElements.has(element)) {
|
|
260
371
|
continue;
|
|
261
372
|
}
|
|
262
373
|
mountedElements.add(element);
|
|
263
|
-
|
|
374
|
+
for (const ref of refs) {
|
|
375
|
+
runPseudo(element, ref);
|
|
376
|
+
}
|
|
264
377
|
}
|
|
265
378
|
|
|
266
379
|
for (const element of elementsIn(scope)) {
|
|
@@ -272,10 +385,21 @@ export function AsyncLoader({ root, signals, handlers, server, router, cache, at
|
|
|
272
385
|
continue;
|
|
273
386
|
}
|
|
274
387
|
visibleElements.add(element);
|
|
275
|
-
|
|
388
|
+
addCleanup(observeVisible(element, () => runPseudo(element, ref)), element);
|
|
276
389
|
}
|
|
277
390
|
}
|
|
278
391
|
|
|
392
|
+
function readPseudoRefs(element, names) {
|
|
393
|
+
const refs = [];
|
|
394
|
+
for (const name of names) {
|
|
395
|
+
const ref = readAttribute(element, attributeConfig, "on", name);
|
|
396
|
+
if (ref != null) {
|
|
397
|
+
refs.push(ref);
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
return refs;
|
|
401
|
+
}
|
|
402
|
+
|
|
279
403
|
async function runPseudo(element, ref) {
|
|
280
404
|
try {
|
|
281
405
|
const results = await handlerRegistry.run(ref, {
|
|
@@ -291,7 +415,7 @@ export function AsyncLoader({ root, signals, handlers, server, router, cache, at
|
|
|
291
415
|
});
|
|
292
416
|
for (const result of results) {
|
|
293
417
|
if (typeof result === "function") {
|
|
294
|
-
|
|
418
|
+
addCleanup(result, element);
|
|
295
419
|
}
|
|
296
420
|
}
|
|
297
421
|
} catch (error) {
|
|
@@ -327,9 +451,170 @@ export function AsyncLoader({ root, signals, handlers, server, router, cache, at
|
|
|
327
451
|
}
|
|
328
452
|
}
|
|
329
453
|
|
|
454
|
+
function addCleanup(cleanup, owner, mode = "self") {
|
|
455
|
+
if (typeof cleanup !== "function") {
|
|
456
|
+
return cleanup;
|
|
457
|
+
}
|
|
458
|
+
cleanups.add(cleanup);
|
|
459
|
+
if (owner) {
|
|
460
|
+
const records = scopedCleanups.get(owner) ?? [];
|
|
461
|
+
records.push({ cleanup, mode });
|
|
462
|
+
scopedCleanups.set(owner, records);
|
|
463
|
+
}
|
|
464
|
+
return cleanup;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
function runCleanup(cleanup) {
|
|
468
|
+
if (typeof cleanup !== "function" || !cleanups.has(cleanup)) {
|
|
469
|
+
return;
|
|
470
|
+
}
|
|
471
|
+
cleanups.delete(cleanup);
|
|
472
|
+
cleanup();
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
function cleanupChildren(container) {
|
|
476
|
+
runScopedCleanups(container, "children");
|
|
477
|
+
for (const child of [...(container.childNodes ?? [])]) {
|
|
478
|
+
cleanupNode(child);
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
function cleanupNode(node) {
|
|
483
|
+
if (node.nodeType !== 1) {
|
|
484
|
+
return;
|
|
485
|
+
}
|
|
486
|
+
for (const element of elementsIn(node)) {
|
|
487
|
+
runScopedCleanups(element);
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
function runScopedCleanups(element, mode) {
|
|
492
|
+
const records = scopedCleanups.get(element);
|
|
493
|
+
if (!records) {
|
|
494
|
+
return;
|
|
495
|
+
}
|
|
496
|
+
const remaining = [];
|
|
497
|
+
for (const record of records) {
|
|
498
|
+
if (mode && record.mode !== mode) {
|
|
499
|
+
remaining.push(record);
|
|
500
|
+
continue;
|
|
501
|
+
}
|
|
502
|
+
runCleanup(record.cleanup);
|
|
503
|
+
}
|
|
504
|
+
if (remaining.length > 0) {
|
|
505
|
+
scopedCleanups.set(element, remaining);
|
|
506
|
+
} else {
|
|
507
|
+
scopedCleanups.delete(element);
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
|
|
330
511
|
return api;
|
|
331
512
|
}
|
|
332
513
|
|
|
514
|
+
function normalizeClassTokens(value, tokens = new Set()) {
|
|
515
|
+
if (value == null || value === false) {
|
|
516
|
+
return tokens;
|
|
517
|
+
}
|
|
518
|
+
if (isSignalRef(value)) {
|
|
519
|
+
const signalValue = value.value;
|
|
520
|
+
if (signalValue === true) {
|
|
521
|
+
tokens.add(signalClassName(value.id));
|
|
522
|
+
return tokens;
|
|
523
|
+
}
|
|
524
|
+
return normalizeClassTokens(signalValue, tokens);
|
|
525
|
+
}
|
|
526
|
+
if (typeof value === "string") {
|
|
527
|
+
for (const token of value.split(/\s+/).filter(Boolean)) {
|
|
528
|
+
tokens.add(token);
|
|
529
|
+
}
|
|
530
|
+
return tokens;
|
|
531
|
+
}
|
|
532
|
+
if (Array.isArray(value)) {
|
|
533
|
+
for (const item of value) {
|
|
534
|
+
normalizeClassTokens(item, tokens);
|
|
535
|
+
}
|
|
536
|
+
return tokens;
|
|
537
|
+
}
|
|
538
|
+
if (typeof value === "object") {
|
|
539
|
+
for (const [token, enabled] of Object.entries(value)) {
|
|
540
|
+
const value = isSignalRef(enabled) ? enabled.value : enabled;
|
|
541
|
+
if (value) {
|
|
542
|
+
normalizeClassTokens(token, tokens);
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
return tokens;
|
|
546
|
+
}
|
|
547
|
+
if (value !== true) {
|
|
548
|
+
tokens.add(String(value));
|
|
549
|
+
}
|
|
550
|
+
return tokens;
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
function resolveInlineValue(value) {
|
|
554
|
+
if (isSignalRef(value)) {
|
|
555
|
+
return value.value;
|
|
556
|
+
}
|
|
557
|
+
if (Array.isArray(value)) {
|
|
558
|
+
return value.map(resolveInlineValue);
|
|
559
|
+
}
|
|
560
|
+
if (value && typeof value === "object") {
|
|
561
|
+
return Object.fromEntries(Object.entries(value).map(([key, entry]) => [key, resolveInlineValue(entry)]));
|
|
562
|
+
}
|
|
563
|
+
return value;
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
function collectSignalRefs(value, refs = new Map()) {
|
|
567
|
+
if (isSignalRef(value)) {
|
|
568
|
+
refs.set(value.id, value);
|
|
569
|
+
return [...refs.values()];
|
|
570
|
+
}
|
|
571
|
+
if (Array.isArray(value)) {
|
|
572
|
+
for (const item of value) {
|
|
573
|
+
collectSignalRefs(item, refs);
|
|
574
|
+
}
|
|
575
|
+
return [...refs.values()];
|
|
576
|
+
}
|
|
577
|
+
if (value && typeof value === "object") {
|
|
578
|
+
for (const item of Object.values(value)) {
|
|
579
|
+
collectSignalRefs(item, refs);
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
return [...refs.values()];
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
function isInlineBinding(value) {
|
|
586
|
+
return typeof value === "string" && value.startsWith(inlineBindingPrefix);
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
function signalClassName(id) {
|
|
590
|
+
return id.split(".").at(-1);
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
function updateClassToken(element, className, enabled) {
|
|
594
|
+
const tokens = readClassTokens(element);
|
|
595
|
+
for (const token of normalizeClassTokens(className)) {
|
|
596
|
+
if (enabled) {
|
|
597
|
+
tokens.add(token);
|
|
598
|
+
} else {
|
|
599
|
+
tokens.delete(token);
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
writeClassTokens(element, tokens);
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
function readClassTokens(element) {
|
|
606
|
+
return normalizeClassTokens(element.getAttribute("class") ?? "");
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
function writeClassTokens(element, tokens) {
|
|
610
|
+
const value = [...tokens].join(" ");
|
|
611
|
+
if (value.length === 0) {
|
|
612
|
+
element.removeAttribute("class");
|
|
613
|
+
return;
|
|
614
|
+
}
|
|
615
|
+
element.setAttribute("class", value);
|
|
616
|
+
}
|
|
617
|
+
|
|
333
618
|
function collectBoundaryTemplates(boundary, id, attributeConfig) {
|
|
334
619
|
const templates = {};
|
|
335
620
|
for (const template of [...boundary.children].filter((child) => child.tagName === "TEMPLATE")) {
|
|
@@ -370,6 +655,14 @@ function updateAttribute(element, attr, value) {
|
|
|
370
655
|
}
|
|
371
656
|
}
|
|
372
657
|
|
|
658
|
+
function updateProperty(element, prop, value) {
|
|
659
|
+
if (value == null) {
|
|
660
|
+
element[prop] = "";
|
|
661
|
+
return;
|
|
662
|
+
}
|
|
663
|
+
element[prop] = value;
|
|
664
|
+
}
|
|
665
|
+
|
|
373
666
|
function selectAll(scope, selector) {
|
|
374
667
|
const elements = [];
|
|
375
668
|
if (scope?.nodeType === 1 && scope.matches?.(selector)) {
|
package/src/partials.js
CHANGED
|
@@ -26,6 +26,11 @@ export function createPartialRegistry(initialMap = {}, options = {}) {
|
|
|
26
26
|
return registry;
|
|
27
27
|
},
|
|
28
28
|
|
|
29
|
+
unregister(id) {
|
|
30
|
+
assertId(id);
|
|
31
|
+
return entries.delete(id);
|
|
32
|
+
},
|
|
33
|
+
|
|
29
34
|
resolve(id) {
|
|
30
35
|
assertId(id);
|
|
31
36
|
return entries.get(id);
|
|
@@ -46,7 +51,7 @@ export function createPartialRegistry(initialMap = {}, options = {}) {
|
|
|
46
51
|
partials: registry
|
|
47
52
|
};
|
|
48
53
|
const result = await fn.call(partialContext, props);
|
|
49
|
-
return normalizePartialResult(result);
|
|
54
|
+
return normalizePartialResult(result, partialContext);
|
|
50
55
|
},
|
|
51
56
|
|
|
52
57
|
_adoptMany() {
|
|
@@ -58,18 +63,18 @@ export function createPartialRegistry(initialMap = {}, options = {}) {
|
|
|
58
63
|
return registry;
|
|
59
64
|
}
|
|
60
65
|
|
|
61
|
-
export function normalizePartialResult(result) {
|
|
66
|
+
export function normalizePartialResult(result, context = {}) {
|
|
62
67
|
if (isPartialEnvelope(result)) {
|
|
63
68
|
return {
|
|
64
69
|
...result,
|
|
65
|
-
html: Object.hasOwn(result, "html") ? renderPartialValue(result.html) : result.html
|
|
70
|
+
html: Object.hasOwn(result, "html") ? renderPartialValue(result.html, context) : result.html
|
|
66
71
|
};
|
|
67
72
|
}
|
|
68
73
|
|
|
69
|
-
return { html: renderPartialValue(result) };
|
|
74
|
+
return { html: renderPartialValue(result, context) };
|
|
70
75
|
}
|
|
71
76
|
|
|
72
|
-
function renderPartialValue(value) {
|
|
77
|
+
function renderPartialValue(value, context) {
|
|
73
78
|
if (value?.nodeType) {
|
|
74
79
|
return value;
|
|
75
80
|
}
|
|
@@ -77,9 +82,17 @@ function renderPartialValue(value) {
|
|
|
77
82
|
return value;
|
|
78
83
|
}
|
|
79
84
|
if (isTemplateResult(value)) {
|
|
80
|
-
return renderTemplate(value);
|
|
85
|
+
return renderTemplate(value, templateRenderOptions(context));
|
|
81
86
|
}
|
|
82
|
-
return renderTemplate(value);
|
|
87
|
+
return renderTemplate(value, templateRenderOptions(context));
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function templateRenderOptions(context) {
|
|
91
|
+
return {
|
|
92
|
+
attributes: context.loader?.attributes,
|
|
93
|
+
signals: context.signals,
|
|
94
|
+
bind: context.loader?._registerBinding?.bind(context.loader)
|
|
95
|
+
};
|
|
83
96
|
}
|
|
84
97
|
|
|
85
98
|
function isPartialEnvelope(value) {
|
package/src/registry-store.js
CHANGED
package/src/router.js
CHANGED
|
@@ -41,6 +41,15 @@ export function createRouteRegistry(initialMap = {}, options = {}) {
|
|
|
41
41
|
return registry;
|
|
42
42
|
},
|
|
43
43
|
|
|
44
|
+
unregister(pattern) {
|
|
45
|
+
assertPattern(pattern);
|
|
46
|
+
const index = routes.findIndex((candidate) => candidate.pattern === pattern);
|
|
47
|
+
if (index !== -1) {
|
|
48
|
+
routes.splice(index, 1);
|
|
49
|
+
}
|
|
50
|
+
return entries.delete(pattern);
|
|
51
|
+
},
|
|
52
|
+
|
|
44
53
|
match(url) {
|
|
45
54
|
const path = toUrl(url).pathname;
|
|
46
55
|
for (const candidate of routes) {
|
package/src/server.js
CHANGED
|
@@ -28,6 +28,11 @@ export function createServerRegistry(initialMap = {}, options = {}) {
|
|
|
28
28
|
return registry;
|
|
29
29
|
},
|
|
30
30
|
|
|
31
|
+
unregister(id) {
|
|
32
|
+
assertServerId(id);
|
|
33
|
+
return entries.delete(id);
|
|
34
|
+
},
|
|
35
|
+
|
|
31
36
|
resolve(id) {
|
|
32
37
|
assertServerId(id);
|
|
33
38
|
return entries.get(id);
|
|
@@ -43,7 +48,7 @@ export function createServerRegistry(initialMap = {}, options = {}) {
|
|
|
43
48
|
let runContext;
|
|
44
49
|
const server = createServerNamespace((childId, childArgs, childContext = {}) => {
|
|
45
50
|
return registry.run(childId, childArgs, { ...runContext, ...childContext });
|
|
46
|
-
});
|
|
51
|
+
}, {}, () => runContext);
|
|
47
52
|
|
|
48
53
|
const mergedContext = {
|
|
49
54
|
...defaults,
|
|
@@ -76,7 +81,7 @@ export function createServerRegistry(initialMap = {}, options = {}) {
|
|
|
76
81
|
}, registryStore, type);
|
|
77
82
|
|
|
78
83
|
registry.registerMany(initialMap);
|
|
79
|
-
return createServerNamespace((id, args, context) => registry.run(id, args, context), registry);
|
|
84
|
+
return createServerNamespace((id, args, context) => registry.run(id, args, context), registry, () => defaults);
|
|
80
85
|
}
|
|
81
86
|
|
|
82
87
|
export function createServerProxy({
|
|
@@ -127,7 +132,7 @@ export function createServerProxy({
|
|
|
127
132
|
_setContext(context = {}) {
|
|
128
133
|
Object.assign(defaults, context);
|
|
129
134
|
}
|
|
130
|
-
});
|
|
135
|
+
}, () => defaults);
|
|
131
136
|
}
|
|
132
137
|
|
|
133
138
|
export function resolveServerCommandArguments(args, context = {}) {
|
|
@@ -205,7 +210,7 @@ export function defaultInput(context = {}) {
|
|
|
205
210
|
};
|
|
206
211
|
}
|
|
207
212
|
|
|
208
|
-
function createServerNamespace(run, root = {}) {
|
|
213
|
+
function createServerNamespace(run, root = {}, contextProvider = () => ({})) {
|
|
209
214
|
const cache = new Map();
|
|
210
215
|
|
|
211
216
|
function namespace(parts) {
|
|
@@ -214,11 +219,14 @@ function createServerNamespace(run, root = {}) {
|
|
|
214
219
|
return cache.get(cacheKey);
|
|
215
220
|
}
|
|
216
221
|
|
|
217
|
-
const callable = (...args) => {
|
|
222
|
+
const callable = async (...args) => {
|
|
218
223
|
if (parts.length === 0) {
|
|
219
224
|
throw new Error("Server namespace is not directly callable.");
|
|
220
225
|
}
|
|
221
|
-
|
|
226
|
+
const context = contextProvider() ?? {};
|
|
227
|
+
const result = await run(parts.join("."), args, context);
|
|
228
|
+
await applyServerResult(result, context);
|
|
229
|
+
return unwrapServerResult(result);
|
|
222
230
|
};
|
|
223
231
|
|
|
224
232
|
const proxy = new Proxy(callable, {
|
|
@@ -229,6 +237,18 @@ function createServerNamespace(run, root = {}) {
|
|
|
229
237
|
if (prop in _target) {
|
|
230
238
|
return _target[prop];
|
|
231
239
|
}
|
|
240
|
+
if (parts.length === 0 && prop === "_withContext") {
|
|
241
|
+
return (context = {}) => createServerNamespace(run, root, () => ({
|
|
242
|
+
...(contextProvider() ?? {}),
|
|
243
|
+
...context
|
|
244
|
+
}));
|
|
245
|
+
}
|
|
246
|
+
if (parts.length === 0 && prop === "run" && typeof root.run === "function") {
|
|
247
|
+
return (id, args = [], context = {}) => root.run(id, args, {
|
|
248
|
+
...(contextProvider() ?? {}),
|
|
249
|
+
...context
|
|
250
|
+
});
|
|
251
|
+
}
|
|
232
252
|
if (parts.length === 0 && Object.hasOwn(root, prop)) {
|
|
233
253
|
return root[prop];
|
|
234
254
|
}
|
package/src/signals.js
CHANGED
|
@@ -121,7 +121,7 @@ export function createSignalRegistry(initialMap = {}, options = {}) {
|
|
|
121
121
|
const registryStore = options.registry ?? createRegistryStore();
|
|
122
122
|
const type = options.type ?? "signal";
|
|
123
123
|
const entries = registryStore._map(type);
|
|
124
|
-
const registryCleanups = new
|
|
124
|
+
const registryCleanups = new Map();
|
|
125
125
|
const runtimeContext = {};
|
|
126
126
|
const boundEntries = new Set();
|
|
127
127
|
|
|
@@ -144,6 +144,19 @@ export function createSignalRegistry(initialMap = {}, options = {}) {
|
|
|
144
144
|
return registry;
|
|
145
145
|
},
|
|
146
146
|
|
|
147
|
+
unregister(id) {
|
|
148
|
+
assertId(id);
|
|
149
|
+
if (!entries.has(id)) {
|
|
150
|
+
return false;
|
|
151
|
+
}
|
|
152
|
+
registryCleanups.get(id)?.();
|
|
153
|
+
registryCleanups.delete(id);
|
|
154
|
+
entries.get(id)?._dispose?.();
|
|
155
|
+
entries.delete(id);
|
|
156
|
+
boundEntries.delete(id);
|
|
157
|
+
return true;
|
|
158
|
+
},
|
|
159
|
+
|
|
147
160
|
ensure(id, initial) {
|
|
148
161
|
assertId(id);
|
|
149
162
|
if (!entries.has(id)) {
|
|
@@ -256,7 +269,7 @@ export function createSignalRegistry(initialMap = {}, options = {}) {
|
|
|
256
269
|
},
|
|
257
270
|
|
|
258
271
|
destroy() {
|
|
259
|
-
for (const cleanup of registryCleanups) {
|
|
272
|
+
for (const cleanup of registryCleanups.values()) {
|
|
260
273
|
cleanup();
|
|
261
274
|
}
|
|
262
275
|
registryCleanups.clear();
|
|
@@ -313,7 +326,7 @@ export function createSignalRegistry(initialMap = {}, options = {}) {
|
|
|
313
326
|
boundEntries.add(id);
|
|
314
327
|
const cleanup = entry._bindRegistry(registry, id);
|
|
315
328
|
if (typeof cleanup === "function") {
|
|
316
|
-
registryCleanups.
|
|
329
|
+
registryCleanups.set(id, cleanup);
|
|
317
330
|
}
|
|
318
331
|
}
|
|
319
332
|
}
|