@almadar/ui 6.51.2 → 6.52.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (121) hide show
  1. package/dist/{Canvas3DHost-CLC_jXh_.d.ts → Canvas3DHost-CWO0tswb.d.ts} +1 -1
  2. package/dist/{FileTree-RKQqRhIM.d.ts → FileTree-DUi7e7Z8.d.ts} +5 -5
  3. package/dist/{GameAudioProvider-BgWLggIy.d.ts → GameAudioProvider-_hPzjqp-.d.ts} +1 -1
  4. package/dist/{NavStackContext-B-XLULCO.d.ts → NavStackContext-04NLdXsY.d.ts} +8 -0
  5. package/dist/Typography-7FRZOS2H.js +5 -0
  6. package/dist/{UISlotContext-DNM8iXna.d.ts → UISlotContext-CS487Uoc.d.ts} +1 -1
  7. package/dist/{UISlotRenderer-7UYHFW4S.js → UISlotRenderer-CR7SCAMR.js} +8 -8
  8. package/dist/avl/index.cjs +3401 -1590
  9. package/dist/avl/index.d.ts +4 -4
  10. package/dist/avl/index.js +26 -23
  11. package/dist/{chunk-6CFYRP4S.js → chunk-2QB2PXOO.js} +1 -3
  12. package/dist/{chunk-R4V7IZQP.js → chunk-5GCI6C7J.js} +2 -2
  13. package/dist/{chunk-BUEIZY54.js → chunk-BHZG4PDZ.js} +420 -422
  14. package/dist/{chunk-MWPX2O4M.js → chunk-CLSLVFSP.js} +3 -16
  15. package/dist/{chunk-GIPZSCND.js → chunk-EERAGPBW.js} +3078 -1832
  16. package/dist/{chunk-OLZ3EEML.js → chunk-G4GMXYMY.js} +109 -15
  17. package/dist/{chunk-HQDKHLFG.js → chunk-IN3BVW2Z.js} +15 -1
  18. package/dist/{chunk-FGHRVJCI.js → chunk-MSBMVG3B.js} +12 -2
  19. package/dist/{chunk-YKHNK5JH.js → chunk-NCNP2RKZ.js} +11 -4
  20. package/dist/{chunk-T7IH5ABO.js → chunk-QWDKMVLX.js} +69 -17
  21. package/dist/{chunk-NTO555KF.js → chunk-TWHYH25Q.js} +136 -15
  22. package/dist/{chunk-GHXWGQTX.js → chunk-V5AGW2JN.js} +1 -1
  23. package/dist/{chunk-L55SAFKL.js → chunk-WB2A6V3I.js} +31 -1
  24. package/dist/{chunk-JXUYEO7U.js → chunk-XUSSAWKL.js} +7 -1
  25. package/dist/{cn-CO-0rolG.d.ts → cn-ClXGlIOY.d.ts} +2 -2
  26. package/dist/components/index.cjs +4933 -3165
  27. package/dist/components/index.d.ts +499 -416
  28. package/dist/components/index.js +11 -11
  29. package/dist/context/index.cjs +12 -4
  30. package/dist/context/index.d.ts +3 -3
  31. package/dist/context/index.js +2 -2
  32. package/dist/hooks/index.cjs +73 -6
  33. package/dist/hooks/index.d.ts +32 -7
  34. package/dist/hooks/index.js +6 -6
  35. package/dist/lib/drawable/three/index.cjs +33 -2
  36. package/dist/lib/drawable/three/index.d.ts +5 -5
  37. package/dist/lib/drawable/three/index.js +7 -7
  38. package/dist/lib/index.cjs +69 -2
  39. package/dist/lib/index.d.ts +29 -6
  40. package/dist/lib/index.js +5 -5
  41. package/dist/locales/index.cjs +93 -3
  42. package/dist/locales/index.js +63 -3
  43. package/dist/{paintDispatch-CMgOyVjS.d.ts → paintDispatch-DHo3WhaU.d.ts} +4 -4
  44. package/dist/providers/index.cjs +3392 -1607
  45. package/dist/providers/index.d.ts +6 -6
  46. package/dist/providers/index.js +55 -16
  47. package/dist/runtime/index.cjs +3417 -1611
  48. package/dist/runtime/index.d.ts +2 -2
  49. package/dist/runtime/index.js +10 -10
  50. package/dist/ssr/index.cjs +306 -141
  51. package/dist/ssr/index.d.cts +32 -44
  52. package/dist/ssr/index.d.ts +32 -44
  53. package/dist/ssr/index.js +249 -84
  54. package/dist/{themeTokens-CN9WCXQc.d.ts → themeTokens-Ccu7EmGY.d.ts} +4 -2
  55. package/dist/{useUISlots-CwUWM1ra.d.ts → useUISlots-1dk2TR4j.d.ts} +1 -9
  56. package/dist/{verificationRegistry-DBHfOR9y.d.ts → verificationRegistry-BYD3cVhS.d.ts} +1 -1
  57. package/locales/ar.json +31 -1
  58. package/locales/en.json +31 -1
  59. package/locales/sl.json +31 -1
  60. package/package.json +12 -9
  61. package/tailwind-preset.cjs +24 -1
  62. package/themes/_base.css +86 -0
  63. package/themes/_contract.md +1 -0
  64. package/themes/almadar-website.css +2 -0
  65. package/themes/almadar.css +2 -0
  66. package/themes/aqua.css +5 -3
  67. package/themes/arabesque.css +9 -7
  68. package/themes/art-deco.css +5 -3
  69. package/themes/art-nouveau.css +4 -0
  70. package/themes/atelier.css +10 -8
  71. package/themes/bauhaus.css +2 -0
  72. package/themes/bloomberg-dense.css +13 -11
  73. package/themes/blueprint.css +2 -0
  74. package/themes/clay.css +2 -0
  75. package/themes/comic.css +12 -10
  76. package/themes/constructivist.css +2 -0
  77. package/themes/corporate.css +4 -2
  78. package/themes/cyberpunk.css +2 -0
  79. package/themes/frutiger-aero.css +2 -0
  80. package/themes/game-adventure.css +1 -0
  81. package/themes/game-rpg.css +1 -0
  82. package/themes/game-sci-fi.css +1 -0
  83. package/themes/game-ui-pack.css +1 -0
  84. package/themes/gazette.css +10 -8
  85. package/themes/glass.css +17 -15
  86. package/themes/gothic.css +2 -0
  87. package/themes/ink-wash.css +2 -0
  88. package/themes/kawaii.css +2 -0
  89. package/themes/kiosk.css +17 -15
  90. package/themes/linear-clean.css +12 -10
  91. package/themes/luxury.css +2 -0
  92. package/themes/mac-classic.css +2 -0
  93. package/themes/material.css +2 -0
  94. package/themes/memphis.css +2 -0
  95. package/themes/mid-century.css +2 -0
  96. package/themes/minimalist.css +2 -0
  97. package/themes/mudcloth.css +2 -0
  98. package/themes/neon.css +2 -0
  99. package/themes/neumorphic.css +2 -0
  100. package/themes/newsprint.css +2 -0
  101. package/themes/notion-editorial.css +12 -10
  102. package/themes/pixel.css +2 -0
  103. package/themes/prism.css +20 -18
  104. package/themes/retro.css +2 -0
  105. package/themes/risograph.css +2 -0
  106. package/themes/scandi.css +2 -0
  107. package/themes/sketch.css +2 -0
  108. package/themes/solarpunk.css +2 -0
  109. package/themes/steampunk.css +2 -0
  110. package/themes/swiss.css +2 -0
  111. package/themes/terminal.css +8 -6
  112. package/themes/trait-wars.css +2 -0
  113. package/themes/ukiyo-e.css +2 -0
  114. package/themes/vaporwave.css +2 -0
  115. package/themes/victorian.css +2 -0
  116. package/themes/wabi-sabi.css +2 -0
  117. package/themes/win95.css +2 -0
  118. package/themes/wireframe.css +2 -0
  119. package/themes/zine.css +2 -0
  120. package/dist/Typography-2Z3JKWHN.js +0 -5
  121. package/dist/{avl-3d-context-MAt_NuU9.d.ts → avl-3d-context-DLteNEwu.d.ts} +3 -3
@@ -1,10 +1,115 @@
1
- import { useSyncExternalStore } from 'react';
1
+ import { domPassthrough, cn } from './chunk-IN3BVW2Z.js';
2
+ import { useState, useRef, useCallback, useLayoutEffect, useEffect, useSyncExternalStore } from 'react';
3
+ import { jsx } from 'react/jsx-runtime';
4
+ import { ENTER_DELAY_STEPS, ENTER_ANIMATIONS, isFileValue } from '@almadar/core';
5
+ export { getNestedValue } from '@almadar/core';
2
6
  import { perfStore, pushPerfEntry } from '@almadar/runtime/ui';
3
7
  export { PERF_NAMESPACE, clearPerf, perfEnd, perfGauge, perfStart, perfTime, perfTimeAsync } from '@almadar/runtime/ui';
4
- import { isFileValue } from '@almadar/core';
5
- export { getNestedValue } from '@almadar/core';
6
8
  import { createLogger, isLogLevelEnabled } from '@almadar/logger';
7
9
 
10
+ var SAFE_EXIT_MS = 1e3;
11
+ var motionEnabledCache = null;
12
+ function isMotionEnabled() {
13
+ if (typeof document === "undefined") return true;
14
+ if (motionEnabledCache !== null) return motionEnabledCache;
15
+ const v = getComputedStyle(document.documentElement).getPropertyValue("--motion-enable").trim().toLowerCase();
16
+ motionEnabledCache = v !== "off";
17
+ return motionEnabledCache;
18
+ }
19
+ function usePresence(show, opts) {
20
+ const { animation, animate = true, onExited } = opts;
21
+ const [mounted, setMounted] = useState(show);
22
+ const [exiting, setExiting] = useState(false);
23
+ const prev = useRef(show);
24
+ const onExitedRef = useRef(onExited);
25
+ onExitedRef.current = onExited;
26
+ const safeTimer = useRef(null);
27
+ const clearSafe = useCallback(() => {
28
+ if (safeTimer.current) {
29
+ clearTimeout(safeTimer.current);
30
+ safeTimer.current = null;
31
+ }
32
+ }, []);
33
+ const finishExit = useCallback(() => {
34
+ clearSafe();
35
+ setExiting(false);
36
+ setMounted(false);
37
+ onExitedRef.current?.();
38
+ }, [clearSafe]);
39
+ useLayoutEffect(() => {
40
+ const moving = animate && isMotionEnabled();
41
+ if (show && !prev.current) {
42
+ setExiting(false);
43
+ setMounted(true);
44
+ } else if (!show && prev.current) {
45
+ if (moving) {
46
+ setExiting(true);
47
+ clearSafe();
48
+ safeTimer.current = setTimeout(finishExit, SAFE_EXIT_MS);
49
+ } else {
50
+ setMounted(false);
51
+ setExiting(false);
52
+ onExitedRef.current?.();
53
+ }
54
+ }
55
+ prev.current = show;
56
+ }, [show, animate, clearSafe, finishExit]);
57
+ useEffect(() => () => clearSafe(), [clearSafe]);
58
+ const disabled = !animate || !isMotionEnabled();
59
+ const className = disabled ? "" : show ? `animate-${animation}-in` : `animate-${animation}-out`;
60
+ const onAnimationEnd = useCallback(
61
+ (e) => {
62
+ if (e.target !== e.currentTarget) return;
63
+ if (exiting) finishExit();
64
+ },
65
+ [exiting, finishExit]
66
+ );
67
+ return { mounted, exiting, className, onAnimationEnd };
68
+ }
69
+ var Presence = ({ show, className, children, ...rest }) => {
70
+ const { mounted, className: animClass, onAnimationEnd } = usePresence(show, rest);
71
+ if (!mounted) return null;
72
+ return /* @__PURE__ */ jsx("div", { className: cn(animClass, className), onAnimationEnd, ...domPassthrough(rest), children });
73
+ };
74
+ Presence.displayName = "Presence";
75
+ var ENTER_SLOT_CLASS = "almadar-enter-slot";
76
+ function asEnterAnimation(value) {
77
+ return ENTER_ANIMATIONS.find((e) => e === value);
78
+ }
79
+ var MAX_ENTER_DELAY = ENTER_DELAY_STEPS.length;
80
+ function enterClassName(enter, enterDelay) {
81
+ if (enter === void 0 || enter === "none" || !isMotionEnabled()) return void 0;
82
+ const base = `almadar-enter-${enter}`;
83
+ if (enterDelay === void 0 || !Number.isInteger(enterDelay) || enterDelay < 1 || enterDelay > MAX_ENTER_DELAY) return base;
84
+ return `${base} almadar-enter-delay-${enterDelay}`;
85
+ }
86
+ function slotPlaysDefaultEnter(rootEnter) {
87
+ return rootEnter === void 0 && isMotionEnabled();
88
+ }
89
+ function useSlotEnterRestart(ref, contentKey, active) {
90
+ const lastKey = useRef(contentKey);
91
+ useLayoutEffect(() => {
92
+ if (lastKey.current === contentKey) return;
93
+ lastKey.current = contentKey;
94
+ const el = ref.current;
95
+ if (!el || !active) return;
96
+ el.classList.remove(ENTER_SLOT_CLASS);
97
+ void el.offsetWidth;
98
+ el.classList.add(ENTER_SLOT_CLASS);
99
+ }, [contentKey, active, ref]);
100
+ }
101
+ function usePerfBuffer() {
102
+ return useSyncExternalStore(perfStore.subscribe, perfStore.getSnapshot, perfStore.getSnapshot);
103
+ }
104
+ var profilerOnRender = (id, phase, actualDuration, baseDuration, _startTime, commitTime) => {
105
+ pushPerfEntry({
106
+ name: `profiler:${id}:${phase}`,
107
+ durationMs: actualDuration,
108
+ ts: commitTime,
109
+ detail: { baseDuration }
110
+ });
111
+ };
112
+
8
113
  // lib/jazari/svg-paths.ts
9
114
  function gearTeethPath(cx, cy, innerRadius, outerRadius, numTeeth) {
10
115
  const parts = [];
@@ -85,17 +190,6 @@ function eightPointedStarPath(cx, cy, outerR, innerR) {
85
190
  function arrowheadPath(size) {
86
191
  return `M 0 0 L ${size} ${size / 2} L 0 ${size} Z`;
87
192
  }
88
- function usePerfBuffer() {
89
- return useSyncExternalStore(perfStore.subscribe, perfStore.getSnapshot, perfStore.getSnapshot);
90
- }
91
- var profilerOnRender = (id, phase, actualDuration, baseDuration, _startTime, commitTime) => {
92
- pushPerfEntry({
93
- name: `profiler:${id}:${phase}`,
94
- durationMs: actualDuration,
95
- ts: commitTime,
96
- detail: { baseDuration }
97
- });
98
- };
99
193
 
100
194
  // lib/editorMotions.ts
101
195
  var EDITOR_MOTIONS = [
@@ -1102,4 +1196,4 @@ function initDebugShortcut() {
1102
1196
  return () => window.removeEventListener("keydown", handleKeyDown);
1103
1197
  }
1104
1198
 
1105
- export { EDITOR_MOTIONS, EDITOR_OPERATORS, INDENT_UNIT, applyMotion, applyOperator, arrowheadPath, brainIconPath, clearDebugEvents, clearEntityProvider, clearGuardHistory, clearTicks, clearTraits, computeLineDiff, debug, debugCollision, debugError, debugGameState, debugGroup, debugGroupEnd, debugInput, debugPhysics, debugTable, debugTime, debugTimeEnd, debugWarn, eightPointedStarPath, gearTeethPath, getAllTicks, getAllTraits, getDebugEvents, getEntitiesByType, getEntityById, getEntitySnapshot, getEventsBySource, getEventsByType, getGuardEvaluationsForTrait, getGuardHistory, getRecentEvents, getRecentGuardEvaluations, getTick, getTrait, initDebugShortcut, isDebugEnabled, isDebugEnabled2, isEditorMotion, isEditorOperator, lockIconPath, logDebugEvent, logEffectExecuted, logError, logEventFired, logInfo, logStateChange, logWarning, motionRange, onDebugToggle, parseContentSegments, parseLessonSegments, parseMarkdownWithCodeBlocks, parseMarkdownWithCodeBlocks2, pipeIconPath, profilerOnRender, recordGuardEvaluation, registerTick, registerTrait, resolveImageUrl, setDebugEnabled, setEntityProvider, setTickActive, subscribeToDebugEvents, subscribeToGuardChanges, subscribeToTickChanges, subscribeToTraitChanges, toggleDebug, unregisterTick, unregisterTrait, updateGuardResult, updateTickExecution, updateTraitState, usePerfBuffer };
1199
+ export { EDITOR_MOTIONS, EDITOR_OPERATORS, ENTER_SLOT_CLASS, INDENT_UNIT, MAX_ENTER_DELAY, Presence, applyMotion, applyOperator, arrowheadPath, asEnterAnimation, brainIconPath, clearDebugEvents, clearEntityProvider, clearGuardHistory, clearTicks, clearTraits, computeLineDiff, debug, debugCollision, debugError, debugGameState, debugGroup, debugGroupEnd, debugInput, debugPhysics, debugTable, debugTime, debugTimeEnd, debugWarn, eightPointedStarPath, enterClassName, gearTeethPath, getAllTicks, getAllTraits, getDebugEvents, getEntitiesByType, getEntityById, getEntitySnapshot, getEventsBySource, getEventsByType, getGuardEvaluationsForTrait, getGuardHistory, getRecentEvents, getRecentGuardEvaluations, getTick, getTrait, initDebugShortcut, isDebugEnabled, isDebugEnabled2, isEditorMotion, isEditorOperator, isMotionEnabled, lockIconPath, logDebugEvent, logEffectExecuted, logError, logEventFired, logInfo, logStateChange, logWarning, motionRange, onDebugToggle, parseContentSegments, parseLessonSegments, parseMarkdownWithCodeBlocks, parseMarkdownWithCodeBlocks2, pipeIconPath, profilerOnRender, recordGuardEvaluation, registerTick, registerTrait, resolveImageUrl, setDebugEnabled, setEntityProvider, setTickActive, slotPlaysDefaultEnter, subscribeToDebugEvents, subscribeToGuardChanges, subscribeToTickChanges, subscribeToTraitChanges, toggleDebug, unregisterTick, unregisterTrait, updateGuardResult, updateTickExecution, updateTraitState, usePerfBuffer, usePresence, useSlotEnterRestart };
@@ -1,6 +1,20 @@
1
1
  import { clsx } from 'clsx';
2
2
  import { extendTailwindMerge } from 'tailwind-merge';
3
3
 
4
+ // lib/domPassthrough.ts
5
+ var GLOBAL_ATTRIBUTES = /* @__PURE__ */ new Set(["role", "id", "lang", "dir", "tabIndex"]);
6
+ function domPassthrough(props) {
7
+ const out = {};
8
+ for (const [key, value] of Object.entries(props)) {
9
+ if (key.startsWith("aria-") || key.startsWith("data-") || GLOBAL_ATTRIBUTES.has(key)) {
10
+ if (typeof value === "string" || typeof value === "number" || typeof value === "boolean" || value === void 0) {
11
+ out[key] = value;
12
+ }
13
+ }
14
+ }
15
+ return out;
16
+ }
17
+
4
18
  // lib/theme-scale.ts
5
19
  var THEME_SCALE_KEYS = {
6
20
  height: ["button-sm", "button-md", "button-lg", "input-sm", "input-md", "input-lg", "row-compact", "row-normal", "row-spacious", "icon-default"],
@@ -33,4 +47,4 @@ function cn(...inputs) {
33
47
  return twMerge(clsx(inputs));
34
48
  }
35
49
 
36
- export { cn };
50
+ export { cn, domPassthrough };
@@ -98,7 +98,10 @@ var MOTION_SHAPE_VARS = [
98
98
  { cssVar: "--motion-popover-exit-to-transform", key: "popoverExit" },
99
99
  { cssVar: "--motion-toast-enter-from-transform", key: "toastEnter" },
100
100
  { cssVar: "--motion-toast-exit-to-transform", key: "toastExit" },
101
- { cssVar: "--motion-page-enter-from-transform", key: "pageEnter" }
101
+ { cssVar: "--motion-page-enter-from-transform", key: "pageEnter" },
102
+ { cssVar: "--motion-enter-rise-from-transform", key: "enterRise" },
103
+ { cssVar: "--motion-enter-scale-from-transform", key: "enterScale" },
104
+ { cssVar: "--motion-enter-slide-from-transform", key: "enterSlide" }
102
105
  ];
103
106
  var ICONOGRAPHY_VARS = [
104
107
  { cssVar: "--icon-family", key: "family" },
@@ -122,6 +125,7 @@ var GEOMETRY_VARS = [
122
125
  { cssVar: "--border-heavy", key: "borderHeavy" },
123
126
  { cssVar: "--corner-shape", key: "cornerShape" },
124
127
  { cssVar: "--corner-shape-pill", key: "cornerShapePill" },
128
+ { cssVar: "--corner-shape-interactive", key: "cornerShapeInteractive" },
125
129
  { cssVar: "--border-style", key: "borderStyle" },
126
130
  { cssVar: "--border-style-interactive", key: "borderStyleInteractive" }
127
131
  ];
@@ -260,6 +264,12 @@ function emitMotion(m, vars) {
260
264
  }
261
265
  }
262
266
  applyFlat(m.shapes, MOTION_SHAPE_VARS, vars);
267
+ if (m.entry?.default !== void 0) vars["--motion-enter-default"] = enterKeyframe(m.entry.default);
268
+ if (m.entry?.stagger !== void 0) vars["--motion-enter-stagger"] = m.entry.stagger;
269
+ if (m.busyDelay !== void 0) vars["--motion-busy-delay"] = m.busyDelay;
270
+ }
271
+ function enterKeyframe(enter) {
272
+ return enter === "none" ? "none" : `almadar-enter-${enter}`;
263
273
  }
264
274
  function emitIconography(i, vars) {
265
275
  applyFlat(i, ICONOGRAPHY_VARS, vars);
@@ -276,4 +286,4 @@ function resolveThemeForRuntime(theme) {
276
286
  return theme;
277
287
  }
278
288
 
279
- export { DENSITY_ELEMENT_VARS, DENSITY_SPACING_VARS, ELEVATION_VARS, GEOMETRY_VARS, ICONOGRAPHY_VARS, LEGACY_PREFIXES, MOTION_DURATION_KEYS, MOTION_EASING_KEYS, MOTION_INTENT_VARS, MOTION_SHAPE_VARS, SURFACE_VARS, TYPE_FAMILY_VARS, TYPE_HEADING_VARS, TYPE_INTENT_VARS, TYPE_SIZE_KEYS, resolveThemeForRuntime, themeTokensToCssVars };
289
+ export { DENSITY_ELEMENT_VARS, DENSITY_SPACING_VARS, ELEVATION_VARS, GEOMETRY_VARS, ICONOGRAPHY_VARS, LEGACY_PREFIXES, MOTION_DURATION_KEYS, MOTION_EASING_KEYS, MOTION_INTENT_VARS, MOTION_SHAPE_VARS, SURFACE_VARS, TYPE_FAMILY_VARS, TYPE_HEADING_VARS, TYPE_INTENT_VARS, TYPE_SIZE_KEYS, enterKeyframe, resolveThemeForRuntime, themeTokensToCssVars };
@@ -1,6 +1,6 @@
1
- import { DEFAULT_CURRENCY } from './chunk-GHXWGQTX.js';
2
- import { en_default } from './chunk-L55SAFKL.js';
3
- import { createContext, useContext, useMemo } from 'react';
1
+ import { DEFAULT_CURRENCY } from './chunk-V5AGW2JN.js';
2
+ import { en_default } from './chunk-WB2A6V3I.js';
3
+ import React, { createContext, useEffect, useContext, useMemo } from 'react';
4
4
 
5
5
  var { $meta: _meta, ...coreMessages } = en_default;
6
6
  var coreLocale = coreMessages;
@@ -18,7 +18,14 @@ var I18nContext = createContext({
18
18
  }
19
19
  });
20
20
  I18nContext.displayName = "I18nContext";
21
- var I18nProvider = I18nContext.Provider;
21
+ function I18nProvider({ value, children, documentRoot }) {
22
+ useEffect(() => {
23
+ if (!documentRoot) return;
24
+ document.documentElement.lang = value.locale;
25
+ document.documentElement.dir = value.direction;
26
+ }, [documentRoot, value.locale, value.direction]);
27
+ return React.createElement(I18nContext.Provider, { value }, children);
28
+ }
22
29
  function useTranslate() {
23
30
  return useContext(I18nContext);
24
31
  }
@@ -1,10 +1,11 @@
1
1
  import { runTickFrame } from './chunk-DQTV6QVS.js';
2
- import { ALL_SLOTS } from './chunk-6CFYRP4S.js';
3
- import { UISlotRenderer } from './chunk-GIPZSCND.js';
4
- import { Box } from './chunk-BUEIZY54.js';
5
- import { Typography } from './chunk-MWPX2O4M.js';
2
+ import { ALL_SLOTS } from './chunk-2QB2PXOO.js';
3
+ import { UISlotRenderer } from './chunk-EERAGPBW.js';
4
+ import { Box } from './chunk-BHZG4PDZ.js';
5
+ import { Typography } from './chunk-CLSLVFSP.js';
6
+ import { beginPending, endPending } from './chunk-TWHYH25Q.js';
6
7
  import { useEventBus } from './chunk-6EV3OEBQ.js';
7
- import { registerTrait, unregisterTrait, perfTimeAsync } from './chunk-OLZ3EEML.js';
8
+ import { registerTrait, unregisterTrait, perfTimeAsync } from './chunk-G4GMXYMY.js';
8
9
  import { bindTraitStateGetter, registerTraitSnapshot, recordTransition, recordingTransport, recordServerResponse } from './chunk-O7OLVVBR.js';
9
10
  import { isNotificationSlot } from './chunk-YHY5K2Y7.js';
10
11
  import React, { useState, useMemo, useEffect, useRef, useCallback, useSyncExternalStore } from 'react';
@@ -363,8 +364,12 @@ function useBusIngress(traitBindings, traitIndex, settle, eventBus) {
363
364
  const dispatch = (traitName, eventKey, payload, source) => {
364
365
  if (!claim(source, traitName, eventKey)) return;
365
366
  const tick = source?.tick;
367
+ const pendingKey = source?.pendingKey;
368
+ if (pendingKey !== void 0) beginPending(pendingKey);
366
369
  void settle(traitName, eventKey, payload, tick).catch((err) => {
367
370
  log2.error("dispatch:failed", { trait: traitName, event: eventKey, error: String(err) });
371
+ }).finally(() => {
372
+ if (pendingKey !== void 0) endPending(pendingKey);
368
373
  });
369
374
  };
370
375
  for (const binding of traitBindings) {
@@ -514,7 +519,7 @@ function resolveLambdaBindings(body, params, item, index) {
514
519
  return body;
515
520
  }
516
521
  var LazySlotContentRenderer = React.lazy(
517
- () => import('./UISlotRenderer-7UYHFW4S.js').then((m) => ({ default: m.SlotContentRenderer }))
522
+ () => import('./UISlotRenderer-CR7SCAMR.js').then((m) => ({ default: m.SlotContentRenderer }))
518
523
  );
519
524
  function deferLambdaEntityExprs(value) {
520
525
  if (typeof value === "string") {
@@ -638,12 +643,15 @@ function useSlotFlush(uiSlots, embeddedTraits) {
638
643
  uiSlotsRef.current = uiSlots;
639
644
  const embeddedTraitsRef = useRef(embeddedTraits);
640
645
  embeddedTraitsRef.current = embeddedTraits;
646
+ const writtenRef = useRef(/* @__PURE__ */ new Map());
647
+ const keyOf = (slot, trait) => `${slot}\0${trait}`;
641
648
  const flushSlot = useCallback((traitName, slot, patterns, source) => {
642
649
  const slots = uiSlotsRef.current;
643
650
  const embedded = embeddedTraitsRef.current;
644
651
  if (patterns.length === 0) {
645
652
  flushLog.debug("clear", { traitName, slot });
646
653
  slots.clearBySource(slot, traitName);
654
+ writtenRef.current.set(keyOf(slot, traitName), { kind: "clear", slot, trait: traitName });
647
655
  return;
648
656
  }
649
657
  const last = patterns[patterns.length - 1];
@@ -652,19 +660,20 @@ function useSlotFlush(uiSlots, embeddedTraits) {
652
660
  const props = convertFnFormLambdasInProps(rawProps, typeof patternType === "string" ? patternType : void 0);
653
661
  const isEmbedded = (embedded?.has(traitName) ?? false) && !isNotificationSlot(slot);
654
662
  if (isEmbedded) {
655
- slots.updateTraitContent(traitName, {
663
+ const content = {
656
664
  pattern: patternType,
657
665
  props,
658
666
  slot,
659
667
  priority: 0,
660
- animation: "fade",
661
668
  transitionEvent: source?.event,
662
669
  fromState: source?.state,
663
670
  entity: source?.entity
664
- });
671
+ };
672
+ slots.updateTraitContent(traitName, content);
673
+ writtenRef.current.set(keyOf(slot, traitName), { kind: "trait", trait: traitName, content });
665
674
  return;
666
675
  }
667
- slots.render({
676
+ const config = {
668
677
  target: slot,
669
678
  pattern: patternType,
670
679
  props,
@@ -672,7 +681,9 @@ function useSlotFlush(uiSlots, embeddedTraits) {
672
681
  transitionEvent: source?.event,
673
682
  fromState: source?.state,
674
683
  entity: source?.entity
675
- });
684
+ };
685
+ slots.render(config);
686
+ writtenRef.current.set(keyOf(slot, traitName), { kind: "render", config });
676
687
  }, []);
677
688
  const applyClientEffects = useCallback((clientEffects, clientEffectsByTrait, onNavigate, onNavigateBack, activeTraits) => {
678
689
  const slots = uiSlotsRef.current;
@@ -687,13 +698,18 @@ function useSlotFlush(uiSlots, embeddedTraits) {
687
698
  const { bareTraitRef, patternType, props: unwrapped } = unwrapPattern(pattern);
688
699
  const isEmbedded = (embedded?.has(sourceTrait) ?? false) && !isNotificationSlot(slot);
689
700
  const propsValue = bareTraitRef !== void 0 ? bareTraitRef : { ...unwrapped ?? {}, ...rawProps };
690
- const props = convertFnFormLambdasInProps(propsValue);
701
+ const props = convertFnFormLambdasInProps(propsValue, typeof patternType === "string" ? patternType : void 0);
691
702
  if (pattern === null) {
692
703
  slots.clearBySource(slot, sourceTrait);
704
+ writtenRef.current.set(keyOf(slot, sourceTrait), { kind: "clear", slot, trait: sourceTrait });
693
705
  } else if (isEmbedded) {
694
- slots.updateTraitContent(sourceTrait, { pattern: patternType, props, slot, priority: 0, animation: "fade", transitionEvent: event, fromState });
706
+ const content = { pattern: patternType, props, slot, priority: 0, transitionEvent: event, fromState };
707
+ slots.updateTraitContent(sourceTrait, content);
708
+ writtenRef.current.set(keyOf(slot, sourceTrait), { kind: "trait", trait: sourceTrait, content });
695
709
  } else {
696
- slots.render({ target: slot, pattern: patternType, props, sourceTrait, transitionEvent: event, fromState });
710
+ const config = { target: slot, pattern: patternType, props, sourceTrait, transitionEvent: event, fromState };
711
+ slots.render(config);
712
+ writtenRef.current.set(keyOf(slot, sourceTrait), { kind: "render", config });
697
713
  }
698
714
  } else if (kind === "navigate") {
699
715
  const [, route, params, options] = effect;
@@ -703,7 +719,26 @@ function useSlotFlush(uiSlots, embeddedTraits) {
703
719
  }
704
720
  }
705
721
  }, []);
706
- return useMemo(() => ({ flushSlot, applyClientEffects }), [flushSlot, applyClientEffects]);
722
+ const checkpoint = useCallback(() => new Map(writtenRef.current), []);
723
+ const rollback = useCallback((cp) => {
724
+ const slots = uiSlotsRef.current;
725
+ for (const [key, now] of writtenRef.current) {
726
+ const then = cp.get(key);
727
+ if (then === now) continue;
728
+ if (then === void 0 || then.kind === "clear") {
729
+ if (now.kind === "render") slots.clearBySource(now.config.target, now.config.sourceTrait ?? "kernel");
730
+ else if (now.kind === "trait") {
731
+ flushLog.warn("rollback:embedded-frame-kept", { trait: now.trait });
732
+ }
733
+ } else if (then.kind === "render") {
734
+ slots.render(then.config);
735
+ } else {
736
+ slots.updateTraitContent(then.trait, then.content);
737
+ }
738
+ }
739
+ writtenRef.current = new Map(cp);
740
+ }, []);
741
+ return useMemo(() => ({ flushSlot, applyClientEffects, checkpoint, rollback }), [flushSlot, applyClientEffects, checkpoint, rollback]);
707
742
  }
708
743
  var log3 = createLogger("almadar:ui:circuit:effect-runner");
709
744
  async function runCircuitEffects(params) {
@@ -1196,6 +1231,7 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
1196
1231
  const orbitalName = traitIndex.byName.get(traitName)?.orbitalName ?? traitName;
1197
1232
  let outcome;
1198
1233
  const composedBefore = new Map(store.callsitePayloads);
1234
+ let beforeLocalPaint;
1199
1235
  try {
1200
1236
  outcome = await kernel.dispatch({
1201
1237
  event: eventKey,
@@ -1203,15 +1239,31 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
1203
1239
  ...entityId !== void 0 ? { entityId } : {},
1204
1240
  ...tick !== void 0 ? { tick } : {},
1205
1241
  targetTrait: traitName
1242
+ }, {
1243
+ onLocal: (local) => {
1244
+ beforeLocalPaint = slotFlush.checkpoint();
1245
+ slotFlush.applyClientEffects(
1246
+ local.clientEffects ?? [],
1247
+ local.clientEffectsByTrait,
1248
+ options.navigate,
1249
+ options.navigateBack,
1250
+ activeTraitNamesRef.current
1251
+ );
1252
+ }
1206
1253
  });
1207
1254
  } catch (err) {
1255
+ if (beforeLocalPaint !== void 0) slotFlush.rollback(beforeLocalPaint);
1208
1256
  recordDispatchVerdict(orbitalName, eventKey, { error: err instanceof Error ? err : String(err) });
1209
1257
  throw err;
1210
1258
  }
1211
1259
  recordDispatchVerdict(orbitalName, eventKey, { response: outcome.response });
1260
+ if (outcome.localPainted && !outcome.response.success && beforeLocalPaint !== void 0) {
1261
+ slotFlush.rollback(beforeLocalPaint);
1262
+ }
1263
+ const settledEffects = outcome.localPainted && outcome.serverEffects !== void 0 ? outcome.serverEffects : outcome.response;
1212
1264
  slotFlush.applyClientEffects(
1213
- outcome.response.clientEffects ?? [],
1214
- outcome.response.clientEffectsByTrait,
1265
+ settledEffects.clientEffects ?? [],
1266
+ settledEffects.clientEffectsByTrait,
1215
1267
  options.navigate,
1216
1268
  options.navigateBack,
1217
1269
  activeTraitNamesRef.current
@@ -1,24 +1,121 @@
1
1
  import { useEventBus } from './chunk-6EV3OEBQ.js';
2
- import { useCallback, useSyncExternalStore, useRef, useEffect, useState, useMemo } from 'react';
2
+ import { createContext, useRef, useCallback, useState, useSyncExternalStore, useContext, useEffect, useMemo } from 'react';
3
3
 
4
- function useMediaQuery(query) {
5
- const subscribe = useCallback(
6
- (onChange) => {
7
- if (!hasMatchMedia()) return () => void 0;
8
- const mql = window.matchMedia(query);
9
- mql.addEventListener("change", onChange);
10
- return () => mql.removeEventListener("change", onChange);
4
+ var counts = /* @__PURE__ */ new Map();
5
+ var keyListeners = /* @__PURE__ */ new Map();
6
+ var seq = 0;
7
+ function notify(key) {
8
+ keyListeners.get(key)?.forEach((cb) => cb());
9
+ }
10
+ function newPendingKey() {
11
+ seq += 1;
12
+ return `pending-${seq}`;
13
+ }
14
+ function beginPending(key) {
15
+ counts.set(key, (counts.get(key) ?? 0) + 1);
16
+ if (counts.get(key) === 1) notify(key);
17
+ }
18
+ function endPending(key) {
19
+ const n = (counts.get(key) ?? 0) - 1;
20
+ if (n > 0) {
21
+ counts.set(key, n);
22
+ return;
23
+ }
24
+ counts.delete(key);
25
+ notify(key);
26
+ }
27
+ function isPending(key) {
28
+ return (counts.get(key) ?? 0) > 0;
29
+ }
30
+ function subscribePending(key, cb) {
31
+ let set = keyListeners.get(key);
32
+ if (!set) {
33
+ set = /* @__PURE__ */ new Set();
34
+ keyListeners.set(key, set);
35
+ }
36
+ set.add(cb);
37
+ return () => {
38
+ set?.delete(cb);
39
+ if (set && set.size === 0) keyListeners.delete(key);
40
+ };
41
+ }
42
+ var PendingScopeContext = createContext(null);
43
+ function usePendingScopeValue() {
44
+ const keys = useRef(/* @__PURE__ */ new Set());
45
+ const listeners = useRef(/* @__PURE__ */ new Set());
46
+ const emitChange = useCallback(() => listeners.current.forEach((cb) => cb()), []);
47
+ return useState(() => ({
48
+ track: (key) => {
49
+ if (!isPending(key)) return;
50
+ keys.current.add(key);
51
+ emitChange();
52
+ const unsub = subscribePending(key, () => {
53
+ if (isPending(key)) return;
54
+ keys.current.delete(key);
55
+ unsub();
56
+ emitChange();
57
+ });
11
58
  },
12
- [query]
13
- );
59
+ hasPending: () => keys.current.size > 0,
60
+ subscribe: (cb) => {
61
+ listeners.current.add(cb);
62
+ return () => listeners.current.delete(cb);
63
+ }
64
+ }))[0];
65
+ }
66
+ function useScopeHasPending(scope) {
14
67
  return useSyncExternalStore(
15
- subscribe,
16
- () => hasMatchMedia() && window.matchMedia(query).matches,
68
+ useCallback((cb) => scope ? scope.subscribe(cb) : () => {
69
+ }, [scope]),
70
+ () => scope ? scope.hasPending() : false,
17
71
  () => false
18
72
  );
19
73
  }
20
- function hasMatchMedia() {
21
- return typeof window !== "undefined" && typeof window.matchMedia === "function";
74
+ function usePendingAction() {
75
+ const scope = useContext(PendingScopeContext);
76
+ const [key, setKey] = useState(null);
77
+ const pending = useSyncExternalStore(
78
+ useCallback((cb) => key ? subscribePending(key, cb) : () => {
79
+ }, [key]),
80
+ () => key ? isPending(key) : false,
81
+ () => false
82
+ );
83
+ useEffect(() => {
84
+ if (key && !pending) setKey(null);
85
+ }, [key, pending]);
86
+ const activate = useCallback((emit) => {
87
+ const k = newPendingKey();
88
+ emit(k);
89
+ if (!isPending(k)) return;
90
+ scope?.track(k);
91
+ setKey(k);
92
+ }, [scope]);
93
+ return { pending, activate };
94
+ }
95
+ function usePendingRows() {
96
+ const [rows, setRows] = useState(/* @__PURE__ */ new Map());
97
+ const unsubs = useRef(/* @__PURE__ */ new Map());
98
+ useEffect(() => () => unsubs.current.forEach((u) => u()), []);
99
+ const activateRow = useCallback((rowId, emit) => {
100
+ const k = newPendingKey();
101
+ emit(k);
102
+ if (!isPending(k)) return;
103
+ setRows((prev) => new Map(prev).set(rowId, k));
104
+ unsubs.current.get(rowId)?.();
105
+ unsubs.current.set(rowId, subscribePending(k, () => {
106
+ if (isPending(k)) return;
107
+ unsubs.current.get(rowId)?.();
108
+ unsubs.current.delete(rowId);
109
+ setRows((prev) => {
110
+ if (prev.get(rowId) !== k) return prev;
111
+ const next = new Map(prev);
112
+ next.delete(rowId);
113
+ return next;
114
+ });
115
+ }));
116
+ }, []);
117
+ const isRowPending = useCallback((rowId) => rows.has(rowId), [rows]);
118
+ return { isRowPending, activateRow };
22
119
  }
23
120
  function useInfiniteScroll(onLoadMore, options = {}) {
24
121
  const { rootMargin = "200px", hasMore = true, isLoading = false } = options;
@@ -50,6 +147,30 @@ function useInfiniteScroll(onLoadMore, options = {}) {
50
147
  }, [rootMargin]);
51
148
  return { sentinelRef };
52
149
  }
150
+ function useMediaQuery(query) {
151
+ const subscribe = useCallback(
152
+ (onChange) => {
153
+ if (!hasMatchMedia()) return () => void 0;
154
+ const mql = window.matchMedia(query);
155
+ mql.addEventListener("change", onChange);
156
+ return () => mql.removeEventListener("change", onChange);
157
+ },
158
+ [query]
159
+ );
160
+ return useSyncExternalStore(
161
+ subscribe,
162
+ () => hasMatchMedia() && window.matchMedia(query).matches,
163
+ () => false
164
+ );
165
+ }
166
+ function hasMatchMedia() {
167
+ return typeof window !== "undefined" && typeof window.matchMedia === "function";
168
+ }
169
+
170
+ // hooks/usePrefersReducedMotion.ts
171
+ function usePrefersReducedMotion() {
172
+ return useMediaQuery("(prefers-reduced-motion: reduce)");
173
+ }
53
174
  function useSwipeGesture(callbacks, options = {}) {
54
175
  const { threshold = 50, velocityThreshold = 0.3, preventDefault = false } = options;
55
176
  const startX = useRef(0);
@@ -557,4 +678,4 @@ function usePullToRefresh(onRefresh, options = {}) {
557
678
  };
558
679
  }
559
680
 
560
- export { ALMADAR_DND_MIME, parseQueryBinding, useAuthContext, useCanvasGestures, useDragReorder, useDraggable, useDropZone, useInfiniteScroll, useLongPress, useMediaQuery, usePullToRefresh, useQuerySingleton, useSwipeGesture };
681
+ export { ALMADAR_DND_MIME, PendingScopeContext, beginPending, endPending, parseQueryBinding, useAuthContext, useCanvasGestures, useDragReorder, useDraggable, useDropZone, useInfiniteScroll, useLongPress, useMediaQuery, usePendingAction, usePendingRows, usePendingScopeValue, usePrefersReducedMotion, usePullToRefresh, useQuerySingleton, useScopeHasPending, useSwipeGesture };
@@ -1,4 +1,4 @@
1
- import { en_default } from './chunk-L55SAFKL.js';
1
+ import { en_default } from './chunk-WB2A6V3I.js';
2
2
 
3
3
  // lib/format.ts
4
4
  var { $meta: _meta, ...coreMessages } = en_default;
@@ -1151,7 +1151,37 @@ var en_default = {
1151
1151
  "flowCanvas.presetSize": "{{label}} ({{width}}px)",
1152
1152
  "avl.breadcrumb.transitionNumber": "Transition #{{n}}",
1153
1153
  "orbInspector.pattern": "Pattern",
1154
- "orbInspector.slotLabel": "slot: {{slot}}"
1154
+ "orbInspector.slotLabel": "slot: {{slot}}",
1155
+ "detailPanel.section.activity": "Activity",
1156
+ "detailPanel.section.lineItems": "Line items",
1157
+ "detailPanel.tab.overview": "Overview",
1158
+ "detailPanel.thread.empty": "No messages yet",
1159
+ "detailPanel.openImage": "Open image",
1160
+ "detailPanel.showImage": "Show image {{index}}",
1161
+ "aria.clearSelection": "Clear selection",
1162
+ "aria.searchOptions": "Search options",
1163
+ "aria.zoomIn": "Zoom in",
1164
+ "aria.zoomOut": "Zoom out",
1165
+ "aria.previousPage": "Previous page",
1166
+ "aria.nextPage": "Next page",
1167
+ "aria.downloadDocument": "Download document",
1168
+ "aria.printDocument": "Print document",
1169
+ "aria.wrapLines": "Wrap lines",
1170
+ "aria.copyCode": "Copy code",
1171
+ "aria.clearInput": "Clear input",
1172
+ "aria.skipToContent": "Skip to content",
1173
+ "aria.mainNavigation": "Main navigation",
1174
+ "aria.carouselSlides": "Slides",
1175
+ "aria.minimap": "Minimap",
1176
+ "aria.canvasScene": "Game scene",
1177
+ "aria.learningCanvas": "Interactive diagram",
1178
+ "aria.scene3d": "3D scene",
1179
+ "aria.graphCanvas": "Graph",
1180
+ "aria.positionedCanvas": "Floor plan",
1181
+ "aria.walkTraitDone": "completed",
1182
+ "aria.walkTraitPending": "pending",
1183
+ "aria.walkGraph": "State graph for {{trait}}: {{from}} to {{to}}",
1184
+ "aria.loading": "Loading"
1155
1185
  };
1156
1186
 
1157
1187
  export { en_default };
@@ -1,3 +1,4 @@
1
+ import { beginPending, endPending } from './chunk-TWHYH25Q.js';
1
2
  import { useEventBus } from './chunk-6EV3OEBQ.js';
2
3
  import { useState, useCallback, useEffect, useMemo, useRef } from 'react';
3
4
  import { createLogger } from '@almadar/logger';
@@ -856,7 +857,12 @@ function useUIEvents(dispatch, traitName, validEvents, eventBusInstance) {
856
857
  if (event.source && event.source.dispatched) {
857
858
  return;
858
859
  }
859
- dispatch(smEvent, event.payload);
860
+ const pendingKey = event.source?.pendingKey;
861
+ const settled = dispatch(smEvent, event.payload);
862
+ if (pendingKey !== void 0 && settled instanceof Promise) {
863
+ beginPending(pendingKey);
864
+ void settled.finally(() => endPending(pendingKey));
865
+ }
860
866
  };
861
867
  unsubscribes.push(
862
868
  eventBus.on(`${UI_PREFIX}${traitName}.${smEvent}`, handler)