@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,220 @@
1
+ import { S as queueTask, d as recordUndo, v as effectScheduler, w as Computation } from "./instance-BAnfmXOR.js";
2
+ import { c as onCommit, d as reportLifecycleError, f as withOwner, i as currentComponent, o as hookSlot } from "./hooks-BimKStr4.js";
3
+ import { t as isRouteActivityActive } from "./route-activity-CNJfpwx-.js";
4
+ //#region src/core/api/lifecycle.ts
5
+ /**
6
+ * Component lifecycle hooks: work that starts after a render commits and
7
+ * ends with the component. Each is a no-op outside a component render and
8
+ * never starts on the server.
9
+ */
10
+ /** Run `fn` once after the component's first commit; a returned function is its cleanup. */
11
+ function task(fn) {
12
+ const instance = currentComponent();
13
+ if (!instance) return;
14
+ const slot = hookSlot(instance, "task", () => ({
15
+ started: false,
16
+ fn
17
+ }));
18
+ if (slot.started || instance.server) return;
19
+ const previous = slot.fn;
20
+ slot.fn = fn;
21
+ recordUndo(() => {
22
+ slot.fn = previous;
23
+ });
24
+ onCommit(instance, () => {
25
+ if (slot.started) return;
26
+ slot.started = true;
27
+ try {
28
+ return withOwner(instance, slot.fn);
29
+ } catch (error) {
30
+ return Promise.reject(error);
31
+ }
32
+ });
33
+ }
34
+ /** Snapshot `fn()` now and return a thunk that yields that value later. */
35
+ function capture(fn) {
36
+ const value = fn();
37
+ return () => value;
38
+ }
39
+ function sameValues(previous, next) {
40
+ if (Array.isArray(previous) && Array.isArray(next)) {
41
+ if (previous.length !== next.length) return false;
42
+ return previous.every((value, index) => Object.is(value, next[index]));
43
+ }
44
+ return Object.is(previous, next);
45
+ }
46
+ function stopGeneration(slot) {
47
+ const controller = slot.controller;
48
+ slot.controller = null;
49
+ if (controller && !controller.signal.aborted) controller.abort();
50
+ const cleanup = slot.cleanup;
51
+ slot.cleanup = null;
52
+ cleanup?.();
53
+ }
54
+ function observe(instance, slot, value, previous) {
55
+ stopGeneration(slot);
56
+ const controller = new AbortController();
57
+ slot.controller = controller;
58
+ const initial = !slot.observed;
59
+ slot.observed = true;
60
+ let result;
61
+ try {
62
+ result = withOwner(instance, () => slot.callback(value, {
63
+ initial,
64
+ previous,
65
+ signal: controller.signal
66
+ }));
67
+ } catch (error) {
68
+ reportLifecycleError(instance, error);
69
+ return;
70
+ }
71
+ if (typeof result === "function") slot.cleanup = result;
72
+ else if (result && typeof result.then === "function") Promise.resolve(result).catch((error) => {
73
+ if (!controller.signal.aborted) reportLifecycleError(instance, error);
74
+ });
75
+ }
76
+ function startWatch(instance, slot) {
77
+ if (slot.effect) {
78
+ slot.effect.invalidate();
79
+ return;
80
+ }
81
+ const effect = new Computation(instance, () => {
82
+ let value;
83
+ try {
84
+ const values = slot.sources.map((source) => source());
85
+ value = values.length === 1 ? values[0] : values;
86
+ } catch (error) {
87
+ reportLifecycleError(instance, error);
88
+ return;
89
+ }
90
+ if (slot.version > 0 && sameValues(slot.value, value)) return;
91
+ slot.value = value;
92
+ const version = ++slot.version;
93
+ queueTask(() => {
94
+ if (instance.disposed || version !== slot.version) return;
95
+ const previous = slot.deliveredValue;
96
+ slot.deliveredValue = value;
97
+ observe(instance, slot, value, previous);
98
+ });
99
+ }, effectScheduler("post", instance.depth, () => {
100
+ reportLifecycleError(instance, /* @__PURE__ */ new Error("[Askr] reactive cycle in watch(): the source changed in its own callback."));
101
+ }, 48), null);
102
+ slot.effect = effect;
103
+ instance.onCleanup(() => {
104
+ stopGeneration(slot);
105
+ slot.effect = null;
106
+ });
107
+ effect.run();
108
+ }
109
+ function watch(sourceOrSources, callback) {
110
+ const instance = currentComponent();
111
+ if (!instance) return;
112
+ const sources = Array.isArray(sourceOrSources) ? sourceOrSources : [sourceOrSources];
113
+ const slot = hookSlot(instance, "watch", () => ({
114
+ sources,
115
+ callback,
116
+ effect: null,
117
+ controller: null,
118
+ cleanup: null,
119
+ observed: false,
120
+ value: void 0,
121
+ deliveredValue: void 0,
122
+ version: 0
123
+ }));
124
+ if (instance.server) return;
125
+ onCommit(instance, () => {
126
+ slot.sources = sources;
127
+ slot.callback = callback;
128
+ startWatch(instance, slot);
129
+ });
130
+ }
131
+ function predicatesOf(when) {
132
+ if (!when) return [];
133
+ return typeof when === "function" ? [when] : when;
134
+ }
135
+ /** Run `fn` every `intervalMs` while the component lives and `options.when` passes. */
136
+ function timer(intervalMs, fn, options) {
137
+ const instance = currentComponent();
138
+ if (!instance) return;
139
+ const slot = hookSlot(instance, "timer", () => ({
140
+ id: null,
141
+ intervalMs: null,
142
+ callback: fn,
143
+ predicates: predicatesOf(options?.when)
144
+ }));
145
+ if (instance.server) return;
146
+ const predicates = predicatesOf(options?.when);
147
+ onCommit(instance, () => {
148
+ slot.callback = fn;
149
+ slot.predicates = predicates;
150
+ if (slot.id !== null && slot.intervalMs === intervalMs) return;
151
+ const first = slot.id === null && slot.intervalMs === null;
152
+ if (slot.id !== null) clearInterval(slot.id);
153
+ slot.intervalMs = intervalMs;
154
+ slot.id = setInterval(() => {
155
+ if (slot.predicates.every((predicate) => predicate())) slot.callback();
156
+ }, intervalMs);
157
+ if (first) instance.onCleanup(() => {
158
+ if (slot.id !== null) clearInterval(slot.id);
159
+ slot.id = null;
160
+ });
161
+ });
162
+ }
163
+ /** An {@link ActivityPredicate} that is true while the current route matches. */
164
+ function routeActive(pathOrPaths) {
165
+ return () => isRouteActivityActive(pathOrPaths);
166
+ }
167
+ function sameOptions(a, b) {
168
+ if (a === b) return true;
169
+ if (typeof a !== "object" || typeof b !== "object" || !a || !b) return false;
170
+ return a.capture === b.capture && a.once === b.once && a.passive === b.passive && a.signal === b.signal;
171
+ }
172
+ /** Attach an event listener to `target` for the component's lifetime. */
173
+ function on(target, event, handler, options) {
174
+ const instance = currentComponent();
175
+ if (!instance) return;
176
+ const slot = hookSlot(instance, "on", () => {
177
+ const created = {
178
+ target: null,
179
+ event,
180
+ options: void 0,
181
+ handler,
182
+ attached: false,
183
+ listener: ((evt) => {
184
+ created.handler.call(created.target, evt);
185
+ })
186
+ };
187
+ return created;
188
+ });
189
+ if (instance.server) return;
190
+ onCommit(instance, () => {
191
+ slot.handler = handler;
192
+ const resolved = typeof target === "function" ? target() ?? null : target;
193
+ if (slot.attached && slot.target === resolved && slot.event === event && sameOptions(slot.options, options)) return;
194
+ const first = !slot.attached && slot.target === null;
195
+ if (slot.attached && slot.target) slot.target.removeEventListener(slot.event, slot.listener, slot.options);
196
+ slot.attached = false;
197
+ slot.target = resolved;
198
+ slot.event = event;
199
+ slot.options = options;
200
+ if (resolved) {
201
+ resolved.addEventListener(event, slot.listener, options);
202
+ slot.attached = true;
203
+ }
204
+ if (first) instance.onCleanup(() => {
205
+ if (slot.attached && slot.target) slot.target.removeEventListener(slot.event, slot.listener, slot.options);
206
+ slot.attached = false;
207
+ });
208
+ });
209
+ }
210
+ //#endregion
211
+ //#region src/resources/browser-activity.ts
212
+ /** Activity gates retain their permissive server defaults. */
213
+ function documentVisible() {
214
+ return () => typeof document === "undefined" || document.visibilityState !== "hidden";
215
+ }
216
+ function windowFocused() {
217
+ return () => typeof document === "undefined" || typeof document.hasFocus !== "function" || document.hasFocus();
218
+ }
219
+ //#endregion
220
+ export { routeActive as a, watch as c, on as i, windowFocused as n, task as o, capture as r, timer as s, documentVisible as t };
@@ -2,6 +2,12 @@ import { VNode } from '../core.js';
2
2
  import { JSXElement, Props } from '../elements.js';
3
3
  type ErrorBoundaryContent = VNode | readonly VNode[];
4
4
  type ErrorBoundaryFallbackValue = ErrorBoundaryContent | Node;
5
+ /** A child of an {@link ErrorBoundary}; a raw DOM `Node` is not a child. */
6
+ type ErrorBoundaryChild =
7
+ | VNode
8
+ | JSXElement
9
+ | (() => unknown)
10
+ | readonly ErrorBoundaryChild[];
5
11
  /** Renders a fallback for the caught error; call `reset` to retry the children. */
6
12
  type ErrorBoundaryFallbackRender = (
7
13
  error: unknown,
@@ -9,7 +15,8 @@ type ErrorBoundaryFallbackRender = (
9
15
  ) => ErrorBoundaryFallbackValue;
10
16
  /** Props for {@link ErrorBoundary}. */
11
17
  interface ErrorBoundaryProps extends Props {
12
- children?: ErrorBoundaryContent;
18
+ /** Boundary content: nodes, elements, function children, or a list of them. */
19
+ children?: ErrorBoundaryChild;
13
20
  /** Static fallback content, or a render function receiving the error and a reset callback. */
14
21
  fallback?: ErrorBoundaryFallbackValue | ErrorBoundaryFallbackRender;
15
22
  /** Called with the caught error when the boundary trips. */
@@ -1,44 +1,2 @@
1
- import { f as getCurrentComponentInstance } from "../scope-Cq_iYbTR.js";
2
- import { t as __ERROR_BOUNDARY__ } from "../vnode-D24p6IW2.js";
3
- import { t as ELEMENT_TYPE } from "../jsx-DRS-qjiN.js";
4
- //#region src/components/error-boundary.tsx
5
- function ensureBoundaryState(instance, resetKey) {
6
- const boundaryState = instance.errorBoundaryState ?? (instance.errorBoundaryState = {
7
- error: null,
8
- resetKey,
9
- notified: false
10
- });
11
- if (!Object.is(boundaryState.resetKey, resetKey)) {
12
- boundaryState.error = null;
13
- boundaryState.resetKey = resetKey;
14
- boundaryState.notified = false;
15
- }
16
- return boundaryState;
17
- }
18
- function createBoundaryVNode(instance, props) {
19
- const key = typeof props.key === "symbol" ? null : props.key ?? null;
20
- return {
21
- $$typeof: ELEMENT_TYPE,
22
- type: __ERROR_BOUNDARY__,
23
- props: {
24
- children: props.children,
25
- fallback: props.fallback,
26
- onError: props.onError,
27
- resetKey: props.resetKey
28
- },
29
- key,
30
- __instance: instance
31
- };
32
- }
33
- /**
34
- * Creates a boundary for descendant mount and post-mount render/commit errors,
35
- * including content materialized through a portal host.
36
- */
37
- function ErrorBoundary(props) {
38
- const instance = getCurrentComponentInstance();
39
- if (!instance) throw new Error("[Askr] ErrorBoundary() can only be used during component render execution.");
40
- ensureBoundaryState(instance, props.resetKey);
41
- return createBoundaryVNode(instance, props);
42
- }
43
- //#endregion
1
+ import { t as ErrorBoundary } from "../error-boundary-Bpwofwps.js";
44
2
  export { ErrorBoundary };
@@ -25,6 +25,8 @@ function noop() {}
25
25
  *
26
26
  * Deterministic props merging.
27
27
  * - For non-handlers: `base` overwrites `injected`.
28
+ * - A `base` value of `undefined` means "not provided" and never overwrites an
29
+ * `injected` value; pass `null` to clear an injected prop explicitly.
28
30
  * - For handlers present in both: handlers are composed with `injected` running
29
31
  * first; it may call `preventDefault()` to suppress the `base` handler.
30
32
  *
@@ -42,15 +44,24 @@ function noop() {}
42
44
  * Injected handler runs first, base handler second.
43
45
  * This allows injected handlers to prevent default.
44
46
  *
45
- * 4. Return Type
46
- * Returns intersection type (TInjected & TBase) for type safety.
47
+ * 4. Undefined Means Absent
48
+ * A `base` key whose value is `undefined` does not override `injected`.
49
+ * Forwarding an optional prop that was not supplied
50
+ * (`onClick={props.onClick}`) must not wipe injected behaviour or ARIA.
51
+ * `null` is an explicit value and still overrides (base wins).
52
+ *
53
+ * 5. Return Type
54
+ * Returns MergedProps<TBase, TInjected>: base value types win unless they
55
+ * may be undefined, in which case the injected type is included.
47
56
  */
48
57
  function isEventHandlerKey(key) {
49
58
  return key.startsWith("on");
50
59
  }
51
60
  /**
52
61
  * Merge `base` props over `injected` props: non-handler keys in `base` win,
53
- * and matching event handlers are composed (`injected` runs first).
62
+ * and matching event handlers are composed (`injected` runs first). `base`
63
+ * values that are `undefined` are treated as absent and never overwrite an
64
+ * injected value; use `null` to clear one explicitly.
54
65
  */
55
66
  function mergeProps(base, injected) {
56
67
  const baseKeys = Object.keys(base);
@@ -59,6 +70,7 @@ function mergeProps(base, injected) {
59
70
  for (const key of baseKeys) {
60
71
  const baseValue = base[key];
61
72
  const injectedValue = injected[key];
73
+ if (baseValue === void 0 && Object.prototype.hasOwnProperty.call(out, key)) continue;
62
74
  if (isEventHandlerKey(key) && typeof baseValue === "function" && typeof injectedValue === "function") {
63
75
  out[key] = composeHandlers(injectedValue, baseValue);
64
76
  continue;
@@ -85,4 +97,29 @@ function ariaSelected(selected) {
85
97
  return selected === void 0 ? {} : { "aria-selected": String(selected) };
86
98
  }
87
99
  //#endregion
88
- export { composeHandlers as a, mergeProps as i, ariaExpanded as n, ariaSelected as r, ariaDisabled as t };
100
+ //#region src/foundations/utilities/compose-ref.ts
101
+ /** Write `value` to a {@link Ref}, ignoring failures on readonly object refs. */
102
+ function setRef(ref, value) {
103
+ if (!ref) return;
104
+ if (typeof ref === "function") {
105
+ ref(value);
106
+ return;
107
+ }
108
+ try {
109
+ ref.current = value;
110
+ } catch {}
111
+ }
112
+ /** Combine multiple refs into one callback ref that writes to all of them. */
113
+ function composeRefs(...refs) {
114
+ return (value) => {
115
+ const errors = [];
116
+ for (const ref of refs) try {
117
+ setRef(ref, value);
118
+ } catch (error) {
119
+ errors.push(error);
120
+ }
121
+ if (errors.length > 0) throw new AggregateError(errors, "Composed ref callbacks failed");
122
+ };
123
+ }
124
+ //#endregion
125
+ export { ariaSelected as a, ariaExpanded as i, setRef as n, mergeProps as o, ariaDisabled as r, composeHandlers as s, composeRefs as t };
@@ -6,6 +6,7 @@ import {
6
6
  Match,
7
7
  MatchProps,
8
8
  ForProps,
9
+ ForGetterProps,
9
10
  Case,
10
11
  } from '../core.js';
11
12
  export {
@@ -13,6 +14,7 @@ export {
13
14
  type CaseProps,
14
15
  For,
15
16
  type ForProps,
17
+ type ForGetterProps,
16
18
  Match,
17
19
  type MatchProps,
18
20
  Show,
@@ -1,2 +1,2 @@
1
- import { i as For, n as Match, r as Show, t as Case } from "../control-BR_mrX__.js";
1
+ import { i as Show, n as For, r as Match, t as Case } from "../control-nfpT2fbK.js";
2
2
  export { Case, For, Match, Show };
@@ -0,0 +1,209 @@
1
+ import { F as logger, I as isDevelopmentEnvironment, T as Signal, d as recordUndo, h as isRendering } from "./instance-BAnfmXOR.js";
2
+ import { n as Fragment, t as ELEMENT_TYPE } from "./jsx-DRS-qjiN.js";
3
+ import { c as onCommit, i as currentComponent, o as hookSlot } from "./hooks-BimKStr4.js";
4
+ //#region src/core/api/control.ts
5
+ /**
6
+ * Control flow as ordinary components.
7
+ *
8
+ * `Show`, `Case`, and `For` are element types like any other component: they
9
+ * render lazily in their own lifetime, read their sources in their own
10
+ * render, and never claim hook slots in the component that uses them. A
11
+ * branch is wrapped in a keyed fragment so switching branches replaces the
12
+ * branch's lifetime instead of patching one branch's DOM into another's.
13
+ */
14
+ function element(type, props, key = null) {
15
+ return {
16
+ $$typeof: ELEMENT_TYPE,
17
+ type,
18
+ props,
19
+ key
20
+ };
21
+ }
22
+ function branch(key, children) {
23
+ return element(Fragment, { children }, key);
24
+ }
25
+ function resolve(value) {
26
+ return typeof value === "function" ? value() : value;
27
+ }
28
+ function isPresent(value) {
29
+ return value !== null && value !== void 0 && value !== false;
30
+ }
31
+ /** Render `children` while `when` is truthy, otherwise `fallback`. */
32
+ function Show(props) {
33
+ const value = resolve(props.when);
34
+ if (value) return branch("show:when", typeof props.children === "function" ? props.children(value) : props.children);
35
+ return isPresent(props.fallback) ? branch("show:fallback", props.fallback) : null;
36
+ }
37
+ /** Declares one branch of a {@link Case}; only valid as its direct child. */
38
+ function Match(_props) {
39
+ throw new Error("[askr] <Match> may only be used as a direct child of <Case>.");
40
+ }
41
+ function flatten(children, out = []) {
42
+ if (Array.isArray(children)) for (const child of children) flatten(child, out);
43
+ else if (isPresent(children)) out.push(children);
44
+ return out;
45
+ }
46
+ /** Render the first {@link Match} whose `when` is truthy, else `fallback`. */
47
+ function Case(props) {
48
+ const children = flatten(props.children);
49
+ for (let index = 0; index < children.length; index++) {
50
+ const child = children[index];
51
+ if (typeof child !== "object" || child === null || child.type !== Match) throw new Error("[askr] <Case> only accepts <Match> children.");
52
+ }
53
+ for (let index = 0; index < children.length; index++) {
54
+ const child = children[index];
55
+ const matchProps = child.props;
56
+ if (!resolve(matchProps.when)) continue;
57
+ return branch(child.key == null ? `match:${index}` : `match:${index}:${typeof child.key}:${String(child.key)}`, resolve(matchProps.children));
58
+ }
59
+ return isPresent(props.fallback) ? branch("case:fallback", props.fallback) : null;
60
+ }
61
+ /** One list row: re-renders only when its item or the row callback changes. */
62
+ function ForRow(props) {
63
+ return props.render(props.row.readItem(), props.row.readIndex);
64
+ }
65
+ function recordRowUndo(undo) {
66
+ if (isRendering() && !currentComponent()?.server) recordUndo(undo);
67
+ }
68
+ function createRow(index, item) {
69
+ const indexSource = new Signal(index);
70
+ const source = new Signal(item);
71
+ const overlay = /* @__PURE__ */ new Map();
72
+ const deleted = /* @__PURE__ */ new Set();
73
+ /** One signal per property a reader has read through the proxy. */
74
+ const properties = /* @__PURE__ */ new Map();
75
+ let proxy = null;
76
+ const readProperty = (key) => {
77
+ let property = properties.get(key);
78
+ if (!property) {
79
+ property = new Signal(Reflect.get(source.peek(), key));
80
+ properties.set(key, property);
81
+ recordRowUndo(() => properties.delete(key));
82
+ }
83
+ return property.read();
84
+ };
85
+ return {
86
+ index: indexSource,
87
+ readIndex: () => indexSource.read(),
88
+ source,
89
+ readItem: () => {
90
+ const item = source.peek();
91
+ if (typeof item !== "object" || item === null || Array.isArray(item)) return item;
92
+ if (proxy) return proxy;
93
+ proxy = new Proxy({}, {
94
+ get(_target, key) {
95
+ if (deleted.has(key)) return void 0;
96
+ const own = overlay.get(key);
97
+ if (own) return own.get ? own.get.call(proxy) : own.value;
98
+ return readProperty(key);
99
+ },
100
+ set(_target, key, value) {
101
+ const previous = overlay.get(key);
102
+ if (!previous && typeof key !== "symbol" && isDevelopmentEnvironment() && key in Object(source.peek())) logger.warn(`[Askr] Assigning to "${String(key)}" on a <For> item shadows a property from the source data - it will not update when the source item changes and does not trigger a re-render. Use a different property name for cached/derived per-row values, or a state() cell for values that should be reactive.`);
103
+ const wasDeleted = deleted.delete(key);
104
+ overlay.set(key, {
105
+ value,
106
+ writable: true,
107
+ enumerable: true,
108
+ configurable: true
109
+ });
110
+ recordRowUndo(() => {
111
+ if (previous) overlay.set(key, previous);
112
+ else overlay.delete(key);
113
+ if (wasDeleted) deleted.add(key);
114
+ });
115
+ return true;
116
+ },
117
+ has(_target, key) {
118
+ return !deleted.has(key) && (overlay.has(key) || Reflect.has(source.read(), key));
119
+ },
120
+ ownKeys() {
121
+ return [...overlay.keys(), ...Reflect.ownKeys(source.read()).filter((key) => !overlay.has(key) && !deleted.has(key))];
122
+ },
123
+ getOwnPropertyDescriptor(_target, key) {
124
+ if (deleted.has(key)) return void 0;
125
+ const descriptor = overlay.get(key) ?? Reflect.getOwnPropertyDescriptor(source.read(), key);
126
+ return descriptor ? {
127
+ ...descriptor,
128
+ configurable: true
129
+ } : void 0;
130
+ },
131
+ getPrototypeOf() {
132
+ return Reflect.getPrototypeOf(source.read());
133
+ },
134
+ deleteProperty(_target, key) {
135
+ const previous = overlay.get(key);
136
+ const wasDeleted = deleted.has(key);
137
+ overlay.delete(key);
138
+ deleted.add(key);
139
+ recordRowUndo(() => {
140
+ if (previous) overlay.set(key, previous);
141
+ if (!wasDeleted) deleted.delete(key);
142
+ });
143
+ return true;
144
+ }
145
+ });
146
+ return proxy;
147
+ },
148
+ writeItem(item) {
149
+ const previousItem = source.peek();
150
+ if (!source.write(item)) return;
151
+ recordRowUndo(() => {
152
+ source.write(previousItem);
153
+ });
154
+ if (typeof item !== "object" || item === null) return;
155
+ for (const [key, property] of properties) {
156
+ const previous = property.peek();
157
+ if (property.write(Reflect.get(item, key))) recordRowUndo(() => {
158
+ property.write(previous);
159
+ });
160
+ }
161
+ }
162
+ };
163
+ }
164
+ function validateKey(key, index) {
165
+ if (key === null || key === void 0) throw new Error(`[askr] Invalid For key detected at index ${index}: ${String(key)}. Keys should be stable, non-null, and unique within a For list.`);
166
+ }
167
+ function For(props) {
168
+ const by = props.by;
169
+ if (!by && props.byIndex !== true) throw new Error("[Askr] <For> requires a stable `by` key function or `byIndex={true}`.");
170
+ if (by && props.byIndex) throw new Error("[Askr] <For> accepts `by` or `byIndex`, not both.");
171
+ const instance = currentComponent();
172
+ const rows = instance ? hookSlot(instance, "for", () => /* @__PURE__ */ new Map()) : /* @__PURE__ */ new Map();
173
+ const items = resolve(props.each) ?? [];
174
+ if (items.length === 0) {
175
+ if (instance) onCommit(instance, () => rows.clear());
176
+ return isPresent(props.fallback) ? branch("for:fallback", props.fallback) : null;
177
+ }
178
+ const output = [];
179
+ const live = /* @__PURE__ */ new Set();
180
+ for (let index = 0; index < items.length; index++) {
181
+ const item = items[index];
182
+ const key = by ? by(item, index) : index;
183
+ validateKey(key, index);
184
+ if (live.has(key)) throw new Error(`[askr] Duplicate For key detected: ${String(key)}. Keys should be stable, non-null, and unique within a For list.`);
185
+ let row = rows.get(key);
186
+ if (!row) {
187
+ row = createRow(index, item);
188
+ rows.set(key, row);
189
+ recordRowUndo(() => rows.delete(key));
190
+ } else {
191
+ const indexSource = row.index;
192
+ const previous = indexSource.peek();
193
+ if (indexSource.write(index)) recordRowUndo(() => indexSource.write(previous));
194
+ row.writeItem(item);
195
+ }
196
+ live.add(key);
197
+ output.push(element(ForRow, {
198
+ item,
199
+ row,
200
+ render: props.children
201
+ }, key));
202
+ }
203
+ if (instance) onCommit(instance, () => {
204
+ for (const key of rows.keys()) if (!live.has(key)) rows.delete(key);
205
+ });
206
+ return output;
207
+ }
208
+ //#endregion
209
+ export { Show as i, For as n, Match as r, Case as t };
package/dist/core.d.ts CHANGED
@@ -1,4 +1,7 @@
1
- /** Compatibility re-export surface for maintained domain contracts. */
1
+ /**
2
+ * Aggregates the maintained domain contracts in `domains/` so entrypoint
3
+ * declarations can import them from one module. Not a package subpath.
4
+ */
2
5
  import './jsx-globals.js';
3
6
  export {
4
7
  selector,
@@ -113,14 +116,6 @@ export {
113
116
  } from './domains/routing.js';
114
117
  export { getSignal } from './domains/component.js';
115
118
  export { scheduleEventHandler } from './domains/scheduler.js';
116
- export {
117
- RuntimeKeyedReorderDecision,
118
- getDefaultRuntime,
119
- RuntimeRendererHost,
120
- AskrRuntime,
121
- AskrRuntimeOptions,
122
- createRuntime,
123
- } from './domains/renderer.js';
124
119
  export {
125
120
  Show,
126
121
  ShowProps,
@@ -129,6 +124,7 @@ export {
129
124
  Match,
130
125
  MatchProps,
131
126
  ForProps,
127
+ ForGetterProps,
132
128
  Case,
133
129
  } from './domains/control.js';
134
130
  export {
@@ -0,0 +1,35 @@
1
+ import { n as isScopeReadable, r as readScope, t as defineScope } from "./scope-CwpVSHVf.js";
2
+ //#region src/csp-nonce.ts
3
+ /** Lexical scope carrying the CSP nonce for the current render, if any. */
4
+ const CspNonceScope = defineScope(void 0);
5
+ /**
6
+ * Read the CSP nonce for the current render from {@link CspNonceScope}.
7
+ *
8
+ * @returns The active nonce, or `undefined` when called outside of a
9
+ * component render or when no nonce was configured.
10
+ */
11
+ function cspNonce() {
12
+ return isScopeReadable() ? readScope(CspNonceScope) : void 0;
13
+ }
14
+ function validateCspNonce(value) {
15
+ if (value === void 0) return void 0;
16
+ const invalid = () => {
17
+ throw new TypeError("CSP nonce must be valid base64 or base64url encoding at least 16 bytes.");
18
+ };
19
+ if (value.length === 0 || !/^[A-Za-z0-9+/_-]+={0,2}$/.test(value) || value.length % 4 === 1 || /=/.test(value.slice(0, -2))) invalid();
20
+ const normalized = value.replace(/-/g, "+").replace(/_/g, "/");
21
+ let end = normalized.length;
22
+ while (end > 0 && normalized[end - 1] === "=") end--;
23
+ const unpadded = normalized.slice(0, end);
24
+ const padded = unpadded.padEnd(Math.ceil(unpadded.length / 4) * 4, "=");
25
+ let decoded = "";
26
+ try {
27
+ decoded = atob(padded);
28
+ } catch {
29
+ invalid();
30
+ }
31
+ if (decoded.length < 16) invalid();
32
+ return value;
33
+ }
34
+ //#endregion
35
+ export { cspNonce as n, validateCspNonce as r, CspNonceScope as t };
@@ -1,4 +1,4 @@
1
- import { i as getDefaultDataRuntime, t as createDataRuntime } from "../data-runtime-CxoY98lX.js";
2
- import { c as createMutation, d as queryScope, l as invalidate, n as createQuery, t as createQueryCollection, u as invalidateOnInterval } from "../data-2edW0jhF.js";
3
- import { a as hydrateDataRuntime, i as dehydrateDataRuntime, n as defineQuery, o as prefetchQuery, r as defineServerQueries, s as serveQuery, t as createQueryPrefetchContext } from "../query-registry-LrYpQsDJ.js";
1
+ import { a as hydrateDataRuntime, i as dehydrateDataRuntime, n as defineQuery, o as prefetchQuery, r as defineServerQueries, s as serveQuery, t as createQueryPrefetchContext } from "../query-registry-WMCN0BJL.js";
2
+ import { a as getDefaultDataRuntime, t as createDataRuntime } from "../data-runtime-C1bFAlba.js";
3
+ import { c as createMutation, d as queryScope, l as invalidate, n as createQuery, t as createQueryCollection, u as invalidateOnInterval } from "../data-BCwClJiF.js";
4
4
  export { createDataRuntime, createMutation, createQuery, createQueryCollection, createQueryPrefetchContext, defineQuery, defineServerQueries, dehydrateDataRuntime, getDefaultDataRuntime, hydrateDataRuntime, invalidate, invalidateOnInterval, prefetchQuery, queryScope, serveQuery };