@windmorph/react 0.2.0 → 0.2.2
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/dist/components/carousel/carousel-styles.d.ts +6 -15
- package/dist/components/carousel/carousel-styles.js +4 -7
- package/dist/components/carousel/carousel-styles.js.map +1 -1
- package/dist/components/checkbox/checkbox-tokens.d.ts +1 -1
- package/dist/components/chip/chip-tokens.d.ts +4 -4
- package/dist/components/date-picker/DatePicker.js +11 -4
- package/dist/components/date-picker/DatePicker.js.map +1 -1
- package/dist/components/date-picker/date-picker-styles.d.ts +6 -6
- package/dist/components/date-picker/date-picker-tokens.d.ts +1 -1
- package/dist/components/dialog/Dialog.d.ts +4 -2
- package/dist/components/dialog/Dialog.js +2 -2
- package/dist/components/dialog/Dialog.js.map +1 -1
- package/dist/components/dialog/dialog-tokens.d.ts +1 -1
- package/dist/components/icon-button/IconButton.d.ts +3 -0
- package/dist/components/icon-button/IconButton.js +3 -0
- package/dist/components/icon-button/IconButton.js.map +1 -1
- package/dist/components/list/List.d.ts +9 -0
- package/dist/components/list/List.js +7 -4
- package/dist/components/list/List.js.map +1 -1
- package/dist/components/list/list-styles.js +6 -6
- package/dist/components/list/list-styles.js.map +1 -1
- package/dist/components/list/list-tokens.d.ts +1 -1
- package/dist/components/list/list-tokens.js +50 -0
- package/dist/components/list/list-tokens.js.map +1 -1
- package/dist/components/menu/menu-tokens.d.ts +1 -1
- package/dist/components/navigation/NavigationRail.js +52 -15
- package/dist/components/navigation/NavigationRail.js.map +1 -1
- package/dist/components/navigation/nav-item-styles.js +4 -4
- package/dist/components/navigation/nav-item-styles.js.map +1 -1
- package/dist/components/search/SearchBar.js +19 -34
- package/dist/components/search/SearchBar.js.map +1 -1
- package/dist/components/search/SearchField.d.ts +111 -0
- package/dist/components/search/SearchField.js +128 -0
- package/dist/components/search/SearchField.js.map +1 -0
- package/dist/components/search/search-field-frame.js +36 -0
- package/dist/components/search/search-field-frame.js.map +1 -0
- package/dist/components/search/search-styles.d.ts +58 -0
- package/dist/components/search/search-styles.js +27 -2
- package/dist/components/search/search-styles.js.map +1 -1
- package/dist/components/select/search-list.js +8 -13
- package/dist/components/select/search-list.js.map +1 -1
- package/dist/components/sheet/SideSheet.d.ts +2 -0
- package/dist/components/sheet/SideSheet.js +1 -1
- package/dist/components/sheet/SideSheet.js.map +1 -1
- package/dist/components/snackbar/snackbar-tokens.d.ts +1 -1
- package/dist/components/text-field/TextField.js +19 -5
- package/dist/components/text-field/TextField.js.map +1 -1
- package/dist/components/toolbar/toolbar-styles.js +2 -2
- package/dist/components/toolbar/toolbar-styles.js.map +1 -1
- package/dist/components/tooltip/tooltip-tokens.d.ts +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.js +3 -2
- package/dist/primitives/ButtonBase.js +1 -0
- package/dist/primitives/ButtonBase.js.map +1 -1
- package/dist/styles/generated/components.css +5 -0
- package/dist/styles/utilities.css +8 -0
- package/dist/styles.compiled.css +1 -1
- package/dist/theme/ThemeProvider.js +17 -0
- package/dist/theme/ThemeProvider.js.map +1 -1
- package/package.json +1 -1
|
@@ -75,6 +75,8 @@ function useStreamedSelectionWarning(state, isHydrated) {
|
|
|
75
75
|
state
|
|
76
76
|
]);
|
|
77
77
|
}
|
|
78
|
+
/** On `<html>` for the frames in which the colours change. */
|
|
79
|
+
const THEME_CHANGING = "data-wm-theme-changing";
|
|
78
80
|
/**
|
|
79
81
|
* Provides the colour theme, mode, contrast level, motion scheme and density to the app,
|
|
80
82
|
* mirrors them as `data-*` attributes on `<html>` and remembers the selection.
|
|
@@ -105,10 +107,25 @@ function ThemeProvider({ children, themes: themeList, theme: themeProp, defaultT
|
|
|
105
107
|
]);
|
|
106
108
|
useStreamedSelectionWarning(state, isHydrated);
|
|
107
109
|
const documentTheme = applyToDocument ? state : null;
|
|
110
|
+
const appliedColors = useRef(null);
|
|
108
111
|
useLayoutEffect(() => {
|
|
109
112
|
if (!isHydrated || !applyToDocument) return;
|
|
110
113
|
const root = document.documentElement;
|
|
114
|
+
const colors = `${state.theme}/${state.mode}/${state.contrast}`;
|
|
115
|
+
const changing = appliedColors.current !== null && appliedColors.current !== colors;
|
|
116
|
+
appliedColors.current = colors;
|
|
117
|
+
if (changing) root.setAttribute(THEME_CHANGING, "");
|
|
111
118
|
for (const [name, value] of Object.entries(themeAttributes(state))) root.setAttribute(name, value);
|
|
119
|
+
if (!changing) return;
|
|
120
|
+
let second = 0;
|
|
121
|
+
const first = requestAnimationFrame(() => {
|
|
122
|
+
second = requestAnimationFrame(() => root.removeAttribute(THEME_CHANGING));
|
|
123
|
+
});
|
|
124
|
+
return () => {
|
|
125
|
+
cancelAnimationFrame(first);
|
|
126
|
+
cancelAnimationFrame(second);
|
|
127
|
+
root.removeAttribute(THEME_CHANGING);
|
|
128
|
+
};
|
|
112
129
|
}, [
|
|
113
130
|
isHydrated,
|
|
114
131
|
applyToDocument,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ThemeProvider.js","names":[],"sources":["../../src/theme/ThemeProvider.tsx"],"sourcesContent":["'use client';\n\nimport {\n useCallback,\n useEffect,\n useLayoutEffect,\n useMemo,\n useRef,\n useState,\n useSyncExternalStore,\n type ReactNode,\n} from 'react';\nimport {\n BUILT_IN_THEME_NAMES,\n type BuiltInThemeName,\n type ColorMode,\n type ContrastLevel,\n} from '../tokens/color';\nimport type { Density } from '../tokens/density';\nimport type { MotionScheme } from '../tokens/motion';\nimport {\n DocumentThemeContext,\n InheritedThemeContext,\n ThemeContext,\n ThemeScopeContext,\n type ThemeProviderValue,\n} from './context';\nimport type { ThemeDefinition } from './create-theme';\nimport {\n DEFAULT_STORAGE_KEY,\n DEFAULT_THEME_STATE,\n parseThemeState,\n serializeThemeState,\n themeAttributes,\n type ThemeState,\n type ThemeStorage,\n} from './state';\nimport { readStoredTheme, writeStoredTheme } from './storage';\n\nexport interface ThemeProviderProps {\n children?: ReactNode;\n /**\n * Themes available to `setTheme`: built-in names and/or `createTheme()` results.\n * Defaults to all six built-in themes.\n */\n themes?: ReadonlyArray<BuiltInThemeName | ThemeDefinition>;\n /** Controlled colour theme. */\n theme?: string;\n /** Initial colour theme when uncontrolled. @default \"baseline\" */\n defaultTheme?: string;\n /** Called with the new colour theme when it changes. */\n onThemeChange?: (theme: string) => void;\n /** Controlled colour mode. */\n mode?: ColorMode;\n /** Initial colour mode when uncontrolled. @default \"system\" */\n defaultMode?: ColorMode;\n /** Called with the new colour mode when it changes. */\n onModeChange?: (mode: ColorMode) => void;\n /** Controlled contrast level. */\n contrast?: ContrastLevel;\n /** Initial contrast level when uncontrolled. @default \"standard\" */\n defaultContrast?: ContrastLevel;\n /** Called with the new contrast level when it changes. */\n onContrastChange?: (contrast: ContrastLevel) => void;\n /** Controlled motion scheme. */\n motion?: MotionScheme;\n /** Initial motion scheme when uncontrolled. @default \"expressive\" */\n defaultMotion?: MotionScheme;\n /** Called with the new motion scheme when it changes. */\n onMotionChange?: (motion: MotionScheme) => void;\n /**\n * Controlled web density, `0` to `-3`: each step takes 4px off the height of fields, menus\n * and other dense-capable components. M3 asks for density to be the reader's choice, not a\n * default, so offer it as a setting; keep the control that changes it at 48px or more.\n */\n density?: Density;\n /** Initial web density when uncontrolled. @default 0 */\n defaultDensity?: Density;\n /** Called with the new density when it changes. */\n onDensityChange?: (density: Density) => void;\n /**\n * Where the selection is remembered. Use `\"cookie\"` (default) with\n * `getThemeFromCookies` or `ThemeScript` for flash-free server rendering. Nothing is written\n * until the selection changes from the one the page loaded with, so a visitor who never\n * picks a setting gets no cookie.\n *\n * The browser's saved selection is applied right after hydration. When it differs from\n * what the server rendered, React renders any Suspense boundary still streaming (a Next.js\n * `loading.tsx`) on the client, dropping its server HTML. A streaming server should read\n * the cookie with `getThemeFromCookies` and pass every value as a `default*` prop; a\n * development warning names the values when it doesn't.\n * @default \"cookie\"\n */\n storage?: ThemeStorage;\n /** Cookie or localStorage key. @default \"windmorph-theme\" */\n storageKey?: string;\n /**\n * Whether the theme attributes are written to `<html>`. Set to `false` when you\n * render them yourself or theme a subtree with `ThemeScope` instead.\n * @default true\n */\n applyToDocument?: boolean;\n}\n\nconst subscribeToNothing = () => () => {};\n\n/** Re-reads localStorage when another tab changes the theme. Cookies have no change event. */\nfunction subscribeToStorage(storage: ThemeStorage, key: string, onChange: () => void) {\n if (storage !== 'local-storage') return () => {};\n const listener = (event: StorageEvent) => {\n if (event.key === key) onChange();\n };\n window.addEventListener('storage', listener);\n return () => window.removeEventListener('storage', listener);\n}\n\n/**\n * One theme dimension: the controlled prop wins, then the user's choice in this\n * session, then the stored selection, then the default.\n */\nfunction useThemeValue<T extends string | number>(\n controlled: T | undefined,\n stored: T | undefined,\n defaultValue: T,\n onChange: ((value: T) => void) | undefined,\n) {\n const [selected, setSelected] = useState<T | undefined>(undefined);\n const value = controlled ?? selected ?? stored ?? defaultValue;\n const setValue = useCallback(\n (next: T) => {\n if (controlled === undefined) setSelected(next);\n if (next !== value) onChange?.(next);\n },\n [controlled, value, onChange],\n );\n return [value, setValue] as const;\n}\n\nconst THEME_STATE_KEYS = ['theme', 'mode', 'contrast', 'motion', 'density'] as const;\n\n/** Whether the page holds a Suspense boundary whose streamed HTML hasn't arrived or shown. */\nfunction hasPendingStreamedBoundary() {\n const walker = document.createTreeWalker(document.documentElement, NodeFilter.SHOW_COMMENT);\n for (let node = walker.nextNode(); node; node = walker.nextNode()) {\n // React's markers for a boundary still streaming (`$?`) or waiting to be revealed (`$~`).\n const { data } = node as Comment;\n if (data === '$?' || data === '$~') return true;\n }\n return false;\n}\n\n/**\n * Development only: warns when the saved selection changes the theme right after hydration\n * while streamed Suspense boundaries are still pending, since React then renders them on the\n * client instead of hydrating them (2026-11-07).\n */\nfunction useStreamedSelectionWarning(state: ThemeState, isHydrated: boolean) {\n // The state hydration rendered (the server's), or null on a client-only mount.\n const [hydratedWith] = useState(() => (isHydrated ? null : state));\n const pendingAtHydration = useRef(false);\n const checked = useRef(false);\n useLayoutEffect(() => {\n if (process.env.NODE_ENV === 'production' || !hydratedWith) return;\n pendingAtHydration.current = hasPendingStreamedBoundary();\n }, [hydratedWith]);\n useEffect(() => {\n if (process.env.NODE_ENV === 'production' || !isHydrated || !hydratedWith) return;\n if (checked.current) return;\n checked.current = true;\n const changed = THEME_STATE_KEYS.filter((key) => state[key] !== hydratedWith[key]);\n if (!pendingAtHydration.current || changed.length === 0) return;\n const describe = (values: ThemeState) =>\n changed.map((key) => `${key} \"${values[key]}\"`).join(', ');\n console.warn(\n `[@windmorph/react] ThemeProvider hydrated with the server's ${describe(hydratedWith)}, ` +\n `then applied the saved ${describe(state)}. A theme change right after hydration makes ` +\n 'React render the Suspense boundaries still streaming on the client, dropping their ' +\n 'server HTML. Give the server the saved selection: storage=\"cookie\" with ' +\n 'getThemeFromCookies from @windmorph/react/next, passing every value as a default* prop ' +\n '(defaultTheme, defaultMode, defaultContrast, defaultMotion, defaultDensity).',\n );\n }, [isHydrated, hydratedWith, state]);\n}\n\n/**\n * Provides the colour theme, mode, contrast level, motion scheme and density to the app,\n * mirrors them as `data-*` attributes on `<html>` and remembers the selection.\n */\nexport function ThemeProvider({\n children,\n themes: themeList,\n theme: themeProp,\n defaultTheme = DEFAULT_THEME_STATE.theme,\n onThemeChange,\n mode: modeProp,\n defaultMode = DEFAULT_THEME_STATE.mode,\n onModeChange,\n contrast: contrastProp,\n defaultContrast = DEFAULT_THEME_STATE.contrast,\n onContrastChange,\n motion: motionProp,\n defaultMotion = DEFAULT_THEME_STATE.motion,\n onMotionChange,\n density: densityProp,\n defaultDensity = DEFAULT_THEME_STATE.density,\n onDensityChange,\n storage = 'cookie',\n storageKey = DEFAULT_STORAGE_KEY,\n applyToDocument = true,\n}: ThemeProviderProps) {\n const definitions = useMemo(\n () => (themeList ?? []).filter((entry): entry is ThemeDefinition => typeof entry !== 'string'),\n [themeList],\n );\n const themeNames = useMemo(\n () =>\n themeList\n ? themeList.map((entry) => (typeof entry === 'string' ? entry : entry.name))\n : BUILT_IN_THEME_NAMES,\n [themeList],\n );\n\n // The stored selection is external data that only exists in the browser. Reading it\n // through useSyncExternalStore renders the server snapshot during hydration and then\n // the stored value, without a hydration mismatch. `isHydrated` flips in the same pass.\n // When the stored value differs from the server's, that second render changes the theme\n // after hydration (see `useStreamedSelectionWarning`), so a streaming server passes it in.\n const storedRaw = useSyncExternalStore(\n useCallback(\n (onChange: () => void) => subscribeToStorage(storage, storageKey, onChange),\n [storage, storageKey],\n ),\n () => readStoredTheme(storage, storageKey),\n () => null,\n );\n const isHydrated = useSyncExternalStore(\n subscribeToNothing,\n () => true,\n () => false,\n );\n const stored = useMemo(() => parseThemeState(storedRaw, themeNames), [storedRaw, themeNames]);\n\n const [theme, setThemeState] = useThemeValue(\n themeProp,\n stored.theme,\n defaultTheme,\n onThemeChange,\n );\n const [mode, setMode] = useThemeValue(modeProp, stored.mode, defaultMode, onModeChange);\n const [contrast, setContrast] = useThemeValue(\n contrastProp,\n stored.contrast,\n defaultContrast,\n onContrastChange,\n );\n const [motion, setMotion] = useThemeValue(\n motionProp,\n stored.motion,\n defaultMotion,\n onMotionChange,\n );\n\n const [density, setDensity] = useThemeValue(\n densityProp,\n stored.density,\n defaultDensity,\n onDensityChange,\n );\n\n const state = useMemo<ThemeState>(\n () => ({ theme, mode, contrast, motion, density }),\n [theme, mode, contrast, motion, density],\n );\n\n useStreamedSelectionWarning(state, isHydrated);\n\n // What `<html>` carries, for scopes and overlays to put only what differs on their elements.\n const documentTheme = applyToDocument ? state : null;\n\n useLayoutEffect(() => {\n if (!isHydrated || !applyToDocument) return;\n const root = document.documentElement;\n for (const [name, value] of Object.entries(themeAttributes(state))) {\n root.setAttribute(name, value);\n }\n }, [isHydrated, applyToDocument, state]);\n\n // Stored only once the selection changes from the one the page loaded with (the stored one,\n // or the defaults): a preference is remembered when someone sets it, and a visit that sets\n // nothing leaves no cookie behind.\n const loaded = useRef<string | null>(null);\n const changed = useRef(false);\n useEffect(() => {\n if (!isHydrated || storage === 'none') return;\n const serialized = serializeThemeState(state);\n loaded.current ??= serialized;\n if (!changed.current && serialized === loaded.current) return;\n changed.current = true;\n writeStoredTheme(storage, storageKey, state);\n }, [isHydrated, storage, storageKey, state]);\n\n const setTheme = useCallback(\n (next: string) => {\n if (!themeNames.includes(next)) {\n if (process.env.NODE_ENV !== 'production') {\n console.warn(\n `[@windmorph/react] setTheme(\"${next}\") ignored: not one of ${themeNames.join(', ')}.`,\n );\n }\n return;\n }\n setThemeState(next);\n },\n [themeNames, setThemeState],\n );\n\n // `mode=\"system\"` is resolved to the device's mode in `useTheme`, not here: the device's mode\n // is known only after hydration, and a context value that changes then reaches the Suspense\n // boundaries still waiting for their streamed HTML, which React then renders on the client\n // instead, showing the page twice for a moment (2026-11-07).\n const value = useMemo<ThemeProviderValue>(\n () => ({\n ...state,\n themes: themeNames,\n setTheme,\n setMode,\n setContrast,\n setMotion,\n setDensity,\n }),\n [state, themeNames, setTheme, setMode, setContrast, setMotion, setDensity],\n );\n\n return (\n <ThemeContext value={value}>\n <ThemeScopeContext value={state}>\n <DocumentThemeContext value={documentTheme}>\n <InheritedThemeContext value={documentTheme}>\n {definitions.map((definition) => (\n <style\n key={definition.name}\n href={`windmorph-theme-${definition.name}`}\n precedence=\"windmorph\"\n >\n {definition.css}\n </style>\n ))}\n {children}\n </InheritedThemeContext>\n </DocumentThemeContext>\n </ThemeScopeContext>\n </ThemeContext>\n );\n}\n"],"mappings":";;;;;;;;AAwGA,MAAM,iCAAiC,CAAC;;AAGxC,SAAS,mBAAmB,SAAuB,KAAa,UAAsB;CACpF,IAAI,YAAY,iBAAiB,aAAa,CAAC;CAC/C,MAAM,YAAY,UAAwB;EACxC,IAAI,MAAM,QAAQ,KAAK,SAAS;CAClC;CACA,OAAO,iBAAiB,WAAW,QAAQ;CAC3C,aAAa,OAAO,oBAAoB,WAAW,QAAQ;AAC7D;;;;;AAMA,SAAS,cACP,YACA,QACA,cACA,UACA;CACA,MAAM,CAAC,UAAU,eAAe,SAAwB,KAAA,CAAS;CACjE,MAAM,QAAQ,cAAc,YAAY,UAAU;CAQlD,OAAO,CAAC,OAPS,aACd,SAAY;EACX,IAAI,eAAe,KAAA,GAAW,YAAY,IAAI;EAC9C,IAAI,SAAS,OAAO,WAAW,IAAI;CACrC,GACA;EAAC;EAAY;EAAO;CAAQ,CAER,CAAC;AACzB;AAEA,MAAM,mBAAmB;CAAC;CAAS;CAAQ;CAAY;CAAU;AAAS;;AAG1E,SAAS,6BAA6B;CACpC,MAAM,SAAS,SAAS,iBAAiB,SAAS,iBAAiB,WAAW,YAAY;CAC1F,KAAK,IAAI,OAAO,OAAO,SAAS,GAAG,MAAM,OAAO,OAAO,SAAS,GAAG;EAEjE,MAAM,EAAE,SAAS;EACjB,IAAI,SAAS,QAAQ,SAAS,MAAM,OAAO;CAC7C;CACA,OAAO;AACT;;;;;;AAOA,SAAS,4BAA4B,OAAmB,YAAqB;CAE3E,MAAM,CAAC,gBAAgB,eAAgB,aAAa,OAAO,KAAM;CACjE,MAAM,qBAAqB,OAAO,KAAK;CACvC,MAAM,UAAU,OAAO,KAAK;CAC5B,sBAAsB;EACpB,IAAI,QAAQ,IAAI,aAAa,gBAAgB,CAAC,cAAc;EAC5D,mBAAmB,UAAU,2BAA2B;CAC1D,GAAG,CAAC,YAAY,CAAC;CACjB,gBAAgB;EACd,IAAI,QAAQ,IAAI,aAAa,gBAAgB,CAAC,cAAc,CAAC,cAAc;EAC3E,IAAI,QAAQ,SAAS;EACrB,QAAQ,UAAU;EAClB,MAAM,UAAU,iBAAiB,QAAQ,QAAQ,MAAM,SAAS,aAAa,IAAI;EACjF,IAAI,CAAC,mBAAmB,WAAW,QAAQ,WAAW,GAAG;EACzD,MAAM,YAAY,WAChB,QAAQ,KAAK,QAAQ,GAAG,IAAI,IAAI,OAAO,KAAK,EAAE,CAAC,CAAC,KAAK,IAAI;EAC3D,QAAQ,KACN,+DAA+D,SAAS,YAAY,EAAE,2BAC1D,SAAS,KAAK,EAAE,4WAK9C;CACF,GAAG;EAAC;EAAY;EAAc;CAAK,CAAC;AACtC;;;;;AAMA,SAAgB,cAAc,EAC5B,UACA,QAAQ,WACR,OAAO,WACP,eAAe,oBAAoB,OACnC,eACA,MAAM,UACN,cAAc,oBAAoB,MAClC,cACA,UAAU,cACV,kBAAkB,oBAAoB,UACtC,kBACA,QAAQ,YACR,gBAAgB,oBAAoB,QACpC,gBACA,SAAS,aACT,iBAAiB,oBAAoB,SACrC,iBACA,UAAU,UACV,aAAa,qBACb,kBAAkB,QACG;CACrB,MAAM,cAAc,eACX,aAAa,CAAC,EAAA,CAAG,QAAQ,UAAoC,OAAO,UAAU,QAAQ,GAC7F,CAAC,SAAS,CACZ;CACA,MAAM,aAAa,cAEf,YACI,UAAU,KAAK,UAAW,OAAO,UAAU,WAAW,QAAQ,MAAM,IAAK,IACzE,sBACN,CAAC,SAAS,CACZ;CAOA,MAAM,YAAY,qBAChB,aACG,aAAyB,mBAAmB,SAAS,YAAY,QAAQ,GAC1E,CAAC,SAAS,UAAU,CACtB,SACM,gBAAgB,SAAS,UAAU,SACnC,IACR;CACA,MAAM,aAAa,qBACjB,0BACM,YACA,KACR;CACA,MAAM,SAAS,cAAc,gBAAgB,WAAW,UAAU,GAAG,CAAC,WAAW,UAAU,CAAC;CAE5F,MAAM,CAAC,OAAO,iBAAiB,cAC7B,WACA,OAAO,OACP,cACA,aACF;CACA,MAAM,CAAC,MAAM,WAAW,cAAc,UAAU,OAAO,MAAM,aAAa,YAAY;CACtF,MAAM,CAAC,UAAU,eAAe,cAC9B,cACA,OAAO,UACP,iBACA,gBACF;CACA,MAAM,CAAC,QAAQ,aAAa,cAC1B,YACA,OAAO,QACP,eACA,cACF;CAEA,MAAM,CAAC,SAAS,cAAc,cAC5B,aACA,OAAO,SACP,gBACA,eACF;CAEA,MAAM,QAAQ,eACL;EAAE;EAAO;EAAM;EAAU;EAAQ;CAAQ,IAChD;EAAC;EAAO;EAAM;EAAU;EAAQ;CAAO,CACzC;CAEA,4BAA4B,OAAO,UAAU;CAG7C,MAAM,gBAAgB,kBAAkB,QAAQ;CAEhD,sBAAsB;EACpB,IAAI,CAAC,cAAc,CAAC,iBAAiB;EACrC,MAAM,OAAO,SAAS;EACtB,KAAK,MAAM,CAAC,MAAM,UAAU,OAAO,QAAQ,gBAAgB,KAAK,CAAC,GAC/D,KAAK,aAAa,MAAM,KAAK;CAEjC,GAAG;EAAC;EAAY;EAAiB;CAAK,CAAC;CAKvC,MAAM,SAAS,OAAsB,IAAI;CACzC,MAAM,UAAU,OAAO,KAAK;CAC5B,gBAAgB;EACd,IAAI,CAAC,cAAc,YAAY,QAAQ;EACvC,MAAM,aAAa,oBAAoB,KAAK;EAC5C,OAAO,YAAY;EACnB,IAAI,CAAC,QAAQ,WAAW,eAAe,OAAO,SAAS;EACvD,QAAQ,UAAU;EAClB,iBAAiB,SAAS,YAAY,KAAK;CAC7C,GAAG;EAAC;EAAY;EAAS;EAAY;CAAK,CAAC;CAE3C,MAAM,WAAW,aACd,SAAiB;EAChB,IAAI,CAAC,WAAW,SAAS,IAAI,GAAG;GAC9B,IAAI,QAAQ,IAAI,aAAa,cAC3B,QAAQ,KACN,gCAAgC,KAAK,yBAAyB,WAAW,KAAK,IAAI,EAAE,EACtF;GAEF;EACF;EACA,cAAc,IAAI;CACpB,GACA,CAAC,YAAY,aAAa,CAC5B;CAMA,MAAM,QAAQ,eACL;EACL,GAAG;EACH,QAAQ;EACR;EACA;EACA;EACA;EACA;CACF,IACA;EAAC;EAAO;EAAY;EAAU;EAAS;EAAa;EAAW;CAAU,CAC3E;CAEA,OACE,oBAAC,cAAD;EAAqB;EACnB,UAAA,oBAAC,mBAAD;GAAmB,OAAO;GACxB,UAAA,oBAAC,sBAAD;IAAsB,OAAO;IAC3B,UAAA,qBAAC,uBAAD;KAAuB,OAAO;KAA9B,UAAA,CACG,YAAY,KAAK,eAChB,oBAAC,SAAD;MAEE,MAAM,mBAAmB,WAAW;MACpC,YAAW;MAEV,UAAA,WAAW;KACP,GALA,WAAW,IAKX,CACR,GACA,QACoB;;GACH,CAAA;EACL,CAAA;CACP,CAAA;AAElB"}
|
|
1
|
+
{"version":3,"file":"ThemeProvider.js","names":[],"sources":["../../src/theme/ThemeProvider.tsx"],"sourcesContent":["'use client';\n\nimport {\n useCallback,\n useEffect,\n useLayoutEffect,\n useMemo,\n useRef,\n useState,\n useSyncExternalStore,\n type ReactNode,\n} from 'react';\nimport {\n BUILT_IN_THEME_NAMES,\n type BuiltInThemeName,\n type ColorMode,\n type ContrastLevel,\n} from '../tokens/color';\nimport type { Density } from '../tokens/density';\nimport type { MotionScheme } from '../tokens/motion';\nimport {\n DocumentThemeContext,\n InheritedThemeContext,\n ThemeContext,\n ThemeScopeContext,\n type ThemeProviderValue,\n} from './context';\nimport type { ThemeDefinition } from './create-theme';\nimport {\n DEFAULT_STORAGE_KEY,\n DEFAULT_THEME_STATE,\n parseThemeState,\n serializeThemeState,\n themeAttributes,\n type ThemeState,\n type ThemeStorage,\n} from './state';\nimport { readStoredTheme, writeStoredTheme } from './storage';\n\nexport interface ThemeProviderProps {\n children?: ReactNode;\n /**\n * Themes available to `setTheme`: built-in names and/or `createTheme()` results.\n * Defaults to all six built-in themes.\n */\n themes?: ReadonlyArray<BuiltInThemeName | ThemeDefinition>;\n /** Controlled colour theme. */\n theme?: string;\n /** Initial colour theme when uncontrolled. @default \"baseline\" */\n defaultTheme?: string;\n /** Called with the new colour theme when it changes. */\n onThemeChange?: (theme: string) => void;\n /** Controlled colour mode. */\n mode?: ColorMode;\n /** Initial colour mode when uncontrolled. @default \"system\" */\n defaultMode?: ColorMode;\n /** Called with the new colour mode when it changes. */\n onModeChange?: (mode: ColorMode) => void;\n /** Controlled contrast level. */\n contrast?: ContrastLevel;\n /** Initial contrast level when uncontrolled. @default \"standard\" */\n defaultContrast?: ContrastLevel;\n /** Called with the new contrast level when it changes. */\n onContrastChange?: (contrast: ContrastLevel) => void;\n /** Controlled motion scheme. */\n motion?: MotionScheme;\n /** Initial motion scheme when uncontrolled. @default \"expressive\" */\n defaultMotion?: MotionScheme;\n /** Called with the new motion scheme when it changes. */\n onMotionChange?: (motion: MotionScheme) => void;\n /**\n * Controlled web density, `0` to `-3`: each step takes 4px off the height of fields, menus\n * and other dense-capable components. M3 asks for density to be the reader's choice, not a\n * default, so offer it as a setting; keep the control that changes it at 48px or more.\n */\n density?: Density;\n /** Initial web density when uncontrolled. @default 0 */\n defaultDensity?: Density;\n /** Called with the new density when it changes. */\n onDensityChange?: (density: Density) => void;\n /**\n * Where the selection is remembered. Use `\"cookie\"` (default) with\n * `getThemeFromCookies` or `ThemeScript` for flash-free server rendering. Nothing is written\n * until the selection changes from the one the page loaded with, so a visitor who never\n * picks a setting gets no cookie.\n *\n * The browser's saved selection is applied right after hydration. When it differs from\n * what the server rendered, React renders any Suspense boundary still streaming (a Next.js\n * `loading.tsx`) on the client, dropping its server HTML. A streaming server should read\n * the cookie with `getThemeFromCookies` and pass every value as a `default*` prop; a\n * development warning names the values when it doesn't.\n * @default \"cookie\"\n */\n storage?: ThemeStorage;\n /** Cookie or localStorage key. @default \"windmorph-theme\" */\n storageKey?: string;\n /**\n * Whether the theme attributes are written to `<html>`. Set to `false` when you\n * render them yourself or theme a subtree with `ThemeScope` instead.\n * @default true\n */\n applyToDocument?: boolean;\n}\n\nconst subscribeToNothing = () => () => {};\n\n/** Re-reads localStorage when another tab changes the theme. Cookies have no change event. */\nfunction subscribeToStorage(storage: ThemeStorage, key: string, onChange: () => void) {\n if (storage !== 'local-storage') return () => {};\n const listener = (event: StorageEvent) => {\n if (event.key === key) onChange();\n };\n window.addEventListener('storage', listener);\n return () => window.removeEventListener('storage', listener);\n}\n\n/**\n * One theme dimension: the controlled prop wins, then the user's choice in this\n * session, then the stored selection, then the default.\n */\nfunction useThemeValue<T extends string | number>(\n controlled: T | undefined,\n stored: T | undefined,\n defaultValue: T,\n onChange: ((value: T) => void) | undefined,\n) {\n const [selected, setSelected] = useState<T | undefined>(undefined);\n const value = controlled ?? selected ?? stored ?? defaultValue;\n const setValue = useCallback(\n (next: T) => {\n if (controlled === undefined) setSelected(next);\n if (next !== value) onChange?.(next);\n },\n [controlled, value, onChange],\n );\n return [value, setValue] as const;\n}\n\nconst THEME_STATE_KEYS = ['theme', 'mode', 'contrast', 'motion', 'density'] as const;\n\n/** Whether the page holds a Suspense boundary whose streamed HTML hasn't arrived or shown. */\nfunction hasPendingStreamedBoundary() {\n const walker = document.createTreeWalker(document.documentElement, NodeFilter.SHOW_COMMENT);\n for (let node = walker.nextNode(); node; node = walker.nextNode()) {\n // React's markers for a boundary still streaming (`$?`) or waiting to be revealed (`$~`).\n const { data } = node as Comment;\n if (data === '$?' || data === '$~') return true;\n }\n return false;\n}\n\n/**\n * Development only: warns when the saved selection changes the theme right after hydration\n * while streamed Suspense boundaries are still pending, since React then renders them on the\n * client instead of hydrating them (2026-11-07).\n */\nfunction useStreamedSelectionWarning(state: ThemeState, isHydrated: boolean) {\n // The state hydration rendered (the server's), or null on a client-only mount.\n const [hydratedWith] = useState(() => (isHydrated ? null : state));\n const pendingAtHydration = useRef(false);\n const checked = useRef(false);\n useLayoutEffect(() => {\n if (process.env.NODE_ENV === 'production' || !hydratedWith) return;\n pendingAtHydration.current = hasPendingStreamedBoundary();\n }, [hydratedWith]);\n useEffect(() => {\n if (process.env.NODE_ENV === 'production' || !isHydrated || !hydratedWith) return;\n if (checked.current) return;\n checked.current = true;\n const changed = THEME_STATE_KEYS.filter((key) => state[key] !== hydratedWith[key]);\n if (!pendingAtHydration.current || changed.length === 0) return;\n const describe = (values: ThemeState) =>\n changed.map((key) => `${key} \"${values[key]}\"`).join(', ');\n console.warn(\n `[@windmorph/react] ThemeProvider hydrated with the server's ${describe(hydratedWith)}, ` +\n `then applied the saved ${describe(state)}. A theme change right after hydration makes ` +\n 'React render the Suspense boundaries still streaming on the client, dropping their ' +\n 'server HTML. Give the server the saved selection: storage=\"cookie\" with ' +\n 'getThemeFromCookies from @windmorph/react/next, passing every value as a default* prop ' +\n '(defaultTheme, defaultMode, defaultContrast, defaultMotion, defaultDensity).',\n );\n }, [isHydrated, hydratedWith, state]);\n}\n\n/** On `<html>` for the frames in which the colours change. */\nconst THEME_CHANGING = 'data-wm-theme-changing';\n\n/**\n * Provides the colour theme, mode, contrast level, motion scheme and density to the app,\n * mirrors them as `data-*` attributes on `<html>` and remembers the selection.\n */\nexport function ThemeProvider({\n children,\n themes: themeList,\n theme: themeProp,\n defaultTheme = DEFAULT_THEME_STATE.theme,\n onThemeChange,\n mode: modeProp,\n defaultMode = DEFAULT_THEME_STATE.mode,\n onModeChange,\n contrast: contrastProp,\n defaultContrast = DEFAULT_THEME_STATE.contrast,\n onContrastChange,\n motion: motionProp,\n defaultMotion = DEFAULT_THEME_STATE.motion,\n onMotionChange,\n density: densityProp,\n defaultDensity = DEFAULT_THEME_STATE.density,\n onDensityChange,\n storage = 'cookie',\n storageKey = DEFAULT_STORAGE_KEY,\n applyToDocument = true,\n}: ThemeProviderProps) {\n const definitions = useMemo(\n () => (themeList ?? []).filter((entry): entry is ThemeDefinition => typeof entry !== 'string'),\n [themeList],\n );\n const themeNames = useMemo(\n () =>\n themeList\n ? themeList.map((entry) => (typeof entry === 'string' ? entry : entry.name))\n : BUILT_IN_THEME_NAMES,\n [themeList],\n );\n\n // The stored selection is external data that only exists in the browser. Reading it\n // through useSyncExternalStore renders the server snapshot during hydration and then\n // the stored value, without a hydration mismatch. `isHydrated` flips in the same pass.\n // When the stored value differs from the server's, that second render changes the theme\n // after hydration (see `useStreamedSelectionWarning`), so a streaming server passes it in.\n const storedRaw = useSyncExternalStore(\n useCallback(\n (onChange: () => void) => subscribeToStorage(storage, storageKey, onChange),\n [storage, storageKey],\n ),\n () => readStoredTheme(storage, storageKey),\n () => null,\n );\n const isHydrated = useSyncExternalStore(\n subscribeToNothing,\n () => true,\n () => false,\n );\n const stored = useMemo(() => parseThemeState(storedRaw, themeNames), [storedRaw, themeNames]);\n\n const [theme, setThemeState] = useThemeValue(\n themeProp,\n stored.theme,\n defaultTheme,\n onThemeChange,\n );\n const [mode, setMode] = useThemeValue(modeProp, stored.mode, defaultMode, onModeChange);\n const [contrast, setContrast] = useThemeValue(\n contrastProp,\n stored.contrast,\n defaultContrast,\n onContrastChange,\n );\n const [motion, setMotion] = useThemeValue(\n motionProp,\n stored.motion,\n defaultMotion,\n onMotionChange,\n );\n\n const [density, setDensity] = useThemeValue(\n densityProp,\n stored.density,\n defaultDensity,\n onDensityChange,\n );\n\n const state = useMemo<ThemeState>(\n () => ({ theme, mode, contrast, motion, density }),\n [theme, mode, contrast, motion, density],\n );\n\n useStreamedSelectionWarning(state, isHydrated);\n\n // What `<html>` carries, for scopes and overlays to put only what differs on their elements.\n const documentTheme = applyToDocument ? state : null;\n\n // A control's colour fade is for its hover and press. Off while the colours change, or each\n // control would lag behind the page (a rule in utilities.css reads this attribute).\n const appliedColors = useRef<string | null>(null);\n useLayoutEffect(() => {\n if (!isHydrated || !applyToDocument) return;\n const root = document.documentElement;\n const colors = `${state.theme}/${state.mode}/${state.contrast}`;\n const changing = appliedColors.current !== null && appliedColors.current !== colors;\n appliedColors.current = colors;\n if (changing) root.setAttribute(THEME_CHANGING, '');\n for (const [name, value] of Object.entries(themeAttributes(state))) {\n root.setAttribute(name, value);\n }\n if (!changing) return;\n let second = 0;\n const first = requestAnimationFrame(() => {\n second = requestAnimationFrame(() => root.removeAttribute(THEME_CHANGING));\n });\n return () => {\n cancelAnimationFrame(first);\n cancelAnimationFrame(second);\n root.removeAttribute(THEME_CHANGING);\n };\n }, [isHydrated, applyToDocument, state]);\n\n // Stored only once the selection changes from the one the page loaded with (the stored one,\n // or the defaults): a preference is remembered when someone sets it, and a visit that sets\n // nothing leaves no cookie behind.\n const loaded = useRef<string | null>(null);\n const changed = useRef(false);\n useEffect(() => {\n if (!isHydrated || storage === 'none') return;\n const serialized = serializeThemeState(state);\n loaded.current ??= serialized;\n if (!changed.current && serialized === loaded.current) return;\n changed.current = true;\n writeStoredTheme(storage, storageKey, state);\n }, [isHydrated, storage, storageKey, state]);\n\n const setTheme = useCallback(\n (next: string) => {\n if (!themeNames.includes(next)) {\n if (process.env.NODE_ENV !== 'production') {\n console.warn(\n `[@windmorph/react] setTheme(\"${next}\") ignored: not one of ${themeNames.join(', ')}.`,\n );\n }\n return;\n }\n setThemeState(next);\n },\n [themeNames, setThemeState],\n );\n\n // `mode=\"system\"` is resolved to the device's mode in `useTheme`, not here: the device's mode\n // is known only after hydration, and a context value that changes then reaches the Suspense\n // boundaries still waiting for their streamed HTML, which React then renders on the client\n // instead, showing the page twice for a moment (2026-11-07).\n const value = useMemo<ThemeProviderValue>(\n () => ({\n ...state,\n themes: themeNames,\n setTheme,\n setMode,\n setContrast,\n setMotion,\n setDensity,\n }),\n [state, themeNames, setTheme, setMode, setContrast, setMotion, setDensity],\n );\n\n return (\n <ThemeContext value={value}>\n <ThemeScopeContext value={state}>\n <DocumentThemeContext value={documentTheme}>\n <InheritedThemeContext value={documentTheme}>\n {definitions.map((definition) => (\n <style\n key={definition.name}\n href={`windmorph-theme-${definition.name}`}\n precedence=\"windmorph\"\n >\n {definition.css}\n </style>\n ))}\n {children}\n </InheritedThemeContext>\n </DocumentThemeContext>\n </ThemeScopeContext>\n </ThemeContext>\n );\n}\n"],"mappings":";;;;;;;;AAwGA,MAAM,iCAAiC,CAAC;;AAGxC,SAAS,mBAAmB,SAAuB,KAAa,UAAsB;CACpF,IAAI,YAAY,iBAAiB,aAAa,CAAC;CAC/C,MAAM,YAAY,UAAwB;EACxC,IAAI,MAAM,QAAQ,KAAK,SAAS;CAClC;CACA,OAAO,iBAAiB,WAAW,QAAQ;CAC3C,aAAa,OAAO,oBAAoB,WAAW,QAAQ;AAC7D;;;;;AAMA,SAAS,cACP,YACA,QACA,cACA,UACA;CACA,MAAM,CAAC,UAAU,eAAe,SAAwB,KAAA,CAAS;CACjE,MAAM,QAAQ,cAAc,YAAY,UAAU;CAQlD,OAAO,CAAC,OAPS,aACd,SAAY;EACX,IAAI,eAAe,KAAA,GAAW,YAAY,IAAI;EAC9C,IAAI,SAAS,OAAO,WAAW,IAAI;CACrC,GACA;EAAC;EAAY;EAAO;CAAQ,CAER,CAAC;AACzB;AAEA,MAAM,mBAAmB;CAAC;CAAS;CAAQ;CAAY;CAAU;AAAS;;AAG1E,SAAS,6BAA6B;CACpC,MAAM,SAAS,SAAS,iBAAiB,SAAS,iBAAiB,WAAW,YAAY;CAC1F,KAAK,IAAI,OAAO,OAAO,SAAS,GAAG,MAAM,OAAO,OAAO,SAAS,GAAG;EAEjE,MAAM,EAAE,SAAS;EACjB,IAAI,SAAS,QAAQ,SAAS,MAAM,OAAO;CAC7C;CACA,OAAO;AACT;;;;;;AAOA,SAAS,4BAA4B,OAAmB,YAAqB;CAE3E,MAAM,CAAC,gBAAgB,eAAgB,aAAa,OAAO,KAAM;CACjE,MAAM,qBAAqB,OAAO,KAAK;CACvC,MAAM,UAAU,OAAO,KAAK;CAC5B,sBAAsB;EACpB,IAAI,QAAQ,IAAI,aAAa,gBAAgB,CAAC,cAAc;EAC5D,mBAAmB,UAAU,2BAA2B;CAC1D,GAAG,CAAC,YAAY,CAAC;CACjB,gBAAgB;EACd,IAAI,QAAQ,IAAI,aAAa,gBAAgB,CAAC,cAAc,CAAC,cAAc;EAC3E,IAAI,QAAQ,SAAS;EACrB,QAAQ,UAAU;EAClB,MAAM,UAAU,iBAAiB,QAAQ,QAAQ,MAAM,SAAS,aAAa,IAAI;EACjF,IAAI,CAAC,mBAAmB,WAAW,QAAQ,WAAW,GAAG;EACzD,MAAM,YAAY,WAChB,QAAQ,KAAK,QAAQ,GAAG,IAAI,IAAI,OAAO,KAAK,EAAE,CAAC,CAAC,KAAK,IAAI;EAC3D,QAAQ,KACN,+DAA+D,SAAS,YAAY,EAAE,2BAC1D,SAAS,KAAK,EAAE,4WAK9C;CACF,GAAG;EAAC;EAAY;EAAc;CAAK,CAAC;AACtC;;AAGA,MAAM,iBAAiB;;;;;AAMvB,SAAgB,cAAc,EAC5B,UACA,QAAQ,WACR,OAAO,WACP,eAAe,oBAAoB,OACnC,eACA,MAAM,UACN,cAAc,oBAAoB,MAClC,cACA,UAAU,cACV,kBAAkB,oBAAoB,UACtC,kBACA,QAAQ,YACR,gBAAgB,oBAAoB,QACpC,gBACA,SAAS,aACT,iBAAiB,oBAAoB,SACrC,iBACA,UAAU,UACV,aAAa,qBACb,kBAAkB,QACG;CACrB,MAAM,cAAc,eACX,aAAa,CAAC,EAAA,CAAG,QAAQ,UAAoC,OAAO,UAAU,QAAQ,GAC7F,CAAC,SAAS,CACZ;CACA,MAAM,aAAa,cAEf,YACI,UAAU,KAAK,UAAW,OAAO,UAAU,WAAW,QAAQ,MAAM,IAAK,IACzE,sBACN,CAAC,SAAS,CACZ;CAOA,MAAM,YAAY,qBAChB,aACG,aAAyB,mBAAmB,SAAS,YAAY,QAAQ,GAC1E,CAAC,SAAS,UAAU,CACtB,SACM,gBAAgB,SAAS,UAAU,SACnC,IACR;CACA,MAAM,aAAa,qBACjB,0BACM,YACA,KACR;CACA,MAAM,SAAS,cAAc,gBAAgB,WAAW,UAAU,GAAG,CAAC,WAAW,UAAU,CAAC;CAE5F,MAAM,CAAC,OAAO,iBAAiB,cAC7B,WACA,OAAO,OACP,cACA,aACF;CACA,MAAM,CAAC,MAAM,WAAW,cAAc,UAAU,OAAO,MAAM,aAAa,YAAY;CACtF,MAAM,CAAC,UAAU,eAAe,cAC9B,cACA,OAAO,UACP,iBACA,gBACF;CACA,MAAM,CAAC,QAAQ,aAAa,cAC1B,YACA,OAAO,QACP,eACA,cACF;CAEA,MAAM,CAAC,SAAS,cAAc,cAC5B,aACA,OAAO,SACP,gBACA,eACF;CAEA,MAAM,QAAQ,eACL;EAAE;EAAO;EAAM;EAAU;EAAQ;CAAQ,IAChD;EAAC;EAAO;EAAM;EAAU;EAAQ;CAAO,CACzC;CAEA,4BAA4B,OAAO,UAAU;CAG7C,MAAM,gBAAgB,kBAAkB,QAAQ;CAIhD,MAAM,gBAAgB,OAAsB,IAAI;CAChD,sBAAsB;EACpB,IAAI,CAAC,cAAc,CAAC,iBAAiB;EACrC,MAAM,OAAO,SAAS;EACtB,MAAM,SAAS,GAAG,MAAM,MAAM,GAAG,MAAM,KAAK,GAAG,MAAM;EACrD,MAAM,WAAW,cAAc,YAAY,QAAQ,cAAc,YAAY;EAC7E,cAAc,UAAU;EACxB,IAAI,UAAU,KAAK,aAAa,gBAAgB,EAAE;EAClD,KAAK,MAAM,CAAC,MAAM,UAAU,OAAO,QAAQ,gBAAgB,KAAK,CAAC,GAC/D,KAAK,aAAa,MAAM,KAAK;EAE/B,IAAI,CAAC,UAAU;EACf,IAAI,SAAS;EACb,MAAM,QAAQ,4BAA4B;GACxC,SAAS,4BAA4B,KAAK,gBAAgB,cAAc,CAAC;EAC3E,CAAC;EACD,aAAa;GACX,qBAAqB,KAAK;GAC1B,qBAAqB,MAAM;GAC3B,KAAK,gBAAgB,cAAc;EACrC;CACF,GAAG;EAAC;EAAY;EAAiB;CAAK,CAAC;CAKvC,MAAM,SAAS,OAAsB,IAAI;CACzC,MAAM,UAAU,OAAO,KAAK;CAC5B,gBAAgB;EACd,IAAI,CAAC,cAAc,YAAY,QAAQ;EACvC,MAAM,aAAa,oBAAoB,KAAK;EAC5C,OAAO,YAAY;EACnB,IAAI,CAAC,QAAQ,WAAW,eAAe,OAAO,SAAS;EACvD,QAAQ,UAAU;EAClB,iBAAiB,SAAS,YAAY,KAAK;CAC7C,GAAG;EAAC;EAAY;EAAS;EAAY;CAAK,CAAC;CAE3C,MAAM,WAAW,aACd,SAAiB;EAChB,IAAI,CAAC,WAAW,SAAS,IAAI,GAAG;GAC9B,IAAI,QAAQ,IAAI,aAAa,cAC3B,QAAQ,KACN,gCAAgC,KAAK,yBAAyB,WAAW,KAAK,IAAI,EAAE,EACtF;GAEF;EACF;EACA,cAAc,IAAI;CACpB,GACA,CAAC,YAAY,aAAa,CAC5B;CAMA,MAAM,QAAQ,eACL;EACL,GAAG;EACH,QAAQ;EACR;EACA;EACA;EACA;EACA;CACF,IACA;EAAC;EAAO;EAAY;EAAU;EAAS;EAAa;EAAW;CAAU,CAC3E;CAEA,OACE,oBAAC,cAAD;EAAqB;EACnB,UAAA,oBAAC,mBAAD;GAAmB,OAAO;GACxB,UAAA,oBAAC,sBAAD;IAAsB,OAAO;IAC3B,UAAA,qBAAC,uBAAD;KAAuB,OAAO;KAA9B,UAAA,CACG,YAAY,KAAK,eAChB,oBAAC,SAAD;MAEE,MAAM,mBAAmB,WAAW;MACpC,YAAW;MAEV,UAAA,WAAW;KACP,GALA,WAAW,IAKX,CACR,GACA,QACoB;;GACH,CAAA;EACL,CAAA;CACP,CAAA;AAElB"}
|