solid-tag-runtime 0.0.15 → 0.0.18
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 +131 -4
- package/README.md +47 -3
- package/bench/observer.mjs +103 -0
- package/docs/api/html.md +33 -2
- package/docs/getting-started.md +18 -3
- package/docs/html-runtime.md +41 -5
- package/docs/lifecycle-events.md +6 -1
- package/docs/rendering.md +4 -0
- package/docs/solid-render.md +227 -29
- package/docs/solid-runtime-setup.md +1 -1
- package/examples/observer-strategies.js +19 -0
- package/examples/render.html +7 -2
- package/html.d.ts +29 -0
- package/package.json +5 -3
- package/src/html/data-literal.js +270 -0
- package/src/html.js +546 -97
package/src/html.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { ModuleResolutionError, SolidTagRuntimeError } from "./errors.js";
|
|
2
2
|
import { createEventDispatcher } from "./events.js";
|
|
3
3
|
import { HTMLRenderError, createDefaultHTMLRenderAdapter, resolveRenderComponent } from "./render.js";
|
|
4
|
+
import { parseHTMLDataLiteral, parseHTMLPropsObject } from "./html/data-literal.js";
|
|
4
5
|
|
|
5
6
|
const DEFAULT_SELECTOR = [
|
|
6
7
|
'script[type="solid-jsx"]',
|
|
@@ -12,6 +13,10 @@ const DEFAULT_SELECTOR = [
|
|
|
12
13
|
].join(",");
|
|
13
14
|
|
|
14
15
|
const RUNTIME_SCOPE_ATTRIBUTE = "data-solid-runtime";
|
|
16
|
+
const SOLID_RENDER_STATE_ATTRIBUTE = "data-solid-render-state";
|
|
17
|
+
const SOLID_RENDER_SHOW_UNTIL_READY_ATTRIBUTE = "show-until-ready";
|
|
18
|
+
const SOLID_RENDER_VISIBILITY_STYLE_ATTRIBUTE = "data-solid-tag-render-visibility";
|
|
19
|
+
const solidRenderVisibilityDocuments = new WeakSet();
|
|
15
20
|
const elementOwners = new WeakMap();
|
|
16
21
|
const defaultControllers = new WeakMap();
|
|
17
22
|
const activeControllers = new Set();
|
|
@@ -73,6 +78,7 @@ export function createHTMLRuntime(runtime, options = {}) {
|
|
|
73
78
|
createElement: options.createElement,
|
|
74
79
|
appendTo: options.appendTo,
|
|
75
80
|
renderAdapter: options.renderAdapter ?? createDefaultHTMLRenderAdapter(runtime),
|
|
81
|
+
parseProps: typeof options.parseProps === "function" ? options.parseProps : undefined,
|
|
76
82
|
bindings: new Map(),
|
|
77
83
|
renderElements: new Set(),
|
|
78
84
|
disposed: false,
|
|
@@ -83,6 +89,10 @@ export function createHTMLRuntime(runtime, options = {}) {
|
|
|
83
89
|
queue: Promise.resolve(),
|
|
84
90
|
lastError: undefined,
|
|
85
91
|
connected: false,
|
|
92
|
+
defaultObservationMode: normalizeObservationMode(options.mode),
|
|
93
|
+
defaultBootstrapIdleMs: options.idleMs == null ? 50 : normalizeBootstrapIdleMs(options.idleMs),
|
|
94
|
+
observationMode: "continuous",
|
|
95
|
+
bootstrapState: undefined,
|
|
86
96
|
registrationDepth: 0,
|
|
87
97
|
registrationPasses: 0,
|
|
88
98
|
solidRenderRetryScheduled: false,
|
|
@@ -130,6 +140,7 @@ export function createHTMLRuntime(runtime, options = {}) {
|
|
|
130
140
|
|
|
131
141
|
controller.api = api;
|
|
132
142
|
activeControllers.add(controller);
|
|
143
|
+
ensureSolidRenderVisibilityStyle(resolveControllerDocument(controller));
|
|
133
144
|
// Do not define/upgrade <solid-render> here. Existing declarative script
|
|
134
145
|
// modules must be installed first; otherwise connectedCallback() can import
|
|
135
146
|
// a module before register()/observe() has defined the preceding <script>.
|
|
@@ -202,8 +213,19 @@ export function createHTMLRuntime(runtime, options = {}) {
|
|
|
202
213
|
async function observe(callOptions = {}) {
|
|
203
214
|
if (controller.connected) return api;
|
|
204
215
|
|
|
216
|
+
const observationMode = normalizeObservationMode(callOptions.mode ?? controller.defaultObservationMode);
|
|
205
217
|
const root = getRoot(mergeOptions(controller, callOptions), "HTML runtime observe");
|
|
206
|
-
controller.
|
|
218
|
+
controller.observationMode = observationMode;
|
|
219
|
+
controller.observerOptions = { ...callOptions, mode: observationMode };
|
|
220
|
+
controller.bootstrapState = observationMode === "bootstrap"
|
|
221
|
+
? {
|
|
222
|
+
active: true,
|
|
223
|
+
activityVersion: 0,
|
|
224
|
+
idleMs: callOptions.idleMs == null
|
|
225
|
+
? controller.defaultBootstrapIdleMs
|
|
226
|
+
: normalizeBootstrapIdleMs(callOptions.idleMs),
|
|
227
|
+
}
|
|
228
|
+
: undefined;
|
|
207
229
|
const pendingBeforeUpgrade = collectConnectedSolidRenderElements(controller, root);
|
|
208
230
|
controller.registrationDepth += 1;
|
|
209
231
|
|
|
@@ -225,6 +247,10 @@ export function createHTMLRuntime(runtime, options = {}) {
|
|
|
225
247
|
controller.registrationDepth = Math.max(0, controller.registrationDepth - 1);
|
|
226
248
|
}
|
|
227
249
|
|
|
250
|
+
if (observationMode === "bootstrap") {
|
|
251
|
+
await finishBootstrapObservation(controller, root);
|
|
252
|
+
}
|
|
253
|
+
|
|
228
254
|
return api;
|
|
229
255
|
}
|
|
230
256
|
|
|
@@ -279,7 +305,10 @@ export function createHTMLRuntime(runtime, options = {}) {
|
|
|
279
305
|
const wasConnected = controller.connected;
|
|
280
306
|
const previousObserverOptions = controller.observerOptions ?? {};
|
|
281
307
|
|
|
282
|
-
if (wasConnected)
|
|
308
|
+
if (wasConnected) {
|
|
309
|
+
markBootstrapObserverActivity(controller);
|
|
310
|
+
stopObserver(controller, { preserveOptions: true, preserveBootstrap: true, reason: "root-change" });
|
|
311
|
+
}
|
|
283
312
|
|
|
284
313
|
controller.root = nextRoot;
|
|
285
314
|
if (Object.prototype.hasOwnProperty.call(callOptions, "appendTo")) {
|
|
@@ -645,6 +674,7 @@ export const htmlRuntimeScopeAttribute = RUNTIME_SCOPE_ATTRIBUTE;
|
|
|
645
674
|
* browser; programmatic-only applications may call it explicitly.
|
|
646
675
|
*/
|
|
647
676
|
export function registerSolidRenderElement(options = {}) {
|
|
677
|
+
ensureSolidRenderVisibilityStyle(options.document ?? globalThis.document);
|
|
648
678
|
const registry = options.customElements ?? globalThis.customElements;
|
|
649
679
|
const HTMLElementBase = options.HTMLElement ?? globalThis.HTMLElement;
|
|
650
680
|
if (!registry || typeof registry.define !== "function" || typeof registry.get !== "function" || typeof HTMLElementBase !== "function") {
|
|
@@ -658,7 +688,7 @@ export function registerSolidRenderElement(options = {}) {
|
|
|
658
688
|
|
|
659
689
|
class SolidRenderElement extends HTMLElementBase {
|
|
660
690
|
static get observedAttributes() {
|
|
661
|
-
return ["module", "component", RUNTIME_SCOPE_ATTRIBUTE];
|
|
691
|
+
return ["module", "component", RUNTIME_SCOPE_ATTRIBUTE, SOLID_RENDER_SHOW_UNTIL_READY_ATTRIBUTE];
|
|
662
692
|
}
|
|
663
693
|
|
|
664
694
|
constructor() {
|
|
@@ -674,13 +704,16 @@ export function registerSolidRenderElement(options = {}) {
|
|
|
674
704
|
propObserver: undefined,
|
|
675
705
|
pendingModuleResolution: undefined,
|
|
676
706
|
pendingCheckGeneration: undefined,
|
|
707
|
+
renderState: "pending",
|
|
677
708
|
});
|
|
678
709
|
}
|
|
679
710
|
|
|
680
711
|
connectedCallback() {
|
|
681
712
|
const state = getSolidRenderState(this);
|
|
682
713
|
state.connected = true;
|
|
714
|
+
setSolidRenderState(this, state, "pending");
|
|
683
715
|
captureSolidRenderChildren(this, state);
|
|
716
|
+
syncSolidRenderPendingContent(this, state);
|
|
684
717
|
startSolidRenderPropObserver(this, state);
|
|
685
718
|
void remountSolidRender(this, "connect");
|
|
686
719
|
}
|
|
@@ -698,9 +731,26 @@ export function registerSolidRenderElement(options = {}) {
|
|
|
698
731
|
if (oldValue === newValue) return;
|
|
699
732
|
const state = getSolidRenderState(this);
|
|
700
733
|
if (!state.connected) return;
|
|
734
|
+
if (name === SOLID_RENDER_SHOW_UNTIL_READY_ATTRIBUTE) {
|
|
735
|
+
syncSolidRenderPendingContent(this, state);
|
|
736
|
+
return;
|
|
737
|
+
}
|
|
701
738
|
void remountSolidRender(this, `identity:${name}`);
|
|
702
739
|
}
|
|
703
740
|
|
|
741
|
+
get renderState() {
|
|
742
|
+
return getSolidRenderState(this).renderState;
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
get hideUntilReady() {
|
|
746
|
+
return !this.hasAttribute?.(SOLID_RENDER_SHOW_UNTIL_READY_ATTRIBUTE);
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
set hideUntilReady(value) {
|
|
750
|
+
if (value === false) this.setAttribute?.(SOLID_RENDER_SHOW_UNTIL_READY_ATTRIBUTE, "");
|
|
751
|
+
else this.removeAttribute?.(SOLID_RENDER_SHOW_UNTIL_READY_ATTRIBUTE);
|
|
752
|
+
}
|
|
753
|
+
|
|
704
754
|
get props() {
|
|
705
755
|
return getSolidRenderState(this).programmaticProps;
|
|
706
756
|
}
|
|
@@ -731,12 +781,78 @@ function getSolidRenderState(element) {
|
|
|
731
781
|
propObserver: undefined,
|
|
732
782
|
pendingModuleResolution: undefined,
|
|
733
783
|
pendingCheckGeneration: undefined,
|
|
784
|
+
renderState: "pending",
|
|
734
785
|
};
|
|
735
786
|
solidRenderInternals.set(element, state);
|
|
736
787
|
}
|
|
737
788
|
return state;
|
|
738
789
|
}
|
|
739
790
|
|
|
791
|
+
|
|
792
|
+
function resolveControllerDocument(controller) {
|
|
793
|
+
const root = controller?.root ?? globalThis.document;
|
|
794
|
+
if (root && typeof root.createElement === "function" && (root.head || root.documentElement || root.body)) return root;
|
|
795
|
+
return root?.ownerDocument ?? globalThis.document;
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
function ensureSolidRenderVisibilityStyle(document) {
|
|
799
|
+
if (!document || (typeof document !== "object" && typeof document !== "function")) return;
|
|
800
|
+
if (solidRenderVisibilityDocuments.has(document)) return;
|
|
801
|
+
|
|
802
|
+
try {
|
|
803
|
+
if (document.querySelector?.(`style[${SOLID_RENDER_VISIBILITY_STYLE_ATTRIBUTE}]`)) {
|
|
804
|
+
solidRenderVisibilityDocuments.add(document);
|
|
805
|
+
return;
|
|
806
|
+
}
|
|
807
|
+
if (typeof document.createElement !== "function") return;
|
|
808
|
+
const style = document.createElement("style");
|
|
809
|
+
style.setAttribute?.(SOLID_RENDER_VISIBILITY_STYLE_ATTRIBUTE, "");
|
|
810
|
+
style.textContent = [
|
|
811
|
+
`solid-render:not([${SOLID_RENDER_SHOW_UNTIL_READY_ATTRIBUTE}]):not(:defined),`,
|
|
812
|
+
`solid-render:not([${SOLID_RENDER_SHOW_UNTIL_READY_ATTRIBUTE}])[${SOLID_RENDER_STATE_ATTRIBUTE}="pending"] {`,
|
|
813
|
+
" visibility: hidden !important;",
|
|
814
|
+
"}",
|
|
815
|
+
].join("\n");
|
|
816
|
+
const target = document.head ?? document.documentElement ?? document.body;
|
|
817
|
+
if (typeof target?.append === "function") target.append(style);
|
|
818
|
+
else if (typeof target?.appendChild === "function") target.appendChild(style);
|
|
819
|
+
else return;
|
|
820
|
+
solidRenderVisibilityDocuments.add(document);
|
|
821
|
+
} catch {
|
|
822
|
+
// Styling is a progressive enhancement for pre-upgrade visibility. The
|
|
823
|
+
// custom element still manages pending/ready/error state in JavaScript.
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
function setSolidRenderState(element, state, nextState) {
|
|
828
|
+
state.renderState = nextState;
|
|
829
|
+
element.setAttribute?.(SOLID_RENDER_STATE_ATTRIBUTE, nextState);
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
function cloneSolidRenderFallbackNodes(state) {
|
|
833
|
+
const clone = state.childrenTemplate?.cloneNode?.(true);
|
|
834
|
+
if (!clone) return [];
|
|
835
|
+
if (clone.childNodes) return Array.from(clone.childNodes);
|
|
836
|
+
return [clone];
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
function restoreSolidRenderFallback(element, state) {
|
|
840
|
+
if (!state.childrenTemplate) {
|
|
841
|
+
element.replaceChildren?.();
|
|
842
|
+
return;
|
|
843
|
+
}
|
|
844
|
+
element.replaceChildren?.(...cloneSolidRenderFallbackNodes(state));
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
function syncSolidRenderPendingContent(element, state) {
|
|
848
|
+
if (state.renderState !== "pending" || state.mount) return;
|
|
849
|
+
if (element.hasAttribute?.(SOLID_RENDER_SHOW_UNTIL_READY_ATTRIBUTE)) {
|
|
850
|
+
restoreSolidRenderFallback(element, state);
|
|
851
|
+
} else {
|
|
852
|
+
element.replaceChildren?.();
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
|
|
740
856
|
function captureSolidRenderChildren(element, state) {
|
|
741
857
|
if (state.captured) return;
|
|
742
858
|
state.captured = true;
|
|
@@ -762,7 +878,11 @@ function startSolidRenderPropObserver(element, state) {
|
|
|
762
878
|
if (typeof MutationObserverImpl !== "function") return;
|
|
763
879
|
|
|
764
880
|
const observer = new MutationObserverImpl(records => {
|
|
765
|
-
|
|
881
|
+
const relevant = Array.from(records ?? []).some(record => {
|
|
882
|
+
const name = String(record?.attributeName ?? "").toLowerCase();
|
|
883
|
+
return name === "props" || name.startsWith("prop:");
|
|
884
|
+
});
|
|
885
|
+
if (!relevant) return;
|
|
766
886
|
updateSolidRenderProps(element, state);
|
|
767
887
|
});
|
|
768
888
|
observer.observe(element, { attributes: true });
|
|
@@ -775,8 +895,10 @@ async function remountSolidRender(element, reason, options = {}) {
|
|
|
775
895
|
const generation = ++state.generation;
|
|
776
896
|
state.pendingModuleResolution = undefined;
|
|
777
897
|
state.pendingCheckGeneration = undefined;
|
|
898
|
+
setSolidRenderState(element, state, "pending");
|
|
778
899
|
await disposeSolidRenderMount(element, state, reason === "connect" ? "reconnect" : reason);
|
|
779
900
|
if (!state.connected || generation !== state.generation) return;
|
|
901
|
+
syncSolidRenderPendingContent(element, state);
|
|
780
902
|
|
|
781
903
|
let controller;
|
|
782
904
|
try {
|
|
@@ -790,7 +912,7 @@ async function remountSolidRender(element, reason, options = {}) {
|
|
|
790
912
|
}
|
|
791
913
|
const componentName = nonEmpty(element.getAttribute?.("component"));
|
|
792
914
|
const exportName = componentName ?? "default";
|
|
793
|
-
const props = buildSolidRenderProps(element, state);
|
|
915
|
+
const props = buildSolidRenderProps(element, state, controller);
|
|
794
916
|
|
|
795
917
|
emitHTML(controller, {
|
|
796
918
|
type: "render-mounting",
|
|
@@ -827,6 +949,7 @@ async function remountSolidRender(element, reason, options = {}) {
|
|
|
827
949
|
state.mount = mount;
|
|
828
950
|
state.pendingModuleResolution = undefined;
|
|
829
951
|
state.pendingCheckGeneration = undefined;
|
|
952
|
+
setSolidRenderState(element, state, "ready");
|
|
830
953
|
emitHTML(controller, {
|
|
831
954
|
type: "render-mounted",
|
|
832
955
|
element,
|
|
@@ -862,6 +985,8 @@ async function remountSolidRender(element, reason, options = {}) {
|
|
|
862
985
|
}
|
|
863
986
|
}
|
|
864
987
|
|
|
988
|
+
setSolidRenderState(element, state, "error");
|
|
989
|
+
if (!state.mount) restoreSolidRenderFallback(element, state);
|
|
865
990
|
if (controller) {
|
|
866
991
|
emitHTMLError(controller, "solid-render", error, {
|
|
867
992
|
element,
|
|
@@ -922,20 +1047,44 @@ async function disposeSolidRenderMount(element, state, reason = "dispose") {
|
|
|
922
1047
|
|
|
923
1048
|
function updateSolidRenderProps(element, state) {
|
|
924
1049
|
if (!state.mount?.updateProps) return;
|
|
925
|
-
state.
|
|
1050
|
+
let controller = state.controller;
|
|
1051
|
+
try {
|
|
1052
|
+
controller ??= resolveHTMLControllerForElement(element);
|
|
1053
|
+
const props = buildSolidRenderProps(element, state, controller);
|
|
1054
|
+
state.mount.updateProps(props);
|
|
1055
|
+
setSolidRenderState(element, state, "ready");
|
|
1056
|
+
} catch (error) {
|
|
1057
|
+
setSolidRenderState(element, state, "error");
|
|
1058
|
+
if (controller) {
|
|
1059
|
+
emitHTMLError(controller, "solid-render", error, {
|
|
1060
|
+
element,
|
|
1061
|
+
moduleId: nonEmpty(element.getAttribute?.("module")),
|
|
1062
|
+
origin: "solid-render",
|
|
1063
|
+
});
|
|
1064
|
+
}
|
|
1065
|
+
dispatchSolidRenderError(element, error);
|
|
1066
|
+
}
|
|
926
1067
|
}
|
|
927
1068
|
|
|
928
|
-
function buildSolidRenderProps(element, state) {
|
|
1069
|
+
function buildSolidRenderProps(element, state, controller) {
|
|
929
1070
|
const declarative = {};
|
|
1071
|
+
const bulk = parseSolidRenderBulkProps(element, controller);
|
|
1072
|
+
copyPropRecord(declarative, bulk);
|
|
1073
|
+
|
|
930
1074
|
for (const attribute of Array.from(element.attributes ?? [])) {
|
|
931
|
-
const
|
|
1075
|
+
const rawName = String(attribute?.name ?? "");
|
|
1076
|
+
const name = rawName.toLowerCase();
|
|
932
1077
|
if (!name.startsWith("prop:")) continue;
|
|
933
1078
|
const key = kebabToCamel(name.slice(5));
|
|
934
1079
|
if (!key) continue;
|
|
935
|
-
declarative[key] =
|
|
1080
|
+
declarative[key] = parseSolidRenderAttributeProp(element, controller, key, attribute.value);
|
|
936
1081
|
}
|
|
937
1082
|
|
|
938
|
-
if (
|
|
1083
|
+
if (
|
|
1084
|
+
state.childrenTemplate
|
|
1085
|
+
&& !("children" in declarative)
|
|
1086
|
+
&& !("children" in (state.programmaticProps ?? {}))
|
|
1087
|
+
) {
|
|
939
1088
|
Object.defineProperty(declarative, "children", {
|
|
940
1089
|
configurable: true,
|
|
941
1090
|
enumerable: true,
|
|
@@ -946,14 +1095,69 @@ function buildSolidRenderProps(element, state) {
|
|
|
946
1095
|
}
|
|
947
1096
|
|
|
948
1097
|
const merged = Object.create(null);
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
1098
|
+
copyPropRecord(merged, declarative);
|
|
1099
|
+
copyPropRecord(merged, state.programmaticProps ?? {});
|
|
1100
|
+
return merged;
|
|
1101
|
+
}
|
|
1102
|
+
|
|
1103
|
+
function copyPropRecord(target, source) {
|
|
1104
|
+
for (const key of Reflect.ownKeys(source ?? {})) {
|
|
1105
|
+
const descriptor = Object.getOwnPropertyDescriptor(source, key);
|
|
1106
|
+
if (descriptor) Object.defineProperty(target, key, descriptor);
|
|
952
1107
|
}
|
|
953
|
-
|
|
954
|
-
|
|
1108
|
+
return target;
|
|
1109
|
+
}
|
|
1110
|
+
|
|
1111
|
+
function parseSolidRenderBulkProps(element, controller) {
|
|
1112
|
+
const source = element.getAttribute?.("props");
|
|
1113
|
+
if (source == null || String(source).trim() === "") return {};
|
|
1114
|
+
const value = parseSolidRenderData(controller, String(source), {
|
|
1115
|
+
element,
|
|
1116
|
+
kind: "props",
|
|
1117
|
+
attribute: "props",
|
|
1118
|
+
});
|
|
1119
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
1120
|
+
throw new HTMLRenderError("<solid-render> `props` must parse to a top-level object.", { element });
|
|
1121
|
+
}
|
|
1122
|
+
return value;
|
|
1123
|
+
}
|
|
1124
|
+
|
|
1125
|
+
function parseSolidRenderAttributeProp(element, controller, key, source) {
|
|
1126
|
+
if (source === "") return true;
|
|
1127
|
+
const raw = String(source);
|
|
1128
|
+
const trimmed = raw.trim();
|
|
1129
|
+
if (!(trimmed.startsWith("{") && trimmed.endsWith("}"))) return raw;
|
|
1130
|
+
|
|
1131
|
+
const inner = trimmed.slice(1, -1).trim();
|
|
1132
|
+
if (!inner) {
|
|
1133
|
+
throw new HTMLRenderError(
|
|
1134
|
+
`<solid-render> prop:${key} uses an outer brace pair for typed data; the inner value cannot be empty.`,
|
|
1135
|
+
{ element },
|
|
1136
|
+
);
|
|
1137
|
+
}
|
|
1138
|
+
return parseSolidRenderData(controller, inner, {
|
|
1139
|
+
element,
|
|
1140
|
+
kind: "prop",
|
|
1141
|
+
attribute: `prop:${key}`,
|
|
1142
|
+
key,
|
|
1143
|
+
});
|
|
1144
|
+
}
|
|
1145
|
+
|
|
1146
|
+
function parseSolidRenderData(controller, source, context) {
|
|
1147
|
+
try {
|
|
1148
|
+
if (typeof controller?.parseProps === "function") {
|
|
1149
|
+
return controller.parseProps(source, context);
|
|
1150
|
+
}
|
|
1151
|
+
return context.kind === "props"
|
|
1152
|
+
? parseHTMLPropsObject(source)
|
|
1153
|
+
: parseHTMLDataLiteral(source);
|
|
1154
|
+
} catch (cause) {
|
|
1155
|
+
const label = context.kind === "props" ? "props" : context.attribute;
|
|
1156
|
+
throw new HTMLRenderError(
|
|
1157
|
+
`<solid-render> could not parse ${label} as declarative data: ${cause?.message ?? cause}`,
|
|
1158
|
+
{ element: context.element, cause },
|
|
1159
|
+
);
|
|
955
1160
|
}
|
|
956
|
-
return merged;
|
|
957
1161
|
}
|
|
958
1162
|
|
|
959
1163
|
function kebabToCamel(value) {
|
|
@@ -1196,6 +1400,80 @@ async function disposeController(controller, options = {}) {
|
|
|
1196
1400
|
controller.events.clear();
|
|
1197
1401
|
}
|
|
1198
1402
|
|
|
1403
|
+
function normalizeObservationMode(mode) {
|
|
1404
|
+
if (mode == null || mode === "continuous") return "continuous";
|
|
1405
|
+
if (mode === "bootstrap") return "bootstrap";
|
|
1406
|
+
throw new TypeError(`Unknown HTML observation mode ${JSON.stringify(mode)}. Expected "continuous" or "bootstrap".`);
|
|
1407
|
+
}
|
|
1408
|
+
|
|
1409
|
+
function normalizeBootstrapIdleMs(value) {
|
|
1410
|
+
if (value == null) return 50;
|
|
1411
|
+
const idleMs = Number(value);
|
|
1412
|
+
if (!Number.isFinite(idleMs) || idleMs < 0) {
|
|
1413
|
+
throw new TypeError("observe({ idleMs }) must be a finite non-negative number.");
|
|
1414
|
+
}
|
|
1415
|
+
return idleMs;
|
|
1416
|
+
}
|
|
1417
|
+
|
|
1418
|
+
function markBootstrapObserverActivity(controller) {
|
|
1419
|
+
const state = controller.bootstrapState;
|
|
1420
|
+
if (state?.active) state.activityVersion += 1;
|
|
1421
|
+
}
|
|
1422
|
+
|
|
1423
|
+
async function finishBootstrapObservation(controller, root) {
|
|
1424
|
+
const state = controller.bootstrapState;
|
|
1425
|
+
if (!state?.active) return;
|
|
1426
|
+
|
|
1427
|
+
await waitForBootstrapDOMReady(root);
|
|
1428
|
+
|
|
1429
|
+
while (state.active && !controller.disposed) {
|
|
1430
|
+
const version = state.activityVersion;
|
|
1431
|
+
await waitForBootstrapIdle(state.idleMs);
|
|
1432
|
+
await controller.queue;
|
|
1433
|
+
|
|
1434
|
+
if (controller.lastError) {
|
|
1435
|
+
const error = controller.lastError;
|
|
1436
|
+
controller.lastError = undefined;
|
|
1437
|
+
stopObserver(controller, { reason: "bootstrap-error" });
|
|
1438
|
+
throw error;
|
|
1439
|
+
}
|
|
1440
|
+
|
|
1441
|
+
if (!state.active || controller.disposed) return;
|
|
1442
|
+
if (state.activityVersion !== version) continue;
|
|
1443
|
+
|
|
1444
|
+
// setRoot() may briefly disconnect/reconnect while preserving bootstrap
|
|
1445
|
+
// state. Wait another turn rather than treating that transition as settled.
|
|
1446
|
+
if (!controller.connected) {
|
|
1447
|
+
await waitForBootstrapIdle(0);
|
|
1448
|
+
continue;
|
|
1449
|
+
}
|
|
1450
|
+
|
|
1451
|
+
stopObserver(controller, { reason: "bootstrap-idle" });
|
|
1452
|
+
return;
|
|
1453
|
+
}
|
|
1454
|
+
}
|
|
1455
|
+
|
|
1456
|
+
function waitForBootstrapIdle(idleMs) {
|
|
1457
|
+
return new Promise(resolve => {
|
|
1458
|
+
const schedule = globalThis.setTimeout ?? ((callback) => Promise.resolve().then(callback));
|
|
1459
|
+
schedule(resolve, idleMs);
|
|
1460
|
+
});
|
|
1461
|
+
}
|
|
1462
|
+
|
|
1463
|
+
function waitForBootstrapDOMReady(root) {
|
|
1464
|
+
const document = root?.nodeType === 9
|
|
1465
|
+
? root
|
|
1466
|
+
: root?.ownerDocument ?? (root === globalThis.document ? root : undefined);
|
|
1467
|
+
|
|
1468
|
+
if (!document || document.readyState !== "loading" || typeof document.addEventListener !== "function") {
|
|
1469
|
+
return Promise.resolve();
|
|
1470
|
+
}
|
|
1471
|
+
|
|
1472
|
+
return new Promise(resolve => {
|
|
1473
|
+
document.addEventListener("DOMContentLoaded", resolve, { once: true });
|
|
1474
|
+
});
|
|
1475
|
+
}
|
|
1476
|
+
|
|
1199
1477
|
async function connectObserver(controller, root, callOptions = {}, mode = {}) {
|
|
1200
1478
|
const MutationObserverImpl = callOptions.MutationObserver ?? controller.MutationObserver ?? globalThis.MutationObserver;
|
|
1201
1479
|
|
|
@@ -1220,8 +1498,16 @@ async function connectObserver(controller, root, callOptions = {}, mode = {}) {
|
|
|
1220
1498
|
}
|
|
1221
1499
|
|
|
1222
1500
|
const observer = new MutationObserverImpl(records => {
|
|
1223
|
-
|
|
1224
|
-
|
|
1501
|
+
const batch = collectObserverMutationBatch(controller, records, callOptions);
|
|
1502
|
+
if (!batch) return;
|
|
1503
|
+
markBootstrapObserverActivity(controller);
|
|
1504
|
+
|
|
1505
|
+
if (batch.requiresFallbackScan) {
|
|
1506
|
+
void enqueueObserverScan(controller, callOptions).catch(() => {});
|
|
1507
|
+
return;
|
|
1508
|
+
}
|
|
1509
|
+
|
|
1510
|
+
void enqueueObserverBatch(controller, batch, callOptions).catch(() => {});
|
|
1225
1511
|
});
|
|
1226
1512
|
|
|
1227
1513
|
controller.observer = observer;
|
|
@@ -1248,61 +1534,134 @@ async function connectObserver(controller, root, callOptions = {}, mode = {}) {
|
|
|
1248
1534
|
return result;
|
|
1249
1535
|
}
|
|
1250
1536
|
|
|
1251
|
-
function
|
|
1537
|
+
function collectObserverMutationBatch(controller, records, callOptions = {}) {
|
|
1252
1538
|
const mutations = Array.from(records ?? []);
|
|
1253
1539
|
|
|
1254
1540
|
// MutationObserver never invokes its callback with an empty batch, but custom
|
|
1255
1541
|
// test/host implementations sometimes do. Preserve the historical conservative
|
|
1256
|
-
// behavior for those implementations
|
|
1257
|
-
if (mutations.length === 0)
|
|
1542
|
+
// behavior for those implementations by falling back to a full scan.
|
|
1543
|
+
if (mutations.length === 0) {
|
|
1544
|
+
return {
|
|
1545
|
+
declarations: [],
|
|
1546
|
+
solidRenders: [],
|
|
1547
|
+
removedRecords: [],
|
|
1548
|
+
requiresFallbackScan: true,
|
|
1549
|
+
};
|
|
1550
|
+
}
|
|
1258
1551
|
|
|
1259
1552
|
const selector = callOptions.selector ?? controller.selector;
|
|
1553
|
+
const declarations = [];
|
|
1554
|
+
const declarationSet = new Set();
|
|
1555
|
+
const solidRenders = [];
|
|
1556
|
+
const solidRenderSet = new Set();
|
|
1557
|
+
const removedRecords = new Set();
|
|
1260
1558
|
|
|
1261
1559
|
for (const mutation of mutations) {
|
|
1262
|
-
if (!mutation || mutation.type !== "childList")
|
|
1560
|
+
if (!mutation || mutation.type !== "childList") {
|
|
1561
|
+
return { declarations, solidRenders, removedRecords: [...removedRecords], requiresFallbackScan: true };
|
|
1562
|
+
}
|
|
1263
1563
|
|
|
1264
1564
|
const addedNodes = mutation.addedNodes;
|
|
1265
1565
|
const removedNodes = mutation.removedNodes;
|
|
1266
|
-
if (addedNodes == null || removedNodes == null)
|
|
1566
|
+
if (addedNodes == null || removedNodes == null) {
|
|
1567
|
+
return { declarations, solidRenders, removedRecords: [...removedRecords], requiresFallbackScan: true };
|
|
1568
|
+
}
|
|
1267
1569
|
|
|
1268
1570
|
for (const node of Array.from(addedNodes)) {
|
|
1269
|
-
|
|
1571
|
+
const declarationMatches = collectNodeMatches(node, selector);
|
|
1572
|
+
if (declarationMatches.uncertain) {
|
|
1573
|
+
return { declarations, solidRenders, removedRecords: [...removedRecords], requiresFallbackScan: true };
|
|
1574
|
+
}
|
|
1575
|
+
for (const element of declarationMatches.elements) {
|
|
1576
|
+
// Explicit APIs claim before inserting into the DOM. If this controller
|
|
1577
|
+
// already owns the element, the observer has no discovery work to do.
|
|
1578
|
+
if (elementOwners.get(element)?.owner === controller) continue;
|
|
1579
|
+
if (controller.ignoredExisting.has(element)) continue;
|
|
1580
|
+
if (!declarationSet.has(element)) {
|
|
1581
|
+
declarationSet.add(element);
|
|
1582
|
+
declarations.push(element);
|
|
1583
|
+
}
|
|
1584
|
+
}
|
|
1270
1585
|
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1586
|
+
const renderMatches = collectNodeMatches(node, "solid-render");
|
|
1587
|
+
if (renderMatches.uncertain) {
|
|
1588
|
+
return { declarations, solidRenders, removedRecords: [...removedRecords], requiresFallbackScan: true };
|
|
1589
|
+
}
|
|
1590
|
+
for (const element of renderMatches.elements) {
|
|
1591
|
+
if (!solidRenderSet.has(element)) {
|
|
1592
|
+
solidRenderSet.add(element);
|
|
1593
|
+
solidRenders.push(element);
|
|
1594
|
+
}
|
|
1595
|
+
}
|
|
1274
1596
|
}
|
|
1275
1597
|
|
|
1276
1598
|
for (const node of Array.from(removedNodes)) {
|
|
1277
|
-
|
|
1599
|
+
const result = collectRemovedMountedRecords(controller, node, removedRecords);
|
|
1600
|
+
if (!result) {
|
|
1601
|
+
return { declarations, solidRenders, removedRecords: [...removedRecords], requiresFallbackScan: true };
|
|
1602
|
+
}
|
|
1278
1603
|
}
|
|
1279
1604
|
}
|
|
1280
1605
|
|
|
1281
|
-
|
|
1606
|
+
if (declarations.length === 0 && solidRenders.length === 0 && removedRecords.size === 0) {
|
|
1607
|
+
return undefined;
|
|
1608
|
+
}
|
|
1609
|
+
|
|
1610
|
+
return {
|
|
1611
|
+
declarations,
|
|
1612
|
+
solidRenders,
|
|
1613
|
+
removedRecords: [...removedRecords],
|
|
1614
|
+
requiresFallbackScan: false,
|
|
1615
|
+
};
|
|
1282
1616
|
}
|
|
1283
1617
|
|
|
1284
|
-
function
|
|
1285
|
-
|
|
1618
|
+
function collectNodeMatches(node, selector) {
|
|
1619
|
+
const elements = [];
|
|
1620
|
+
const seen = new Set();
|
|
1621
|
+
if (!node) return { elements, uncertain: false };
|
|
1286
1622
|
|
|
1623
|
+
let inspected = false;
|
|
1287
1624
|
try {
|
|
1288
|
-
if (typeof node.matches === "function"
|
|
1289
|
-
|
|
1290
|
-
|
|
1625
|
+
if (typeof node.matches === "function") {
|
|
1626
|
+
inspected = true;
|
|
1627
|
+
if (node.matches(selector)) {
|
|
1628
|
+
seen.add(node);
|
|
1629
|
+
elements.push(node);
|
|
1630
|
+
}
|
|
1631
|
+
}
|
|
1632
|
+
|
|
1633
|
+
if (typeof node.querySelectorAll === "function") {
|
|
1634
|
+
inspected = true;
|
|
1635
|
+
for (const element of Array.from(node.querySelectorAll(selector) ?? [])) {
|
|
1636
|
+
if (seen.has(element)) continue;
|
|
1637
|
+
seen.add(element);
|
|
1638
|
+
elements.push(element);
|
|
1639
|
+
}
|
|
1640
|
+
}
|
|
1291
1641
|
} catch {
|
|
1292
|
-
|
|
1293
|
-
// should keep the old conservative behavior rather than miss discovery.
|
|
1294
|
-
return true;
|
|
1642
|
+
return { elements, uncertain: true };
|
|
1295
1643
|
}
|
|
1296
1644
|
|
|
1297
|
-
|
|
1298
|
-
|
|
1645
|
+
if (inspected) return { elements, uncertain: false };
|
|
1646
|
+
|
|
1647
|
+
// Text/comment nodes cannot contain declarations. Element/document/fragment
|
|
1648
|
+
// nodes in a standards DOM should expose matches/querySelectorAll as
|
|
1649
|
+
// appropriate; a host object that does not is conservatively scanned.
|
|
1650
|
+
if (typeof node.nodeType === "number") {
|
|
1651
|
+
if (node.nodeType === 3 || node.nodeType === 4 || node.nodeType === 8) {
|
|
1652
|
+
return { elements, uncertain: false };
|
|
1653
|
+
}
|
|
1654
|
+
return { elements, uncertain: true };
|
|
1655
|
+
}
|
|
1299
1656
|
|
|
1300
|
-
|
|
1301
|
-
|
|
1657
|
+
return {
|
|
1658
|
+
elements,
|
|
1659
|
+
uncertain: typeof node.getAttribute === "function" || typeof node.contains === "function",
|
|
1660
|
+
};
|
|
1302
1661
|
}
|
|
1303
1662
|
|
|
1304
|
-
function
|
|
1305
|
-
if (!node) return
|
|
1663
|
+
function collectRemovedMountedRecords(controller, node, records) {
|
|
1664
|
+
if (!node) return true;
|
|
1306
1665
|
|
|
1307
1666
|
for (const record of controller.bindings.values()) {
|
|
1308
1667
|
if (!record.mount) continue;
|
|
@@ -1310,16 +1669,22 @@ function removedNodeContainsMountedDeclaration(controller, node) {
|
|
|
1310
1669
|
? record.range?.startMarker
|
|
1311
1670
|
: record.element;
|
|
1312
1671
|
if (!anchor) continue;
|
|
1313
|
-
if (node === anchor)
|
|
1672
|
+
if (node === anchor) {
|
|
1673
|
+
records.add(record);
|
|
1674
|
+
continue;
|
|
1675
|
+
}
|
|
1314
1676
|
|
|
1315
1677
|
try {
|
|
1316
|
-
if (typeof node.contains === "function" && node.contains(anchor))
|
|
1678
|
+
if (typeof node.contains === "function" && node.contains(anchor)) {
|
|
1679
|
+
records.add(record);
|
|
1680
|
+
}
|
|
1317
1681
|
} catch {
|
|
1318
|
-
return
|
|
1682
|
+
return false;
|
|
1319
1683
|
}
|
|
1320
1684
|
}
|
|
1321
1685
|
|
|
1322
|
-
return
|
|
1686
|
+
if (typeof node.nodeType === "number" || typeof node.contains === "function") return true;
|
|
1687
|
+
return typeof node.getAttribute !== "function";
|
|
1323
1688
|
}
|
|
1324
1689
|
|
|
1325
1690
|
function stopObserver(controller, options = {}) {
|
|
@@ -1330,6 +1695,10 @@ function stopObserver(controller, options = {}) {
|
|
|
1330
1695
|
controller.observer = undefined;
|
|
1331
1696
|
controller.observerRoot = undefined;
|
|
1332
1697
|
if (!options.preserveOptions) controller.observerOptions = undefined;
|
|
1698
|
+
if (!options.preserveBootstrap && controller.bootstrapState) {
|
|
1699
|
+
controller.bootstrapState.active = false;
|
|
1700
|
+
controller.bootstrapState = undefined;
|
|
1701
|
+
}
|
|
1333
1702
|
emitHTML(controller, {
|
|
1334
1703
|
type: "observer-disconnected",
|
|
1335
1704
|
root,
|
|
@@ -1345,54 +1714,56 @@ async function enqueueObserverScan(controller, callOptions = {}) {
|
|
|
1345
1714
|
const selector = callOptions.selector ?? controller.selector;
|
|
1346
1715
|
const origin = callOptions.origin ?? "observer";
|
|
1347
1716
|
const discovered = Array.from(root.querySelectorAll(selector));
|
|
1348
|
-
const
|
|
1349
|
-
let matched = 0;
|
|
1350
|
-
let ignored = 0;
|
|
1351
|
-
|
|
1352
|
-
for (const element of discovered) {
|
|
1353
|
-
if (controller.ignoredExisting.has(element)) {
|
|
1354
|
-
ignored += 1;
|
|
1355
|
-
emitHTML(controller, {
|
|
1356
|
-
type: "element-discovered",
|
|
1357
|
-
element,
|
|
1358
|
-
moduleId: nonEmpty(element?.getAttribute?.("module")),
|
|
1359
|
-
origin,
|
|
1360
|
-
accepted: false,
|
|
1361
|
-
});
|
|
1362
|
-
continue;
|
|
1363
|
-
}
|
|
1717
|
+
const classified = classifyObserverElements(controller, discovered, callOptions, origin);
|
|
1364
1718
|
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1719
|
+
let result;
|
|
1720
|
+
try {
|
|
1721
|
+
result = await processElements(controller, classified.elements, {
|
|
1722
|
+
...callOptions,
|
|
1723
|
+
root,
|
|
1370
1724
|
origin,
|
|
1371
|
-
|
|
1725
|
+
executeEntries: callOptions.executeEntries ?? controller.executeEntries,
|
|
1726
|
+
executeRenders: callOptions.executeRenders ?? controller.executeRenders,
|
|
1372
1727
|
});
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
}
|
|
1378
|
-
matched += 1;
|
|
1728
|
+
} catch (error) {
|
|
1729
|
+
emitHTMLError(controller, "observer", error, { origin });
|
|
1730
|
+
throw error;
|
|
1731
|
+
}
|
|
1379
1732
|
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
}
|
|
1385
|
-
|
|
1386
|
-
ignored += 1;
|
|
1387
|
-
continue;
|
|
1388
|
-
}
|
|
1733
|
+
await cleanupDetachedRenderedDeclarations(controller, root);
|
|
1734
|
+
await retrySolidRenderElements(
|
|
1735
|
+
controller,
|
|
1736
|
+
collectConnectedSolidRenderElements(controller, root),
|
|
1737
|
+
{ reason: "observer-batch-complete", forceMissingError: true },
|
|
1738
|
+
);
|
|
1389
1739
|
|
|
1390
|
-
|
|
1391
|
-
|
|
1740
|
+
emitObserverBatch(controller, {
|
|
1741
|
+
root,
|
|
1742
|
+
origin,
|
|
1743
|
+
discovered: discovered.length,
|
|
1744
|
+
matched: classified.matched,
|
|
1745
|
+
ignored: classified.ignored,
|
|
1746
|
+
result,
|
|
1747
|
+
});
|
|
1748
|
+
return result;
|
|
1749
|
+
});
|
|
1750
|
+
|
|
1751
|
+
trackObserverTask(controller, task, callOptions);
|
|
1752
|
+
return task;
|
|
1753
|
+
}
|
|
1754
|
+
|
|
1755
|
+
async function enqueueObserverBatch(controller, batch, callOptions = {}) {
|
|
1756
|
+
const task = controller.queue.then(async () => {
|
|
1757
|
+
if (!controller.connected) return emptyRegistrationResult();
|
|
1758
|
+
|
|
1759
|
+
const root = controller.observerRoot ?? getRoot(mergeOptions(controller, callOptions), "HTML runtime observer");
|
|
1760
|
+
const origin = callOptions.origin ?? "observer";
|
|
1761
|
+
const discovered = batch.declarations;
|
|
1762
|
+
const classified = classifyObserverElements(controller, discovered, callOptions, origin);
|
|
1392
1763
|
|
|
1393
1764
|
let result;
|
|
1394
1765
|
try {
|
|
1395
|
-
result = await processElements(controller, elements, {
|
|
1766
|
+
result = await processElements(controller, classified.elements, {
|
|
1396
1767
|
...callOptions,
|
|
1397
1768
|
root,
|
|
1398
1769
|
origin,
|
|
@@ -1404,26 +1775,106 @@ async function enqueueObserverScan(controller, callOptions = {}) {
|
|
|
1404
1775
|
throw error;
|
|
1405
1776
|
}
|
|
1406
1777
|
|
|
1407
|
-
await
|
|
1778
|
+
await cleanupRemovedRenderedDeclarations(controller, root, batch.removedRecords);
|
|
1408
1779
|
await retrySolidRenderElements(
|
|
1409
1780
|
controller,
|
|
1410
|
-
|
|
1781
|
+
batch.solidRenders,
|
|
1411
1782
|
{ reason: "observer-batch-complete", forceMissingError: true },
|
|
1412
1783
|
);
|
|
1413
1784
|
|
|
1414
|
-
|
|
1415
|
-
type: "observer-batch",
|
|
1785
|
+
emitObserverBatch(controller, {
|
|
1416
1786
|
root,
|
|
1417
1787
|
origin,
|
|
1418
1788
|
discovered: discovered.length,
|
|
1419
|
-
matched,
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
modules: result.modules.map(module => module.id),
|
|
1789
|
+
matched: classified.matched,
|
|
1790
|
+
ignored: classified.ignored,
|
|
1791
|
+
result,
|
|
1423
1792
|
});
|
|
1424
1793
|
return result;
|
|
1425
1794
|
});
|
|
1426
1795
|
|
|
1796
|
+
trackObserverTask(controller, task, callOptions);
|
|
1797
|
+
return task;
|
|
1798
|
+
}
|
|
1799
|
+
|
|
1800
|
+
function classifyObserverElements(controller, discovered, callOptions, origin) {
|
|
1801
|
+
const elements = [];
|
|
1802
|
+
let matched = 0;
|
|
1803
|
+
let ignored = 0;
|
|
1804
|
+
|
|
1805
|
+
for (const element of discovered) {
|
|
1806
|
+
if (controller.ignoredExisting.has(element)) {
|
|
1807
|
+
ignored += 1;
|
|
1808
|
+
emitHTML(controller, {
|
|
1809
|
+
type: "element-discovered",
|
|
1810
|
+
element,
|
|
1811
|
+
moduleId: nonEmpty(element?.getAttribute?.("module")),
|
|
1812
|
+
origin,
|
|
1813
|
+
accepted: false,
|
|
1814
|
+
});
|
|
1815
|
+
continue;
|
|
1816
|
+
}
|
|
1817
|
+
|
|
1818
|
+
const accepted = acceptsDiscoveredElement(controller, element, callOptions);
|
|
1819
|
+
emitHTML(controller, {
|
|
1820
|
+
type: "element-discovered",
|
|
1821
|
+
element,
|
|
1822
|
+
moduleId: nonEmpty(element?.getAttribute?.("module")),
|
|
1823
|
+
origin,
|
|
1824
|
+
accepted,
|
|
1825
|
+
});
|
|
1826
|
+
if (!accepted) {
|
|
1827
|
+
ignored += 1;
|
|
1828
|
+
maybeWarnUnresolvedRenderScope(controller, element, callOptions, origin);
|
|
1829
|
+
continue;
|
|
1830
|
+
}
|
|
1831
|
+
matched += 1;
|
|
1832
|
+
|
|
1833
|
+
const ownership = elementOwners.get(element);
|
|
1834
|
+
if (!ownership) {
|
|
1835
|
+
elements.push(element);
|
|
1836
|
+
continue;
|
|
1837
|
+
}
|
|
1838
|
+
if (ownership.owner === controller) {
|
|
1839
|
+
ignored += 1;
|
|
1840
|
+
continue;
|
|
1841
|
+
}
|
|
1842
|
+
|
|
1843
|
+
throw ownershipError(controller, element, ownership);
|
|
1844
|
+
}
|
|
1845
|
+
|
|
1846
|
+
return { elements, matched, ignored };
|
|
1847
|
+
}
|
|
1848
|
+
|
|
1849
|
+
async function cleanupRemovedRenderedDeclarations(controller, root, records) {
|
|
1850
|
+
for (const record of records ?? []) {
|
|
1851
|
+
if (!record?.mount) continue;
|
|
1852
|
+
const anchor = record.mount.mode === "in-place"
|
|
1853
|
+
? record.range?.startMarker
|
|
1854
|
+
: record.element;
|
|
1855
|
+
if (!anchor || rootContainsElement(root, anchor)) continue;
|
|
1856
|
+
|
|
1857
|
+
await disposeRecordMount(controller, record, {
|
|
1858
|
+
reason: "declaration-detached",
|
|
1859
|
+
removeAnchors: false,
|
|
1860
|
+
});
|
|
1861
|
+
}
|
|
1862
|
+
}
|
|
1863
|
+
|
|
1864
|
+
function emitObserverBatch(controller, { root, origin, discovered, matched, ignored, result }) {
|
|
1865
|
+
emitHTML(controller, {
|
|
1866
|
+
type: "observer-batch",
|
|
1867
|
+
root,
|
|
1868
|
+
origin,
|
|
1869
|
+
discovered,
|
|
1870
|
+
matched,
|
|
1871
|
+
claimed: result.modules.length,
|
|
1872
|
+
ignored,
|
|
1873
|
+
modules: result.modules.map(module => module.id),
|
|
1874
|
+
});
|
|
1875
|
+
}
|
|
1876
|
+
|
|
1877
|
+
function trackObserverTask(controller, task, callOptions) {
|
|
1427
1878
|
controller.queue = task.then(
|
|
1428
1879
|
() => undefined,
|
|
1429
1880
|
error => {
|
|
@@ -1435,8 +1886,6 @@ async function enqueueObserverScan(controller, callOptions = {}) {
|
|
|
1435
1886
|
}
|
|
1436
1887
|
},
|
|
1437
1888
|
);
|
|
1438
|
-
|
|
1439
|
-
return task;
|
|
1440
1889
|
}
|
|
1441
1890
|
|
|
1442
1891
|
async function processElements(controller, elements, options = {}) {
|