solid-tag-runtime 0.0.5 → 0.0.6
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 +54 -0
- package/README.md +63 -1
- package/html.d.ts +33 -0
- package/package.json +1 -1
- package/src/html.js +147 -44
package/ARCHITECTURE.md
CHANGED
|
@@ -235,6 +235,10 @@ html.observe(options?);
|
|
|
235
235
|
html.flush(options?);
|
|
236
236
|
html.disconnect();
|
|
237
237
|
|
|
238
|
+
await html.setRoot(root, options?);
|
|
239
|
+
await html.moveTo(root, options?);
|
|
240
|
+
html.setAppendTarget(target);
|
|
241
|
+
|
|
238
242
|
html.defineElement(element, options?);
|
|
239
243
|
html.registerElement(element, options?);
|
|
240
244
|
html.append(element, options?);
|
|
@@ -321,6 +325,45 @@ A scoped controller accepts matching scripts. It may also claim unscoped scripts
|
|
|
321
325
|
|
|
322
326
|
An unscoped controller does not silently claim a script carrying `data-solid-runtime` for another runtime.
|
|
323
327
|
|
|
328
|
+
|
|
329
|
+
### Mutable root and append-target semantics
|
|
330
|
+
|
|
331
|
+
`0.0.6` makes the HTML controller's DOM boundaries mutable without replacing the controller or core runtime.
|
|
332
|
+
|
|
333
|
+
The discovery root may be any DOM-like container that supports `querySelectorAll()` and can be observed by `MutationObserver`, including browser `Document`, `Element`, `DocumentFragment`, and `ShadowRoot`.
|
|
334
|
+
|
|
335
|
+
```ts
|
|
336
|
+
await html.setRoot(nextRoot, {
|
|
337
|
+
registerExisting: true,
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
await html.moveTo(nextRoot);
|
|
341
|
+
html.setAppendTarget(target);
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
The two boundaries remain distinct:
|
|
345
|
+
|
|
346
|
+
```text
|
|
347
|
+
root
|
|
348
|
+
one-shot discovery + MutationObserver target
|
|
349
|
+
|
|
350
|
+
appendTarget
|
|
351
|
+
default destination for append()/addModule()
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
Important invariants:
|
|
355
|
+
|
|
356
|
+
1. Reparenting the exact same observed root node does not require rebinding; MutationObserver follows node identity rather than the node's former parent.
|
|
357
|
+
2. Replacing that root with a different node requires `setRoot()`/`moveTo()` for the controller to observe the new subtree.
|
|
358
|
+
3. A connected controller stays connected when `setRoot()` changes the node: pending work against the old root is drained, the old observer target is disconnected, and observation is rebound to the new root.
|
|
359
|
+
4. Connected root changes register matching scripts already present in the new root by default; `registerExisting: false` opts out.
|
|
360
|
+
5. Disconnected `setRoot()` is configuration-only by default; `registerExisting: true` explicitly performs a one-shot scan.
|
|
361
|
+
6. `moveTo()` changes both discovery root and append target. For a `Document`, its body/documentElement is chosen as the append target rather than appending directly beside the document element.
|
|
362
|
+
7. `setAppendTarget()` never changes observation.
|
|
363
|
+
8. Root rebinding does not reset module ownership or the runtime graph. Elements already owned remain owned; module IDs remain reserved by the same controller.
|
|
364
|
+
|
|
365
|
+
Each HTML runtime controller owns its own MutationObserver instance. The observer is attached directly to the selected root; the implementation does not observe the entire document and then perform containment filtering. This keeps unrelated DOM mutation traffic out of the controller and correctly supports isolated `ShadowRoot` trees.
|
|
366
|
+
|
|
324
367
|
### User-created script elements
|
|
325
368
|
|
|
326
369
|
Applications may create the element themselves and explicitly associate it with one runtime:
|
|
@@ -926,3 +969,14 @@ Invariant: invalidation must result in a fresh executable module identity on the
|
|
|
926
969
|
|
|
927
970
|
Regression coverage: host-module replacement must update already-imported dependents, and explicit `runtime.invalidate()` must cause a side-effecting module to evaluate again on the next import.
|
|
928
971
|
|
|
972
|
+
|
|
973
|
+
|
|
974
|
+
### 0.0.6 — mutable HTML runtime roots and append targets
|
|
975
|
+
|
|
976
|
+
Decision: HTML runtime controllers now expose `setRoot()`, `moveTo()`, and `setAppendTarget()`, plus `root` and `appendTarget` introspection. A connected observer is rebound directly to the new root rather than keeping a document-wide observer and filtering by containment.
|
|
977
|
+
|
|
978
|
+
Reason: applications can reparent an existing observed root without intervention, but replacing a mount/container with a different DOM node would otherwise leave the observer and append destination attached to stale nodes. Explicit mutable boundaries preserve one controller/module graph while allowing UI containers, previews, micro-frontends, and shadow roots to move during application lifetime.
|
|
979
|
+
|
|
980
|
+
Invariant: a root change must not create a second observer, lose ownership records, or race pending scans from the old root. Pending observer work is drained before rebinding. A live root change registers existing scripts in the new root by default; a disconnected root change does not perform discovery unless requested.
|
|
981
|
+
|
|
982
|
+
Regression coverage: the suite verifies same-node reparenting, live root rebinding, skipping existing scripts, disconnected root changes, `moveTo()` synchronization of root/append target, and independent append-target changes.
|
package/README.md
CHANGED
|
@@ -270,9 +270,71 @@ solid-module → infer from module/src extension
|
|
|
270
270
|
|
|
271
271
|
`language="js"` or `language="jsx"` may override inference.
|
|
272
272
|
|
|
273
|
+
|
|
274
|
+
### Change the observed root
|
|
275
|
+
|
|
276
|
+
The HTML controller can move to a different discovery root without creating a new runtime:
|
|
277
|
+
|
|
278
|
+
```ts
|
|
279
|
+
const first = document.querySelector("#first-runtime")!;
|
|
280
|
+
const second = document.querySelector("#second-runtime")!;
|
|
281
|
+
|
|
282
|
+
const html = createHTMLRuntime(runtime, {
|
|
283
|
+
root: first,
|
|
284
|
+
appendTo: first,
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
await html.observe({ registerExisting: false });
|
|
288
|
+
|
|
289
|
+
// Rebind a live observer to a different node. Because the observer is already
|
|
290
|
+
// connected, matching scripts already in the new root are registered by default.
|
|
291
|
+
await html.setRoot(second);
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
If the controller is disconnected, `setRoot()` only changes configuration unless `registerExisting: true` is passed:
|
|
295
|
+
|
|
296
|
+
```ts
|
|
297
|
+
await html.setRoot(second, {
|
|
298
|
+
registerExisting: true,
|
|
299
|
+
});
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
Use `registerExisting: false` when switching a live observer but intentionally ignoring scripts already present in the new root.
|
|
303
|
+
|
|
304
|
+
Moving the **same root node** elsewhere in the DOM does not require `setRoot()`. `MutationObserver` remains attached to that node object even when it is reparented.
|
|
305
|
+
|
|
306
|
+
### Move root and append target together
|
|
307
|
+
|
|
308
|
+
When the runtime module area itself moves to another container, `moveTo()` changes both boundaries:
|
|
309
|
+
|
|
310
|
+
```ts
|
|
311
|
+
await html.moveTo(nextContainer, {
|
|
312
|
+
registerExisting: true,
|
|
313
|
+
});
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
For an `Element`, `DocumentFragment`, or `ShadowRoot`, future `append()` / `addModule()` calls insert into that root. For a `Document`, the controller chooses the document body (or document element fallback) as the append target.
|
|
317
|
+
|
|
318
|
+
### Change only the append target
|
|
319
|
+
|
|
320
|
+
Observation and insertion can have different boundaries:
|
|
321
|
+
|
|
322
|
+
```ts
|
|
323
|
+
html.setAppendTarget(document.head);
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
This does not change the currently observed root. The controller exposes the active values for diagnostics:
|
|
327
|
+
|
|
328
|
+
```ts
|
|
329
|
+
html.root;
|
|
330
|
+
html.appendTarget;
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
The `root` may be a `Document`, normal `Element`, `DocumentFragment`, or `ShadowRoot` (which is a `DocumentFragment`). A controller observes the selected root directly rather than observing the whole document and filtering mutations afterward.
|
|
334
|
+
|
|
273
335
|
### Addition-only observation
|
|
274
336
|
|
|
275
|
-
Observation remains addition-only in `0.0.
|
|
337
|
+
Observation remains addition-only in `0.0.6`.
|
|
276
338
|
|
|
277
339
|
Changing the source/attributes of an already owned script or removing it from the DOM does not implicitly update/delete the corresponding runtime module. Use `runtime.update()` / `runtime.invalidate()` for explicit module lifecycle changes.
|
|
278
340
|
|
package/html.d.ts
CHANGED
|
@@ -5,6 +5,7 @@ export interface HTMLModuleScriptElement {
|
|
|
5
5
|
textContent?: string | null;
|
|
6
6
|
baseURI?: string;
|
|
7
7
|
ownerDocument?: HTMLDocumentLike | null;
|
|
8
|
+
parentNode?: unknown;
|
|
8
9
|
getAttribute(name: string): string | null;
|
|
9
10
|
hasAttribute?(name: string): boolean;
|
|
10
11
|
setAttribute?(name: string, value: string): void;
|
|
@@ -119,10 +120,24 @@ export interface HTMLRuntimeOptions extends ObserveHTMLOptions {
|
|
|
119
120
|
createElement?: (tagName: string) => HTMLModuleScriptElement;
|
|
120
121
|
}
|
|
121
122
|
|
|
123
|
+
export interface SetHTMLRootOptions extends RegisterHTMLOptions {
|
|
124
|
+
/**
|
|
125
|
+
* Register matching scripts already present in the new root.
|
|
126
|
+
* Defaults to true when rebinding a connected observer and false otherwise.
|
|
127
|
+
*/
|
|
128
|
+
registerExisting?: boolean;
|
|
129
|
+
/** Optionally update the controller's default append target at the same time. */
|
|
130
|
+
appendTo?: HTMLAppendTarget;
|
|
131
|
+
}
|
|
132
|
+
|
|
122
133
|
export interface HTMLRuntimeController {
|
|
123
134
|
readonly runtime: SolidTagRuntime;
|
|
124
135
|
readonly scope?: string;
|
|
125
136
|
readonly connected: boolean;
|
|
137
|
+
/** Current configured/effective discovery root when available. */
|
|
138
|
+
readonly root?: HTMLModuleRoot;
|
|
139
|
+
/** Current effective target used by append()/addModule() when available. */
|
|
140
|
+
readonly appendTarget?: HTMLAppendTarget;
|
|
126
141
|
/** Result of the observer's initial scan. */
|
|
127
142
|
readonly initial: RegisterHTMLResult;
|
|
128
143
|
|
|
@@ -136,6 +151,24 @@ export interface HTMLRuntimeController {
|
|
|
136
151
|
flush(options?: RegisterHTMLOptions): Promise<RegisterHTMLResult>;
|
|
137
152
|
disconnect(): void;
|
|
138
153
|
|
|
154
|
+
/**
|
|
155
|
+
* Change the discovery/observation root. A connected observer is rebound to
|
|
156
|
+
* the new node automatically.
|
|
157
|
+
*/
|
|
158
|
+
setRoot(
|
|
159
|
+
root: HTMLModuleRoot,
|
|
160
|
+
options?: SetHTMLRootOptions,
|
|
161
|
+
): Promise<RegisterHTMLResult>;
|
|
162
|
+
|
|
163
|
+
/** Change both the discovery root and append target to the selected root. */
|
|
164
|
+
moveTo(
|
|
165
|
+
root: HTMLModuleRoot,
|
|
166
|
+
options?: SetHTMLRootOptions,
|
|
167
|
+
): Promise<RegisterHTMLResult>;
|
|
168
|
+
|
|
169
|
+
/** Change only where append()/addModule() insert future elements. */
|
|
170
|
+
setAppendTarget(target: HTMLAppendTarget): HTMLRuntimeController;
|
|
171
|
+
|
|
139
172
|
/** Define one element without auto-running an entry. */
|
|
140
173
|
defineElement(
|
|
141
174
|
element: HTMLModuleScriptElement,
|
package/package.json
CHANGED
package/src/html.js
CHANGED
|
@@ -61,6 +61,7 @@ export function createHTMLRuntime(runtime, options = {}) {
|
|
|
61
61
|
bindings: new Map(),
|
|
62
62
|
observer: undefined,
|
|
63
63
|
observerRoot: undefined,
|
|
64
|
+
observerOptions: undefined,
|
|
64
65
|
ignoredExisting: new WeakSet(),
|
|
65
66
|
queue: Promise.resolve(),
|
|
66
67
|
lastError: undefined,
|
|
@@ -81,10 +82,19 @@ export function createHTMLRuntime(runtime, options = {}) {
|
|
|
81
82
|
get initial() {
|
|
82
83
|
return controller.initial;
|
|
83
84
|
},
|
|
85
|
+
get root() {
|
|
86
|
+
return controller.root ?? globalThis.document;
|
|
87
|
+
},
|
|
88
|
+
get appendTarget() {
|
|
89
|
+
return getAppendTarget(controller);
|
|
90
|
+
},
|
|
84
91
|
register,
|
|
85
92
|
observe,
|
|
86
93
|
flush,
|
|
87
94
|
disconnect,
|
|
95
|
+
setRoot,
|
|
96
|
+
moveTo,
|
|
97
|
+
setAppendTarget,
|
|
88
98
|
defineElement,
|
|
89
99
|
registerElement,
|
|
90
100
|
append,
|
|
@@ -120,47 +130,14 @@ export function createHTMLRuntime(runtime, options = {}) {
|
|
|
120
130
|
async function observe(callOptions = {}) {
|
|
121
131
|
if (controller.connected) return api;
|
|
122
132
|
|
|
123
|
-
const
|
|
124
|
-
|
|
125
|
-
const MutationObserverImpl = callOptions.MutationObserver ?? controller.MutationObserver ?? globalThis.MutationObserver;
|
|
126
|
-
|
|
127
|
-
if (typeof MutationObserverImpl !== "function") {
|
|
128
|
-
throw new TypeError(
|
|
129
|
-
"observe() requires MutationObserver support or a MutationObserver option.",
|
|
130
|
-
);
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
controller.observerRoot = root;
|
|
134
|
-
controller.connected = true;
|
|
135
|
-
controller.lastError = undefined;
|
|
136
|
-
controller.ignoredExisting = new WeakSet();
|
|
137
|
-
|
|
138
|
-
if (callOptions.registerExisting === false) {
|
|
139
|
-
for (const element of Array.from(root.querySelectorAll(callOptions.selector ?? controller.selector))) {
|
|
140
|
-
if (acceptsDiscoveredElement(controller, element, callOptions)) {
|
|
141
|
-
controller.ignoredExisting.add(element);
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
controller.initial = emptyRegistrationResult();
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
const observer = new MutationObserverImpl(() => {
|
|
148
|
-
void enqueueObserverScan(controller, callOptions).catch(() => {});
|
|
149
|
-
});
|
|
150
|
-
|
|
151
|
-
controller.observer = observer;
|
|
152
|
-
observer.observe(root, {
|
|
153
|
-
childList: true,
|
|
154
|
-
subtree: callOptions.subtree ?? controller.subtree,
|
|
155
|
-
});
|
|
133
|
+
const root = getRoot(mergeOptions(controller, callOptions), "HTML runtime observe");
|
|
134
|
+
controller.observerOptions = { ...callOptions };
|
|
156
135
|
|
|
157
136
|
try {
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
});
|
|
163
|
-
}
|
|
137
|
+
controller.initial = await connectObserver(controller, root, callOptions, {
|
|
138
|
+
registerExisting: callOptions.registerExisting !== false,
|
|
139
|
+
initial: true,
|
|
140
|
+
});
|
|
164
141
|
} catch (error) {
|
|
165
142
|
disconnect();
|
|
166
143
|
throw error;
|
|
@@ -184,11 +161,80 @@ export function createHTMLRuntime(runtime, options = {}) {
|
|
|
184
161
|
}
|
|
185
162
|
|
|
186
163
|
function disconnect() {
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
164
|
+
stopObserver(controller);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Change the discovery/observation root. If observation is active, it is
|
|
169
|
+
* rebound to the new root and existing matching scripts are registered by
|
|
170
|
+
* default. When disconnected, changing the root is configuration-only unless
|
|
171
|
+
* registerExisting:true is requested.
|
|
172
|
+
*/
|
|
173
|
+
async function setRoot(root, callOptions = {}) {
|
|
174
|
+
const nextRoot = getRoot({ root }, "HTML runtime setRoot");
|
|
175
|
+
|
|
176
|
+
// Finish work scheduled against the old root before rebinding observer state.
|
|
177
|
+
await controller.queue;
|
|
178
|
+
if (controller.lastError) {
|
|
179
|
+
const error = controller.lastError;
|
|
180
|
+
controller.lastError = undefined;
|
|
181
|
+
throw error;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const wasConnected = controller.connected;
|
|
185
|
+
const previousObserverOptions = controller.observerOptions ?? {};
|
|
186
|
+
|
|
187
|
+
if (wasConnected) stopObserver(controller, { preserveOptions: true });
|
|
188
|
+
|
|
189
|
+
controller.root = nextRoot;
|
|
190
|
+
if (Object.prototype.hasOwnProperty.call(callOptions, "appendTo")) {
|
|
191
|
+
controller.appendTo = callOptions.appendTo;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const registerExisting = callOptions.registerExisting ?? wasConnected;
|
|
195
|
+
|
|
196
|
+
if (wasConnected) {
|
|
197
|
+
const observeOptions = {
|
|
198
|
+
...previousObserverOptions,
|
|
199
|
+
...callOptions,
|
|
200
|
+
root: nextRoot,
|
|
201
|
+
registerExisting,
|
|
202
|
+
};
|
|
203
|
+
controller.observerOptions = { ...observeOptions };
|
|
204
|
+
try {
|
|
205
|
+
return await connectObserver(controller, nextRoot, observeOptions, {
|
|
206
|
+
registerExisting,
|
|
207
|
+
initial: false,
|
|
208
|
+
});
|
|
209
|
+
} catch (error) {
|
|
210
|
+
stopObserver(controller, { preserveOptions: true });
|
|
211
|
+
throw error;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if (registerExisting) {
|
|
216
|
+
return register({ ...callOptions, root: nextRoot });
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
return emptyRegistrationResult();
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/** Change both observation root and default append target to the same node. */
|
|
223
|
+
async function moveTo(root, callOptions = {}) {
|
|
224
|
+
const nextRoot = getRoot({ root }, "HTML runtime moveTo");
|
|
225
|
+
return setRoot(nextRoot, {
|
|
226
|
+
...callOptions,
|
|
227
|
+
appendTo: callOptions.appendTo ?? defaultAppendTargetForRoot(nextRoot),
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/** Change only where append()/addModule() insert future script elements. */
|
|
232
|
+
function setAppendTarget(target) {
|
|
233
|
+
if (!target || (typeof target.append !== "function" && typeof target.appendChild !== "function")) {
|
|
234
|
+
throw new TypeError("setAppendTarget() requires a target with append() or appendChild().");
|
|
235
|
+
}
|
|
236
|
+
controller.appendTo = target;
|
|
237
|
+
return api;
|
|
192
238
|
}
|
|
193
239
|
|
|
194
240
|
/** Define one element without automatically executing an entry module. */
|
|
@@ -326,6 +372,59 @@ export async function defineScript(runtime, element, options = {}) {
|
|
|
326
372
|
export const htmlModuleSelector = DEFAULT_SELECTOR;
|
|
327
373
|
export const htmlRuntimeScopeAttribute = RUNTIME_SCOPE_ATTRIBUTE;
|
|
328
374
|
|
|
375
|
+
async function connectObserver(controller, root, callOptions = {}, mode = {}) {
|
|
376
|
+
const MutationObserverImpl = callOptions.MutationObserver ?? controller.MutationObserver ?? globalThis.MutationObserver;
|
|
377
|
+
|
|
378
|
+
if (typeof MutationObserverImpl !== "function") {
|
|
379
|
+
throw new TypeError(
|
|
380
|
+
"observe() requires MutationObserver support or a MutationObserver option.",
|
|
381
|
+
);
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
controller.observerRoot = root;
|
|
385
|
+
controller.connected = true;
|
|
386
|
+
controller.lastError = undefined;
|
|
387
|
+
controller.ignoredExisting = new WeakSet();
|
|
388
|
+
|
|
389
|
+
const registerExisting = mode.registerExisting !== false;
|
|
390
|
+
if (!registerExisting) {
|
|
391
|
+
for (const element of Array.from(root.querySelectorAll(callOptions.selector ?? controller.selector))) {
|
|
392
|
+
if (acceptsDiscoveredElement(controller, element, callOptions)) {
|
|
393
|
+
controller.ignoredExisting.add(element);
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
const observer = new MutationObserverImpl(() => {
|
|
399
|
+
void enqueueObserverScan(controller, callOptions).catch(() => {});
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
controller.observer = observer;
|
|
403
|
+
observer.observe(root, {
|
|
404
|
+
childList: true,
|
|
405
|
+
subtree: callOptions.subtree ?? controller.subtree,
|
|
406
|
+
});
|
|
407
|
+
|
|
408
|
+
const result = registerExisting
|
|
409
|
+
? await enqueueObserverScan(controller, {
|
|
410
|
+
...callOptions,
|
|
411
|
+
origin: mode.initial ? "observer-initial" : "observer-root-change",
|
|
412
|
+
})
|
|
413
|
+
: emptyRegistrationResult();
|
|
414
|
+
|
|
415
|
+
if (mode.initial) controller.initial = result;
|
|
416
|
+
return result;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
function stopObserver(controller, options = {}) {
|
|
420
|
+
if (!controller.connected && !controller.observer) return;
|
|
421
|
+
controller.connected = false;
|
|
422
|
+
controller.observer?.disconnect();
|
|
423
|
+
controller.observer = undefined;
|
|
424
|
+
controller.observerRoot = undefined;
|
|
425
|
+
if (!options.preserveOptions) controller.observerOptions = undefined;
|
|
426
|
+
}
|
|
427
|
+
|
|
329
428
|
async function enqueueObserverScan(controller, callOptions = {}) {
|
|
330
429
|
const task = controller.queue.then(async () => {
|
|
331
430
|
if (!controller.connected) return emptyRegistrationResult();
|
|
@@ -669,6 +768,10 @@ function getAppendTarget(controller, options = {}) {
|
|
|
669
768
|
if (explicit) return explicit;
|
|
670
769
|
|
|
671
770
|
const root = options.root ?? controller.root ?? globalThis.document;
|
|
771
|
+
return defaultAppendTargetForRoot(root);
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
function defaultAppendTargetForRoot(root) {
|
|
672
775
|
if (root?.body) return root.body;
|
|
673
776
|
if (root?.documentElement && root.documentElement !== root) return root.documentElement;
|
|
674
777
|
return root;
|