solid-tag-runtime 0.0.7 → 0.0.9
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/ARCHITECTURE.md +166 -1
- package/README.md +272 -15
- package/html.d.ts +166 -63
- package/index.d.ts +42 -1
- package/package.json +1 -1
- package/src/events.js +63 -0
- package/src/html.js +1061 -53
- package/src/render.js +205 -0
- package/src/runtime.js +198 -94
package/src/html.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { SolidTagRuntimeError } from "./errors.js";
|
|
2
|
+
import { createEventDispatcher } from "./events.js";
|
|
3
|
+
import { HTMLRenderError, createDefaultHTMLRenderAdapter, resolveRenderComponent } from "./render.js";
|
|
2
4
|
|
|
3
5
|
const DEFAULT_SELECTOR = [
|
|
4
6
|
'script[type="solid-jsx"]',
|
|
@@ -12,6 +14,9 @@ const DEFAULT_SELECTOR = [
|
|
|
12
14
|
const RUNTIME_SCOPE_ATTRIBUTE = "data-solid-runtime";
|
|
13
15
|
const elementOwners = new WeakMap();
|
|
14
16
|
const defaultControllers = new WeakMap();
|
|
17
|
+
const activeControllers = new Set();
|
|
18
|
+
const solidRenderClasses = new WeakMap();
|
|
19
|
+
const solidRenderInternals = new WeakMap();
|
|
15
20
|
let anonymousSequence = 0;
|
|
16
21
|
let controllerSequence = 0;
|
|
17
22
|
|
|
@@ -34,6 +39,8 @@ export class HTMLModuleOwnershipError extends HTMLModuleError {
|
|
|
34
39
|
}
|
|
35
40
|
}
|
|
36
41
|
|
|
42
|
+
export { HTMLRenderError };
|
|
43
|
+
|
|
37
44
|
/**
|
|
38
45
|
* Create a persistent DOM/HTML adapter for one solid-tag-runtime instance.
|
|
39
46
|
*
|
|
@@ -47,18 +54,27 @@ export function createHTMLRuntime(runtime, options = {}) {
|
|
|
47
54
|
const controller = {
|
|
48
55
|
id: `solid-tag-html-${++controllerSequence}`,
|
|
49
56
|
runtime,
|
|
57
|
+
events: createEventDispatcher({
|
|
58
|
+
onListenerError(error) {
|
|
59
|
+
globalThis.console?.error?.("solid-tag-runtime/html lifecycle subscriber error", error);
|
|
60
|
+
},
|
|
61
|
+
}),
|
|
50
62
|
scope: normalizeScope(options.scope),
|
|
51
63
|
root: options.root,
|
|
52
64
|
selector: options.selector ?? DEFAULT_SELECTOR,
|
|
53
65
|
acceptUnscoped: options.acceptUnscoped !== false,
|
|
54
66
|
executeEntries: options.executeEntries !== false,
|
|
67
|
+
executeRenders: options.executeRenders !== false,
|
|
55
68
|
fetch: options.fetch,
|
|
56
69
|
baseUrl: options.baseUrl,
|
|
57
70
|
MutationObserver: options.MutationObserver,
|
|
58
71
|
subtree: options.subtree !== false,
|
|
59
72
|
createElement: options.createElement,
|
|
60
73
|
appendTo: options.appendTo,
|
|
74
|
+
renderAdapter: options.renderAdapter ?? createDefaultHTMLRenderAdapter(runtime),
|
|
61
75
|
bindings: new Map(),
|
|
76
|
+
renderElements: new Set(),
|
|
77
|
+
disposed: false,
|
|
62
78
|
observer: undefined,
|
|
63
79
|
observerRoot: undefined,
|
|
64
80
|
observerOptions: undefined,
|
|
@@ -88,6 +104,7 @@ export function createHTMLRuntime(runtime, options = {}) {
|
|
|
88
104
|
get appendTarget() {
|
|
89
105
|
return getAppendTarget(controller);
|
|
90
106
|
},
|
|
107
|
+
subscribe,
|
|
91
108
|
register,
|
|
92
109
|
observe,
|
|
93
110
|
flush,
|
|
@@ -104,25 +121,58 @@ export function createHTMLRuntime(runtime, options = {}) {
|
|
|
104
121
|
owns,
|
|
105
122
|
getModuleId,
|
|
106
123
|
getElement,
|
|
124
|
+
dispose,
|
|
107
125
|
};
|
|
108
126
|
|
|
109
127
|
controller.api = api;
|
|
128
|
+
activeControllers.add(controller);
|
|
129
|
+
// The HTML adapter owns the global custom-element registration. Registering
|
|
130
|
+
// here means a controller is already available before any existing
|
|
131
|
+
// <solid-render> elements are upgraded/connected. The helper is idempotent.
|
|
132
|
+
registerSolidRenderElement();
|
|
133
|
+
controller.runtimeDisposeUnsubscribe = runtime.subscribe?.("runtime-disposed", () => {
|
|
134
|
+
void disposeController(controller, { runtimeDisposed: true });
|
|
135
|
+
});
|
|
110
136
|
return api;
|
|
111
137
|
|
|
138
|
+
function subscribe(typesOrListener, maybeListener) {
|
|
139
|
+
return controller.events.subscribe(typesOrListener, maybeListener);
|
|
140
|
+
}
|
|
141
|
+
|
|
112
142
|
/** One-shot scan of matching scripts. */
|
|
113
143
|
async function register(callOptions = {}) {
|
|
114
144
|
const root = getRoot(mergeOptions(controller, callOptions), "HTML runtime register");
|
|
115
145
|
const selector = callOptions.selector ?? controller.selector;
|
|
116
|
-
const
|
|
117
|
-
|
|
118
|
-
|
|
146
|
+
const origin = callOptions.origin ?? "initial-scan";
|
|
147
|
+
const discovered = Array.from(root.querySelectorAll(selector));
|
|
148
|
+
const elements = [];
|
|
149
|
+
|
|
150
|
+
for (const element of discovered) {
|
|
151
|
+
const accepted = acceptsDiscoveredElement(controller, element, callOptions);
|
|
152
|
+
emitHTML(controller, {
|
|
153
|
+
type: "element-discovered",
|
|
154
|
+
element,
|
|
155
|
+
moduleId: nonEmpty(element?.getAttribute?.("module")),
|
|
156
|
+
origin,
|
|
157
|
+
accepted,
|
|
158
|
+
});
|
|
159
|
+
if (accepted) elements.push(element);
|
|
160
|
+
}
|
|
119
161
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
162
|
+
try {
|
|
163
|
+
const result = await processElements(controller, elements, {
|
|
164
|
+
...callOptions,
|
|
165
|
+
root,
|
|
166
|
+
origin,
|
|
167
|
+
executeEntries: callOptions.executeEntries ?? controller.executeEntries,
|
|
168
|
+
executeRenders: callOptions.executeRenders ?? controller.executeRenders,
|
|
169
|
+
});
|
|
170
|
+
registerSolidRenderElement();
|
|
171
|
+
return result;
|
|
172
|
+
} catch (error) {
|
|
173
|
+
emitHTMLError(controller, "register", error, { origin });
|
|
174
|
+
throw error;
|
|
175
|
+
}
|
|
126
176
|
}
|
|
127
177
|
|
|
128
178
|
/**
|
|
@@ -140,6 +190,7 @@ export function createHTMLRuntime(runtime, options = {}) {
|
|
|
140
190
|
registerExisting: callOptions.registerExisting !== false,
|
|
141
191
|
initial: true,
|
|
142
192
|
});
|
|
193
|
+
registerSolidRenderElement();
|
|
143
194
|
} catch (error) {
|
|
144
195
|
disconnect();
|
|
145
196
|
throw error;
|
|
@@ -166,6 +217,10 @@ export function createHTMLRuntime(runtime, options = {}) {
|
|
|
166
217
|
stopObserver(controller);
|
|
167
218
|
}
|
|
168
219
|
|
|
220
|
+
async function dispose() {
|
|
221
|
+
await disposeController(controller, { runtimeDisposed: false });
|
|
222
|
+
}
|
|
223
|
+
|
|
169
224
|
/**
|
|
170
225
|
* Change the discovery/observation root. If observation is active, it is
|
|
171
226
|
* rebound to the new root and existing matching scripts are registered by
|
|
@@ -174,6 +229,15 @@ export function createHTMLRuntime(runtime, options = {}) {
|
|
|
174
229
|
*/
|
|
175
230
|
async function setRoot(root, callOptions = {}) {
|
|
176
231
|
const nextRoot = getRoot({ root }, "HTML runtime setRoot");
|
|
232
|
+
const previousRoot = controller.root ?? globalThis.document;
|
|
233
|
+
const previousAppendTarget = getAppendTarget(controller);
|
|
234
|
+
emitHTML(controller, {
|
|
235
|
+
type: "root-changing",
|
|
236
|
+
previousRoot,
|
|
237
|
+
root: nextRoot,
|
|
238
|
+
connected: controller.connected,
|
|
239
|
+
registerExisting: callOptions.registerExisting ?? controller.connected,
|
|
240
|
+
});
|
|
177
241
|
|
|
178
242
|
// Finish work scheduled against the old root before rebinding observer state.
|
|
179
243
|
await controller.queue;
|
|
@@ -186,7 +250,7 @@ export function createHTMLRuntime(runtime, options = {}) {
|
|
|
186
250
|
const wasConnected = controller.connected;
|
|
187
251
|
const previousObserverOptions = controller.observerOptions ?? {};
|
|
188
252
|
|
|
189
|
-
if (wasConnected) stopObserver(controller, { preserveOptions: true });
|
|
253
|
+
if (wasConnected) stopObserver(controller, { preserveOptions: true, reason: "root-change" });
|
|
190
254
|
|
|
191
255
|
controller.root = nextRoot;
|
|
192
256
|
if (Object.prototype.hasOwnProperty.call(callOptions, "appendTo")) {
|
|
@@ -204,10 +268,13 @@ export function createHTMLRuntime(runtime, options = {}) {
|
|
|
204
268
|
};
|
|
205
269
|
controller.observerOptions = { ...observeOptions };
|
|
206
270
|
try {
|
|
207
|
-
|
|
271
|
+
const result = await connectObserver(controller, nextRoot, observeOptions, {
|
|
208
272
|
registerExisting,
|
|
209
273
|
initial: false,
|
|
274
|
+
reason: "root-change",
|
|
210
275
|
});
|
|
276
|
+
emitRootChanged(controller, previousRoot, nextRoot, registerExisting, previousAppendTarget);
|
|
277
|
+
return result;
|
|
211
278
|
} catch (error) {
|
|
212
279
|
stopObserver(controller, { preserveOptions: true });
|
|
213
280
|
throw error;
|
|
@@ -215,9 +282,12 @@ export function createHTMLRuntime(runtime, options = {}) {
|
|
|
215
282
|
}
|
|
216
283
|
|
|
217
284
|
if (registerExisting) {
|
|
218
|
-
|
|
285
|
+
const result = await register({ ...callOptions, root: nextRoot, origin: callOptions.origin ?? "root-change-scan" });
|
|
286
|
+
emitRootChanged(controller, previousRoot, nextRoot, registerExisting, previousAppendTarget);
|
|
287
|
+
return result;
|
|
219
288
|
}
|
|
220
289
|
|
|
290
|
+
emitRootChanged(controller, previousRoot, nextRoot, registerExisting, previousAppendTarget);
|
|
221
291
|
return emptyRegistrationResult();
|
|
222
292
|
}
|
|
223
293
|
|
|
@@ -235,7 +305,15 @@ export function createHTMLRuntime(runtime, options = {}) {
|
|
|
235
305
|
if (!target || (typeof target.append !== "function" && typeof target.appendChild !== "function")) {
|
|
236
306
|
throw new TypeError("setAppendTarget() requires a target with append() or appendChild().");
|
|
237
307
|
}
|
|
308
|
+
const previousTarget = getAppendTarget(controller);
|
|
238
309
|
controller.appendTo = target;
|
|
310
|
+
if (previousTarget !== target) {
|
|
311
|
+
emitHTML(controller, {
|
|
312
|
+
type: "append-target-changed",
|
|
313
|
+
previousTarget,
|
|
314
|
+
target,
|
|
315
|
+
});
|
|
316
|
+
}
|
|
239
317
|
return api;
|
|
240
318
|
}
|
|
241
319
|
|
|
@@ -246,6 +324,7 @@ export function createHTMLRuntime(runtime, options = {}) {
|
|
|
246
324
|
origin: callOptions.origin ?? "define-element",
|
|
247
325
|
explicit: true,
|
|
248
326
|
executeEntries: false,
|
|
327
|
+
executeRenders: false,
|
|
249
328
|
});
|
|
250
329
|
return result.modules[0] ?? definitionFromOwnedElement(controller, element);
|
|
251
330
|
}
|
|
@@ -257,6 +336,7 @@ export function createHTMLRuntime(runtime, options = {}) {
|
|
|
257
336
|
origin: callOptions.origin ?? "register-element",
|
|
258
337
|
explicit: true,
|
|
259
338
|
executeEntries: callOptions.executeEntries ?? controller.executeEntries,
|
|
339
|
+
executeRenders: callOptions.executeRenders ?? controller.executeRenders,
|
|
260
340
|
});
|
|
261
341
|
return result.modules[0] ?? definitionFromOwnedElement(controller, element);
|
|
262
342
|
}
|
|
@@ -268,6 +348,12 @@ export function createHTMLRuntime(runtime, options = {}) {
|
|
|
268
348
|
*/
|
|
269
349
|
async function updateElement(element, callOptions = {}) {
|
|
270
350
|
const record = requireOwnedRecord(controller, element, "updateElement");
|
|
351
|
+
emitHTML(controller, {
|
|
352
|
+
type: "element-updating",
|
|
353
|
+
element,
|
|
354
|
+
moduleId: record.moduleId,
|
|
355
|
+
origin: callOptions.origin ?? "update-element",
|
|
356
|
+
});
|
|
271
357
|
const metadata = inspectElement(controller, element, {
|
|
272
358
|
...callOptions,
|
|
273
359
|
existingId: record.moduleId,
|
|
@@ -285,15 +371,30 @@ export function createHTMLRuntime(runtime, options = {}) {
|
|
|
285
371
|
...record,
|
|
286
372
|
format: metadata.format,
|
|
287
373
|
entry: metadata.entry,
|
|
374
|
+
render: metadata.render,
|
|
375
|
+
component: metadata.component,
|
|
288
376
|
sourceUrl: metadata.sourceUrl,
|
|
289
377
|
inline: !metadata.sourceUrl,
|
|
290
378
|
};
|
|
291
379
|
|
|
292
380
|
const { source } = await prepareRecordSource(controller, candidate, callOptions);
|
|
381
|
+
const previousRender = record.render;
|
|
293
382
|
runtime.update(record.moduleId, source, { format: candidate.format });
|
|
294
383
|
|
|
384
|
+
if (record.mount) {
|
|
385
|
+
const previousInPlace = previousRender === true;
|
|
386
|
+
const nextInPlace = candidate.render === true;
|
|
387
|
+
await disposeRecordMount(controller, record, {
|
|
388
|
+
reason: "element-update",
|
|
389
|
+
removeAnchors: previousInPlace && !nextInPlace,
|
|
390
|
+
restoreElement: previousInPlace && !nextInPlace,
|
|
391
|
+
});
|
|
392
|
+
}
|
|
393
|
+
|
|
295
394
|
record.format = candidate.format;
|
|
296
395
|
record.entry = candidate.entry;
|
|
396
|
+
record.render = candidate.render;
|
|
397
|
+
record.component = candidate.component;
|
|
297
398
|
record.sourceUrl = candidate.sourceUrl;
|
|
298
399
|
record.inline = candidate.inline;
|
|
299
400
|
record.state = "defined";
|
|
@@ -301,17 +402,24 @@ export function createHTMLRuntime(runtime, options = {}) {
|
|
|
301
402
|
record.definition = definitionFromRecord(record);
|
|
302
403
|
|
|
303
404
|
if (record.entry && (callOptions.executeEntries ?? controller.executeEntries) !== false) {
|
|
304
|
-
record
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
}
|
|
405
|
+
await executeEntryRecord(controller, record, {
|
|
406
|
+
...callOptions,
|
|
407
|
+
origin: callOptions.origin ?? "update-element",
|
|
408
|
+
});
|
|
409
|
+
} else if (record.render !== undefined && (callOptions.executeRenders ?? controller.executeRenders) !== false) {
|
|
410
|
+
await renderDeclarationRecord(controller, record, {
|
|
411
|
+
...callOptions,
|
|
412
|
+
origin: callOptions.origin ?? "update-element",
|
|
413
|
+
});
|
|
313
414
|
}
|
|
314
415
|
|
|
416
|
+
emitHTML(controller, {
|
|
417
|
+
type: "element-updated",
|
|
418
|
+
element,
|
|
419
|
+
moduleId: record.moduleId,
|
|
420
|
+
definition: record.definition,
|
|
421
|
+
origin: callOptions.origin ?? "update-element",
|
|
422
|
+
});
|
|
315
423
|
return record.definition;
|
|
316
424
|
}
|
|
317
425
|
|
|
@@ -322,10 +430,27 @@ export function createHTMLRuntime(runtime, options = {}) {
|
|
|
322
430
|
*/
|
|
323
431
|
async function removeElement(element, callOptions = {}) {
|
|
324
432
|
const record = requireOwnedRecord(controller, element, "removeElement");
|
|
433
|
+
const origin = callOptions.origin ?? "remove-element";
|
|
434
|
+
emitHTML(controller, {
|
|
435
|
+
type: "element-removing",
|
|
436
|
+
element,
|
|
437
|
+
moduleId: record.moduleId,
|
|
438
|
+
origin,
|
|
439
|
+
removeModule: callOptions.removeModule !== false,
|
|
440
|
+
removeFromDOM: callOptions.removeFromDOM !== false,
|
|
441
|
+
});
|
|
325
442
|
const removeModule = callOptions.removeModule !== false;
|
|
326
443
|
const removeFromDOM = callOptions.removeFromDOM !== false;
|
|
327
444
|
const invalidateDependents = callOptions.invalidateDependents !== false;
|
|
328
445
|
|
|
446
|
+
if (record.mount) {
|
|
447
|
+
await disposeRecordMount(controller, record, {
|
|
448
|
+
reason: "element-remove",
|
|
449
|
+
removeAnchors: record.render === true,
|
|
450
|
+
restoreElement: record.render === true && !removeFromDOM,
|
|
451
|
+
});
|
|
452
|
+
}
|
|
453
|
+
|
|
329
454
|
let moduleRemoved = false;
|
|
330
455
|
if (removeModule) {
|
|
331
456
|
moduleRemoved = runtime.remove(record.moduleId, { invalidateDependents });
|
|
@@ -334,22 +459,37 @@ export function createHTMLRuntime(runtime, options = {}) {
|
|
|
334
459
|
controller.bindings.delete(record.moduleId);
|
|
335
460
|
elementOwners.delete(element);
|
|
336
461
|
record.state = "removed";
|
|
462
|
+
emitHTML(controller, {
|
|
463
|
+
type: "element-released",
|
|
464
|
+
element,
|
|
465
|
+
moduleId: record.moduleId,
|
|
466
|
+
origin,
|
|
467
|
+
});
|
|
337
468
|
|
|
338
469
|
let elementRemoved = false;
|
|
339
470
|
if (removeFromDOM) {
|
|
340
|
-
elementRemoved = removeDOMElement(element);
|
|
471
|
+
elementRemoved = record.render === true ? true : removeDOMElement(element);
|
|
341
472
|
} else {
|
|
342
473
|
// Keep a released element in the observed tree from being immediately
|
|
343
474
|
// rediscovered. Explicit registerElement() can still claim it again.
|
|
344
475
|
controller.ignoredExisting.add(element);
|
|
345
476
|
}
|
|
346
477
|
|
|
347
|
-
|
|
478
|
+
const result = {
|
|
348
479
|
id: record.moduleId,
|
|
349
480
|
element,
|
|
350
481
|
moduleRemoved,
|
|
351
482
|
elementRemoved,
|
|
352
483
|
};
|
|
484
|
+
emitHTML(controller, {
|
|
485
|
+
type: "element-removed",
|
|
486
|
+
element,
|
|
487
|
+
moduleId: record.moduleId,
|
|
488
|
+
origin,
|
|
489
|
+
moduleRemoved,
|
|
490
|
+
elementRemoved,
|
|
491
|
+
});
|
|
492
|
+
return result;
|
|
353
493
|
}
|
|
354
494
|
|
|
355
495
|
/**
|
|
@@ -371,6 +511,7 @@ export function createHTMLRuntime(runtime, options = {}) {
|
|
|
371
511
|
...callOptions,
|
|
372
512
|
origin: callOptions.origin ?? "append",
|
|
373
513
|
executeEntries: callOptions.executeEntries ?? controller.executeEntries,
|
|
514
|
+
executeRenders: callOptions.executeRenders ?? controller.executeRenders,
|
|
374
515
|
});
|
|
375
516
|
|
|
376
517
|
if (!claim.fresh) await claim.record.promise;
|
|
@@ -409,6 +550,9 @@ export function createHTMLRuntime(runtime, options = {}) {
|
|
|
409
550
|
if (id) element.setAttribute("module", id);
|
|
410
551
|
if (moduleOptions.src) element.setAttribute("src", String(moduleOptions.src));
|
|
411
552
|
if (moduleOptions.entry) element.setAttribute("entry", "");
|
|
553
|
+
if (moduleOptions.render === true) element.setAttribute("render", "");
|
|
554
|
+
else if (typeof moduleOptions.render === "string") element.setAttribute("render", moduleOptions.render);
|
|
555
|
+
if (moduleOptions.component) element.setAttribute("component", String(moduleOptions.component));
|
|
412
556
|
if (moduleOptions.language) element.setAttribute("language", String(moduleOptions.language));
|
|
413
557
|
if (moduleOptions.source != null) element.textContent = String(moduleOptions.source);
|
|
414
558
|
|
|
@@ -465,6 +609,399 @@ export async function defineScript(runtime, element, options = {}) {
|
|
|
465
609
|
export const htmlModuleSelector = DEFAULT_SELECTOR;
|
|
466
610
|
export const htmlRuntimeScopeAttribute = RUNTIME_SCOPE_ATTRIBUTE;
|
|
467
611
|
|
|
612
|
+
|
|
613
|
+
/**
|
|
614
|
+
* Register the global <solid-render> custom element once for the selected
|
|
615
|
+
* CustomElementRegistry. registerHTML()/observe() call this automatically in a
|
|
616
|
+
* browser; programmatic-only applications may call it explicitly.
|
|
617
|
+
*/
|
|
618
|
+
export function registerSolidRenderElement(options = {}) {
|
|
619
|
+
const registry = options.customElements ?? globalThis.customElements;
|
|
620
|
+
const HTMLElementBase = options.HTMLElement ?? globalThis.HTMLElement;
|
|
621
|
+
if (!registry || typeof registry.define !== "function" || typeof registry.get !== "function" || typeof HTMLElementBase !== "function") {
|
|
622
|
+
return undefined;
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
const existing = registry.get("solid-render");
|
|
626
|
+
if (existing) return existing;
|
|
627
|
+
const cached = solidRenderClasses.get(registry);
|
|
628
|
+
if (cached) return cached;
|
|
629
|
+
|
|
630
|
+
class SolidRenderElement extends HTMLElementBase {
|
|
631
|
+
static get observedAttributes() {
|
|
632
|
+
return ["module", "component", RUNTIME_SCOPE_ATTRIBUTE];
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
constructor() {
|
|
636
|
+
super();
|
|
637
|
+
solidRenderInternals.set(this, {
|
|
638
|
+
connected: false,
|
|
639
|
+
generation: 0,
|
|
640
|
+
captured: false,
|
|
641
|
+
childrenTemplate: undefined,
|
|
642
|
+
programmaticProps: {},
|
|
643
|
+
mount: undefined,
|
|
644
|
+
controller: undefined,
|
|
645
|
+
propObserver: undefined,
|
|
646
|
+
});
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
connectedCallback() {
|
|
650
|
+
const state = getSolidRenderState(this);
|
|
651
|
+
state.connected = true;
|
|
652
|
+
captureSolidRenderChildren(this, state);
|
|
653
|
+
startSolidRenderPropObserver(this, state);
|
|
654
|
+
void remountSolidRender(this, "connect");
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
disconnectedCallback() {
|
|
658
|
+
const state = getSolidRenderState(this);
|
|
659
|
+
state.connected = false;
|
|
660
|
+
state.generation += 1;
|
|
661
|
+
state.propObserver?.disconnect?.();
|
|
662
|
+
state.propObserver = undefined;
|
|
663
|
+
void disposeSolidRenderMount(this, state, "disconnect");
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
attributeChangedCallback(name, oldValue, newValue) {
|
|
667
|
+
if (oldValue === newValue) return;
|
|
668
|
+
const state = getSolidRenderState(this);
|
|
669
|
+
if (!state.connected) return;
|
|
670
|
+
void remountSolidRender(this, `identity:${name}`);
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
get props() {
|
|
674
|
+
return getSolidRenderState(this).programmaticProps;
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
set props(value) {
|
|
678
|
+
const state = getSolidRenderState(this);
|
|
679
|
+
state.programmaticProps = value && typeof value === "object" ? value : {};
|
|
680
|
+
updateSolidRenderProps(this, state);
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
registry.define("solid-render", SolidRenderElement);
|
|
685
|
+
solidRenderClasses.set(registry, SolidRenderElement);
|
|
686
|
+
return SolidRenderElement;
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
function getSolidRenderState(element) {
|
|
690
|
+
let state = solidRenderInternals.get(element);
|
|
691
|
+
if (!state) {
|
|
692
|
+
state = {
|
|
693
|
+
connected: false,
|
|
694
|
+
generation: 0,
|
|
695
|
+
captured: false,
|
|
696
|
+
childrenTemplate: undefined,
|
|
697
|
+
programmaticProps: {},
|
|
698
|
+
mount: undefined,
|
|
699
|
+
controller: undefined,
|
|
700
|
+
propObserver: undefined,
|
|
701
|
+
};
|
|
702
|
+
solidRenderInternals.set(element, state);
|
|
703
|
+
}
|
|
704
|
+
return state;
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
function captureSolidRenderChildren(element, state) {
|
|
708
|
+
if (state.captured) return;
|
|
709
|
+
state.captured = true;
|
|
710
|
+
const document = element.ownerDocument ?? globalThis.document;
|
|
711
|
+
if (!document || typeof document.createDocumentFragment !== "function") return;
|
|
712
|
+
const template = document.createDocumentFragment();
|
|
713
|
+
for (const node of Array.from(element.childNodes ?? [])) {
|
|
714
|
+
template.appendChild?.(node.cloneNode ? node.cloneNode(true) : node);
|
|
715
|
+
}
|
|
716
|
+
state.childrenTemplate = template;
|
|
717
|
+
element.replaceChildren?.();
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
function startSolidRenderPropObserver(element, state) {
|
|
721
|
+
if (state.propObserver) return;
|
|
722
|
+
let controller;
|
|
723
|
+
try {
|
|
724
|
+
controller = resolveHTMLControllerForElement(element);
|
|
725
|
+
} catch {
|
|
726
|
+
controller = undefined;
|
|
727
|
+
}
|
|
728
|
+
const MutationObserverImpl = controller?.MutationObserver ?? globalThis.MutationObserver;
|
|
729
|
+
if (typeof MutationObserverImpl !== "function") return;
|
|
730
|
+
|
|
731
|
+
const observer = new MutationObserverImpl(records => {
|
|
732
|
+
if (!Array.from(records ?? []).some(record => String(record?.attributeName ?? "").startsWith("prop:"))) return;
|
|
733
|
+
updateSolidRenderProps(element, state);
|
|
734
|
+
});
|
|
735
|
+
observer.observe(element, { attributes: true });
|
|
736
|
+
state.propObserver = observer;
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
async function remountSolidRender(element, reason) {
|
|
740
|
+
const state = getSolidRenderState(element);
|
|
741
|
+
if (!state.connected) return;
|
|
742
|
+
const generation = ++state.generation;
|
|
743
|
+
await disposeSolidRenderMount(element, state, reason === "connect" ? "reconnect" : reason);
|
|
744
|
+
if (!state.connected || generation !== state.generation) return;
|
|
745
|
+
|
|
746
|
+
let controller;
|
|
747
|
+
try {
|
|
748
|
+
controller = resolveHTMLControllerForElement(element);
|
|
749
|
+
state.controller = controller;
|
|
750
|
+
controller.renderElements.add(element);
|
|
751
|
+
|
|
752
|
+
const moduleId = nonEmpty(element.getAttribute?.("module"));
|
|
753
|
+
if (!moduleId) {
|
|
754
|
+
throw new HTMLRenderError("<solid-render> requires a `module` attribute.", { element });
|
|
755
|
+
}
|
|
756
|
+
const componentName = nonEmpty(element.getAttribute?.("component"));
|
|
757
|
+
const exportName = componentName ?? "default";
|
|
758
|
+
const props = buildSolidRenderProps(element, state);
|
|
759
|
+
|
|
760
|
+
emitHTML(controller, {
|
|
761
|
+
type: "render-mounting",
|
|
762
|
+
element,
|
|
763
|
+
moduleId,
|
|
764
|
+
component: exportName,
|
|
765
|
+
mode: "container",
|
|
766
|
+
origin: "solid-render",
|
|
767
|
+
source: "solid-render",
|
|
768
|
+
});
|
|
769
|
+
|
|
770
|
+
const namespace = await controller.runtime.import(moduleId);
|
|
771
|
+
if (!state.connected || generation !== state.generation) return;
|
|
772
|
+
const { Component } = resolveRenderComponent(namespace, componentName, {
|
|
773
|
+
moduleId,
|
|
774
|
+
element,
|
|
775
|
+
});
|
|
776
|
+
|
|
777
|
+
element.replaceChildren?.();
|
|
778
|
+
const mount = await controller.renderAdapter.mountContainer({
|
|
779
|
+
runtime: controller.runtime,
|
|
780
|
+
target: element,
|
|
781
|
+
Component,
|
|
782
|
+
props,
|
|
783
|
+
element,
|
|
784
|
+
moduleId,
|
|
785
|
+
});
|
|
786
|
+
if (!state.connected || generation !== state.generation) {
|
|
787
|
+
await mount.dispose?.();
|
|
788
|
+
return;
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
mount.mode = "container";
|
|
792
|
+
state.mount = mount;
|
|
793
|
+
emitHTML(controller, {
|
|
794
|
+
type: "render-mounted",
|
|
795
|
+
element,
|
|
796
|
+
moduleId,
|
|
797
|
+
component: exportName,
|
|
798
|
+
mode: "container",
|
|
799
|
+
target: element,
|
|
800
|
+
origin: "solid-render",
|
|
801
|
+
source: "solid-render",
|
|
802
|
+
});
|
|
803
|
+
} catch (error) {
|
|
804
|
+
// Identity changes or disconnects invalidate in-flight work. A stale
|
|
805
|
+
// failure belongs to the abandoned generation just like a stale success,
|
|
806
|
+
// so it must not overwrite/report against the current element state.
|
|
807
|
+
if (!state.connected || generation !== state.generation) return;
|
|
808
|
+
if (controller) {
|
|
809
|
+
emitHTMLError(controller, "solid-render", error, {
|
|
810
|
+
element,
|
|
811
|
+
moduleId: nonEmpty(element.getAttribute?.("module")),
|
|
812
|
+
origin: "solid-render",
|
|
813
|
+
});
|
|
814
|
+
}
|
|
815
|
+
dispatchSolidRenderError(element, error);
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
async function disposeSolidRenderMount(element, state, reason = "dispose") {
|
|
820
|
+
const mount = state.mount;
|
|
821
|
+
const controller = state.controller;
|
|
822
|
+
if (!mount) {
|
|
823
|
+
controller?.renderElements?.delete(element);
|
|
824
|
+
state.controller = undefined;
|
|
825
|
+
return;
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
const moduleId = nonEmpty(element.getAttribute?.("module"));
|
|
829
|
+
const component = nonEmpty(element.getAttribute?.("component")) ?? "default";
|
|
830
|
+
if (controller) {
|
|
831
|
+
emitHTML(controller, {
|
|
832
|
+
type: "render-disposing",
|
|
833
|
+
element,
|
|
834
|
+
moduleId,
|
|
835
|
+
component,
|
|
836
|
+
mode: "container",
|
|
837
|
+
reason,
|
|
838
|
+
source: "solid-render",
|
|
839
|
+
origin: "solid-render",
|
|
840
|
+
});
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
try {
|
|
844
|
+
await mount.dispose?.();
|
|
845
|
+
} finally {
|
|
846
|
+
state.mount = undefined;
|
|
847
|
+
element.replaceChildren?.();
|
|
848
|
+
controller?.renderElements?.delete(element);
|
|
849
|
+
state.controller = undefined;
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
if (controller) {
|
|
853
|
+
emitHTML(controller, {
|
|
854
|
+
type: "render-disposed",
|
|
855
|
+
element,
|
|
856
|
+
moduleId,
|
|
857
|
+
component,
|
|
858
|
+
mode: "container",
|
|
859
|
+
reason,
|
|
860
|
+
source: "solid-render",
|
|
861
|
+
origin: "solid-render",
|
|
862
|
+
});
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
function updateSolidRenderProps(element, state) {
|
|
867
|
+
if (!state.mount?.updateProps) return;
|
|
868
|
+
state.mount.updateProps(buildSolidRenderProps(element, state));
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
function buildSolidRenderProps(element, state) {
|
|
872
|
+
const declarative = {};
|
|
873
|
+
for (const attribute of Array.from(element.attributes ?? [])) {
|
|
874
|
+
const name = String(attribute?.name ?? "").toLowerCase();
|
|
875
|
+
if (!name.startsWith("prop:")) continue;
|
|
876
|
+
const key = kebabToCamel(name.slice(5));
|
|
877
|
+
if (!key) continue;
|
|
878
|
+
declarative[key] = attribute.value === "" ? true : attribute.value;
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
if (state.childrenTemplate && !("children" in (state.programmaticProps ?? {}))) {
|
|
882
|
+
Object.defineProperty(declarative, "children", {
|
|
883
|
+
configurable: true,
|
|
884
|
+
enumerable: true,
|
|
885
|
+
get() {
|
|
886
|
+
return state.childrenTemplate.cloneNode?.(true) ?? state.childrenTemplate;
|
|
887
|
+
},
|
|
888
|
+
});
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
const merged = Object.create(null);
|
|
892
|
+
for (const key of Reflect.ownKeys(declarative)) {
|
|
893
|
+
const descriptor = Object.getOwnPropertyDescriptor(declarative, key);
|
|
894
|
+
if (descriptor) Object.defineProperty(merged, key, descriptor);
|
|
895
|
+
}
|
|
896
|
+
for (const key of Reflect.ownKeys(state.programmaticProps ?? {})) {
|
|
897
|
+
merged[key] = state.programmaticProps[key];
|
|
898
|
+
}
|
|
899
|
+
return merged;
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
function kebabToCamel(value) {
|
|
903
|
+
return String(value).replace(/-([a-z0-9])/g, (_match, character) => character.toUpperCase());
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
function resolveHTMLControllerForElement(element) {
|
|
907
|
+
const scope = nonEmpty(element.getAttribute?.(RUNTIME_SCOPE_ATTRIBUTE));
|
|
908
|
+
const live = [...activeControllers].filter(controller => !controller.disposed);
|
|
909
|
+
let candidates = scope
|
|
910
|
+
? live.filter(controller => controller.scope === scope)
|
|
911
|
+
: live.filter(controller => !controller.scope);
|
|
912
|
+
|
|
913
|
+
if (!scope && candidates.length === 0 && live.length === 1) candidates = live;
|
|
914
|
+
const contained = candidates.filter(controller => rootContainsElement(controller.root ?? globalThis.document, element));
|
|
915
|
+
if (contained.length === 1) return contained[0];
|
|
916
|
+
if (contained.length > 1) {
|
|
917
|
+
const specific = mostSpecificController(contained);
|
|
918
|
+
if (specific) return specific;
|
|
919
|
+
}
|
|
920
|
+
if (candidates.length === 1) return candidates[0];
|
|
921
|
+
|
|
922
|
+
if (candidates.length === 0) {
|
|
923
|
+
throw new HTMLRenderError(
|
|
924
|
+
scope
|
|
925
|
+
? `No HTML runtime is registered for data-solid-runtime=${JSON.stringify(scope)}.`
|
|
926
|
+
: "No unambiguous HTML runtime is available for <solid-render>. Add data-solid-runtime when multiple runtimes exist.",
|
|
927
|
+
{ element },
|
|
928
|
+
);
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
throw new HTMLRenderError(
|
|
932
|
+
`Multiple HTML runtimes match <solid-render>${scope ? ` for scope ${JSON.stringify(scope)}` : ""}. Use distinct scopes or DOM roots.`,
|
|
933
|
+
{ element },
|
|
934
|
+
);
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
function rootContainsElement(root, element) {
|
|
938
|
+
if (!root || !element) return false;
|
|
939
|
+
if (root === element) return true;
|
|
940
|
+
if (typeof root.contains === "function") return root.contains(element);
|
|
941
|
+
let node = element.parentNode;
|
|
942
|
+
while (node) {
|
|
943
|
+
if (node === root) return true;
|
|
944
|
+
node = node.parentNode;
|
|
945
|
+
}
|
|
946
|
+
return false;
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
function mostSpecificController(controllers) {
|
|
950
|
+
for (const candidate of controllers) {
|
|
951
|
+
const root = candidate.root ?? globalThis.document;
|
|
952
|
+
if (controllers.every(other => {
|
|
953
|
+
if (other === candidate) return true;
|
|
954
|
+
const otherRoot = other.root ?? globalThis.document;
|
|
955
|
+
return !rootContainsElement(root, otherRoot);
|
|
956
|
+
})) return candidate;
|
|
957
|
+
}
|
|
958
|
+
return undefined;
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
function dispatchSolidRenderError(element, error) {
|
|
962
|
+
try {
|
|
963
|
+
const EventImpl = globalThis.CustomEvent;
|
|
964
|
+
if (typeof element?.dispatchEvent === "function" && typeof EventImpl === "function") {
|
|
965
|
+
element.dispatchEvent(new EventImpl("solid-render-error", {
|
|
966
|
+
detail: { error },
|
|
967
|
+
bubbles: false,
|
|
968
|
+
composed: false,
|
|
969
|
+
}));
|
|
970
|
+
}
|
|
971
|
+
} catch {
|
|
972
|
+
// Error reporting must not create a second failure.
|
|
973
|
+
}
|
|
974
|
+
globalThis.console?.error?.("solid-tag-runtime <solid-render> error", error);
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
async function disposeController(controller, options = {}) {
|
|
978
|
+
if (controller.disposed) return;
|
|
979
|
+
controller.disposed = true;
|
|
980
|
+
stopObserver(controller, { reason: options.runtimeDisposed ? "runtime-disposed" : "controller-disposed" });
|
|
981
|
+
|
|
982
|
+
for (const record of controller.bindings.values()) {
|
|
983
|
+
if (record.mount) {
|
|
984
|
+
await disposeRecordMount(controller, record, {
|
|
985
|
+
reason: options.runtimeDisposed ? "runtime-disposed" : "controller-disposed",
|
|
986
|
+
removeAnchors: record.render === true,
|
|
987
|
+
restoreElement: record.render === true,
|
|
988
|
+
});
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
for (const element of [...controller.renderElements]) {
|
|
993
|
+
const state = solidRenderInternals.get(element);
|
|
994
|
+
if (!state || state.controller !== controller) continue;
|
|
995
|
+
state.generation += 1;
|
|
996
|
+
await disposeSolidRenderMount(element, state, options.runtimeDisposed ? "runtime-disposed" : "controller-disposed");
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
activeControllers.delete(controller);
|
|
1000
|
+
controller.runtimeDisposeUnsubscribe?.();
|
|
1001
|
+
controller.runtimeDisposeUnsubscribe = undefined;
|
|
1002
|
+
controller.events.clear();
|
|
1003
|
+
}
|
|
1004
|
+
|
|
468
1005
|
async function connectObserver(controller, root, callOptions = {}, mode = {}) {
|
|
469
1006
|
const MutationObserverImpl = callOptions.MutationObserver ?? controller.MutationObserver ?? globalThis.MutationObserver;
|
|
470
1007
|
|
|
@@ -497,6 +1034,13 @@ async function connectObserver(controller, root, callOptions = {}, mode = {}) {
|
|
|
497
1034
|
childList: true,
|
|
498
1035
|
subtree: callOptions.subtree ?? controller.subtree,
|
|
499
1036
|
});
|
|
1037
|
+
emitHTML(controller, {
|
|
1038
|
+
type: "observer-connected",
|
|
1039
|
+
root,
|
|
1040
|
+
subtree: callOptions.subtree ?? controller.subtree,
|
|
1041
|
+
registerExisting,
|
|
1042
|
+
reason: mode.reason ?? (mode.initial ? "observe" : "connect"),
|
|
1043
|
+
});
|
|
500
1044
|
|
|
501
1045
|
const result = registerExisting
|
|
502
1046
|
? await enqueueObserverScan(controller, {
|
|
@@ -511,11 +1055,17 @@ async function connectObserver(controller, root, callOptions = {}, mode = {}) {
|
|
|
511
1055
|
|
|
512
1056
|
function stopObserver(controller, options = {}) {
|
|
513
1057
|
if (!controller.connected && !controller.observer) return;
|
|
1058
|
+
const root = controller.observerRoot;
|
|
514
1059
|
controller.connected = false;
|
|
515
1060
|
controller.observer?.disconnect();
|
|
516
1061
|
controller.observer = undefined;
|
|
517
1062
|
controller.observerRoot = undefined;
|
|
518
1063
|
if (!options.preserveOptions) controller.observerOptions = undefined;
|
|
1064
|
+
emitHTML(controller, {
|
|
1065
|
+
type: "observer-disconnected",
|
|
1066
|
+
root,
|
|
1067
|
+
reason: options.reason ?? "disconnect",
|
|
1068
|
+
});
|
|
519
1069
|
}
|
|
520
1070
|
|
|
521
1071
|
async function enqueueObserverScan(controller, callOptions = {}) {
|
|
@@ -524,23 +1074,79 @@ async function enqueueObserverScan(controller, callOptions = {}) {
|
|
|
524
1074
|
|
|
525
1075
|
const root = controller.observerRoot ?? getRoot(mergeOptions(controller, callOptions), "HTML runtime observer");
|
|
526
1076
|
const selector = callOptions.selector ?? controller.selector;
|
|
527
|
-
const
|
|
528
|
-
|
|
529
|
-
|
|
1077
|
+
const origin = callOptions.origin ?? "observer";
|
|
1078
|
+
const discovered = Array.from(root.querySelectorAll(selector));
|
|
1079
|
+
const elements = [];
|
|
1080
|
+
let matched = 0;
|
|
1081
|
+
let ignored = 0;
|
|
1082
|
+
|
|
1083
|
+
for (const element of discovered) {
|
|
1084
|
+
if (controller.ignoredExisting.has(element)) {
|
|
1085
|
+
ignored += 1;
|
|
1086
|
+
emitHTML(controller, {
|
|
1087
|
+
type: "element-discovered",
|
|
1088
|
+
element,
|
|
1089
|
+
moduleId: nonEmpty(element?.getAttribute?.("module")),
|
|
1090
|
+
origin,
|
|
1091
|
+
accepted: false,
|
|
1092
|
+
});
|
|
1093
|
+
continue;
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
const accepted = acceptsDiscoveredElement(controller, element, callOptions);
|
|
1097
|
+
emitHTML(controller, {
|
|
1098
|
+
type: "element-discovered",
|
|
1099
|
+
element,
|
|
1100
|
+
moduleId: nonEmpty(element?.getAttribute?.("module")),
|
|
1101
|
+
origin,
|
|
1102
|
+
accepted,
|
|
1103
|
+
});
|
|
1104
|
+
if (!accepted) {
|
|
1105
|
+
ignored += 1;
|
|
1106
|
+
continue;
|
|
1107
|
+
}
|
|
1108
|
+
matched += 1;
|
|
530
1109
|
|
|
531
1110
|
const ownership = elementOwners.get(element);
|
|
532
|
-
if (!ownership)
|
|
533
|
-
|
|
1111
|
+
if (!ownership) {
|
|
1112
|
+
elements.push(element);
|
|
1113
|
+
continue;
|
|
1114
|
+
}
|
|
1115
|
+
if (ownership.owner === controller) {
|
|
1116
|
+
ignored += 1;
|
|
1117
|
+
continue;
|
|
1118
|
+
}
|
|
534
1119
|
|
|
535
1120
|
throw ownershipError(controller, element, ownership);
|
|
536
|
-
}
|
|
1121
|
+
}
|
|
537
1122
|
|
|
538
|
-
|
|
539
|
-
|
|
1123
|
+
let result;
|
|
1124
|
+
try {
|
|
1125
|
+
result = await processElements(controller, elements, {
|
|
1126
|
+
...callOptions,
|
|
1127
|
+
root,
|
|
1128
|
+
origin,
|
|
1129
|
+
executeEntries: callOptions.executeEntries ?? controller.executeEntries,
|
|
1130
|
+
executeRenders: callOptions.executeRenders ?? controller.executeRenders,
|
|
1131
|
+
});
|
|
1132
|
+
} catch (error) {
|
|
1133
|
+
emitHTMLError(controller, "observer", error, { origin });
|
|
1134
|
+
throw error;
|
|
1135
|
+
}
|
|
1136
|
+
|
|
1137
|
+
await cleanupDetachedRenderedDeclarations(controller, root);
|
|
1138
|
+
|
|
1139
|
+
emitHTML(controller, {
|
|
1140
|
+
type: "observer-batch",
|
|
540
1141
|
root,
|
|
541
|
-
origin
|
|
542
|
-
|
|
1142
|
+
origin,
|
|
1143
|
+
discovered: discovered.length,
|
|
1144
|
+
matched,
|
|
1145
|
+
claimed: result.modules.length,
|
|
1146
|
+
ignored,
|
|
1147
|
+
modules: result.modules.map(module => module.id),
|
|
543
1148
|
});
|
|
1149
|
+
return result;
|
|
544
1150
|
});
|
|
545
1151
|
|
|
546
1152
|
controller.queue = task.then(
|
|
@@ -587,6 +1193,12 @@ async function processClaims(controller, records, options = {}) {
|
|
|
587
1193
|
try {
|
|
588
1194
|
prepared = await Promise.all(records.map(record => prepareRecordSource(controller, record, options)));
|
|
589
1195
|
} catch (error) {
|
|
1196
|
+
const failed = records.find(record => record.state === "loading") ?? records[0];
|
|
1197
|
+
emitHTMLError(controller, failed?.sourceUrl ? "fetch" : "register", error, {
|
|
1198
|
+
element: failed?.element,
|
|
1199
|
+
moduleId: failed?.moduleId,
|
|
1200
|
+
origin: failed?.origin ?? options.origin,
|
|
1201
|
+
});
|
|
590
1202
|
for (const record of records) rejectRecord(record, error);
|
|
591
1203
|
throw error;
|
|
592
1204
|
}
|
|
@@ -604,8 +1216,21 @@ async function processClaims(controller, records, options = {}) {
|
|
|
604
1216
|
record.definition = definitionFromRecord(record);
|
|
605
1217
|
record.resolve(record.definition);
|
|
606
1218
|
modules.push(record.definition);
|
|
1219
|
+
emitHTML(controller, {
|
|
1220
|
+
type: "element-registered",
|
|
1221
|
+
element: record.element,
|
|
1222
|
+
moduleId: record.moduleId,
|
|
1223
|
+
definition: record.definition,
|
|
1224
|
+
origin: record.origin,
|
|
1225
|
+
});
|
|
607
1226
|
}
|
|
608
1227
|
} catch (error) {
|
|
1228
|
+
const failed = records.find(record => record.state === "claimed" || record.state === "loading") ?? records[0];
|
|
1229
|
+
emitHTMLError(controller, "register", error, {
|
|
1230
|
+
element: failed?.element,
|
|
1231
|
+
moduleId: failed?.moduleId,
|
|
1232
|
+
origin: failed?.origin ?? options.origin,
|
|
1233
|
+
});
|
|
609
1234
|
for (const record of records) {
|
|
610
1235
|
if (record.state === "claimed" || record.state === "loading") rejectRecord(record, error);
|
|
611
1236
|
}
|
|
@@ -613,34 +1238,324 @@ async function processClaims(controller, records, options = {}) {
|
|
|
613
1238
|
}
|
|
614
1239
|
|
|
615
1240
|
const entries = modules.filter(module => module.entry);
|
|
1241
|
+
const renders = modules.filter(module => module.render !== undefined);
|
|
616
1242
|
const executed = [];
|
|
1243
|
+
const rendered = [];
|
|
617
1244
|
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
1245
|
+
// Every module in the batch is defined before any side-effect entry or render
|
|
1246
|
+
// action executes. This keeps document ordering independent from imports.
|
|
1247
|
+
for (const module of modules) {
|
|
1248
|
+
const record = elementOwners.get(module.element);
|
|
1249
|
+
if (!record) continue;
|
|
622
1250
|
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
record.error = error;
|
|
631
|
-
}
|
|
632
|
-
throw error;
|
|
633
|
-
}
|
|
1251
|
+
if (record.entry && options.executeEntries !== false) {
|
|
1252
|
+
executed.push(await executeEntryRecord(controller, record, options));
|
|
1253
|
+
continue;
|
|
1254
|
+
}
|
|
1255
|
+
|
|
1256
|
+
if (record.render !== undefined && options.executeRenders !== false) {
|
|
1257
|
+
rendered.push(await renderDeclarationRecord(controller, record, options));
|
|
634
1258
|
}
|
|
635
1259
|
}
|
|
636
1260
|
|
|
1261
|
+
registerSolidRenderElement();
|
|
637
1262
|
return {
|
|
638
1263
|
modules,
|
|
639
1264
|
entries: entries.map(entry => entry.id),
|
|
640
1265
|
executed,
|
|
1266
|
+
renders: renders.map(render => render.id),
|
|
1267
|
+
rendered,
|
|
641
1268
|
};
|
|
642
1269
|
}
|
|
643
1270
|
|
|
1271
|
+
|
|
1272
|
+
async function executeEntryRecord(controller, record, options = {}) {
|
|
1273
|
+
record.state = "executing";
|
|
1274
|
+
const origin = record.origin ?? options.origin ?? "entry";
|
|
1275
|
+
try {
|
|
1276
|
+
emitHTML(controller, {
|
|
1277
|
+
type: "entry-executing",
|
|
1278
|
+
element: record.element,
|
|
1279
|
+
moduleId: record.moduleId,
|
|
1280
|
+
origin,
|
|
1281
|
+
});
|
|
1282
|
+
const namespace = await controller.runtime.import(record.moduleId);
|
|
1283
|
+
record.state = "ready";
|
|
1284
|
+
emitHTML(controller, {
|
|
1285
|
+
type: "entry-executed",
|
|
1286
|
+
element: record.element,
|
|
1287
|
+
moduleId: record.moduleId,
|
|
1288
|
+
namespace,
|
|
1289
|
+
origin,
|
|
1290
|
+
});
|
|
1291
|
+
return { id: record.moduleId, namespace };
|
|
1292
|
+
} catch (error) {
|
|
1293
|
+
record.state = "error";
|
|
1294
|
+
record.error = error;
|
|
1295
|
+
emitHTMLError(controller, "entry", error, {
|
|
1296
|
+
element: record.element,
|
|
1297
|
+
moduleId: record.moduleId,
|
|
1298
|
+
origin,
|
|
1299
|
+
});
|
|
1300
|
+
throw error;
|
|
1301
|
+
}
|
|
1302
|
+
}
|
|
1303
|
+
|
|
1304
|
+
async function renderDeclarationRecord(controller, record, options = {}) {
|
|
1305
|
+
const origin = record.origin ?? options.origin ?? "render";
|
|
1306
|
+
const mode = record.render === true ? "in-place" : "selector";
|
|
1307
|
+
const exportName = record.component ?? "default";
|
|
1308
|
+
|
|
1309
|
+
emitHTML(controller, {
|
|
1310
|
+
type: "render-mounting",
|
|
1311
|
+
element: record.element,
|
|
1312
|
+
moduleId: record.moduleId,
|
|
1313
|
+
component: exportName,
|
|
1314
|
+
mode,
|
|
1315
|
+
origin,
|
|
1316
|
+
source: "script-render",
|
|
1317
|
+
});
|
|
1318
|
+
|
|
1319
|
+
try {
|
|
1320
|
+
if (record.mount) {
|
|
1321
|
+
await disposeRecordMount(controller, record, {
|
|
1322
|
+
reason: "remount",
|
|
1323
|
+
removeAnchors: false,
|
|
1324
|
+
});
|
|
1325
|
+
}
|
|
1326
|
+
|
|
1327
|
+
const namespace = await controller.runtime.import(record.moduleId);
|
|
1328
|
+
const { Component } = resolveRenderComponent(namespace, record.component, {
|
|
1329
|
+
moduleId: record.moduleId,
|
|
1330
|
+
element: record.element,
|
|
1331
|
+
});
|
|
1332
|
+
|
|
1333
|
+
let mount;
|
|
1334
|
+
if (mode === "selector") {
|
|
1335
|
+
const target = resolveRenderTarget(controller, record.element, record.render, options);
|
|
1336
|
+
mount = await controller.renderAdapter.mountContainer({
|
|
1337
|
+
runtime: controller.runtime,
|
|
1338
|
+
target,
|
|
1339
|
+
Component,
|
|
1340
|
+
props: {},
|
|
1341
|
+
element: record.element,
|
|
1342
|
+
moduleId: record.moduleId,
|
|
1343
|
+
});
|
|
1344
|
+
mount.mode = "selector";
|
|
1345
|
+
mount.renderTarget = record.render;
|
|
1346
|
+
mount.target = target;
|
|
1347
|
+
} else {
|
|
1348
|
+
const range = ensureInPlaceRange(record);
|
|
1349
|
+
mount = await controller.renderAdapter.mountRange({
|
|
1350
|
+
runtime: controller.runtime,
|
|
1351
|
+
parent: range.parent,
|
|
1352
|
+
before: range.endMarker,
|
|
1353
|
+
startMarker: range.startMarker,
|
|
1354
|
+
endMarker: range.endMarker,
|
|
1355
|
+
Component,
|
|
1356
|
+
props: {},
|
|
1357
|
+
element: record.element,
|
|
1358
|
+
moduleId: record.moduleId,
|
|
1359
|
+
});
|
|
1360
|
+
mount.mode = "in-place";
|
|
1361
|
+
mount.range = range;
|
|
1362
|
+
}
|
|
1363
|
+
|
|
1364
|
+
record.mount = mount;
|
|
1365
|
+
record.state = "ready";
|
|
1366
|
+
const result = {
|
|
1367
|
+
id: record.moduleId,
|
|
1368
|
+
namespace,
|
|
1369
|
+
component: exportName,
|
|
1370
|
+
mode,
|
|
1371
|
+
target: mount.target,
|
|
1372
|
+
element: record.element,
|
|
1373
|
+
};
|
|
1374
|
+
emitHTML(controller, {
|
|
1375
|
+
type: "render-mounted",
|
|
1376
|
+
element: record.element,
|
|
1377
|
+
moduleId: record.moduleId,
|
|
1378
|
+
component: exportName,
|
|
1379
|
+
mode,
|
|
1380
|
+
target: mount.target,
|
|
1381
|
+
origin,
|
|
1382
|
+
source: "script-render",
|
|
1383
|
+
});
|
|
1384
|
+
return result;
|
|
1385
|
+
} catch (error) {
|
|
1386
|
+
record.state = "error";
|
|
1387
|
+
record.error = error;
|
|
1388
|
+
emitHTMLError(controller, "render", error, {
|
|
1389
|
+
element: record.element,
|
|
1390
|
+
moduleId: record.moduleId,
|
|
1391
|
+
origin,
|
|
1392
|
+
});
|
|
1393
|
+
throw error;
|
|
1394
|
+
}
|
|
1395
|
+
}
|
|
1396
|
+
|
|
1397
|
+
async function disposeRecordMount(controller, record, options = {}) {
|
|
1398
|
+
const mount = record?.mount;
|
|
1399
|
+
if (!mount) return false;
|
|
1400
|
+
const mode = mount.mode ?? (record.render === true ? "in-place" : "selector");
|
|
1401
|
+
emitHTML(controller, {
|
|
1402
|
+
type: "render-disposing",
|
|
1403
|
+
element: record.element,
|
|
1404
|
+
moduleId: record.moduleId,
|
|
1405
|
+
component: record.component ?? "default",
|
|
1406
|
+
mode,
|
|
1407
|
+
reason: options.reason ?? "dispose",
|
|
1408
|
+
source: "script-render",
|
|
1409
|
+
origin: record.origin ?? "render",
|
|
1410
|
+
});
|
|
1411
|
+
|
|
1412
|
+
try {
|
|
1413
|
+
await mount.dispose?.();
|
|
1414
|
+
} finally {
|
|
1415
|
+
if (mode === "in-place" && options.removeAnchors !== false) {
|
|
1416
|
+
removeRangeAnchors(record, { restoreElement: options.restoreElement === true });
|
|
1417
|
+
}
|
|
1418
|
+
record.mount = undefined;
|
|
1419
|
+
}
|
|
1420
|
+
|
|
1421
|
+
emitHTML(controller, {
|
|
1422
|
+
type: "render-disposed",
|
|
1423
|
+
element: record.element,
|
|
1424
|
+
moduleId: record.moduleId,
|
|
1425
|
+
component: record.component ?? "default",
|
|
1426
|
+
mode,
|
|
1427
|
+
reason: options.reason ?? "dispose",
|
|
1428
|
+
source: "script-render",
|
|
1429
|
+
origin: record.origin ?? "render",
|
|
1430
|
+
});
|
|
1431
|
+
return true;
|
|
1432
|
+
}
|
|
1433
|
+
|
|
1434
|
+
function resolveRenderTarget(controller, element, selector, options = {}) {
|
|
1435
|
+
if (typeof selector !== "string" || !selector) {
|
|
1436
|
+
throw new HTMLRenderError("Selector rendering requires a non-empty render selector.", {
|
|
1437
|
+
moduleId: elementOwners.get(element)?.moduleId,
|
|
1438
|
+
element,
|
|
1439
|
+
});
|
|
1440
|
+
}
|
|
1441
|
+
|
|
1442
|
+
const root = options.renderRoot
|
|
1443
|
+
?? options.root
|
|
1444
|
+
?? controller.root
|
|
1445
|
+
?? element.ownerDocument
|
|
1446
|
+
?? globalThis.document;
|
|
1447
|
+
|
|
1448
|
+
if (!root || typeof root.querySelectorAll !== "function") {
|
|
1449
|
+
throw new HTMLRenderError(`Cannot resolve render target ${JSON.stringify(selector)} without a queryable DOM root.`, {
|
|
1450
|
+
moduleId: elementOwners.get(element)?.moduleId,
|
|
1451
|
+
element,
|
|
1452
|
+
});
|
|
1453
|
+
}
|
|
1454
|
+
|
|
1455
|
+
let target;
|
|
1456
|
+
try {
|
|
1457
|
+
if (typeof root.querySelector === "function") target = root.querySelector(selector);
|
|
1458
|
+
else target = Array.from(root.querySelectorAll(selector))[0];
|
|
1459
|
+
} catch (cause) {
|
|
1460
|
+
throw new HTMLRenderError(`Invalid render selector ${JSON.stringify(selector)}.`, {
|
|
1461
|
+
cause,
|
|
1462
|
+
moduleId: elementOwners.get(element)?.moduleId,
|
|
1463
|
+
element,
|
|
1464
|
+
});
|
|
1465
|
+
}
|
|
1466
|
+
|
|
1467
|
+
if (!target) {
|
|
1468
|
+
throw new HTMLRenderError(`Render target ${JSON.stringify(selector)} was not found.`, {
|
|
1469
|
+
moduleId: elementOwners.get(element)?.moduleId,
|
|
1470
|
+
element,
|
|
1471
|
+
});
|
|
1472
|
+
}
|
|
1473
|
+
return target;
|
|
1474
|
+
}
|
|
1475
|
+
|
|
1476
|
+
function ensureInPlaceRange(record) {
|
|
1477
|
+
if (record.range?.startMarker?.parentNode && record.range?.endMarker?.parentNode) {
|
|
1478
|
+
return record.range;
|
|
1479
|
+
}
|
|
1480
|
+
|
|
1481
|
+
const element = record.element;
|
|
1482
|
+
const parent = element?.parentNode;
|
|
1483
|
+
if (!parent) {
|
|
1484
|
+
throw new HTMLRenderError("In-place rendering requires the declaration script to have a parent node.", {
|
|
1485
|
+
moduleId: record.moduleId,
|
|
1486
|
+
element,
|
|
1487
|
+
});
|
|
1488
|
+
}
|
|
1489
|
+
|
|
1490
|
+
const document = element.ownerDocument ?? globalThis.document;
|
|
1491
|
+
if (!document || typeof document.createComment !== "function") {
|
|
1492
|
+
throw new HTMLRenderError("In-place rendering requires document.createComment().", {
|
|
1493
|
+
moduleId: record.moduleId,
|
|
1494
|
+
element,
|
|
1495
|
+
});
|
|
1496
|
+
}
|
|
1497
|
+
|
|
1498
|
+
const startMarker = document.createComment("solid-runtime:start");
|
|
1499
|
+
const endMarker = document.createComment("solid-runtime:end");
|
|
1500
|
+
|
|
1501
|
+
if (typeof element.replaceWith === "function") {
|
|
1502
|
+
element.replaceWith(startMarker, endMarker);
|
|
1503
|
+
} else if (typeof parent.insertBefore === "function" && typeof parent.removeChild === "function") {
|
|
1504
|
+
parent.insertBefore(startMarker, element);
|
|
1505
|
+
parent.insertBefore(endMarker, element);
|
|
1506
|
+
parent.removeChild(element);
|
|
1507
|
+
} else {
|
|
1508
|
+
throw new HTMLRenderError("In-place rendering requires replaceWith() or insertBefore()/removeChild() support.", {
|
|
1509
|
+
moduleId: record.moduleId,
|
|
1510
|
+
element,
|
|
1511
|
+
});
|
|
1512
|
+
}
|
|
1513
|
+
|
|
1514
|
+
record.range = { parent, startMarker, endMarker };
|
|
1515
|
+
return record.range;
|
|
1516
|
+
}
|
|
1517
|
+
|
|
1518
|
+
function removeRangeAnchors(record, options = {}) {
|
|
1519
|
+
const range = record.range;
|
|
1520
|
+
if (!range) return;
|
|
1521
|
+
const { parent, startMarker, endMarker } = range;
|
|
1522
|
+
|
|
1523
|
+
if (options.restoreElement && parent && startMarker?.parentNode === parent) {
|
|
1524
|
+
if (typeof parent.insertBefore === "function") parent.insertBefore(record.element, startMarker);
|
|
1525
|
+
}
|
|
1526
|
+
|
|
1527
|
+
clearNodesBetween(startMarker, endMarker);
|
|
1528
|
+
startMarker?.parentNode?.removeChild?.(startMarker);
|
|
1529
|
+
endMarker?.parentNode?.removeChild?.(endMarker);
|
|
1530
|
+
record.range = undefined;
|
|
1531
|
+
}
|
|
1532
|
+
|
|
1533
|
+
function clearNodesBetween(start, end) {
|
|
1534
|
+
if (!start || !end) return;
|
|
1535
|
+
let node = start.nextSibling;
|
|
1536
|
+
while (node && node !== end) {
|
|
1537
|
+
const next = node.nextSibling;
|
|
1538
|
+
node.parentNode?.removeChild?.(node);
|
|
1539
|
+
node = next;
|
|
1540
|
+
}
|
|
1541
|
+
}
|
|
1542
|
+
|
|
1543
|
+
async function cleanupDetachedRenderedDeclarations(controller, root) {
|
|
1544
|
+
if (!root || typeof root.contains !== "function") return;
|
|
1545
|
+
for (const record of controller.bindings.values()) {
|
|
1546
|
+
if (!record.mount) continue;
|
|
1547
|
+
const anchor = record.mount.mode === "in-place"
|
|
1548
|
+
? record.range?.startMarker
|
|
1549
|
+
: record.element;
|
|
1550
|
+
if (anchor && !root.contains(anchor)) {
|
|
1551
|
+
await disposeRecordMount(controller, record, {
|
|
1552
|
+
reason: "declaration-detached",
|
|
1553
|
+
removeAnchors: false,
|
|
1554
|
+
});
|
|
1555
|
+
}
|
|
1556
|
+
}
|
|
1557
|
+
}
|
|
1558
|
+
|
|
644
1559
|
function claimElement(controller, element, options = {}) {
|
|
645
1560
|
assertElement(element, options.origin ?? "HTML runtime");
|
|
646
1561
|
|
|
@@ -688,8 +1603,12 @@ function claimElement(controller, element, options = {}) {
|
|
|
688
1603
|
moduleId,
|
|
689
1604
|
format: metadata.format,
|
|
690
1605
|
entry: metadata.entry,
|
|
1606
|
+
render: metadata.render,
|
|
1607
|
+
component: metadata.component,
|
|
691
1608
|
sourceUrl: metadata.sourceUrl,
|
|
692
1609
|
inline: !metadata.sourceUrl,
|
|
1610
|
+
mount: undefined,
|
|
1611
|
+
range: undefined,
|
|
693
1612
|
state: "claimed",
|
|
694
1613
|
origin: options.origin ?? "unknown",
|
|
695
1614
|
promise,
|
|
@@ -701,6 +1620,17 @@ function claimElement(controller, element, options = {}) {
|
|
|
701
1620
|
|
|
702
1621
|
elementOwners.set(element, record);
|
|
703
1622
|
controller.bindings.set(moduleId, record);
|
|
1623
|
+
emitHTML(controller, {
|
|
1624
|
+
type: "element-claimed",
|
|
1625
|
+
element,
|
|
1626
|
+
moduleId,
|
|
1627
|
+
format: record.format,
|
|
1628
|
+
entry: record.entry,
|
|
1629
|
+
render: record.render,
|
|
1630
|
+
component: record.component,
|
|
1631
|
+
sourceUrl: record.sourceUrl,
|
|
1632
|
+
origin: record.origin,
|
|
1633
|
+
});
|
|
704
1634
|
return { fresh: true, record };
|
|
705
1635
|
}
|
|
706
1636
|
|
|
@@ -709,12 +1639,29 @@ async function prepareRecordSource(controller, record, options) {
|
|
|
709
1639
|
|
|
710
1640
|
let source;
|
|
711
1641
|
if (record.sourceUrl) {
|
|
1642
|
+
const startedAt = performanceNow();
|
|
1643
|
+
emitHTML(controller, {
|
|
1644
|
+
type: "element-loading",
|
|
1645
|
+
element: record.element,
|
|
1646
|
+
moduleId: record.moduleId,
|
|
1647
|
+
sourceUrl: record.sourceUrl,
|
|
1648
|
+
origin: record.origin,
|
|
1649
|
+
});
|
|
712
1650
|
source = await fetchSource(
|
|
713
1651
|
record.sourceUrl,
|
|
714
1652
|
options.fetch ?? controller.fetch ?? globalThis.fetch,
|
|
715
1653
|
record.element,
|
|
716
1654
|
record.moduleId,
|
|
717
1655
|
);
|
|
1656
|
+
emitHTML(controller, {
|
|
1657
|
+
type: "element-loaded",
|
|
1658
|
+
element: record.element,
|
|
1659
|
+
moduleId: record.moduleId,
|
|
1660
|
+
sourceUrl: record.sourceUrl,
|
|
1661
|
+
origin: record.origin,
|
|
1662
|
+
bytes: String(source).length,
|
|
1663
|
+
durationMs: performanceNow() - startedAt,
|
|
1664
|
+
});
|
|
718
1665
|
} else {
|
|
719
1666
|
source = record.element.textContent ?? "";
|
|
720
1667
|
}
|
|
@@ -725,6 +1672,14 @@ async function prepareRecordSource(controller, record, options) {
|
|
|
725
1672
|
function inspectElement(controller, element, options = {}) {
|
|
726
1673
|
const type = normalizeType(element.getAttribute("type"));
|
|
727
1674
|
const entry = hasAttribute(element, "entry");
|
|
1675
|
+
const render = inspectRenderAttribute(element);
|
|
1676
|
+
const component = nonEmpty(element.getAttribute("component"));
|
|
1677
|
+
if (entry && render !== undefined) {
|
|
1678
|
+
throw new HTMLModuleError(
|
|
1679
|
+
"Runtime script declarations cannot combine `entry` and `render`. Use separate declarations for side effects and UI mounting.",
|
|
1680
|
+
{ element },
|
|
1681
|
+
);
|
|
1682
|
+
}
|
|
728
1683
|
const srcAttribute = nonEmpty(element.getAttribute("src"));
|
|
729
1684
|
const explicitId = nonEmpty(element.getAttribute("module"));
|
|
730
1685
|
const baseUrl = options.baseUrl
|
|
@@ -742,15 +1697,16 @@ function inspectElement(controller, element, options = {}) {
|
|
|
742
1697
|
sourceUrl,
|
|
743
1698
|
});
|
|
744
1699
|
|
|
745
|
-
const
|
|
1700
|
+
const action = entry ? "entry" : render !== undefined ? "render" : undefined;
|
|
1701
|
+
const id = explicitId ?? sourceUrl ?? (action ? options.existingId ?? createAnonymousId(format, action) : undefined);
|
|
746
1702
|
if (!id) {
|
|
747
1703
|
throw new HTMLModuleError(
|
|
748
|
-
"Inline solid runtime module scripts require a `module` attribute unless they are marked `entry`.",
|
|
1704
|
+
"Inline solid runtime module scripts require a `module` attribute unless they are marked `entry` or `render`.",
|
|
749
1705
|
{ element },
|
|
750
1706
|
);
|
|
751
1707
|
}
|
|
752
1708
|
|
|
753
|
-
return { id, format, entry, sourceUrl };
|
|
1709
|
+
return { id, format, entry, render, component, sourceUrl };
|
|
754
1710
|
}
|
|
755
1711
|
|
|
756
1712
|
function requireOwnedRecord(controller, element, caller) {
|
|
@@ -787,6 +1743,8 @@ function definitionFromRecord(record) {
|
|
|
787
1743
|
id: record.moduleId,
|
|
788
1744
|
format: record.format,
|
|
789
1745
|
entry: record.entry,
|
|
1746
|
+
render: record.render,
|
|
1747
|
+
component: record.component,
|
|
790
1748
|
sourceUrl: record.sourceUrl,
|
|
791
1749
|
inline: record.inline,
|
|
792
1750
|
element: record.element,
|
|
@@ -863,6 +1821,46 @@ function ownershipError(controller, element, ownership) {
|
|
|
863
1821
|
);
|
|
864
1822
|
}
|
|
865
1823
|
|
|
1824
|
+
function emitHTML(controller, event) {
|
|
1825
|
+
controller.events.emit({
|
|
1826
|
+
htmlRuntimeId: controller.id,
|
|
1827
|
+
scope: controller.scope,
|
|
1828
|
+
timestamp: Date.now(),
|
|
1829
|
+
...event,
|
|
1830
|
+
});
|
|
1831
|
+
}
|
|
1832
|
+
|
|
1833
|
+
function emitHTMLError(controller, operation, error, context = {}) {
|
|
1834
|
+
emitHTML(controller, {
|
|
1835
|
+
type: "html-error",
|
|
1836
|
+
operation,
|
|
1837
|
+
error,
|
|
1838
|
+
...context,
|
|
1839
|
+
});
|
|
1840
|
+
}
|
|
1841
|
+
|
|
1842
|
+
function emitRootChanged(controller, previousRoot, root, registerExisting, previousAppendTarget) {
|
|
1843
|
+
emitHTML(controller, {
|
|
1844
|
+
type: "root-changed",
|
|
1845
|
+
previousRoot,
|
|
1846
|
+
root,
|
|
1847
|
+
registerExisting,
|
|
1848
|
+
connected: controller.connected,
|
|
1849
|
+
});
|
|
1850
|
+
const nextAppendTarget = getAppendTarget(controller);
|
|
1851
|
+
if (previousAppendTarget !== nextAppendTarget) {
|
|
1852
|
+
emitHTML(controller, {
|
|
1853
|
+
type: "append-target-changed",
|
|
1854
|
+
previousTarget: previousAppendTarget,
|
|
1855
|
+
target: nextAppendTarget,
|
|
1856
|
+
});
|
|
1857
|
+
}
|
|
1858
|
+
}
|
|
1859
|
+
|
|
1860
|
+
function performanceNow() {
|
|
1861
|
+
return globalThis.performance?.now?.() ?? Date.now();
|
|
1862
|
+
}
|
|
1863
|
+
|
|
866
1864
|
function getDefaultController(runtime, options = {}) {
|
|
867
1865
|
assertRuntime(runtime);
|
|
868
1866
|
let controller = defaultControllers.get(runtime);
|
|
@@ -871,6 +1869,7 @@ function getDefaultController(runtime, options = {}) {
|
|
|
871
1869
|
root: options.root,
|
|
872
1870
|
selector: options.selector,
|
|
873
1871
|
executeEntries: options.executeEntries,
|
|
1872
|
+
executeRenders: options.executeRenders,
|
|
874
1873
|
fetch: options.fetch,
|
|
875
1874
|
baseUrl: options.baseUrl,
|
|
876
1875
|
MutationObserver: options.MutationObserver,
|
|
@@ -923,6 +1922,7 @@ function mergeOptions(controller, options) {
|
|
|
923
1922
|
root: options.root ?? controller.root,
|
|
924
1923
|
selector: options.selector ?? controller.selector,
|
|
925
1924
|
executeEntries: options.executeEntries ?? controller.executeEntries,
|
|
1925
|
+
executeRenders: options.executeRenders ?? controller.executeRenders,
|
|
926
1926
|
fetch: options.fetch ?? controller.fetch,
|
|
927
1927
|
baseUrl: options.baseUrl ?? controller.baseUrl,
|
|
928
1928
|
MutationObserver: options.MutationObserver ?? controller.MutationObserver,
|
|
@@ -937,6 +1937,8 @@ function emptyRegistrationResult() {
|
|
|
937
1937
|
modules: [],
|
|
938
1938
|
entries: [],
|
|
939
1939
|
executed: [],
|
|
1940
|
+
renders: [],
|
|
1941
|
+
rendered: [],
|
|
940
1942
|
};
|
|
941
1943
|
}
|
|
942
1944
|
|
|
@@ -1037,9 +2039,15 @@ function resolveSourceUrl(src, baseUrl) {
|
|
|
1037
2039
|
}
|
|
1038
2040
|
}
|
|
1039
2041
|
|
|
1040
|
-
function createAnonymousId(format) {
|
|
2042
|
+
function createAnonymousId(format, kind = "entry") {
|
|
1041
2043
|
const extension = format === "js" ? "js" : "jsx";
|
|
1042
|
-
return `/__solid_tag_runtime__/html
|
|
2044
|
+
return `/__solid_tag_runtime__/html-${kind}-${++anonymousSequence}.${extension}`;
|
|
2045
|
+
}
|
|
2046
|
+
|
|
2047
|
+
function inspectRenderAttribute(element) {
|
|
2048
|
+
if (!hasAttribute(element, "render")) return undefined;
|
|
2049
|
+
const value = element.getAttribute("render");
|
|
2050
|
+
return value == null || value === "" ? true : String(value);
|
|
1043
2051
|
}
|
|
1044
2052
|
|
|
1045
2053
|
function stripQueryAndHash(value) {
|