@askrjs/askr 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (123) hide show
  1. package/CHANGELOG.md +874 -0
  2. package/README.md +61 -29
  3. package/capabilities.json +19 -9
  4. package/dist/actions/index.js +56 -12
  5. package/dist/benchmark.js +10 -15
  6. package/dist/boot/index.d.ts +3 -2
  7. package/dist/boot/index.js +1 -1
  8. package/dist/boot-DaoTYoCw.js +3989 -0
  9. package/dist/browser-activity-CB8e-V4f.js +220 -0
  10. package/dist/components/index.d.ts +8 -1
  11. package/dist/components/index.js +1 -43
  12. package/dist/{aria-hKjThHcp.js → compose-ref-DWzdbxsD.js} +41 -4
  13. package/dist/control/index.d.ts +2 -0
  14. package/dist/control/index.js +1 -1
  15. package/dist/control-nfpT2fbK.js +209 -0
  16. package/dist/core.d.ts +5 -9
  17. package/dist/csp-nonce-DwGw1A6Q.js +35 -0
  18. package/dist/data/index.js +3 -3
  19. package/dist/{data-2edW0jhF.js → data-BCwClJiF.js} +283 -94
  20. package/dist/data-runtime-C1bFAlba.js +242 -0
  21. package/dist/{deferred-CbgNhf67.js → deferred-bEEHHvic.js} +42 -20
  22. package/dist/domains/actions.d.ts +8 -3
  23. package/dist/domains/component.d.ts +1 -107
  24. package/dist/domains/context.d.ts +0 -15
  25. package/dist/domains/control.d.ts +36 -249
  26. package/dist/domains/data.d.ts +31 -13
  27. package/dist/domains/diagnostics.d.ts +1 -2
  28. package/dist/domains/lifecycle.d.ts +2 -5
  29. package/dist/domains/refs.d.ts +0 -2
  30. package/dist/domains/routing.d.ts +43 -8
  31. package/dist/domains/scheduler.d.ts +1 -76
  32. package/dist/domains/server.d.ts +5 -2
  33. package/dist/domains/state.d.ts +2 -5
  34. package/dist/domains/telemetry.d.ts +0 -2
  35. package/dist/elements.d.ts +214 -51
  36. package/dist/error-boundary-Bpwofwps.js +293 -0
  37. package/dist/foundations/icon/index.d.ts +0 -58
  38. package/dist/foundations/icon/index.js +2 -2
  39. package/dist/foundations/index.js +2 -1
  40. package/dist/foundations/interactions/index.js +1 -1
  41. package/dist/foundations/state/index.d.ts +3 -3
  42. package/dist/foundations/state/index.js +5 -3
  43. package/dist/foundations/structures/index.js +2 -1
  44. package/dist/foundations/utilities/index.d.ts +2 -0
  45. package/dist/foundations/utilities/index.js +1 -2
  46. package/dist/fx/index.d.ts +31 -58
  47. package/dist/fx/index.js +313 -195
  48. package/dist/hooks-BimKStr4.js +202 -0
  49. package/dist/index.d.ts +11 -74
  50. package/dist/index.js +8 -447
  51. package/dist/instance-BAnfmXOR.js +988 -0
  52. package/dist/{interactions-CTeACdT_.js → interactions-D2BoREb-.js} +3 -2
  53. package/dist/jsx-dev-runtime.d.ts +41 -18
  54. package/dist/jsx-dev-runtime.js +1 -6
  55. package/dist/{jsx-runtime-CPJXAOBF.js → jsx-runtime-C4idqHY0.js} +18 -7
  56. package/dist/jsx-runtime.js +1 -1
  57. package/dist/jsx.d.ts +61 -37
  58. package/dist/{manifest-D1P3UNWV.js → manifest-CW9gyNBY.js} +3 -3
  59. package/dist/{navigate-prC5V8XV.js → navigate-CP_0Zy5U.js} +454 -87
  60. package/dist/navigation.d.ts +12 -3
  61. package/dist/peer-types.d.ts +8 -0
  62. package/dist/portal-DuOKfnLa.js +349 -0
  63. package/dist/promise-Dz8iIHAm.js +16 -0
  64. package/dist/query-registry-WMCN0BJL.js +320 -0
  65. package/dist/readable-DFWOsTac.js +21 -0
  66. package/dist/{render-context-B7ohlh6t.js → render-context-DGHnEveC.js} +27 -11
  67. package/dist/render-sync-IC5lMFpq.js +1240 -0
  68. package/dist/resolution-Cx82-xjW.js +1244 -0
  69. package/dist/resource-CLjB7RBQ.js +377 -0
  70. package/dist/resources/index.d.ts +21 -11
  71. package/dist/resources/index.js +45 -244
  72. package/dist/router/index.d.ts +14 -5
  73. package/dist/router/index.js +22 -53
  74. package/dist/scope-CwpVSHVf.js +55 -0
  75. package/dist/{shared-kU8yvryp.js → shared-O2ZnmYAf.js} +1 -7
  76. package/dist/{snapshot-source-An4iKajG.js → snapshot-Cjx4N988.js} +6 -1
  77. package/dist/ssg/index.d.ts +1 -2
  78. package/dist/ssg/index.js +5 -13
  79. package/dist/ssr/index.d.ts +26 -28
  80. package/dist/ssr/index.js +4 -4
  81. package/dist/{ssr-CiNm6Vzl.js → ssr-BJ8ug7zH.js} +133 -122
  82. package/dist/ssr-DoV-vJ-5.js +229 -0
  83. package/dist/state-DWO1KzkX.js +232 -0
  84. package/dist/structures-D4dDMqHX.js +231 -0
  85. package/dist/testing/index.d.ts +8 -4
  86. package/dist/testing/index.js +29 -34
  87. package/dist/url-CnbfpX0K.js +185 -0
  88. package/dist/utilities.d.ts +48 -2
  89. package/dist/verify-hydration-DpVaDnqz.js +152 -0
  90. package/package.json +19 -9
  91. package/dist/access-CEA2pOry.js +0 -1046
  92. package/dist/activity-oy2YFTFF.js +0 -201
  93. package/dist/boot-BMBMWUGI.js +0 -1084
  94. package/dist/capabilities-Bbf0mlp9.js +0 -42
  95. package/dist/compose-ref-v0qGlHoN.js +0 -26
  96. package/dist/control-BR_mrX__.js +0 -173
  97. package/dist/control-C-ApnFs1.js +0 -20
  98. package/dist/csp-nonce-BfFWcd0w.js +0 -173
  99. package/dist/data-runtime-CxoY98lX.js +0 -149
  100. package/dist/default-portal-runtime-DvMvWJVp.js +0 -20
  101. package/dist/dom-renderer.d.ts +0 -77
  102. package/dist/domains/renderer.d.ts +0 -127
  103. package/dist/eager-control.d.ts +0 -8
  104. package/dist/effect-C3Zsc1k6.js +0 -292
  105. package/dist/for-state-BDykeOJL.js +0 -1753
  106. package/dist/instance-VAFYVJOa.js +0 -1027
  107. package/dist/notify-DxxuDPeY.js +0 -266
  108. package/dist/portal-w3oQ6oQU.js +0 -29
  109. package/dist/query-registry-LrYpQsDJ.js +0 -83
  110. package/dist/render-resolved-BC0W7_5b.js +0 -1123
  111. package/dist/renderer-CT0HeY58.js +0 -8623
  112. package/dist/resolution-3DskUkjG.js +0 -577
  113. package/dist/resource-18-sFRl9.js +0 -344
  114. package/dist/route-matching-CUzZ31gT.js +0 -551
  115. package/dist/router-internal-7nuNEHfG.js +0 -36
  116. package/dist/scope-Cq_iYbTR.js +0 -403
  117. package/dist/selector-BSPVkjKP.js +0 -311
  118. package/dist/ssr-EPjwJH5R.js +0 -29
  119. package/dist/state-BXJfudq1.js +0 -91
  120. package/dist/structures-CjEzfj4h.js +0 -654
  121. package/dist/timer-C7uqnJVt.js +0 -85
  122. package/dist/verify-hydration-ikaxotf7.js +0 -27
  123. package/dist/vnode-D24p6IW2.js +0 -7
@@ -0,0 +1,3989 @@
1
+ import { C as schedule, F as logger, I as isDevelopmentEnvironment, L as isProductionEnvironment, M as getOwner, N as runWithOwner, S as queueTask, _ as batch, c as setRenderHost, d as recordUndo, f as rewindJournal, g as outsideRendering, i as getContextEpoch, j as Owner, l as createRenderJournal, m as withRenderJournal, n as HookOrderChangeError, p as settleJournal, t as ComponentInstance, u as journalMark, v as effectScheduler, w as Computation, y as flushSync } from "./instance-BAnfmXOR.js";
2
+ import { n as readValue } from "./readable-DFWOsTac.js";
3
+ import { n as Fragment, t as ELEMENT_TYPE } from "./jsx-DRS-qjiN.js";
4
+ import { _ as RenderDepthError, b as reportUncaughtErrorLater, g as routeError, h as noteErrorOrigin, i as currentComponent, l as provideAppRuntime, m as deliverToBoundary, p as clarifyRenderError, v as clarifyRenderOverflow, y as reportUncaughtError } from "./hooks-BimKStr4.js";
5
+ import { r as validateCspNonce, t as CspNonceScope } from "./csp-nonce-DwGw1A6Q.js";
6
+ import { d as startHydrationRenderPhase, p as stopHydrationRenderPhase, x as pageRenderEnvelope } from "./render-context-DGHnEveC.js";
7
+ import { a as descriptorType, o as functionChildOutput, s as normalizeChildren } from "./error-boundary-Bpwofwps.js";
8
+ import { D as clearStagedAppRenderRouteLocation, E as configureRootUpdateHost, O as createAppRenderRuntime, S as unregisterAppInstance, T as setServerLocation, _ as registerAppInstance, c as reconcileRouteMeta, d as configureScrollRestoration, k as stageAppRenderRouteLocation, l as resolveRouteMeta, o as readHistoryIndex, s as loadDocument, t as initializeNavigation } from "./navigate-CP_0Zy5U.js";
9
+ import { t as assertSyncComponentResult } from "./promise-Dz8iIHAm.js";
10
+ import { a as hydrateDataRuntime, m as reviveDeferredValue } from "./query-registry-WMCN0BJL.js";
11
+ import { A as clearRouteState, D as _setActiveRouteAuthOptions, Y as lockRouteRegistration, a as resolveRouteRequest, gt as withoutRouteHydrationMetadata, i as hasRouteRenderData, m as withoutHydratedAuth, n as getRouteRenderContext, p as readHydratedAuth, q as hasRegisteredRoutes, r as getRouteRenderData } from "./resolution-Cx82-xjW.js";
12
+ import { c as isSSRPortalHydrationAnchor, i as provideDefaultPortal, l as isSSRPortalWriterAnchor, t as DefaultPortal } from "./portal-DuOKfnLa.js";
13
+ import { r as assertExecutionModel, t as _applyManifest } from "./manifest-CW9gyNBY.js";
14
+ import { a as getDefaultDataRuntime } from "./data-runtime-C1bFAlba.js";
15
+ import { c as resolveNavigationUrl, d as isCustomElementName, l as attributeNamespace, n as UNSAFE_URL_SCHEME_ATTRIBUTES, s as rejectUnsafeUrlAttribute, t as SCRIPT_URL_RESOURCE_ATTRIBUTES, u as getPublicAttributeName } from "./url-CnbfpX0K.js";
16
+ import { a as PROPERTY_PROP_PREFIX, c as isKnownBooleanProperty, d as booleanAttributeValue, f as isSkippedProp, h as styleValueText, i as ATTRIBUTE_PROP_PREFIX, m as normalizeStylePropertyName, o as domPropertyValue, p as keepsFalseValue, r as sanitizeCssValue, s as getDomPropertyName, u as propertyReflectsAttribute } from "./ssr-DoV-vJ-5.js";
17
+ //#region src/boot/root-handler.ts
18
+ const AUTO_PORTAL = {
19
+ $$typeof: ELEMENT_TYPE,
20
+ type: DefaultPortal,
21
+ props: { __askrAutoDefaultPortal: true },
22
+ key: "__default_portal"
23
+ };
24
+ /**
25
+ * Wrap a root's component: its output is followed by the automatic default
26
+ * portal host, and the CSP nonce (if any) is provided to everything below.
27
+ */
28
+ function wrapRootRouteHandler(componentFn, cspNonce) {
29
+ const wrappedFn = (props, ctx) => {
30
+ const out = componentFn(props, ctx);
31
+ assertSyncComponentResult(out);
32
+ const root = {
33
+ $$typeof: ELEMENT_TYPE,
34
+ type: Fragment,
35
+ props: { children: out === void 0 || out === null ? [AUTO_PORTAL] : [out, AUTO_PORTAL] }
36
+ };
37
+ return cspNonce === void 0 ? root : CspNonceScope({
38
+ value: cspNonce,
39
+ children: root
40
+ });
41
+ };
42
+ Object.defineProperty(wrappedFn, "name", { value: componentFn.name || "Component" });
43
+ return wrappedFn;
44
+ }
45
+ //#endregion
46
+ //#region src/boot/root-update.ts
47
+ /**
48
+ * Route updates for application roots.
49
+ *
50
+ * `apply()` renders the destination (render phase only); `publish()` commits
51
+ * it; `rollback()` discards it and restores the root's previous route state.
52
+ * A navigation applies every root first and publishes only if all succeeded.
53
+ */
54
+ function prepare(root, input) {
55
+ const app = root;
56
+ const current = app.appRuntime;
57
+ const runtime = createAppRenderRuntime({
58
+ framework: withoutRouteHydrationMetadata(current?.framework),
59
+ dataRuntime: current?.dataRuntime,
60
+ routeRegistry: current?.routeRegistry,
61
+ routeAuth: current?.routeAuth,
62
+ route: input.routeData,
63
+ hasRoute: input.hasRouteData,
64
+ lifetime: current?.lifetime
65
+ });
66
+ const previous = {
67
+ appRuntime: app.appRuntime,
68
+ component: app.component,
69
+ handler: app.handler,
70
+ generation: app.generation
71
+ };
72
+ let prepared = null;
73
+ let settled = false;
74
+ const restore = () => {
75
+ app.appRuntime = previous.appRuntime;
76
+ app.component = previous.component;
77
+ app.handler = previous.handler;
78
+ app.generation = previous.generation;
79
+ };
80
+ return {
81
+ apply() {
82
+ if (prepared || settled) return;
83
+ stageAppRenderRouteLocation(runtime, input.href);
84
+ app.appRuntime = runtime;
85
+ if (input.replaceLifetime) {
86
+ app.component = input.handler;
87
+ app.handler = wrapRootRouteHandler(input.handler, app.cspNonce);
88
+ app.generation++;
89
+ runtime.lifetime = app.generation;
90
+ }
91
+ try {
92
+ prepared = app.root.prepare(app.view());
93
+ prepared.apply();
94
+ } catch (error) {
95
+ restore();
96
+ clearStagedAppRenderRouteLocation(runtime);
97
+ throw error;
98
+ }
99
+ },
100
+ publish() {
101
+ if (settled) return {
102
+ aborted: false,
103
+ errors: []
104
+ };
105
+ settled = true;
106
+ try {
107
+ prepared?.publish();
108
+ return {
109
+ aborted: false,
110
+ errors: []
111
+ };
112
+ } catch (error) {
113
+ const aborted = prepared?.aborted === true;
114
+ if (aborted) restore();
115
+ return {
116
+ aborted,
117
+ errors: [error]
118
+ };
119
+ } finally {
120
+ clearStagedAppRenderRouteLocation(runtime);
121
+ }
122
+ },
123
+ rollback() {
124
+ if (settled) return [];
125
+ settled = true;
126
+ clearStagedAppRenderRouteLocation(runtime);
127
+ const errors = prepared?.rollback() ?? [];
128
+ restore();
129
+ return errors;
130
+ },
131
+ retire() {
132
+ return [];
133
+ }
134
+ };
135
+ }
136
+ function installRootUpdateHost() {
137
+ configureRootUpdateHost({ prepare });
138
+ }
139
+ //#endregion
140
+ //#region src/boot/composition.ts
141
+ let composed = false;
142
+ /**
143
+ * Browser composition root, owned by the boot entry: connects route updates
144
+ * from the router to application roots.
145
+ */
146
+ function composeBrowserRuntime() {
147
+ if (composed) return;
148
+ composed = true;
149
+ installRootUpdateHost();
150
+ }
151
+ /** Compose whatever is still missing, for a mount reached without the boot entry. */
152
+ function ensureBrowserRuntime() {
153
+ composeBrowserRuntime();
154
+ }
155
+ //#endregion
156
+ //#region src/core/dom/pass.ts
157
+ /**
158
+ * A render pass.
159
+ *
160
+ * The render phase runs components, diffs their output, and builds new DOM
161
+ * off-document. Anything that would change live DOM or committed renderer
162
+ * state is recorded as an operation instead. `commit()` applies the
163
+ * operations in order; `discard()` drops them, disposes every owner the pass
164
+ * created, and rewinds the render journal (props and scope values set during
165
+ * render). A failed structural DOM write restores child lists that this pass
166
+ * already applied, then discards its provisional owners and subscriptions.
167
+ *
168
+ * Operations are recorded parent-first: a reconcile reserves its slot before
169
+ * its children record theirs, so a parent places its children before the
170
+ * children update their own contents.
171
+ */
172
+ /** A structural DOM write failed before its render pass could publish. */
173
+ var CommitMutationError = class extends Error {
174
+ constructor(failure) {
175
+ super("DOM commit failed");
176
+ this.failure = failure;
177
+ }
178
+ };
179
+ var Pass = class {
180
+ constructor() {
181
+ this.hasDormantPortalWriter = false;
182
+ this.commitAborted = false;
183
+ this.ops = [];
184
+ this.created = [];
185
+ this.renderedInstances = [];
186
+ this.afterCommit = [];
187
+ this.beforeSettle = [];
188
+ this.commitUndo = [];
189
+ this.commitSettle = [];
190
+ this.journal = createRenderJournal();
191
+ this.phase = "prepared";
192
+ this.failures = [];
193
+ }
194
+ /** Run render work with this pass's provisional undo journal active. */
195
+ run(render) {
196
+ return withRenderJournal(this.journal, render);
197
+ }
198
+ /** Record an operation on committed state. */
199
+ op(fn) {
200
+ this.ops.push(fn);
201
+ }
202
+ /** Reserve an operation slot to fill after nested work records its own. */
203
+ reserve() {
204
+ this.ops.push(null);
205
+ return this.ops.length - 1;
206
+ }
207
+ fill(slot, fn) {
208
+ this.ops[slot] = fn;
209
+ }
210
+ /** An owner this pass created; disposed if the pass is discarded. */
211
+ own(owner) {
212
+ this.created.push(owner);
213
+ }
214
+ /** An instance rendered by this pass; it commits (and mounts) with it. */
215
+ markRendered(instance) {
216
+ this.renderedInstances.push(instance);
217
+ }
218
+ /** Undo a provisional render-time change if this work is discarded. */
219
+ onDiscard(fn) {
220
+ recordUndo(fn, this.journal);
221
+ }
222
+ /** Run after all operations are applied (refs). */
223
+ after(fn) {
224
+ this.afterCommit.push(fn);
225
+ }
226
+ /** Run after DOM operations while the pass can still be rolled back. */
227
+ beforeJournalSettle(fn) {
228
+ this.beforeSettle.push(fn);
229
+ }
230
+ /** Restore a reversible write on abort; settle its old lifetime on success. */
231
+ onReversibleCommit(undo, settle = () => {}) {
232
+ this.commitUndo.push(undo);
233
+ this.commitSettle.push(settle);
234
+ }
235
+ mark() {
236
+ return {
237
+ ops: this.ops.length,
238
+ created: this.created.length,
239
+ rendered: this.renderedInstances.length,
240
+ journal: journalMark(this.journal),
241
+ after: this.afterCommit.length,
242
+ beforeSettle: this.beforeSettle.length,
243
+ commitUndo: this.commitUndo.length,
244
+ commitSettle: this.commitSettle.length,
245
+ dormantPortalWriter: this.hasDormantPortalWriter
246
+ };
247
+ }
248
+ /** Discard everything recorded since `mark` (an error boundary caught). */
249
+ rewind(mark) {
250
+ const errors = [];
251
+ this.ops.length = mark.ops;
252
+ this.afterCommit.length = mark.after;
253
+ this.renderedInstances.length = mark.rendered;
254
+ this.hasDormantPortalWriter = mark.dormantPortalWriter;
255
+ this.beforeSettle.length = mark.beforeSettle;
256
+ this.commitUndo.length = mark.commitUndo;
257
+ this.commitSettle.length = mark.commitSettle;
258
+ rewindJournal(this.journal, mark.journal, errors);
259
+ for (const owner of this.created.splice(mark.created).reverse()) owner.dispose(errors);
260
+ return errors;
261
+ }
262
+ discard() {
263
+ if (this.phase === "applied") return this.rollback();
264
+ if (this.phase === "published" || this.phase === "discarded") return [];
265
+ this.phase = "discarded";
266
+ return this.rewind({
267
+ ops: 0,
268
+ created: 0,
269
+ rendered: 0,
270
+ journal: 0,
271
+ after: 0,
272
+ beforeSettle: 0,
273
+ commitUndo: 0,
274
+ commitSettle: 0,
275
+ dormantPortalWriter: false
276
+ });
277
+ }
278
+ /** Apply reversible operations while retaining undo work for rollback. */
279
+ apply() {
280
+ if (this.phase === "applied") return;
281
+ if (this.phase !== "prepared") throw new Error("[Askr] Cannot apply a settled render pass.");
282
+ const abort = (failure) => {
283
+ this.commitAborted = true;
284
+ for (const error of this.rollback()) reportUncaughtErrorLater(error);
285
+ throw failure;
286
+ };
287
+ this.phase = "applied";
288
+ for (const op of this.ops) {
289
+ if (!op) continue;
290
+ try {
291
+ op();
292
+ } catch (error) {
293
+ if (error instanceof CommitMutationError) abort(error.failure);
294
+ this.failures.push(error);
295
+ }
296
+ }
297
+ for (const fn of this.beforeSettle) try {
298
+ fn();
299
+ } catch (error) {
300
+ if (error instanceof CommitMutationError) abort(error.failure);
301
+ this.failures.push(error);
302
+ }
303
+ }
304
+ /** Publish applied work and run irreversible cleanup, refs, and lifecycles. */
305
+ publish() {
306
+ if (this.phase === "prepared") this.apply();
307
+ if (this.phase !== "applied") throw new Error("[Askr] Cannot publish a render pass that was not applied.");
308
+ this.phase = "published";
309
+ settleJournal(this.journal, 0);
310
+ for (const settle of this.commitSettle) try {
311
+ settle();
312
+ } catch (error) {
313
+ this.failures.push(error);
314
+ }
315
+ for (const fn of this.afterCommit) try {
316
+ fn();
317
+ } catch (error) {
318
+ this.failures.push(error);
319
+ }
320
+ for (const instance of this.renderedInstances) if (!instance.disposed) try {
321
+ mount(instance);
322
+ } catch (error) {
323
+ this.failures.push(error);
324
+ }
325
+ if (this.failures.length === 1) throw this.failures[0];
326
+ if (this.failures.length > 1) throw new AggregateError(this.failures, "Commit failed");
327
+ }
328
+ /** Undo applied operations and discard every provisional render mutation. */
329
+ rollback() {
330
+ if (this.phase === "published" || this.phase === "discarded") return [];
331
+ const errors = [];
332
+ if (this.phase === "applied") for (let index = this.commitUndo.length - 1; index >= 0; index--) try {
333
+ this.commitUndo[index]();
334
+ } catch (error) {
335
+ errors.push(error);
336
+ }
337
+ this.phase = "prepared";
338
+ errors.push(...this.discard());
339
+ return errors;
340
+ }
341
+ /** Apply and publish immediately for a standalone root render. */
342
+ commit() {
343
+ this.apply();
344
+ this.publish();
345
+ }
346
+ };
347
+ /** Mark an instance committed and schedule its post-commit work. */
348
+ function mount(instance) {
349
+ instance.mounted = true;
350
+ const syncQueue = instance.commitSyncQueue;
351
+ instance.commitSyncQueue = null;
352
+ for (const fn of syncQueue ?? []) fn();
353
+ const queue = instance.commitQueue;
354
+ if (!queue) return;
355
+ instance.commitQueue = null;
356
+ queueTask(() => {
357
+ if (instance.disposed) return;
358
+ for (const fn of queue) try {
359
+ const cleanup = fn();
360
+ if (typeof cleanup === "function") instance.onCleanup(cleanup);
361
+ } catch (error) {
362
+ reportUncaughtErrorLater(error);
363
+ }
364
+ });
365
+ }
366
+ //#endregion
367
+ //#region src/core/dom/element-attributes.ts
368
+ /**
369
+ * Low-level attribute access for rendered elements: the attribute name a prop
370
+ * renders as, namespaced writes, and class names on HTML and SVG elements.
371
+ */
372
+ const SVG_NAMESPACE = "http://www.w3.org/2000/svg";
373
+ function isSVGDomElement(el) {
374
+ return typeof SVGElement !== "undefined" && el instanceof SVGElement;
375
+ }
376
+ function getRenderedAttributeName(el, propName) {
377
+ const attributeName = getPublicAttributeName(propName, isCustomElementName(el.localName));
378
+ return el.namespaceURI === SVG_NAMESPACE ? attributeName : attributeName.toLowerCase();
379
+ }
380
+ /**
381
+ * Write an attribute, placing `xlink:`/`xml:`/`xmlns:` names on SVG and MathML
382
+ * elements in their namespace.
383
+ */
384
+ function writeAttribute(el, attributeName, value, namespace = attributeNamespace(el.namespaceURI, attributeName)) {
385
+ if (namespace === null) el.setAttribute(attributeName, value);
386
+ else el.setAttributeNS(namespace, attributeName, value);
387
+ }
388
+ function setRenderedAttribute(el, propName, value) {
389
+ writeAttribute(el, getRenderedAttributeName(el, propName), value);
390
+ }
391
+ function removeRenderedAttribute(el, propName) {
392
+ el.removeAttribute(getRenderedAttributeName(el, propName));
393
+ }
394
+ function readElementClassName(el) {
395
+ if (isSVGDomElement(el)) return el.getAttribute("class") ?? "";
396
+ return el.className;
397
+ }
398
+ function writeElementClassName(el, value) {
399
+ if (isSVGDomElement(el)) {
400
+ if (value.length > 0) el.setAttribute("class", value);
401
+ else el.removeAttribute("class");
402
+ return;
403
+ }
404
+ el.className = value;
405
+ }
406
+ function tagNamesEqualIgnoreCase(a, b) {
407
+ if (a === b) return true;
408
+ if (a.length !== b.length) return false;
409
+ for (let index = 0; index < a.length; index += 1) {
410
+ const aCode = a.charCodeAt(index);
411
+ const bCode = b.charCodeAt(index);
412
+ if (aCode === bCode) continue;
413
+ if ((aCode >= 65 && aCode <= 90 ? aCode + 32 : aCode) !== (bCode >= 65 && bCode <= 90 ? bCode + 32 : bCode)) return false;
414
+ }
415
+ return true;
416
+ }
417
+ //#endregion
418
+ //#region src/core/dom/events.ts
419
+ /**
420
+ * Event handler props on rendered elements.
421
+ *
422
+ * Bubbling UI events (`click`, `input`, keyboard, mouse, touch) are
423
+ * delegated: each app root, plus `document.body` for content rendered outside
424
+ * any root (portals), listens once per event type. When an event reaches a
425
+ * container, its handlers along the event path are snapshotted and run
426
+ * target-first in one batch, so state writes flush once, synchronously, after
427
+ * the last handler, and a handler that removes an ancestor does not stop that
428
+ * ancestor's handler for the same event. Other events and capture handlers
429
+ * get one direct listener per element, run in their own batch.
430
+ *
431
+ * A handler's handler function can be swapped without re-registering. It runs
432
+ * with its component as the current owner, and a throwing handler is reported
433
+ * like an uncaught listener exception.
434
+ */
435
+ /** Root container visible to listeners through their component owner. */
436
+ const EVENT_ROOT_CONTAINER = Symbol("askr.event-root-container");
437
+ const DELEGATED_EVENTS = /* @__PURE__ */ new Set([
438
+ "click",
439
+ "dblclick",
440
+ "mousedown",
441
+ "mouseup",
442
+ "mouseover",
443
+ "mouseout",
444
+ "mousemove",
445
+ "touchend",
446
+ "touchcancel",
447
+ "keydown",
448
+ "keyup",
449
+ "keypress",
450
+ "input"
451
+ ]);
452
+ /** `onClick` -> click, `onKeyDownCapture` -> keydown (capture); else null. */
453
+ function parseEventProp(propName) {
454
+ if (propName.length <= 2 || !propName.startsWith("on")) return null;
455
+ const capture = propName.endsWith("Capture") && !propName.endsWith("PointerCapture") && propName.length > 9;
456
+ const name = capture ? propName.slice(0, -7) : propName;
457
+ if (name.length <= 2) return null;
458
+ return {
459
+ eventName: name.slice(2).toLowerCase(),
460
+ capture
461
+ };
462
+ }
463
+ /**
464
+ * `wheel`, `touchstart` and `touchmove` opt out of passive defaults so a
465
+ * handler can call `preventDefault()`; `scroll` is always passive.
466
+ */
467
+ function listenerOptions(eventName, capture) {
468
+ let options;
469
+ if (eventName === "scroll") options = { passive: true };
470
+ else if (eventName === "wheel" || eventName === "touchstart" || eventName === "touchmove") options = { passive: false };
471
+ if (!capture) return options;
472
+ return {
473
+ ...options,
474
+ capture: true
475
+ };
476
+ }
477
+ function invoke(listener, el, event) {
478
+ runWithOwner(listener.owner, () => {
479
+ try {
480
+ listener.handler.call(el, event);
481
+ } catch (error) {
482
+ reportUncaughtError(error);
483
+ }
484
+ });
485
+ }
486
+ /** Delegated handlers per element, by event type. */
487
+ const delegatedHandlers = /* @__PURE__ */ new WeakMap();
488
+ /** Elements holding a delegated handler, per event type. */
489
+ const delegatedUsage = /* @__PURE__ */ new Map();
490
+ /** Registered app roots, each counted once per live root using it. */
491
+ const roots = /* @__PURE__ */ new Map();
492
+ /** Container listeners, per container, by event type. */
493
+ const containerListeners = /* @__PURE__ */ new Map();
494
+ function fallbackContainer() {
495
+ return typeof document === "undefined" ? null : document.body;
496
+ }
497
+ function listen(container, eventName) {
498
+ let listeners = containerListeners.get(container);
499
+ if (listeners?.has(eventName)) return;
500
+ const listener = (event) => dispatch(event, eventName);
501
+ container.addEventListener(eventName, listener);
502
+ if (!listeners) containerListeners.set(container, listeners = /* @__PURE__ */ new Map());
503
+ listeners.set(eventName, listener);
504
+ }
505
+ function unlisten(container, eventName) {
506
+ const listeners = containerListeners.get(container);
507
+ const listener = listeners?.get(eventName);
508
+ if (!listener) return;
509
+ container.removeEventListener(eventName, listener);
510
+ listeners.delete(eventName);
511
+ if (listeners.size === 0) containerListeners.delete(container);
512
+ }
513
+ function containers() {
514
+ const all = [...roots.keys()];
515
+ const fallback = fallbackContainer();
516
+ if (fallback && !roots.has(fallback)) all.push(fallback);
517
+ return all;
518
+ }
519
+ /** Delegate events rendered under `root` at `root`, until released. */
520
+ function registerEventRoot(root) {
521
+ roots.set(root, (roots.get(root) ?? 0) + 1);
522
+ for (const eventName of delegatedUsage.keys()) listen(root, eventName);
523
+ let released = false;
524
+ return () => {
525
+ if (released) return;
526
+ released = true;
527
+ const count = roots.get(root) - 1;
528
+ if (count > 0) {
529
+ roots.set(root, count);
530
+ return;
531
+ }
532
+ roots.delete(root);
533
+ if (root === fallbackContainer()) return;
534
+ for (const eventName of delegatedUsage.keys()) unlisten(root, eventName);
535
+ };
536
+ }
537
+ function isShadowRoot(node) {
538
+ return node.nodeType === 11 && node.host != null;
539
+ }
540
+ /**
541
+ * Index of `container` in `composed`, or -1 when the composed path cannot be
542
+ * used: it lacks the container, or it skips an ancestor of the (retargeted)
543
+ * target below the container, as some browser hosts do.
544
+ */
545
+ function composedPathEnd(composed, target, container) {
546
+ const end = composed.indexOf(container);
547
+ if (end === -1) return -1;
548
+ let expected = target;
549
+ for (let i = 0; i < end && expected; i++) if (composed[i] === expected) expected = expected.parentNode;
550
+ return expected === container ? end : -1;
551
+ }
552
+ /**
553
+ * The target's parent chain up to `container`, supplemented with any
554
+ * composed path nodes it misses, for hosts whose composed path is unusable.
555
+ */
556
+ function ancestryPath(event, container) {
557
+ const path = [];
558
+ const seen = /* @__PURE__ */ new Set();
559
+ for (let node = event.target; node; node = node.parentNode) {
560
+ seen.add(node);
561
+ path.push(node);
562
+ if (node === container) return path;
563
+ }
564
+ for (const node of event.composedPath?.() ?? []) if (!seen.has(node)) {
565
+ seen.add(node);
566
+ path.push(node);
567
+ }
568
+ return path;
569
+ }
570
+ /** The event as seen by one element's handler. */
571
+ function eventFor(event, currentTarget, target) {
572
+ return new Proxy(event, { get(native, property) {
573
+ if (property === "currentTarget") return currentTarget;
574
+ if (property === "target") return target;
575
+ const value = Reflect.get(native, property, native);
576
+ return typeof value === "function" ? value.bind(native) : value;
577
+ } });
578
+ }
579
+ function dispatch(event, eventName) {
580
+ const container = event.currentTarget;
581
+ const composed = event.composedPath?.() ?? [];
582
+ const end = composedPathEnd(composed, event.target, container);
583
+ const path = end === -1 ? ancestryPath(event, container) : composed;
584
+ const last = end === -1 ? path.length : end;
585
+ const retarget = end > 0 && composed[0] !== event.target;
586
+ let target = retarget ? composed[0] : event.target;
587
+ let depth = 0;
588
+ let targetDepth = 0;
589
+ const handlers = [];
590
+ for (let i = 0; i < last; i++) {
591
+ const node = path[i];
592
+ if (node === container) break;
593
+ if (retarget) {
594
+ if (isShadowRoot(node)) {
595
+ if (--depth < targetDepth) {
596
+ target = node.host;
597
+ targetDepth = depth;
598
+ }
599
+ } else if (i > 0 && path[i - 1].assignedSlot === node) depth++;
600
+ }
601
+ if (node.nodeType !== 1) continue;
602
+ if (containerListeners.get(node)?.has(eventName)) handlers.length = 0;
603
+ const listener = delegatedHandlers.get(node)?.get(eventName);
604
+ if (listener) handlers.push({
605
+ el: node,
606
+ listener,
607
+ target
608
+ });
609
+ }
610
+ if (handlers.length === 0) return;
611
+ outsideRendering(() => batch(() => {
612
+ for (const { el, listener, target } of handlers) {
613
+ invoke(listener, el, eventFor(event, el, target));
614
+ if (event.cancelBubble) break;
615
+ }
616
+ }));
617
+ }
618
+ function delegate(el, listener) {
619
+ let byEvent = delegatedHandlers.get(el);
620
+ if (!byEvent) delegatedHandlers.set(el, byEvent = /* @__PURE__ */ new Map());
621
+ byEvent.set(listener.eventName, listener);
622
+ const usage = delegatedUsage.get(listener.eventName) ?? 0;
623
+ delegatedUsage.set(listener.eventName, usage + 1);
624
+ if (usage === 0) for (const container of containers()) listen(container, listener.eventName);
625
+ }
626
+ function undelegate(el, eventName) {
627
+ const byEvent = delegatedHandlers.get(el);
628
+ if (!byEvent?.delete(eventName)) return;
629
+ if (byEvent.size === 0) delegatedHandlers.delete(el);
630
+ const usage = delegatedUsage.get(eventName) - 1;
631
+ if (usage > 0) {
632
+ delegatedUsage.set(eventName, usage);
633
+ return;
634
+ }
635
+ delegatedUsage.delete(eventName);
636
+ for (const container of containerListeners.keys()) unlisten(container, eventName);
637
+ }
638
+ function setListener(listeners, el, propName, event, handler, owner) {
639
+ const existing = listeners.get(propName);
640
+ if (existing) {
641
+ existing.handler = handler;
642
+ return;
643
+ }
644
+ if (!event.capture && DELEGATED_EVENTS.has(event.eventName) && (roots.size > 0 || fallbackContainer())) {
645
+ const listener = {
646
+ handler,
647
+ owner,
648
+ eventName: event.eventName,
649
+ wrapped: null,
650
+ options: void 0
651
+ };
652
+ listeners.set(propName, listener);
653
+ delegate(el, listener);
654
+ return;
655
+ }
656
+ const options = listenerOptions(event.eventName, event.capture);
657
+ const listener = {
658
+ handler,
659
+ owner,
660
+ eventName: event.eventName,
661
+ options,
662
+ wrapped: (nativeEvent) => {
663
+ outsideRendering(() => batch(() => invoke(listener, el, nativeEvent)));
664
+ }
665
+ };
666
+ listeners.set(propName, listener);
667
+ el.addEventListener(event.eventName, listener.wrapped, options);
668
+ }
669
+ function removeListener(listeners, el, propName) {
670
+ const listener = listeners.get(propName);
671
+ if (!listener) return;
672
+ listeners.delete(propName);
673
+ if (!listener.wrapped) {
674
+ undelegate(el, listener.eventName);
675
+ return;
676
+ }
677
+ el.removeEventListener(listener.eventName, listener.wrapped, listener.options);
678
+ }
679
+ function removeAllListeners(listeners, el, errors) {
680
+ for (const propName of Array.from(listeners.keys())) try {
681
+ removeListener(listeners, el, propName);
682
+ } catch (error) {
683
+ errors.push(error);
684
+ }
685
+ }
686
+ //#endregion
687
+ //#region src/core/dom/hydration.ts
688
+ /**
689
+ * Hydration: rendering over server markup with a cursor.
690
+ *
691
+ * While a root's first render hydrates, each container element has a cursor
692
+ * at its next unclaimed child. Creating a text or element node first tries to
693
+ * claim the node under the cursor: an element with the same tag, or a text
694
+ * node (split when the server merged adjacent texts). A node that does not
695
+ * match is created fresh instead. When a container's children are rendered,
696
+ * one commit operation puts the claimed and created nodes in order and
697
+ * removes whatever the server rendered that the client did not.
698
+ */
699
+ var HydrationCursor = class HydrationCursor {
700
+ constructor(stopAt = null) {
701
+ this.stopAt = stopAt;
702
+ this.next = /* @__PURE__ */ new Map();
703
+ this.deferred = [];
704
+ }
705
+ /** Consume private SSR metadata emitted immediately before a component output. */
706
+ claimResourceSlots(container) {
707
+ let node = this.next.has(container) ? this.next.get(container) : container.firstChild;
708
+ for (; node && node !== this.stopAt; node = node.nextSibling) {
709
+ if (node.nodeType === 3 && isInsignificantWhitespace(node)) continue;
710
+ if (node.nodeType !== 8) return null;
711
+ const match = /^askr-resource:(r:\d+(?:,r:\d+)*)$/.exec(node.data);
712
+ if (match) {
713
+ this.advance(container, node);
714
+ return match[1].split(",");
715
+ }
716
+ if (isRangeMarker(node, "askr-range-start") || isRangeMarker(node, "askr-range-end")) return null;
717
+ }
718
+ return null;
719
+ }
720
+ /** Server nodes reserved for a portal host whose writer has not hydrated. */
721
+ heldNodes(container) {
722
+ if (!this.stopAt) return [];
723
+ const nodes = [];
724
+ let node = this.next.has(container) ? this.next.get(container) : container.firstChild;
725
+ while (node && node !== this.stopAt) {
726
+ if (node.nodeType !== 8) nodes.push(node);
727
+ node = node.nextSibling;
728
+ }
729
+ return nodes;
730
+ }
731
+ /**
732
+ * Reserve the server nodes at `container`'s cursor for a render that runs
733
+ * later. A server range (`askr-range-start` ... `askr-range-end`) is skipped
734
+ * as a whole; the returned cursor claims inside it, or from the reserved
735
+ * position onward when there is no range.
736
+ */
737
+ reserve(container) {
738
+ let node = this.next.has(container) ? this.next.get(container) : container.firstChild;
739
+ while (node && node !== this.stopAt && node.nodeType === 3 && isInsignificantWhitespace(node)) node = node.nextSibling;
740
+ if (node && isRangeMarker(node, "askr-range-start")) {
741
+ let depth = 0;
742
+ let end = node;
743
+ for (; end && end !== this.stopAt; end = end.nextSibling) if (isRangeMarker(end, "askr-range-start")) depth++;
744
+ else if (isRangeMarker(end, "askr-range-end") && --depth === 0) break;
745
+ if (end && end !== this.stopAt) {
746
+ this.next.set(container, end.nextSibling);
747
+ const inner = new HydrationCursor(end);
748
+ inner.next.set(container, node.nextSibling);
749
+ return inner;
750
+ }
751
+ }
752
+ const later = new HydrationCursor(this.stopAt);
753
+ later.next.set(container, node);
754
+ return later;
755
+ }
756
+ peek(container) {
757
+ let node = this.next.has(container) ? this.next.get(container) : container.firstChild;
758
+ while (node && node !== this.stopAt && (node.nodeType === 8 || node.nodeType === 3 && isInsignificantWhitespace(node))) node = node.nextSibling;
759
+ return node === this.stopAt ? null : node;
760
+ }
761
+ advance(container, node) {
762
+ this.next.set(container, node.nextSibling);
763
+ }
764
+ claimElement(container, tag, namespace) {
765
+ const node = this.peek(container);
766
+ if (!node || node.nodeType !== 1) return null;
767
+ const el = node;
768
+ const expectedNs = namespace ?? "http://www.w3.org/1999/xhtml";
769
+ if (!(expectedNs === "http://www.w3.org/1999/xhtml" ? el.localName === tag.toLowerCase() : el.localName === tag)) return null;
770
+ if ((el.namespaceURI ?? expectedNs) !== expectedNs) return null;
771
+ this.advance(container, el);
772
+ return el;
773
+ }
774
+ /**
775
+ * Claim a text node for `text`. A server text that begins with `text` is
776
+ * split so the remainder stays claimable by the next text; any other
777
+ * server text is claimed and corrected at commit.
778
+ */
779
+ claimText(container, text) {
780
+ const node = this.peek(container);
781
+ if (!node || node.nodeType !== 3) return null;
782
+ const textNode = node;
783
+ if (text.length > 0 && textNode.data.length > text.length && textNode.data.startsWith(text)) {
784
+ const remainder = document.createTextNode(textNode.data.slice(text.length));
785
+ this.next.set(container, remainder);
786
+ return textNode;
787
+ }
788
+ this.advance(container, textNode);
789
+ return textNode;
790
+ }
791
+ };
792
+ function isRangeMarker(node, data) {
793
+ return node.nodeType === 8 && node.data === data;
794
+ }
795
+ function isInsignificantWhitespace(node) {
796
+ return node.data.trim() === "" && node.data.includes("\n");
797
+ }
798
+ /**
799
+ * Put `container`'s content in the order `expected`, removing any server
800
+ * node that was not claimed. Nodes after `stopAt` are left alone.
801
+ */
802
+ function syncChildren(pass, container, expected, stopAt = null) {
803
+ const previous = Array.from(container.childNodes);
804
+ pass.onReversibleCommit(() => restoreChildren(container, previous));
805
+ try {
806
+ const skipAnchors = (node) => {
807
+ while (node && node !== stopAt && isSSRPortalHydrationAnchor(node)) node = node.nextSibling;
808
+ return node;
809
+ };
810
+ let cursor = skipAnchors(container.firstChild);
811
+ for (const node of expected) if (node === cursor) cursor = skipAnchors(cursor.nextSibling);
812
+ else container.insertBefore(node, cursor);
813
+ while (cursor && cursor !== stopAt) {
814
+ const next = cursor.nextSibling;
815
+ if (!isSSRPortalHydrationAnchor(cursor)) container.removeChild(cursor);
816
+ cursor = next;
817
+ }
818
+ } catch (error) {
819
+ throw new CommitMutationError(error);
820
+ }
821
+ }
822
+ function restoreChildren(container, previous) {
823
+ for (let index = 0; index < previous.length; index++) {
824
+ const current = container.childNodes[index] ?? null;
825
+ if (current !== previous[index]) container.insertBefore(previous[index], current);
826
+ }
827
+ while (container.childNodes.length > previous.length) container.removeChild(container.lastChild);
828
+ }
829
+ /** Attribute names `props` renders on `el`. */
830
+ function renderedAttributes(el, props) {
831
+ const names = /* @__PURE__ */ new Set();
832
+ if (el.localName === "option") {
833
+ const parent = el.parentElement;
834
+ const select = parent?.localName === "optgroup" ? parent.parentElement : parent;
835
+ if (select?.localName === "select" && select.hasAttribute("value")) names.add("selected");
836
+ }
837
+ for (const key in props) {
838
+ if (isSkippedProp(key) || parseEventProp(key)) continue;
839
+ const value = props[key];
840
+ if (typeof value !== "function") {
841
+ if (value === null || value === void 0) continue;
842
+ if (value === false && !keepsFalseValue(key)) continue;
843
+ }
844
+ if (key === "className") {
845
+ names.add("class");
846
+ continue;
847
+ }
848
+ const name = key.startsWith("attr:") ? key.slice(ATTRIBUTE_PROP_PREFIX.length) : key;
849
+ names.add(getRenderedAttributeName(el, name));
850
+ }
851
+ return names;
852
+ }
853
+ /**
854
+ * Server attributes the client does not render are removed from an adopted
855
+ * element, so a mismatched attribute does not survive hydration.
856
+ */
857
+ function removeUnrenderedAttributes(el, props, beforeRemove) {
858
+ const keep = renderedAttributes(el, props);
859
+ const removed = Array.from(el.attributes).filter((attribute) => !keep.has(attribute.name));
860
+ beforeRemove?.(removed);
861
+ for (const attribute of removed) el.removeAttribute(attribute.name);
862
+ }
863
+ //#endregion
864
+ //#region src/core/dom/dom-properties.ts
865
+ /**
866
+ * Per element, the props Askr wrote as DOM properties, mapped to the property
867
+ * name. A property cannot be "removed" like an attribute, so this is what lets
868
+ * an absent prop reset its property.
869
+ */
870
+ const writtenDomProperties = /* @__PURE__ */ new WeakMap();
871
+ /**
872
+ * Property names `prop:` never assigns: raw HTML sinks (use
873
+ * `dangerouslySetInnerHTML`) and names that would change the prototype chain.
874
+ */
875
+ const BLOCKED_PROPERTY_NAMES = /* @__PURE__ */ new Set([
876
+ "innerHTML",
877
+ "outerHTML",
878
+ "srcdoc",
879
+ "__proto__",
880
+ "constructor",
881
+ "prototype"
882
+ ]);
883
+ function isBlockedPropertyName(name) {
884
+ if (!BLOCKED_PROPERTY_NAMES.has(name)) return false;
885
+ if (isDevelopmentEnvironment()) logger.warn(`[Askr] ${PROPERTY_PROP_PREFIX}${name} is ignored.`);
886
+ return true;
887
+ }
888
+ function isUrlPropertyName(name) {
889
+ const lower = name.toLowerCase();
890
+ return UNSAFE_URL_SCHEME_ATTRIBUTES.has(lower) || SCRIPT_URL_RESOURCE_ATTRIBUTES.has(lower);
891
+ }
892
+ function writeProperty(el, name, value) {
893
+ const host = el;
894
+ if (Object.is(host[name], value)) return;
895
+ host[name] = value;
896
+ }
897
+ /** Capture property and ownership state before a pass writes a DOM property. */
898
+ function captureDomPropertyUndo(el, key, value, tagName) {
899
+ const name = getDomPropertyName(tagName, key, value);
900
+ const written = writtenDomProperties.get(el);
901
+ const previousName = written?.get(key);
902
+ if (name === null && previousName === void 0) return null;
903
+ const names = /* @__PURE__ */ new Set();
904
+ if (name !== null && !BLOCKED_PROPERTY_NAMES.has(name)) names.add(name);
905
+ if (previousName !== void 0) names.add(previousName);
906
+ const host = el;
907
+ const properties = Array.from(names, (property) => ({
908
+ property,
909
+ descriptor: Object.getOwnPropertyDescriptor(host, property),
910
+ value: host[property]
911
+ }));
912
+ const attributeNames = /* @__PURE__ */ new Set();
913
+ for (const property of names) attributeNames.add(getRenderedAttributeName(el, property));
914
+ if (!key.startsWith("prop:")) attributeNames.add(getRenderedAttributeName(el, key));
915
+ const attributes = Array.from(attributeNames, (attribute) => ({
916
+ attribute,
917
+ value: el.getAttribute(attribute)
918
+ }));
919
+ const hadWrittenMap = written !== void 0;
920
+ const writtenBefore = written ? new Map(written) : null;
921
+ return () => {
922
+ const failures = [];
923
+ for (const { property, descriptor, value: before } of properties) try {
924
+ if (descriptor && "value" in descriptor) {
925
+ Object.defineProperty(host, property, descriptor);
926
+ continue;
927
+ }
928
+ if (!Reflect.set(host, property, before)) throw new TypeError(`Could not restore DOM property ${property}`);
929
+ if (descriptor) Object.defineProperty(host, property, descriptor);
930
+ else delete host[property];
931
+ } catch (error) {
932
+ failures.push(error);
933
+ }
934
+ for (const { attribute, value: before } of attributes) try {
935
+ if (before === null) el.removeAttribute(attribute);
936
+ else el.setAttribute(attribute, before);
937
+ } catch (error) {
938
+ failures.push(error);
939
+ }
940
+ try {
941
+ if (hadWrittenMap) writtenDomProperties.set(el, new Map(writtenBefore));
942
+ else writtenDomProperties.delete(el);
943
+ } catch (error) {
944
+ failures.push(error);
945
+ }
946
+ if (failures.length === 1) throw failures[0];
947
+ if (failures.length > 1) throw new AggregateError(failures, "DOM property rollback failed");
948
+ };
949
+ }
950
+ function hasDomPropertyWrite(el, key, value, tagName) {
951
+ return getDomPropertyName(tagName, key, value) !== null || writtenDomProperties.get(el)?.has(key) === true;
952
+ }
953
+ /**
954
+ * Put a property back to its default once Askr no longer sets it.
955
+ *
956
+ * - `muted`/`indeterminate` become `false`.
957
+ * - A property with a same-named attribute (`href`, `title`, `hidden`,
958
+ * `className` -> `class`, ...) has that attribute removed, which restores
959
+ * the element's own default.
960
+ * - An own property (an expando, or a value set before a custom element
961
+ * upgraded) is deleted.
962
+ * - Otherwise strings become `''` and booleans `false`. Numbers keep their
963
+ * value (there is no safe generic default), and anything else becomes
964
+ * `undefined`.
965
+ */
966
+ function resetProperty(el, key, name) {
967
+ const host = el;
968
+ if (isKnownBooleanProperty(key)) {
969
+ writeProperty(el, name, false);
970
+ return;
971
+ }
972
+ const attributeName = getRenderedAttributeName(el, name);
973
+ if (el.hasAttribute(attributeName)) {
974
+ el.removeAttribute(attributeName);
975
+ return;
976
+ }
977
+ if (Object.prototype.hasOwnProperty.call(host, name)) {
978
+ delete host[name];
979
+ return;
980
+ }
981
+ const current = host[name];
982
+ if (typeof current === "number") return;
983
+ writeProperty(el, name, typeof current === "string" ? "" : typeof current === "boolean" ? false : void 0);
984
+ }
985
+ function forgetProperty(el, key) {
986
+ const written = writtenDomProperties.get(el);
987
+ const name = written?.get(key);
988
+ if (name === void 0) return;
989
+ written.delete(key);
990
+ resetProperty(el, key, name);
991
+ }
992
+ /**
993
+ * Write a prop that targets a DOM property.
994
+ *
995
+ * Returns `true` when the prop is fully handled, and `false` when the caller
996
+ * must still run the attribute path: for attribute props, and for `muted`,
997
+ * which keeps its attribute alongside the property. A prop that was a
998
+ * property and is now an attribute (a custom element prop that went from an
999
+ * object to a string or `null`) has its property reset first.
1000
+ */
1001
+ function applyDomPropertyProp(el, key, value, tagName) {
1002
+ const name = getDomPropertyName(tagName, key, value);
1003
+ if (name === null) {
1004
+ if (isCustomElementName(tagName)) forgetProperty(el, key);
1005
+ return false;
1006
+ }
1007
+ if (isBlockedPropertyName(name)) return true;
1008
+ if (value === null || value === void 0) {
1009
+ forgetProperty(el, key);
1010
+ return true;
1011
+ }
1012
+ let next = domPropertyValue(key, value);
1013
+ if (next !== null && next !== void 0 && isUrlPropertyName(name)) {
1014
+ const text = String(next);
1015
+ if (rejectUnsafeUrlAttribute(name, text)) {
1016
+ forgetProperty(el, key);
1017
+ return true;
1018
+ }
1019
+ if (!isCustomElementName(tagName)) next = text;
1020
+ }
1021
+ let written = writtenDomProperties.get(el);
1022
+ if (!written) {
1023
+ written = /* @__PURE__ */ new Map();
1024
+ writtenDomProperties.set(el, written);
1025
+ }
1026
+ if (!written.has(key)) {
1027
+ written.set(key, name);
1028
+ if (!key.startsWith("prop:") && !isKnownBooleanProperty(key)) {
1029
+ const attributeName = getRenderedAttributeName(el, key);
1030
+ if (el.hasAttribute(attributeName)) el.removeAttribute(attributeName);
1031
+ }
1032
+ }
1033
+ writeProperty(el, name, next);
1034
+ return !propertyReflectsAttribute(key);
1035
+ }
1036
+ //#endregion
1037
+ //#region src/core/dom/prop-values.ts
1038
+ /**
1039
+ * How a scalar prop value is written to an element: class tokens, owned style
1040
+ * properties, form state, DOM properties, URL-guarded attributes, and the
1041
+ * `attr:`/`prop:` escape hatches. Shared semantics with SSR attribute
1042
+ * serialization; the renderer decides when these run.
1043
+ */
1044
+ /** Props whose live DOM property must be synced alongside the attribute. */
1045
+ function isFormControlProp(key) {
1046
+ return key === "value" || key === "checked" || key === "selected";
1047
+ }
1048
+ /** Attribute text for a scalar prop, rendering HTML booleans bare. */
1049
+ function renderedScalarValue(el, key, value) {
1050
+ return value === true ? booleanAttributeValue(getRenderedAttributeName(el, key)) : String(value);
1051
+ }
1052
+ /**
1053
+ * The text to write for a scalar attribute prop, or `null` when the renderer
1054
+ * never writes it: unsafe URLs, and inline event handler or object values
1055
+ * requested through the `attr:` escape hatch (SSR skips those too). The value
1056
+ * is converted once, and the checked text is the text written, so a
1057
+ * `toString()` cannot pass the URL check and then return something else.
1058
+ */
1059
+ function renderableAttributeText(el, key, value) {
1060
+ let name = key;
1061
+ if (key.startsWith("attr:")) {
1062
+ name = key.slice(ATTRIBUTE_PROP_PREFIX.length);
1063
+ if (value !== null && typeof value === "object" || name.slice(0, 2).toLowerCase() === "on") return null;
1064
+ }
1065
+ const text = renderedScalarValue(el, key, value);
1066
+ return rejectUnsafeUrlAttribute(name, text) ? null : text;
1067
+ }
1068
+ function isDangerousInnerHTMLPayload(value) {
1069
+ return value !== null && typeof value === "object" && "__html" in value;
1070
+ }
1071
+ function applyDangerousInnerHTMLValue(el, value) {
1072
+ if (!isDangerousInnerHTMLPayload(value)) return;
1073
+ if (isDevelopmentEnvironment()) logger.warn("[Askr] dangerouslySetInnerHTML is being used, which bypasses the framework's normal rendering and can introduce XSS vulnerabilities if the HTML is derived from untrusted input. Make sure the value is sanitized.");
1074
+ const html = value.__html;
1075
+ const nextHtml = html === null || html === void 0 ? "" : String(html);
1076
+ if (hasMatchingInnerHTML(el, nextHtml)) return;
1077
+ el.innerHTML = nextHtml;
1078
+ }
1079
+ /** Compare parsed HTML without replacing the live descendants. */
1080
+ function hasMatchingInnerHTML(el, html) {
1081
+ try {
1082
+ if (el.innerHTML === html) return true;
1083
+ if (el.childNodes.length === 0 || !el.namespaceURI) return false;
1084
+ const probe = el.ownerDocument.createElement("template").content.ownerDocument.createElementNS(el.namespaceURI, el.localName);
1085
+ probe.innerHTML = html;
1086
+ return probe.innerHTML === el.innerHTML;
1087
+ } catch {
1088
+ return false;
1089
+ }
1090
+ }
1091
+ function applyFormControlProp(el, key, value, tagName) {
1092
+ if (key === "value") {
1093
+ const stringValue = String(value);
1094
+ if (tagNamesEqualIgnoreCase(tagName, "select")) {
1095
+ const select = el;
1096
+ const selectedValues = new Set(select.multiple && Array.isArray(value) ? value.map(String) : [stringValue]);
1097
+ let firstMatch = -1;
1098
+ for (let index = 0; index < select.options.length; index++) {
1099
+ const option = select.options[index];
1100
+ const selected = selectedValues.has(option.value) && (select.multiple || firstMatch < 0);
1101
+ if (selected && firstMatch < 0) firstMatch = index;
1102
+ if (option.hasAttribute("selected") !== selected) {
1103
+ if (selected) option.setAttribute("selected", "");
1104
+ else option.removeAttribute("selected");
1105
+ }
1106
+ if (select.multiple && option.selected !== selected) option.selected = selected;
1107
+ }
1108
+ if (!select.multiple && select.selectedIndex !== firstMatch) select.selectedIndex = firstMatch;
1109
+ } else if (tagNamesEqualIgnoreCase(tagName, "input") || tagNamesEqualIgnoreCase(tagName, "textarea")) {
1110
+ const control = el;
1111
+ if (control.value !== stringValue) control.value = stringValue;
1112
+ }
1113
+ if (el.getAttribute("value") !== stringValue) el.setAttribute("value", stringValue);
1114
+ return;
1115
+ }
1116
+ if (key === "selected") {
1117
+ const selected = Boolean(value);
1118
+ if (tagNamesEqualIgnoreCase(tagName, "option")) {
1119
+ const option = el;
1120
+ if (option.selected !== selected) option.selected = selected;
1121
+ }
1122
+ if (selected) {
1123
+ if (!el.hasAttribute("selected")) el.setAttribute("selected", "");
1124
+ } else if (el.hasAttribute("selected")) el.removeAttribute("selected");
1125
+ return;
1126
+ }
1127
+ if (key === "checked") {
1128
+ if (tagNamesEqualIgnoreCase(tagName, "input")) {
1129
+ const checked = Boolean(value);
1130
+ const input = el;
1131
+ if (input.checked !== checked) input.checked = checked;
1132
+ if (checked) {
1133
+ if (!el.hasAttribute("checked")) el.setAttribute("checked", "");
1134
+ } else if (el.hasAttribute("checked")) el.removeAttribute("checked");
1135
+ } else if (value) {
1136
+ if (!el.hasAttribute("checked")) el.setAttribute("checked", "");
1137
+ } else if (el.hasAttribute("checked")) el.removeAttribute("checked");
1138
+ }
1139
+ }
1140
+ const IMPORTANT_SUFFIX = " !important";
1141
+ function readStyleEntry(style, propertyName) {
1142
+ return style.getPropertyValue(propertyName) + (style.getPropertyPriority(propertyName) ? IMPORTANT_SUFFIX : "");
1143
+ }
1144
+ function collectStyleEntries(style) {
1145
+ const entries = /* @__PURE__ */ new Map();
1146
+ for (let index = 0; index < style.length; index += 1) {
1147
+ const propertyName = style.item(index);
1148
+ entries.set(propertyName, readStyleEntry(style, propertyName));
1149
+ }
1150
+ return entries;
1151
+ }
1152
+ function normalizeStyleEntries(value) {
1153
+ if (!value || typeof value !== "object" || Array.isArray(value)) return null;
1154
+ const entries = /* @__PURE__ */ new Map();
1155
+ for (const [key, entryValue] of Object.entries(value)) {
1156
+ if (entryValue === void 0 || entryValue === null || entryValue === false) continue;
1157
+ const propertyName = normalizeStylePropertyName(key);
1158
+ const safeValue = sanitizeCssValue(styleValueText(propertyName, entryValue));
1159
+ if (safeValue) entries.set(propertyName, safeValue);
1160
+ }
1161
+ return entries;
1162
+ }
1163
+ let scratchStyle = null;
1164
+ /** Style entries a prop value renders; `null` when it is not expressible. */
1165
+ function readStyleEntries(el, value) {
1166
+ if (value === null || value === void 0 || value === false) return /* @__PURE__ */ new Map();
1167
+ if (typeof value !== "string") return normalizeStyleEntries(value);
1168
+ scratchStyle ??= el.ownerDocument.createElement("div").style;
1169
+ scratchStyle.cssText = value;
1170
+ const entries = collectStyleEntries(scratchStyle);
1171
+ scratchStyle.cssText = "";
1172
+ return entries;
1173
+ }
1174
+ /**
1175
+ * Apply a `style` prop. `previousValue` is the value Askr last applied
1176
+ * (`null` when none): only the properties it owns are removed or rewritten, so
1177
+ * properties set by other code survive. When `previousValue` is `undefined`
1178
+ * the element's whole style is treated as Askr-owned.
1179
+ */
1180
+ function applyStylePropValue(el, value, previousValue) {
1181
+ const style = el.style;
1182
+ const isEmpty = value === null || value === void 0 || value === false;
1183
+ if (!style) {
1184
+ if (isEmpty) {
1185
+ el.removeAttribute("style");
1186
+ return;
1187
+ }
1188
+ el.setAttribute("style", String(value));
1189
+ return;
1190
+ }
1191
+ if ((el.getAttribute("style") ?? "") === (isEmpty ? "" : value) || isEmpty && previousValue === null) return;
1192
+ if (previousValue === void 0 && (isEmpty || typeof value === "string")) {
1193
+ style.cssText = isEmpty ? "" : value;
1194
+ if (isEmpty) el.removeAttribute("style");
1195
+ return;
1196
+ }
1197
+ const nextEntries = readStyleEntries(el, value);
1198
+ if (!nextEntries) {
1199
+ style.cssText = String(value);
1200
+ return;
1201
+ }
1202
+ const previousEntries = (previousValue === void 0 ? null : previousValue === value ? nextEntries : readStyleEntries(el, previousValue)) ?? collectStyleEntries(style);
1203
+ let didWrite = false;
1204
+ for (const [propertyName] of previousEntries) {
1205
+ if (nextEntries.has(propertyName) || !style.getPropertyValue(propertyName)) continue;
1206
+ style.removeProperty(propertyName);
1207
+ didWrite = true;
1208
+ }
1209
+ for (const [propertyName, propertyValue] of nextEntries) {
1210
+ if (readStyleEntry(style, propertyName) === propertyValue) continue;
1211
+ const important = propertyValue.endsWith(IMPORTANT_SUFFIX);
1212
+ style.setProperty(propertyName, important ? propertyValue.slice(0, -11) : propertyValue, important ? "important" : "");
1213
+ didWrite = true;
1214
+ }
1215
+ if (isEmpty && style.length === 0) {
1216
+ el.removeAttribute("style");
1217
+ didWrite = true;
1218
+ }
1219
+ if (!didWrite) {}
1220
+ }
1221
+ function applyStaticScalarPropsToElement(el, props, tagName) {
1222
+ for (const key in props) {
1223
+ if (isSkippedProp(key)) continue;
1224
+ const value = props[key];
1225
+ if (applyDomPropertyProp(el, key, value, tagName)) continue;
1226
+ if (value === void 0 || value === null || value === false && !keepsFalseValue(key)) continue;
1227
+ if (key === "class" || key === "className") writeElementClassName(el, String(value));
1228
+ else if (key === "style") applyStylePropValue(el, value);
1229
+ else if (isFormControlProp(key)) applyFormControlProp(el, key, value, tagName);
1230
+ else if (key === "dangerouslySetInnerHTML") applyDangerousInnerHTMLValue(el, value);
1231
+ else {
1232
+ const text = renderableAttributeText(el, key, value);
1233
+ if (text === null) removeRenderedAttribute(el, key);
1234
+ else setRenderedAttribute(el, key, text);
1235
+ }
1236
+ }
1237
+ }
1238
+ const EMPTY_CLASS_TOKENS = [];
1239
+ /** Class tokens Askr last applied; `null` means unknown. */
1240
+ function previousClassTokens(previousValue) {
1241
+ return previousValue === null || previousValue === false ? EMPTY_CLASS_TOKENS : tokenizeClassValue(previousValue);
1242
+ }
1243
+ function tokenizeClassValue(value) {
1244
+ if (typeof value !== "string") return null;
1245
+ const trimmed = value.trim();
1246
+ if (trimmed.length === 0) return [];
1247
+ return trimmed.split(/\s+/);
1248
+ }
1249
+ function patchClassList(el, previousTokens, nextTokens) {
1250
+ if (previousTokens.length === nextTokens.length) {
1251
+ let identical = true;
1252
+ for (let index = 0; index < previousTokens.length; index += 1) if (previousTokens[index] !== nextTokens[index]) {
1253
+ identical = false;
1254
+ break;
1255
+ }
1256
+ if (identical) {
1257
+ for (const token of nextTokens) if (!el.classList.contains(token)) el.classList.add(token);
1258
+ return;
1259
+ }
1260
+ }
1261
+ if (previousTokens.length === 0) {
1262
+ if (nextTokens.length === 0) return;
1263
+ el.classList.add(...nextTokens);
1264
+ return;
1265
+ }
1266
+ if (nextTokens.length === 0) {
1267
+ el.classList.remove(...previousTokens);
1268
+ return;
1269
+ }
1270
+ if (previousTokens.length === 1 && nextTokens.length === 1) {
1271
+ el.classList.remove(previousTokens[0]);
1272
+ el.classList.add(nextTokens[0]);
1273
+ return;
1274
+ }
1275
+ const nextSet = new Set(nextTokens);
1276
+ const previousSet = new Set(previousTokens);
1277
+ for (const token of previousTokens) if (!nextSet.has(token)) el.classList.remove(token);
1278
+ for (const token of nextTokens) if (!previousSet.has(token)) el.classList.add(token);
1279
+ }
1280
+ function applyClassPropValue(el, value, previousValue, descriptor) {
1281
+ const nextString = String(value);
1282
+ if (!descriptor && nextString.length > 0 && readElementClassName(el) === nextString) return;
1283
+ const nextTokens = tokenizeClassValue(nextString);
1284
+ const previousTokens = descriptor?.lastClassTokens ?? previousClassTokens(previousValue);
1285
+ if (nextTokens && previousTokens) {
1286
+ if (Object.is(value, previousValue)) {
1287
+ for (const token of nextTokens) if (!el.classList.contains(token)) el.classList.add(token);
1288
+ return;
1289
+ }
1290
+ patchClassList(el, previousTokens, nextTokens);
1291
+ dropEmptySvgClass(el);
1292
+ if (descriptor) descriptor.lastClassTokens = nextTokens;
1293
+ return;
1294
+ }
1295
+ writeElementClassName(el, nextString);
1296
+ if (descriptor) descriptor.lastClassTokens = nextTokens;
1297
+ }
1298
+ /** An SVG element renders no `class` attribute for an empty class list. */
1299
+ function dropEmptySvgClass(el) {
1300
+ if (isSVGDomElement(el) && el.getAttribute("class") === "") el.removeAttribute("class");
1301
+ }
1302
+ function applyScalarPropValue(el, key, value, tagName, previousValue, descriptor) {
1303
+ if (key === "dangerouslySetInnerHTML" && !isDangerousInnerHTMLPayload(value)) return;
1304
+ if (applyDomPropertyProp(el, key, value, tagName)) return;
1305
+ if (value === void 0 || value === null || value === false && !keepsFalseValue(key)) {
1306
+ if (key === "class" || key === "className") {
1307
+ const previousTokens = descriptor ? descriptor.lastClassTokens : previousClassTokens(previousValue);
1308
+ if (previousTokens === null) writeElementClassName(el, "");
1309
+ else if (previousTokens.length > 0) {
1310
+ el.classList.remove(...previousTokens);
1311
+ dropEmptySvgClass(el);
1312
+ }
1313
+ if (descriptor) descriptor.lastClassTokens = [];
1314
+ } else if (key === "value") applyFormControlProp(el, key, "", tagName);
1315
+ else if (key === "checked" || key === "selected") applyFormControlProp(el, key, false, tagName);
1316
+ else if (key === "style") applyStylePropValue(el, null, previousValue);
1317
+ else removeRenderedAttribute(el, key);
1318
+ return;
1319
+ }
1320
+ if (key === "class" || key === "className") applyClassPropValue(el, value, previousValue, descriptor);
1321
+ else if (key === "style") applyStylePropValue(el, value, previousValue);
1322
+ else if (isFormControlProp(key)) applyFormControlProp(el, key, value, tagName);
1323
+ else if (key === "dangerouslySetInnerHTML") applyDangerousInnerHTMLValue(el, value);
1324
+ else {
1325
+ const nextValue = renderableAttributeText(el, key, value);
1326
+ if (nextValue === null) {
1327
+ removeRenderedAttribute(el, key);
1328
+ return;
1329
+ }
1330
+ const attributeName = getRenderedAttributeName(el, key);
1331
+ if (el.getAttribute(attributeName) === nextValue) return;
1332
+ writeAttribute(el, attributeName, nextValue);
1333
+ }
1334
+ }
1335
+ //#endregion
1336
+ //#region src/core/dom/refs.ts
1337
+ /**
1338
+ * Refs: a callback receives the element (then null on removal); an object
1339
+ * ref's `current` is assigned. A read-only object ref is ignored.
1340
+ */
1341
+ function setRef(ref, value) {
1342
+ if (!ref) return;
1343
+ if (typeof ref === "function") {
1344
+ ref(value);
1345
+ return;
1346
+ }
1347
+ try {
1348
+ ref.current = value;
1349
+ } catch {}
1350
+ }
1351
+ //#endregion
1352
+ //#region src/core/dom/tree.ts
1353
+ const HOST = 0;
1354
+ const DYNAMIC = 4;
1355
+ /** Parents whose children are DOM children of one element. */
1356
+ function isContainer(parent) {
1357
+ return parent.kind === HOST || parent.kind === 5;
1358
+ }
1359
+ function containerElement(parent) {
1360
+ return parent.el;
1361
+ }
1362
+ /** The element that directly contains `parent`'s DOM. */
1363
+ function containerOf(parent) {
1364
+ for (let p = parent; p; p = p.parent) if (isContainer(p)) return containerElement(p);
1365
+ throw new Error("[Askr] Rendered node is not attached to a container.");
1366
+ }
1367
+ function firstDom(node) {
1368
+ const pending = [node];
1369
+ while (pending.length) {
1370
+ const current = pending.pop();
1371
+ switch (current.kind) {
1372
+ case HOST: return current.el;
1373
+ case 1:
1374
+ case 7: return current.node;
1375
+ default: for (let i = current.children.length - 1; i >= 0; i--) pending.push(current.children[i]);
1376
+ }
1377
+ }
1378
+ return null;
1379
+ }
1380
+ function collectDom(node, out = []) {
1381
+ const pending = [node];
1382
+ while (pending.length) {
1383
+ const current = pending.pop();
1384
+ switch (current.kind) {
1385
+ case HOST:
1386
+ out.push(current.el);
1387
+ break;
1388
+ case 1:
1389
+ case 7:
1390
+ out.push(current.node);
1391
+ break;
1392
+ default: for (let i = current.children.length - 1; i >= 0; i--) pending.push(current.children[i]);
1393
+ }
1394
+ }
1395
+ return out;
1396
+ }
1397
+ /**
1398
+ * The DOM node that content at the end of `parent` must be inserted before:
1399
+ * null for an element's children, otherwise the first DOM after `parent`.
1400
+ */
1401
+ function endOf(parent) {
1402
+ if (parent.kind === 5) return parent.tail?.parentNode === parent.el ? parent.tail : null;
1403
+ if (isContainer(parent)) return null;
1404
+ return nextDomAfter(parent);
1405
+ }
1406
+ /** The first DOM node after `node`'s own DOM within its container. */
1407
+ function nextDomAfter(node) {
1408
+ let current = node;
1409
+ for (let parent = current.parent; parent; parent = current.parent) {
1410
+ const siblings = parent.children;
1411
+ for (let i = siblings.indexOf(current) + 1; i < siblings.length; i++) {
1412
+ const dom = firstDom(siblings[i]);
1413
+ if (dom) return dom;
1414
+ }
1415
+ if (parent.kind === 5) return parent.tail?.parentNode === parent.el ? parent.tail : null;
1416
+ if (isContainer(parent)) return null;
1417
+ current = parent;
1418
+ }
1419
+ return null;
1420
+ }
1421
+ //#endregion
1422
+ //#region src/core/dom/teardown.ts
1423
+ /**
1424
+ * Failures while tearing down removed content are reported after the update,
1425
+ * never thrown into it: the DOM change has already happened and is not
1426
+ * rolled back.
1427
+ */
1428
+ function reportTeardown(errors) {
1429
+ if (errors.length === 1) reportUncaughtErrorLater(errors[0]);
1430
+ else if (errors.length > 1) reportUncaughtErrorLater(new AggregateError(errors, "Cleanup failed while removing content"));
1431
+ }
1432
+ //#endregion
1433
+ //#region src/core/dom/props.ts
1434
+ function isBinding(key, value) {
1435
+ return typeof value === "function" && key !== "ref" && key !== "dangerouslySetInnerHTML";
1436
+ }
1437
+ /** A `<select>` value can only select options that already exist. */
1438
+ function followsChildren(tag, key) {
1439
+ return key === "value" && tag === "select";
1440
+ }
1441
+ function isSimpleAttribute(tag, key, value) {
1442
+ return !isCustomElementName(tag) && !key.includes(":") && getDomPropertyName(tag, key, value) === null && key !== "class" && key !== "className" && key !== "style" && key !== "value" && key !== "checked" && key !== "selected" && key !== "dangerouslySetInnerHTML";
1443
+ }
1444
+ function isInputValue(tag, key) {
1445
+ return key === "value" && (tag === "input" || tag === "textarea");
1446
+ }
1447
+ function isBooleanControl(tag, key) {
1448
+ return tag === "input" && key === "checked" || tag === "option" && key === "selected";
1449
+ }
1450
+ /** Record the live attribute before applying a reversible scalar write. */
1451
+ function writeSimpleAttribute(pass, node, key, value, previous, beforeApply) {
1452
+ const { el, tag } = node;
1453
+ const name = getRenderedAttributeName(el, key);
1454
+ pass.op(() => {
1455
+ recordAttributeUndo(pass, el, name);
1456
+ try {
1457
+ beforeApply?.();
1458
+ applyScalarPropValue(el, key, value, tag, previous);
1459
+ } catch (error) {
1460
+ throw new CommitMutationError(error);
1461
+ }
1462
+ });
1463
+ }
1464
+ /** Record a reflected class or style write for an abortable commit. */
1465
+ function writeReflectedProp(pass, node, key, apply, beforeApply) {
1466
+ const { el } = node;
1467
+ const name = getRenderedAttributeName(el, key);
1468
+ pass.op(() => {
1469
+ recordAttributeUndo(pass, el, name);
1470
+ try {
1471
+ beforeApply?.();
1472
+ apply();
1473
+ } catch (error) {
1474
+ throw new CommitMutationError(error);
1475
+ }
1476
+ });
1477
+ }
1478
+ function recordAttributeUndo(pass, el, name) {
1479
+ const namespace = attributeNamespace(el.namespaceURI, name);
1480
+ const colon = name.indexOf(":");
1481
+ const localName = colon === -1 ? name : name.slice(colon + 1);
1482
+ const before = namespace ? el.getAttributeNS(namespace, localName) : el.getAttribute(name);
1483
+ pass.onReversibleCommit(() => {
1484
+ if (before === null) {
1485
+ if (namespace) el.removeAttributeNS(namespace, localName);
1486
+ else el.removeAttribute(name);
1487
+ } else if (namespace) el.setAttributeNS(namespace, name, before);
1488
+ else el.setAttribute(name, before);
1489
+ });
1490
+ }
1491
+ /** Restore exact child identities without needlessly disconnecting unchanged nodes. */
1492
+ function restoreElementChildren(el, children) {
1493
+ if (el.childNodes.length === children.length) {
1494
+ let unchanged = true;
1495
+ for (let index = 0; index < children.length; index += 1) if (el.childNodes[index] !== children[index]) {
1496
+ unchanged = false;
1497
+ break;
1498
+ }
1499
+ if (unchanged) return;
1500
+ }
1501
+ el.replaceChildren(...children);
1502
+ }
1503
+ /** Make property-backed scalar writes part of the pass rollback journal. */
1504
+ function writePropertyScalar(pass, node, key, value, apply) {
1505
+ pass.op(() => {
1506
+ try {
1507
+ const undo = captureDomPropertyUndo(node.el, key, value, node.tag);
1508
+ if (undo) pass.onReversibleCommit(undo);
1509
+ apply();
1510
+ } catch (error) {
1511
+ throw new CommitMutationError(error);
1512
+ }
1513
+ });
1514
+ }
1515
+ function queueScalarProp(pass, node, key, value, apply) {
1516
+ if (key === "dangerouslySetInnerHTML") {
1517
+ if (!isDangerousInnerHTMLPayload(value)) {
1518
+ pass.op(apply);
1519
+ return;
1520
+ }
1521
+ pass.op(() => {
1522
+ const children = Array.from(node.el.childNodes);
1523
+ pass.onReversibleCommit(() => restoreElementChildren(node.el, children));
1524
+ try {
1525
+ apply();
1526
+ } catch (error) {
1527
+ if (error instanceof CommitMutationError) throw error;
1528
+ throw new CommitMutationError(error);
1529
+ }
1530
+ });
1531
+ return;
1532
+ }
1533
+ if (hasDomPropertyWrite(node.el, key, value, node.tag)) writePropertyScalar(pass, node, key, value, apply);
1534
+ else pass.op(() => {
1535
+ recordAttributeUndo(pass, node.el, getRenderedAttributeName(node.el, key));
1536
+ try {
1537
+ apply();
1538
+ } catch (error) {
1539
+ if (error instanceof CommitMutationError) throw error;
1540
+ throw new CommitMutationError(error);
1541
+ }
1542
+ });
1543
+ }
1544
+ /** Remove a committed binding provisionally and restore it if the pass aborts. */
1545
+ function retireBindingForCommit(pass, node, key) {
1546
+ const binding = node.bindings?.get(key);
1547
+ if (!binding) return void 0;
1548
+ node.bindings.delete(key);
1549
+ pass.onReversibleCommit(() => {
1550
+ (node.bindings ??= /* @__PURE__ */ new Map()).set(key, binding);
1551
+ }, () => reportTeardown(binding.dispose()));
1552
+ return binding;
1553
+ }
1554
+ /** Restore or remove a binding-map entry when a provisional install aborts. */
1555
+ function recordBindingInstallUndo(pass, node, key, binding, previous) {
1556
+ pass.onReversibleCommit(() => {
1557
+ if (node.bindings?.get(key) !== binding) return;
1558
+ if (previous) node.bindings.set(key, previous);
1559
+ else {
1560
+ node.bindings.delete(key);
1561
+ if (node.bindings.size === 0) node.bindings = null;
1562
+ }
1563
+ });
1564
+ }
1565
+ /** Restore both the reflected attribute and the live form value on abort. */
1566
+ function writeInputValue(pass, node, value, previous) {
1567
+ const { el, tag } = node;
1568
+ pass.op(() => {
1569
+ recordInputValueUndo(pass, node);
1570
+ try {
1571
+ applyScalarPropValue(el, "value", value, tag, previous);
1572
+ } catch (error) {
1573
+ throw new CommitMutationError(error);
1574
+ }
1575
+ });
1576
+ }
1577
+ /** Restore a checkbox or option's live state and reflected attribute. */
1578
+ function writeBooleanControl(pass, node, key, value, previous) {
1579
+ const { el, tag } = node;
1580
+ pass.op(() => {
1581
+ recordBooleanControlUndo(pass, node, key);
1582
+ try {
1583
+ applyScalarPropValue(el, key, value, tag, previous);
1584
+ } catch (error) {
1585
+ throw new CommitMutationError(error);
1586
+ }
1587
+ });
1588
+ }
1589
+ function recordInputValueUndo(pass, node) {
1590
+ const control = node.el;
1591
+ const beforeValue = control.value;
1592
+ const beforeAttribute = node.el.getAttribute("value");
1593
+ pass.onReversibleCommit(() => {
1594
+ if (beforeAttribute === null) node.el.removeAttribute("value");
1595
+ else node.el.setAttribute("value", beforeAttribute);
1596
+ control.value = beforeValue;
1597
+ });
1598
+ }
1599
+ function recordBooleanControlUndo(pass, node, key) {
1600
+ const control = node.el;
1601
+ const beforeValue = control[key];
1602
+ const beforeAttribute = node.el.getAttribute(key);
1603
+ pass.onReversibleCommit(() => {
1604
+ if (beforeAttribute === null) node.el.removeAttribute(key);
1605
+ else node.el.setAttribute(key, beforeAttribute);
1606
+ control[key] = beforeValue;
1607
+ });
1608
+ }
1609
+ /** The last value each binding applied, for diffing its next write. */
1610
+ const appliedByBinding = /* @__PURE__ */ new WeakMap();
1611
+ function lastApplied(binding) {
1612
+ return binding ? appliedByBinding.get(binding) : void 0;
1613
+ }
1614
+ function setHandler(node, key, value) {
1615
+ const event = parseEventProp(key);
1616
+ if (typeof value === "function") setListener(node.listeners ??= /* @__PURE__ */ new Map(), node.el, key, event, value, node.owner);
1617
+ else if (node.listeners) removeListener(node.listeners, node.el, key);
1618
+ }
1619
+ function setHandlerForCommit(pass, node, key, value, previous) {
1620
+ pass.op(() => {
1621
+ pass.onReversibleCommit(() => setHandler(node, key, previous));
1622
+ try {
1623
+ setHandler(node, key, value);
1624
+ } catch (error) {
1625
+ throw new CommitMutationError(error);
1626
+ }
1627
+ });
1628
+ }
1629
+ /**
1630
+ * Write props to an element that has no committed props yet: a new, detached
1631
+ * element (written now) or an adopted server element (`adopted`: recorded as
1632
+ * commit operations).
1633
+ */
1634
+ function applyInitialProps(pass, node, adopted = false) {
1635
+ const props = node.props;
1636
+ let scalars = null;
1637
+ const flushScalars = () => {
1638
+ if (!scalars) return;
1639
+ const batch = scalars;
1640
+ scalars = null;
1641
+ if (adopted) for (const key in batch) {
1642
+ const value = batch[key];
1643
+ if (isInputValue(node.tag, key)) writeInputValue(pass, node, value, void 0);
1644
+ else if (isBooleanControl(node.tag, key)) writeBooleanControl(pass, node, key, value, void 0);
1645
+ else if (isSimpleAttribute(node.tag, key, value)) writeSimpleAttribute(pass, node, key, value, void 0);
1646
+ else if (key === "class" || key === "className" || key === "style") writeReflectedProp(pass, node, key, () => applyStaticScalarPropsToElement(node.el, { [key]: value }, node.tag));
1647
+ else queueScalarProp(pass, node, key, value, () => applyStaticScalarPropsToElement(node.el, { [key]: value }, node.tag));
1648
+ }
1649
+ else applyStaticScalarPropsToElement(node.el, batch, node.tag);
1650
+ };
1651
+ for (const key in props) {
1652
+ if (isSkippedProp(key)) continue;
1653
+ const value = props[key];
1654
+ if (parseEventProp(key)) {
1655
+ flushScalars();
1656
+ if (adopted) setHandlerForCommit(pass, node, key, value, void 0);
1657
+ else {
1658
+ setHandler(node, key, value);
1659
+ recordUndo(() => setHandler(node, key, void 0));
1660
+ }
1661
+ } else if (isBinding(key, value)) {
1662
+ flushScalars();
1663
+ if (!followsChildren(node.tag, key)) bind(pass, node, key, value, void 0, !adopted);
1664
+ } else if (!followsChildren(node.tag, key)) (scalars ??= {})[key] = value;
1665
+ }
1666
+ flushScalars();
1667
+ }
1668
+ /** Write props that must follow the element's children. */
1669
+ function captureSelectUndo(select) {
1670
+ const beforeValue = select.getAttribute("value");
1671
+ const beforeIndex = select.selectedIndex;
1672
+ const beforeOptions = Array.from(select.options, (option) => ({
1673
+ option,
1674
+ selected: option.selected,
1675
+ attribute: option.hasAttribute("selected")
1676
+ }));
1677
+ return () => {
1678
+ if (beforeValue === null) select.removeAttribute("value");
1679
+ else select.setAttribute("value", beforeValue);
1680
+ for (const { option, attribute } of beforeOptions) if (attribute) option.setAttribute("selected", "");
1681
+ else option.removeAttribute("selected");
1682
+ if (select.multiple) for (const { option, selected } of beforeOptions) option.selected = selected;
1683
+ else select.selectedIndex = beforeIndex;
1684
+ };
1685
+ }
1686
+ function recordSelectUndo(pass, node) {
1687
+ pass.onReversibleCommit(captureSelectUndo(node.el));
1688
+ }
1689
+ function applyTrailingProps(pass, node, props, initial = false, adopted = false) {
1690
+ if (node.tag !== "select" || !("value" in props)) return;
1691
+ if (initial && adopted) pass.op(() => recordSelectUndo(pass, node));
1692
+ if (typeof props.value === "function") {
1693
+ if (initial) bind(pass, node, "value", props.value, void 0, !adopted);
1694
+ else node.bindings?.get("value")?.run();
1695
+ return;
1696
+ }
1697
+ const writeValue = () => {
1698
+ try {
1699
+ applyScalarPropValue(node.el, "value", props.value, node.tag, void 0);
1700
+ } catch (error) {
1701
+ if (initial && !adopted) throw error;
1702
+ throw new CommitMutationError(error);
1703
+ }
1704
+ };
1705
+ if (initial && adopted) pass.op(writeValue);
1706
+ else writeValue();
1707
+ }
1708
+ /** The select whose options include content under `parent`, if any. */
1709
+ function enclosingSelect(parent) {
1710
+ for (let p = parent; p; p = p.parent) {
1711
+ if (p.kind !== HOST) {
1712
+ if (p.kind === 5) return null;
1713
+ continue;
1714
+ }
1715
+ if (p.tag === "select") return p;
1716
+ if (p.tag !== "optgroup" && p.tag !== "option") return null;
1717
+ }
1718
+ return null;
1719
+ }
1720
+ /**
1721
+ * Re-apply a committed select's controlled value against its current
1722
+ * options. Its options changed without the select itself being patched.
1723
+ */
1724
+ function resyncSelect(select) {
1725
+ if (select.dormant || !("value" in select.props)) return;
1726
+ const value = select.props.value;
1727
+ const resolved = typeof value === "function" ? readValue(value) : value;
1728
+ const undo = captureSelectUndo(select.el);
1729
+ try {
1730
+ applyScalarPropValue(select.el, "value", resolved, select.tag, void 0);
1731
+ } catch (error) {
1732
+ try {
1733
+ undo();
1734
+ } catch (rollbackError) {
1735
+ throw new AggregateError([error, rollbackError], "Controlled select resynchronization and rollback failed");
1736
+ }
1737
+ throw error;
1738
+ }
1739
+ }
1740
+ /** Record the writes that turn `previous` into `next` on a committed element. */
1741
+ function patchProps(pass, node, previous, next) {
1742
+ const { el, tag } = node;
1743
+ for (const key in previous) {
1744
+ if (key in next || isSkippedProp(key)) continue;
1745
+ if (followsChildren(tag, key)) pass.op(() => recordSelectUndo(pass, node));
1746
+ const old = previous[key];
1747
+ if (parseEventProp(key)) setHandlerForCommit(pass, node, key, void 0, old);
1748
+ else if (!isBinding(key, old) && isInputValue(tag, key)) writeInputValue(pass, node, void 0, old);
1749
+ else if (!isBinding(key, old) && isBooleanControl(tag, key)) writeBooleanControl(pass, node, key, void 0, old);
1750
+ else if (!isBinding(key, old) && isSimpleAttribute(tag, key, void 0)) writeSimpleAttribute(pass, node, key, void 0, old);
1751
+ else if (!isBinding(key, old) && (key === "class" || key === "className" || key === "style")) writeReflectedProp(pass, node, key, () => applyScalarPropValue(el, key, void 0, tag, old));
1752
+ else if (isBinding(key, old) && isSimpleAttribute(tag, key, void 0)) {
1753
+ const binding = node.bindings?.get(key);
1754
+ writeSimpleAttribute(pass, node, key, void 0, lastApplied(binding), () => retireBindingForCommit(pass, node, key));
1755
+ } else if (isBinding(key, old) && (key === "class" || key === "className" || key === "style")) {
1756
+ const binding = node.bindings?.get(key);
1757
+ writeReflectedProp(pass, node, key, () => applyScalarPropValue(el, key, void 0, tag, lastApplied(binding)), () => retireBindingForCommit(pass, node, key));
1758
+ } else queueScalarProp(pass, node, key, void 0, () => {
1759
+ const binding = retireBindingForCommit(pass, node, key);
1760
+ const from = isBinding(key, old) ? lastApplied(binding) : old;
1761
+ try {
1762
+ applyScalarPropValue(el, key, void 0, tag, from);
1763
+ } catch (error) {
1764
+ if (!followsChildren(tag, key)) throw error;
1765
+ throw new CommitMutationError(error);
1766
+ }
1767
+ });
1768
+ }
1769
+ for (const key in next) {
1770
+ if (isSkippedProp(key)) continue;
1771
+ if (followsChildren(tag, key)) pass.op(() => recordSelectUndo(pass, node));
1772
+ const value = next[key];
1773
+ const old = previous[key];
1774
+ if (parseEventProp(key)) {
1775
+ if (value !== old) setHandlerForCommit(pass, node, key, value, old);
1776
+ continue;
1777
+ }
1778
+ if (isBinding(key, value)) {
1779
+ if (value === old && node.bindings?.has(key)) continue;
1780
+ const replaced = node.bindings?.get(key);
1781
+ const from = replaced ? lastApplied(replaced) : old;
1782
+ pass.op(() => retireBindingForCommit(pass, node, key));
1783
+ bind(pass, node, key, value, from, false);
1784
+ continue;
1785
+ }
1786
+ if (isBinding(key, old)) {
1787
+ const binding = node.bindings?.get(key);
1788
+ const from = lastApplied(binding);
1789
+ if (isSimpleAttribute(tag, key, value)) writeSimpleAttribute(pass, node, key, value, from, () => retireBindingForCommit(pass, node, key));
1790
+ else if (key === "class" || key === "className" || key === "style") writeReflectedProp(pass, node, key, () => applyScalarPropValue(el, key, value, tag, from), () => retireBindingForCommit(pass, node, key));
1791
+ else queueScalarProp(pass, node, key, value, () => {
1792
+ const current = retireBindingForCommit(pass, node, key);
1793
+ applyScalarPropValue(el, key, value, tag, lastApplied(current));
1794
+ });
1795
+ continue;
1796
+ }
1797
+ if (Object.is(value, old) && key === "dangerouslySetInnerHTML") continue;
1798
+ if (isInputValue(tag, key)) writeInputValue(pass, node, value, old);
1799
+ else if (isBooleanControl(tag, key)) writeBooleanControl(pass, node, key, value, old);
1800
+ else if (isSimpleAttribute(tag, key, value)) writeSimpleAttribute(pass, node, key, value, old);
1801
+ else if (key === "class" || key === "className" || key === "style") writeReflectedProp(pass, node, key, () => applyScalarPropValue(el, key, value, tag, old));
1802
+ else queueScalarProp(pass, node, key, value, () => {
1803
+ try {
1804
+ applyScalarPropValue(el, key, value, tag, old);
1805
+ } catch (error) {
1806
+ if (!followsChildren(tag, key)) throw error;
1807
+ throw new CommitMutationError(error);
1808
+ }
1809
+ });
1810
+ }
1811
+ }
1812
+ function bind(pass, node, key, read, previousValue, fresh) {
1813
+ const { el, tag } = node;
1814
+ let last = previousValue;
1815
+ let installing = true;
1816
+ let propertyBacked = false;
1817
+ const binding = new Computation(node.owner, () => {
1818
+ try {
1819
+ const value = key.startsWith("prop:") ? read() : readValue(read);
1820
+ propertyBacked = hasDomPropertyWrite(el, key, value, tag);
1821
+ const propertyUndo = propertyBacked ? captureDomPropertyUndo(el, key, value, tag) : null;
1822
+ if (propertyUndo && installing && !fresh) pass.onReversibleCommit(propertyUndo);
1823
+ try {
1824
+ applyScalarPropValue(el, key, value, tag, last);
1825
+ } catch (error) {
1826
+ if (propertyUndo && (fresh || !installing)) try {
1827
+ propertyUndo();
1828
+ } catch (rollbackError) {
1829
+ throw new AggregateError([error, rollbackError], "Bound DOM property write and rollback failed");
1830
+ }
1831
+ throw error;
1832
+ }
1833
+ const changed = !Object.is(last, value);
1834
+ last = value;
1835
+ if (changed && tag === "option" && key === "value") {
1836
+ const select = enclosingSelect(node.parent);
1837
+ if (select) resyncSelect(select);
1838
+ }
1839
+ appliedByBinding.set(binding, value);
1840
+ } catch (error) {
1841
+ if (!deliverToBoundary(node.owner, error)) throw error;
1842
+ }
1843
+ }, effectScheduler("effect", (node.owner?.depth ?? 0) + 1), null, "previous");
1844
+ pass.own(binding);
1845
+ const install = () => {
1846
+ const previous = node.bindings?.get(key);
1847
+ (node.bindings ??= /* @__PURE__ */ new Map()).set(key, binding);
1848
+ const reversibleAttribute = isSimpleAttribute(tag, key, void 0) || key === "class" || key === "className" || key === "style";
1849
+ const reversibleControl = isInputValue(tag, key) || isBooleanControl(tag, key);
1850
+ if (!fresh) {
1851
+ recordBindingInstallUndo(pass, node, key, binding, previous);
1852
+ if (reversibleAttribute) recordAttributeUndo(pass, el, getRenderedAttributeName(el, key));
1853
+ else if (isInputValue(tag, key)) recordInputValueUndo(pass, node);
1854
+ else if (isBooleanControl(tag, key)) recordBooleanControlUndo(pass, node, key);
1855
+ }
1856
+ try {
1857
+ binding.run();
1858
+ if (binding._hasError) throw binding._error;
1859
+ } catch (error) {
1860
+ if (!fresh && (reversibleAttribute || reversibleControl || propertyBacked)) throw new CommitMutationError(error);
1861
+ throw error;
1862
+ } finally {
1863
+ installing = false;
1864
+ }
1865
+ };
1866
+ if (fresh) install();
1867
+ else pass.op(install);
1868
+ }
1869
+ /** Point the element's ref at it once the pass commits. */
1870
+ function attachRef(pass, node, previous) {
1871
+ const ref = node.props.ref;
1872
+ if (ref === previous) return;
1873
+ const el = node.el;
1874
+ pass.after(() => {
1875
+ if (previous) setRef(previous, null);
1876
+ if (ref) setRef(ref, el);
1877
+ });
1878
+ }
1879
+ /** End a removed element's bindings, listeners, and ref. */
1880
+ function releaseProps(node, errors) {
1881
+ if (node.bindings) {
1882
+ for (const binding of node.bindings.values()) binding.dispose(errors);
1883
+ node.bindings = null;
1884
+ }
1885
+ if (node.listeners) removeAllListeners(node.listeners, node.el, errors);
1886
+ const ref = node.props.ref;
1887
+ if (ref) try {
1888
+ setRef(ref, null);
1889
+ } catch (error) {
1890
+ errors.push(error);
1891
+ }
1892
+ }
1893
+ //#endregion
1894
+ //#region src/core/dom/reconcile.ts
1895
+ function withOwner(ctx, owner) {
1896
+ return owner === ctx.owner ? ctx : {
1897
+ ...ctx,
1898
+ owner
1899
+ };
1900
+ }
1901
+ function typeOf(node) {
1902
+ switch (node.kind) {
1903
+ case HOST: return node.tag;
1904
+ case 2: return node.instance.fn;
1905
+ case 7: return node.node;
1906
+ default: return node.kind;
1907
+ }
1908
+ }
1909
+ function matches(node, child) {
1910
+ return node.kind === child.kind && typeOf(node) === descriptorType(child);
1911
+ }
1912
+ /**
1913
+ * Reconcile `parent`'s children against `value`.
1914
+ *
1915
+ * With `fresh`, `parent` is new: its children are built and, for an element,
1916
+ * appended to it directly. Otherwise the committed list is replaced by an
1917
+ * operation. Returns the new child list.
1918
+ */
1919
+ function reconcileChildren(ctx, parent, value, fresh, checkKeys = true) {
1920
+ const next = normalizeChildren(value);
1921
+ if (fresh) {
1922
+ const seen = /* @__PURE__ */ new Set();
1923
+ for (const child of next) {
1924
+ if (child.key === void 0) continue;
1925
+ if (seen.has(child.key)) throw new Error(`[Askr] Duplicate key ${String(child.key)} among siblings.`);
1926
+ seen.add(child.key);
1927
+ }
1928
+ const result = next.map((child) => ctx.nodes.create(ctx, parent, child));
1929
+ if (parent.kind === HOST) {
1930
+ if (ctx.hydrate?.container === parent.el) {
1931
+ const el = parent.el;
1932
+ ctx.pass.op(() => syncChildren(ctx.pass, el, result.flatMap((node) => collectDom(node))));
1933
+ } else for (const node of result) for (const dom of collectDom(node)) parent.el.appendChild(dom);
1934
+ }
1935
+ return result;
1936
+ }
1937
+ const previous = parent.children;
1938
+ if (next.length === previous.length) {
1939
+ let samePositions = true;
1940
+ for (let i = 0; i < next.length; i++) if (next[i].key !== previous[i].key || !matches(previous[i], next[i])) {
1941
+ samePositions = false;
1942
+ break;
1943
+ }
1944
+ if (samePositions) {
1945
+ const result = previous.slice();
1946
+ for (let i = 0; i < next.length; i++) ctx.nodes.patch(ctx, previous[i], next[i]);
1947
+ return result;
1948
+ }
1949
+ }
1950
+ const slot = ctx.pass.reserve();
1951
+ const index = indexChildren(previous);
1952
+ const used = new Uint8Array(previous.length);
1953
+ const sources = new Int32Array(next.length).fill(-1);
1954
+ const result = [];
1955
+ const cursors = /* @__PURE__ */ new Map();
1956
+ let seen = null;
1957
+ for (let i = 0; i < next.length; i++) {
1958
+ const child = next[i];
1959
+ let match = -1;
1960
+ if (child.key !== void 0) {
1961
+ seen ??= /* @__PURE__ */ new Set();
1962
+ if (seen.has(child.key)) throw new Error(`[Askr] Duplicate key ${String(child.key)} among siblings.`);
1963
+ seen.add(child.key);
1964
+ const at = index.keyed?.get(child.key);
1965
+ if (at !== void 0 && !used[at] && matches(previous[at], child)) match = at;
1966
+ } else {
1967
+ const type = descriptorType(child);
1968
+ const bucket = index.unkeyed?.get(type);
1969
+ if (bucket) {
1970
+ let cursor = cursors.get(type) ?? 0;
1971
+ while (cursor < bucket.length && used[bucket[cursor]]) cursor++;
1972
+ if (cursor < bucket.length && matches(previous[bucket[cursor]], child)) {
1973
+ match = bucket[cursor];
1974
+ cursor++;
1975
+ }
1976
+ cursors.set(type, cursor);
1977
+ }
1978
+ }
1979
+ if (match >= 0) {
1980
+ used[match] = 1;
1981
+ sources[i] = match;
1982
+ ctx.nodes.patch(ctx, previous[match], child);
1983
+ result.push(previous[match]);
1984
+ } else result.push(ctx.nodes.create(ctx, parent, child));
1985
+ }
1986
+ const removed = previous.filter((_, i) => !used[i]);
1987
+ if (removed.length === 0 && result.length === previous.length && result.every((node, index) => node === previous[index])) return result;
1988
+ const stable = longestIncreasingSubsequence(sources);
1989
+ const nodes = ctx.nodes;
1990
+ nodes.listChanged?.(ctx, parent);
1991
+ ctx.pass.fill(slot, () => {
1992
+ const positions = [];
1993
+ const record = (node) => {
1994
+ positions.push({
1995
+ node,
1996
+ parent: node.parentNode,
1997
+ next: node.nextSibling
1998
+ });
1999
+ };
2000
+ ctx.pass.onReversibleCommit(() => {
2001
+ for (let index = positions.length - 1; index >= 0; index--) {
2002
+ const { node, parent, next } = positions[index];
2003
+ if (!parent) node.parentNode?.removeChild(node);
2004
+ else if (node.parentNode !== parent || node.nextSibling !== next) parent.insertBefore(node, next);
2005
+ }
2006
+ parent.children = previous;
2007
+ }, () => {
2008
+ const errors = [];
2009
+ for (const node of removed) nodes.release(node, errors);
2010
+ reportTeardown(errors);
2011
+ });
2012
+ try {
2013
+ placeChildren(parent, result, sources, stable, record);
2014
+ for (const node of removed) for (const dom of collectDom(node)) {
2015
+ record(dom);
2016
+ dom.parentNode?.removeChild(dom);
2017
+ }
2018
+ } catch (error) {
2019
+ throw new CommitMutationError(error);
2020
+ }
2021
+ parent.children = result;
2022
+ for (const node of result) if (node.parent !== parent) node.parent = parent;
2023
+ });
2024
+ return result;
2025
+ }
2026
+ function indexChildren(children) {
2027
+ const index = {
2028
+ keyed: null,
2029
+ unkeyed: null
2030
+ };
2031
+ for (let i = 0; i < children.length; i++) {
2032
+ const node = children[i];
2033
+ if (node.key !== void 0) {
2034
+ (index.keyed ??= /* @__PURE__ */ new Map()).set(node.key, i);
2035
+ continue;
2036
+ }
2037
+ const type = typeOf(node);
2038
+ const bucket = (index.unkeyed ??= /* @__PURE__ */ new Map()).get(type);
2039
+ if (bucket) bucket.push(i);
2040
+ else index.unkeyed.set(type, [i]);
2041
+ }
2042
+ return index;
2043
+ }
2044
+ /**
2045
+ * Put `children`'s DOM in order, walking backwards from the end so every
2046
+ * insertion has a settled reference node. Reused children on the stable
2047
+ * subsequence stay where they are.
2048
+ */
2049
+ function placeChildren(parent, children, sources, stable, record) {
2050
+ const container = containerOf(parent);
2051
+ let next = endOf(parent);
2052
+ for (let i = children.length - 1; i >= 0; i--) {
2053
+ const node = children[i];
2054
+ if (sources[i] >= 0 && stable.has(i)) {
2055
+ next = firstDom(node) ?? next;
2056
+ continue;
2057
+ }
2058
+ const nodes = collectDom(node);
2059
+ for (let j = nodes.length - 1; j >= 0; j--) {
2060
+ const dom = nodes[j];
2061
+ if (dom.parentNode !== container || dom.nextSibling !== next) {
2062
+ record(dom);
2063
+ container.insertBefore(dom, next);
2064
+ }
2065
+ next = dom;
2066
+ }
2067
+ }
2068
+ }
2069
+ /** Indices of a longest increasing run of `sources` values (ignoring -1). */
2070
+ function longestIncreasingSubsequence(sources) {
2071
+ const tails = [];
2072
+ const previous = new Int32Array(sources.length).fill(-1);
2073
+ for (let i = 0; i < sources.length; i++) {
2074
+ const value = sources[i];
2075
+ if (value < 0) continue;
2076
+ let low = 0;
2077
+ let high = tails.length;
2078
+ while (low < high) {
2079
+ const mid = low + high >> 1;
2080
+ if (sources[tails[mid]] < value) low = mid + 1;
2081
+ else high = mid;
2082
+ }
2083
+ if (low > 0) previous[i] = tails[low - 1];
2084
+ tails[low] = i;
2085
+ }
2086
+ const result = /* @__PURE__ */ new Set();
2087
+ for (let i = tails.length ? tails[tails.length - 1] : -1; i >= 0; i = previous[i]) result.add(i);
2088
+ return result;
2089
+ }
2090
+ //#endregion
2091
+ //#region src/core/dom/nodes.ts
2092
+ const SVG_NS = "http://www.w3.org/2000/svg";
2093
+ const MATHML_NS = "http://www.w3.org/1998/Math/MathML";
2094
+ /** Namespace for children of an element in namespace `ns` with tag `tag`. */
2095
+ function childNamespace(tag, ns) {
2096
+ if (tag === "svg") return SVG_NS;
2097
+ if (tag === "math") return MATHML_NS;
2098
+ if (ns === SVG_NS && tag === "foreignObject") return null;
2099
+ return ns;
2100
+ }
2101
+ function elementNamespace(tag, parentNs) {
2102
+ if (tag === "svg") return SVG_NS;
2103
+ if (tag === "math") return MATHML_NS;
2104
+ return parentNs;
2105
+ }
2106
+ /** Namespace new children of `parent` are created in. */
2107
+ function namespaceAt(parent) {
2108
+ for (let p = parent; p; p = p.parent) {
2109
+ if (p.kind === HOST) return childNamespace(p.tag, namespaceOf(p.el));
2110
+ if (p.kind === 5) return childNamespace(p.el.localName, namespaceOf(p.el));
2111
+ }
2112
+ return null;
2113
+ }
2114
+ function createRenderContext(pass, owner, ns, hydrate = null) {
2115
+ return {
2116
+ pass,
2117
+ owner,
2118
+ ns,
2119
+ nodes: domNodes,
2120
+ hydrate
2121
+ };
2122
+ }
2123
+ /** Schedules a standalone update of a function child (see `updates`). */
2124
+ let scheduleDynamicUpdate = () => {};
2125
+ function setDynamicUpdateScheduler(schedule) {
2126
+ scheduleDynamicUpdate = schedule;
2127
+ }
2128
+ function nearestInstance(owner) {
2129
+ for (let o = owner; o; o = o.parent) if (o instanceof ComponentInstance) return o;
2130
+ return null;
2131
+ }
2132
+ function createHost(ctx, parent, key, tag, props) {
2133
+ const ns = elementNamespace(tag, ctx.ns);
2134
+ const hydrate = ctx.hydrate;
2135
+ const adopted = hydrate ? hydrate.cursor.claimElement(hydrate.container, tag, ns) : null;
2136
+ const el = adopted ?? (ns ? document.createElementNS(ns, tag) : document.createElement(tag));
2137
+ const node = {
2138
+ kind: HOST,
2139
+ parent,
2140
+ key,
2141
+ el,
2142
+ tag,
2143
+ props,
2144
+ children: [],
2145
+ bindings: null,
2146
+ listeners: null,
2147
+ owner: nearestInstance(ctx.owner)
2148
+ };
2149
+ if (adopted?.hasAttribute(SKIP_HYDRATE)) {
2150
+ ctx.pass.hasDormantPortalWriter ||= containsPortalWriterAnchor(adopted);
2151
+ node.dormant = {
2152
+ owner: ctx.owner,
2153
+ ns
2154
+ };
2155
+ ctx.pass.op(() => {
2156
+ const previous = dormantHosts.get(adopted);
2157
+ ctx.pass.onReversibleCommit(() => {
2158
+ if (previous) dormantHosts.set(adopted, previous);
2159
+ else dormantHosts.delete(adopted);
2160
+ });
2161
+ dormantHosts.set(adopted, node);
2162
+ });
2163
+ return node;
2164
+ }
2165
+ applyInitialProps(ctx.pass, node, adopted !== null);
2166
+ if (adopted) ctx.pass.op(() => {
2167
+ try {
2168
+ removeUnrenderedAttributes(adopted, props, (attributes) => {
2169
+ ctx.pass.onReversibleCommit(() => {
2170
+ for (const attribute of attributes) adopted.setAttributeNode(attribute);
2171
+ });
2172
+ });
2173
+ } catch (error) {
2174
+ throw new CommitMutationError(error);
2175
+ }
2176
+ });
2177
+ if (!isDangerousInnerHTMLPayload(props.dangerouslySetInnerHTML)) node.children = reconcileChildren({
2178
+ ...ctx,
2179
+ ns: childNamespace(tag, ns),
2180
+ hydrate: adopted && hydrate ? {
2181
+ cursor: hydrate.cursor,
2182
+ container: adopted
2183
+ } : null
2184
+ }, node, props.children, true);
2185
+ applyTrailingProps(ctx.pass, node, props, true, adopted !== null);
2186
+ attachRef(ctx.pass, node, void 0);
2187
+ return node;
2188
+ }
2189
+ const SKIP_HYDRATE = "data-skip-hydrate";
2190
+ const dormantHosts = /* @__PURE__ */ new WeakMap();
2191
+ function containsPortalWriterAnchor(root) {
2192
+ const pending = Array.from(root.childNodes);
2193
+ while (pending.length) {
2194
+ const node = pending.pop();
2195
+ if (isSSRPortalWriterAnchor(node)) return true;
2196
+ for (const child of node.childNodes) pending.push(child);
2197
+ }
2198
+ return false;
2199
+ }
2200
+ /** The dormant host adopted for `el`, if it has not been activated. */
2201
+ function dormantHostFor(el) {
2202
+ const node = dormantHosts.get(el);
2203
+ return node?.dormant ? node : null;
2204
+ }
2205
+ /**
2206
+ * Hydrate a dormant host in place: claim its server children, then apply
2207
+ * its props, listeners, and ref. The marker is removed when the pass
2208
+ * commits, so a failed activation leaves it for a retry.
2209
+ */
2210
+ function hydrateDormantHost(pass, node) {
2211
+ const dormant = node.dormant;
2212
+ const el = node.el;
2213
+ const ctx = createRenderContext(pass, dormant.owner, dormant.ns, {
2214
+ cursor: new HydrationCursor(),
2215
+ container: el
2216
+ });
2217
+ const props = node.props;
2218
+ applyInitialProps(pass, node, true);
2219
+ if (!isDangerousInnerHTMLPayload(props.dangerouslySetInnerHTML)) {
2220
+ node.children = reconcileChildren({
2221
+ ...ctx,
2222
+ ns: childNamespace(node.tag, dormant.ns)
2223
+ }, node, props.children, true);
2224
+ pass.onDiscard(() => {
2225
+ node.children = [];
2226
+ });
2227
+ }
2228
+ applyTrailingProps(pass, node, props, true, true);
2229
+ attachRef(pass, node, void 0);
2230
+ pass.op(() => {
2231
+ pass.onReversibleCommit(() => {
2232
+ node.dormant = dormant;
2233
+ dormantHosts.set(el, node);
2234
+ });
2235
+ try {
2236
+ removeUnrenderedAttributes(el, props, (attributes) => {
2237
+ pass.onReversibleCommit(() => {
2238
+ for (const attribute of attributes) el.setAttributeNode(attribute);
2239
+ });
2240
+ });
2241
+ } catch (error) {
2242
+ throw new CommitMutationError(error);
2243
+ }
2244
+ node.dormant = null;
2245
+ dormantHosts.delete(el);
2246
+ });
2247
+ }
2248
+ function ownsChildren(props) {
2249
+ return !props.imperativeChildren && !isDangerousInnerHTMLPayload(props.dangerouslySetInnerHTML);
2250
+ }
2251
+ function patchHost(ctx, node, props) {
2252
+ const previous = node.props;
2253
+ if (previous === props) return;
2254
+ if (node.dormant) {
2255
+ ctx.pass.op(() => {
2256
+ const previous = node.props;
2257
+ ctx.pass.onReversibleCommit(() => {
2258
+ node.props = previous;
2259
+ });
2260
+ node.props = props;
2261
+ });
2262
+ return;
2263
+ }
2264
+ const wasManaged = ownsChildren(previous);
2265
+ const isManaged = ownsChildren(props);
2266
+ if (wasManaged && !isManaged) reconcileChildren(ctx, node, null, false);
2267
+ patchProps(ctx.pass, node, previous, props);
2268
+ if (node.tag === "option" && !Object.is(previous.value, props.value)) syncEnclosingSelect(ctx.pass, node.parent);
2269
+ if (!wasManaged && isManaged && node.children.length === 0) ctx.pass.op(() => {
2270
+ const children = Array.from(node.el.childNodes);
2271
+ ctx.pass.onReversibleCommit(() => restoreElementChildren(node.el, children));
2272
+ try {
2273
+ node.el.replaceChildren();
2274
+ } catch (error) {
2275
+ throw new CommitMutationError(error);
2276
+ }
2277
+ });
2278
+ if (isManaged) {
2279
+ reconcileChildren({
2280
+ ...ctx,
2281
+ ns: childNamespace(node.tag, namespaceOf(node.el))
2282
+ }, node, props.children, false);
2283
+ if (props.children === void 0 || props.children === null || props.children === false) ctx.pass.op(() => {
2284
+ if (!node.el.firstChild) return;
2285
+ const previous = Array.from(node.el.childNodes);
2286
+ ctx.pass.onReversibleCommit(() => restoreElementChildren(node.el, previous));
2287
+ try {
2288
+ node.el.replaceChildren();
2289
+ } catch (error) {
2290
+ throw new CommitMutationError(error);
2291
+ }
2292
+ });
2293
+ }
2294
+ ctx.pass.op(() => {
2295
+ const previous = node.props;
2296
+ ctx.pass.onReversibleCommit(() => {
2297
+ node.props = previous;
2298
+ });
2299
+ node.props = props;
2300
+ });
2301
+ if (node.tag === "select") ctx.pass.op(() => {
2302
+ applyTrailingProps(ctx.pass, node, props);
2303
+ markSelectSynced(ctx.pass, node);
2304
+ });
2305
+ if (props.ref !== previous.ref) attachRef(ctx.pass, {
2306
+ ...node,
2307
+ props
2308
+ }, previous.ref);
2309
+ }
2310
+ function namespaceOf(el) {
2311
+ const uri = el.namespaceURI;
2312
+ return uri === SVG_NS || uri === MATHML_NS ? uri : null;
2313
+ }
2314
+ function createComponent(ctx, parent, key, fn, props) {
2315
+ const instance = new ComponentInstance(ctx.owner, fn, props);
2316
+ ctx.pass.own(instance);
2317
+ const node = {
2318
+ kind: 2,
2319
+ parent,
2320
+ key,
2321
+ instance,
2322
+ children: []
2323
+ };
2324
+ instance.view = node;
2325
+ node.children = renderInstance(ctx, node, true);
2326
+ return node;
2327
+ }
2328
+ function propsChanged(previous, next) {
2329
+ if (previous === next) return false;
2330
+ let count = 0;
2331
+ for (const key in previous) {
2332
+ count++;
2333
+ if (!(key in next) || !Object.is(previous[key], next[key])) return true;
2334
+ }
2335
+ for (const key in next) count--;
2336
+ return count !== 0;
2337
+ }
2338
+ function patchComponent(ctx, node, props) {
2339
+ const instance = node.instance;
2340
+ if (!propsChanged(instance.props, props) && !instance.computation.stale && !instance.computation._hasError && instance.seenAncestorContextRevision === ancestorContextRevision(instance)) return;
2341
+ instance.setProps(props);
2342
+ renderInstance(ctx, node, false);
2343
+ }
2344
+ function nearestComponent(instance) {
2345
+ for (let owner = instance.parent; owner; owner = owner.parent) if (owner instanceof ComponentInstance) return owner;
2346
+ return null;
2347
+ }
2348
+ /**
2349
+ * Highest `contextRevision` among `instance`'s component ancestors. Each
2350
+ * instance caches its inherited value for the current context epoch, so
2351
+ * patching every component in a deep chain stays linear. The cache relies on
2352
+ * a live instance's parent chain never changing (owners are not reparented).
2353
+ * The epoch is global, so a `provide()` anywhere invalidates every cache; the
2354
+ * worst case is the uncached O(depth) walk.
2355
+ */
2356
+ function ancestorContextRevision(instance) {
2357
+ const epoch = getContextEpoch();
2358
+ let chain = null;
2359
+ let revision = 0;
2360
+ for (let current = nearestComponent(instance); current;) {
2361
+ if (current.inheritedContextEpoch === epoch) {
2362
+ revision = Math.max(current.inheritedContextRevision, current.contextRevision);
2363
+ break;
2364
+ }
2365
+ (chain ??= []).push(current);
2366
+ current = nearestComponent(current);
2367
+ }
2368
+ if (!chain) return revision;
2369
+ for (let index = chain.length - 1; index >= 0; index--) {
2370
+ const ancestor = chain[index];
2371
+ ancestor.inheritedContextRevision = revision;
2372
+ ancestor.inheritedContextEpoch = epoch;
2373
+ revision = Math.max(revision, ancestor.contextRevision);
2374
+ }
2375
+ return revision;
2376
+ }
2377
+ /**
2378
+ * Render a component and reconcile its output. An error boundary catches a
2379
+ * failure anywhere in its subtree: the work recorded since it started is
2380
+ * rewound and it renders again showing its fallback.
2381
+ */
2382
+ /** The component rendering now while its root hydrates. */
2383
+ let hydratingRender = null;
2384
+ function renderComponent(ctx, node) {
2385
+ const previous = hydratingRender;
2386
+ hydratingRender = ctx.hydrate ? {
2387
+ ctx,
2388
+ node
2389
+ } : null;
2390
+ node.instance.hydrationResourceKeys = ctx.hydrate ? ctx.hydrate.cursor.claimResourceSlots(ctx.hydrate.container) : null;
2391
+ try {
2392
+ return node.instance.render((undo) => ctx.pass.onDiscard(undo));
2393
+ } finally {
2394
+ node.instance.hydrationResourceKeys = null;
2395
+ hydratingRender = previous;
2396
+ }
2397
+ }
2398
+ function isHydratingRender() {
2399
+ return hydratingRender !== null;
2400
+ }
2401
+ function commitSeenAncestorContextRevision(pass, instance, revision) {
2402
+ pass.op(() => {
2403
+ const previous = instance.seenAncestorContextRevision;
2404
+ pass.onReversibleCommit(() => {
2405
+ instance.seenAncestorContextRevision = previous;
2406
+ });
2407
+ instance.seenAncestorContextRevision = revision;
2408
+ });
2409
+ }
2410
+ /**
2411
+ * Render the hydrating component again after the rest of its root, claiming
2412
+ * the server nodes reserved at its position (portal hosts whose content is
2413
+ * written later in the same render).
2414
+ */
2415
+ function deferHydratingRender(instance) {
2416
+ const active = hydratingRender;
2417
+ if (!active || active.node.instance !== instance) return false;
2418
+ const { ctx, node } = active;
2419
+ const hydrate = ctx.hydrate;
2420
+ const cursor = hydrate.cursor.reserve(hydrate.container);
2421
+ hydrate.cursor.deferred.push({ render: () => {
2422
+ if (instance.disposed) return;
2423
+ if (!instance.computation.stale) {
2424
+ if (ctx.pass.hasDormantPortalWriter) {
2425
+ const held = cursor.heldNodes(hydrate.container);
2426
+ node.children = held.map((dom) => ({
2427
+ kind: 7,
2428
+ parent: node,
2429
+ key: void 0,
2430
+ node: dom
2431
+ }));
2432
+ node.deferredHydration = {
2433
+ cursor,
2434
+ container: hydrate.container
2435
+ };
2436
+ }
2437
+ return;
2438
+ }
2439
+ renderInstance({
2440
+ ...ctx,
2441
+ hydrate: {
2442
+ cursor,
2443
+ container: hydrate.container
2444
+ }
2445
+ }, node, true);
2446
+ } });
2447
+ return true;
2448
+ }
2449
+ /** Adopt held server portal output when its deferred writer first runs. */
2450
+ function hydrateDeferredComponent(ctx, node, output) {
2451
+ const deferred = node.deferredHydration;
2452
+ const previous = node.children;
2453
+ const next = reconcileChildren(withOwner({
2454
+ ...ctx,
2455
+ hydrate: deferred
2456
+ }, node.instance), node, output, true);
2457
+ const container = deferred.container;
2458
+ const after = nextDomAfter(node);
2459
+ ctx.pass.op(() => {
2460
+ const previousDom = Array.from(container.childNodes);
2461
+ ctx.pass.onReversibleCommit(() => {
2462
+ try {
2463
+ restoreNodeChildren(container, previousDom);
2464
+ } finally {
2465
+ node.children = previous;
2466
+ node.deferredHydration = deferred;
2467
+ }
2468
+ });
2469
+ const retained = new Set(next.flatMap((child) => collectDom(child)));
2470
+ try {
2471
+ for (const child of previous) for (const dom of collectDom(child)) if (!retained.has(dom)) dom.parentNode?.removeChild(dom);
2472
+ let before = after;
2473
+ for (let index = next.length - 1; index >= 0; index--) {
2474
+ const doms = collectDom(next[index]);
2475
+ for (let item = doms.length - 1; item >= 0; item--) {
2476
+ const dom = doms[item];
2477
+ if (dom.parentNode !== container || dom.nextSibling !== before) container.insertBefore(dom, before);
2478
+ before = dom;
2479
+ }
2480
+ }
2481
+ } catch (error) {
2482
+ throw new CommitMutationError(error);
2483
+ }
2484
+ node.children = next;
2485
+ node.deferredHydration = null;
2486
+ });
2487
+ ctx.pass.markRendered(node.instance);
2488
+ }
2489
+ function renderInstance(ctx, node, fresh) {
2490
+ const instance = node.instance;
2491
+ const inner = withOwner(ctx, instance);
2492
+ const mark = ctx.pass.mark();
2493
+ try {
2494
+ const output = renderComponent(ctx, node);
2495
+ if (instance.mounted) ctx.pass.onDiscard(() => {
2496
+ if (ctx.pass.commitAborted) instance.computation.deferRetry();
2497
+ else instance.computation.invalidate();
2498
+ });
2499
+ const children = reconcileComponentOutput(inner, node, output, fresh);
2500
+ ctx.pass.markRendered(instance);
2501
+ const revision = ancestorContextRevision(instance);
2502
+ commitSeenAncestorContextRevision(ctx.pass, instance, revision);
2503
+ return children;
2504
+ } catch (caught) {
2505
+ const error = clarifyRenderError(caught);
2506
+ const boundary = instance.boundary;
2507
+ if (!boundary) {
2508
+ noteErrorOrigin(instance.parent, error);
2509
+ throw error;
2510
+ }
2511
+ for (const failure of ctx.pass.rewind(mark)) reportUncaughtErrorLater(failure);
2512
+ if (!boundary(error)) throw error;
2513
+ const children = reconcileChildren(inner, node, componentOutput(renderComponent(ctx, node)), fresh);
2514
+ ctx.pass.markRendered(instance);
2515
+ const revision = ancestorContextRevision(instance);
2516
+ commitSeenAncestorContextRevision(ctx.pass, instance, revision);
2517
+ return children;
2518
+ }
2519
+ }
2520
+ /**
2521
+ * A component that returns the same component type directly can form a very
2522
+ * deep transparent chain. Walk the hook-free part of that chain explicitly;
2523
+ * all other output still goes through the ordinary child reconciler.
2524
+ */
2525
+ function reconcileComponentOutput(ctx, node, value, fresh) {
2526
+ let output = componentOutput(value);
2527
+ let current = node;
2528
+ let inner = ctx;
2529
+ let creating = fresh;
2530
+ const rendered = [];
2531
+ for (;;) {
2532
+ const next = current.instance.hooks.length === 0 && !current.instance.boundary ? selfChild(output, current.instance.fn) : null;
2533
+ if (!next) {
2534
+ const children = reconcileChildren(inner, current, output, creating);
2535
+ if (creating) current.children = children;
2536
+ break;
2537
+ }
2538
+ let child;
2539
+ if (creating) {
2540
+ const instance = new ComponentInstance(current.instance, next.fn, next.props);
2541
+ ctx.pass.own(instance);
2542
+ child = {
2543
+ kind: 2,
2544
+ parent: current,
2545
+ key: next.key,
2546
+ instance,
2547
+ children: []
2548
+ };
2549
+ instance.view = child;
2550
+ current.children = [child];
2551
+ } else {
2552
+ const previous = current.children;
2553
+ if (previous.length !== 1 || previous[0].kind !== 2 || previous[0].key !== next.key || previous[0].instance.fn !== next.fn) {
2554
+ reconcileChildren(inner, current, output, false);
2555
+ break;
2556
+ }
2557
+ child = previous[0];
2558
+ const instance = child.instance;
2559
+ if (!propsChanged(instance.props, next.props) && !instance.computation.stale && !instance.computation._hasError && instance.seenAncestorContextRevision === ancestorContextRevision(instance)) break;
2560
+ instance.setProps(next.props);
2561
+ }
2562
+ const instance = child.instance;
2563
+ rendered.push(child);
2564
+ inner = withOwner(inner, instance);
2565
+ current = child;
2566
+ output = componentOutput(renderComponent(inner, child));
2567
+ if (instance.mounted) ctx.pass.onDiscard(() => {
2568
+ if (ctx.pass.commitAborted) instance.computation.deferRetry();
2569
+ else instance.computation.invalidate();
2570
+ });
2571
+ creating = fresh;
2572
+ }
2573
+ for (let i = rendered.length - 1; i >= 0; i--) {
2574
+ const instance = rendered[i].instance;
2575
+ ctx.pass.markRendered(instance);
2576
+ const revision = ancestorContextRevision(instance);
2577
+ commitSeenAncestorContextRevision(ctx.pass, instance, revision);
2578
+ }
2579
+ return node.children;
2580
+ }
2581
+ function restoreNodeChildren(container, previous) {
2582
+ for (let index = 0; index < previous.length; index += 1) {
2583
+ const current = container.childNodes[index] ?? null;
2584
+ if (current !== previous[index]) container.insertBefore(previous[index], current);
2585
+ }
2586
+ while (container.childNodes.length > previous.length) container.removeChild(container.lastChild);
2587
+ }
2588
+ function selfChild(output, fn) {
2589
+ if (!output || typeof output !== "object" || Array.isArray(output) || output.type !== fn) return null;
2590
+ const children = normalizeChildren(output);
2591
+ return children.length === 1 && children[0].kind === 2 ? children[0] : null;
2592
+ }
2593
+ /** A function returned by a component is a value, not a child slot. */
2594
+ function componentOutput(value) {
2595
+ return typeof value === "function" ? null : value;
2596
+ }
2597
+ function createDynamic(ctx, parent, fn) {
2598
+ const node = {
2599
+ kind: DYNAMIC,
2600
+ parent,
2601
+ key: void 0,
2602
+ fn,
2603
+ children: [],
2604
+ depth: (nearestInstance(ctx.owner)?.depth ?? 0) + 1
2605
+ };
2606
+ node.instance = new ComponentInstance(ctx.owner, () => functionChildOutput(readValue(node.fn)), {}, () => scheduleDynamicUpdate(node));
2607
+ node.computation = node.instance.computation;
2608
+ ctx.pass.own(node.instance);
2609
+ node.children = reconcileChildren(withOwner(ctx, node.instance), node, readDynamic(node), true, false);
2610
+ ctx.pass.markRendered(node.instance);
2611
+ return node;
2612
+ }
2613
+ /** Run a function child's read now, tracking what it reads. */
2614
+ function readDynamic(node, pass) {
2615
+ const output = node.instance.render(pass ? (undo) => pass.onDiscard(undo) : void 0);
2616
+ if (pass && node.instance.mounted) pass.onDiscard(() => {
2617
+ if (pass.commitAborted) node.computation.deferRetry();
2618
+ else node.computation.invalidate();
2619
+ });
2620
+ return componentOutput(output);
2621
+ }
2622
+ function patchDynamic(ctx, node, fn) {
2623
+ const previous = node.fn;
2624
+ node.fn = fn;
2625
+ ctx.pass.onDiscard(() => {
2626
+ node.fn = previous;
2627
+ });
2628
+ updateDynamic(ctx, node);
2629
+ }
2630
+ /** Reconcile a function child, remounting only when its own hooks change. */
2631
+ function updateDynamic(ctx, node) {
2632
+ const mark = ctx.pass.mark();
2633
+ try {
2634
+ reconcileChildren(withOwner(ctx, node.instance), node, readDynamic(node, ctx.pass), false, false);
2635
+ ctx.pass.markRendered(node.instance);
2636
+ } catch (error) {
2637
+ if (!(error instanceof HookOrderChangeError) || error.instance !== node.instance) throw error;
2638
+ for (const failure of ctx.pass.rewind(mark)) reportUncaughtErrorLater(failure);
2639
+ const slot = ctx.pass.reserve();
2640
+ const replacement = createDynamic(withOwner(ctx, node.instance.parent), node.parent, node.fn);
2641
+ ctx.pass.fill(slot, () => {
2642
+ const parent = node.parent;
2643
+ const container = containerOf(parent);
2644
+ const next = nextDomAfter(node);
2645
+ const oldDom = collectDom(node);
2646
+ const replacementDom = collectDom(replacement);
2647
+ const index = parent.children.indexOf(node);
2648
+ ctx.pass.onReversibleCommit(() => {
2649
+ for (const dom of replacementDom) dom.parentNode?.removeChild(dom);
2650
+ parent.children[index] = node;
2651
+ }, () => {
2652
+ const errors = [];
2653
+ for (const dom of oldDom) dom.parentNode?.removeChild(dom);
2654
+ release(node, errors);
2655
+ reportTeardown(errors);
2656
+ });
2657
+ parent.children[index] = replacement;
2658
+ try {
2659
+ for (const dom of replacementDom) container.insertBefore(dom, next);
2660
+ } catch (error) {
2661
+ throw new CommitMutationError(error);
2662
+ }
2663
+ });
2664
+ }
2665
+ }
2666
+ function release(node, errors) {
2667
+ const pending = [{
2668
+ node,
2669
+ finish: false
2670
+ }];
2671
+ while (pending.length) {
2672
+ const frame = pending.pop();
2673
+ const current = frame.node;
2674
+ if (frame.finish) {
2675
+ if (current.kind === HOST) {
2676
+ if (current.dormant) dormantHosts.delete(current.el);
2677
+ releaseProps(current, errors);
2678
+ }
2679
+ if (current.kind === 2 || current.kind === DYNAMIC) current.instance.dispose(errors);
2680
+ continue;
2681
+ }
2682
+ if (current.kind === 1 || current.kind === 7) continue;
2683
+ pending.push({
2684
+ node: current,
2685
+ finish: true
2686
+ });
2687
+ for (let i = current.children.length - 1; i >= 0; i--) pending.push({
2688
+ node: current.children[i],
2689
+ finish: false
2690
+ });
2691
+ }
2692
+ }
2693
+ /**
2694
+ * A select's controlled value depends on its options. When a pass changes
2695
+ * them without patching the select (a child component, `For`, or function
2696
+ * child adds, removes, or edits options), re-apply the select's value after
2697
+ * the pass commits, once per select. Each change registers its own sync so
2698
+ * an error boundary that rewinds part of the pass drops only its own.
2699
+ */
2700
+ const scheduledSelectSyncs = /* @__PURE__ */ new WeakMap();
2701
+ const writtenSelectValues = /* @__PURE__ */ new WeakMap();
2702
+ function syncEnclosingSelect(pass, parent) {
2703
+ const select = enclosingSelect(parent);
2704
+ if (!select || select.dormant || !("value" in select.props)) return;
2705
+ let scheduled = scheduledSelectSyncs.get(pass);
2706
+ if (!scheduled) scheduledSelectSyncs.set(pass, scheduled = /* @__PURE__ */ new Set());
2707
+ if (scheduled.has(select)) return;
2708
+ scheduled.add(select);
2709
+ pass.onDiscard(() => scheduled.delete(select));
2710
+ recordSelectUndo(pass, select);
2711
+ pass.beforeJournalSettle(() => {
2712
+ if (writtenSelectValues.get(pass)?.has(select)) return;
2713
+ try {
2714
+ resyncSelect(select);
2715
+ } catch (error) {
2716
+ throw new CommitMutationError(error);
2717
+ }
2718
+ });
2719
+ }
2720
+ /** The select's own patch applied its value after its options. */
2721
+ function markSelectSynced(pass, select) {
2722
+ let written = writtenSelectValues.get(pass);
2723
+ if (!written) writtenSelectValues.set(pass, written = /* @__PURE__ */ new Set());
2724
+ written.add(select);
2725
+ }
2726
+ const domNodes = {
2727
+ listChanged(ctx, parent) {
2728
+ syncEnclosingSelect(ctx.pass, parent);
2729
+ },
2730
+ create(ctx, parent, child) {
2731
+ switch (child.kind) {
2732
+ case 1: {
2733
+ const hydrate = ctx.hydrate;
2734
+ const claimed = hydrate?.cursor.claimText(hydrate.container, child.text);
2735
+ if (claimed && claimed.data !== child.text) {
2736
+ const text = child.text;
2737
+ ctx.pass.op(() => {
2738
+ const previous = claimed.data;
2739
+ ctx.pass.onReversibleCommit(() => {
2740
+ claimed.data = previous;
2741
+ });
2742
+ try {
2743
+ claimed.data = text;
2744
+ } catch (error) {
2745
+ throw new CommitMutationError(error);
2746
+ }
2747
+ });
2748
+ }
2749
+ return {
2750
+ kind: 1,
2751
+ parent,
2752
+ key: void 0,
2753
+ node: claimed ?? document.createTextNode(child.text),
2754
+ text: child.text
2755
+ };
2756
+ }
2757
+ case 0: return createHost(ctx, parent, child.key, child.tag, child.props);
2758
+ case 2: return createComponent(ctx, parent, child.key, child.fn, child.props);
2759
+ case 3: {
2760
+ const node = {
2761
+ kind: 3,
2762
+ parent,
2763
+ key: child.key,
2764
+ children: []
2765
+ };
2766
+ node.children = reconcileChildren(withOwner(ctx, child.owner ?? ctx.owner), node, child.children, true);
2767
+ return node;
2768
+ }
2769
+ case 4: return createDynamic(ctx, parent, child.fn);
2770
+ case 7: return {
2771
+ kind: 7,
2772
+ parent,
2773
+ key: void 0,
2774
+ node: child.node
2775
+ };
2776
+ }
2777
+ },
2778
+ patch(ctx, node, child) {
2779
+ switch (node.kind) {
2780
+ case 1: {
2781
+ const text = child.text;
2782
+ if (node.text !== text) {
2783
+ ctx.pass.op(() => {
2784
+ const previous = node.text;
2785
+ const previousData = node.node.data;
2786
+ ctx.pass.onReversibleCommit(() => {
2787
+ try {
2788
+ node.node.data = previousData;
2789
+ } finally {
2790
+ node.text = previous;
2791
+ }
2792
+ });
2793
+ try {
2794
+ node.node.data = text;
2795
+ node.text = text;
2796
+ } catch (error) {
2797
+ throw new CommitMutationError(error);
2798
+ }
2799
+ });
2800
+ syncEnclosingSelect(ctx.pass, node.parent);
2801
+ }
2802
+ return;
2803
+ }
2804
+ case HOST:
2805
+ patchHost(ctx, node, child.props);
2806
+ return;
2807
+ case 2:
2808
+ patchComponent(ctx, node, child.props);
2809
+ return;
2810
+ case DYNAMIC:
2811
+ patchDynamic(ctx, node, child.fn);
2812
+ return;
2813
+ case 7: return;
2814
+ case 3: {
2815
+ const owner = child.owner;
2816
+ reconcileChildren(withOwner(ctx, owner ?? ctx.owner), node, child.children, false);
2817
+ return;
2818
+ }
2819
+ }
2820
+ },
2821
+ release
2822
+ };
2823
+ //#endregion
2824
+ //#region src/core/dom/updates.ts
2825
+ /**
2826
+ * Standalone updates: a component or function child whose own reads changed
2827
+ * re-renders in its own pass, independent of its parent.
2828
+ *
2829
+ * A failed update discards its pass and is delivered to the nearest error
2830
+ * boundary above it; without one, the error propagates to the scheduler,
2831
+ * which reports it after the flush.
2832
+ */
2833
+ function runPass(owner, render) {
2834
+ const pass = new Pass();
2835
+ try {
2836
+ pass.run(() => render(pass));
2837
+ } catch (error) {
2838
+ for (const failure of pass.discard()) reportUncaughtErrorLater(failure);
2839
+ routeError(owner, clarifyRenderError(error));
2840
+ return;
2841
+ }
2842
+ try {
2843
+ pass.commit();
2844
+ } catch (error) {
2845
+ if (pass.commitAborted) routeError(owner, error);
2846
+ else throw error;
2847
+ }
2848
+ }
2849
+ function rerenderInstance(instance) {
2850
+ const node = instance.view;
2851
+ if (!node || instance.disposed || !instance.mounted) return;
2852
+ if (!instance.computation.stale) return;
2853
+ runPass(instance.parent, (pass) => {
2854
+ const before = instance.renderCount;
2855
+ const computation = instance.computation;
2856
+ const sources = new Set(computation._sources ?? []);
2857
+ pass.onDiscard(() => computation.restoreSources(sources));
2858
+ try {
2859
+ computation.update();
2860
+ if (computation._hasError) throw computation._error;
2861
+ } catch (error) {
2862
+ if (!instance.boundary) throw error;
2863
+ }
2864
+ if (instance.renderCount === before && !computation._hasError) return;
2865
+ renderAfterUpdate(createRenderContext(pass, instance.parent, namespaceAt(node.parent)), node);
2866
+ });
2867
+ }
2868
+ /**
2869
+ * The instance already ran with fresh reads; reconcile that output (or, for
2870
+ * a boundary whose render failed, render its fallback).
2871
+ */
2872
+ function renderAfterUpdate(ctx, node) {
2873
+ const instance = node.instance;
2874
+ const computation = instance.computation;
2875
+ if (computation._hasError) {
2876
+ renderInstance(ctx, node, false);
2877
+ return;
2878
+ }
2879
+ const mark = ctx.pass.mark();
2880
+ try {
2881
+ if (node.deferredHydration) {
2882
+ hydrateDeferredComponent(ctx, node, componentOutput(computation._value));
2883
+ return;
2884
+ }
2885
+ reconcileChildren(withOwner(ctx, instance), node, componentOutput(computation._value), false);
2886
+ ctx.pass.markRendered(instance);
2887
+ } catch (caught) {
2888
+ const error = clarifyRenderError(caught);
2889
+ if (!instance.boundary) throw error;
2890
+ ctx.pass.rewind(mark);
2891
+ if (!instance.boundary(error)) throw error;
2892
+ renderInstance(ctx, node, false);
2893
+ }
2894
+ }
2895
+ const dynamicJobs = /* @__PURE__ */ new WeakMap();
2896
+ function scheduleDynamic(node) {
2897
+ let job = dynamicJobs.get(node);
2898
+ if (!job) {
2899
+ job = {
2900
+ depth: node.depth,
2901
+ run: () => rerenderDynamic(node),
2902
+ cancel: () => node.computation.dropScheduled(),
2903
+ get skip() {
2904
+ return node.computation.disposed || !node.computation.stale;
2905
+ }
2906
+ };
2907
+ dynamicJobs.set(node, job);
2908
+ }
2909
+ schedule(job, "render");
2910
+ }
2911
+ function rerenderDynamic(node) {
2912
+ const computation = node.computation;
2913
+ if (computation.disposed || !computation.stale) return;
2914
+ runPass(node.instance.parent, (pass) => {
2915
+ updateDynamic(createRenderContext(pass, node.instance, namespaceAt(node.parent)), node);
2916
+ });
2917
+ }
2918
+ /** Bind standalone updates when a DOM root is created. */
2919
+ function installRenderUpdates() {
2920
+ setRenderHost({
2921
+ rerender: rerenderInstance,
2922
+ isHydrating: isHydratingRender,
2923
+ deferHydration: deferHydratingRender
2924
+ });
2925
+ setDynamicUpdateScheduler(scheduleDynamic);
2926
+ }
2927
+ //#endregion
2928
+ //#region src/core/dom/root.ts
2929
+ /**
2930
+ * A root renders a value into a container element and owns everything it
2931
+ * renders.
2932
+ *
2933
+ * `prepare()` runs the render phase and returns work that is either
2934
+ * committed or discarded as a unit, so a caller updating several roots (a
2935
+ * navigation) can commit all of them or none. `render()` prepares and
2936
+ * commits in one step.
2937
+ */
2938
+ function createRoot(container, options = {}) {
2939
+ installRenderUpdates();
2940
+ const owner = new Owner(options.owner === void 0 ? getOwner() : options.owner);
2941
+ (owner.context ??= /* @__PURE__ */ new Map()).set(EVENT_ROOT_CONTAINER, container);
2942
+ const node = {
2943
+ kind: 5,
2944
+ el: container,
2945
+ children: [],
2946
+ parent: null,
2947
+ key: void 0,
2948
+ tail: options.before ?? null
2949
+ };
2950
+ let mounted = false;
2951
+ let hydrate = options.hydrate === true;
2952
+ let releaseEvents = null;
2953
+ function prepare(value) {
2954
+ const pass = new Pass();
2955
+ const fresh = !mounted;
2956
+ const tail = node.tail?.parentNode === container ? node.tail : null;
2957
+ const cursor = fresh && hydrate ? new HydrationCursor(tail) : null;
2958
+ const ctx = createRenderContext(pass, owner, namespaceAt(node), cursor ? {
2959
+ cursor,
2960
+ container
2961
+ } : null);
2962
+ try {
2963
+ pass.run(() => runWithOwner(owner, () => {
2964
+ if (!fresh) {
2965
+ reconcileChildren(ctx, node, value, false);
2966
+ return;
2967
+ }
2968
+ const children = reconcileChildren(ctx, node, value, true);
2969
+ for (const deferred of cursor?.deferred ?? []) deferred.render();
2970
+ const adopting = ctx.hydrate !== null;
2971
+ pass.op(() => {
2972
+ const before = node.tail?.parentNode === container ? node.tail : null;
2973
+ const dom = children.flatMap((child) => collectDom(child));
2974
+ if (adopting) {
2975
+ const previous = node.children;
2976
+ pass.onReversibleCommit(() => {
2977
+ node.children = previous;
2978
+ });
2979
+ syncChildren(pass, container, dom, before);
2980
+ node.children = children;
2981
+ return;
2982
+ }
2983
+ const previousChildren = node.children;
2984
+ if (!before) {
2985
+ const replaced = Array.from(container.childNodes);
2986
+ pass.onReversibleCommit(() => {
2987
+ try {
2988
+ restoreRootChildren(container, replaced);
2989
+ } finally {
2990
+ node.children = previousChildren;
2991
+ }
2992
+ });
2993
+ try {
2994
+ container.replaceChildren(...dom);
2995
+ } catch (error) {
2996
+ if (error instanceof CommitMutationError) throw error;
2997
+ throw new CommitMutationError(error);
2998
+ }
2999
+ node.children = children;
3000
+ return;
3001
+ }
3002
+ const previous = Array.from(container.childNodes);
3003
+ pass.onReversibleCommit(() => {
3004
+ try {
3005
+ restoreRootChildren(container, previous);
3006
+ } finally {
3007
+ node.children = previousChildren;
3008
+ }
3009
+ });
3010
+ try {
3011
+ for (const item of dom) container.insertBefore(item, before);
3012
+ } catch (error) {
3013
+ if (error instanceof CommitMutationError) throw error;
3014
+ throw new CommitMutationError(error);
3015
+ }
3016
+ node.children = children;
3017
+ });
3018
+ }));
3019
+ } catch (error) {
3020
+ for (const failure of pass.discard()) reportUncaughtErrorLater(failure);
3021
+ throw clarifyRenderOverflow(error);
3022
+ }
3023
+ let settled = false;
3024
+ let phase = "prepared";
3025
+ let registeredEventsForPass = false;
3026
+ const apply = () => {
3027
+ if (settled || phase !== "prepared") return;
3028
+ try {
3029
+ if (!releaseEvents) {
3030
+ releaseEvents = registerEventRoot(container);
3031
+ registeredEventsForPass = true;
3032
+ }
3033
+ pass.apply();
3034
+ phase = "applied";
3035
+ } catch (error) {
3036
+ phase = "discarded";
3037
+ settled = true;
3038
+ for (const failure of pass.discard()) reportUncaughtErrorLater(failure);
3039
+ if (registeredEventsForPass) {
3040
+ try {
3041
+ releaseEvents?.();
3042
+ } catch (cleanupError) {
3043
+ reportUncaughtErrorLater(cleanupError);
3044
+ }
3045
+ releaseEvents = null;
3046
+ registeredEventsForPass = false;
3047
+ }
3048
+ throw error;
3049
+ }
3050
+ };
3051
+ const publish = () => {
3052
+ if (settled) return;
3053
+ if (phase === "prepared") apply();
3054
+ if (phase !== "applied") throw new Error("[Askr] Cannot publish a render that was not applied.");
3055
+ settled = true;
3056
+ phase = "published";
3057
+ try {
3058
+ pass.publish();
3059
+ } finally {
3060
+ mounted = true;
3061
+ hydrate = false;
3062
+ }
3063
+ };
3064
+ const rollback = () => {
3065
+ if (settled || phase === "discarded") return [];
3066
+ settled = true;
3067
+ phase = "discarded";
3068
+ const errors = pass.rollback();
3069
+ if (registeredEventsForPass) {
3070
+ try {
3071
+ releaseEvents?.();
3072
+ } catch (error) {
3073
+ errors.push(error);
3074
+ }
3075
+ releaseEvents = null;
3076
+ registeredEventsForPass = false;
3077
+ }
3078
+ return errors;
3079
+ };
3080
+ const discard = () => {
3081
+ if (settled || phase === "discarded") return [];
3082
+ if (phase === "applied") return rollback();
3083
+ settled = true;
3084
+ phase = "discarded";
3085
+ return pass.discard();
3086
+ };
3087
+ return {
3088
+ apply,
3089
+ publish,
3090
+ commit() {
3091
+ apply();
3092
+ publish();
3093
+ },
3094
+ rollback,
3095
+ discard,
3096
+ get aborted() {
3097
+ return pass.commitAborted;
3098
+ }
3099
+ };
3100
+ }
3101
+ return {
3102
+ node,
3103
+ owner,
3104
+ prepare,
3105
+ render(value) {
3106
+ prepare(value).commit();
3107
+ },
3108
+ dispose() {
3109
+ const errors = [];
3110
+ for (const child of node.children) {
3111
+ for (const dom of collectDom(child)) dom.parentNode?.removeChild(dom);
3112
+ domNodes.release(child, errors);
3113
+ }
3114
+ node.children = [];
3115
+ mounted = false;
3116
+ owner.dispose(errors);
3117
+ releaseEvents?.();
3118
+ releaseEvents = null;
3119
+ return errors;
3120
+ }
3121
+ };
3122
+ }
3123
+ function restoreRootChildren(container, previous) {
3124
+ for (let index = 0; index < previous.length; index += 1) {
3125
+ const current = container.childNodes[index] ?? null;
3126
+ if (current !== previous[index]) container.insertBefore(previous[index], current);
3127
+ }
3128
+ while (container.childNodes.length > previous.length) container.removeChild(container.lastChild);
3129
+ }
3130
+ //#endregion
3131
+ //#region src/boot/root-element.ts
3132
+ function resolveRootElement(root) {
3133
+ if (typeof root !== "string") return root;
3134
+ const id = root.startsWith("#") ? root.slice(1) : root;
3135
+ return document.getElementById(id);
3136
+ }
3137
+ //#endregion
3138
+ //#region src/boot/root-lifecycle.ts
3139
+ /**
3140
+ * Application roots: one per mounted container element.
3141
+ *
3142
+ * An application root owns a core render root, its application runtime (route
3143
+ * data, data runtime), its default portal, and its cleanup callbacks. The
3144
+ * rendered tree is `RootView`, which provides the application runtime and
3145
+ * renders the current route handler followed by the automatic default portal
3146
+ * host. The root view keeps shared route shells in place across navigation.
3147
+ */
3148
+ var AppRoot = class {
3149
+ constructor(element, component, hydrate) {
3150
+ this.cleanupStrict = false;
3151
+ this.generation = 0;
3152
+ this.routed = false;
3153
+ this.callbacks = /* @__PURE__ */ new Set();
3154
+ this.element = element;
3155
+ this.root = createRoot(element, {
3156
+ owner: null,
3157
+ hydrate
3158
+ });
3159
+ provideDefaultPortal(this.root.owner, element);
3160
+ this.component = component;
3161
+ this.handler = component;
3162
+ }
3163
+ view() {
3164
+ return {
3165
+ $$typeof: ELEMENT_TYPE,
3166
+ type: RootView,
3167
+ props: {
3168
+ handler: this.handler,
3169
+ runtime: this.appRuntime
3170
+ },
3171
+ key: this.routed ? 0 : this.generation
3172
+ };
3173
+ }
3174
+ };
3175
+ function RootView(props, context) {
3176
+ provideAppRuntime(props.runtime);
3177
+ const instance = currentComponent();
3178
+ if (instance) instance.name = props.handler.name || null;
3179
+ return props.handler({}, context);
3180
+ }
3181
+ const appRoots = /* @__PURE__ */ new WeakMap();
3182
+ let routedRootCount = 0;
3183
+ function registerRootCleanupCallback(rootElement, callback) {
3184
+ const app = appRoots.get(rootElement);
3185
+ if (!app) return () => {};
3186
+ app.callbacks.add(callback);
3187
+ return () => app.callbacks.delete(callback);
3188
+ }
3189
+ function disposeAppRoot(app) {
3190
+ if (appRoots.get(app.element) !== app) return;
3191
+ appRoots.delete(app.element);
3192
+ restoreInnerHTML(app.element);
3193
+ const errors = app.root.dispose();
3194
+ for (const callback of Array.from(app.callbacks)) try {
3195
+ callback();
3196
+ } catch (error) {
3197
+ errors.push(error);
3198
+ }
3199
+ app.callbacks.clear();
3200
+ unregisterAppInstance(app);
3201
+ if (app.routed) {
3202
+ app.routed = false;
3203
+ routedRootCount--;
3204
+ if (routedRootCount === 0) clearRouteState();
3205
+ }
3206
+ if (errors.length === 0) return;
3207
+ if (app.cleanupStrict) throw new AggregateError(errors, "cleanup failed for app root");
3208
+ reportUncaughtErrorLater(errors.length === 1 ? errors[0] : new AggregateError(errors, "cleanup failed for app root"));
3209
+ }
3210
+ /**
3211
+ * Clearing a root's markup with `innerHTML = ''` tears the app down first,
3212
+ * so its lifetimes end with the DOM they own.
3213
+ */
3214
+ function interceptInnerHTML(app) {
3215
+ const element = app.element;
3216
+ const descriptor = Object.getOwnPropertyDescriptor(Element.prototype, "innerHTML") ?? Object.getOwnPropertyDescriptor(Object.getPrototypeOf(element), "innerHTML");
3217
+ if (!descriptor?.set || !descriptor.get) return;
3218
+ try {
3219
+ Object.defineProperty(element, "innerHTML", {
3220
+ configurable: true,
3221
+ get() {
3222
+ return descriptor.get.call(this);
3223
+ },
3224
+ set(value) {
3225
+ if (value === "" && appRoots.get(this) === app) {
3226
+ disposeAppRoot(app);
3227
+ if (appRoots.has(this)) return;
3228
+ }
3229
+ descriptor.set.call(this, value);
3230
+ }
3231
+ });
3232
+ } catch {}
3233
+ }
3234
+ function restoreInnerHTML(element) {
3235
+ if (Object.prototype.hasOwnProperty.call(element, "innerHTML")) Reflect.deleteProperty(element, "innerHTML");
3236
+ }
3237
+ /** Mount `component` at `rootElement`, or re-render the app already there. */
3238
+ function mountOrUpdate(rootElement, component, options) {
3239
+ ensureBrowserRuntime();
3240
+ const nonce = validateCspNonce(options?.cspNonce);
3241
+ let app = appRoots.get(rootElement);
3242
+ const replacingIsland = app !== void 0 && app.component !== component && !app.routed;
3243
+ if (!app) {
3244
+ app = new AppRoot(rootElement, component, options?.hydrate === true);
3245
+ appRoots.set(rootElement, app);
3246
+ interceptInnerHTML(app);
3247
+ } else if (app.component !== component) app.generation++;
3248
+ app.component = component;
3249
+ app.handler = wrapRootRouteHandler(component, nonce);
3250
+ app.cspNonce = nonce;
3251
+ app.appRuntime = options?.appRuntime;
3252
+ if (typeof options?.cleanupStrict === "boolean") app.cleanupStrict = options.cleanupStrict;
3253
+ app.root.render(app.view());
3254
+ options?.onCommit?.();
3255
+ flushSync();
3256
+ if (replacingIsland) {
3257
+ const callbacks = Array.from(app.callbacks);
3258
+ app.callbacks.clear();
3259
+ const errors = [];
3260
+ for (const callback of callbacks) try {
3261
+ callback();
3262
+ } catch (error) {
3263
+ errors.push(error);
3264
+ }
3265
+ if (errors.length > 0) {
3266
+ if (app.cleanupStrict) throw new AggregateError(errors, "cleanup failed for app root");
3267
+ reportUncaughtErrorLater(errors.length === 1 ? errors[0] : new AggregateError(errors, "cleanup failed for app root"));
3268
+ }
3269
+ }
3270
+ return app;
3271
+ }
3272
+ async function registerAppNavigation(rootElement, path, source) {
3273
+ ensureBrowserRuntime();
3274
+ const app = appRoots.get(rootElement);
3275
+ if (!app) throw new Error("Internal error: app instance missing");
3276
+ if (!app.routed) {
3277
+ app.routed = true;
3278
+ routedRootCount++;
3279
+ }
3280
+ registerAppInstance(app, path, source);
3281
+ initializeNavigation();
3282
+ }
3283
+ function activateHydrationBoundary(rootElement, boundary) {
3284
+ const app = appRoots.get(rootElement);
3285
+ if (!app || !rootElement.contains(boundary)) return false;
3286
+ const host = dormantHostFor(boundary);
3287
+ if (!host) return false;
3288
+ let parent = host.parent;
3289
+ while (parent && parent.kind !== 5) parent = parent.parent;
3290
+ if (parent !== app.root.node) return false;
3291
+ const pass = new Pass();
3292
+ try {
3293
+ pass.run(() => hydrateDormantHost(pass, host));
3294
+ } catch (error) {
3295
+ for (const failure of pass.discard()) reportUncaughtErrorLater(failure);
3296
+ throw clarifyRenderOverflow(error);
3297
+ }
3298
+ pass.commit();
3299
+ flushSync();
3300
+ return true;
3301
+ }
3302
+ /**
3303
+ * Tear down the app mounted at `root`: its content, lifetimes, and cleanup
3304
+ * callbacks, and its route registration.
3305
+ */
3306
+ function cleanupApp(root) {
3307
+ const rootElement = resolveRootElement(root);
3308
+ if (!rootElement) return;
3309
+ const app = appRoots.get(rootElement);
3310
+ if (app) disposeAppRoot(app);
3311
+ }
3312
+ /** Check whether an app is currently mounted at `root`. */
3313
+ function hasApp(root) {
3314
+ const rootElement = resolveRootElement(root);
3315
+ return rootElement ? appRoots.has(rootElement) : false;
3316
+ }
3317
+ //#endregion
3318
+ //#region src/boot/islands.ts
3319
+ /**
3320
+ * createIsland: Enhances existing DOM (no router, mounts once)
3321
+ */
3322
+ function createIsland(config) {
3323
+ assertExecutionModel("islands");
3324
+ if (!config || typeof config !== "object") throw new Error("createIsland requires a config object");
3325
+ if (typeof config.component !== "function") throw new Error("createIsland: component must be a function");
3326
+ validateCspNonce(config.cspNonce);
3327
+ const rootElement = resolveRootElement(config.root);
3328
+ if (!rootElement) throw new Error(`Root element not found: ${config.root}`);
3329
+ if ("routes" in config) throw new Error("createIsland does not accept routes; use createSPA for routed apps");
3330
+ if (hasRegisteredRoutes()) throw new Error("Routes are not supported with islands. Use createSPA or hydrateSPA with a route registry instead.");
3331
+ mountOrUpdate(rootElement, config.component, {
3332
+ cleanupStrict: config.cleanupStrict,
3333
+ cspNonce: config.cspNonce,
3334
+ appRuntime: createAppRenderRuntime({ dataRuntime: config.dataRuntime ?? getDefaultDataRuntime() })
3335
+ });
3336
+ }
3337
+ /**
3338
+ * createIslands: Enhances one or more existing DOM roots (no router).
3339
+ * The only public islands constructor.
3340
+ */
3341
+ function createIslands(config) {
3342
+ assertExecutionModel("islands");
3343
+ if (!config || typeof config !== "object") throw new Error("createIslands requires a config object");
3344
+ validateCspNonce(config.cspNonce);
3345
+ if (!Array.isArray(config.islands) || config.islands.length === 0) throw new Error("createIslands requires a non-empty islands array");
3346
+ for (const island of config.islands) createIsland({
3347
+ ...island,
3348
+ cspNonce: island.cspNonce ?? config.cspNonce
3349
+ });
3350
+ }
3351
+ //#endregion
3352
+ //#region src/boot/route-startup.ts
3353
+ const MAX_INITIAL_ROUTE_REDIRECTS = 20;
3354
+ function bindResolvedRouteHandler(resolved) {
3355
+ return () => resolved.handler(resolved.params);
3356
+ }
3357
+ function createDeniedStatusNode(status) {
3358
+ return {
3359
+ type: "div",
3360
+ props: { "data-route-denied": String(status) },
3361
+ children: [String(status)]
3362
+ };
3363
+ }
3364
+ function bindDeniedStatus(status) {
3365
+ return () => createDeniedStatusNode(status);
3366
+ }
3367
+ function bindDeniedRouteHandler(status) {
3368
+ return () => createDeniedStatusNode(status);
3369
+ }
3370
+ async function reconcileInitialRouteMetadata(resolved) {
3371
+ if (resolved?.kind !== "render" || !resolved.record) return;
3372
+ if (!resolved.record.options.title && !resolved.record.options.meta && !resolved.record.metaChain?.length) return;
3373
+ const context = getRouteRenderContext(resolved);
3374
+ if (!context) return;
3375
+ reconcileRouteMeta(await resolveRouteMeta(resolved.record, context));
3376
+ }
3377
+ /**
3378
+ * Resolve the route for the current location, following redirects (and
3379
+ * replacing the history entry for each) until a render, deny, or no-match
3380
+ * result is reached. Returns `null` when a redirect leaves the origin: the
3381
+ * browser is loading that document, so there is nothing to mount.
3382
+ */
3383
+ async function resolveInitialRoute(auth, source) {
3384
+ let path = typeof window !== "undefined" ? window.location.pathname : "/";
3385
+ let href = typeof window !== "undefined" ? `${window.location.pathname}${window.location.search}${window.location.hash}` : path;
3386
+ const visited = /* @__PURE__ */ new Set();
3387
+ for (let redirects = 0; redirects <= MAX_INITIAL_ROUTE_REDIRECTS; redirects++) {
3388
+ if (visited.has(href)) throw new Error(`[Askr] Route redirect cycle detected at ${href}.`);
3389
+ visited.add(href);
3390
+ if (!source?.registry) throw new TypeError("resolveInitialRoute requires a route registry.");
3391
+ const resolved = await resolveRouteRequest(href, {
3392
+ registry: source.registry,
3393
+ auth,
3394
+ load: source.load,
3395
+ authContext: source.authContext,
3396
+ dataRuntime: source.dataRuntime
3397
+ });
3398
+ if (!resolved || resolved.kind !== "redirect") return {
3399
+ path,
3400
+ href,
3401
+ resolved
3402
+ };
3403
+ const base = typeof window !== "undefined" ? window.location.href : new URL(href, "http://localhost").href;
3404
+ const redirectTarget = resolveNavigationUrl(resolved.to, base);
3405
+ if (redirectTarget.origin !== new URL(base).origin) {
3406
+ if (typeof window !== "undefined") loadDocument(redirectTarget.href, "replace");
3407
+ return null;
3408
+ }
3409
+ const redirectHref = `${redirectTarget.pathname}${redirectTarget.search}${redirectTarget.hash}`;
3410
+ if (typeof window !== "undefined") window.history.replaceState({
3411
+ path: redirectHref,
3412
+ askrIndex: readHistoryIndex(window.history.state)
3413
+ }, "", redirectTarget.href);
3414
+ path = redirectTarget.pathname;
3415
+ href = redirectHref;
3416
+ }
3417
+ throw new Error(`[Askr] Route redirect limit exceeded (${MAX_INITIAL_ROUTE_REDIRECTS}).`);
3418
+ }
3419
+ //#endregion
3420
+ //#region src/boot/spa.ts
3421
+ /**
3422
+ * App bootstrap and mount
3423
+ */
3424
+ /**
3425
+ * createSPA: Initializes the router and mounts the app with an explicit route registry.
3426
+ * ```ts
3427
+ * import { createRouteRegistry } from '@askrjs/askr/router';
3428
+ * const registry = createRouteRegistry(() => { ... });
3429
+ * await createSPA({ root: '#app', registry });
3430
+ * ```
3431
+ *
3432
+ */
3433
+ async function createSPA(config) {
3434
+ assertExecutionModel("spa");
3435
+ if (!config || typeof config !== "object") throw new Error("createSPA requires a config object");
3436
+ validateCspNonce(config.cspNonce);
3437
+ if (!config.registry) throw new Error("createSPA requires a route registry.");
3438
+ const { manifest } = config.registry;
3439
+ if (manifest.records.length === 0) throw new Error("createSPA requires a route registry with at least one route. If you are enhancing existing HTML, use createIsland instead.");
3440
+ const rootElement = resolveRootElement(config.root);
3441
+ if (!rootElement) throw new Error(`Root element not found: ${config.root}`);
3442
+ configureScrollRestoration(config.scrollRestoration);
3443
+ clearRouteState();
3444
+ _applyManifest(manifest);
3445
+ const routeAuth = config.auth ?? manifest.auth;
3446
+ const dataRuntime = config.dataRuntime ?? getDefaultDataRuntime();
3447
+ const appRouteSource = {
3448
+ registry: config.registry,
3449
+ auth: routeAuth
3450
+ };
3451
+ _setActiveRouteAuthOptions(routeAuth);
3452
+ if (isProductionEnvironment()) lockRouteRegistration();
3453
+ const initialRoute = await resolveInitialRoute(routeAuth, {
3454
+ registry: config.registry,
3455
+ dataRuntime
3456
+ });
3457
+ if (!initialRoute) return;
3458
+ const { path, href: currentUrl, resolved } = initialRoute;
3459
+ setServerLocation(currentUrl);
3460
+ const appRuntime = createAppRenderRuntime({
3461
+ ...resolved?.kind === "render" && hasRouteRenderData(resolved) ? {
3462
+ route: getRouteRenderData(resolved),
3463
+ hasRoute: true
3464
+ } : {},
3465
+ dataRuntime,
3466
+ routeRegistry: config.registry,
3467
+ routeAuth
3468
+ });
3469
+ if (!resolved) {
3470
+ mountOrUpdate(rootElement, () => ({
3471
+ type: "div",
3472
+ children: []
3473
+ }), {
3474
+ cleanupStrict: config.cleanupStrict,
3475
+ appRuntime,
3476
+ cspNonce: config.cspNonce
3477
+ });
3478
+ await registerAppNavigation(rootElement, path, {
3479
+ ...appRouteSource,
3480
+ runtime: appRuntime
3481
+ });
3482
+ return;
3483
+ }
3484
+ await reconcileInitialRouteMetadata(resolved);
3485
+ mountOrUpdate(rootElement, resolved.kind === "deny" ? bindDeniedStatus(resolved.status) : bindResolvedRouteHandler({
3486
+ handler: resolved.handler,
3487
+ params: resolved.params
3488
+ }), {
3489
+ cleanupStrict: config.cleanupStrict,
3490
+ appRuntime,
3491
+ cspNonce: config.cspNonce
3492
+ });
3493
+ await registerAppNavigation(rootElement, path, {
3494
+ ...appRouteSource,
3495
+ runtime: appRuntime
3496
+ });
3497
+ }
3498
+ //#endregion
3499
+ //#region src/boot/hydration.ts
3500
+ const DEFERRED_PAYLOAD = "__askr_deferred__";
3501
+ const SSR_STYLE_REGISTRY_ATTR = "data-askr-style-registry";
3502
+ function applyDeferredStreamPatches(rootElement) {
3503
+ const patches = Array.from(rootElement.querySelectorAll("template[data-askr-deferred-patch]"));
3504
+ for (const template of patches) {
3505
+ const id = template.getAttribute("data-askr-deferred-patch");
3506
+ if (!id) continue;
3507
+ const boundary = Array.from(rootElement.querySelectorAll("askr-resolve[data-askr-deferred]")).find((candidate) => candidate.getAttribute("data-askr-deferred") === id);
3508
+ if (boundary) boundary.replaceWith(template.content.cloneNode(true));
3509
+ template.remove();
3510
+ for (const script of Array.from(rootElement.querySelectorAll("script[data-askr-deferred-apply]"))) if (script.getAttribute("data-askr-deferred-apply") === id) script.remove();
3511
+ }
3512
+ }
3513
+ function hydrationReviver(_key, value) {
3514
+ if (!value || typeof value !== "object") return value;
3515
+ const payload = value;
3516
+ if (payload[DEFERRED_PAYLOAD] === "fulfilled") return reviveDeferredValue("fulfilled", payload.value, void 0);
3517
+ if (payload[DEFERRED_PAYLOAD] === "rejected") return reviveDeferredValue("rejected", void 0, payload.error);
3518
+ return value;
3519
+ }
3520
+ /**
3521
+ * Relocate the SSR style registry carrier out of the hydration root.
3522
+ *
3523
+ * `@askrjs/server` prepends the collected styles to the page body, so they land
3524
+ * as a direct child of the mount root. That extra element makes the root's
3525
+ * child list disagree with the rendered tree, which costs the app in-place
3526
+ * adoption and discards the carried CSS during reconciliation. The carrier is
3527
+ * transport rather than application markup, so move it into `<head>`, where it
3528
+ * still applies and no longer participates in reconciliation.
3529
+ *
3530
+ * Deferred style patches resolve the carrier with a document-wide query, so
3531
+ * they keep working from its new position.
3532
+ */
3533
+ function adoptSsrStyleCarriers(rootElement) {
3534
+ const head = document.head;
3535
+ if (!head) return;
3536
+ for (const child of Array.from(rootElement.children)) if (child instanceof HTMLStyleElement && child.hasAttribute(SSR_STYLE_REGISTRY_ATTR)) head.append(child);
3537
+ }
3538
+ function takeHydrationRenderData(rootElement) {
3539
+ for (const child of Array.from(rootElement.children)) if (child instanceof HTMLScriptElement && child.getAttribute("data-askr-render-data") === "true") {
3540
+ const raw = child.textContent ?? "";
3541
+ child.remove();
3542
+ if (!raw) return pageRenderEnvelope(void 0);
3543
+ try {
3544
+ return pageRenderEnvelope(JSON.parse(raw, hydrationReviver));
3545
+ } catch (err) {
3546
+ const error = /* @__PURE__ */ new Error("[Askr] Failed to parse embedded SSR render data during hydration.");
3547
+ error.cause = err;
3548
+ throw error;
3549
+ }
3550
+ }
3551
+ return null;
3552
+ }
3553
+ function markSkippedElements(root, skipSelectors) {
3554
+ if (skipSelectors.length === 0) return;
3555
+ const selectorList = Array.from(new Set(skipSelectors)).join(", ");
3556
+ root.querySelectorAll(selectorList).forEach((el) => el.setAttribute("data-skip-hydrate", "true"));
3557
+ }
3558
+ function collectDeferredBelowFoldBoundaries(root, foldY) {
3559
+ const boundaries = [];
3560
+ const stack = [];
3561
+ for (let index = root.children.length - 1; index >= 0; index -= 1) stack.push(root.children[index]);
3562
+ while (stack.length > 0) {
3563
+ const element = stack.pop();
3564
+ if (element.hasAttribute("data-skip-hydrate")) continue;
3565
+ if (element.getBoundingClientRect().top >= foldY) {
3566
+ element.setAttribute("data-skip-hydrate", "true");
3567
+ boundaries.push(element);
3568
+ continue;
3569
+ }
3570
+ for (let index = element.children.length - 1; index >= 0; index -= 1) stack.push(element.children[index]);
3571
+ }
3572
+ return boundaries;
3573
+ }
3574
+ function activateVisibleDeferredBoundaries(root, boundaries, foldY, activate, reportedDepthErrors) {
3575
+ let activated = false;
3576
+ let remaining = 0;
3577
+ for (const element of boundaries) {
3578
+ if (!element.hasAttribute("data-skip-hydrate")) continue;
3579
+ if (element.getBoundingClientRect().top < foldY) try {
3580
+ if (activate(root, element)) {
3581
+ activated = true;
3582
+ reportedDepthErrors.delete(element);
3583
+ }
3584
+ } catch (error) {
3585
+ const clarified = clarifyRenderOverflow(error);
3586
+ if (clarified instanceof RenderDepthError && !reportedDepthErrors.has(element)) {
3587
+ reportedDepthErrors.add(element);
3588
+ reportUncaughtErrorLater(clarified);
3589
+ }
3590
+ }
3591
+ if (element.hasAttribute("data-skip-hydrate")) remaining += 1;
3592
+ }
3593
+ return {
3594
+ activated,
3595
+ remaining
3596
+ };
3597
+ }
3598
+ function queueIdleWork(work) {
3599
+ return new Promise((resolve, reject) => {
3600
+ const run = () => {
3601
+ try {
3602
+ work();
3603
+ resolve();
3604
+ } catch (error) {
3605
+ reject(error);
3606
+ }
3607
+ };
3608
+ if (typeof requestIdleCallback !== "undefined") {
3609
+ requestIdleCallback(run, { timeout: 2e3 });
3610
+ return;
3611
+ }
3612
+ setTimeout(run, 0);
3613
+ });
3614
+ }
3615
+ function shouldVerifyHydrationMarkup(config) {
3616
+ const explicit = config.hydrate?.verifyMarkup;
3617
+ if (typeof explicit === "boolean") return explicit;
3618
+ return !isProductionEnvironment();
3619
+ }
3620
+ async function applySelectiveHydration(rootElement, resolved, path, cleanupStrict, hydrateOptions, source, hooks, interactionReplay) {
3621
+ const hasBelowFoldDeferral = !!hydrateOptions.deferBelowFold;
3622
+ let releaseSelectiveHydrationResources = () => {};
3623
+ let registerSelectiveHydrationCleanup = () => {};
3624
+ if (hydrateOptions.skipSelectors?.length) markSkippedElements(rootElement, hydrateOptions.skipSelectors);
3625
+ let deferredBoundaries = [];
3626
+ const reportedDepthErrors = /* @__PURE__ */ new WeakSet();
3627
+ if (hydrateOptions.deferBelowFold) {
3628
+ const foldY = hydrateOptions.foldThreshold ?? window.innerHeight;
3629
+ deferredBoundaries = collectDeferredBelowFoldBoundaries(rootElement, foldY);
3630
+ interactionReplay.registerDeferredBoundaries(deferredBoundaries);
3631
+ let selectiveHydrationResourcesReleased = false;
3632
+ let unregisterRootCleanupCallback = () => {};
3633
+ function handleScroll() {
3634
+ const { activated, remaining } = activateVisibleDeferredBoundaries(rootElement, deferredBoundaries, foldY, hooks.activateHydrationBoundary, reportedDepthErrors);
3635
+ if (!activated) return;
3636
+ if (remaining === 0) releaseSelectiveHydrationResources();
3637
+ }
3638
+ releaseSelectiveHydrationResources = () => {
3639
+ if (selectiveHydrationResourcesReleased) return;
3640
+ selectiveHydrationResourcesReleased = true;
3641
+ unregisterRootCleanupCallback();
3642
+ window.removeEventListener("scroll", handleScroll);
3643
+ interactionReplay.clearDeferredBoundaries();
3644
+ };
3645
+ interactionReplay.setOnDeferredBoundariesDrained(releaseSelectiveHydrationResources);
3646
+ registerSelectiveHydrationCleanup = () => {
3647
+ unregisterRootCleanupCallback = hooks.registerRootCleanupCallback(rootElement, releaseSelectiveHydrationResources);
3648
+ };
3649
+ window.addEventListener("scroll", handleScroll, { passive: true });
3650
+ }
3651
+ if (hydrateOptions.deferUntilIdle && !hasBelowFoldDeferral) {
3652
+ await queueIdleWork(() => {
3653
+ hooks.mountOrUpdate(rootElement, (() => resolved.handler(resolved.params)), {
3654
+ cleanupStrict,
3655
+ appRuntime: source?.runtime
3656
+ });
3657
+ });
3658
+ await hooks.registerAppNavigation(rootElement, path, source);
3659
+ return;
3660
+ }
3661
+ try {
3662
+ hooks.mountOrUpdate(rootElement, (() => resolved.handler(resolved.params)), {
3663
+ cleanupStrict,
3664
+ appRuntime: source?.runtime
3665
+ });
3666
+ registerSelectiveHydrationCleanup();
3667
+ await hooks.registerAppNavigation(rootElement, path, source);
3668
+ } catch (error) {
3669
+ releaseSelectiveHydrationResources();
3670
+ throw error;
3671
+ }
3672
+ if (hydrateOptions.deferUntilIdle && deferredBoundaries.length > 0) await queueIdleWork(() => {
3673
+ try {
3674
+ activateVisibleDeferredBoundaries(rootElement, deferredBoundaries, Number.POSITIVE_INFINITY, hooks.activateHydrationBoundary, reportedDepthErrors);
3675
+ } finally {
3676
+ releaseSelectiveHydrationResources();
3677
+ }
3678
+ });
3679
+ if (deferredBoundaries.length === 0) releaseSelectiveHydrationResources();
3680
+ }
3681
+ //#endregion
3682
+ //#region src/boot/hydration-interaction-replay.ts
3683
+ const HYDRATION_INTERACTION_TYPES = [
3684
+ "click",
3685
+ "dblclick",
3686
+ "pointerdown",
3687
+ "pointerup",
3688
+ "keydown",
3689
+ "keyup",
3690
+ "input",
3691
+ "change",
3692
+ "submit"
3693
+ ];
3694
+ function cloneInteractionEvent(event) {
3695
+ const EventConstructor = event.constructor;
3696
+ const eventWithDetails = event;
3697
+ const init = {
3698
+ bubbles: event.bubbles,
3699
+ cancelable: event.cancelable,
3700
+ composed: event.composed,
3701
+ detail: eventWithDetails.detail,
3702
+ view: eventWithDetails.view,
3703
+ key: eventWithDetails.key,
3704
+ code: eventWithDetails.code,
3705
+ location: eventWithDetails.location,
3706
+ repeat: eventWithDetails.repeat,
3707
+ isComposing: eventWithDetails.isComposing,
3708
+ ctrlKey: eventWithDetails.ctrlKey,
3709
+ shiftKey: eventWithDetails.shiftKey,
3710
+ altKey: eventWithDetails.altKey,
3711
+ metaKey: eventWithDetails.metaKey,
3712
+ button: eventWithDetails.button,
3713
+ buttons: eventWithDetails.buttons,
3714
+ clientX: eventWithDetails.clientX,
3715
+ clientY: eventWithDetails.clientY,
3716
+ screenX: eventWithDetails.screenX,
3717
+ screenY: eventWithDetails.screenY,
3718
+ pointerId: eventWithDetails.pointerId,
3719
+ pointerType: eventWithDetails.pointerType,
3720
+ isPrimary: eventWithDetails.isPrimary,
3721
+ data: eventWithDetails.data,
3722
+ inputType: eventWithDetails.inputType
3723
+ };
3724
+ try {
3725
+ return new EventConstructor(event.type, init);
3726
+ } catch {
3727
+ return new Event(event.type, {
3728
+ bubbles: event.bubbles,
3729
+ cancelable: event.cancelable,
3730
+ composed: event.composed
3731
+ });
3732
+ }
3733
+ }
3734
+ /**
3735
+ * Capture discrete interactions before hydrated handlers exist, then replay
3736
+ * them after the listener transaction commits. Deferred boundaries remain
3737
+ * interaction-activatable until they are revealed or the root is cleaned up.
3738
+ */
3739
+ function beginHydrationInteractionReplay(root, activateBoundary, permanentSkipSelectors = []) {
3740
+ const queued = [];
3741
+ const deferredBoundaries = /* @__PURE__ */ new Set();
3742
+ let active = true;
3743
+ let hydratingRoot = true;
3744
+ let replaying = false;
3745
+ let hadDeferredBoundaries = false;
3746
+ let onDeferredBoundariesDrained;
3747
+ function isPermanentlySkipped(target) {
3748
+ const element = target instanceof Element ? target : target instanceof Node ? target.parentElement : null;
3749
+ if (!element) return false;
3750
+ return permanentSkipSelectors.some((selector) => {
3751
+ try {
3752
+ return element.closest(selector) !== null;
3753
+ } catch {
3754
+ return false;
3755
+ }
3756
+ });
3757
+ }
3758
+ function findDeferredBoundary(target) {
3759
+ if (!(target instanceof Node)) return;
3760
+ for (const boundary of deferredBoundaries) if (boundary.hasAttribute("data-skip-hydrate") && boundary.contains(target)) return boundary;
3761
+ }
3762
+ function removeCaptureListeners() {
3763
+ if (!active) return;
3764
+ active = false;
3765
+ for (const type of HYDRATION_INTERACTION_TYPES) root.removeEventListener(type, captureInteraction, true);
3766
+ }
3767
+ function releaseIfFinished() {
3768
+ if (!hydratingRoot && deferredBoundaries.size === 0) removeCaptureListeners();
3769
+ }
3770
+ function notifyIfDeferredBoundariesDrained() {
3771
+ if (hadDeferredBoundaries && deferredBoundaries.size === 0) onDeferredBoundariesDrained?.();
3772
+ }
3773
+ function replayInteraction(interaction) {
3774
+ if (!(interaction.target instanceof Node) || !root.contains(interaction.target)) return;
3775
+ const boundary = findDeferredBoundary(interaction.target);
3776
+ if (boundary) {
3777
+ try {
3778
+ if (!activateBoundary(boundary)) return;
3779
+ } catch {
3780
+ return;
3781
+ }
3782
+ deferredBoundaries.delete(boundary);
3783
+ notifyIfDeferredBoundariesDrained();
3784
+ }
3785
+ replaying = true;
3786
+ try {
3787
+ interaction.target.dispatchEvent(interaction.event);
3788
+ } finally {
3789
+ replaying = false;
3790
+ }
3791
+ releaseIfFinished();
3792
+ }
3793
+ function captureInteraction(event) {
3794
+ if (!active || replaying) return;
3795
+ const target = event.target;
3796
+ if (!target || isPermanentlySkipped(target)) return;
3797
+ const boundary = findDeferredBoundary(target);
3798
+ if (!hydratingRoot && !boundary) return;
3799
+ event.preventDefault();
3800
+ event.stopImmediatePropagation();
3801
+ const interaction = {
3802
+ target,
3803
+ event: cloneInteractionEvent(event)
3804
+ };
3805
+ if (hydratingRoot) {
3806
+ queued.push(interaction);
3807
+ return;
3808
+ }
3809
+ replayInteraction(interaction);
3810
+ }
3811
+ const installedTypes = [];
3812
+ try {
3813
+ for (const type of HYDRATION_INTERACTION_TYPES) {
3814
+ root.addEventListener(type, captureInteraction, true);
3815
+ installedTypes.push(type);
3816
+ }
3817
+ } catch (error) {
3818
+ active = false;
3819
+ for (const type of installedTypes) root.removeEventListener(type, captureInteraction, true);
3820
+ throw error;
3821
+ }
3822
+ return {
3823
+ registerDeferredBoundaries(boundaries) {
3824
+ for (const boundary of boundaries) deferredBoundaries.add(boundary);
3825
+ hadDeferredBoundaries ||= boundaries.length > 0;
3826
+ },
3827
+ setOnDeferredBoundariesDrained(callback) {
3828
+ onDeferredBoundariesDrained = callback;
3829
+ },
3830
+ clearDeferredBoundaries() {
3831
+ deferredBoundaries.clear();
3832
+ releaseIfFinished();
3833
+ },
3834
+ complete() {
3835
+ if (!active || !hydratingRoot) return;
3836
+ hydratingRoot = false;
3837
+ const pending = queued.splice(0);
3838
+ for (const interaction of pending) replayInteraction(interaction);
3839
+ releaseIfFinished();
3840
+ },
3841
+ abort() {
3842
+ queued.length = 0;
3843
+ deferredBoundaries.clear();
3844
+ onDeferredBoundariesDrained = void 0;
3845
+ hydratingRoot = false;
3846
+ removeCaptureListeners();
3847
+ }
3848
+ };
3849
+ }
3850
+ //#endregion
3851
+ //#region src/boot/hydrate-spa.ts
3852
+ /**
3853
+ * App bootstrap and mount
3854
+ */
3855
+ /**
3856
+ * hydrateSPA: Hydrate server-rendered HTML with an explicit route registry.
3857
+ */
3858
+ async function hydrateSPA(config) {
3859
+ assertExecutionModel("spa");
3860
+ if (!config || typeof config !== "object") throw new Error("hydrateSPA requires a config object");
3861
+ validateCspNonce(config.cspNonce);
3862
+ if (!config.registry) throw new Error("hydrateSPA requires a route registry.");
3863
+ const { manifest } = config.registry;
3864
+ if (manifest.records.length === 0) throw new Error("hydrateSPA requires a route registry with at least one route. If you are enhancing existing HTML, use createIsland instead.");
3865
+ const rootElement = resolveRootElement(config.root);
3866
+ if (!rootElement) throw new Error(`Root element not found: ${config.root}`);
3867
+ const interactionReplay = beginHydrationInteractionReplay(rootElement, (boundary) => activateHydrationBoundary(rootElement, boundary), config.hydrate?.skipSelectors);
3868
+ try {
3869
+ applyDeferredStreamPatches(rootElement);
3870
+ adoptSsrStyleCarriers(rootElement);
3871
+ const hydrationRenderData = takeHydrationRenderData(rootElement);
3872
+ const hydrationQueryCache = hydrationRenderData?.queries;
3873
+ const dataRuntime = config.dataRuntime ?? getDefaultDataRuntime();
3874
+ if (hydrationQueryCache) hydrateDataRuntime(dataRuntime, hydrationQueryCache);
3875
+ const hydrationRenderDataForApp = withoutHydratedAuth(hydrationRenderData);
3876
+ configureScrollRestoration(config.scrollRestoration);
3877
+ clearRouteState();
3878
+ _applyManifest(manifest);
3879
+ const hydratedAuth = readHydratedAuth(hydrationRenderData);
3880
+ const routeAuth = config.auth ?? manifest.auth;
3881
+ const appRouteSource = {
3882
+ registry: config.registry,
3883
+ auth: routeAuth,
3884
+ runtime: createAppRenderRuntime({
3885
+ hydrationResources: hydrationRenderDataForApp?.resources,
3886
+ framework: hydrationRenderDataForApp?.framework,
3887
+ route: hydrationRenderData?.route,
3888
+ hasRoute: hydrationRenderData !== null,
3889
+ dataRuntime,
3890
+ routeRegistry: config.registry,
3891
+ routeAuth
3892
+ })
3893
+ };
3894
+ _setActiveRouteAuthOptions(routeAuth);
3895
+ const initialRoute = await resolveInitialRoute(routeAuth, {
3896
+ registry: config.registry,
3897
+ load: false,
3898
+ authContext: hydratedAuth,
3899
+ dataRuntime
3900
+ });
3901
+ if (!initialRoute) {
3902
+ interactionReplay.abort();
3903
+ return;
3904
+ }
3905
+ const { path, href: currentUrl, resolved } = initialRoute;
3906
+ setServerLocation(currentUrl);
3907
+ if (isProductionEnvironment()) lockRouteRegistration();
3908
+ if (!resolved) throw new Error(`hydrateSPA: no route found for current path (${path}).`);
3909
+ await reconcileInitialRouteMetadata(resolved);
3910
+ const hydrationResolved = resolved.kind === "deny" ? {
3911
+ handler: bindDeniedRouteHandler(resolved.status),
3912
+ params: {}
3913
+ } : resolved;
3914
+ let verifyClientMarkup;
3915
+ let clientMarkup = null;
3916
+ const captureClientMarkup = () => {
3917
+ if (verifyClientMarkup) clientMarkup = rootElement.innerHTML;
3918
+ };
3919
+ const mountHydratedRoot = (...args) => mountOrUpdate(args[0], args[1], {
3920
+ ...args[2],
3921
+ cspNonce: config.cspNonce,
3922
+ hydrate: true,
3923
+ onCommit: captureClientMarkup
3924
+ });
3925
+ if (shouldVerifyHydrationMarkup(config)) {
3926
+ const { captureServerHydrationMarkup, verifyClientHydrationMarkup, verifyHydrationSyncForUrl } = await import("./verify-hydration-DpVaDnqz.js");
3927
+ if (!verifyHydrationSyncForUrl({
3928
+ root: rootElement,
3929
+ url: currentUrl,
3930
+ registry: config.registry,
3931
+ resolved: hydrationResolved,
3932
+ options: {
3933
+ data: hydrationRenderDataForApp?.resources,
3934
+ dataRuntime,
3935
+ envelope: hydrationRenderDataForApp ?? void 0,
3936
+ cspNonce: config.cspNonce
3937
+ }
3938
+ })) throw new Error("[Askr] Hydration mismatch detected. Server HTML does not match expected server-render output.");
3939
+ const serverMarkup = captureServerHydrationMarkup(rootElement, currentUrl, hydrationRenderDataForApp ?? void 0);
3940
+ if (serverMarkup !== null) verifyClientMarkup = async () => {
3941
+ await Promise.resolve();
3942
+ if (!(clientMarkup !== null && verifyClientHydrationMarkup(clientMarkup, serverMarkup)) && !verifyClientHydrationMarkup(rootElement.innerHTML, serverMarkup)) throw new Error("[Askr] Hydration mismatch detected between server and client markup.");
3943
+ };
3944
+ }
3945
+ const hydrateOptions = config.hydrate;
3946
+ if (hydrateOptions) {
3947
+ if (hydrateOptions.deferUntilIdle || hydrateOptions.deferBelowFold) {
3948
+ if (hydrationRenderDataForApp) startHydrationRenderPhase(hydrationRenderDataForApp);
3949
+ try {
3950
+ await applySelectiveHydration(rootElement, hydrationResolved, path, config.cleanupStrict, hydrateOptions, appRouteSource, {
3951
+ mountOrUpdate: mountHydratedRoot,
3952
+ registerAppNavigation,
3953
+ registerRootCleanupCallback,
3954
+ activateHydrationBoundary
3955
+ }, interactionReplay);
3956
+ } finally {
3957
+ if (hydrationRenderDataForApp) stopHydrationRenderPhase();
3958
+ }
3959
+ flushSync();
3960
+ if (!rootElement.querySelector("[data-skip-hydrate]")) await verifyClientMarkup?.();
3961
+ interactionReplay.complete();
3962
+ return;
3963
+ }
3964
+ if (hydrateOptions.skipSelectors?.length) markSkippedElements(rootElement, hydrateOptions.skipSelectors);
3965
+ }
3966
+ if (hydrationRenderDataForApp) startHydrationRenderPhase(hydrationRenderDataForApp);
3967
+ try {
3968
+ mountHydratedRoot(rootElement, resolved.kind === "deny" ? bindDeniedStatus(resolved.status) : bindResolvedRouteHandler(hydrationResolved), {
3969
+ cleanupStrict: config.cleanupStrict,
3970
+ appRuntime: appRouteSource.runtime
3971
+ });
3972
+ flushSync();
3973
+ } finally {
3974
+ if (hydrationRenderDataForApp) stopHydrationRenderPhase();
3975
+ }
3976
+ await verifyClientMarkup?.();
3977
+ interactionReplay.complete();
3978
+ await registerAppNavigation(rootElement, path, { ...appRouteSource });
3979
+ } catch (error) {
3980
+ interactionReplay.abort();
3981
+ throw error;
3982
+ }
3983
+ }
3984
+ //#endregion
3985
+ //#region src/boot/index.ts
3986
+ /** Stable bootstrap exports; each mounting mode owns its orchestration. */
3987
+ composeBrowserRuntime();
3988
+ //#endregion
3989
+ export { cleanupApp as a, createIslands as i, createSPA as n, hasApp as o, createIsland as r, hydrateSPA as t };