@askrjs/askr 0.0.6 → 0.0.8

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.
@@ -1,6 +1,194 @@
1
- // src/foundations/layout.tsx
1
+ // src/foundations/structures/layout.tsx
2
2
  function layout(Layout) {
3
- return (children, props) => Layout({ ...props, children });
3
+ return (children, props) => {
4
+ const mergedProps = { ...props, children };
5
+ return Layout(mergedProps);
6
+ };
7
+ }
8
+
9
+ // src/common/jsx.ts
10
+ var ELEMENT_TYPE = /* @__PURE__ */ Symbol.for("askr.element");
11
+ var Fragment = /* @__PURE__ */ Symbol.for("askr.fragment");
12
+
13
+ // src/jsx/utils.ts
14
+ function isElement(value) {
15
+ return typeof value === "object" && value !== null && value.$$typeof === ELEMENT_TYPE;
16
+ }
17
+ function cloneElement(element, props) {
18
+ return {
19
+ ...element,
20
+ props: { ...element.props, ...props }
21
+ };
22
+ }
23
+
24
+ // src/foundations/structures/slot.tsx
25
+ function Slot(props) {
26
+ if (props.asChild) {
27
+ const { children, asChild: _asChild, ...rest } = props;
28
+ if (isElement(children)) {
29
+ return cloneElement(children, rest);
30
+ }
31
+ return null;
32
+ }
33
+ const element = {
34
+ $$typeof: ELEMENT_TYPE,
35
+ type: Fragment,
36
+ props: { children: props.children },
37
+ key: null
38
+ };
39
+ return element;
40
+ }
41
+
42
+ // src/foundations/structures/presence.ts
43
+ function Presence({
44
+ present,
45
+ children
46
+ }) {
47
+ const isPresent = typeof present === "function" ? present() : Boolean(present);
48
+ if (!isPresent) return null;
49
+ const element = {
50
+ $$typeof: ELEMENT_TYPE,
51
+ type: Fragment,
52
+ props: { children },
53
+ key: null
54
+ };
55
+ return element;
56
+ }
57
+
58
+ // src/foundations/structures/portal.tsx
59
+ function definePortal() {
60
+ if (typeof createPortalSlot === "function") {
61
+ let PortalHost2 = function() {
62
+ return slot.read();
63
+ };
64
+ const slot = createPortalSlot();
65
+ PortalHost2.render = function PortalRender(props) {
66
+ slot.write(props.children);
67
+ return null;
68
+ };
69
+ return PortalHost2;
70
+ }
71
+ let mounted = false;
72
+ let value;
73
+ function PortalHostFallback() {
74
+ mounted = true;
75
+ return value;
76
+ }
77
+ PortalHostFallback.render = function PortalRenderFallback(props) {
78
+ if (!mounted) return null;
79
+ value = props.children;
80
+ return null;
81
+ };
82
+ return PortalHostFallback;
83
+ }
84
+ var _defaultPortal;
85
+ function ensureDefaultPortal() {
86
+ if (!_defaultPortal) _defaultPortal = definePortal();
87
+ return _defaultPortal;
88
+ }
89
+ var DefaultPortal = (() => {
90
+ function Host() {
91
+ const v = ensureDefaultPortal()();
92
+ return v === void 0 ? null : v;
93
+ }
94
+ Host.render = function Render(props) {
95
+ ensureDefaultPortal().render(props);
96
+ return null;
97
+ };
98
+ return Host;
99
+ })();
100
+
101
+ // src/foundations/utilities/composeHandlers.ts
102
+ function isDefaultPrevented(value) {
103
+ return typeof value === "object" && value !== null && "defaultPrevented" in value && value.defaultPrevented === true;
104
+ }
105
+ function composeHandlers(first, second, options) {
106
+ if (!first && !second) {
107
+ return noop;
108
+ }
109
+ if (!first) return second;
110
+ if (!second) return first;
111
+ const checkDefaultPrevented = options?.checkDefaultPrevented !== false;
112
+ return function composed(...args) {
113
+ first(...args);
114
+ if (checkDefaultPrevented && isDefaultPrevented(args[0])) {
115
+ return;
116
+ }
117
+ second(...args);
118
+ };
119
+ }
120
+ function noop() {
121
+ }
122
+
123
+ // src/foundations/utilities/mergeProps.ts
124
+ function isEventHandlerKey(key) {
125
+ return key.startsWith("on");
126
+ }
127
+ function mergeProps(base, injected) {
128
+ const baseKeys = Object.keys(base);
129
+ if (baseKeys.length === 0) {
130
+ return injected;
131
+ }
132
+ const out = { ...injected };
133
+ for (const key of baseKeys) {
134
+ const baseValue = base[key];
135
+ const injectedValue = injected[key];
136
+ if (isEventHandlerKey(key) && typeof baseValue === "function" && typeof injectedValue === "function") {
137
+ out[key] = composeHandlers(
138
+ injectedValue,
139
+ baseValue
140
+ );
141
+ continue;
142
+ }
143
+ out[key] = baseValue;
144
+ }
145
+ return out;
146
+ }
147
+
148
+ // src/foundations/utilities/aria.ts
149
+ function ariaDisabled(disabled) {
150
+ return disabled ? { "aria-disabled": "true" } : {};
151
+ }
152
+ function ariaExpanded(expanded) {
153
+ return expanded === void 0 ? {} : { "aria-expanded": String(expanded) };
154
+ }
155
+ function ariaSelected(selected) {
156
+ return selected === void 0 ? {} : { "aria-selected": String(selected) };
157
+ }
158
+
159
+ // src/foundations/utilities/composeRef.ts
160
+ function setRef(ref, value) {
161
+ if (!ref) return;
162
+ if (typeof ref === "function") {
163
+ ref(value);
164
+ return;
165
+ }
166
+ try {
167
+ ref.current = value;
168
+ } catch {
169
+ }
170
+ }
171
+ function composeRefs(...refs) {
172
+ return (value) => {
173
+ for (const ref of refs) setRef(ref, value);
174
+ };
175
+ }
176
+
177
+ // src/foundations/utilities/useId.ts
178
+ function useId(options) {
179
+ const prefix = options.prefix ?? "askr";
180
+ return `${prefix}-${String(options.id)}`;
181
+ }
182
+
183
+ // src/dev/invariant.ts
184
+ function invariant(condition, message, context) {
185
+ if (!condition) {
186
+ const contextStr = "";
187
+ throw new Error(`[Askr Invariant] ${message}${contextStr}`);
188
+ }
189
+ }
190
+ function assertSchedulingPrecondition(condition, violationMessage) {
191
+ invariant(condition, `[Scheduler Precondition] ${violationMessage}`);
4
192
  }
5
193
 
6
194
  // src/dev/logger.ts
@@ -33,114 +221,853 @@ var logger = {
33
221
  }
34
222
  };
35
223
 
36
- // src/common/jsx.ts
37
- var ELEMENT_TYPE = /* @__PURE__ */ Symbol.for("askr.element");
38
- var Fragment = /* @__PURE__ */ Symbol.for("askr.fragment");
224
+ // src/runtime/scheduler.ts
225
+ var MAX_FLUSH_DEPTH = 50;
226
+ function isBulkCommitActive() {
227
+ try {
228
+ const fb = globalThis.__ASKR_FASTLANE;
229
+ return typeof fb?.isBulkCommitActive === "function" ? !!fb.isBulkCommitActive() : false;
230
+ } catch (e) {
231
+ return false;
232
+ }
233
+ }
234
+ var Scheduler = class {
235
+ constructor() {
236
+ this.q = [];
237
+ this.head = 0;
238
+ this.running = false;
239
+ this.inHandler = false;
240
+ this.depth = 0;
241
+ this.executionDepth = 0;
242
+ // for compat with existing diagnostics
243
+ // Monotonic flush version increments at end of each flush
244
+ this.flushVersion = 0;
245
+ // Best-effort microtask kick scheduling
246
+ this.kickScheduled = false;
247
+ // Escape hatch flag for runWithSyncProgress
248
+ this.allowSyncProgress = false;
249
+ // Waiters waiting for flushVersion >= target
250
+ this.waiters = [];
251
+ // Keep a lightweight taskCount for compatibility/diagnostics
252
+ this.taskCount = 0;
253
+ }
254
+ enqueue(task) {
255
+ assertSchedulingPrecondition(
256
+ typeof task === "function",
257
+ "enqueue() requires a function"
258
+ );
259
+ if (isBulkCommitActive() && !this.allowSyncProgress) {
260
+ if (process.env.NODE_ENV !== "production") {
261
+ throw new Error(
262
+ "[Scheduler] enqueue() during bulk commit (not allowed)"
263
+ );
264
+ }
265
+ return;
266
+ }
267
+ this.q.push(task);
268
+ this.taskCount++;
269
+ if (!this.running && !this.kickScheduled && !this.inHandler && !isBulkCommitActive()) {
270
+ this.kickScheduled = true;
271
+ queueMicrotask(() => {
272
+ this.kickScheduled = false;
273
+ if (this.running) return;
274
+ if (isBulkCommitActive()) return;
275
+ try {
276
+ this.flush();
277
+ } catch (err) {
278
+ setTimeout(() => {
279
+ throw err;
280
+ });
281
+ }
282
+ });
283
+ }
284
+ }
285
+ flush() {
286
+ invariant(
287
+ !this.running,
288
+ "[Scheduler] flush() called while already running"
289
+ );
290
+ if (process.env.NODE_ENV !== "production") {
291
+ if (isBulkCommitActive() && !this.allowSyncProgress) {
292
+ throw new Error(
293
+ "[Scheduler] flush() started during bulk commit (not allowed)"
294
+ );
295
+ }
296
+ }
297
+ this.running = true;
298
+ this.depth = 0;
299
+ let fatal = null;
300
+ try {
301
+ while (this.head < this.q.length) {
302
+ this.depth++;
303
+ if (process.env.NODE_ENV !== "production" && this.depth > MAX_FLUSH_DEPTH) {
304
+ throw new Error(
305
+ `[Scheduler] exceeded MAX_FLUSH_DEPTH (${MAX_FLUSH_DEPTH}). Likely infinite update loop.`
306
+ );
307
+ }
308
+ const task = this.q[this.head++];
309
+ try {
310
+ this.executionDepth++;
311
+ task();
312
+ this.executionDepth--;
313
+ } catch (err) {
314
+ if (this.executionDepth > 0) this.executionDepth = 0;
315
+ fatal = err;
316
+ break;
317
+ }
318
+ if (this.taskCount > 0) this.taskCount--;
319
+ }
320
+ } finally {
321
+ this.running = false;
322
+ this.depth = 0;
323
+ this.executionDepth = 0;
324
+ if (this.head >= this.q.length) {
325
+ this.q.length = 0;
326
+ this.head = 0;
327
+ } else if (this.head > 0) {
328
+ const remaining = this.q.length - this.head;
329
+ for (let i = 0; i < remaining; i++) {
330
+ this.q[i] = this.q[this.head + i];
331
+ }
332
+ this.q.length = remaining;
333
+ this.head = 0;
334
+ }
335
+ this.flushVersion++;
336
+ this.resolveWaiters();
337
+ }
338
+ if (fatal) throw fatal;
339
+ }
340
+ runWithSyncProgress(fn) {
341
+ const prev = this.allowSyncProgress;
342
+ this.allowSyncProgress = true;
343
+ const g = globalThis;
344
+ const origQueueMicrotask = g.queueMicrotask;
345
+ const origSetTimeout = g.setTimeout;
346
+ if (process.env.NODE_ENV !== "production") {
347
+ g.queueMicrotask = () => {
348
+ throw new Error(
349
+ "[Scheduler] queueMicrotask not allowed during runWithSyncProgress"
350
+ );
351
+ };
352
+ g.setTimeout = () => {
353
+ throw new Error(
354
+ "[Scheduler] setTimeout not allowed during runWithSyncProgress"
355
+ );
356
+ };
357
+ }
358
+ const startVersion = this.flushVersion;
359
+ try {
360
+ const res = fn();
361
+ if (!this.running && this.q.length - this.head > 0) {
362
+ this.flush();
363
+ }
364
+ if (process.env.NODE_ENV !== "production") {
365
+ if (this.q.length - this.head > 0) {
366
+ throw new Error(
367
+ "[Scheduler] tasks remain after runWithSyncProgress flush"
368
+ );
369
+ }
370
+ }
371
+ return res;
372
+ } finally {
373
+ if (process.env.NODE_ENV !== "production") {
374
+ g.queueMicrotask = origQueueMicrotask;
375
+ g.setTimeout = origSetTimeout;
376
+ }
377
+ try {
378
+ if (this.flushVersion === startVersion) {
379
+ this.flushVersion++;
380
+ this.resolveWaiters();
381
+ }
382
+ } catch (e) {
383
+ }
384
+ this.allowSyncProgress = prev;
385
+ }
386
+ }
387
+ waitForFlush(targetVersion, timeoutMs = 2e3) {
388
+ const target = typeof targetVersion === "number" ? targetVersion : this.flushVersion + 1;
389
+ if (this.flushVersion >= target) return Promise.resolve();
390
+ return new Promise((resolve, reject) => {
391
+ const timer = setTimeout(() => {
392
+ const ns = globalThis.__ASKR__ || {};
393
+ const diag = {
394
+ flushVersion: this.flushVersion,
395
+ queueLen: this.q.length - this.head,
396
+ running: this.running,
397
+ inHandler: this.inHandler,
398
+ bulk: isBulkCommitActive(),
399
+ namespace: ns
400
+ };
401
+ reject(
402
+ new Error(
403
+ `waitForFlush timeout ${timeoutMs}ms: ${JSON.stringify(diag)}`
404
+ )
405
+ );
406
+ }, timeoutMs);
407
+ this.waiters.push({ target, resolve, reject, timer });
408
+ });
409
+ }
410
+ getState() {
411
+ return {
412
+ queueLength: this.q.length - this.head,
413
+ running: this.running,
414
+ depth: this.depth,
415
+ executionDepth: this.executionDepth,
416
+ taskCount: this.taskCount,
417
+ flushVersion: this.flushVersion,
418
+ // New fields for optional inspection
419
+ inHandler: this.inHandler,
420
+ allowSyncProgress: this.allowSyncProgress
421
+ };
422
+ }
423
+ setInHandler(v) {
424
+ this.inHandler = v;
425
+ }
426
+ isInHandler() {
427
+ return this.inHandler;
428
+ }
429
+ isExecuting() {
430
+ return this.running || this.executionDepth > 0;
431
+ }
432
+ // Clear pending synchronous tasks (used by fastlane enter/exit)
433
+ clearPendingSyncTasks() {
434
+ const remaining = this.q.length - this.head;
435
+ if (remaining <= 0) return 0;
436
+ if (this.running) {
437
+ this.q.length = this.head;
438
+ this.taskCount = Math.max(0, this.taskCount - remaining);
439
+ queueMicrotask(() => {
440
+ try {
441
+ this.flushVersion++;
442
+ this.resolveWaiters();
443
+ } catch (e) {
444
+ }
445
+ });
446
+ return remaining;
447
+ }
448
+ this.q.length = 0;
449
+ this.head = 0;
450
+ this.taskCount = Math.max(0, this.taskCount - remaining);
451
+ this.flushVersion++;
452
+ this.resolveWaiters();
453
+ return remaining;
454
+ }
455
+ resolveWaiters() {
456
+ if (this.waiters.length === 0) return;
457
+ const ready = [];
458
+ const remaining = [];
459
+ for (const w of this.waiters) {
460
+ if (this.flushVersion >= w.target) {
461
+ if (w.timer) clearTimeout(w.timer);
462
+ ready.push(w.resolve);
463
+ } else {
464
+ remaining.push(w);
465
+ }
466
+ }
467
+ this.waiters = remaining;
468
+ for (const r of ready) r();
469
+ }
470
+ };
471
+ var globalScheduler = new Scheduler();
39
472
 
40
- // src/jsx/utils.ts
41
- function isElement(value) {
42
- return typeof value === "object" && value !== null && value.$$typeof === ELEMENT_TYPE;
473
+ // src/renderer/utils.ts
474
+ function isIgnoredForPropChanges(key) {
475
+ if (key === "children" || key === "key") return true;
476
+ if (key.startsWith("on") && key.length > 2) return true;
477
+ if (key.startsWith("data-")) return true;
478
+ return false;
43
479
  }
44
- function cloneElement(element, props) {
45
- return {
46
- ...element,
47
- props: { ...element.props, ...props }
48
- };
480
+ function hasPropChanged(el, key, value) {
481
+ try {
482
+ if (key === "class" || key === "className") {
483
+ return el.className !== String(value);
484
+ }
485
+ if (key === "value" || key === "checked") {
486
+ return el[key] !== value;
487
+ }
488
+ const attr = el.getAttribute(key);
489
+ if (value === void 0 || value === null || value === false) {
490
+ return attr !== null;
491
+ }
492
+ return String(value) !== attr;
493
+ } catch {
494
+ return true;
495
+ }
496
+ }
497
+ function hasNonTrivialProps(props) {
498
+ for (const k of Object.keys(props)) {
499
+ if (isIgnoredForPropChanges(k)) continue;
500
+ return true;
501
+ }
502
+ return false;
503
+ }
504
+ function extractKey(vnode) {
505
+ if (typeof vnode !== "object" || vnode === null) return void 0;
506
+ const obj = vnode;
507
+ const rawKey = obj.key ?? obj.props?.key;
508
+ if (rawKey === void 0) return void 0;
509
+ return typeof rawKey === "symbol" ? String(rawKey) : rawKey;
510
+ }
511
+ function buildKeyMapFromChildren(parent) {
512
+ const map = /* @__PURE__ */ new Map();
513
+ for (let ch = parent.firstElementChild; ch; ch = ch.nextElementSibling) {
514
+ const k = ch.getAttribute("data-key");
515
+ if (k !== null) {
516
+ map.set(k, ch);
517
+ const n = Number(k);
518
+ if (!Number.isNaN(n)) map.set(n, ch);
519
+ }
520
+ }
521
+ return map;
49
522
  }
50
523
 
51
- // src/foundations/slot.tsx
52
- function Slot(props) {
53
- if (props.asChild) {
54
- const { children, ...rest } = props;
55
- if (isElement(children)) {
56
- return cloneElement(children, rest);
524
+ // src/renderer/keyed.ts
525
+ var keyedElements = /* @__PURE__ */ new WeakMap();
526
+ function getKeyMapForElement(el) {
527
+ return keyedElements.get(el);
528
+ }
529
+ function populateKeyMapForElement(parent) {
530
+ try {
531
+ if (keyedElements.has(parent)) return;
532
+ let domMap = buildKeyMapFromChildren(parent);
533
+ if (domMap.size === 0) {
534
+ domMap = /* @__PURE__ */ new Map();
535
+ const children = Array.from(parent.children);
536
+ for (const ch of children) {
537
+ const text = (ch.textContent || "").trim();
538
+ if (text) {
539
+ domMap.set(text, ch);
540
+ const n = Number(text);
541
+ if (!Number.isNaN(n)) domMap.set(n, ch);
542
+ }
543
+ }
544
+ }
545
+ if (domMap.size > 0) keyedElements.set(parent, domMap);
546
+ } catch {
547
+ }
548
+ }
549
+ function extractKeyedVnodes(newChildren) {
550
+ const result = [];
551
+ for (const child of newChildren) {
552
+ const key = extractKey(child);
553
+ if (key !== void 0) {
554
+ result.push({ key, vnode: child });
555
+ }
556
+ }
557
+ return result;
558
+ }
559
+ function computeLISLength(positions) {
560
+ const tails = [];
561
+ for (const pos of positions) {
562
+ if (pos === -1) continue;
563
+ let lo = 0;
564
+ let hi = tails.length;
565
+ while (lo < hi) {
566
+ const mid = lo + hi >> 1;
567
+ if (tails[mid] < pos) lo = mid + 1;
568
+ else hi = mid;
569
+ }
570
+ if (lo === tails.length) tails.push(pos);
571
+ else tails[lo] = pos;
572
+ }
573
+ return tails.length;
574
+ }
575
+ function checkVnodesHaveProps(keyedVnodes) {
576
+ for (const { vnode } of keyedVnodes) {
577
+ if (typeof vnode !== "object" || vnode === null) continue;
578
+ const vnodeObj = vnode;
579
+ if (vnodeObj.props && hasNonTrivialProps(vnodeObj.props)) {
580
+ return true;
581
+ }
582
+ }
583
+ return false;
584
+ }
585
+ function checkVnodePropChanges(keyedVnodes, oldKeyMap) {
586
+ for (const { key, vnode } of keyedVnodes) {
587
+ const el = oldKeyMap?.get(key);
588
+ if (!el || typeof vnode !== "object" || vnode === null) continue;
589
+ const vnodeObj = vnode;
590
+ const props = vnodeObj.props || {};
591
+ for (const k of Object.keys(props)) {
592
+ if (isIgnoredForPropChanges(k)) continue;
593
+ if (hasPropChanged(el, k, props[k])) {
594
+ return true;
595
+ }
596
+ }
597
+ }
598
+ return false;
599
+ }
600
+ function isKeyedReorderFastPathEligible(parent, newChildren, oldKeyMap) {
601
+ const keyedVnodes = extractKeyedVnodes(newChildren);
602
+ const totalKeyed = keyedVnodes.length;
603
+ const newKeyOrder = keyedVnodes.map((kv) => kv.key);
604
+ const oldKeyOrder = oldKeyMap ? Array.from(oldKeyMap.keys()) : [];
605
+ let moveCount = 0;
606
+ for (let i = 0; i < newKeyOrder.length; i++) {
607
+ const k = newKeyOrder[i];
608
+ if (i >= oldKeyOrder.length || oldKeyOrder[i] !== k || !oldKeyMap?.has(k)) {
609
+ moveCount++;
57
610
  }
58
- logger.warn("<Slot asChild> expects a single JSX element child.");
59
- return null;
60
611
  }
612
+ const FAST_MOVE_THRESHOLD_ABS = 64;
613
+ const FAST_MOVE_THRESHOLD_REL = 0.1;
614
+ const cheapMoveTrigger = totalKeyed >= 128 && oldKeyOrder.length > 0 && moveCount > Math.max(
615
+ FAST_MOVE_THRESHOLD_ABS,
616
+ Math.floor(totalKeyed * FAST_MOVE_THRESHOLD_REL)
617
+ );
618
+ let lisTrigger = false;
619
+ let lisLen = 0;
620
+ if (totalKeyed >= 128) {
621
+ const parentChildren = Array.from(parent.children);
622
+ const positions = keyedVnodes.map(({ key }) => {
623
+ const el = oldKeyMap?.get(key);
624
+ return el?.parentElement === parent ? parentChildren.indexOf(el) : -1;
625
+ });
626
+ lisLen = computeLISLength(positions);
627
+ lisTrigger = lisLen < Math.floor(totalKeyed * 0.5);
628
+ }
629
+ const hasPropsPresent = checkVnodesHaveProps(keyedVnodes);
630
+ const hasPropChanges = checkVnodePropChanges(keyedVnodes, oldKeyMap);
631
+ const useFastPath = (cheapMoveTrigger || lisTrigger) && !hasPropChanges && !hasPropsPresent;
61
632
  return {
62
- $$typeof: ELEMENT_TYPE,
63
- type: Fragment,
64
- props: { children: props.children }
633
+ useFastPath,
634
+ totalKeyed,
635
+ moveCount,
636
+ lisLen,
637
+ hasPropChanges
65
638
  };
66
639
  }
67
640
 
68
- // src/runtime/component.ts
69
- var currentInstance = null;
70
- function getCurrentComponentInstance() {
71
- return currentInstance;
641
+ // src/runtime/dev-namespace.ts
642
+ function getDevNamespace() {
643
+ if (process.env.NODE_ENV === "production") return {};
644
+ try {
645
+ const g = globalThis;
646
+ if (!g.__ASKR__) g.__ASKR__ = {};
647
+ return g.__ASKR__;
648
+ } catch {
649
+ return {};
650
+ }
651
+ }
652
+ function setDevValue(key, value) {
653
+ if (process.env.NODE_ENV === "production") return;
654
+ try {
655
+ getDevNamespace()[key] = value;
656
+ } catch {
657
+ }
658
+ }
659
+ function getDevValue(key) {
660
+ if (process.env.NODE_ENV === "production") return void 0;
661
+ try {
662
+ return getDevNamespace()[key];
663
+ } catch {
664
+ return void 0;
665
+ }
72
666
  }
73
667
 
74
- // src/foundations/portal.tsx
75
- function definePortal() {
76
- if (typeof createPortalSlot !== "function") {
77
- let HostFallback2 = function() {
78
- const inst = getCurrentComponentInstance();
79
- if (process.env.NODE_ENV !== "production") {
80
- const ns = globalThis.__ASKR__ || (globalThis.__ASKR__ = {});
81
- ns.__PORTAL_READS = (ns.__PORTAL_READS || 0) + 1;
668
+ // src/runtime/fastlane.ts
669
+ var _bulkCommitActive = false;
670
+ var _appliedParents = null;
671
+ function enterBulkCommit() {
672
+ _bulkCommitActive = true;
673
+ _appliedParents = /* @__PURE__ */ new WeakSet();
674
+ try {
675
+ const cleared = globalScheduler.clearPendingSyncTasks?.() ?? 0;
676
+ setDevValue("__ASKR_FASTLANE_CLEARED_TASKS", cleared);
677
+ } catch (err) {
678
+ if (process.env.NODE_ENV !== "production") throw err;
679
+ }
680
+ }
681
+ function exitBulkCommit() {
682
+ _bulkCommitActive = false;
683
+ _appliedParents = null;
684
+ }
685
+ function isBulkCommitActive2() {
686
+ return _bulkCommitActive;
687
+ }
688
+ function markFastPathApplied(parent) {
689
+ if (!_appliedParents) return;
690
+ try {
691
+ _appliedParents.add(parent);
692
+ } catch (e) {
693
+ }
694
+ }
695
+ function isFastPathApplied(parent) {
696
+ return !!(_appliedParents && _appliedParents.has(parent));
697
+ }
698
+ function finalizeReadSubscriptions(instance) {
699
+ const newSet = instance._pendingReadStates ?? /* @__PURE__ */ new Set();
700
+ const oldSet = instance._lastReadStates ?? /* @__PURE__ */ new Set();
701
+ const token = instance._currentRenderToken;
702
+ if (token === void 0) return;
703
+ for (const s of oldSet) {
704
+ if (!newSet.has(s)) {
705
+ const readers = s._readers;
706
+ if (readers) readers.delete(instance);
707
+ }
708
+ }
709
+ instance.lastRenderToken = token;
710
+ for (const s of newSet) {
711
+ let readers = s._readers;
712
+ if (!readers) {
713
+ readers = /* @__PURE__ */ new Map();
714
+ s._readers = readers;
715
+ }
716
+ readers.set(instance, instance.lastRenderToken ?? 0);
717
+ }
718
+ instance._lastReadStates = newSet;
719
+ instance._pendingReadStates = /* @__PURE__ */ new Set();
720
+ instance._currentRenderToken = void 0;
721
+ }
722
+ function unwrapFragmentForFastPath(vnode) {
723
+ if (!vnode || typeof vnode !== "object" || !("type" in vnode)) return vnode;
724
+ const v = vnode;
725
+ if (typeof v.type === "symbol" && (v.type === Fragment || String(v.type) === "Symbol(askr.fragment)")) {
726
+ const children = v.children || v.props?.children;
727
+ if (Array.isArray(children) && children.length > 0) {
728
+ for (const child of children) {
729
+ if (child && typeof child === "object" && "type" in child) {
730
+ const c = child;
731
+ if (typeof c.type === "string") {
732
+ return child;
733
+ }
734
+ }
82
735
  }
83
- if (process.env.NODE_ENV !== "production") ;
84
- return inst && owner && inst === owner ? pending : void 0;
85
- };
86
- let owner = null;
87
- let pending;
88
- HostFallback2.render = function RenderFallback(props) {
89
- return null;
90
- };
91
- return HostFallback2;
736
+ }
92
737
  }
93
- const slot = createPortalSlot();
94
- function PortalHost() {
95
- return slot.read();
738
+ return vnode;
739
+ }
740
+ function classifyUpdate(instance, result) {
741
+ const unwrappedResult = unwrapFragmentForFastPath(result);
742
+ if (!unwrappedResult || typeof unwrappedResult !== "object" || !("type" in unwrappedResult))
743
+ return { useFastPath: false, reason: "not-vnode" };
744
+ const vnode = unwrappedResult;
745
+ if (vnode == null || typeof vnode.type !== "string")
746
+ return { useFastPath: false, reason: "not-intrinsic" };
747
+ const parent = instance.target;
748
+ if (!parent) return { useFastPath: false, reason: "no-root" };
749
+ const firstChild = parent.children[0];
750
+ if (!firstChild) return { useFastPath: false, reason: "no-first-child" };
751
+ if (firstChild.tagName.toLowerCase() !== String(vnode.type).toLowerCase())
752
+ return { useFastPath: false, reason: "root-tag-mismatch" };
753
+ const children = vnode.children || vnode.props?.children;
754
+ if (!Array.isArray(children))
755
+ return { useFastPath: false, reason: "no-children-array" };
756
+ for (const c of children) {
757
+ if (typeof c === "object" && c !== null && "type" in c && typeof c.type === "function") {
758
+ return { useFastPath: false, reason: "component-child-present" };
759
+ }
760
+ }
761
+ if (instance.mountOperations.length > 0)
762
+ return { useFastPath: false, reason: "pending-mounts" };
763
+ try {
764
+ populateKeyMapForElement(firstChild);
765
+ } catch {
766
+ }
767
+ const oldKeyMap = getKeyMapForElement(firstChild);
768
+ const decision = isKeyedReorderFastPathEligible(
769
+ firstChild,
770
+ children,
771
+ oldKeyMap
772
+ );
773
+ if (!decision.useFastPath || decision.totalKeyed < 128)
774
+ return { ...decision, useFastPath: false, reason: "renderer-declined" };
775
+ return { ...decision, useFastPath: true };
776
+ }
777
+ function commitReorderOnly(instance, result) {
778
+ const evaluate = globalThis.__ASKR_RENDERER?.evaluate;
779
+ if (typeof evaluate !== "function") {
780
+ logger.warn(
781
+ "[Tempo][FASTPATH][DEV] renderer.evaluate not available; declining fast-lane"
782
+ );
783
+ return false;
96
784
  }
97
- PortalHost.render = function PortalRender(props) {
785
+ const schedBefore = process.env.NODE_ENV !== "production" ? globalScheduler.getState() : null;
786
+ enterBulkCommit();
787
+ try {
788
+ globalScheduler.runWithSyncProgress(() => {
789
+ evaluate(result, instance.target);
790
+ try {
791
+ finalizeReadSubscriptions(instance);
792
+ } catch (e) {
793
+ if (process.env.NODE_ENV !== "production") throw e;
794
+ }
795
+ });
796
+ const clearedAfter = globalScheduler.clearPendingSyncTasks?.() ?? 0;
797
+ setDevValue("__FASTLANE_CLEARED_AFTER", clearedAfter);
98
798
  if (process.env.NODE_ENV !== "production") {
99
- const ns = globalThis.__ASKR__ || (globalThis.__ASKR__ = {});
100
- ns.__PORTAL_WRITES = (ns.__PORTAL_WRITES || 0) + 1;
799
+ validateFastLaneInvariants(instance, schedBefore);
101
800
  }
102
- slot.write(props.children);
103
- return null;
801
+ return true;
802
+ } finally {
803
+ exitBulkCommit();
804
+ }
805
+ if (process.env.NODE_ENV !== "production") {
806
+ if (isBulkCommitActive2()) {
807
+ throw new Error(
808
+ "Fast-lane invariant violated: bulk commit flag still set after commit"
809
+ );
810
+ }
811
+ }
812
+ }
813
+ function validateFastLaneInvariants(instance, schedBefore) {
814
+ const commitCount = getDevValue("__LAST_FASTPATH_COMMIT_COUNT") ?? 0;
815
+ const invariants = {
816
+ commitCount,
817
+ mountOps: instance.mountOperations.length,
818
+ cleanupFns: instance.cleanupFns.length
104
819
  };
105
- return PortalHost;
820
+ setDevValue("__LAST_FASTLANE_INVARIANTS", invariants);
821
+ if (commitCount !== 1) {
822
+ console.error(
823
+ "[FASTLANE][INV] commitCount",
824
+ commitCount,
825
+ "diag",
826
+ globalThis.__ASKR_DIAG
827
+ );
828
+ throw new Error(
829
+ "Fast-lane invariant violated: expected exactly one DOM commit during reorder-only commit"
830
+ );
831
+ }
832
+ if (invariants.mountOps > 0) {
833
+ throw new Error(
834
+ "Fast-lane invariant violated: mount operations were registered during bulk commit"
835
+ );
836
+ }
837
+ if (invariants.cleanupFns > 0) {
838
+ throw new Error(
839
+ "Fast-lane invariant violated: cleanup functions were added during bulk commit"
840
+ );
841
+ }
842
+ const schedAfter = globalScheduler.getState();
843
+ if (schedBefore && schedAfter && schedAfter.taskCount > schedBefore.taskCount) {
844
+ console.error(
845
+ "[FASTLANE] schedBefore, schedAfter",
846
+ schedBefore,
847
+ schedAfter
848
+ );
849
+ console.error("[FASTLANE] enqueue logs", getDevValue("__ENQUEUE_LOGS"));
850
+ throw new Error(
851
+ "Fast-lane invariant violated: scheduler enqueued leftover work during bulk commit"
852
+ );
853
+ }
854
+ let finalState = globalScheduler.getState();
855
+ const executing = globalScheduler.isExecuting();
856
+ let outstandingAfter = Math.max(
857
+ 0,
858
+ finalState.taskCount - (executing ? 1 : 0)
859
+ );
860
+ if (outstandingAfter !== 0) {
861
+ let attempts = 0;
862
+ while (attempts < 5) {
863
+ const cleared = globalScheduler.clearPendingSyncTasks?.() ?? 0;
864
+ if (cleared === 0) break;
865
+ attempts++;
866
+ }
867
+ finalState = globalScheduler.getState();
868
+ outstandingAfter = Math.max(
869
+ 0,
870
+ finalState.taskCount - (globalScheduler.isExecuting() ? 1 : 0)
871
+ );
872
+ if (outstandingAfter !== 0) {
873
+ console.error(
874
+ "[FASTLANE] Post-commit enqueue logs:",
875
+ getDevValue("__ENQUEUE_LOGS")
876
+ );
877
+ console.error(
878
+ "[FASTLANE] Cleared counts:",
879
+ getDevValue("__FASTLANE_CLEARED_TASKS"),
880
+ getDevValue("__FASTLANE_CLEARED_AFTER")
881
+ );
882
+ throw new Error(
883
+ `Fast-lane invariant violated: scheduler has ${finalState.taskCount} pending task(s) after commit`
884
+ );
885
+ }
886
+ }
106
887
  }
107
- var _defaultPortal;
108
- var _defaultPortalIsFallback = false;
109
- function ensureDefaultPortal() {
110
- if (!_defaultPortal) {
111
- if (typeof createPortalSlot === "function") {
112
- _defaultPortal = definePortal();
113
- _defaultPortalIsFallback = false;
888
+ function tryRuntimeFastLaneSync(instance, result) {
889
+ const cls = classifyUpdate(instance, result);
890
+ if (!cls.useFastPath) {
891
+ setDevValue("__LAST_FASTPATH_STATS", void 0);
892
+ setDevValue("__LAST_FASTPATH_COMMIT_COUNT", 0);
893
+ return false;
894
+ }
895
+ try {
896
+ return commitReorderOnly(instance, result);
897
+ } catch (err) {
898
+ if (process.env.NODE_ENV !== "production") throw err;
899
+ return false;
900
+ }
901
+ }
902
+ if (typeof globalThis !== "undefined") {
903
+ globalThis.__ASKR_FASTLANE = {
904
+ isBulkCommitActive: isBulkCommitActive2,
905
+ enterBulkCommit,
906
+ exitBulkCommit,
907
+ tryRuntimeFastLaneSync,
908
+ markFastPathApplied,
909
+ isFastPathApplied
910
+ };
911
+ }
912
+
913
+ // src/runtime/state.ts
914
+ function state(initialValue) {
915
+ {
916
+ throw new Error(
917
+ "state() can only be called during component render execution. Move state() calls to the top level of your component function."
918
+ );
919
+ }
920
+ }
921
+
922
+ // src/foundations/state/controllable.ts
923
+ function isControlled(value) {
924
+ return value !== void 0;
925
+ }
926
+ function resolveControllable(value, defaultValue) {
927
+ const controlled = isControlled(value);
928
+ return {
929
+ value: controlled ? value : defaultValue,
930
+ isControlled: controlled
931
+ };
932
+ }
933
+ function makeControllable(options) {
934
+ const { value, defaultValue, onChange, setInternal } = options;
935
+ const { isControlled: isControlled2 } = resolveControllable(value, defaultValue);
936
+ function set(next) {
937
+ if (isControlled2) {
938
+ onChange?.(next);
114
939
  } else {
115
- _defaultPortal = definePortal();
116
- _defaultPortalIsFallback = true;
940
+ setInternal?.(next);
941
+ onChange?.(next);
117
942
  }
118
- return _defaultPortal;
119
- }
120
- if (_defaultPortalIsFallback && typeof createPortalSlot === "function") {
121
- const real = definePortal();
122
- _defaultPortal = real;
123
- _defaultPortalIsFallback = false;
124
943
  }
125
- if (!_defaultPortalIsFallback && typeof createPortalSlot !== "function") {
126
- const fallback = definePortal();
127
- _defaultPortal = fallback;
128
- _defaultPortalIsFallback = true;
944
+ return { set, isControlled: isControlled2 };
945
+ }
946
+ function controllableState(options) {
947
+ const internal = state(options.defaultValue);
948
+ const isControlled2 = options.value !== void 0;
949
+ function read() {
950
+ return isControlled2 ? options.value : internal();
129
951
  }
130
- return _defaultPortal;
952
+ read.set = (nextOrUpdater) => {
953
+ const prev = read();
954
+ const next = typeof nextOrUpdater === "function" ? nextOrUpdater(prev) : nextOrUpdater;
955
+ if (Object.is(prev, next)) return;
956
+ if (isControlled2) {
957
+ options.onChange?.(next);
958
+ return;
959
+ }
960
+ internal.set(nextOrUpdater);
961
+ options.onChange?.(next);
962
+ };
963
+ read.isControlled = isControlled2;
964
+ return read;
131
965
  }
132
- var DefaultPortal = (() => {
133
- function Host() {
134
- const v = ensureDefaultPortal()();
135
- return v === void 0 ? null : v;
966
+
967
+ // src/foundations/interactions/pressable.ts
968
+ function pressable({
969
+ disabled,
970
+ onPress,
971
+ isNativeButton = false
972
+ }) {
973
+ const props = {
974
+ onClick: (e) => {
975
+ if (disabled) {
976
+ e.preventDefault?.();
977
+ e.stopPropagation?.();
978
+ return;
979
+ }
980
+ onPress?.(e);
981
+ }
982
+ };
983
+ if (isNativeButton) {
984
+ if (disabled) {
985
+ props.disabled = true;
986
+ Object.assign(props, ariaDisabled(disabled));
987
+ }
988
+ return props;
136
989
  }
137
- Host.render = function Render(props) {
138
- ensureDefaultPortal().render(props);
139
- return null;
990
+ props.role = "button";
991
+ props.tabIndex = disabled ? -1 : 0;
992
+ props.onKeyDown = (e) => {
993
+ if (disabled) {
994
+ e.preventDefault?.();
995
+ e.stopPropagation?.();
996
+ return;
997
+ }
998
+ if (e.key === "Enter") {
999
+ e.preventDefault?.();
1000
+ onPress?.(e);
1001
+ return;
1002
+ }
1003
+ if (e.key === " ") {
1004
+ e.preventDefault?.();
1005
+ }
140
1006
  };
141
- return Host;
142
- })();
1007
+ props.onKeyUp = (e) => {
1008
+ if (disabled) {
1009
+ e.preventDefault?.();
1010
+ e.stopPropagation?.();
1011
+ return;
1012
+ }
1013
+ if (e.key === " ") {
1014
+ e.preventDefault?.();
1015
+ onPress?.(e);
1016
+ }
1017
+ };
1018
+ if (disabled) Object.assign(props, ariaDisabled(disabled));
1019
+ return props;
1020
+ }
1021
+
1022
+ // src/foundations/interactions/dismissable.ts
1023
+ function dismissable({ onDismiss, disabled }) {
1024
+ return {
1025
+ // Prop for the component root to handle Escape
1026
+ onKeyDown: disabled ? void 0 : (e) => {
1027
+ if (e.key === "Escape") {
1028
+ e.preventDefault?.();
1029
+ e.stopPropagation?.();
1030
+ onDismiss?.();
1031
+ }
1032
+ },
1033
+ // Factory: runtime should attach this listener at the appropriate scope.
1034
+ outsideListener: disabled ? void 0 : (isInside) => (e) => {
1035
+ if (!isInside(e.target)) {
1036
+ e.preventDefault?.();
1037
+ e.stopPropagation?.();
1038
+ onDismiss?.();
1039
+ }
1040
+ }
1041
+ };
1042
+ }
1043
+
1044
+ // src/foundations/interactions/focusable.ts
1045
+ function focusable({
1046
+ disabled,
1047
+ tabIndex
1048
+ }) {
1049
+ return {
1050
+ tabIndex: disabled ? -1 : tabIndex === void 0 ? 0 : tabIndex,
1051
+ ...ariaDisabled(disabled)
1052
+ };
1053
+ }
1054
+
1055
+ // src/foundations/interactions/hoverable.ts
1056
+ function hoverable({
1057
+ disabled,
1058
+ onEnter,
1059
+ onLeave
1060
+ }) {
1061
+ return {
1062
+ onPointerEnter: disabled ? void 0 : (e) => {
1063
+ onEnter?.(e);
1064
+ },
1065
+ onPointerLeave: disabled ? void 0 : (e) => {
1066
+ onLeave?.(e);
1067
+ }
1068
+ };
1069
+ }
143
1070
 
144
- export { DefaultPortal, Slot, definePortal, layout };
1071
+ export { DefaultPortal, Presence, Slot, ariaDisabled, ariaExpanded, ariaSelected, composeHandlers, composeRefs, controllableState, definePortal, dismissable, focusable, hoverable, isControlled, layout, makeControllable, mergeProps, pressable, resolveControllable, setRef, useId };
145
1072
  //# sourceMappingURL=index.js.map
146
1073
  //# sourceMappingURL=index.js.map