@askrjs/askr 0.0.35 → 0.0.36

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 (146) hide show
  1. package/dist/bench/components/benchmark-row.js.map +1 -1
  2. package/dist/bench/components/benchmark-table.js.map +1 -1
  3. package/dist/benchmark.js +2 -2
  4. package/dist/benchmark.js.map +1 -1
  5. package/dist/bin/askr-ssg.js.map +1 -1
  6. package/dist/boot/index.d.ts.map +1 -1
  7. package/dist/boot/index.js +9 -29
  8. package/dist/boot/index.js.map +1 -1
  9. package/dist/common/control.js.map +1 -1
  10. package/dist/common/env.js.map +1 -1
  11. package/dist/common/ssr-errors.js.map +1 -1
  12. package/dist/common/vnode.js.map +1 -1
  13. package/dist/components/error-boundary.js.map +1 -1
  14. package/dist/components/link.js.map +1 -1
  15. package/dist/control/case.js.map +1 -1
  16. package/dist/control/for.js.map +1 -1
  17. package/dist/control/shared.js.map +1 -1
  18. package/dist/control/show.js.map +1 -1
  19. package/dist/data/index.d.ts.map +1 -1
  20. package/dist/data/index.js +165 -23
  21. package/dist/data/index.js.map +1 -1
  22. package/dist/dev/invariant.js.map +1 -1
  23. package/dist/dev/logger.js.map +1 -1
  24. package/dist/foundations/icon/icon.js.map +1 -1
  25. package/dist/foundations/interactions/dismissable.js.map +1 -1
  26. package/dist/foundations/interactions/focusable.js.map +1 -1
  27. package/dist/foundations/interactions/hoverable.js.map +1 -1
  28. package/dist/foundations/interactions/interaction-policy.js.map +1 -1
  29. package/dist/foundations/interactions/pressable.js.map +1 -1
  30. package/dist/foundations/interactions/roving-focus.js.map +1 -1
  31. package/dist/foundations/state/controllable.js.map +1 -1
  32. package/dist/foundations/structures/collection.js.map +1 -1
  33. package/dist/foundations/structures/layer.js.map +1 -1
  34. package/dist/foundations/structures/layout.js.map +1 -1
  35. package/dist/foundations/structures/portal.js.map +1 -1
  36. package/dist/foundations/structures/presence.js.map +1 -1
  37. package/dist/foundations/structures/slot.js.map +1 -1
  38. package/dist/foundations/utilities/aria.js.map +1 -1
  39. package/dist/foundations/utilities/compose-handlers.js.map +1 -1
  40. package/dist/foundations/utilities/compose-ref.js.map +1 -1
  41. package/dist/foundations/utilities/merge-props.js.map +1 -1
  42. package/dist/foundations/utilities/use-id.js.map +1 -1
  43. package/dist/fx/fx.js.map +1 -1
  44. package/dist/fx/timing.js.map +1 -1
  45. package/dist/index.d.ts +2 -2
  46. package/dist/index.js +2 -2
  47. package/dist/index.js.map +1 -1
  48. package/dist/jsx/utils.js.map +1 -1
  49. package/dist/jsx-dev-runtime.js.map +1 -1
  50. package/dist/jsx-runtime.js.map +1 -1
  51. package/dist/renderer/children.js.map +1 -1
  52. package/dist/renderer/cleanup.js.map +1 -1
  53. package/dist/renderer/dom.js +128 -53
  54. package/dist/renderer/dom.js.map +1 -1
  55. package/dist/renderer/evaluate.js +7 -0
  56. package/dist/renderer/evaluate.js.map +1 -1
  57. package/dist/renderer/fastpath.js.map +1 -1
  58. package/dist/renderer/index.js.map +1 -1
  59. package/dist/renderer/keyed.js +1 -1
  60. package/dist/renderer/keyed.js.map +1 -1
  61. package/dist/renderer/reconcile.js.map +1 -1
  62. package/dist/renderer/utils.js.map +1 -1
  63. package/dist/router/index.d.ts +2 -2
  64. package/dist/router/index.js +2 -2
  65. package/dist/router/match.js.map +1 -1
  66. package/dist/router/navigate.d.ts.map +1 -1
  67. package/dist/router/navigate.js +4 -2
  68. package/dist/router/navigate.js.map +1 -1
  69. package/dist/router/policy.js.map +1 -1
  70. package/dist/router/route-context.js.map +1 -1
  71. package/dist/router/route.d.ts +1 -1
  72. package/dist/router/route.d.ts.map +1 -1
  73. package/dist/router/route.js +3 -34
  74. package/dist/router/route.js.map +1 -1
  75. package/dist/runtime/child-scope.d.ts +1 -0
  76. package/dist/runtime/child-scope.d.ts.map +1 -1
  77. package/dist/runtime/child-scope.js +2 -0
  78. package/dist/runtime/child-scope.js.map +1 -1
  79. package/dist/runtime/component.js +1 -1
  80. package/dist/runtime/component.js.map +1 -1
  81. package/dist/runtime/context.js.map +1 -1
  82. package/dist/runtime/control.js.map +1 -1
  83. package/dist/runtime/derive.d.ts +2 -1
  84. package/dist/runtime/derive.d.ts.map +1 -1
  85. package/dist/runtime/derive.js +15 -10
  86. package/dist/runtime/derive.js.map +1 -1
  87. package/dist/runtime/dev-namespace.js.map +1 -1
  88. package/dist/runtime/effect.d.ts.map +1 -1
  89. package/dist/runtime/effect.js +34 -9
  90. package/dist/runtime/effect.js.map +1 -1
  91. package/dist/runtime/events.js.map +1 -1
  92. package/dist/runtime/execution-model.js.map +1 -1
  93. package/dist/runtime/fastlane.js.map +1 -1
  94. package/dist/runtime/for-bench.d.ts +1 -1
  95. package/dist/runtime/for-bench.d.ts.map +1 -1
  96. package/dist/runtime/for-bench.js +15 -14
  97. package/dist/runtime/for-bench.js.map +1 -1
  98. package/dist/runtime/for.d.ts +1 -0
  99. package/dist/runtime/for.d.ts.map +1 -1
  100. package/dist/runtime/for.js +63 -16
  101. package/dist/runtime/for.js.map +1 -1
  102. package/dist/runtime/operations.d.ts.map +1 -1
  103. package/dist/runtime/operations.js +9 -7
  104. package/dist/runtime/operations.js.map +1 -1
  105. package/dist/runtime/perf-metrics.js +8 -2
  106. package/dist/runtime/perf-metrics.js.map +1 -1
  107. package/dist/runtime/readable.js.map +1 -1
  108. package/dist/runtime/resource-cell.js +3 -2
  109. package/dist/runtime/resource-cell.js.map +1 -1
  110. package/dist/runtime/scheduler.d.ts +0 -1
  111. package/dist/runtime/scheduler.d.ts.map +1 -1
  112. package/dist/runtime/scheduler.js +6 -19
  113. package/dist/runtime/scheduler.js.map +1 -1
  114. package/dist/runtime/selector.d.ts.map +1 -1
  115. package/dist/runtime/selector.js +6 -4
  116. package/dist/runtime/selector.js.map +1 -1
  117. package/dist/runtime/snapshot-source.d.ts +10 -0
  118. package/dist/runtime/snapshot-source.d.ts.map +1 -0
  119. package/dist/runtime/snapshot-source.js +17 -0
  120. package/dist/runtime/snapshot-source.js.map +1 -0
  121. package/dist/runtime/ssr-bridge.js.map +1 -1
  122. package/dist/runtime/state.d.ts.map +1 -1
  123. package/dist/runtime/state.js +1 -5
  124. package/dist/runtime/state.js.map +1 -1
  125. package/dist/ssg/batch-render.js.map +1 -1
  126. package/dist/ssg/create-static-gen.js.map +1 -1
  127. package/dist/ssg/generate-metadata.js.map +1 -1
  128. package/dist/ssg/incremental-manifest.js.map +1 -1
  129. package/dist/ssg/resolve-ssg-data.js.map +1 -1
  130. package/dist/ssg/route-utils.js.map +1 -1
  131. package/dist/ssg/write-static-files.js.map +1 -1
  132. package/dist/ssr/attrs.js.map +1 -1
  133. package/dist/ssr/context.d.ts +1 -0
  134. package/dist/ssr/context.d.ts.map +1 -1
  135. package/dist/ssr/context.js +3 -0
  136. package/dist/ssr/context.js.map +1 -1
  137. package/dist/ssr/escape.js +56 -16
  138. package/dist/ssr/escape.js.map +1 -1
  139. package/dist/ssr/index.d.ts.map +1 -1
  140. package/dist/ssr/index.js +15 -3
  141. package/dist/ssr/index.js.map +1 -1
  142. package/dist/ssr/render-keys.js.map +1 -1
  143. package/dist/ssr/sink.js.map +1 -1
  144. package/dist/ssr/stream-render.js +14 -2
  145. package/dist/ssr/stream-render.js.map +1 -1
  146. package/package.json +8 -6
@@ -1,9 +1,67 @@
1
1
  import { globalScheduler } from "../runtime/scheduler.js";
2
2
  import { markReactivePropsDirtySource, markReadableDerivedSubscribersDirty, notifyReadableReaders, recordReadableRead } from "../runtime/readable.js";
3
+ import { claimHookIndex, getCurrentComponentInstance } from "../runtime/component.js";
4
+ import { getRenderContext } from "../ssr/context.js";
3
5
  //#region src/data/index.ts
6
+ const RECONCILE_MAX_ATTEMPTS = 3;
7
+ const RECONCILE_RETRY_DELAY_MS = 25;
8
+ const globalQueryCache = /* @__PURE__ */ new Map();
9
+ const querySlotsByInstance = /* @__PURE__ */ new WeakMap();
10
+ const mutationSlotsByInstance = /* @__PURE__ */ new WeakMap();
11
+ const queryCleanupRegistered = /* @__PURE__ */ new WeakSet();
12
+ const mutationCleanupRegistered = /* @__PURE__ */ new WeakSet();
4
13
  function createReadableSource() {
5
14
  return (() => void 0);
6
15
  }
16
+ function getQueryCache() {
17
+ return getRenderContext()?.queryCache ?? globalQueryCache;
18
+ }
19
+ function getQuerySlotStore(instance) {
20
+ let store = querySlotsByInstance.get(instance);
21
+ if (!store) {
22
+ store = /* @__PURE__ */ new Map();
23
+ querySlotsByInstance.set(instance, store);
24
+ }
25
+ return store;
26
+ }
27
+ function getMutationSlotStore(instance) {
28
+ let store = mutationSlotsByInstance.get(instance);
29
+ if (!store) {
30
+ store = /* @__PURE__ */ new Map();
31
+ mutationSlotsByInstance.set(instance, store);
32
+ }
33
+ return store;
34
+ }
35
+ function ensureQueryCleanup(instance) {
36
+ if (queryCleanupRegistered.has(instance)) return;
37
+ queryCleanupRegistered.add(instance);
38
+ instance.cleanupFns.push(() => {
39
+ const slots = querySlotsByInstance.get(instance);
40
+ if (!slots) {
41
+ queryCleanupRegistered.delete(instance);
42
+ return;
43
+ }
44
+ for (const [hookIndex, slot] of slots) slot.cell.detach(instance, hookIndex);
45
+ slots.clear();
46
+ querySlotsByInstance.delete(instance);
47
+ queryCleanupRegistered.delete(instance);
48
+ });
49
+ }
50
+ function ensureMutationCleanup(instance) {
51
+ if (mutationCleanupRegistered.has(instance)) return;
52
+ mutationCleanupRegistered.add(instance);
53
+ instance.cleanupFns.push(() => {
54
+ const slots = mutationSlotsByInstance.get(instance);
55
+ if (!slots) {
56
+ mutationCleanupRegistered.delete(instance);
57
+ return;
58
+ }
59
+ for (const cell of slots.values()) cell.abort();
60
+ slots.clear();
61
+ mutationSlotsByInstance.delete(instance);
62
+ mutationCleanupRegistered.delete(instance);
63
+ });
64
+ }
7
65
  function notifySource(source) {
8
66
  markReadableDerivedSubscribersDirty(source);
9
67
  markReactivePropsDirtySource(source);
@@ -12,22 +70,26 @@ function notifySource(source) {
12
70
  function isAbortError(error, signal) {
13
71
  return signal.aborted || error instanceof Error && error.name === "AbortError" || typeof DOMException !== "undefined" && error instanceof DOMException && error.name === "AbortError";
14
72
  }
15
- const queryCache = /* @__PURE__ */ new Map();
16
73
  function invalidateQueries(prefix, markPendingWrite) {
17
- for (const [key, query] of queryCache) {
74
+ const cache = getQueryCache();
75
+ for (const [key, query] of cache) {
18
76
  if (!key.startsWith(prefix)) continue;
19
77
  if (markPendingWrite) query.markPendingWrite();
20
78
  query.refresh();
21
79
  }
22
80
  }
23
81
  var QueryCell = class {
24
- constructor(options) {
82
+ constructor(options, key, cache) {
25
83
  this.source = createReadableSource();
26
84
  this.controller = null;
27
85
  this.generation = 0;
28
86
  this.startQueued = false;
29
87
  this.pendingRefresh = null;
88
+ this.pendingRefreshResolve = null;
30
89
  this.reconcileAttemptCount = 0;
90
+ this.destroyed = false;
91
+ this.ownerCount = 0;
92
+ this.owners = /* @__PURE__ */ new Map();
31
93
  this.state = {
32
94
  data: null,
33
95
  error: null,
@@ -37,9 +99,37 @@ var QueryCell = class {
37
99
  consistency: "fresh"
38
100
  };
39
101
  this.options = options;
102
+ this.key = key;
103
+ this.cache = cache;
40
104
  }
41
- setOptions(options) {
42
- this.options = options;
105
+ attach(instance, hookIndex) {
106
+ let hooks = this.owners.get(instance);
107
+ if (!hooks) {
108
+ hooks = /* @__PURE__ */ new Set();
109
+ this.owners.set(instance, hooks);
110
+ }
111
+ if (hooks.has(hookIndex)) return;
112
+ hooks.add(hookIndex);
113
+ this.ownerCount += 1;
114
+ }
115
+ detach(instance, hookIndex) {
116
+ const hooks = this.owners.get(instance);
117
+ if (!hooks || !hooks.delete(hookIndex)) return;
118
+ this.ownerCount -= 1;
119
+ if (hooks.size === 0) this.owners.delete(instance);
120
+ if (this.ownerCount <= 0) this.destroy();
121
+ }
122
+ destroy() {
123
+ if (this.destroyed) return;
124
+ this.destroyed = true;
125
+ this.controller?.abort();
126
+ this.controller = null;
127
+ this.startQueued = false;
128
+ this.reconcileAttemptCount = 0;
129
+ this.ownerCount = 0;
130
+ this.owners.clear();
131
+ this.cache.delete(this.key);
132
+ this.finishPendingRefresh();
43
133
  }
44
134
  get data() {
45
135
  recordReadableRead(this.source);
@@ -66,15 +156,17 @@ var QueryCell = class {
66
156
  return this.state.consistency;
67
157
  }
68
158
  ensureStarted() {
69
- if (this.state.data !== null || this.pendingRefresh || this.startQueued) return;
159
+ if (this.destroyed || this.state.data !== null || this.pendingRefresh || this.startQueued) return;
70
160
  this.queueStart("initial");
71
161
  }
72
162
  refresh() {
163
+ if (this.destroyed) return Promise.resolve();
73
164
  if (this.pendingRefresh) return this.pendingRefresh;
74
165
  this.queueStart("manual");
75
166
  return this.pendingRefresh ?? Promise.resolve();
76
167
  }
77
168
  markPendingWrite() {
169
+ if (this.destroyed) return;
78
170
  this.setState({
79
171
  refreshing: true,
80
172
  stale: true,
@@ -82,18 +174,30 @@ var QueryCell = class {
82
174
  });
83
175
  }
84
176
  queueStart(reason) {
177
+ if (this.destroyed) return;
85
178
  this.startQueued = true;
86
179
  this.pendingRefresh = new Promise((resolve) => {
180
+ this.pendingRefreshResolve = resolve;
87
181
  globalScheduler.enqueue(() => {
88
182
  this.startQueued = false;
183
+ if (this.destroyed) {
184
+ this.finishPendingRefresh();
185
+ return;
186
+ }
89
187
  this.start(reason).finally(() => {
90
- this.pendingRefresh = null;
91
- resolve();
188
+ this.finishPendingRefresh();
92
189
  });
93
190
  });
94
191
  });
95
192
  }
193
+ finishPendingRefresh() {
194
+ const resolve = this.pendingRefreshResolve;
195
+ this.pendingRefresh = null;
196
+ this.pendingRefreshResolve = null;
197
+ resolve?.();
198
+ }
96
199
  setState(next) {
200
+ if (this.destroyed) return;
97
201
  this.state = {
98
202
  ...this.state,
99
203
  ...next
@@ -101,6 +205,7 @@ var QueryCell = class {
101
205
  notifySource(this.source);
102
206
  }
103
207
  async start(reason) {
208
+ if (this.destroyed) return;
104
209
  this.generation += 1;
105
210
  const generation = this.generation;
106
211
  this.controller?.abort();
@@ -118,7 +223,7 @@ var QueryCell = class {
118
223
  try {
119
224
  nextData = await this.options.fetch({ signal: controller.signal });
120
225
  } catch (error) {
121
- if (this.generation !== generation || this.controller !== controller) return;
226
+ if (this.destroyed || this.generation !== generation || this.controller !== controller) return;
122
227
  if (isAbortError(error, controller.signal)) {
123
228
  this.setState({
124
229
  loading: false,
@@ -135,7 +240,7 @@ var QueryCell = class {
135
240
  });
136
241
  return;
137
242
  }
138
- if (this.generation !== generation || this.controller !== controller) return;
243
+ if (this.destroyed || this.generation !== generation || this.controller !== controller) return;
139
244
  if (!(this.options.isConsistent?.(nextData) ?? true)) {
140
245
  this.setState({
141
246
  data: nextData,
@@ -158,17 +263,17 @@ var QueryCell = class {
158
263
  });
159
264
  }
160
265
  async reconcile(data) {
161
- if (!(this.options.reconcile?.(data, { key: this.options.key }) ?? false)) return;
266
+ if (!(this.options.reconcile?.(data, { key: this.options.key }) ?? false) || this.destroyed) return;
162
267
  this.reconcileAttemptCount += 1;
163
- if (this.reconcileAttemptCount > 3) {
268
+ if (this.reconcileAttemptCount > RECONCILE_MAX_ATTEMPTS) {
164
269
  this.setState({
165
270
  consistency: "stale",
166
271
  refreshing: false
167
272
  });
168
273
  return;
169
274
  }
170
- await new Promise((resolve) => setTimeout(resolve, 25));
171
- if (this.state.consistency === "fresh") return;
275
+ await new Promise((resolve) => setTimeout(resolve, RECONCILE_RETRY_DELAY_MS));
276
+ if (this.destroyed || this.state.consistency === "fresh") return;
172
277
  await this.refresh();
173
278
  }
174
279
  };
@@ -186,6 +291,11 @@ var MutationCell = class {
186
291
  this.affects = options.affects;
187
292
  this.afterSuccess = options.afterSuccess;
188
293
  }
294
+ setOptions(options) {
295
+ this.action = options.action;
296
+ this.affects = options.affects;
297
+ this.afterSuccess = options.afterSuccess;
298
+ }
189
299
  get status() {
190
300
  recordReadableRead(this.source);
191
301
  return this.state.status;
@@ -271,21 +381,53 @@ var MutationCell = class {
271
381
  }
272
382
  };
273
383
  function createQuery(options) {
274
- const existing = queryCache.get(options.key);
275
- if (existing) {
276
- existing.setOptions(options);
277
- return existing;
384
+ const instance = getCurrentComponentInstance();
385
+ const cache = getQueryCache();
386
+ if (!instance) {
387
+ let cell = cache.get(options.key);
388
+ if (!cell) {
389
+ cell = new QueryCell(options, options.key, cache);
390
+ cache.set(options.key, cell);
391
+ cell.ensureStarted();
392
+ }
393
+ return cell;
278
394
  }
279
- const created = new QueryCell(options);
280
- queryCache.set(options.key, created);
281
- created.ensureStarted();
282
- return created;
395
+ const hookIndex = claimHookIndex(instance, "query");
396
+ ensureQueryCleanup(instance);
397
+ const slotStore = getQuerySlotStore(instance);
398
+ const existingSlot = slotStore.get(hookIndex);
399
+ if (existingSlot && existingSlot.key === options.key) return existingSlot.cell;
400
+ if (existingSlot) existingSlot.cell.detach(instance, hookIndex);
401
+ let cell = cache.get(options.key);
402
+ if (!cell) {
403
+ cell = new QueryCell(options, options.key, cache);
404
+ cache.set(options.key, cell);
405
+ cell.ensureStarted();
406
+ }
407
+ slotStore.set(hookIndex, {
408
+ key: options.key,
409
+ cell
410
+ });
411
+ cell.attach(instance, hookIndex);
412
+ return cell;
283
413
  }
284
414
  function invalidate(prefix) {
285
415
  invalidateQueries(prefix, false);
286
416
  }
287
417
  function createMutation(options) {
288
- return new MutationCell(options);
418
+ const instance = getCurrentComponentInstance();
419
+ if (!instance) return new MutationCell(options);
420
+ const hookIndex = claimHookIndex(instance, "mutation");
421
+ ensureMutationCleanup(instance);
422
+ const slotStore = getMutationSlotStore(instance);
423
+ const existing = slotStore.get(hookIndex);
424
+ if (existing) {
425
+ existing.setOptions(options);
426
+ return existing;
427
+ }
428
+ const created = new MutationCell(options);
429
+ slotStore.set(hookIndex, created);
430
+ return created;
289
431
  }
290
432
  //#endregion
291
433
  export { createMutation, createQuery, invalidate };
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../src/data/index.ts"],"sourcesContent":["import { globalScheduler } from '../runtime/scheduler';\nimport {\n markReadableDerivedSubscribersDirty,\n markReactivePropsDirtySource,\n notifyReadableReaders,\n recordReadableRead,\n type ReadableSource,\n} from '../runtime/readable';\n\nexport type QueryConsistency =\n | 'fresh'\n | 'stale'\n | 'refreshing'\n | 'pending-write';\n\nexport type Query<T> = {\n data: T | null;\n error: unknown | null;\n loading: boolean;\n refreshing: boolean;\n stale: boolean;\n consistency: QueryConsistency;\n\n refresh(): Promise<void>;\n};\n\nexport type Mutation<TInput, TResult> = {\n status: 'idle' | 'pending' | 'success' | 'error';\n pending: boolean;\n error: unknown | null;\n result: TResult | null;\n\n execute(input: TInput): Promise<TResult>;\n abort(): void;\n reset(): void;\n};\n\ntype QueryOptions<T> = {\n key: string;\n fetch: (ctx: { signal: AbortSignal }) => Promise<T>;\n isConsistent?: (data: T) => boolean;\n reconcile?: (data: T, ctx: { key: string }) => Promise<boolean> | boolean;\n};\n\ntype MutationOptions<TInput, TResult> = {\n action: (input: TInput, ctx: { signal: AbortSignal }) => Promise<TResult>;\n affects?: (input: TInput, result: TResult) => string[];\n afterSuccess?: 'invalidate';\n};\n\ntype QueryState<T> = {\n data: T | null;\n error: unknown | null;\n loading: boolean;\n refreshing: boolean;\n stale: boolean;\n consistency: QueryConsistency;\n};\n\ntype MutationState<TResult> = {\n status: 'idle' | 'pending' | 'success' | 'error';\n error: unknown | null;\n result: TResult | null;\n};\n\nfunction createReadableSource(): ReadableSource<unknown> {\n return (() => undefined) as ReadableSource<unknown>;\n}\n\nfunction notifySource(source: ReadableSource<unknown>): void {\n markReadableDerivedSubscribersDirty(source);\n markReactivePropsDirtySource(source);\n notifyReadableReaders(source);\n}\n\nfunction isAbortError(error: unknown, signal: AbortSignal): boolean {\n return (\n signal.aborted ||\n (error instanceof Error && error.name === 'AbortError') ||\n (typeof DOMException !== 'undefined' &&\n error instanceof DOMException &&\n error.name === 'AbortError')\n );\n}\n\nconst queryCache = new Map<string, QueryCell<unknown>>();\n\nfunction invalidateQueries(prefix: string, markPendingWrite: boolean): void {\n for (const [key, query] of queryCache) {\n if (!key.startsWith(prefix)) {\n continue;\n }\n\n if (markPendingWrite) {\n query.markPendingWrite();\n }\n\n void query.refresh();\n }\n}\n\nclass QueryCell<T> implements Query<T> {\n private readonly source = createReadableSource();\n private options: QueryOptions<T>;\n private controller: AbortController | null = null;\n private generation = 0;\n private startQueued = false;\n private pendingRefresh: Promise<void> | null = null;\n private reconcileAttemptCount = 0;\n\n private state: QueryState<T> = {\n data: null,\n error: null,\n loading: true,\n refreshing: false,\n stale: false,\n consistency: 'fresh',\n };\n\n constructor(options: QueryOptions<T>) {\n this.options = options;\n }\n\n setOptions(options: QueryOptions<T>): void {\n this.options = options;\n }\n\n get data(): T | null {\n recordReadableRead(this.source);\n return this.state.data;\n }\n\n get error(): unknown | null {\n recordReadableRead(this.source);\n return this.state.error;\n }\n\n get loading(): boolean {\n recordReadableRead(this.source);\n return this.state.loading;\n }\n\n get refreshing(): boolean {\n recordReadableRead(this.source);\n return this.state.refreshing;\n }\n\n get stale(): boolean {\n recordReadableRead(this.source);\n return this.state.stale;\n }\n\n get consistency(): QueryConsistency {\n recordReadableRead(this.source);\n return this.state.consistency;\n }\n\n ensureStarted(): void {\n if (this.state.data !== null || this.pendingRefresh || this.startQueued) {\n return;\n }\n\n this.queueStart('initial');\n }\n\n refresh(): Promise<void> {\n if (this.pendingRefresh) {\n return this.pendingRefresh;\n }\n\n this.queueStart('manual');\n return this.pendingRefresh ?? Promise.resolve();\n }\n\n markPendingWrite(): void {\n this.setState({\n refreshing: true,\n stale: true,\n consistency: 'pending-write',\n });\n }\n\n private queueStart(\n reason: 'initial' | 'manual' | 'invalidate' | 'pending-write'\n ): void {\n this.startQueued = true;\n this.pendingRefresh = new Promise<void>((resolve) => {\n globalScheduler.enqueue(() => {\n this.startQueued = false;\n void this.start(reason).finally(() => {\n this.pendingRefresh = null;\n resolve();\n });\n });\n });\n }\n\n private setState(next: Partial<QueryState<T>>): void {\n this.state = {\n ...this.state,\n ...next,\n };\n notifySource(this.source);\n }\n\n private async start(\n reason: 'initial' | 'manual' | 'invalidate' | 'pending-write'\n ): Promise<void> {\n this.generation += 1;\n const generation = this.generation;\n\n this.controller?.abort();\n const controller = new AbortController();\n this.controller = controller;\n\n const hasData = this.state.data !== null;\n this.setState({\n loading: !hasData,\n refreshing: hasData,\n stale: hasData,\n consistency:\n reason === 'pending-write'\n ? 'pending-write'\n : hasData\n ? 'refreshing'\n : 'fresh',\n error: null,\n });\n\n let nextData: T;\n try {\n nextData = await this.options.fetch({ signal: controller.signal });\n } catch (error) {\n if (this.generation !== generation || this.controller !== controller) {\n return;\n }\n\n if (isAbortError(error, controller.signal)) {\n this.setState({ loading: false, refreshing: false });\n return;\n }\n\n this.setState({\n loading: false,\n refreshing: false,\n stale: true,\n consistency: 'stale',\n error,\n });\n return;\n }\n\n if (this.generation !== generation || this.controller !== controller) {\n return;\n }\n\n const isConsistent = this.options.isConsistent?.(nextData) ?? true;\n if (!isConsistent) {\n this.setState({\n data: nextData,\n loading: false,\n refreshing: false,\n stale: true,\n consistency: 'stale',\n });\n await this.reconcile(nextData);\n return;\n }\n\n this.reconcileAttemptCount = 0;\n this.setState({\n data: nextData,\n loading: false,\n refreshing: false,\n stale: false,\n consistency: 'fresh',\n error: null,\n });\n }\n\n private async reconcile(data: T): Promise<void> {\n const shouldRetry =\n this.options.reconcile?.(data, { key: this.options.key }) ?? false;\n\n if (!shouldRetry) {\n return;\n }\n\n this.reconcileAttemptCount += 1;\n if (this.reconcileAttemptCount > 3) {\n this.setState({ consistency: 'stale', refreshing: false });\n return;\n }\n\n await new Promise<void>((resolve) => setTimeout(resolve, 25));\n if (this.state.consistency === 'fresh') {\n return;\n }\n\n await this.refresh();\n }\n}\n\nclass MutationCell<TInput, TResult> implements Mutation<TInput, TResult> {\n private readonly source = createReadableSource();\n private readonly action: MutationOptions<TInput, TResult>['action'];\n private readonly affects?: MutationOptions<TInput, TResult>['affects'];\n private readonly afterSuccess?: MutationOptions<\n TInput,\n TResult\n >['afterSuccess'];\n private controller: AbortController | null = null;\n private generation = 0;\n\n private state: MutationState<TResult> = {\n status: 'idle',\n error: null,\n result: null,\n };\n\n constructor(options: MutationOptions<TInput, TResult>) {\n this.action = options.action;\n this.affects = options.affects;\n this.afterSuccess = options.afterSuccess;\n }\n\n get status(): 'idle' | 'pending' | 'success' | 'error' {\n recordReadableRead(this.source);\n return this.state.status;\n }\n\n get pending(): boolean {\n recordReadableRead(this.source);\n return this.state.status === 'pending';\n }\n\n get error(): unknown | null {\n recordReadableRead(this.source);\n return this.state.error;\n }\n\n get result(): TResult | null {\n recordReadableRead(this.source);\n return this.state.result;\n }\n\n private setState(next: Partial<MutationState<TResult>>): void {\n this.state = {\n ...this.state,\n ...next,\n };\n notifySource(this.source);\n }\n\n async execute(input: TInput): Promise<TResult> {\n this.generation += 1;\n const generation = this.generation;\n\n this.controller?.abort();\n const controller = new AbortController();\n this.controller = controller;\n\n this.setState({ status: 'pending', error: null, result: null });\n\n let result: TResult;\n try {\n result = await this.action(input, { signal: controller.signal });\n } catch (error) {\n if (this.generation !== generation || this.controller !== controller) {\n throw error;\n }\n\n if (isAbortError(error, controller.signal)) {\n this.setState({ status: 'idle', error: null });\n throw error;\n }\n\n this.setState({ status: 'error', error });\n throw error;\n }\n\n if (this.generation !== generation || this.controller !== controller) {\n return result;\n }\n\n this.setState({ status: 'success', error: null, result });\n\n if (this.afterSuccess === 'invalidate') {\n const prefixes = this.affects?.(input, result) ?? [];\n for (const prefix of new Set(prefixes)) {\n invalidateQueries(prefix, true);\n }\n }\n\n return result;\n }\n\n abort(): void {\n this.generation += 1;\n this.controller?.abort();\n this.controller = null;\n this.setState({ status: 'idle', error: null });\n }\n\n reset(): void {\n this.generation += 1;\n this.controller?.abort();\n this.controller = null;\n this.setState({ status: 'idle', error: null, result: null });\n }\n}\n\nexport function createQuery<T>(options: QueryOptions<T>): Query<T> {\n const existing = queryCache.get(options.key) as QueryCell<T> | undefined;\n if (existing) {\n existing.setOptions(options);\n return existing;\n }\n\n const created = new QueryCell(options);\n queryCache.set(options.key, created as QueryCell<unknown>);\n created.ensureStarted();\n return created;\n}\n\nexport function invalidate(prefix: string): void {\n invalidateQueries(prefix, false);\n}\n\nexport function createMutation<TInput, TResult>(\n options: MutationOptions<TInput, TResult>\n): Mutation<TInput, TResult> {\n return new MutationCell(options);\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../src/data/index.ts"],"sourcesContent":["import { globalScheduler } from '../runtime/scheduler';\nimport {\n claimHookIndex,\n getCurrentComponentInstance,\n type ComponentInstance,\n} from '../runtime/component';\nimport {\n markReadableDerivedSubscribersDirty,\n markReactivePropsDirtySource,\n notifyReadableReaders,\n recordReadableRead,\n type ReadableSource,\n} from '../runtime/readable';\nimport { getRenderContext } from '../ssr/context';\n\nexport type QueryConsistency =\n | 'fresh'\n | 'stale'\n | 'refreshing'\n | 'pending-write';\n\nexport type Query<T> = {\n data: T | null;\n error: unknown | null;\n loading: boolean;\n refreshing: boolean;\n stale: boolean;\n consistency: QueryConsistency;\n\n refresh(): Promise<void>;\n};\n\nexport type Mutation<TInput, TResult> = {\n status: 'idle' | 'pending' | 'success' | 'error';\n pending: boolean;\n error: unknown | null;\n result: TResult | null;\n\n execute(input: TInput): Promise<TResult>;\n abort(): void;\n reset(): void;\n};\n\ntype QueryOptions<T> = {\n key: string;\n fetch: (ctx: { signal: AbortSignal }) => Promise<T>;\n isConsistent?: (data: T) => boolean;\n reconcile?: (data: T, ctx: { key: string }) => Promise<boolean> | boolean;\n};\n\ntype MutationOptions<TInput, TResult> = {\n action: (input: TInput, ctx: { signal: AbortSignal }) => Promise<TResult>;\n affects?: (input: TInput, result: TResult) => string[];\n afterSuccess?: 'invalidate';\n};\n\ntype QueryState<T> = {\n data: T | null;\n error: unknown | null;\n loading: boolean;\n refreshing: boolean;\n stale: boolean;\n consistency: QueryConsistency;\n};\n\ntype MutationState<TResult> = {\n status: 'idle' | 'pending' | 'success' | 'error';\n error: unknown | null;\n result: TResult | null;\n};\n\ntype QuerySlot = {\n key: string;\n cell: QueryCell<unknown>;\n};\n\nconst RECONCILE_MAX_ATTEMPTS = 3;\nconst RECONCILE_RETRY_DELAY_MS = 25;\nconst globalQueryCache = new Map<string, QueryCell<unknown>>();\nconst querySlotsByInstance = new WeakMap<\n ComponentInstance,\n Map<number, QuerySlot>\n>();\nconst mutationSlotsByInstance = new WeakMap<\n ComponentInstance,\n Map<number, MutationCell<unknown, unknown>>\n>();\nconst queryCleanupRegistered = new WeakSet<ComponentInstance>();\nconst mutationCleanupRegistered = new WeakSet<ComponentInstance>();\n\nfunction createReadableSource(): ReadableSource<unknown> {\n return (() => undefined) as ReadableSource<unknown>;\n}\n\nfunction getQueryCache(): Map<string, QueryCell<unknown>> {\n return (\n (getRenderContext()?.queryCache as Map<\n string,\n QueryCell<unknown>\n > | null) ?? globalQueryCache\n );\n}\n\nfunction getQuerySlotStore(\n instance: ComponentInstance\n): Map<number, QuerySlot> {\n let store = querySlotsByInstance.get(instance);\n if (!store) {\n store = new Map();\n querySlotsByInstance.set(instance, store);\n }\n return store;\n}\n\nfunction getMutationSlotStore(\n instance: ComponentInstance\n): Map<number, MutationCell<unknown, unknown>> {\n let store = mutationSlotsByInstance.get(instance);\n if (!store) {\n store = new Map();\n mutationSlotsByInstance.set(instance, store);\n }\n return store;\n}\n\nfunction ensureQueryCleanup(instance: ComponentInstance): void {\n if (queryCleanupRegistered.has(instance)) {\n return;\n }\n\n queryCleanupRegistered.add(instance);\n instance.cleanupFns.push(() => {\n const slots = querySlotsByInstance.get(instance);\n if (!slots) {\n queryCleanupRegistered.delete(instance);\n return;\n }\n\n for (const [hookIndex, slot] of slots) {\n slot.cell.detach(instance, hookIndex);\n }\n\n slots.clear();\n querySlotsByInstance.delete(instance);\n queryCleanupRegistered.delete(instance);\n });\n}\n\nfunction ensureMutationCleanup(instance: ComponentInstance): void {\n if (mutationCleanupRegistered.has(instance)) {\n return;\n }\n\n mutationCleanupRegistered.add(instance);\n instance.cleanupFns.push(() => {\n const slots = mutationSlotsByInstance.get(instance);\n if (!slots) {\n mutationCleanupRegistered.delete(instance);\n return;\n }\n\n for (const cell of slots.values()) {\n cell.abort();\n }\n\n slots.clear();\n mutationSlotsByInstance.delete(instance);\n mutationCleanupRegistered.delete(instance);\n });\n}\n\nfunction notifySource(source: ReadableSource<unknown>): void {\n markReadableDerivedSubscribersDirty(source);\n markReactivePropsDirtySource(source);\n notifyReadableReaders(source);\n}\n\nfunction isAbortError(error: unknown, signal: AbortSignal): boolean {\n return (\n signal.aborted ||\n (error instanceof Error && error.name === 'AbortError') ||\n (typeof DOMException !== 'undefined' &&\n error instanceof DOMException &&\n error.name === 'AbortError')\n );\n}\n\nfunction invalidateQueries(prefix: string, markPendingWrite: boolean): void {\n const cache = getQueryCache();\n\n for (const [key, query] of cache) {\n if (!key.startsWith(prefix)) {\n continue;\n }\n\n if (markPendingWrite) {\n query.markPendingWrite();\n }\n\n void query.refresh();\n }\n}\n\nclass QueryCell<T> implements Query<T> {\n private readonly source = createReadableSource();\n private readonly key: string;\n private readonly cache: Map<string, QueryCell<unknown>>;\n private options: QueryOptions<T>;\n private controller: AbortController | null = null;\n private generation = 0;\n private startQueued = false;\n private pendingRefresh: Promise<void> | null = null;\n private pendingRefreshResolve: (() => void) | null = null;\n private reconcileAttemptCount = 0;\n private destroyed = false;\n private ownerCount = 0;\n private readonly owners = new Map<ComponentInstance, Set<number>>();\n\n private state: QueryState<T> = {\n data: null,\n error: null,\n loading: true,\n refreshing: false,\n stale: false,\n consistency: 'fresh',\n };\n\n constructor(\n options: QueryOptions<T>,\n key: string,\n cache: Map<string, QueryCell<unknown>>\n ) {\n this.options = options;\n this.key = key;\n this.cache = cache;\n }\n\n attach(instance: ComponentInstance, hookIndex: number): void {\n let hooks = this.owners.get(instance);\n if (!hooks) {\n hooks = new Set();\n this.owners.set(instance, hooks);\n }\n\n if (hooks.has(hookIndex)) {\n return;\n }\n\n hooks.add(hookIndex);\n this.ownerCount += 1;\n }\n\n detach(instance: ComponentInstance, hookIndex: number): void {\n const hooks = this.owners.get(instance);\n if (!hooks || !hooks.delete(hookIndex)) {\n return;\n }\n\n this.ownerCount -= 1;\n if (hooks.size === 0) {\n this.owners.delete(instance);\n }\n\n if (this.ownerCount <= 0) {\n this.destroy();\n }\n }\n\n private destroy(): void {\n if (this.destroyed) {\n return;\n }\n\n this.destroyed = true;\n this.controller?.abort();\n this.controller = null;\n this.startQueued = false;\n this.reconcileAttemptCount = 0;\n this.ownerCount = 0;\n this.owners.clear();\n this.cache.delete(this.key);\n this.finishPendingRefresh();\n }\n\n get data(): T | null {\n recordReadableRead(this.source);\n return this.state.data;\n }\n\n get error(): unknown | null {\n recordReadableRead(this.source);\n return this.state.error;\n }\n\n get loading(): boolean {\n recordReadableRead(this.source);\n return this.state.loading;\n }\n\n get refreshing(): boolean {\n recordReadableRead(this.source);\n return this.state.refreshing;\n }\n\n get stale(): boolean {\n recordReadableRead(this.source);\n return this.state.stale;\n }\n\n get consistency(): QueryConsistency {\n recordReadableRead(this.source);\n return this.state.consistency;\n }\n\n ensureStarted(): void {\n if (\n this.destroyed ||\n this.state.data !== null ||\n this.pendingRefresh ||\n this.startQueued\n ) {\n return;\n }\n\n this.queueStart('initial');\n }\n\n refresh(): Promise<void> {\n if (this.destroyed) {\n return Promise.resolve();\n }\n\n if (this.pendingRefresh) {\n return this.pendingRefresh;\n }\n\n this.queueStart('manual');\n return this.pendingRefresh ?? Promise.resolve();\n }\n\n markPendingWrite(): void {\n if (this.destroyed) {\n return;\n }\n\n this.setState({\n refreshing: true,\n stale: true,\n consistency: 'pending-write',\n });\n }\n\n private queueStart(\n reason: 'initial' | 'manual' | 'invalidate' | 'pending-write'\n ): void {\n if (this.destroyed) {\n return;\n }\n\n this.startQueued = true;\n this.pendingRefresh = new Promise<void>((resolve) => {\n this.pendingRefreshResolve = resolve;\n globalScheduler.enqueue(() => {\n this.startQueued = false;\n if (this.destroyed) {\n this.finishPendingRefresh();\n return;\n }\n void this.start(reason).finally(() => {\n this.finishPendingRefresh();\n });\n });\n });\n }\n\n private finishPendingRefresh(): void {\n const resolve = this.pendingRefreshResolve;\n this.pendingRefresh = null;\n this.pendingRefreshResolve = null;\n resolve?.();\n }\n\n private setState(next: Partial<QueryState<T>>): void {\n if (this.destroyed) {\n return;\n }\n\n this.state = {\n ...this.state,\n ...next,\n };\n notifySource(this.source);\n }\n\n private async start(\n reason: 'initial' | 'manual' | 'invalidate' | 'pending-write'\n ): Promise<void> {\n if (this.destroyed) {\n return;\n }\n\n this.generation += 1;\n const generation = this.generation;\n\n this.controller?.abort();\n const controller = new AbortController();\n this.controller = controller;\n\n const hasData = this.state.data !== null;\n this.setState({\n loading: !hasData,\n refreshing: hasData,\n stale: hasData,\n consistency:\n reason === 'pending-write'\n ? 'pending-write'\n : hasData\n ? 'refreshing'\n : 'fresh',\n error: null,\n });\n\n let nextData: T;\n try {\n nextData = await this.options.fetch({ signal: controller.signal });\n } catch (error) {\n if (\n this.destroyed ||\n this.generation !== generation ||\n this.controller !== controller\n ) {\n return;\n }\n\n if (isAbortError(error, controller.signal)) {\n this.setState({ loading: false, refreshing: false });\n return;\n }\n\n this.setState({\n loading: false,\n refreshing: false,\n stale: true,\n consistency: 'stale',\n error,\n });\n return;\n }\n\n if (\n this.destroyed ||\n this.generation !== generation ||\n this.controller !== controller\n ) {\n return;\n }\n\n const isConsistent = this.options.isConsistent?.(nextData) ?? true;\n if (!isConsistent) {\n this.setState({\n data: nextData,\n loading: false,\n refreshing: false,\n stale: true,\n consistency: 'stale',\n });\n await this.reconcile(nextData);\n return;\n }\n\n this.reconcileAttemptCount = 0;\n this.setState({\n data: nextData,\n loading: false,\n refreshing: false,\n stale: false,\n consistency: 'fresh',\n error: null,\n });\n }\n\n private async reconcile(data: T): Promise<void> {\n const shouldRetry =\n this.options.reconcile?.(data, { key: this.options.key }) ?? false;\n\n if (!shouldRetry || this.destroyed) {\n return;\n }\n\n this.reconcileAttemptCount += 1;\n if (this.reconcileAttemptCount > RECONCILE_MAX_ATTEMPTS) {\n this.setState({ consistency: 'stale', refreshing: false });\n return;\n }\n\n await new Promise<void>((resolve) =>\n setTimeout(resolve, RECONCILE_RETRY_DELAY_MS)\n );\n if (this.destroyed || this.state.consistency === 'fresh') {\n return;\n }\n\n await this.refresh();\n }\n}\n\nclass MutationCell<TInput, TResult> implements Mutation<TInput, TResult> {\n private readonly source = createReadableSource();\n private action: MutationOptions<TInput, TResult>['action'];\n private affects?: MutationOptions<TInput, TResult>['affects'];\n private afterSuccess?: MutationOptions<TInput, TResult>['afterSuccess'];\n private controller: AbortController | null = null;\n private generation = 0;\n\n private state: MutationState<TResult> = {\n status: 'idle',\n error: null,\n result: null,\n };\n\n constructor(options: MutationOptions<TInput, TResult>) {\n this.action = options.action;\n this.affects = options.affects;\n this.afterSuccess = options.afterSuccess;\n }\n\n setOptions(options: MutationOptions<TInput, TResult>): void {\n this.action = options.action;\n this.affects = options.affects;\n this.afterSuccess = options.afterSuccess;\n }\n\n get status(): 'idle' | 'pending' | 'success' | 'error' {\n recordReadableRead(this.source);\n return this.state.status;\n }\n\n get pending(): boolean {\n recordReadableRead(this.source);\n return this.state.status === 'pending';\n }\n\n get error(): unknown | null {\n recordReadableRead(this.source);\n return this.state.error;\n }\n\n get result(): TResult | null {\n recordReadableRead(this.source);\n return this.state.result;\n }\n\n private setState(next: Partial<MutationState<TResult>>): void {\n this.state = {\n ...this.state,\n ...next,\n };\n notifySource(this.source);\n }\n\n async execute(input: TInput): Promise<TResult> {\n this.generation += 1;\n const generation = this.generation;\n\n this.controller?.abort();\n const controller = new AbortController();\n this.controller = controller;\n\n this.setState({ status: 'pending', error: null, result: null });\n\n let result: TResult;\n try {\n result = await this.action(input, { signal: controller.signal });\n } catch (error) {\n if (this.generation !== generation || this.controller !== controller) {\n throw error;\n }\n\n if (isAbortError(error, controller.signal)) {\n this.setState({ status: 'idle', error: null });\n throw error;\n }\n\n this.setState({ status: 'error', error });\n throw error;\n }\n\n if (this.generation !== generation || this.controller !== controller) {\n return result;\n }\n\n this.setState({ status: 'success', error: null, result });\n\n if (this.afterSuccess === 'invalidate') {\n const prefixes = this.affects?.(input, result) ?? [];\n for (const prefix of new Set(prefixes)) {\n invalidateQueries(prefix, true);\n }\n }\n\n return result;\n }\n\n abort(): void {\n this.generation += 1;\n this.controller?.abort();\n this.controller = null;\n this.setState({ status: 'idle', error: null });\n }\n\n reset(): void {\n this.generation += 1;\n this.controller?.abort();\n this.controller = null;\n this.setState({ status: 'idle', error: null, result: null });\n }\n}\n\nexport function createQuery<T>(options: QueryOptions<T>): Query<T> {\n const instance = getCurrentComponentInstance();\n const cache = getQueryCache();\n if (!instance) {\n let cell = cache.get(options.key) as QueryCell<T> | undefined;\n if (!cell) {\n cell = new QueryCell(options, options.key, cache);\n cache.set(options.key, cell as QueryCell<unknown>);\n cell.ensureStarted();\n }\n return cell;\n }\n\n const hookIndex = claimHookIndex(instance, 'query');\n ensureQueryCleanup(instance);\n\n const slotStore = getQuerySlotStore(instance);\n const existingSlot = slotStore.get(hookIndex);\n if (existingSlot && existingSlot.key === options.key) {\n return existingSlot.cell as QueryCell<T>;\n }\n\n if (existingSlot) {\n existingSlot.cell.detach(instance, hookIndex);\n }\n\n let cell = cache.get(options.key) as QueryCell<T> | undefined;\n if (!cell) {\n cell = new QueryCell(options, options.key, cache);\n cache.set(options.key, cell as QueryCell<unknown>);\n cell.ensureStarted();\n }\n\n slotStore.set(hookIndex, {\n key: options.key,\n cell: cell as QueryCell<unknown>,\n });\n cell.attach(instance, hookIndex);\n return cell;\n}\n\nexport function invalidate(prefix: string): void {\n invalidateQueries(prefix, false);\n}\n\nexport function createMutation<TInput, TResult>(\n options: MutationOptions<TInput, TResult>\n): Mutation<TInput, TResult> {\n const instance = getCurrentComponentInstance();\n\n if (!instance) {\n return new MutationCell(options);\n }\n\n const hookIndex = claimHookIndex(instance, 'mutation');\n ensureMutationCleanup(instance);\n\n const slotStore = getMutationSlotStore(instance);\n const existing = slotStore.get(hookIndex) as\n | MutationCell<TInput, TResult>\n | undefined;\n if (existing) {\n existing.setOptions(options);\n return existing;\n }\n\n const created = new MutationCell(options);\n slotStore.set(hookIndex, created as MutationCell<unknown, unknown>);\n return created;\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"invariant.js","names":[],"sources":["../../src/dev/invariant.ts"],"sourcesContent":["/**\n * Invariant assertion utilities for correctness checking\n * Production-safe: invariants are enforced at build-time or with minimal overhead\n *\n * Core principle: fail fast when invariants are violated\n * All functions throw descriptive errors for debugging\n */\n\n/**\n * Assert a condition; throw with context if false\n * @internal\n */\nexport function invariant(\n condition: boolean,\n message: string,\n context?: Record<string, unknown>\n): asserts condition {\n if (!condition) {\n const contextStr = context ? '\\n' + JSON.stringify(context, null, 2) : '';\n throw new Error(`[Askr Invariant] ${message}${contextStr}`);\n }\n}\n\n/**\n * Assert object property exists and has correct type\n * @internal\n */\nexport function assertProperty<T extends object, K extends keyof T>(\n obj: T,\n prop: K,\n expectedType?: string\n): asserts obj is T & Required<Pick<T, K>> {\n invariant(prop in obj, `Object missing required property '${String(prop)}'`, {\n object: obj,\n });\n\n if (expectedType) {\n const actualType = typeof obj[prop];\n invariant(\n actualType === expectedType,\n `Property '${String(prop)}' has type '${actualType}', expected '${expectedType}'`,\n { value: obj[prop], expectedType }\n );\n }\n}\n\n/**\n * Assert a reference is not null/undefined\n * @internal\n */\nexport function assertDefined<T>(\n value: T | null | undefined,\n message: string\n): asserts value is T {\n invariant(value !== null && value !== undefined, message, { value });\n}\n\n/**\n * Assert a task runs exactly once atomically\n * Useful for verifying lifecycle events fire precisely when expected\n * @internal\n */\nexport class Once {\n private called = false;\n private calledAt: number | null = null;\n readonly name: string;\n\n constructor(name: string) {\n this.name = name;\n }\n\n check(): boolean {\n return this.called;\n }\n\n mark(): void {\n invariant(\n !this.called,\n `${this.name} called multiple times (previously at ${this.calledAt}ms)`,\n { now: Date.now() }\n );\n this.called = true;\n this.calledAt = Date.now();\n }\n\n reset(): void {\n this.called = false;\n this.calledAt = null;\n }\n}\n\n/**\n * Assert a value falls in an enumerated set\n * @internal\n */\nexport function assertEnum<T extends readonly unknown[]>(\n value: unknown,\n allowedValues: T,\n fieldName: string\n): asserts value is T[number] {\n invariant(\n allowedValues.includes(value),\n `${fieldName} must be one of [${allowedValues.join(', ')}], got ${JSON.stringify(value)}`,\n { value, allowed: allowedValues }\n );\n}\n\n/**\n * Assert execution context (scheduler, component, etc)\n * @internal\n */\nexport function assertContext(\n actual: unknown,\n expected: unknown,\n contextName: string\n): asserts actual is typeof expected {\n invariant(\n actual === expected,\n `Invalid ${contextName} context. Expected ${expected}, got ${actual}`,\n { expected, actual }\n );\n}\n\n/**\n * Assert scheduling precondition (not reentering, not during render, etc)\n * @internal\n */\nexport function assertSchedulingPrecondition(\n condition: boolean,\n violationMessage: string\n): asserts condition {\n invariant(condition, `[Scheduler Precondition] ${violationMessage}`);\n}\n\n/**\n * Assert state precondition\n * @internal\n */\nexport function assertStatePrecondition(\n condition: boolean,\n violationMessage: string\n): asserts condition {\n invariant(condition, `[State Precondition] ${violationMessage}`);\n}\n\n/**\n * Verify AbortController lifecycle\n * @internal\n */\nexport function assertAbortControllerState(\n signal: AbortSignal,\n expectedAborted: boolean,\n context: string\n): void {\n invariant(\n signal.aborted === expectedAborted,\n `AbortSignal ${expectedAborted ? 'should be' : 'should not be'} aborted in ${context}`,\n { actual: signal.aborted, expected: expectedAborted }\n );\n}\n\n/**\n * Guard: throw if callback is null when it shouldn't be\n * Used for notifyUpdate, event handlers, etc.\n * @internal\n */\nexport function assertCallbackAvailable<\n T extends (...args: unknown[]) => unknown,\n>(callback: T | null | undefined, callbackName: string): asserts callback is T {\n invariant(\n callback !== null && callback !== undefined,\n `${callbackName} callback is required but not available`,\n { callback }\n );\n}\n\n/**\n * Verify evaluation generation prevents stale evaluations\n * @internal\n */\nexport function assertEvaluationGeneration(\n current: number,\n latest: number,\n context: string\n): void {\n invariant(\n current === latest,\n `Stale evaluation generation in ${context}: current ${current}, latest ${latest}`,\n { current, latest }\n );\n}\n\n/**\n * Verify mounted flag state\n * @internal\n */\nexport function assertMountedState(\n mounted: boolean,\n expectedMounted: boolean,\n context: string\n): void {\n invariant(\n mounted === expectedMounted,\n `Invalid mounted state in ${context}: expected ${expectedMounted}, got ${mounted}`,\n { mounted, expected: expectedMounted }\n );\n}\n\n/**\n * Verify no null target when rendering\n * @internal\n */\nexport function assertRenderTarget(\n target: Element | null,\n context: string\n): asserts target is Element {\n invariant(target !== null, `Cannot render in ${context}: target is null`, {\n target,\n });\n}\n"],"mappings":";;;;;;;;;;;;AAYA,SAAgB,UACd,WACA,SACA,SACmB;AACnB,KAAI,CAAC,WAAW;EACd,MAAM,aAAa,UAAU,OAAO,KAAK,UAAU,SAAS,MAAM,EAAE,GAAG;AACvE,QAAM,IAAI,MAAM,oBAAoB,UAAU,aAAa;;;;;;;AA4G/D,SAAgB,6BACd,WACA,kBACmB;AACnB,WAAU,WAAW,4BAA4B,mBAAmB"}
1
+ {"version":3,"file":"invariant.js","names":[],"sources":["../../src/dev/invariant.ts"],"sourcesContent":["/**\n * Invariant assertion utilities for correctness checking\n * Production-safe: invariants are enforced at build-time or with minimal overhead\n *\n * Core principle: fail fast when invariants are violated\n * All functions throw descriptive errors for debugging\n */\n\n/**\n * Assert a condition; throw with context if false\n * @internal\n */\nexport function invariant(\n condition: boolean,\n message: string,\n context?: Record<string, unknown>\n): asserts condition {\n if (!condition) {\n const contextStr = context ? '\\n' + JSON.stringify(context, null, 2) : '';\n throw new Error(`[Askr Invariant] ${message}${contextStr}`);\n }\n}\n\n/**\n * Assert object property exists and has correct type\n * @internal\n */\nexport function assertProperty<T extends object, K extends keyof T>(\n obj: T,\n prop: K,\n expectedType?: string\n): asserts obj is T & Required<Pick<T, K>> {\n invariant(prop in obj, `Object missing required property '${String(prop)}'`, {\n object: obj,\n });\n\n if (expectedType) {\n const actualType = typeof obj[prop];\n invariant(\n actualType === expectedType,\n `Property '${String(prop)}' has type '${actualType}', expected '${expectedType}'`,\n { value: obj[prop], expectedType }\n );\n }\n}\n\n/**\n * Assert a reference is not null/undefined\n * @internal\n */\nexport function assertDefined<T>(\n value: T | null | undefined,\n message: string\n): asserts value is T {\n invariant(value !== null && value !== undefined, message, { value });\n}\n\n/**\n * Assert a task runs exactly once atomically\n * Useful for verifying lifecycle events fire precisely when expected\n * @internal\n */\nexport class Once {\n private called = false;\n private calledAt: number | null = null;\n readonly name: string;\n\n constructor(name: string) {\n this.name = name;\n }\n\n check(): boolean {\n return this.called;\n }\n\n mark(): void {\n invariant(\n !this.called,\n `${this.name} called multiple times (previously at ${this.calledAt}ms)`,\n { now: Date.now() }\n );\n this.called = true;\n this.calledAt = Date.now();\n }\n\n reset(): void {\n this.called = false;\n this.calledAt = null;\n }\n}\n\n/**\n * Assert a value falls in an enumerated set\n * @internal\n */\nexport function assertEnum<T extends readonly unknown[]>(\n value: unknown,\n allowedValues: T,\n fieldName: string\n): asserts value is T[number] {\n invariant(\n allowedValues.includes(value),\n `${fieldName} must be one of [${allowedValues.join(', ')}], got ${JSON.stringify(value)}`,\n { value, allowed: allowedValues }\n );\n}\n\n/**\n * Assert execution context (scheduler, component, etc)\n * @internal\n */\nexport function assertContext(\n actual: unknown,\n expected: unknown,\n contextName: string\n): asserts actual is typeof expected {\n invariant(\n actual === expected,\n `Invalid ${contextName} context. Expected ${expected}, got ${actual}`,\n { expected, actual }\n );\n}\n\n/**\n * Assert scheduling precondition (not reentering, not during render, etc)\n * @internal\n */\nexport function assertSchedulingPrecondition(\n condition: boolean,\n violationMessage: string\n): asserts condition {\n invariant(condition, `[Scheduler Precondition] ${violationMessage}`);\n}\n\n/**\n * Assert state precondition\n * @internal\n */\nexport function assertStatePrecondition(\n condition: boolean,\n violationMessage: string\n): asserts condition {\n invariant(condition, `[State Precondition] ${violationMessage}`);\n}\n\n/**\n * Verify AbortController lifecycle\n * @internal\n */\nexport function assertAbortControllerState(\n signal: AbortSignal,\n expectedAborted: boolean,\n context: string\n): void {\n invariant(\n signal.aborted === expectedAborted,\n `AbortSignal ${expectedAborted ? 'should be' : 'should not be'} aborted in ${context}`,\n { actual: signal.aborted, expected: expectedAborted }\n );\n}\n\n/**\n * Guard: throw if callback is null when it shouldn't be\n * Used for notifyUpdate, event handlers, etc.\n * @internal\n */\nexport function assertCallbackAvailable<\n T extends (...args: unknown[]) => unknown,\n>(callback: T | null | undefined, callbackName: string): asserts callback is T {\n invariant(\n callback !== null && callback !== undefined,\n `${callbackName} callback is required but not available`,\n { callback }\n );\n}\n\n/**\n * Verify evaluation generation prevents stale evaluations\n * @internal\n */\nexport function assertEvaluationGeneration(\n current: number,\n latest: number,\n context: string\n): void {\n invariant(\n current === latest,\n `Stale evaluation generation in ${context}: current ${current}, latest ${latest}`,\n { current, latest }\n );\n}\n\n/**\n * Verify mounted flag state\n * @internal\n */\nexport function assertMountedState(\n mounted: boolean,\n expectedMounted: boolean,\n context: string\n): void {\n invariant(\n mounted === expectedMounted,\n `Invalid mounted state in ${context}: expected ${expectedMounted}, got ${mounted}`,\n { mounted, expected: expectedMounted }\n );\n}\n\n/**\n * Verify no null target when rendering\n * @internal\n */\nexport function assertRenderTarget(\n target: Element | null,\n context: string\n): asserts target is Element {\n invariant(target !== null, `Cannot render in ${context}: target is null`, {\n target,\n });\n}\n"],"mappings":";;;;;;;;;;;;AAYA,SAAgB,UACd,WACA,SACA,SACmB;CACnB,IAAI,CAAC,WAAW;EACd,MAAM,aAAa,UAAU,OAAO,KAAK,UAAU,SAAS,MAAM,EAAE,GAAG;EACvE,MAAM,IAAI,MAAM,oBAAoB,UAAU,aAAa;;;;;;;AA4G/D,SAAgB,6BACd,WACA,kBACmB;CACnB,UAAU,WAAW,4BAA4B,mBAAmB"}
@@ -1 +1 @@
1
- {"version":3,"file":"logger.js","names":[],"sources":["../../src/dev/logger.ts"],"sourcesContent":["/**\n * Centralized logger interface\n * - Keeps production builds silent for debug/warn/info messages\n * - Ensures consistent behavior across the codebase\n * - Protects against missing `console` in some environments\n */\n\nimport { isProductionEnvironment } from '../common/env';\n\nfunction callConsole(method: string, args: unknown[]): void {\n const c = typeof console !== 'undefined' ? (console as unknown) : undefined;\n if (!c) return;\n const fn = (c as Record<string, unknown>)[method];\n if (typeof fn === 'function') {\n try {\n (fn as (...a: unknown[]) => unknown).apply(console, args as unknown[]);\n } catch {\n // ignore logging errors\n }\n }\n}\n\nexport const logger = {\n debug: (...args: unknown[]) => {\n if (isProductionEnvironment()) return;\n callConsole('debug', args);\n },\n\n info: (...args: unknown[]) => {\n if (isProductionEnvironment()) return;\n callConsole('info', args);\n },\n\n warn: (...args: unknown[]) => {\n if (isProductionEnvironment()) return;\n callConsole('warn', args);\n },\n\n error: (...args: unknown[]) => {\n callConsole('error', args);\n },\n};\n"],"mappings":";;;;;;;;AASA,SAAS,YAAY,QAAgB,MAAuB;CAC1D,MAAM,IAAI,OAAO,YAAY,cAAe,UAAsB;AAClE,KAAI,CAAC,EAAG;CACR,MAAM,KAAM,EAA8B;AAC1C,KAAI,OAAO,OAAO,WAChB,KAAI;AACD,KAAoC,MAAM,SAAS,KAAkB;SAChE;;AAMZ,MAAa,SAAS;CACpB,QAAQ,GAAG,SAAoB;AAC7B,MAAI,yBAAyB,CAAE;AAC/B,cAAY,SAAS,KAAK;;CAG5B,OAAO,GAAG,SAAoB;AAC5B,MAAI,yBAAyB,CAAE;AAC/B,cAAY,QAAQ,KAAK;;CAG3B,OAAO,GAAG,SAAoB;AAC5B,MAAI,yBAAyB,CAAE;AAC/B,cAAY,QAAQ,KAAK;;CAG3B,QAAQ,GAAG,SAAoB;AAC7B,cAAY,SAAS,KAAK;;CAE7B"}
1
+ {"version":3,"file":"logger.js","names":[],"sources":["../../src/dev/logger.ts"],"sourcesContent":["/**\n * Centralized logger interface\n * - Keeps production builds silent for debug/warn/info messages\n * - Ensures consistent behavior across the codebase\n * - Protects against missing `console` in some environments\n */\n\nimport { isProductionEnvironment } from '../common/env';\n\nfunction callConsole(method: string, args: unknown[]): void {\n const c = typeof console !== 'undefined' ? (console as unknown) : undefined;\n if (!c) return;\n const fn = (c as Record<string, unknown>)[method];\n if (typeof fn === 'function') {\n try {\n (fn as (...a: unknown[]) => unknown).apply(console, args as unknown[]);\n } catch {\n // ignore logging errors\n }\n }\n}\n\nexport const logger = {\n debug: (...args: unknown[]) => {\n if (isProductionEnvironment()) return;\n callConsole('debug', args);\n },\n\n info: (...args: unknown[]) => {\n if (isProductionEnvironment()) return;\n callConsole('info', args);\n },\n\n warn: (...args: unknown[]) => {\n if (isProductionEnvironment()) return;\n callConsole('warn', args);\n },\n\n error: (...args: unknown[]) => {\n callConsole('error', args);\n },\n};\n"],"mappings":";;;;;;;;AASA,SAAS,YAAY,QAAgB,MAAuB;CAC1D,MAAM,IAAI,OAAO,YAAY,cAAe,UAAsB;CAClE,IAAI,CAAC,GAAG;CACR,MAAM,KAAM,EAA8B;CAC1C,IAAI,OAAO,OAAO,YAChB,IAAI;EACF,GAAqC,MAAM,SAAS,KAAkB;SAChE;;AAMZ,MAAa,SAAS;CACpB,QAAQ,GAAG,SAAoB;EAC7B,IAAI,yBAAyB,EAAE;EAC/B,YAAY,SAAS,KAAK;;CAG5B,OAAO,GAAG,SAAoB;EAC5B,IAAI,yBAAyB,EAAE;EAC/B,YAAY,QAAQ,KAAK;;CAG3B,OAAO,GAAG,SAAoB;EAC5B,IAAI,yBAAyB,EAAE;EAC/B,YAAY,QAAQ,KAAK;;CAG3B,QAAQ,GAAG,SAAoB;EAC7B,YAAY,SAAS,KAAK;;CAE7B"}
@@ -1 +1 @@
1
- {"version":3,"file":"icon.js","names":[],"sources":["../../../src/foundations/icon/icon.tsx"],"sourcesContent":["/* eslint-disable askr/no-hardcoded-theme-tokens -- Icon exposes a stable CSS variable contract consumed by themes. */\nimport { jsx, jsxs } from '../../jsx/jsx-runtime';\nimport type { IconProps, IconSizeToken, IconStyleObject } from './icon.types';\n\nconst ICON_SIZE_TOKENS: readonly IconSizeToken[] = ['sm', 'md', 'lg', 'xl'];\n\nexport function isIconSizeToken(value: unknown): value is IconSizeToken {\n return (\n typeof value === 'string' &&\n ICON_SIZE_TOKENS.includes(value as IconSizeToken)\n );\n}\n\nexport function normalizeIconSizeValue(size: number | string): string {\n if (typeof size === 'number') return `${size}px`;\n return size;\n}\n\nexport function resolveIconSizeVariable(size: number | string): string {\n if (isIconSizeToken(size)) {\n return `var(--ak-icon-size-${size}, var(--ak-icon-size-md, 1.25rem))`;\n }\n return normalizeIconSizeValue(size);\n}\n\nexport function resolveIconStrokeWidthVariable(\n strokeWidth: number,\n sizeToken: IconSizeToken | undefined\n): string {\n if (sizeToken) {\n return `var(--ak-icon-stroke-width-${sizeToken}, var(--ak-icon-stroke-width-md, ${strokeWidth}))`;\n }\n return `var(--ak-icon-stroke-width-md, ${strokeWidth})`;\n}\n\nfunction camelToKebab(key: string): string {\n return key.replace(/([A-Z])/g, (match) => `-${match.toLowerCase()}`);\n}\n\nexport function serializeIconStyle(\n style: string | IconStyleObject | undefined\n): string {\n if (!style) return '';\n if (typeof style === 'string') return style.trim();\n return Object.entries(style)\n .filter(([, value]) => value !== undefined && value !== null)\n .map(([key, value]) => `${camelToKebab(key)}:${String(value)}`)\n .join(';');\n}\n\nexport function joinIconStyle(\n ...styles: Array<string | undefined>\n): string | undefined {\n const merged = styles.map((style) => style?.trim()).filter(Boolean);\n return merged.length > 0 ? merged.join(';') : undefined;\n}\n\nexport function getIconContractProps({\n size = 20,\n strokeWidth = 2,\n color = 'currentColor',\n title,\n style,\n iconName,\n}: Pick<\n IconProps,\n 'color' | 'iconName' | 'size' | 'strokeWidth' | 'style' | 'title'\n>) {\n const sizeToken = isIconSizeToken(size) ? size : undefined;\n const decorative = title ? undefined : 'true';\n const resolvedSize = resolveIconSizeVariable(size);\n const resolvedStrokeWidth = resolveIconStrokeWidthVariable(\n strokeWidth,\n sizeToken\n );\n const iconStyle = joinIconStyle(\n `--ak-icon-size:${resolvedSize}`,\n `--ak-icon-stroke-width:${resolvedStrokeWidth}`,\n 'display:inline-block',\n 'flex-shrink:0',\n 'width:var(--ak-icon-size)',\n 'height:var(--ak-icon-size)',\n 'stroke-width:var(--ak-icon-stroke-width)',\n serializeIconStyle(style)\n );\n\n return {\n sizeToken,\n decorative,\n iconStyle,\n attrs: {\n xmlns: 'http://www.w3.org/2000/svg',\n width: '24',\n height: '24',\n fill: 'none',\n stroke: color,\n 'stroke-width': 'var(--ak-icon-stroke-width)',\n role: 'img',\n 'aria-hidden': title ? undefined : 'true',\n style: iconStyle,\n 'data-slot': 'icon',\n 'data-icon': iconName,\n 'data-size': sizeToken,\n 'data-decorative': decorative,\n 'data-color': color === 'currentColor' ? 'current' : undefined,\n },\n };\n}\n\nexport function IconBase({\n size = 20,\n strokeWidth = 2,\n color = 'currentColor',\n title,\n class: className,\n style,\n iconName,\n children,\n ref,\n ...rest\n}: IconProps) {\n const { attrs } = getIconContractProps({\n size,\n strokeWidth,\n color,\n title,\n style,\n iconName,\n });\n\n const finalChildren = title\n ? [jsx('title', { children: title }), children]\n : children;\n\n return jsxs('svg', {\n ...rest,\n ...attrs,\n viewBox: rest.viewBox ?? '0 0 24 24',\n 'stroke-linecap': rest['stroke-linecap'] ?? 'round',\n 'stroke-linejoin': rest['stroke-linejoin'] ?? 'round',\n class: className,\n ref,\n children: finalChildren,\n });\n}\n"],"mappings":";;AAIA,MAAM,mBAA6C;CAAC;CAAM;CAAM;CAAM;CAAK;AAE3E,SAAgB,gBAAgB,OAAwC;AACtE,QACE,OAAO,UAAU,YACjB,iBAAiB,SAAS,MAAuB;;AAIrD,SAAgB,uBAAuB,MAA+B;AACpE,KAAI,OAAO,SAAS,SAAU,QAAO,GAAG,KAAK;AAC7C,QAAO;;AAGT,SAAgB,wBAAwB,MAA+B;AACrE,KAAI,gBAAgB,KAAK,CACvB,QAAO,sBAAsB,KAAK;AAEpC,QAAO,uBAAuB,KAAK;;AAGrC,SAAgB,+BACd,aACA,WACQ;AACR,KAAI,UACF,QAAO,8BAA8B,UAAU,mCAAmC,YAAY;AAEhG,QAAO,kCAAkC,YAAY;;AAGvD,SAAS,aAAa,KAAqB;AACzC,QAAO,IAAI,QAAQ,aAAa,UAAU,IAAI,MAAM,aAAa,GAAG;;AAGtE,SAAgB,mBACd,OACQ;AACR,KAAI,CAAC,MAAO,QAAO;AACnB,KAAI,OAAO,UAAU,SAAU,QAAO,MAAM,MAAM;AAClD,QAAO,OAAO,QAAQ,MAAM,CACzB,QAAQ,GAAG,WAAW,UAAU,UAAa,UAAU,KAAK,CAC5D,KAAK,CAAC,KAAK,WAAW,GAAG,aAAa,IAAI,CAAC,GAAG,OAAO,MAAM,GAAG,CAC9D,KAAK,IAAI;;AAGd,SAAgB,cACd,GAAG,QACiB;CACpB,MAAM,SAAS,OAAO,KAAK,UAAU,OAAO,MAAM,CAAC,CAAC,OAAO,QAAQ;AACnE,QAAO,OAAO,SAAS,IAAI,OAAO,KAAK,IAAI,GAAG;;AAGhD,SAAgB,qBAAqB,EACnC,OAAO,IACP,cAAc,GACd,QAAQ,gBACR,OACA,OACA,YAIC;CACD,MAAM,YAAY,gBAAgB,KAAK,GAAG,OAAO;CACjD,MAAM,aAAa,QAAQ,SAAY;CACvC,MAAM,eAAe,wBAAwB,KAAK;CAClD,MAAM,sBAAsB,+BAC1B,aACA,UACD;CACD,MAAM,YAAY,cAChB,kBAAkB,gBAClB,0BAA0B,uBAC1B,wBACA,iBACA,6BACA,8BACA,4CACA,mBAAmB,MAAM,CAC1B;AAED,QAAO;EACL;EACA;EACA;EACA,OAAO;GACL,OAAO;GACP,OAAO;GACP,QAAQ;GACR,MAAM;GACN,QAAQ;GACR,gBAAgB;GAChB,MAAM;GACN,eAAe,QAAQ,SAAY;GACnC,OAAO;GACP,aAAa;GACb,aAAa;GACb,aAAa;GACb,mBAAmB;GACnB,cAAc,UAAU,iBAAiB,YAAY;GACtD;EACF;;AAGH,SAAgB,SAAS,EACvB,OAAO,IACP,cAAc,GACd,QAAQ,gBACR,OACA,OAAO,WACP,OACA,UACA,UACA,KACA,GAAG,QACS;CACZ,MAAM,EAAE,UAAU,qBAAqB;EACrC;EACA;EACA;EACA;EACA;EACA;EACD,CAAC;CAEF,MAAM,gBAAgB,QAClB,CAAC,IAAI,SAAS,EAAE,UAAU,OAAO,CAAC,EAAE,SAAS,GAC7C;AAEJ,QAAO,KAAK,OAAO;EACjB,GAAG;EACH,GAAG;EACH,SAAS,KAAK,WAAW;EACzB,kBAAkB,KAAK,qBAAqB;EAC5C,mBAAmB,KAAK,sBAAsB;EAC9C,OAAO;EACP;EACA,UAAU;EACX,CAAC"}
1
+ {"version":3,"file":"icon.js","names":[],"sources":["../../../src/foundations/icon/icon.tsx"],"sourcesContent":["/* eslint-disable askr/no-hardcoded-theme-tokens -- Icon exposes a stable CSS variable contract consumed by themes. */\nimport { jsx, jsxs } from '../../jsx/jsx-runtime';\nimport type { IconProps, IconSizeToken, IconStyleObject } from './icon.types';\n\nconst ICON_SIZE_TOKENS: readonly IconSizeToken[] = ['sm', 'md', 'lg', 'xl'];\n\nexport function isIconSizeToken(value: unknown): value is IconSizeToken {\n return (\n typeof value === 'string' &&\n ICON_SIZE_TOKENS.includes(value as IconSizeToken)\n );\n}\n\nexport function normalizeIconSizeValue(size: number | string): string {\n if (typeof size === 'number') return `${size}px`;\n return size;\n}\n\nexport function resolveIconSizeVariable(size: number | string): string {\n if (isIconSizeToken(size)) {\n return `var(--ak-icon-size-${size}, var(--ak-icon-size-md, 1.25rem))`;\n }\n return normalizeIconSizeValue(size);\n}\n\nexport function resolveIconStrokeWidthVariable(\n strokeWidth: number,\n sizeToken: IconSizeToken | undefined\n): string {\n if (sizeToken) {\n return `var(--ak-icon-stroke-width-${sizeToken}, var(--ak-icon-stroke-width-md, ${strokeWidth}))`;\n }\n return `var(--ak-icon-stroke-width-md, ${strokeWidth})`;\n}\n\nfunction camelToKebab(key: string): string {\n return key.replace(/([A-Z])/g, (match) => `-${match.toLowerCase()}`);\n}\n\nexport function serializeIconStyle(\n style: string | IconStyleObject | undefined\n): string {\n if (!style) return '';\n if (typeof style === 'string') return style.trim();\n return Object.entries(style)\n .filter(([, value]) => value !== undefined && value !== null)\n .map(([key, value]) => `${camelToKebab(key)}:${String(value)}`)\n .join(';');\n}\n\nexport function joinIconStyle(\n ...styles: Array<string | undefined>\n): string | undefined {\n const merged = styles.map((style) => style?.trim()).filter(Boolean);\n return merged.length > 0 ? merged.join(';') : undefined;\n}\n\nexport function getIconContractProps({\n size = 20,\n strokeWidth = 2,\n color = 'currentColor',\n title,\n style,\n iconName,\n}: Pick<\n IconProps,\n 'color' | 'iconName' | 'size' | 'strokeWidth' | 'style' | 'title'\n>) {\n const sizeToken = isIconSizeToken(size) ? size : undefined;\n const decorative = title ? undefined : 'true';\n const resolvedSize = resolveIconSizeVariable(size);\n const resolvedStrokeWidth = resolveIconStrokeWidthVariable(\n strokeWidth,\n sizeToken\n );\n const iconStyle = joinIconStyle(\n `--ak-icon-size:${resolvedSize}`,\n `--ak-icon-stroke-width:${resolvedStrokeWidth}`,\n 'display:inline-block',\n 'flex-shrink:0',\n 'width:var(--ak-icon-size)',\n 'height:var(--ak-icon-size)',\n 'stroke-width:var(--ak-icon-stroke-width)',\n serializeIconStyle(style)\n );\n\n return {\n sizeToken,\n decorative,\n iconStyle,\n attrs: {\n xmlns: 'http://www.w3.org/2000/svg',\n width: '24',\n height: '24',\n fill: 'none',\n stroke: color,\n 'stroke-width': 'var(--ak-icon-stroke-width)',\n role: 'img',\n 'aria-hidden': title ? undefined : 'true',\n style: iconStyle,\n 'data-slot': 'icon',\n 'data-icon': iconName,\n 'data-size': sizeToken,\n 'data-decorative': decorative,\n 'data-color': color === 'currentColor' ? 'current' : undefined,\n },\n };\n}\n\nexport function IconBase({\n size = 20,\n strokeWidth = 2,\n color = 'currentColor',\n title,\n class: className,\n style,\n iconName,\n children,\n ref,\n ...rest\n}: IconProps) {\n const { attrs } = getIconContractProps({\n size,\n strokeWidth,\n color,\n title,\n style,\n iconName,\n });\n\n const finalChildren = title\n ? [jsx('title', { children: title }), children]\n : children;\n\n return jsxs('svg', {\n ...rest,\n ...attrs,\n viewBox: rest.viewBox ?? '0 0 24 24',\n 'stroke-linecap': rest['stroke-linecap'] ?? 'round',\n 'stroke-linejoin': rest['stroke-linejoin'] ?? 'round',\n class: className,\n ref,\n children: finalChildren,\n });\n}\n"],"mappings":";;AAIA,MAAM,mBAA6C;CAAC;CAAM;CAAM;CAAM;CAAK;AAE3E,SAAgB,gBAAgB,OAAwC;CACtE,OACE,OAAO,UAAU,YACjB,iBAAiB,SAAS,MAAuB;;AAIrD,SAAgB,uBAAuB,MAA+B;CACpE,IAAI,OAAO,SAAS,UAAU,OAAO,GAAG,KAAK;CAC7C,OAAO;;AAGT,SAAgB,wBAAwB,MAA+B;CACrE,IAAI,gBAAgB,KAAK,EACvB,OAAO,sBAAsB,KAAK;CAEpC,OAAO,uBAAuB,KAAK;;AAGrC,SAAgB,+BACd,aACA,WACQ;CACR,IAAI,WACF,OAAO,8BAA8B,UAAU,mCAAmC,YAAY;CAEhG,OAAO,kCAAkC,YAAY;;AAGvD,SAAS,aAAa,KAAqB;CACzC,OAAO,IAAI,QAAQ,aAAa,UAAU,IAAI,MAAM,aAAa,GAAG;;AAGtE,SAAgB,mBACd,OACQ;CACR,IAAI,CAAC,OAAO,OAAO;CACnB,IAAI,OAAO,UAAU,UAAU,OAAO,MAAM,MAAM;CAClD,OAAO,OAAO,QAAQ,MAAM,CACzB,QAAQ,GAAG,WAAW,UAAU,UAAa,UAAU,KAAK,CAC5D,KAAK,CAAC,KAAK,WAAW,GAAG,aAAa,IAAI,CAAC,GAAG,OAAO,MAAM,GAAG,CAC9D,KAAK,IAAI;;AAGd,SAAgB,cACd,GAAG,QACiB;CACpB,MAAM,SAAS,OAAO,KAAK,UAAU,OAAO,MAAM,CAAC,CAAC,OAAO,QAAQ;CACnE,OAAO,OAAO,SAAS,IAAI,OAAO,KAAK,IAAI,GAAG;;AAGhD,SAAgB,qBAAqB,EACnC,OAAO,IACP,cAAc,GACd,QAAQ,gBACR,OACA,OACA,YAIC;CACD,MAAM,YAAY,gBAAgB,KAAK,GAAG,OAAO;CACjD,MAAM,aAAa,QAAQ,SAAY;CACvC,MAAM,eAAe,wBAAwB,KAAK;CAClD,MAAM,sBAAsB,+BAC1B,aACA,UACD;CACD,MAAM,YAAY,cAChB,kBAAkB,gBAClB,0BAA0B,uBAC1B,wBACA,iBACA,6BACA,8BACA,4CACA,mBAAmB,MAAM,CAC1B;CAED,OAAO;EACL;EACA;EACA;EACA,OAAO;GACL,OAAO;GACP,OAAO;GACP,QAAQ;GACR,MAAM;GACN,QAAQ;GACR,gBAAgB;GAChB,MAAM;GACN,eAAe,QAAQ,SAAY;GACnC,OAAO;GACP,aAAa;GACb,aAAa;GACb,aAAa;GACb,mBAAmB;GACnB,cAAc,UAAU,iBAAiB,YAAY;GACtD;EACF;;AAGH,SAAgB,SAAS,EACvB,OAAO,IACP,cAAc,GACd,QAAQ,gBACR,OACA,OAAO,WACP,OACA,UACA,UACA,KACA,GAAG,QACS;CACZ,MAAM,EAAE,UAAU,qBAAqB;EACrC;EACA;EACA;EACA;EACA;EACA;EACD,CAAC;CAEF,MAAM,gBAAgB,QAClB,CAAC,IAAI,SAAS,EAAE,UAAU,OAAO,CAAC,EAAE,SAAS,GAC7C;CAEJ,OAAO,KAAK,OAAO;EACjB,GAAG;EACH,GAAG;EACH,SAAS,KAAK,WAAW;EACzB,kBAAkB,KAAK,qBAAqB;EAC5C,mBAAmB,KAAK,sBAAsB;EAC9C,OAAO;EACP;EACA,UAAU;EACX,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"dismissable.js","names":[],"sources":["../../../src/foundations/interactions/dismissable.ts"],"sourcesContent":["/**\n * dismissable\n *\n * THE dismissal primitive. Handles Escape key and outside interactions.\n *\n * INVARIANTS:\n * 1. Returns props that compose via mergeProps (no factories)\n * 2. Disabled state respected exactly once, here\n * 3. No side effects - pure props generation\n * 4. Outside detection requires explicit node reference\n * 5. This is the ONLY dismissal primitive - do not create alternatives\n *\n * DESIGN:\n * - Returns standard event handler props (onKeyDown, onPointerDownCapture)\n * - Composable via mergeProps with other foundations\n * - Caller provides node reference for outside detection\n * - Single onDismiss callback for all dismiss triggers\n *\n * PIT OF SUCCESS:\n * ✓ Can't accidentally bypass (only way to get dismiss behavior)\n * ✓ Can't duplicate (disabled checked once)\n * ✓ Composes via mergeProps (standard props)\n * ✓ Wrong usage is hard (no factories to misuse)\n *\n * USAGE:\n * const props = dismissable({\n * node: elementRef,\n * disabled: false,\n * onDismiss: () => close()\n * });\n *\n * <div ref={elementRef} {...props}>Content</div>\n *\n * MISUSE EXAMPLE (PREVENTED):\n * ❌ Can't forget to check disabled - checked inside dismissable\n * ❌ Can't create custom escape handler - this is the only one\n * ❌ Can't bypass via direct event listeners - mergeProps composes correctly\n */\n\nexport interface DismissableOptions {\n /**\n * Reference to the element for outside click detection\n */\n node?: Node | null;\n\n /**\n * Whether dismiss is disabled\n */\n disabled?: boolean;\n\n /**\n * Called when dismiss is triggered (Escape or outside click)\n */\n onDismiss?: (trigger: 'escape' | 'outside') => void;\n}\n\nimport type {\n KeyboardLikeEvent,\n PointerLikeEvent,\n} from '../utilities/event-types';\n\nexport function dismissable({ node, disabled, onDismiss }: DismissableOptions) {\n function handleKeyDown(e: KeyboardLikeEvent) {\n if (disabled) return;\n if (e.key === 'Escape') {\n e.preventDefault?.();\n e.stopPropagation?.();\n onDismiss?.('escape');\n }\n }\n\n function handlePointerDownCapture(e: PointerLikeEvent) {\n if (disabled) return;\n\n const target = e.target;\n if (!(target instanceof Node)) return;\n\n // If no node provided, can't detect outside clicks\n if (!node) return;\n\n // Check if click is outside\n if (!node.contains(target)) {\n onDismiss?.('outside');\n }\n }\n\n return {\n onKeyDown: handleKeyDown,\n // Use capture phase to catch events before they bubble\n onPointerDownCapture: handlePointerDownCapture,\n };\n}\n"],"mappings":";AA6DA,SAAgB,YAAY,EAAE,MAAM,UAAU,aAAiC;CAC7E,SAAS,cAAc,GAAsB;AAC3C,MAAI,SAAU;AACd,MAAI,EAAE,QAAQ,UAAU;AACtB,KAAE,kBAAkB;AACpB,KAAE,mBAAmB;AACrB,eAAY,SAAS;;;CAIzB,SAAS,yBAAyB,GAAqB;AACrD,MAAI,SAAU;EAEd,MAAM,SAAS,EAAE;AACjB,MAAI,EAAE,kBAAkB,MAAO;AAG/B,MAAI,CAAC,KAAM;AAGX,MAAI,CAAC,KAAK,SAAS,OAAO,CACxB,aAAY,UAAU;;AAI1B,QAAO;EACL,WAAW;EAEX,sBAAsB;EACvB"}
1
+ {"version":3,"file":"dismissable.js","names":[],"sources":["../../../src/foundations/interactions/dismissable.ts"],"sourcesContent":["/**\n * dismissable\n *\n * THE dismissal primitive. Handles Escape key and outside interactions.\n *\n * INVARIANTS:\n * 1. Returns props that compose via mergeProps (no factories)\n * 2. Disabled state respected exactly once, here\n * 3. No side effects - pure props generation\n * 4. Outside detection requires explicit node reference\n * 5. This is the ONLY dismissal primitive - do not create alternatives\n *\n * DESIGN:\n * - Returns standard event handler props (onKeyDown, onPointerDownCapture)\n * - Composable via mergeProps with other foundations\n * - Caller provides node reference for outside detection\n * - Single onDismiss callback for all dismiss triggers\n *\n * PIT OF SUCCESS:\n * ✓ Can't accidentally bypass (only way to get dismiss behavior)\n * ✓ Can't duplicate (disabled checked once)\n * ✓ Composes via mergeProps (standard props)\n * ✓ Wrong usage is hard (no factories to misuse)\n *\n * USAGE:\n * const props = dismissable({\n * node: elementRef,\n * disabled: false,\n * onDismiss: () => close()\n * });\n *\n * <div ref={elementRef} {...props}>Content</div>\n *\n * MISUSE EXAMPLE (PREVENTED):\n * ❌ Can't forget to check disabled - checked inside dismissable\n * ❌ Can't create custom escape handler - this is the only one\n * ❌ Can't bypass via direct event listeners - mergeProps composes correctly\n */\n\nexport interface DismissableOptions {\n /**\n * Reference to the element for outside click detection\n */\n node?: Node | null;\n\n /**\n * Whether dismiss is disabled\n */\n disabled?: boolean;\n\n /**\n * Called when dismiss is triggered (Escape or outside click)\n */\n onDismiss?: (trigger: 'escape' | 'outside') => void;\n}\n\nimport type {\n KeyboardLikeEvent,\n PointerLikeEvent,\n} from '../utilities/event-types';\n\nexport function dismissable({ node, disabled, onDismiss }: DismissableOptions) {\n function handleKeyDown(e: KeyboardLikeEvent) {\n if (disabled) return;\n if (e.key === 'Escape') {\n e.preventDefault?.();\n e.stopPropagation?.();\n onDismiss?.('escape');\n }\n }\n\n function handlePointerDownCapture(e: PointerLikeEvent) {\n if (disabled) return;\n\n const target = e.target;\n if (!(target instanceof Node)) return;\n\n // If no node provided, can't detect outside clicks\n if (!node) return;\n\n // Check if click is outside\n if (!node.contains(target)) {\n onDismiss?.('outside');\n }\n }\n\n return {\n onKeyDown: handleKeyDown,\n // Use capture phase to catch events before they bubble\n onPointerDownCapture: handlePointerDownCapture,\n };\n}\n"],"mappings":";AA6DA,SAAgB,YAAY,EAAE,MAAM,UAAU,aAAiC;CAC7E,SAAS,cAAc,GAAsB;EAC3C,IAAI,UAAU;EACd,IAAI,EAAE,QAAQ,UAAU;GACtB,EAAE,kBAAkB;GACpB,EAAE,mBAAmB;GACrB,YAAY,SAAS;;;CAIzB,SAAS,yBAAyB,GAAqB;EACrD,IAAI,UAAU;EAEd,MAAM,SAAS,EAAE;EACjB,IAAI,EAAE,kBAAkB,OAAO;EAG/B,IAAI,CAAC,MAAM;EAGX,IAAI,CAAC,KAAK,SAAS,OAAO,EACxB,YAAY,UAAU;;CAI1B,OAAO;EACL,WAAW;EAEX,sBAAsB;EACvB"}
@@ -1 +1 @@
1
- {"version":3,"file":"focusable.js","names":[],"sources":["../../../src/foundations/interactions/focusable.ts"],"sourcesContent":["/**\n * focusable\n *\n * Normalize focus-related props for hosts.\n * - No DOM manipulation here; returns props that the runtime may attach.\n */\n\nimport { ariaDisabled } from '../utilities/aria';\n\nexport interface FocusableOptions {\n disabled?: boolean;\n tabIndex?: number | undefined;\n}\n\nexport interface FocusableResult {\n tabIndex?: number;\n 'aria-disabled'?: 'true';\n}\n\nexport function focusable({\n disabled,\n tabIndex,\n}: FocusableOptions): FocusableResult {\n return {\n tabIndex: disabled ? -1 : tabIndex === undefined ? 0 : tabIndex,\n ...ariaDisabled(disabled),\n };\n}\n"],"mappings":";;;;;;;;AAmBA,SAAgB,UAAU,EACxB,UACA,YACoC;AACpC,QAAO;EACL,UAAU,WAAW,KAAK,aAAa,SAAY,IAAI;EACvD,GAAG,aAAa,SAAS;EAC1B"}
1
+ {"version":3,"file":"focusable.js","names":[],"sources":["../../../src/foundations/interactions/focusable.ts"],"sourcesContent":["/**\n * focusable\n *\n * Normalize focus-related props for hosts.\n * - No DOM manipulation here; returns props that the runtime may attach.\n */\n\nimport { ariaDisabled } from '../utilities/aria';\n\nexport interface FocusableOptions {\n disabled?: boolean;\n tabIndex?: number | undefined;\n}\n\nexport interface FocusableResult {\n tabIndex?: number;\n 'aria-disabled'?: 'true';\n}\n\nexport function focusable({\n disabled,\n tabIndex,\n}: FocusableOptions): FocusableResult {\n return {\n tabIndex: disabled ? -1 : tabIndex === undefined ? 0 : tabIndex,\n ...ariaDisabled(disabled),\n };\n}\n"],"mappings":";;;;;;;;AAmBA,SAAgB,UAAU,EACxB,UACA,YACoC;CACpC,OAAO;EACL,UAAU,WAAW,KAAK,aAAa,SAAY,IAAI;EACvD,GAAG,aAAa,SAAS;EAC1B"}
@@ -1 +1 @@
1
- {"version":3,"file":"hoverable.js","names":[],"sources":["../../../src/foundations/interactions/hoverable.ts"],"sourcesContent":["/**\n * hoverable\n *\n * Produces props for pointer enter/leave handling. Pure and deterministic.\n */\n\nexport interface HoverableOptions {\n disabled?: boolean;\n onEnter?: (e: HoverEvent) => void;\n onLeave?: (e: HoverEvent) => void;\n}\n\nimport type {\n DefaultPreventable,\n PropagationStoppable,\n} from '../utilities/event-types';\n\ntype HoverEvent = DefaultPreventable & PropagationStoppable;\n\nexport interface HoverableResult {\n onPointerEnter?: (e: HoverEvent) => void;\n onPointerLeave?: (e: HoverEvent) => void;\n}\n\nexport function hoverable({\n disabled,\n onEnter,\n onLeave,\n}: HoverableOptions): HoverableResult {\n return {\n onPointerEnter: disabled\n ? undefined\n : (e) => {\n onEnter?.(e);\n },\n onPointerLeave: disabled\n ? undefined\n : (e) => {\n onLeave?.(e);\n },\n };\n}\n"],"mappings":";AAwBA,SAAgB,UAAU,EACxB,UACA,SACA,WACoC;AACpC,QAAO;EACL,gBAAgB,WACZ,UACC,MAAM;AACL,aAAU,EAAE;;EAElB,gBAAgB,WACZ,UACC,MAAM;AACL,aAAU,EAAE;;EAEnB"}
1
+ {"version":3,"file":"hoverable.js","names":[],"sources":["../../../src/foundations/interactions/hoverable.ts"],"sourcesContent":["/**\n * hoverable\n *\n * Produces props for pointer enter/leave handling. Pure and deterministic.\n */\n\nexport interface HoverableOptions {\n disabled?: boolean;\n onEnter?: (e: HoverEvent) => void;\n onLeave?: (e: HoverEvent) => void;\n}\n\nimport type {\n DefaultPreventable,\n PropagationStoppable,\n} from '../utilities/event-types';\n\ntype HoverEvent = DefaultPreventable & PropagationStoppable;\n\nexport interface HoverableResult {\n onPointerEnter?: (e: HoverEvent) => void;\n onPointerLeave?: (e: HoverEvent) => void;\n}\n\nexport function hoverable({\n disabled,\n onEnter,\n onLeave,\n}: HoverableOptions): HoverableResult {\n return {\n onPointerEnter: disabled\n ? undefined\n : (e) => {\n onEnter?.(e);\n },\n onPointerLeave: disabled\n ? undefined\n : (e) => {\n onLeave?.(e);\n },\n };\n}\n"],"mappings":";AAwBA,SAAgB,UAAU,EACxB,UACA,SACA,WACoC;CACpC,OAAO;EACL,gBAAgB,WACZ,UACC,MAAM;GACL,UAAU,EAAE;;EAElB,gBAAgB,WACZ,UACC,MAAM;GACL,UAAU,EAAE;;EAEnB"}
@@ -1 +1 @@
1
- {"version":3,"file":"interaction-policy.js","names":["mergePropsBase"],"sources":["../../../src/foundations/interactions/interaction-policy.ts"],"sourcesContent":["/**\n * INTERACTION POLICY (FRAMEWORK LAW)\n *\n * This is THE ONLY way to create interactive elements. Components MUST NOT\n * implement interaction logic directly.\n *\n * INVARIANTS (ENFORCED):\n * 1. \"Press\" is the semantic. Click/touch/Enter/Space are implementation details.\n * 2. Disabled is enforced exactly once, here. Components may not check disabled.\n * 3. Keyboard handling is automatic. Components may not add onKeyDown for activation.\n * 4. Native elements opt out of polyfills, not semantics.\n * 5. asChild may replace the host element, not interaction behavior.\n * 6. This policy is the SINGLE SOURCE OF TRUTH for interactive behavior.\n *\n * DESIGN:\n * - Single public function: applyInteractionPolicy\n * - Returns props that compose via mergeProps\n * - Delegates to pressable for mechanics\n * - Enforces disabled once and only once\n * - No configuration beyond disabled and native element type\n *\n * PIT OF SUCCESS:\n * ✓ Can't bypass policy (only way to get interaction behavior)\n * ✓ Can't duplicate disabled checks (enforced once, here)\n * ✓ Can't write custom keyboard handlers for buttons (policy owns it)\n * ✓ Composes via mergeProps (standard props)\n * ✓ Wrong usage is impossible (no escape hatch)\n *\n * USAGE:\n * function Button({ onPress, disabled }) {\n * const interaction = applyInteractionPolicy({\n * isNative: true,\n * disabled,\n * onPress\n * });\n *\n * return <button {...interaction}>Click me</button>;\n * }\n *\n * MISUSE EXAMPLE (PREVENTED):\n * ❌ Button checking disabled again:\n * function Button({ disabled, onPress }) {\n * if (disabled) return; // NO! Policy handles this\n * const interaction = applyInteractionPolicy(...);\n * }\n *\n * ❌ Custom keyboard handler:\n * function Button({ onPress }) {\n * const interaction = applyInteractionPolicy(...);\n * return <button {...interaction} onKeyDown={...}>; // NO! Policy owns this\n * }\n *\n * ❌ Direct event handler:\n * <button onClick={onPress}>; // NO! Use applyInteractionPolicy\n */\n\nimport { pressable } from './pressable';\nimport { composeHandlers } from '../utilities/compose-handlers';\nimport { composeRefs, Ref } from '../utilities/compose-ref';\nimport { mergeProps as mergePropsBase } from '../utilities/merge-props';\n\nexport interface InteractionPolicyInput {\n /** Whether the host element is a native interactive element (button, a, etc) */\n isNative: boolean;\n /** Disabled state - checked ONLY here, never in components */\n disabled: boolean;\n /** User-provided press handler - semantic action, not DOM event */\n onPress?: (e: Event) => void;\n /** Optional ref to compose */\n ref?: Ref<unknown>;\n}\n\n/**\n * THE interaction policy. Components MUST use this, NEVER implement\n * interaction logic directly.\n */\nexport function applyInteractionPolicy({\n isNative,\n disabled,\n onPress,\n ref,\n}: InteractionPolicyInput) {\n // Disabled is enforced exactly once, here.\n // Components downstream may NOT check disabled.\n function invokePress(e: Event) {\n if (disabled) {\n e.preventDefault?.();\n return;\n }\n onPress?.(e);\n }\n\n if (isNative) {\n return {\n disabled: disabled || undefined,\n onClick: (e: Event) => invokePress(e),\n ref,\n };\n }\n\n // Non-native elements get full button semantics\n const interaction = pressable({\n disabled,\n isNativeButton: false,\n onPress: (e) => invokePress(e as Event),\n });\n\n return {\n ...interaction,\n 'aria-disabled': disabled || undefined,\n tabIndex: disabled ? -1 : (interaction.tabIndex ?? 0),\n ref,\n };\n}\n\n/**\n * Merge rule for Slot / asChild\n *\n * Precedence:\n * policy → user → child\n *\n * Event handlers are composed (policy first).\n * Refs are always composed.\n * Policy props MUST take precedence to enforce invariants.\n */\nexport function mergeInteractionProps(\n childProps: Record<string, unknown>,\n policyProps: Record<string, unknown>,\n userProps?: Record<string, unknown>\n) {\n let out = mergePropsBase(childProps, policyProps);\n if (userProps) out = mergePropsBase(out, userProps);\n\n // Ensure policy handlers always run first\n for (const k in out) {\n if (!k.startsWith('on')) continue;\n\n const policyHandler = policyProps?.[k];\n const userHandler = userProps?.[k];\n const childHandler = childProps?.[k];\n\n if (policyHandler || userHandler || childHandler) {\n const toHandler = (h: unknown) =>\n typeof h === 'function'\n ? (h as (...args: readonly unknown[]) => void)\n : undefined;\n\n out[k] = composeHandlers(\n toHandler(policyHandler),\n composeHandlers(toHandler(userHandler), toHandler(childHandler))\n );\n }\n }\n\n const childRef = childProps?.ref as Ref<unknown> | undefined;\n const userRef = userProps?.ref as Ref<unknown> | undefined;\n const policyRef = policyProps?.ref as Ref<unknown> | undefined;\n\n out.ref = composeRefs(childRef, userRef, policyRef);\n\n return out;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4EA,SAAgB,uBAAuB,EACrC,UACA,UACA,SACA,OACyB;CAGzB,SAAS,YAAY,GAAU;AAC7B,MAAI,UAAU;AACZ,KAAE,kBAAkB;AACpB;;AAEF,YAAU,EAAE;;AAGd,KAAI,SACF,QAAO;EACL,UAAU,YAAY;EACtB,UAAU,MAAa,YAAY,EAAE;EACrC;EACD;CAIH,MAAM,cAAc,UAAU;EAC5B;EACA,gBAAgB;EAChB,UAAU,MAAM,YAAY,EAAW;EACxC,CAAC;AAEF,QAAO;EACL,GAAG;EACH,iBAAiB,YAAY;EAC7B,UAAU,WAAW,KAAM,YAAY,YAAY;EACnD;EACD;;;;;;;;;;;;AAaH,SAAgB,sBACd,YACA,aACA,WACA;CACA,IAAI,MAAMA,WAAe,YAAY,YAAY;AACjD,KAAI,UAAW,OAAMA,WAAe,KAAK,UAAU;AAGnD,MAAK,MAAM,KAAK,KAAK;AACnB,MAAI,CAAC,EAAE,WAAW,KAAK,CAAE;EAEzB,MAAM,gBAAgB,cAAc;EACpC,MAAM,cAAc,YAAY;EAChC,MAAM,eAAe,aAAa;AAElC,MAAI,iBAAiB,eAAe,cAAc;GAChD,MAAM,aAAa,MACjB,OAAO,MAAM,aACR,IACD;AAEN,OAAI,KAAK,gBACP,UAAU,cAAc,EACxB,gBAAgB,UAAU,YAAY,EAAE,UAAU,aAAa,CAAC,CACjE;;;CAIL,MAAM,WAAW,YAAY;CAC7B,MAAM,UAAU,WAAW;CAC3B,MAAM,YAAY,aAAa;AAE/B,KAAI,MAAM,YAAY,UAAU,SAAS,UAAU;AAEnD,QAAO"}
1
+ {"version":3,"file":"interaction-policy.js","names":["mergePropsBase"],"sources":["../../../src/foundations/interactions/interaction-policy.ts"],"sourcesContent":["/**\n * INTERACTION POLICY (FRAMEWORK LAW)\n *\n * This is THE ONLY way to create interactive elements. Components MUST NOT\n * implement interaction logic directly.\n *\n * INVARIANTS (ENFORCED):\n * 1. \"Press\" is the semantic. Click/touch/Enter/Space are implementation details.\n * 2. Disabled is enforced exactly once, here. Components may not check disabled.\n * 3. Keyboard handling is automatic. Components may not add onKeyDown for activation.\n * 4. Native elements opt out of polyfills, not semantics.\n * 5. asChild may replace the host element, not interaction behavior.\n * 6. This policy is the SINGLE SOURCE OF TRUTH for interactive behavior.\n *\n * DESIGN:\n * - Single public function: applyInteractionPolicy\n * - Returns props that compose via mergeProps\n * - Delegates to pressable for mechanics\n * - Enforces disabled once and only once\n * - No configuration beyond disabled and native element type\n *\n * PIT OF SUCCESS:\n * ✓ Can't bypass policy (only way to get interaction behavior)\n * ✓ Can't duplicate disabled checks (enforced once, here)\n * ✓ Can't write custom keyboard handlers for buttons (policy owns it)\n * ✓ Composes via mergeProps (standard props)\n * ✓ Wrong usage is impossible (no escape hatch)\n *\n * USAGE:\n * function Button({ onPress, disabled }) {\n * const interaction = applyInteractionPolicy({\n * isNative: true,\n * disabled,\n * onPress\n * });\n *\n * return <button {...interaction}>Click me</button>;\n * }\n *\n * MISUSE EXAMPLE (PREVENTED):\n * ❌ Button checking disabled again:\n * function Button({ disabled, onPress }) {\n * if (disabled) return; // NO! Policy handles this\n * const interaction = applyInteractionPolicy(...);\n * }\n *\n * ❌ Custom keyboard handler:\n * function Button({ onPress }) {\n * const interaction = applyInteractionPolicy(...);\n * return <button {...interaction} onKeyDown={...}>; // NO! Policy owns this\n * }\n *\n * ❌ Direct event handler:\n * <button onClick={onPress}>; // NO! Use applyInteractionPolicy\n */\n\nimport { pressable } from './pressable';\nimport { composeHandlers } from '../utilities/compose-handlers';\nimport { composeRefs, Ref } from '../utilities/compose-ref';\nimport { mergeProps as mergePropsBase } from '../utilities/merge-props';\n\nexport interface InteractionPolicyInput {\n /** Whether the host element is a native interactive element (button, a, etc) */\n isNative: boolean;\n /** Disabled state - checked ONLY here, never in components */\n disabled: boolean;\n /** User-provided press handler - semantic action, not DOM event */\n onPress?: (e: Event) => void;\n /** Optional ref to compose */\n ref?: Ref<unknown>;\n}\n\n/**\n * THE interaction policy. Components MUST use this, NEVER implement\n * interaction logic directly.\n */\nexport function applyInteractionPolicy({\n isNative,\n disabled,\n onPress,\n ref,\n}: InteractionPolicyInput) {\n // Disabled is enforced exactly once, here.\n // Components downstream may NOT check disabled.\n function invokePress(e: Event) {\n if (disabled) {\n e.preventDefault?.();\n return;\n }\n onPress?.(e);\n }\n\n if (isNative) {\n return {\n disabled: disabled || undefined,\n onClick: (e: Event) => invokePress(e),\n ref,\n };\n }\n\n // Non-native elements get full button semantics\n const interaction = pressable({\n disabled,\n isNativeButton: false,\n onPress: (e) => invokePress(e as Event),\n });\n\n return {\n ...interaction,\n 'aria-disabled': disabled || undefined,\n tabIndex: disabled ? -1 : (interaction.tabIndex ?? 0),\n ref,\n };\n}\n\n/**\n * Merge rule for Slot / asChild\n *\n * Precedence:\n * policy → user → child\n *\n * Event handlers are composed (policy first).\n * Refs are always composed.\n * Policy props MUST take precedence to enforce invariants.\n */\nexport function mergeInteractionProps(\n childProps: Record<string, unknown>,\n policyProps: Record<string, unknown>,\n userProps?: Record<string, unknown>\n) {\n let out = mergePropsBase(childProps, policyProps);\n if (userProps) out = mergePropsBase(out, userProps);\n\n // Ensure policy handlers always run first\n for (const k in out) {\n if (!k.startsWith('on')) continue;\n\n const policyHandler = policyProps?.[k];\n const userHandler = userProps?.[k];\n const childHandler = childProps?.[k];\n\n if (policyHandler || userHandler || childHandler) {\n const toHandler = (h: unknown) =>\n typeof h === 'function'\n ? (h as (...args: readonly unknown[]) => void)\n : undefined;\n\n out[k] = composeHandlers(\n toHandler(policyHandler),\n composeHandlers(toHandler(userHandler), toHandler(childHandler))\n );\n }\n }\n\n const childRef = childProps?.ref as Ref<unknown> | undefined;\n const userRef = userProps?.ref as Ref<unknown> | undefined;\n const policyRef = policyProps?.ref as Ref<unknown> | undefined;\n\n out.ref = composeRefs(childRef, userRef, policyRef);\n\n return out;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4EA,SAAgB,uBAAuB,EACrC,UACA,UACA,SACA,OACyB;CAGzB,SAAS,YAAY,GAAU;EAC7B,IAAI,UAAU;GACZ,EAAE,kBAAkB;GACpB;;EAEF,UAAU,EAAE;;CAGd,IAAI,UACF,OAAO;EACL,UAAU,YAAY;EACtB,UAAU,MAAa,YAAY,EAAE;EACrC;EACD;CAIH,MAAM,cAAc,UAAU;EAC5B;EACA,gBAAgB;EAChB,UAAU,MAAM,YAAY,EAAW;EACxC,CAAC;CAEF,OAAO;EACL,GAAG;EACH,iBAAiB,YAAY;EAC7B,UAAU,WAAW,KAAM,YAAY,YAAY;EACnD;EACD;;;;;;;;;;;;AAaH,SAAgB,sBACd,YACA,aACA,WACA;CACA,IAAI,MAAMA,WAAe,YAAY,YAAY;CACjD,IAAI,WAAW,MAAMA,WAAe,KAAK,UAAU;CAGnD,KAAK,MAAM,KAAK,KAAK;EACnB,IAAI,CAAC,EAAE,WAAW,KAAK,EAAE;EAEzB,MAAM,gBAAgB,cAAc;EACpC,MAAM,cAAc,YAAY;EAChC,MAAM,eAAe,aAAa;EAElC,IAAI,iBAAiB,eAAe,cAAc;GAChD,MAAM,aAAa,MACjB,OAAO,MAAM,aACR,IACD;GAEN,IAAI,KAAK,gBACP,UAAU,cAAc,EACxB,gBAAgB,UAAU,YAAY,EAAE,UAAU,aAAa,CAAC,CACjE;;;CAIL,MAAM,WAAW,YAAY;CAC7B,MAAM,UAAU,WAAW;CAC3B,MAAM,YAAY,aAAa;CAE/B,IAAI,MAAM,YAAY,UAAU,SAAS,UAAU;CAEnD,OAAO"}
@@ -1 +1 @@
1
- {"version":3,"file":"pressable.js","names":[],"sources":["../../../src/foundations/interactions/pressable.ts"],"sourcesContent":["/**\n * pressable\n *\n * Interaction helper that produces VNode props for 'press' semantics.\n * - Pure and deterministic: no DOM construction or mutation here\n * - The runtime owns event attachment and scheduling\n * - This helper returns plain props (handlers) intended to be attached by the runtime\n *\n * Behaviour:\n * - For native buttons: only an `onClick` prop is provided (no ARIA or keyboard shims)\n * - For non-button elements: add `role=\"button\"` and `tabIndex` and keyboard handlers\n * - Activation: `Enter` activates on keydown, `Space` activates on keyup (matches native button)\n * - Disabled: handlers short-circuit and `aria-disabled` is set for all hosts\n *\n * POLICY DECISIONS (LOCKED):\n *\n * 1. Activation Timing (Platform Parity)\n * - Enter fires on keydown (immediate response)\n * - Space fires on keyup (allows cancel by moving focus, matches native)\n * - Space keydown prevents scroll (matches native button behavior)\n *\n * 2. Disabled Enforcement Strategy\n * - Native buttons: Use HTML `disabled` attribute (platform-enforced non-interactivity)\n * AND `aria-disabled` (consistent a11y signaling)\n * - Non-native: Use `tabIndex=-1` (removes from tab order)\n * AND `aria-disabled` (signals disabled state to AT)\n * - Click handler short-circuits as defense-in-depth (prevents leaked focus issues)\n *\n * 3. Key Repeat Behavior\n * - Held Enter/Space will fire onPress repeatedly (matches native button)\n * - No debouncing or repeat prevention (platform parity)\n */\n\nexport interface PressableOptions {\n disabled?: boolean;\n onPress?: (e: PressEvent) => void;\n /**\n * Whether the host is a native button. Defaults to false.\n */\n isNativeButton?: boolean;\n}\n\nimport type {\n DefaultPreventable,\n KeyboardLikeEvent,\n PropagationStoppable,\n} from '../utilities/event-types';\nimport { ariaDisabled } from '../utilities/aria';\n\ntype PressEvent = DefaultPreventable & PropagationStoppable;\n\nexport interface PressableResult {\n onClick: (e: PressEvent) => void;\n disabled?: true;\n role?: 'button';\n tabIndex?: number;\n onKeyDown?: (e: KeyboardLikeEvent) => void;\n onKeyUp?: (e: KeyboardLikeEvent) => void;\n 'aria-disabled'?: 'true';\n}\n\nexport function pressable({\n disabled,\n onPress,\n isNativeButton = false,\n}: PressableOptions): PressableResult {\n const props: PressableResult = {\n onClick: (e) => {\n if (disabled) {\n e.preventDefault?.();\n e.stopPropagation?.();\n return;\n }\n onPress?.(e);\n },\n };\n\n if (isNativeButton) {\n if (disabled) {\n props.disabled = true;\n Object.assign(props, ariaDisabled(disabled));\n }\n return props;\n }\n\n props.role = 'button';\n props.tabIndex = disabled ? -1 : 0;\n\n props.onKeyDown = (e) => {\n if (disabled) {\n e.preventDefault?.();\n e.stopPropagation?.();\n return;\n }\n\n if (e.key === 'Enter') {\n e.preventDefault?.();\n onPress?.(e);\n return;\n }\n\n if (e.key === ' ') {\n // Prevent scrolling while Space is held.\n e.preventDefault?.();\n }\n };\n\n props.onKeyUp = (e) => {\n if (disabled) {\n e.preventDefault?.();\n e.stopPropagation?.();\n return;\n }\n if (e.key === ' ') {\n e.preventDefault?.();\n onPress?.(e);\n }\n };\n\n if (disabled) Object.assign(props, ariaDisabled(disabled));\n return props;\n}\n"],"mappings":";;AA6DA,SAAgB,UAAU,EACxB,UACA,SACA,iBAAiB,SACmB;CACpC,MAAM,QAAyB,EAC7B,UAAU,MAAM;AACd,MAAI,UAAU;AACZ,KAAE,kBAAkB;AACpB,KAAE,mBAAmB;AACrB;;AAEF,YAAU,EAAE;IAEf;AAED,KAAI,gBAAgB;AAClB,MAAI,UAAU;AACZ,SAAM,WAAW;AACjB,UAAO,OAAO,OAAO,aAAa,SAAS,CAAC;;AAE9C,SAAO;;AAGT,OAAM,OAAO;AACb,OAAM,WAAW,WAAW,KAAK;AAEjC,OAAM,aAAa,MAAM;AACvB,MAAI,UAAU;AACZ,KAAE,kBAAkB;AACpB,KAAE,mBAAmB;AACrB;;AAGF,MAAI,EAAE,QAAQ,SAAS;AACrB,KAAE,kBAAkB;AACpB,aAAU,EAAE;AACZ;;AAGF,MAAI,EAAE,QAAQ,IAEZ,GAAE,kBAAkB;;AAIxB,OAAM,WAAW,MAAM;AACrB,MAAI,UAAU;AACZ,KAAE,kBAAkB;AACpB,KAAE,mBAAmB;AACrB;;AAEF,MAAI,EAAE,QAAQ,KAAK;AACjB,KAAE,kBAAkB;AACpB,aAAU,EAAE;;;AAIhB,KAAI,SAAU,QAAO,OAAO,OAAO,aAAa,SAAS,CAAC;AAC1D,QAAO"}
1
+ {"version":3,"file":"pressable.js","names":[],"sources":["../../../src/foundations/interactions/pressable.ts"],"sourcesContent":["/**\n * pressable\n *\n * Interaction helper that produces VNode props for 'press' semantics.\n * - Pure and deterministic: no DOM construction or mutation here\n * - The runtime owns event attachment and scheduling\n * - This helper returns plain props (handlers) intended to be attached by the runtime\n *\n * Behaviour:\n * - For native buttons: only an `onClick` prop is provided (no ARIA or keyboard shims)\n * - For non-button elements: add `role=\"button\"` and `tabIndex` and keyboard handlers\n * - Activation: `Enter` activates on keydown, `Space` activates on keyup (matches native button)\n * - Disabled: handlers short-circuit and `aria-disabled` is set for all hosts\n *\n * POLICY DECISIONS (LOCKED):\n *\n * 1. Activation Timing (Platform Parity)\n * - Enter fires on keydown (immediate response)\n * - Space fires on keyup (allows cancel by moving focus, matches native)\n * - Space keydown prevents scroll (matches native button behavior)\n *\n * 2. Disabled Enforcement Strategy\n * - Native buttons: Use HTML `disabled` attribute (platform-enforced non-interactivity)\n * AND `aria-disabled` (consistent a11y signaling)\n * - Non-native: Use `tabIndex=-1` (removes from tab order)\n * AND `aria-disabled` (signals disabled state to AT)\n * - Click handler short-circuits as defense-in-depth (prevents leaked focus issues)\n *\n * 3. Key Repeat Behavior\n * - Held Enter/Space will fire onPress repeatedly (matches native button)\n * - No debouncing or repeat prevention (platform parity)\n */\n\nexport interface PressableOptions {\n disabled?: boolean;\n onPress?: (e: PressEvent) => void;\n /**\n * Whether the host is a native button. Defaults to false.\n */\n isNativeButton?: boolean;\n}\n\nimport type {\n DefaultPreventable,\n KeyboardLikeEvent,\n PropagationStoppable,\n} from '../utilities/event-types';\nimport { ariaDisabled } from '../utilities/aria';\n\ntype PressEvent = DefaultPreventable & PropagationStoppable;\n\nexport interface PressableResult {\n onClick: (e: PressEvent) => void;\n disabled?: true;\n role?: 'button';\n tabIndex?: number;\n onKeyDown?: (e: KeyboardLikeEvent) => void;\n onKeyUp?: (e: KeyboardLikeEvent) => void;\n 'aria-disabled'?: 'true';\n}\n\nexport function pressable({\n disabled,\n onPress,\n isNativeButton = false,\n}: PressableOptions): PressableResult {\n const props: PressableResult = {\n onClick: (e) => {\n if (disabled) {\n e.preventDefault?.();\n e.stopPropagation?.();\n return;\n }\n onPress?.(e);\n },\n };\n\n if (isNativeButton) {\n if (disabled) {\n props.disabled = true;\n Object.assign(props, ariaDisabled(disabled));\n }\n return props;\n }\n\n props.role = 'button';\n props.tabIndex = disabled ? -1 : 0;\n\n props.onKeyDown = (e) => {\n if (disabled) {\n e.preventDefault?.();\n e.stopPropagation?.();\n return;\n }\n\n if (e.key === 'Enter') {\n e.preventDefault?.();\n onPress?.(e);\n return;\n }\n\n if (e.key === ' ') {\n // Prevent scrolling while Space is held.\n e.preventDefault?.();\n }\n };\n\n props.onKeyUp = (e) => {\n if (disabled) {\n e.preventDefault?.();\n e.stopPropagation?.();\n return;\n }\n if (e.key === ' ') {\n e.preventDefault?.();\n onPress?.(e);\n }\n };\n\n if (disabled) Object.assign(props, ariaDisabled(disabled));\n return props;\n}\n"],"mappings":";;AA6DA,SAAgB,UAAU,EACxB,UACA,SACA,iBAAiB,SACmB;CACpC,MAAM,QAAyB,EAC7B,UAAU,MAAM;EACd,IAAI,UAAU;GACZ,EAAE,kBAAkB;GACpB,EAAE,mBAAmB;GACrB;;EAEF,UAAU,EAAE;IAEf;CAED,IAAI,gBAAgB;EAClB,IAAI,UAAU;GACZ,MAAM,WAAW;GACjB,OAAO,OAAO,OAAO,aAAa,SAAS,CAAC;;EAE9C,OAAO;;CAGT,MAAM,OAAO;CACb,MAAM,WAAW,WAAW,KAAK;CAEjC,MAAM,aAAa,MAAM;EACvB,IAAI,UAAU;GACZ,EAAE,kBAAkB;GACpB,EAAE,mBAAmB;GACrB;;EAGF,IAAI,EAAE,QAAQ,SAAS;GACrB,EAAE,kBAAkB;GACpB,UAAU,EAAE;GACZ;;EAGF,IAAI,EAAE,QAAQ,KAEZ,EAAE,kBAAkB;;CAIxB,MAAM,WAAW,MAAM;EACrB,IAAI,UAAU;GACZ,EAAE,kBAAkB;GACpB,EAAE,mBAAmB;GACrB;;EAEF,IAAI,EAAE,QAAQ,KAAK;GACjB,EAAE,kBAAkB;GACpB,UAAU,EAAE;;;CAIhB,IAAI,UAAU,OAAO,OAAO,OAAO,aAAa,SAAS,CAAC;CAC1D,OAAO"}
@@ -1 +1 @@
1
- {"version":3,"file":"roving-focus.js","names":[],"sources":["../../../src/foundations/interactions/roving-focus.ts"],"sourcesContent":["/**\n * rovingFocus\n *\n * Single tab stop navigation with arrow-key control.\n *\n * INVARIANTS:\n * 1. Only one item in the group is reachable via Tab (single tab stop)\n * 2. Arrow keys move focus within the group\n * 3. Orientation determines which arrow keys are active\n * 4. Looping is opt-in\n * 5. Disabled items are skipped\n * 6. Returns props objects, never factories (composes via mergeProps)\n *\n * DESIGN:\n * - Container gets onKeyDown for arrow navigation\n * - Each item gets tabIndex based on current selection\n * - Navigation logic is pure - caller controls focus application\n * - Disabled check happens per-item via predicate\n *\n * PIT OF SUCCESS:\n * ✓ Can't accidentally break tab order (tabIndex assigned correctly)\n * ✓ Can't duplicate navigation logic (single source)\n * ✓ Composes via mergeProps (all standard props)\n * ✓ Type-safe - invalid indices caught at call site\n *\n * USAGE:\n * const nav = rovingFocus({\n * currentIndex: 0,\n * itemCount: 3,\n * orientation: 'horizontal',\n * onNavigate: setIndex\n * });\n *\n * <div {...nav.container}>\n * <button {...nav.item(0)}>First</button>\n * <button {...nav.item(1)}>Second</button>\n * </div>\n *\n * MISUSE EXAMPLE (PREVENTED):\n * ❌ Can't forget to set tabIndex - returned in item props\n * ❌ Can't create conflicting arrow handlers - mergeProps composes\n * ❌ Can't skip disabled items incorrectly - logic is internal\n */\n\nimport type { KeyboardLikeEvent } from '../utilities/event-types';\n\nexport type Orientation = 'horizontal' | 'vertical' | 'both';\n\nexport interface RovingFocusOptions {\n /**\n * Current focused index\n */\n currentIndex: number;\n\n /**\n * Total number of items\n */\n itemCount: number;\n\n /**\n * Navigation orientation\n * - horizontal: ArrowLeft/ArrowRight\n * - vertical: ArrowUp/ArrowDown\n * - both: all arrow keys\n */\n orientation?: Orientation;\n\n /**\n * Whether to loop when reaching the end\n */\n loop?: boolean;\n\n /**\n * Callback when navigation occurs\n */\n onNavigate?: (index: number) => void;\n\n /**\n * Optional disabled state check per index\n */\n isDisabled?: (index: number) => boolean;\n}\n\nexport interface RovingFocusResult {\n /**\n * Props for the container element (composes via mergeProps)\n */\n container: {\n onKeyDown: (e: KeyboardLikeEvent) => void;\n };\n\n /**\n * Generate props for an item at the given index (composes via mergeProps)\n */\n item: (index: number) => {\n tabIndex: number;\n 'data-roving-index': number;\n };\n}\n\nexport function rovingFocus(options: RovingFocusOptions): RovingFocusResult {\n const {\n currentIndex,\n itemCount,\n orientation = 'horizontal',\n loop = false,\n onNavigate,\n isDisabled,\n } = options;\n\n function findNextIndex(from: number, direction: 1 | -1): number | undefined {\n let next = from + direction;\n\n // Handle looping\n if (loop) {\n if (next < 0) next = itemCount - 1;\n if (next >= itemCount) next = 0;\n } else {\n if (next < 0 || next >= itemCount) return undefined;\n }\n\n // Skip disabled items\n if (isDisabled?.(next)) {\n // Recursively find the next non-disabled item\n if (next === from) return undefined; // Prevent infinite loop\n return findNextIndex(next, direction);\n }\n\n return next;\n }\n\n function handleKeyDown(e: KeyboardLikeEvent) {\n const { key } = e;\n\n let direction: 1 | -1 | undefined;\n\n if (orientation === 'horizontal' || orientation === 'both') {\n if (key === 'ArrowRight') direction = 1;\n if (key === 'ArrowLeft') direction = -1;\n }\n\n if (orientation === 'vertical' || orientation === 'both') {\n if (key === 'ArrowDown') direction = 1;\n if (key === 'ArrowUp') direction = -1;\n }\n\n if (direction === undefined) return;\n\n const nextIndex = findNextIndex(currentIndex, direction);\n if (nextIndex === undefined) return;\n\n e.preventDefault?.();\n e.stopPropagation?.();\n\n onNavigate?.(nextIndex);\n }\n\n return {\n container: {\n onKeyDown: handleKeyDown,\n },\n item: (index: number) => ({\n tabIndex: index === currentIndex ? 0 : -1,\n 'data-roving-index': index,\n }),\n };\n}\n\n/**\n * USAGE EXAMPLE:\n *\n * function Menu() {\n * const [focusIndex, setFocusIndex] = state(0);\n * const items = ['File', 'Edit', 'View'];\n *\n * const navigation = rovingFocus({\n * currentIndex: focusIndex(),\n * itemCount: items.length,\n * orientation: 'horizontal',\n * loop: true,\n * onNavigate: setFocusIndex,\n * });\n *\n * return (\n * <div {...navigation.container}>\n * {items.map((label, index) => (\n * <button {...navigation.item(index)}>\n * {label}\n * </button>\n * ))}\n * </div>\n * );\n * }\n */\n"],"mappings":";AAoGA,SAAgB,YAAY,SAAgD;CAC1E,MAAM,EACJ,cACA,WACA,cAAc,cACd,OAAO,OACP,YACA,eACE;CAEJ,SAAS,cAAc,MAAc,WAAuC;EAC1E,IAAI,OAAO,OAAO;AAGlB,MAAI,MAAM;AACR,OAAI,OAAO,EAAG,QAAO,YAAY;AACjC,OAAI,QAAQ,UAAW,QAAO;aAE1B,OAAO,KAAK,QAAQ,UAAW,QAAO;AAI5C,MAAI,aAAa,KAAK,EAAE;AAEtB,OAAI,SAAS,KAAM,QAAO;AAC1B,UAAO,cAAc,MAAM,UAAU;;AAGvC,SAAO;;CAGT,SAAS,cAAc,GAAsB;EAC3C,MAAM,EAAE,QAAQ;EAEhB,IAAI;AAEJ,MAAI,gBAAgB,gBAAgB,gBAAgB,QAAQ;AAC1D,OAAI,QAAQ,aAAc,aAAY;AACtC,OAAI,QAAQ,YAAa,aAAY;;AAGvC,MAAI,gBAAgB,cAAc,gBAAgB,QAAQ;AACxD,OAAI,QAAQ,YAAa,aAAY;AACrC,OAAI,QAAQ,UAAW,aAAY;;AAGrC,MAAI,cAAc,OAAW;EAE7B,MAAM,YAAY,cAAc,cAAc,UAAU;AACxD,MAAI,cAAc,OAAW;AAE7B,IAAE,kBAAkB;AACpB,IAAE,mBAAmB;AAErB,eAAa,UAAU;;AAGzB,QAAO;EACL,WAAW,EACT,WAAW,eACZ;EACD,OAAO,WAAmB;GACxB,UAAU,UAAU,eAAe,IAAI;GACvC,qBAAqB;GACtB;EACF"}
1
+ {"version":3,"file":"roving-focus.js","names":[],"sources":["../../../src/foundations/interactions/roving-focus.ts"],"sourcesContent":["/**\n * rovingFocus\n *\n * Single tab stop navigation with arrow-key control.\n *\n * INVARIANTS:\n * 1. Only one item in the group is reachable via Tab (single tab stop)\n * 2. Arrow keys move focus within the group\n * 3. Orientation determines which arrow keys are active\n * 4. Looping is opt-in\n * 5. Disabled items are skipped\n * 6. Returns props objects, never factories (composes via mergeProps)\n *\n * DESIGN:\n * - Container gets onKeyDown for arrow navigation\n * - Each item gets tabIndex based on current selection\n * - Navigation logic is pure - caller controls focus application\n * - Disabled check happens per-item via predicate\n *\n * PIT OF SUCCESS:\n * ✓ Can't accidentally break tab order (tabIndex assigned correctly)\n * ✓ Can't duplicate navigation logic (single source)\n * ✓ Composes via mergeProps (all standard props)\n * ✓ Type-safe - invalid indices caught at call site\n *\n * USAGE:\n * const nav = rovingFocus({\n * currentIndex: 0,\n * itemCount: 3,\n * orientation: 'horizontal',\n * onNavigate: setIndex\n * });\n *\n * <div {...nav.container}>\n * <button {...nav.item(0)}>First</button>\n * <button {...nav.item(1)}>Second</button>\n * </div>\n *\n * MISUSE EXAMPLE (PREVENTED):\n * ❌ Can't forget to set tabIndex - returned in item props\n * ❌ Can't create conflicting arrow handlers - mergeProps composes\n * ❌ Can't skip disabled items incorrectly - logic is internal\n */\n\nimport type { KeyboardLikeEvent } from '../utilities/event-types';\n\nexport type Orientation = 'horizontal' | 'vertical' | 'both';\n\nexport interface RovingFocusOptions {\n /**\n * Current focused index\n */\n currentIndex: number;\n\n /**\n * Total number of items\n */\n itemCount: number;\n\n /**\n * Navigation orientation\n * - horizontal: ArrowLeft/ArrowRight\n * - vertical: ArrowUp/ArrowDown\n * - both: all arrow keys\n */\n orientation?: Orientation;\n\n /**\n * Whether to loop when reaching the end\n */\n loop?: boolean;\n\n /**\n * Callback when navigation occurs\n */\n onNavigate?: (index: number) => void;\n\n /**\n * Optional disabled state check per index\n */\n isDisabled?: (index: number) => boolean;\n}\n\nexport interface RovingFocusResult {\n /**\n * Props for the container element (composes via mergeProps)\n */\n container: {\n onKeyDown: (e: KeyboardLikeEvent) => void;\n };\n\n /**\n * Generate props for an item at the given index (composes via mergeProps)\n */\n item: (index: number) => {\n tabIndex: number;\n 'data-roving-index': number;\n };\n}\n\nexport function rovingFocus(options: RovingFocusOptions): RovingFocusResult {\n const {\n currentIndex,\n itemCount,\n orientation = 'horizontal',\n loop = false,\n onNavigate,\n isDisabled,\n } = options;\n\n function findNextIndex(from: number, direction: 1 | -1): number | undefined {\n let next = from + direction;\n\n // Handle looping\n if (loop) {\n if (next < 0) next = itemCount - 1;\n if (next >= itemCount) next = 0;\n } else {\n if (next < 0 || next >= itemCount) return undefined;\n }\n\n // Skip disabled items\n if (isDisabled?.(next)) {\n // Recursively find the next non-disabled item\n if (next === from) return undefined; // Prevent infinite loop\n return findNextIndex(next, direction);\n }\n\n return next;\n }\n\n function handleKeyDown(e: KeyboardLikeEvent) {\n const { key } = e;\n\n let direction: 1 | -1 | undefined;\n\n if (orientation === 'horizontal' || orientation === 'both') {\n if (key === 'ArrowRight') direction = 1;\n if (key === 'ArrowLeft') direction = -1;\n }\n\n if (orientation === 'vertical' || orientation === 'both') {\n if (key === 'ArrowDown') direction = 1;\n if (key === 'ArrowUp') direction = -1;\n }\n\n if (direction === undefined) return;\n\n const nextIndex = findNextIndex(currentIndex, direction);\n if (nextIndex === undefined) return;\n\n e.preventDefault?.();\n e.stopPropagation?.();\n\n onNavigate?.(nextIndex);\n }\n\n return {\n container: {\n onKeyDown: handleKeyDown,\n },\n item: (index: number) => ({\n tabIndex: index === currentIndex ? 0 : -1,\n 'data-roving-index': index,\n }),\n };\n}\n\n/**\n * USAGE EXAMPLE:\n *\n * function Menu() {\n * const [focusIndex, setFocusIndex] = state(0);\n * const items = ['File', 'Edit', 'View'];\n *\n * const navigation = rovingFocus({\n * currentIndex: focusIndex(),\n * itemCount: items.length,\n * orientation: 'horizontal',\n * loop: true,\n * onNavigate: setFocusIndex,\n * });\n *\n * return (\n * <div {...navigation.container}>\n * {items.map((label, index) => (\n * <button {...navigation.item(index)}>\n * {label}\n * </button>\n * ))}\n * </div>\n * );\n * }\n */\n"],"mappings":";AAoGA,SAAgB,YAAY,SAAgD;CAC1E,MAAM,EACJ,cACA,WACA,cAAc,cACd,OAAO,OACP,YACA,eACE;CAEJ,SAAS,cAAc,MAAc,WAAuC;EAC1E,IAAI,OAAO,OAAO;EAGlB,IAAI,MAAM;GACR,IAAI,OAAO,GAAG,OAAO,YAAY;GACjC,IAAI,QAAQ,WAAW,OAAO;SAE9B,IAAI,OAAO,KAAK,QAAQ,WAAW,OAAO;EAI5C,IAAI,aAAa,KAAK,EAAE;GAEtB,IAAI,SAAS,MAAM,OAAO;GAC1B,OAAO,cAAc,MAAM,UAAU;;EAGvC,OAAO;;CAGT,SAAS,cAAc,GAAsB;EAC3C,MAAM,EAAE,QAAQ;EAEhB,IAAI;EAEJ,IAAI,gBAAgB,gBAAgB,gBAAgB,QAAQ;GAC1D,IAAI,QAAQ,cAAc,YAAY;GACtC,IAAI,QAAQ,aAAa,YAAY;;EAGvC,IAAI,gBAAgB,cAAc,gBAAgB,QAAQ;GACxD,IAAI,QAAQ,aAAa,YAAY;GACrC,IAAI,QAAQ,WAAW,YAAY;;EAGrC,IAAI,cAAc,QAAW;EAE7B,MAAM,YAAY,cAAc,cAAc,UAAU;EACxD,IAAI,cAAc,QAAW;EAE7B,EAAE,kBAAkB;EACpB,EAAE,mBAAmB;EAErB,aAAa,UAAU;;CAGzB,OAAO;EACL,WAAW,EACT,WAAW,eACZ;EACD,OAAO,WAAmB;GACxB,UAAU,UAAU,eAAe,IAAI;GACvC,qBAAqB;GACtB;EACF"}