@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.
- package/README.md +4 -1
- package/dist/{chunk-XASQT4CQ.js → chunk-66EYJBIP.js} +47 -27
- package/dist/chunk-66EYJBIP.js.map +1 -0
- package/dist/{chunk-6SG7V77J.js → chunk-HHWRTQKQ.js} +100 -67
- package/dist/chunk-HHWRTQKQ.js.map +1 -0
- package/dist/chunk-HLL5EHXC.js +485 -0
- package/dist/chunk-HLL5EHXC.js.map +1 -0
- package/dist/{chunk-6ZJVLUBE.js → chunk-I5GK7VOV.js} +8 -6
- package/dist/chunk-I5GK7VOV.js.map +1 -0
- package/dist/chunk-RIOKLMYM.js +145 -0
- package/dist/chunk-RIOKLMYM.js.map +1 -0
- package/dist/{chunk-SKBITB5X.js → chunk-RRAZKP3J.js} +3 -3
- package/dist/{chunk-SKBITB5X.js.map → chunk-RRAZKP3J.js.map} +1 -1
- package/dist/{chunk-QXR55VVS.js → chunk-TG6NMHXI.js} +241 -162
- package/dist/chunk-TG6NMHXI.js.map +1 -0
- package/dist/{chunk-A5RE3F6O.js → chunk-WVRMABDN.js} +249 -427
- package/dist/chunk-WVRMABDN.js.map +1 -0
- package/dist/index.js +7 -5
- package/dist/modules/ambient/analyze.d.ts +18 -3
- package/dist/modules/ambient/analyze.d.ts.map +1 -1
- package/dist/modules/ambient/dom.d.ts +14 -1
- package/dist/modules/ambient/dom.d.ts.map +1 -1
- package/dist/modules/ambient/hooks.d.ts.map +1 -1
- package/dist/modules/ambient/index.d.ts +2 -2
- package/dist/modules/ambient/index.d.ts.map +1 -1
- package/dist/modules/ambient/index.js +1 -1
- package/dist/modules/ambient/server/index.d.ts +1 -1
- package/dist/modules/ambient/server/index.d.ts.map +1 -1
- package/dist/modules/ambient/server/index.js +2 -2
- package/dist/modules/ambient/server/index.js.map +1 -1
- package/dist/modules/ambient/settings.d.ts +1 -0
- package/dist/modules/ambient/settings.d.ts.map +1 -1
- package/dist/modules/ambient/types.d.ts +13 -0
- package/dist/modules/ambient/types.d.ts.map +1 -1
- package/dist/modules/ambient/worker/index.js +1 -1
- package/dist/modules/context-menu/index.js +4 -3
- package/dist/modules/dock/cards.d.ts.map +1 -1
- package/dist/modules/dock/constants.d.ts +1 -35
- package/dist/modules/dock/constants.d.ts.map +1 -1
- package/dist/modules/dock/index.js +3 -2
- package/dist/modules/dock/motion.d.ts +7 -5
- package/dist/modules/dock/motion.d.ts.map +1 -1
- package/dist/modules/dock/overlay.d.ts.map +1 -1
- package/dist/modules/dock/routing/guards.d.ts +1 -1
- package/dist/modules/dock/routing/navigation.d.ts +7 -4
- package/dist/modules/dock/routing/navigation.d.ts.map +1 -1
- package/dist/modules/dock/runtime/display.d.ts +31 -2
- package/dist/modules/dock/runtime/display.d.ts.map +1 -1
- package/dist/modules/dock/runtime/dock.d.ts +2 -3
- package/dist/modules/dock/runtime/dock.d.ts.map +1 -1
- package/dist/modules/dock/runtime/pipeline.d.ts.map +1 -1
- package/dist/modules/dock/styles.d.ts +1 -1
- package/dist/modules/dock/styles.d.ts.map +1 -1
- package/dist/modules/dock/surface/hooks.d.ts.map +1 -1
- package/dist/modules/dock/surface/stack.d.ts.map +1 -1
- package/dist/modules/dock/types.d.ts +88 -43
- package/dist/modules/dock/types.d.ts.map +1 -1
- package/dist/modules/index.d.ts +3 -2
- package/dist/modules/index.d.ts.map +1 -1
- package/dist/modules/index.js +7 -5
- package/dist/modules/loading/context.d.ts.map +1 -1
- package/dist/modules/loading/index.js +1 -1
- package/dist/modules/navigation/constants.d.ts +37 -0
- package/dist/modules/navigation/constants.d.ts.map +1 -0
- package/dist/modules/navigation/context.d.ts +10 -0
- package/dist/modules/navigation/context.d.ts.map +1 -0
- package/dist/modules/navigation/hooks.d.ts +29 -0
- package/dist/modules/navigation/hooks.d.ts.map +1 -0
- package/dist/modules/navigation/index.d.ts +10 -0
- package/dist/modules/navigation/index.d.ts.map +1 -0
- package/dist/modules/navigation/index.js +81 -0
- package/dist/modules/navigation/index.js.map +1 -0
- package/dist/modules/navigation/listeners.d.ts +15 -0
- package/dist/modules/navigation/listeners.d.ts.map +1 -0
- package/dist/modules/navigation/module.d.ts +25 -0
- package/dist/modules/navigation/module.d.ts.map +1 -0
- package/dist/modules/navigation/navigator.d.ts +8 -0
- package/dist/modules/navigation/navigator.d.ts.map +1 -0
- package/dist/modules/navigation/settings.d.ts +10 -0
- package/dist/modules/navigation/settings.d.ts.map +1 -0
- package/dist/modules/navigation/state.d.ts +20 -0
- package/dist/modules/navigation/state.d.ts.map +1 -0
- package/dist/modules/navigation/types.d.ts +168 -0
- package/dist/modules/navigation/types.d.ts.map +1 -0
- package/dist/modules/navigation/utils.d.ts +11 -0
- package/dist/modules/navigation/utils.d.ts.map +1 -0
- package/docs/getting-started.md +2 -0
- package/docs/modules/README.md +5 -1
- package/docs/modules/ambient.md +9 -6
- package/docs/modules/dock.md +31 -13
- package/docs/modules/loading.md +2 -2
- package/docs/modules/navigation.md +216 -0
- package/docs/proposals/navigation.md +209 -0
- package/docs/theming.md +1 -1
- package/package.json +6 -1
- package/templates/AGENTS.md +2 -0
- package/templates/motion/dock.ts +83 -15
- package/dist/chunk-6SG7V77J.js.map +0 -1
- package/dist/chunk-6ZJVLUBE.js.map +0 -1
- package/dist/chunk-A5RE3F6O.js.map +0 -1
- package/dist/chunk-QXR55VVS.js.map +0 -1
- package/dist/chunk-XASQT4CQ.js.map +0 -1
- package/dist/modules/dock/routing/transactions.d.ts +0 -21
- 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:**
|
|
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
|
|
98
|
-
(options
|
|
100
|
+
const beginTask = useCallback(
|
|
101
|
+
(key, options) => {
|
|
99
102
|
clearStopTimer();
|
|
100
103
|
const nextState = normalizeLoadingOptions(options);
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
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
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
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
|
-
|
|
138
|
-
|
|
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
|
-
|
|
163
|
+
endTask(key);
|
|
144
164
|
}
|
|
145
165
|
},
|
|
146
|
-
[
|
|
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-
|
|
280
|
-
//# sourceMappingURL=chunk-
|
|
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
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
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,
|
|
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(
|
|
330
|
+
population: round3(population),
|
|
313
331
|
ramp
|
|
314
332
|
};
|
|
315
333
|
}
|
|
316
|
-
|
|
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,
|
|
389
|
-
const
|
|
390
|
-
members.forEach((bin, i) =>
|
|
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
|
-
|
|
394
|
-
|
|
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
|
|
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])
|
|
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
|
|
440
|
-
|
|
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
|
-
|
|
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
|
-
|
|
471
|
-
|
|
472
|
-
cap: 0.
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
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
|
-
|
|
483
|
-
|
|
484
|
-
cap: 0.
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
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
|
-
|
|
495
|
-
|
|
496
|
-
cap: 0.
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
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,
|
|
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((
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
const
|
|
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 =
|
|
572
|
+
pick = local;
|
|
539
573
|
}
|
|
540
574
|
});
|
|
541
575
|
const chosen = neutral || strength < MIN_PRESENCE ? null : pick;
|
|
542
|
-
const tone = chosen ?
|
|
576
|
+
const tone = chosen ? fitTone(
|
|
543
577
|
chosen.h,
|
|
544
578
|
clamp(chosen.l, 0.42, 0.72),
|
|
545
|
-
|
|
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-
|
|
577
|
-
//# sourceMappingURL=chunk-
|
|
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
|