solid-tag-runtime 0.0.8 → 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 +53 -1
- package/README.md +157 -3
- package/html.d.ts +119 -65
- package/package.json +1 -1
- package/src/html.js +798 -65
- package/src/render.js +205 -0
package/src/html.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { SolidTagRuntimeError } from "./errors.js";
|
|
2
2
|
import { createEventDispatcher } from "./events.js";
|
|
3
|
+
import { HTMLRenderError, createDefaultHTMLRenderAdapter, resolveRenderComponent } from "./render.js";
|
|
3
4
|
|
|
4
5
|
const DEFAULT_SELECTOR = [
|
|
5
6
|
'script[type="solid-jsx"]',
|
|
@@ -13,6 +14,9 @@ const DEFAULT_SELECTOR = [
|
|
|
13
14
|
const RUNTIME_SCOPE_ATTRIBUTE = "data-solid-runtime";
|
|
14
15
|
const elementOwners = new WeakMap();
|
|
15
16
|
const defaultControllers = new WeakMap();
|
|
17
|
+
const activeControllers = new Set();
|
|
18
|
+
const solidRenderClasses = new WeakMap();
|
|
19
|
+
const solidRenderInternals = new WeakMap();
|
|
16
20
|
let anonymousSequence = 0;
|
|
17
21
|
let controllerSequence = 0;
|
|
18
22
|
|
|
@@ -35,6 +39,8 @@ export class HTMLModuleOwnershipError extends HTMLModuleError {
|
|
|
35
39
|
}
|
|
36
40
|
}
|
|
37
41
|
|
|
42
|
+
export { HTMLRenderError };
|
|
43
|
+
|
|
38
44
|
/**
|
|
39
45
|
* Create a persistent DOM/HTML adapter for one solid-tag-runtime instance.
|
|
40
46
|
*
|
|
@@ -58,13 +64,17 @@ export function createHTMLRuntime(runtime, options = {}) {
|
|
|
58
64
|
selector: options.selector ?? DEFAULT_SELECTOR,
|
|
59
65
|
acceptUnscoped: options.acceptUnscoped !== false,
|
|
60
66
|
executeEntries: options.executeEntries !== false,
|
|
67
|
+
executeRenders: options.executeRenders !== false,
|
|
61
68
|
fetch: options.fetch,
|
|
62
69
|
baseUrl: options.baseUrl,
|
|
63
70
|
MutationObserver: options.MutationObserver,
|
|
64
71
|
subtree: options.subtree !== false,
|
|
65
72
|
createElement: options.createElement,
|
|
66
73
|
appendTo: options.appendTo,
|
|
74
|
+
renderAdapter: options.renderAdapter ?? createDefaultHTMLRenderAdapter(runtime),
|
|
67
75
|
bindings: new Map(),
|
|
76
|
+
renderElements: new Set(),
|
|
77
|
+
disposed: false,
|
|
68
78
|
observer: undefined,
|
|
69
79
|
observerRoot: undefined,
|
|
70
80
|
observerOptions: undefined,
|
|
@@ -111,9 +121,18 @@ export function createHTMLRuntime(runtime, options = {}) {
|
|
|
111
121
|
owns,
|
|
112
122
|
getModuleId,
|
|
113
123
|
getElement,
|
|
124
|
+
dispose,
|
|
114
125
|
};
|
|
115
126
|
|
|
116
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
|
+
});
|
|
117
136
|
return api;
|
|
118
137
|
|
|
119
138
|
function subscribe(typesOrListener, maybeListener) {
|
|
@@ -141,12 +160,15 @@ export function createHTMLRuntime(runtime, options = {}) {
|
|
|
141
160
|
}
|
|
142
161
|
|
|
143
162
|
try {
|
|
144
|
-
|
|
163
|
+
const result = await processElements(controller, elements, {
|
|
145
164
|
...callOptions,
|
|
146
165
|
root,
|
|
147
166
|
origin,
|
|
148
167
|
executeEntries: callOptions.executeEntries ?? controller.executeEntries,
|
|
168
|
+
executeRenders: callOptions.executeRenders ?? controller.executeRenders,
|
|
149
169
|
});
|
|
170
|
+
registerSolidRenderElement();
|
|
171
|
+
return result;
|
|
150
172
|
} catch (error) {
|
|
151
173
|
emitHTMLError(controller, "register", error, { origin });
|
|
152
174
|
throw error;
|
|
@@ -168,6 +190,7 @@ export function createHTMLRuntime(runtime, options = {}) {
|
|
|
168
190
|
registerExisting: callOptions.registerExisting !== false,
|
|
169
191
|
initial: true,
|
|
170
192
|
});
|
|
193
|
+
registerSolidRenderElement();
|
|
171
194
|
} catch (error) {
|
|
172
195
|
disconnect();
|
|
173
196
|
throw error;
|
|
@@ -194,6 +217,10 @@ export function createHTMLRuntime(runtime, options = {}) {
|
|
|
194
217
|
stopObserver(controller);
|
|
195
218
|
}
|
|
196
219
|
|
|
220
|
+
async function dispose() {
|
|
221
|
+
await disposeController(controller, { runtimeDisposed: false });
|
|
222
|
+
}
|
|
223
|
+
|
|
197
224
|
/**
|
|
198
225
|
* Change the discovery/observation root. If observation is active, it is
|
|
199
226
|
* rebound to the new root and existing matching scripts are registered by
|
|
@@ -297,6 +324,7 @@ export function createHTMLRuntime(runtime, options = {}) {
|
|
|
297
324
|
origin: callOptions.origin ?? "define-element",
|
|
298
325
|
explicit: true,
|
|
299
326
|
executeEntries: false,
|
|
327
|
+
executeRenders: false,
|
|
300
328
|
});
|
|
301
329
|
return result.modules[0] ?? definitionFromOwnedElement(controller, element);
|
|
302
330
|
}
|
|
@@ -308,6 +336,7 @@ export function createHTMLRuntime(runtime, options = {}) {
|
|
|
308
336
|
origin: callOptions.origin ?? "register-element",
|
|
309
337
|
explicit: true,
|
|
310
338
|
executeEntries: callOptions.executeEntries ?? controller.executeEntries,
|
|
339
|
+
executeRenders: callOptions.executeRenders ?? controller.executeRenders,
|
|
311
340
|
});
|
|
312
341
|
return result.modules[0] ?? definitionFromOwnedElement(controller, element);
|
|
313
342
|
}
|
|
@@ -342,15 +371,30 @@ export function createHTMLRuntime(runtime, options = {}) {
|
|
|
342
371
|
...record,
|
|
343
372
|
format: metadata.format,
|
|
344
373
|
entry: metadata.entry,
|
|
374
|
+
render: metadata.render,
|
|
375
|
+
component: metadata.component,
|
|
345
376
|
sourceUrl: metadata.sourceUrl,
|
|
346
377
|
inline: !metadata.sourceUrl,
|
|
347
378
|
};
|
|
348
379
|
|
|
349
380
|
const { source } = await prepareRecordSource(controller, candidate, callOptions);
|
|
381
|
+
const previousRender = record.render;
|
|
350
382
|
runtime.update(record.moduleId, source, { format: candidate.format });
|
|
351
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
|
+
|
|
352
394
|
record.format = candidate.format;
|
|
353
395
|
record.entry = candidate.entry;
|
|
396
|
+
record.render = candidate.render;
|
|
397
|
+
record.component = candidate.component;
|
|
354
398
|
record.sourceUrl = candidate.sourceUrl;
|
|
355
399
|
record.inline = candidate.inline;
|
|
356
400
|
record.state = "defined";
|
|
@@ -358,33 +402,15 @@ export function createHTMLRuntime(runtime, options = {}) {
|
|
|
358
402
|
record.definition = definitionFromRecord(record);
|
|
359
403
|
|
|
360
404
|
if (record.entry && (callOptions.executeEntries ?? controller.executeEntries) !== false) {
|
|
361
|
-
record
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
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,
|
|
366
412
|
origin: callOptions.origin ?? "update-element",
|
|
367
413
|
});
|
|
368
|
-
try {
|
|
369
|
-
const namespace = await runtime.import(record.moduleId);
|
|
370
|
-
record.state = "ready";
|
|
371
|
-
emitHTML(controller, {
|
|
372
|
-
type: "entry-executed",
|
|
373
|
-
element,
|
|
374
|
-
moduleId: record.moduleId,
|
|
375
|
-
namespace,
|
|
376
|
-
origin: callOptions.origin ?? "update-element",
|
|
377
|
-
});
|
|
378
|
-
} catch (error) {
|
|
379
|
-
record.state = "error";
|
|
380
|
-
record.error = error;
|
|
381
|
-
emitHTMLError(controller, "entry", error, {
|
|
382
|
-
element,
|
|
383
|
-
moduleId: record.moduleId,
|
|
384
|
-
origin: callOptions.origin ?? "update-element",
|
|
385
|
-
});
|
|
386
|
-
throw error;
|
|
387
|
-
}
|
|
388
414
|
}
|
|
389
415
|
|
|
390
416
|
emitHTML(controller, {
|
|
@@ -417,6 +443,14 @@ export function createHTMLRuntime(runtime, options = {}) {
|
|
|
417
443
|
const removeFromDOM = callOptions.removeFromDOM !== false;
|
|
418
444
|
const invalidateDependents = callOptions.invalidateDependents !== false;
|
|
419
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
|
+
|
|
420
454
|
let moduleRemoved = false;
|
|
421
455
|
if (removeModule) {
|
|
422
456
|
moduleRemoved = runtime.remove(record.moduleId, { invalidateDependents });
|
|
@@ -434,7 +468,7 @@ export function createHTMLRuntime(runtime, options = {}) {
|
|
|
434
468
|
|
|
435
469
|
let elementRemoved = false;
|
|
436
470
|
if (removeFromDOM) {
|
|
437
|
-
elementRemoved = removeDOMElement(element);
|
|
471
|
+
elementRemoved = record.render === true ? true : removeDOMElement(element);
|
|
438
472
|
} else {
|
|
439
473
|
// Keep a released element in the observed tree from being immediately
|
|
440
474
|
// rediscovered. Explicit registerElement() can still claim it again.
|
|
@@ -477,6 +511,7 @@ export function createHTMLRuntime(runtime, options = {}) {
|
|
|
477
511
|
...callOptions,
|
|
478
512
|
origin: callOptions.origin ?? "append",
|
|
479
513
|
executeEntries: callOptions.executeEntries ?? controller.executeEntries,
|
|
514
|
+
executeRenders: callOptions.executeRenders ?? controller.executeRenders,
|
|
480
515
|
});
|
|
481
516
|
|
|
482
517
|
if (!claim.fresh) await claim.record.promise;
|
|
@@ -515,6 +550,9 @@ export function createHTMLRuntime(runtime, options = {}) {
|
|
|
515
550
|
if (id) element.setAttribute("module", id);
|
|
516
551
|
if (moduleOptions.src) element.setAttribute("src", String(moduleOptions.src));
|
|
517
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));
|
|
518
556
|
if (moduleOptions.language) element.setAttribute("language", String(moduleOptions.language));
|
|
519
557
|
if (moduleOptions.source != null) element.textContent = String(moduleOptions.source);
|
|
520
558
|
|
|
@@ -571,6 +609,399 @@ export async function defineScript(runtime, element, options = {}) {
|
|
|
571
609
|
export const htmlModuleSelector = DEFAULT_SELECTOR;
|
|
572
610
|
export const htmlRuntimeScopeAttribute = RUNTIME_SCOPE_ATTRIBUTE;
|
|
573
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
|
+
|
|
574
1005
|
async function connectObserver(controller, root, callOptions = {}, mode = {}) {
|
|
575
1006
|
const MutationObserverImpl = callOptions.MutationObserver ?? controller.MutationObserver ?? globalThis.MutationObserver;
|
|
576
1007
|
|
|
@@ -696,12 +1127,15 @@ async function enqueueObserverScan(controller, callOptions = {}) {
|
|
|
696
1127
|
root,
|
|
697
1128
|
origin,
|
|
698
1129
|
executeEntries: callOptions.executeEntries ?? controller.executeEntries,
|
|
1130
|
+
executeRenders: callOptions.executeRenders ?? controller.executeRenders,
|
|
699
1131
|
});
|
|
700
1132
|
} catch (error) {
|
|
701
1133
|
emitHTMLError(controller, "observer", error, { origin });
|
|
702
1134
|
throw error;
|
|
703
1135
|
}
|
|
704
1136
|
|
|
1137
|
+
await cleanupDetachedRenderedDeclarations(controller, root);
|
|
1138
|
+
|
|
705
1139
|
emitHTML(controller, {
|
|
706
1140
|
type: "observer-batch",
|
|
707
1141
|
root,
|
|
@@ -804,52 +1238,324 @@ async function processClaims(controller, records, options = {}) {
|
|
|
804
1238
|
}
|
|
805
1239
|
|
|
806
1240
|
const entries = modules.filter(module => module.entry);
|
|
1241
|
+
const renders = modules.filter(module => module.render !== undefined);
|
|
807
1242
|
const executed = [];
|
|
1243
|
+
const rendered = [];
|
|
808
1244
|
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
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;
|
|
813
1250
|
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
const namespace = await controller.runtime.import(entry.id);
|
|
822
|
-
if (record) record.state = "ready";
|
|
823
|
-
executed.push({ id: entry.id, namespace });
|
|
824
|
-
emitHTML(controller, {
|
|
825
|
-
type: "entry-executed",
|
|
826
|
-
element: entry.element,
|
|
827
|
-
moduleId: entry.id,
|
|
828
|
-
namespace,
|
|
829
|
-
origin: record?.origin ?? options.origin ?? "entry",
|
|
830
|
-
});
|
|
831
|
-
} catch (error) {
|
|
832
|
-
if (record) {
|
|
833
|
-
record.state = "error";
|
|
834
|
-
record.error = error;
|
|
835
|
-
}
|
|
836
|
-
emitHTMLError(controller, "entry", error, {
|
|
837
|
-
element: entry.element,
|
|
838
|
-
moduleId: entry.id,
|
|
839
|
-
origin: record?.origin ?? options.origin ?? "entry",
|
|
840
|
-
});
|
|
841
|
-
throw error;
|
|
842
|
-
}
|
|
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));
|
|
843
1258
|
}
|
|
844
1259
|
}
|
|
845
1260
|
|
|
1261
|
+
registerSolidRenderElement();
|
|
846
1262
|
return {
|
|
847
1263
|
modules,
|
|
848
1264
|
entries: entries.map(entry => entry.id),
|
|
849
1265
|
executed,
|
|
1266
|
+
renders: renders.map(render => render.id),
|
|
1267
|
+
rendered,
|
|
850
1268
|
};
|
|
851
1269
|
}
|
|
852
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
|
+
|
|
853
1559
|
function claimElement(controller, element, options = {}) {
|
|
854
1560
|
assertElement(element, options.origin ?? "HTML runtime");
|
|
855
1561
|
|
|
@@ -897,8 +1603,12 @@ function claimElement(controller, element, options = {}) {
|
|
|
897
1603
|
moduleId,
|
|
898
1604
|
format: metadata.format,
|
|
899
1605
|
entry: metadata.entry,
|
|
1606
|
+
render: metadata.render,
|
|
1607
|
+
component: metadata.component,
|
|
900
1608
|
sourceUrl: metadata.sourceUrl,
|
|
901
1609
|
inline: !metadata.sourceUrl,
|
|
1610
|
+
mount: undefined,
|
|
1611
|
+
range: undefined,
|
|
902
1612
|
state: "claimed",
|
|
903
1613
|
origin: options.origin ?? "unknown",
|
|
904
1614
|
promise,
|
|
@@ -916,6 +1626,8 @@ function claimElement(controller, element, options = {}) {
|
|
|
916
1626
|
moduleId,
|
|
917
1627
|
format: record.format,
|
|
918
1628
|
entry: record.entry,
|
|
1629
|
+
render: record.render,
|
|
1630
|
+
component: record.component,
|
|
919
1631
|
sourceUrl: record.sourceUrl,
|
|
920
1632
|
origin: record.origin,
|
|
921
1633
|
});
|
|
@@ -960,6 +1672,14 @@ async function prepareRecordSource(controller, record, options) {
|
|
|
960
1672
|
function inspectElement(controller, element, options = {}) {
|
|
961
1673
|
const type = normalizeType(element.getAttribute("type"));
|
|
962
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
|
+
}
|
|
963
1683
|
const srcAttribute = nonEmpty(element.getAttribute("src"));
|
|
964
1684
|
const explicitId = nonEmpty(element.getAttribute("module"));
|
|
965
1685
|
const baseUrl = options.baseUrl
|
|
@@ -977,15 +1697,16 @@ function inspectElement(controller, element, options = {}) {
|
|
|
977
1697
|
sourceUrl,
|
|
978
1698
|
});
|
|
979
1699
|
|
|
980
|
-
const
|
|
1700
|
+
const action = entry ? "entry" : render !== undefined ? "render" : undefined;
|
|
1701
|
+
const id = explicitId ?? sourceUrl ?? (action ? options.existingId ?? createAnonymousId(format, action) : undefined);
|
|
981
1702
|
if (!id) {
|
|
982
1703
|
throw new HTMLModuleError(
|
|
983
|
-
"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`.",
|
|
984
1705
|
{ element },
|
|
985
1706
|
);
|
|
986
1707
|
}
|
|
987
1708
|
|
|
988
|
-
return { id, format, entry, sourceUrl };
|
|
1709
|
+
return { id, format, entry, render, component, sourceUrl };
|
|
989
1710
|
}
|
|
990
1711
|
|
|
991
1712
|
function requireOwnedRecord(controller, element, caller) {
|
|
@@ -1022,6 +1743,8 @@ function definitionFromRecord(record) {
|
|
|
1022
1743
|
id: record.moduleId,
|
|
1023
1744
|
format: record.format,
|
|
1024
1745
|
entry: record.entry,
|
|
1746
|
+
render: record.render,
|
|
1747
|
+
component: record.component,
|
|
1025
1748
|
sourceUrl: record.sourceUrl,
|
|
1026
1749
|
inline: record.inline,
|
|
1027
1750
|
element: record.element,
|
|
@@ -1146,6 +1869,7 @@ function getDefaultController(runtime, options = {}) {
|
|
|
1146
1869
|
root: options.root,
|
|
1147
1870
|
selector: options.selector,
|
|
1148
1871
|
executeEntries: options.executeEntries,
|
|
1872
|
+
executeRenders: options.executeRenders,
|
|
1149
1873
|
fetch: options.fetch,
|
|
1150
1874
|
baseUrl: options.baseUrl,
|
|
1151
1875
|
MutationObserver: options.MutationObserver,
|
|
@@ -1198,6 +1922,7 @@ function mergeOptions(controller, options) {
|
|
|
1198
1922
|
root: options.root ?? controller.root,
|
|
1199
1923
|
selector: options.selector ?? controller.selector,
|
|
1200
1924
|
executeEntries: options.executeEntries ?? controller.executeEntries,
|
|
1925
|
+
executeRenders: options.executeRenders ?? controller.executeRenders,
|
|
1201
1926
|
fetch: options.fetch ?? controller.fetch,
|
|
1202
1927
|
baseUrl: options.baseUrl ?? controller.baseUrl,
|
|
1203
1928
|
MutationObserver: options.MutationObserver ?? controller.MutationObserver,
|
|
@@ -1212,6 +1937,8 @@ function emptyRegistrationResult() {
|
|
|
1212
1937
|
modules: [],
|
|
1213
1938
|
entries: [],
|
|
1214
1939
|
executed: [],
|
|
1940
|
+
renders: [],
|
|
1941
|
+
rendered: [],
|
|
1215
1942
|
};
|
|
1216
1943
|
}
|
|
1217
1944
|
|
|
@@ -1312,9 +2039,15 @@ function resolveSourceUrl(src, baseUrl) {
|
|
|
1312
2039
|
}
|
|
1313
2040
|
}
|
|
1314
2041
|
|
|
1315
|
-
function createAnonymousId(format) {
|
|
2042
|
+
function createAnonymousId(format, kind = "entry") {
|
|
1316
2043
|
const extension = format === "js" ? "js" : "jsx";
|
|
1317
|
-
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);
|
|
1318
2051
|
}
|
|
1319
2052
|
|
|
1320
2053
|
function stripQueryAndHash(value) {
|