@omerdlw/base-framework 2.0.1 → 3.0.1

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 (104) hide show
  1. package/README.md +4 -1
  2. package/dist/{chunk-XASQT4CQ.js → chunk-66EYJBIP.js} +47 -27
  3. package/dist/chunk-66EYJBIP.js.map +1 -0
  4. package/dist/{chunk-6SG7V77J.js → chunk-HHWRTQKQ.js} +100 -67
  5. package/dist/chunk-HHWRTQKQ.js.map +1 -0
  6. package/dist/chunk-HLL5EHXC.js +485 -0
  7. package/dist/chunk-HLL5EHXC.js.map +1 -0
  8. package/dist/{chunk-6ZJVLUBE.js → chunk-I5GK7VOV.js} +8 -6
  9. package/dist/chunk-I5GK7VOV.js.map +1 -0
  10. package/dist/chunk-RIOKLMYM.js +145 -0
  11. package/dist/chunk-RIOKLMYM.js.map +1 -0
  12. package/dist/{chunk-SKBITB5X.js → chunk-RRAZKP3J.js} +3 -3
  13. package/dist/{chunk-SKBITB5X.js.map → chunk-RRAZKP3J.js.map} +1 -1
  14. package/dist/{chunk-QXR55VVS.js → chunk-TG6NMHXI.js} +241 -162
  15. package/dist/chunk-TG6NMHXI.js.map +1 -0
  16. package/dist/{chunk-A5RE3F6O.js → chunk-WVRMABDN.js} +249 -427
  17. package/dist/chunk-WVRMABDN.js.map +1 -0
  18. package/dist/index.js +7 -5
  19. package/dist/modules/ambient/analyze.d.ts +18 -3
  20. package/dist/modules/ambient/analyze.d.ts.map +1 -1
  21. package/dist/modules/ambient/dom.d.ts +14 -1
  22. package/dist/modules/ambient/dom.d.ts.map +1 -1
  23. package/dist/modules/ambient/hooks.d.ts.map +1 -1
  24. package/dist/modules/ambient/index.d.ts +2 -2
  25. package/dist/modules/ambient/index.d.ts.map +1 -1
  26. package/dist/modules/ambient/index.js +1 -1
  27. package/dist/modules/ambient/server/index.d.ts +1 -1
  28. package/dist/modules/ambient/server/index.d.ts.map +1 -1
  29. package/dist/modules/ambient/server/index.js +2 -2
  30. package/dist/modules/ambient/server/index.js.map +1 -1
  31. package/dist/modules/ambient/settings.d.ts +1 -0
  32. package/dist/modules/ambient/settings.d.ts.map +1 -1
  33. package/dist/modules/ambient/types.d.ts +13 -0
  34. package/dist/modules/ambient/types.d.ts.map +1 -1
  35. package/dist/modules/ambient/worker/index.js +1 -1
  36. package/dist/modules/context-menu/index.js +4 -3
  37. package/dist/modules/dock/cards.d.ts.map +1 -1
  38. package/dist/modules/dock/constants.d.ts +1 -35
  39. package/dist/modules/dock/constants.d.ts.map +1 -1
  40. package/dist/modules/dock/index.js +3 -2
  41. package/dist/modules/dock/motion.d.ts +7 -5
  42. package/dist/modules/dock/motion.d.ts.map +1 -1
  43. package/dist/modules/dock/overlay.d.ts.map +1 -1
  44. package/dist/modules/dock/routing/guards.d.ts +1 -1
  45. package/dist/modules/dock/routing/navigation.d.ts +7 -4
  46. package/dist/modules/dock/routing/navigation.d.ts.map +1 -1
  47. package/dist/modules/dock/runtime/display.d.ts +31 -2
  48. package/dist/modules/dock/runtime/display.d.ts.map +1 -1
  49. package/dist/modules/dock/runtime/dock.d.ts +2 -3
  50. package/dist/modules/dock/runtime/dock.d.ts.map +1 -1
  51. package/dist/modules/dock/runtime/pipeline.d.ts.map +1 -1
  52. package/dist/modules/dock/styles.d.ts +1 -1
  53. package/dist/modules/dock/styles.d.ts.map +1 -1
  54. package/dist/modules/dock/surface/hooks.d.ts.map +1 -1
  55. package/dist/modules/dock/surface/stack.d.ts.map +1 -1
  56. package/dist/modules/dock/types.d.ts +88 -43
  57. package/dist/modules/dock/types.d.ts.map +1 -1
  58. package/dist/modules/index.d.ts +3 -2
  59. package/dist/modules/index.d.ts.map +1 -1
  60. package/dist/modules/index.js +7 -5
  61. package/dist/modules/loading/context.d.ts.map +1 -1
  62. package/dist/modules/loading/index.js +1 -1
  63. package/dist/modules/navigation/constants.d.ts +37 -0
  64. package/dist/modules/navigation/constants.d.ts.map +1 -0
  65. package/dist/modules/navigation/context.d.ts +10 -0
  66. package/dist/modules/navigation/context.d.ts.map +1 -0
  67. package/dist/modules/navigation/hooks.d.ts +29 -0
  68. package/dist/modules/navigation/hooks.d.ts.map +1 -0
  69. package/dist/modules/navigation/index.d.ts +10 -0
  70. package/dist/modules/navigation/index.d.ts.map +1 -0
  71. package/dist/modules/navigation/index.js +81 -0
  72. package/dist/modules/navigation/index.js.map +1 -0
  73. package/dist/modules/navigation/listeners.d.ts +15 -0
  74. package/dist/modules/navigation/listeners.d.ts.map +1 -0
  75. package/dist/modules/navigation/module.d.ts +25 -0
  76. package/dist/modules/navigation/module.d.ts.map +1 -0
  77. package/dist/modules/navigation/navigator.d.ts +8 -0
  78. package/dist/modules/navigation/navigator.d.ts.map +1 -0
  79. package/dist/modules/navigation/settings.d.ts +10 -0
  80. package/dist/modules/navigation/settings.d.ts.map +1 -0
  81. package/dist/modules/navigation/state.d.ts +20 -0
  82. package/dist/modules/navigation/state.d.ts.map +1 -0
  83. package/dist/modules/navigation/types.d.ts +168 -0
  84. package/dist/modules/navigation/types.d.ts.map +1 -0
  85. package/dist/modules/navigation/utils.d.ts +11 -0
  86. package/dist/modules/navigation/utils.d.ts.map +1 -0
  87. package/docs/getting-started.md +2 -0
  88. package/docs/modules/README.md +5 -1
  89. package/docs/modules/ambient.md +9 -6
  90. package/docs/modules/dock.md +31 -13
  91. package/docs/modules/loading.md +2 -2
  92. package/docs/modules/navigation.md +216 -0
  93. package/docs/proposals/navigation.md +209 -0
  94. package/docs/theming.md +1 -1
  95. package/package.json +6 -1
  96. package/templates/AGENTS.md +2 -0
  97. package/templates/motion/dock.ts +83 -15
  98. package/dist/chunk-6SG7V77J.js.map +0 -1
  99. package/dist/chunk-6ZJVLUBE.js.map +0 -1
  100. package/dist/chunk-A5RE3F6O.js.map +0 -1
  101. package/dist/chunk-QXR55VVS.js.map +0 -1
  102. package/dist/chunk-XASQT4CQ.js.map +0 -1
  103. package/dist/modules/dock/routing/transactions.d.ts +0 -21
  104. package/dist/modules/dock/routing/transactions.d.ts.map +0 -1
package/README.md CHANGED
@@ -33,6 +33,7 @@ For in-depth architecture guides and module-by-module references, explore the [`
33
33
  - 🖱️ **[Context Menu (`context-menu`)](./docs/modules/context-menu.md)** — Viewport clamping, declarative menus
34
34
  - 🎛️ **[Controls Module (`controls`)](./docs/modules/controls.md)** — Paired HUD action rails beside the dock
35
35
  - ⏳ **[Loading & Skeleton (`loading`)](./docs/modules/loading.md)** — Coordinated loading, anti-flicker delay
36
+ - 🧭 **[Navigation (`navigation`)](./docs/modules/navigation.md)** — Exit holds, payload for the next page, back-button hold, intent prefetch
36
37
  - 🎵 **[Media Transport (`media`)](./docs/modules/media.md)** — Session sync, leader vs audible displacement
37
38
  - 🔒 **[Result Pattern (`result`)](./docs/modules/result.md)** — Functional `Result<T, E>`, `ok()`, `err()`
38
39
  - 🛡️ **[Error Boundary (`error-boundary`)](./docs/modules/error-boundary.md)** — Isolated boundaries, deduplicating reporter
@@ -42,7 +43,7 @@ For in-depth architecture guides and module-by-module references, explore the [`
42
43
  ## ✨ Features
43
44
 
44
45
  - ⚡ **Microkernel Architecture:** Ultra-lean orchestration host with topological module dependency sorting, transactional multi-source registry, and atomic route lifecycle commits.
45
- - 🧩 **First-Party Declarative UI Modules:** 9 production-tested modules (`dock`, `modal`, `notification`, `ambient`, `background`, `context-menu`, `controls`, `loading`, `media`) that communicate strictly through typed contracts without tight coupling.
46
+ - 🧩 **First-Party Declarative UI Modules:** 10 production-tested modules (`dock`, `modal`, `notification`, `ambient`, `background`, `context-menu`, `controls`, `loading`, `media`, `navigation`) that communicate strictly through typed contracts without tight coupling.
46
47
  - 🎯 **Single Route Declaration (`usePage`):** Cleanly declare titles, navigation cards, modals, loading states, and background media in a single atomic hook call.
47
48
  - 🔒 **Type-Safe Result Pattern (`Result<T, E>`):** Functional, bulletproof error handling with `ok()`, `err()`, and direct feedback bridging via `toast.fromResult()`.
48
49
  - 🛡️ **Cross-Bundle Context Deduplication:** Guarantees stable React Context singletons across Next.js split chunks and monorepo boundaries.
@@ -76,6 +77,7 @@ import { notificationModule } from "@omerdlw/base-framework/modules/notification
76
77
  import { ambientModule } from "@omerdlw/base-framework/modules/ambient";
77
78
  import { backgroundModule } from "@omerdlw/base-framework/modules/background";
78
79
  import { loadingModule } from "@omerdlw/base-framework/modules/loading";
80
+ import { navigationModule } from "@omerdlw/base-framework/modules/navigation";
79
81
 
80
82
  const modules = [
81
83
  dockModule,
@@ -84,6 +86,7 @@ const modules = [
84
86
  ambientModule,
85
87
  backgroundModule,
86
88
  loadingModule,
89
+ navigationModule,
87
90
  ];
88
91
 
89
92
  export function Providers({ children }: { children: React.ReactNode }) {
@@ -68,6 +68,7 @@ function calculateRemainingMinDuration(startTime, minDuration, now = Date.now())
68
68
  return remaining > 0 ? remaining : 0;
69
69
  }
70
70
  var LoadingContext = getOrCreateGlobalContext("LoadingContext", null);
71
+ var MANUAL_TASK = "manual";
71
72
  var NOOP_LOADING_STORE = createStore(
72
73
  DEFAULT_LOADING_STATE_WITH_PAGE
73
74
  );
@@ -78,6 +79,8 @@ function LoadingProvider({ children }) {
78
79
  const startTimeRef = useRef(null);
79
80
  const minDurationRef = useRef(0);
80
81
  const stopTimerRef = useRef(null);
82
+ const tasksRef = useRef([]);
83
+ const nextTaskRef = useRef(1);
81
84
  const registryLoading = useRegistryValue(
82
85
  "loading",
83
86
  LOADING_REGISTRY_KEY
@@ -94,31 +97,47 @@ function LoadingProvider({ children }) {
94
97
  startTimeRef.current = null;
95
98
  setManualState(DEFAULT_LOADING_STATE);
96
99
  }, [clearStopTimer]);
97
- const startLoading = useCallback(
98
- (options = {}) => {
100
+ const beginTask = useCallback(
101
+ (key, options) => {
99
102
  clearStopTimer();
100
103
  const nextState = normalizeLoadingOptions(options);
101
- startTimeRef.current = Date.now();
102
- minDurationRef.current = nextState.minDuration;
103
- setManualState({
104
- ...nextState,
105
- isLoading: true
106
- });
104
+ const tasks = tasksRef.current.filter((task) => task.key !== key);
105
+ tasks.push({ key, state: nextState });
106
+ tasksRef.current = tasks;
107
+ startTimeRef.current ??= Date.now();
108
+ minDurationRef.current = Math.max(minDurationRef.current, nextState.minDuration);
109
+ setManualState({ ...nextState, isLoading: true });
107
110
  },
108
111
  [clearStopTimer]
109
112
  );
110
- const stopLoading = useCallback(() => {
111
- const remaining = calculateRemainingMinDuration(
112
- startTimeRef.current,
113
- minDurationRef.current
114
- );
115
- if (remaining <= 0) {
116
- resetState();
117
- return;
118
- }
119
- clearStopTimer();
120
- stopTimerRef.current = setTimeout(resetState, remaining);
121
- }, [clearStopTimer, resetState]);
113
+ const endTask = useCallback(
114
+ (key) => {
115
+ const tasks = tasksRef.current;
116
+ if (!tasks.some((task) => task.key === key)) return;
117
+ tasksRef.current = tasks.filter((task) => task.key !== key);
118
+ const latest = tasksRef.current.at(-1);
119
+ if (latest) {
120
+ setManualState({ ...latest.state, isLoading: true });
121
+ return;
122
+ }
123
+ const remaining = calculateRemainingMinDuration(
124
+ startTimeRef.current,
125
+ minDurationRef.current
126
+ );
127
+ if (remaining <= 0) {
128
+ resetState();
129
+ return;
130
+ }
131
+ clearStopTimer();
132
+ stopTimerRef.current = setTimeout(resetState, remaining);
133
+ },
134
+ [clearStopTimer, resetState]
135
+ );
136
+ const startLoading = useCallback(
137
+ (options = {}) => beginTask(MANUAL_TASK, options),
138
+ [beginTask]
139
+ );
140
+ const stopLoading = useCallback(() => endTask(MANUAL_TASK), [endTask]);
122
141
  const setLoading = useCallback(
123
142
  (value2) => {
124
143
  if (value2) startLoading();
@@ -129,21 +148,22 @@ function LoadingProvider({ children }) {
129
148
  const setSkeleton = useCallback((nextSkeleton) => {
130
149
  setManualState((currentState) => {
131
150
  const skeleton = typeof nextSkeleton === "function" ? nextSkeleton(currentState.skeleton) : nextSkeleton;
151
+ const latest = tasksRef.current.at(-1);
152
+ if (latest) latest.state = { ...latest.state, skeleton };
132
153
  return currentState.skeleton === skeleton ? currentState : { ...currentState, skeleton };
133
154
  });
134
155
  }, []);
135
156
  const withLoading = useCallback(
136
157
  async (task, options = {}) => {
137
- startLoading(
138
- typeof options === "string" ? { message: options } : options
139
- );
158
+ const key = nextTaskRef.current++;
159
+ beginTask(key, typeof options === "string" ? { message: options } : options);
140
160
  try {
141
161
  return typeof task === "function" ? await task() : await task;
142
162
  } finally {
143
- stopLoading();
163
+ endTask(key);
144
164
  }
145
165
  },
146
- [startLoading, stopLoading]
166
+ [beginTask, endTask]
147
167
  );
148
168
  useEffect(() => clearStopTimer, [clearStopTimer]);
149
169
  const state = useMemo(
@@ -276,5 +296,5 @@ var loadingModule = defineModule({
276
296
  });
277
297
 
278
298
  export { DEFAULT_LOADING_STATE, DEFAULT_LOADING_STATE_WITH_PAGE, INERT_LOADING_ACTIONS, LOADING_REGISTRY_KEY, LoadingContext, LoadingOverlay, LoadingProvider, NOOP_LOADING_STORE, calculateRemainingMinDuration, defineLoading, loadingModule, loadingTheme, normalizeLoadingOptions, resolveLoadingState, selectPageLoading, useLoading, useLoadingActions, useLoadingOverlayModel, useLoadingRegistration, useLoadingState, useOptionalLoadingActions, useOptionalLoadingState };
279
- //# sourceMappingURL=chunk-XASQT4CQ.js.map
280
- //# sourceMappingURL=chunk-XASQT4CQ.js.map
299
+ //# sourceMappingURL=chunk-66EYJBIP.js.map
300
+ //# sourceMappingURL=chunk-66EYJBIP.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/modules/loading/constants.ts","../src/modules/loading/state.ts","../src/modules/loading/context.tsx","../src/modules/loading/hooks.ts","../src/modules/loading/overlay.tsx","../src/modules/loading/module.tsx"],"names":["value","useMemo","jsx"],"mappings":"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","file":"chunk-66EYJBIP.js","sourcesContent":["import { defineThemeSpec } from \"@/theme\";\nimport {\n type LoadingActions,\n type LoadingState,\n type LoadingStateWithPage,\n type LoadingThemeSlot,\n} from \"./types\";\n\nexport const DEFAULT_LOADING_STATE: LoadingState = Object.freeze({\n isLoading: false,\n skeleton: null,\n minDuration: 0,\n showOverlay: true,\n message: null,\n});\n\nexport const DEFAULT_LOADING_STATE_WITH_PAGE: LoadingStateWithPage = Object.freeze({\n ...DEFAULT_LOADING_STATE,\n isPageLoading: false,\n});\n\nexport const INERT_LOADING_ACTIONS: LoadingActions = Object.freeze({\n setLoading: () => {},\n setSkeleton: () => {},\n startLoading: () => {},\n stopLoading: () => {},\n withLoading: async <T,>(task: Promise<T> | (() => Promise<T>)) =>\n typeof task === \"function\" ? task() : task,\n});\n\nexport const LOADING_REGISTRY_KEY = \"page-loading\";\n\nexport const loadingTheme = defineThemeSpec<LoadingThemeSlot>(\"loading\");\n","import {\n type LoadingOptions,\n type LoadingPageConfig,\n type LoadingState,\n type LoadingStateWithPage,\n} from \"./types\";\n\nexport function normalizeLoadingOptions(\n options: LoadingOptions = {},\n): Omit<LoadingState, \"isLoading\"> {\n const minDuration = Number(options.minDuration);\n return {\n message: typeof options.message === \"string\" ? options.message : null,\n minDuration:\n Number.isFinite(minDuration) && minDuration > 0 ? minDuration : 0,\n showOverlay: options.showOverlay !== false,\n skeleton: options.skeleton ?? null,\n };\n}\n\nexport function selectPageLoading(\n loading: LoadingPageConfig | null | undefined,\n): LoadingOptions | null {\n if (loading === undefined || loading === null) return null;\n if (typeof loading === \"boolean\") return { isLoading: loading };\n if (typeof loading === \"string\") return { isLoading: true, message: loading };\n return loading;\n}\n\nexport function resolveLoadingState(\n manualState: LoadingState,\n registryLoading?: LoadingOptions | null,\n): LoadingStateWithPage {\n if (registryLoading?.isLoading) {\n const normalized = normalizeLoadingOptions(registryLoading);\n return {\n ...normalized,\n isLoading: true,\n isPageLoading: true,\n };\n }\n return {\n ...manualState,\n isPageLoading: manualState.isLoading,\n };\n}\n\nexport function calculateRemainingMinDuration(\n startTime: number | null,\n minDuration: number,\n now = Date.now(),\n): number {\n if (startTime === null || minDuration <= 0) return 0;\n const elapsed = now - startTime;\n const remaining = minDuration - elapsed;\n return remaining > 0 ? remaining : 0;\n}\n","\"use client\";\n\nimport {\n useCallback,\n useEffect,\n useMemo,\n useRef,\n useState,\n} from \"react\";\nimport { useIsomorphicLayoutEffect } from \"@/hooks\";\nimport {\n getOrCreateGlobalContext,\n useRegistryValue,\n} from \"@/kernel\";\nimport { createStore } from \"@/utils\";\nimport {\n DEFAULT_LOADING_STATE,\n DEFAULT_LOADING_STATE_WITH_PAGE,\n LOADING_REGISTRY_KEY,\n} from \"./constants\";\nimport {\n calculateRemainingMinDuration,\n normalizeLoadingOptions,\n resolveLoadingState,\n} from \"./state\";\nimport {\n type LoadingActions,\n type LoadingContextValue,\n type LoadingOptions,\n type LoadingProviderProps,\n type LoadingState,\n type LoadingStateWithPage,\n type SkeletonValue,\n} from \"./types\";\n\nexport const LoadingContext =\n getOrCreateGlobalContext<LoadingContextValue | null>(\"LoadingContext\", null);\n\nconst MANUAL_TASK = \"manual\";\ntype LoadingTaskKey = typeof MANUAL_TASK | number;\n\ninterface LoadingTask {\n key: LoadingTaskKey;\n state: Omit<LoadingState, \"isLoading\">;\n}\n\nexport const NOOP_LOADING_STORE = createStore<LoadingStateWithPage>(\n DEFAULT_LOADING_STATE_WITH_PAGE,\n);\n\nexport function LoadingProvider({ children }: LoadingProviderProps) {\n const [manualState, setManualState] = useState<LoadingState>(\n DEFAULT_LOADING_STATE,\n );\n\n const startTimeRef = useRef<number | null>(null);\n const minDurationRef = useRef<number>(0);\n const stopTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n // What is loading, oldest first: the `startLoading` switch and each\n // `withLoading` task. The overlay shows the latest and stays up until the\n // last one ends, so one task finishing does not hide another.\n const tasksRef = useRef<LoadingTask[]>([]);\n const nextTaskRef = useRef(1);\n\n const registryLoading = useRegistryValue<\"loading\", LoadingOptions>(\n \"loading\",\n LOADING_REGISTRY_KEY,\n );\n\n const clearStopTimer = useCallback(() => {\n if (stopTimerRef.current !== null) {\n clearTimeout(stopTimerRef.current);\n stopTimerRef.current = null;\n }\n }, []);\n\n const resetState = useCallback(() => {\n clearStopTimer();\n minDurationRef.current = 0;\n startTimeRef.current = null;\n setManualState(DEFAULT_LOADING_STATE);\n }, [clearStopTimer]);\n\n const beginTask = useCallback(\n (key: LoadingTaskKey, options: LoadingOptions) => {\n clearStopTimer();\n const nextState = normalizeLoadingOptions(options);\n const tasks = tasksRef.current.filter((task) => task.key !== key);\n tasks.push({ key, state: nextState });\n tasksRef.current = tasks;\n startTimeRef.current ??= Date.now();\n minDurationRef.current = Math.max(minDurationRef.current, nextState.minDuration);\n setManualState({ ...nextState, isLoading: true });\n },\n [clearStopTimer],\n );\n\n const endTask = useCallback(\n (key: LoadingTaskKey) => {\n const tasks = tasksRef.current;\n if (!tasks.some((task) => task.key === key)) return;\n tasksRef.current = tasks.filter((task) => task.key !== key);\n const latest = tasksRef.current.at(-1);\n if (latest) {\n setManualState({ ...latest.state, isLoading: true });\n return;\n }\n\n const remaining = calculateRemainingMinDuration(\n startTimeRef.current,\n minDurationRef.current,\n );\n if (remaining <= 0) {\n resetState();\n return;\n }\n clearStopTimer();\n stopTimerRef.current = setTimeout(resetState, remaining);\n },\n [clearStopTimer, resetState],\n );\n\n const startLoading = useCallback(\n (options: LoadingOptions = {}) => beginTask(MANUAL_TASK, options),\n [beginTask],\n );\n\n const stopLoading = useCallback(() => endTask(MANUAL_TASK), [endTask]);\n\n const setLoading = useCallback(\n (value: boolean) => {\n if (value) startLoading();\n else stopLoading();\n },\n [startLoading, stopLoading],\n );\n\n const setSkeleton = useCallback((nextSkeleton: SkeletonValue) => {\n setManualState((currentState) => {\n const skeleton =\n typeof nextSkeleton === \"function\"\n ? nextSkeleton(currentState.skeleton)\n : nextSkeleton;\n\n const latest = tasksRef.current.at(-1);\n if (latest) latest.state = { ...latest.state, skeleton };\n return currentState.skeleton === skeleton\n ? currentState\n : { ...currentState, skeleton };\n });\n }, []);\n\n const withLoading = useCallback(\n async <T,>(\n task: Promise<T> | (() => Promise<T>),\n options: LoadingOptions | string = {},\n ): Promise<T> => {\n const key = nextTaskRef.current++;\n beginTask(key, typeof options === \"string\" ? { message: options } : options);\n try {\n return typeof task === \"function\" ? await task() : await task;\n } finally {\n endTask(key);\n }\n },\n [beginTask, endTask],\n );\n\n useEffect(() => clearStopTimer, [clearStopTimer]);\n\n const state = useMemo<LoadingStateWithPage>(\n () => resolveLoadingState(manualState, registryLoading),\n [manualState, registryLoading],\n );\n\n const actions = useMemo<LoadingActions>(\n () => ({\n setLoading,\n setSkeleton,\n startLoading,\n stopLoading,\n withLoading,\n }),\n [setLoading, setSkeleton, startLoading, stopLoading, withLoading],\n );\n\n const [store] = useState(() =>\n createStore<LoadingStateWithPage>(state, { freezeSnapshots: false }),\n );\n useIsomorphicLayoutEffect(() => {\n store.publish(state);\n }, [state, store]);\n\n const value = useMemo<LoadingContextValue>(\n () => ({ actions, store }),\n [actions, store],\n );\n\n return <LoadingContext value={value}>{children}</LoadingContext>;\n}\n","\"use client\";\n\nimport { useContext, useMemo } from \"react\";\nimport {\n useIsFullscreenStateActive,\n useRequiredContext,\n useStore,\n} from \"@/hooks\";\nimport {\n useModuleRegistration,\n type RegistryMetadata,\n} from \"@/kernel\";\nimport { useModuleTheme } from \"@/theme\";\nimport {\n INERT_LOADING_ACTIONS,\n loadingTheme,\n} from \"./constants\";\nimport {\n LoadingContext,\n NOOP_LOADING_STORE,\n} from \"./context\";\nimport {\n type LoadingActions,\n type LoadingPageConfig,\n type LoadingStateWithPage,\n type LoadingTheme,\n} from \"./types\";\n\nexport function useLoadingRegistration(\n config: LoadingPageConfig | null | undefined,\n options?: RegistryMetadata & { enabled?: boolean },\n): void {\n useModuleRegistration(\"loading\", config, options);\n}\n\nexport function useLoadingState(): LoadingStateWithPage {\n const { store } = useRequiredContext(\n LoadingContext,\n \"useLoadingState\",\n \"LoadingProvider\",\n );\n return useStore(store);\n}\n\nexport function useLoadingActions(): LoadingActions {\n return useRequiredContext(\n LoadingContext,\n \"useLoadingActions\",\n \"LoadingProvider\",\n ).actions;\n}\n\nexport function useLoading(\n config?: LoadingPageConfig | null,\n options?: RegistryMetadata & { enabled?: boolean },\n): LoadingStateWithPage & LoadingActions {\n useLoadingRegistration(config, options);\n\n const actions = useLoadingActions();\n const state = useLoadingState();\n\n return useMemo(() => ({ ...state, ...actions }), [actions, state]);\n}\n\nexport function useOptionalLoadingState(): LoadingStateWithPage;\nexport function useOptionalLoadingState<T>(\n selector: (state: LoadingStateWithPage) => T,\n isEqual?: (a: T, b: T) => boolean,\n): T;\nexport function useOptionalLoadingState<T = LoadingStateWithPage>(\n selector?: (state: LoadingStateWithPage) => T,\n isEqual?: (a: T, b: T) => boolean,\n): T {\n const ctx = useContext(LoadingContext);\n return useStore(\n ctx?.store ?? NOOP_LOADING_STORE,\n selector as (state: LoadingStateWithPage) => T,\n isEqual,\n );\n}\n\nexport function useOptionalLoadingActions(): LoadingActions {\n const ctx = useContext(LoadingContext);\n return ctx?.actions ?? INERT_LOADING_ACTIONS;\n}\n\nexport function useLoadingOverlayModel(): {\n skeleton: React.ReactNode | null;\n isVisible: boolean;\n theme: LoadingTheme;\n} {\n const { isLoading, skeleton, showOverlay } = useLoadingState();\n const isFullscreenStateActive = useIsFullscreenStateActive();\n const isVisible = isLoading && showOverlay && !isFullscreenStateActive;\n\n const theme = useModuleTheme(loadingTheme);\n\n return { skeleton, isVisible, theme };\n}\n","\"use client\";\n\nimport { Spinner } from \"@/atoms\";\nimport { useLoadingOverlayModel } from \"./hooks\";\n\nexport function LoadingOverlay() {\n const { skeleton, isVisible, theme } = useLoadingOverlayModel();\n if (!isVisible) return null;\n return (\n <div\n className={theme.slots.overlay}\n aria-label=\"Loading\"\n aria-busy=\"true\"\n role=\"status\"\n style={theme.styles.overlay}\n >\n {skeleton || <Spinner size={30} />}\n </div>\n );\n}\n","\"use client\";\n\nimport { useMemo } from \"react\";\nimport { defineModule, type PageModuleApi } from \"@/kernel\";\nimport { LOADING_REGISTRY_KEY } from \"./constants\";\nimport {\n LoadingContext,\n LoadingProvider,\n} from \"./context\";\nimport {\n useLoading,\n useLoadingActions,\n} from \"./hooks\";\nimport { selectPageLoading } from \"./state\";\nimport { LoadingOverlay } from \"./overlay\";\nimport {\n type DefinedLoading,\n type LoadingOptions,\n type LoadingPageApi,\n type LoadingPageConfig,\n} from \"./types\";\n\nfunction useLoadingPage(\n _slice: LoadingOptions | null,\n page: PageModuleApi,\n): LoadingPageApi {\n const actions = useLoadingActions();\n return useMemo(\n () => ({\n ...actions,\n set: (loading: LoadingPageConfig) => page.set({ loading }),\n }),\n [actions, page],\n );\n}\n\nexport function defineLoading(\n definition: (LoadingOptions & { id?: string }) | string = {},\n): DefinedLoading {\n const { id = \"loading\", ...config }: LoadingOptions & { id?: string } =\n selectPageLoading(definition) ?? {};\n\n return Object.freeze({\n config,\n id,\n use: function useDefinedLoading(overrides = {}, options = {}) {\n return useLoading(\n { ...config, ...selectPageLoading(overrides) },\n options,\n );\n },\n });\n}\n\nexport const loadingModule = defineModule({\n id: \"loading\",\n context: LoadingContext,\n Provider: LoadingProvider,\n Overlay: LoadingOverlay,\n registry: {\n cleanupDelayMs: 600,\n keyPolicy: \"singleton\",\n lifecycle: \"graceful\",\n singletonKey: LOADING_REGISTRY_KEY,\n },\n page: {\n select: (config) => selectPageLoading(config.loading),\n entries: (slice: LoadingOptions) => [\n { key: LOADING_REGISTRY_KEY, value: slice },\n ],\n use: useLoadingPage,\n },\n});\n\ndeclare module \"@omerdlw/base-framework/kernel\" {\n interface CoreModules {\n loading: typeof loadingModule;\n }\n interface PageConfig {\n loading?: LoadingPageConfig;\n }\n}\n"]}
@@ -130,10 +130,12 @@ var DEFAULT_CSS_VAR_NAMES = Object.freeze({
130
130
  tone: "--ambient-{role}-{tone}"
131
131
  });
132
132
  var DEFAULT_PROXY = "/api/ambient/proxy?url=";
133
+ var DEFAULT_LINGER_MS = 3e3;
133
134
  var createDefaults = () => ({
134
135
  analysis: { ...DEFAULT_ANALYSIS },
135
136
  cacheLimit: 100,
136
137
  cssVars: { ...DEFAULT_CSS_VAR_NAMES, tones: AMBIENT_TONES },
138
+ linger: DEFAULT_LINGER_MS,
137
139
  proxy: DEFAULT_PROXY,
138
140
  strategy: "auto",
139
141
  worker: null
@@ -148,6 +150,7 @@ function mergeAmbientSettings(base, input) {
148
150
  analysis: { ...base.analysis, ...input.analysis },
149
151
  cacheLimit: input.cacheLimit ?? base.cacheLimit,
150
152
  cssVars: { ...base.cssVars, ...input.cssVars },
153
+ linger: input.linger ?? base.linger,
151
154
  proxy: input.proxy === void 0 ? base.proxy : input.proxy,
152
155
  strategy: input.strategy ?? base.strategy,
153
156
  worker: input.worker === void 0 ? base.worker : input.worker
@@ -238,6 +241,7 @@ function kmeans(points, weights, requested, { iterations = 16, lightnessWeight =
238
241
  }
239
242
 
240
243
  // src/modules/ambient/analyze.ts
244
+ var AMBIENT_ANALYSIS_VERSION = 2;
241
245
  var MIN_CHROMA = 0.022;
242
246
  var MIN_PRESENCE = 0.03;
243
247
  var MIN_POPULATION = 4e-3;
@@ -294,26 +298,47 @@ function pickBest(items, score) {
294
298
  return best;
295
299
  }
296
300
  var round3 = (value) => Math.round(value * 1e3) / 1e3;
297
- function buildSwatch(source, { cap, max, min, rel }, fallback, fromImage, minContrast) {
298
- const h = source?.h ?? fallback.h;
299
- const l = clamp(source?.l ?? fallback.l, min, max);
300
- const tone = toneOf(h, l, rel, cap);
301
+ function skinLikeness({ c, h, l }) {
302
+ return (1 - smoothstep(12, 26, hueDistance(h, 54))) * smoothstep(0.02, 0.035, c) * (1 - smoothstep(0.11, 0.15, c)) * smoothstep(0.26, 0.38, l) * (1 - smoothstep(0.88, 0.95, l));
303
+ }
304
+ var warmth = (h) => 1 - smoothstep(22, 50, hueDistance(h, 42));
305
+ var salience = (color) => 1 - 0.65 * skinLikeness(color);
306
+ var roleChroma = (source, { cap, gain }) => Math.min(
307
+ cap * (1 - 0.3 * warmth(source.h)),
308
+ // A faint tint is raised further, so it still reads as a colour.
309
+ Math.max(source.c * gain, Math.min(0.05, source.c * 1.6)) * (1 - 0.3 * skinLikeness(source))
310
+ );
311
+ var fitTone = (h, l, c) => ({
312
+ c: Math.max(0, Math.min(c, maxChroma(l, h) * 0.96)),
313
+ h,
314
+ l
315
+ });
316
+ function buildSwatch(tone, population, fromImage, minContrast) {
317
+ const room = maxChroma(tone.l, tone.h) * 0.96;
318
+ const rel = room > 0 ? tone.c / room : 0;
301
319
  const ramp = {};
302
320
  for (const step of AMBIENT_TONES) {
303
- ramp[step] = formatOklch(toneOf(h, step / 100, rel, cap));
321
+ ramp[step] = formatOklch(toneOf(tone.h, step / 100, rel, Math.max(tone.c, 0.04)));
304
322
  }
305
323
  return {
306
324
  c: round3(tone.c),
307
325
  color: formatOklch(tone),
308
326
  fromImage,
309
- h: Math.round(h * 10) / 10,
327
+ h: Math.round(tone.h * 10) / 10,
310
328
  l: round3(tone.l),
311
329
  on: formatOklch(onColorFor(tone, minContrast)),
312
- population: round3(source?.pop ?? 0),
330
+ population: round3(population),
313
331
  ramp
314
332
  };
315
333
  }
316
- var lift = (rel, base, gain, ceiling) => clamp(base + gain * rel, 0, ceiling);
334
+ function roleTone(source, spec, fallback, dilution = 1) {
335
+ if (!source) return fitTone(fallback.h, clamp(fallback.l, spec.min, spec.max), fallback.c);
336
+ return fitTone(
337
+ source.h,
338
+ clamp(source.l, spec.min, spec.max),
339
+ roleChroma(source, spec) * dilution
340
+ );
341
+ }
317
342
  function clusterAmbientPixels(pixels, options) {
318
343
  const { data, width, height } = pixels;
319
344
  const bounds = options.letterbox ? findContentBounds(pixels) : { bottom: height, left: 0, right: width, top: 0 };
@@ -385,18 +410,23 @@ function clusterAmbientPixels(pixels, options) {
385
410
  points[i * 3 + 2] = b;
386
411
  weights[i] = w * (1 + 5 * Math.sqrt(a * a + b * b));
387
412
  });
388
- const { assignment, centroids, k } = kmeans(points, weights, budgets[group]);
389
- const pop = new Float64Array(k);
390
- members.forEach((bin, i) => pop[assignment[i]] += binSums[bin * 4 + 3]);
413
+ const { assignment, k } = kmeans(points, weights, budgets[group]);
414
+ const sums = new Float64Array(k * 4);
415
+ members.forEach((bin, i) => {
416
+ for (let channel = 0; channel < 4; channel++) {
417
+ sums[assignment[i] * 4 + channel] += binSums[bin * 4 + channel];
418
+ }
419
+ });
391
420
  const index = new Int32Array(k).fill(-1);
392
421
  for (let j = 0; j < k; j++) {
393
- if (pop[j] <= 0) continue;
394
- const lch = labToLch([centroids[j * 3], centroids[j * 3 + 1], centroids[j * 3 + 2]]);
422
+ const pop = sums[j * 4 + 3];
423
+ if (pop <= 0) continue;
424
+ const lch = labToLch([sums[j * 4] / pop, sums[j * 4 + 1] / pop, sums[j * 4 + 2] / pop]);
395
425
  const room = maxChroma(lch.l, lch.h);
396
426
  index[j] = clusters.length;
397
427
  clusters.push({
398
428
  ...lch,
399
- pop: pop[j] / total,
429
+ pop: pop / total,
400
430
  rel: room > 0 ? clamp(lch.c / room, 0, 1) : 0,
401
431
  rely: reliability(lch.l)
402
432
  });
@@ -404,20 +434,26 @@ function clusterAmbientPixels(pixels, options) {
404
434
  members.forEach((bin, i) => clusterOfBin[bin] = index[assignment[i]]);
405
435
  });
406
436
  const regionWeights = AMBIENT_REGIONS.map(() => new Float64Array(clusters.length));
437
+ const regionLab = AMBIENT_REGIONS.map(() => new Float64Array(clusters.length * 3));
407
438
  for (let i = 0; i < pixelBins.length; i++) {
408
439
  const bin = pixelBins[i];
409
440
  if (bin < 0) continue;
410
441
  const cluster = clusterOfBin[bin];
411
442
  const bits = pixelRegions[i];
443
+ const w = binSums[bin * 4 + 3];
412
444
  AMBIENT_REGIONS.forEach((region, r) => {
413
- if (bits & REGION_BITS[region]) regionWeights[r][cluster] += 1;
445
+ if (!(bits & REGION_BITS[region])) return;
446
+ regionWeights[r][cluster] += 1;
447
+ for (let channel = 0; channel < 3; channel++) {
448
+ regionLab[r][cluster * 3 + channel] += binSums[bin * 4 + channel] / w;
449
+ }
414
450
  });
415
451
  }
416
- return { clusters, meanL, regionWeights };
452
+ return { clusters, meanL, regionLab, regionWeights };
417
453
  }
418
454
  function analyzeAmbientPixels(pixels, overrides = {}) {
419
455
  const options = { ...DEFAULT_ANALYSIS, ...overrides };
420
- const { clusters, meanL, regionWeights } = clusterAmbientPixels(pixels, options);
456
+ const { clusters, meanL, regionLab, regionWeights } = clusterAmbientPixels(pixels, options);
421
457
  const chromatic = clusters.filter(isChromatic);
422
458
  const presence = chromatic.reduce(
423
459
  (sum, cluster) => sum + cluster.pop * colorfulness(cluster.c) * cluster.rely,
@@ -426,7 +462,7 @@ function analyzeAmbientPixels(pixels, overrides = {}) {
426
462
  const neutral = presence < MIN_PRESENCE;
427
463
  const dilution = 0.55 + 0.45 * Math.min(1, presence / 0.25);
428
464
  const { minContrast, minHueDistance } = options;
429
- const grey = { h: 0, l: clamp(meanL, 0.3, 0.8) };
465
+ const grey = { c: 0, h: 0, l: clamp(meanL, 0.3, 0.8) };
430
466
  const family = /* @__PURE__ */ new Map();
431
467
  for (const cluster of chromatic) {
432
468
  let share2 = 0;
@@ -436,69 +472,62 @@ function analyzeAmbientPixels(pixels, overrides = {}) {
436
472
  family.set(cluster, share2);
437
473
  }
438
474
  const share = (cluster) => family.get(cluster) ?? cluster.pop;
439
- const withShare = (cluster) => cluster ? { ...cluster, pop: share(cluster) } : null;
440
- const dominantPick = neutral ? null : pickBest(chromatic, (cluster) => share(cluster) * cluster.c * cluster.rely);
475
+ const dominantPick = neutral ? null : pickBest(
476
+ chromatic,
477
+ (cluster) => share(cluster) * cluster.c * cluster.rely * salience(cluster)
478
+ );
441
479
  const vividPick = neutral ? null : pickBest(
442
480
  chromatic.filter((cluster) => share(cluster) >= MIN_POPULATION),
443
- (cluster) => cluster.c * cluster.c * Math.pow(share(cluster), 0.3) * cluster.rely
481
+ (cluster) => cluster.c * cluster.c * Math.pow(share(cluster), 0.3) * cluster.rely * salience(cluster)
444
482
  );
445
483
  const accentPick = pickBest(
446
484
  chromatic.filter(
447
485
  (cluster) => neutral ? share(cluster) >= 6e-3 && cluster.c >= 0.06 : share(cluster) >= MIN_POPULATION && cluster.c >= 0.035 && (!dominantPick || hueDistance(cluster.h, dominantPick.h) >= minHueDistance)
448
486
  ),
449
- (cluster) => Math.pow(share(cluster), 0.6) * cluster.c * cluster.rely
487
+ (cluster) => Math.pow(share(cluster), 0.6) * cluster.c * cluster.rely * salience(cluster)
450
488
  );
451
489
  const mutedPick = neutral ? null : pickBest(
452
490
  chromatic.filter((cluster) => cluster.c <= 0.09),
453
491
  (cluster) => share(cluster) * cluster.rely * (1 - cluster.c / 0.12)
454
492
  );
455
493
  const dominantHue = dominantPick?.h ?? vividPick?.h ?? 0;
494
+ const accentSource = accentPick ?? vividPick;
456
495
  const roles = {
457
496
  dominant: buildSwatch(
458
- withShare(dominantPick),
459
- {
460
- cap: 0.22,
461
- max: 0.74,
462
- min: 0.42,
463
- rel: dominantPick ? lift(dominantPick.rel, 0.25, 0.85, 0.9) : 0
464
- },
465
- grey,
497
+ roleTone(dominantPick, { cap: 0.2, gain: 1.15, max: 0.74, min: 0.42 }, grey),
498
+ dominantPick ? share(dominantPick) : 0,
466
499
  true,
467
500
  minContrast
468
501
  ),
469
502
  vibrant: buildSwatch(
470
- withShare(vividPick),
471
- {
472
- cap: 0.24,
473
- max: 0.84,
474
- min: 0.62,
475
- rel: vividPick ? lift(vividPick.rel, 0.4, 0.75, 0.95) * dilution : 0
476
- },
477
- { h: dominantHue, l: 0.78 },
503
+ roleTone(
504
+ vividPick,
505
+ { cap: 0.22, gain: 1.35, max: 0.84, min: 0.62 },
506
+ { c: 0, h: dominantHue, l: 0.78 },
507
+ dilution
508
+ ),
509
+ vividPick ? share(vividPick) : 0,
478
510
  true,
479
511
  minContrast
480
512
  ),
481
513
  muted: buildSwatch(
482
- withShare(mutedPick),
483
- {
484
- cap: 0.08,
485
- max: 0.66,
486
- min: 0.38,
487
- rel: mutedPick ? clamp(mutedPick.rel, 0.1, 0.3) : dominantPick ? 0.2 : 0
488
- },
489
- { h: dominantHue, l: clamp(meanL, 0.38, 0.66) },
514
+ roleTone(
515
+ mutedPick,
516
+ { cap: 0.07, gain: 1, max: 0.66, min: 0.38 },
517
+ { c: dominantPick ? 0.035 : 0, h: dominantHue, l: clamp(meanL, 0.38, 0.66) }
518
+ ),
519
+ mutedPick ? share(mutedPick) : 0,
490
520
  Boolean(mutedPick) || neutral,
491
521
  minContrast
492
522
  ),
493
523
  accent: buildSwatch(
494
- withShare(accentPick ?? vividPick),
495
- {
496
- cap: 0.24,
497
- max: 0.8,
498
- min: 0.55,
499
- rel: accentPick ?? vividPick ? lift((accentPick ?? vividPick).rel, 0.35, 0.75, 0.92) * dilution : 0
500
- },
501
- { h: dominantHue, l: 0.72 },
524
+ roleTone(
525
+ accentSource,
526
+ { cap: 0.22, gain: 1.3, max: 0.8, min: 0.55 },
527
+ { c: 0, h: dominantHue, l: 0.72 },
528
+ dilution
529
+ ),
530
+ accentSource ? share(accentSource) : 0,
502
531
  Boolean(accentPick),
503
532
  minContrast
504
533
  )
@@ -515,35 +544,39 @@ function analyzeAmbientPixels(pixels, overrides = {}) {
515
544
  options.shadowLightness - 0.02,
516
545
  options.shadowLightness + 0.02
517
546
  );
518
- const shadow = deepSource ? toneOf(deepSource.h, shadowL, lift(deepSource.rel, 0.3, 0.7, 0.85), 0.1) : { c: 0, h: 0, l: shadowL };
547
+ const shadow = deepSource ? toneOf(deepSource.h, shadowL, clamp(0.3 + 0.7 * deepSource.rel, 0, 0.85), 0.1) : { c: 0, h: 0, l: shadowL };
519
548
  const regions = {};
520
549
  AMBIENT_REGIONS.forEach((region, r) => {
521
550
  const weights = regionWeights[r];
551
+ const lab = regionLab[r];
522
552
  let regionTotal = 0;
523
553
  let regionL = 0;
524
554
  clusters.forEach((cluster, j) => {
525
555
  regionTotal += weights[j];
526
556
  regionL += cluster.l * weights[j];
527
557
  });
528
- const share2 = (j) => regionTotal > 0 ? weights[j] / regionTotal : 0;
529
558
  let strength = 0;
530
559
  let pick = null;
531
560
  let pickScore = 0;
532
- clusters.forEach((cluster, j) => {
533
- if (!isChromatic(cluster)) return;
534
- strength += share2(j) * colorfulness(cluster.c) * cluster.rely;
535
- const score = share2(j) * cluster.c * cluster.rely;
561
+ clusters.forEach((_, j) => {
562
+ const count = weights[j];
563
+ if (count <= 0) return;
564
+ const local = labToLch([lab[j * 3] / count, lab[j * 3 + 1] / count, lab[j * 3 + 2] / count]);
565
+ const rely = reliability(local.l);
566
+ if (local.c < MIN_CHROMA || rely <= 0) return;
567
+ const part = count / regionTotal;
568
+ strength += part * colorfulness(local.c) * rely;
569
+ const score = part * local.c * rely * salience(local);
536
570
  if (score > pickScore) {
537
571
  pickScore = score;
538
- pick = cluster;
572
+ pick = local;
539
573
  }
540
574
  });
541
575
  const chosen = neutral || strength < MIN_PRESENCE ? null : pick;
542
- const tone = chosen ? toneOf(
576
+ const tone = chosen ? fitTone(
543
577
  chosen.h,
544
578
  clamp(chosen.l, 0.42, 0.72),
545
- lift(chosen.rel, 0.25, 0.85, 0.9),
546
- 0.22
579
+ roleChroma(chosen, { cap: 0.18, gain: 1.15 })
547
580
  ) : {
548
581
  c: 0,
549
582
  h: 0,
@@ -572,6 +605,6 @@ function fitSample(width, height, size) {
572
605
  };
573
606
  }
574
607
 
575
- export { analyzeAmbientPixels, configureAmbient, contrastRatio, findContentBounds, fitSample, formatOklch, getAmbientSettings, maxChroma, onColorFor, resolveAnalysis, srgbToOklab, toneOf };
576
- //# sourceMappingURL=chunk-6SG7V77J.js.map
577
- //# sourceMappingURL=chunk-6SG7V77J.js.map
608
+ export { AMBIENT_ANALYSIS_VERSION, analyzeAmbientPixels, configureAmbient, contrastRatio, findContentBounds, fitSample, formatOklch, getAmbientSettings, maxChroma, onColorFor, resolveAnalysis, srgbToOklab, toneOf };
609
+ //# sourceMappingURL=chunk-HHWRTQKQ.js.map
610
+ //# sourceMappingURL=chunk-HHWRTQKQ.js.map