@weasel-js/theme 1.8.1 → 1.9.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/dist/react.js.map +1 -1
- package/package.json +2 -2
package/dist/react.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/useColorMode.ts","../src/react.tsx"],"names":[],"mappings":"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","file":"react.js","sourcesContent":["import { useCallback, useEffect, useState } from 'react';\nimport { isColorModePreference, type ColorMode, type ColorModePreference } from './colorMode';\n\nfunction systemQuery(): MediaQueryList | null {\n if (typeof globalThis.matchMedia !== 'function') return null;\n return globalThis.matchMedia('(prefers-color-scheme: light)');\n}\n\nconst systemMode = (): ColorMode => (systemQuery()?.matches ? 'light' : 'dark');\n\n/**\n * Resolves a preference to the mode a `ThemeProvider` selection takes.\n * `'auto'` subscribes to the OS setting and follows it live; an explicit\n * choice opts out of the subscription entirely.\n */\nexport function useResolvedColorMode(preference: ColorModePreference): ColorMode {\n const [system, setSystem] = useState<ColorMode>(systemMode);\n\n useEffect(() => {\n if (preference !== 'auto') return;\n const mq = systemQuery();\n if (!mq) return;\n setSystem(mq.matches ? 'light' : 'dark');\n const onChange = (e: { matches: boolean }) => setSystem(e.matches ? 'light' : 'dark');\n mq.addEventListener('change', onChange);\n return () => mq.removeEventListener('change', onChange);\n }, [preference]);\n\n return preference === 'auto' ? system : preference;\n}\n\n/** The part of `Storage` {@link useColorModePreference} reads and writes. */\nexport type ColorModeStorage = Pick<Storage, 'getItem' | 'setItem'>;\n\n/** Options for {@link useColorModePreference}. */\nexport interface ColorModePreferenceOptions {\n /** Where the choice is remembered. Unset, it lasts as long as the component. */\n readonly storageKey?: string;\n /** The store `storageKey` names a slot in. Defaults to `localStorage`. */\n readonly storage?: ColorModeStorage;\n /** The preference before the user has chosen one. Defaults to `'auto'`. */\n readonly defaultPreference?: ColorModePreference;\n}\n\n/** What {@link useColorModePreference} returns. */\nexport interface ColorModePreferenceState {\n readonly preference: ColorModePreference;\n readonly setPreference: (next: ColorModePreference) => void;\n /** `preference` resolved against the OS — what to put in the theme's `mode` axis. */\n readonly mode: ColorMode;\n}\n\nfunction defaultStorage(): ColorModeStorage | undefined {\n try {\n return globalThis.localStorage ?? undefined;\n } catch {\n return undefined;\n }\n}\n\n/**\n * A user's color-mode choice, optionally remembered across loads, and the mode\n * it resolves to. Storage that is missing or throws (a private window, blocked\n * site data) degrades to an unremembered choice rather than an error.\n */\nexport function useColorModePreference(\n options: ColorModePreferenceOptions = {},\n): ColorModePreferenceState {\n const { storageKey, storage, defaultPreference = 'auto' } = options;\n\n const [preference, setState] = useState<ColorModePreference>(() => {\n if (storageKey === undefined) return defaultPreference;\n try {\n const stored = (storage ?? defaultStorage())?.getItem(storageKey);\n return isColorModePreference(stored) ? stored : defaultPreference;\n } catch {\n return defaultPreference;\n }\n });\n\n const setPreference = useCallback(\n (next: ColorModePreference) => {\n setState(next);\n if (storageKey === undefined) return;\n try {\n (storage ?? defaultStorage())?.setItem(storageKey, next);\n } catch {\n // Unremembered, but still applied.\n }\n },\n [storageKey, storage],\n );\n\n return { preference, setPreference, mode: useResolvedColorMode(preference) };\n}\n","import React, { createContext, useContext, useMemo, useRef, useLayoutEffect } from 'react';\nimport { applyTheme } from './applyTheme';\nimport { themeTones, type ColorList } from './colorList';\nimport { fullSelection, selectionKey, type Selection } from './axes';\nimport { resolveTheme, themeAxes, type ResolvedTheme } from './resolveTheme';\nimport { weaselTheme, type Theme } from './theme';\n\nexport {\n useColorModePreference,\n useResolvedColorMode,\n type ColorModePreferenceOptions,\n type ColorModePreferenceState,\n type ColorModeStorage,\n} from './useColorMode';\nexport { isColorModePreference, type ColorMode, type ColorModePreference } from './colorMode';\n\n/** What `<ThemeProvider>` publishes: the theme, the full selection in force, the\n * fully resolved token record for that pair, and the tone list in force. */\nexport interface ThemeContextValue {\n readonly theme: Theme;\n readonly selection: Selection;\n readonly resolved: ResolvedTheme;\n readonly tones: ColorList;\n}\n\n/** What `<ThemeProvider>` publishes. Provide `null` to hide an outer theme from a subtree, as a\n * boundary that renders its children the way a page with no provider would. */\nexport const ThemeContext = createContext<ThemeContextValue | null>(null);\n\n/** Props for `<ThemeProvider>`. */\nexport interface ThemeProviderProps {\n readonly theme?: Theme;\n /** Axis values, e.g. `{ mode: 'light' }`. A missing axis takes its default. */\n readonly selection?: Selection;\n /** The colors a `tone` index picks from in this subtree. Absent: the theme's own. */\n readonly tones?: ColorList;\n /** Applied to the wrapper element, so it can be the layout element too. */\n readonly className?: string;\n readonly style?: React.CSSProperties;\n readonly children: React.ReactNode;\n}\n\n/**\n * Applies a theme to a wrapper element and publishes the resolved record.\n *\n * Consumers that draw outside the DOM (the WebGL HUD) read `resolved` and\n * never touch `getComputedStyle`.\n *\n * The wrapper is a real element in the layout. Pass `className` rather than\n * nesting your own div inside — an anonymous div between a flex parent and\n * its child breaks percentage heights.\n */\nexport function ThemeProvider({\n theme = weaselTheme,\n selection,\n tones,\n className,\n style,\n children,\n}: ThemeProviderProps): React.ReactElement {\n const ref = useRef<HTMLDivElement>(null);\n const axes = themeAxes(theme);\n const full = fullSelection(axes, selection);\n // Keyed on the selection's content: callers pass a fresh object every render.\n const key = selectionKey(axes, full);\n\n const value = useMemo<ThemeContextValue>(\n () => ({ theme, selection: full, resolved: resolveTheme(theme, full), tones: tones ?? themeTones(theme) }),\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [theme, key, tones],\n );\n\n useLayoutEffect(() => {\n if (ref.current) applyTheme(ref.current, theme, value.selection);\n }, [theme, value]);\n\n const attrs = Object.fromEntries(Object.keys(axes).map((a) => [`data-wzl-${a}`, full[a]]));\n return (\n <ThemeContext.Provider value={value}>\n <div ref={ref} className={className} style={style} data-wzl-theme={theme.name} {...attrs}>\n {children}\n </div>\n </ThemeContext.Provider>\n );\n}\n\n/** The theme in force. Throws outside a `<ThemeProvider>`; use\n * `useThemeOptional` where the provider is not guaranteed. */\nexport function useTheme(): ThemeContextValue {\n const ctx = useContext(ThemeContext);\n if (!ctx) throw new Error('useTheme must be used inside a <ThemeProvider>');\n return ctx;\n}\n\n/**\n * The theme if one is provided, `null` otherwise.\n *\n * For kit internals that should follow a theme when the app supplies one but\n * must still work in an app that never mounted a provider — `useHud` is the\n * motivating case.\n */\nexport function useThemeOptional(): ThemeContextValue | null {\n return useContext(ThemeContext);\n}\n\nlet defaultTones: ThemeContextValue | undefined;\n\n/**\n * The tone list in force and what it resolves against — the nearest\n * `<ThemeProvider>`'s, else the built-in theme's at its defaults. Pass the\n * result as `colorAt`'s context.\n */\nexport function useTones(): ThemeContextValue {\n const ctx = useContext(ThemeContext);\n if (ctx) return ctx;\n return (defaultTones ??= {\n theme: weaselTheme,\n selection: fullSelection(themeAxes(weaselTheme)),\n resolved: resolveTheme(weaselTheme),\n tones: themeTones(weaselTheme),\n });\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/useColorMode.ts","../src/react.tsx"],"names":[],"mappings":";;;;;;AAGA,SAAS,WAAA,GAAqC;AAC5C,EAAA,IAAI,OAAO,UAAA,CAAW,UAAA,KAAe,UAAA,EAAY,OAAO,IAAA;AACxD,EAAA,OAAO,UAAA,CAAW,WAAW,+BAA+B,CAAA;AAC9D;AAEA,IAAM,UAAA,GAAa,MAAkB,WAAA,EAAY,EAAG,UAAU,OAAA,GAAU,MAAA;AAOjE,SAAS,qBAAqB,UAAA,EAA4C;AAC/E,EAAA,MAAM,CAAC,MAAA,EAAQ,SAAS,CAAA,GAAI,SAAoB,UAAU,CAAA;AAE1D,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,IAAI,eAAe,MAAA,EAAQ;AAC3B,IAAA,MAAM,KAAK,WAAA,EAAY;AACvB,IAAA,IAAI,CAAC,EAAA,EAAI;AAET,IAAA,SAAA,CAAU,EAAA,CAAG,OAAA,GAAU,OAAA,GAAU,MAAM,CAAA;AACvC,IAAA,MAAM,WAAW,CAAC,CAAA,KAA4B,UAAU,CAAA,CAAE,OAAA,GAAU,UAAU,MAAM,CAAA;AACpF,IAAA,EAAA,CAAG,gBAAA,CAAiB,UAAU,QAAQ,CAAA;AACtC,IAAA,OAAO,MAAM,EAAA,CAAG,mBAAA,CAAoB,QAAA,EAAU,QAAQ,CAAA;AAAA,EACxD,CAAA,EAAG,CAAC,UAAU,CAAC,CAAA;AAEf,EAAA,OAAO,UAAA,KAAe,SAAS,MAAA,GAAS,UAAA;AAC1C;AAuBA,SAAS,cAAA,GAA+C;AACtD,EAAA,IAAI;AACF,IAAA,OAAO,WAAW,YAAA,IAAgB,KAAA,CAAA;AAAA,EACpC,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO,MAAA;AAAA,EACT;AACF;AAOO,SAAS,sBAAA,CACd,OAAA,GAAsC,EAAC,EACb;AAC1B,EAAA,MAAM,EAAE,UAAA,EAAY,OAAA,EAAS,iBAAA,GAAoB,QAAO,GAAI,OAAA;AAE5D,EAAA,MAAM,CAAC,UAAA,EAAY,QAAQ,CAAA,GAAI,SAA8B,MAAM;AACjE,IAAA,IAAI,UAAA,KAAe,QAAW,OAAO,iBAAA;AACrC,IAAA,IAAI;AACF,MAAA,MAAM,MAAA,GAAA,CAAU,OAAA,IAAW,cAAA,EAAe,GAAI,QAAQ,UAAU,CAAA;AAChE,MAAA,OAAO,qBAAA,CAAsB,MAAM,CAAA,GAAI,MAAA,GAAS,iBAAA;AAAA,IAClD,CAAA,CAAA,MAAQ;AACN,MAAA,OAAO,iBAAA;AAAA,IACT;AAAA,EACF,CAAC,CAAA;AAED,EAAA,MAAM,aAAA,GAAgB,WAAA;AAAA,IACpB,CAAC,IAAA,KAA8B;AAC7B,MAAA,QAAA,CAAS,IAAI,CAAA;AACb,MAAA,IAAI,eAAe,MAAA,EAAW;AAC9B,MAAA,IAAI;AACF,QAAA,CAAC,OAAA,IAAW,cAAA,EAAe,GAAI,OAAA,CAAQ,YAAY,IAAI,CAAA;AAAA,MACzD,CAAA,CAAA,MAAQ;AAAA,MAER;AAAA,IACF,CAAA;AAAA,IACA,CAAC,YAAY,OAAO;AAAA,GACtB;AAEA,EAAA,OAAO,EAAE,UAAA,EAAY,aAAA,EAAe,IAAA,EAAM,oBAAA,CAAqB,UAAU,CAAA,EAAE;AAC7E;ACpEO,IAAM,YAAA,GAAe,cAAwC,IAAI;AAyBjE,SAAS,aAAA,CAAc;AAAA,EAC5B,KAAA,GAAQ,WAAA;AAAA,EACR,SAAA;AAAA,EACA,KAAA;AAAA,EACA,SAAA;AAAA,EACA,KAAA;AAAA,EACA;AACF,CAAA,EAA2C;AACzC,EAAA,MAAM,GAAA,GAAM,OAAuB,IAAI,CAAA;AACvC,EAAA,MAAM,IAAA,GAAO,UAAU,KAAK,CAAA;AAC5B,EAAA,MAAM,IAAA,GAAO,aAAA,CAAc,IAAA,EAAM,SAAS,CAAA;AAE1C,EAAA,MAAM,GAAA,GAAM,YAAA,CAAa,IAAA,EAAM,IAAI,CAAA;AAEnC,EAAA,MAAM,KAAA,GAAQ,OAAA;AAAA,IACZ,OAAO,EAAE,KAAA,EAAO,SAAA,EAAW,MAAM,QAAA,EAAU,YAAA,CAAa,KAAA,EAAO,IAAI,CAAA,EAAG,KAAA,EAAO,KAAA,IAAS,UAAA,CAAW,KAAK,CAAA,EAAE,CAAA;AAAA;AAAA,IAExG,CAAC,KAAA,EAAO,GAAA,EAAK,KAAK;AAAA,GACpB;AAEA,EAAA,eAAA,CAAgB,MAAM;AACpB,IAAA,IAAI,IAAI,OAAA,EAAS,UAAA,CAAW,IAAI,OAAA,EAAS,KAAA,EAAO,MAAM,SAAS,CAAA;AAAA,EACjE,CAAA,EAAG,CAAC,KAAA,EAAO,KAAK,CAAC,CAAA;AAEjB,EAAA,MAAM,QAAQ,MAAA,CAAO,WAAA,CAAY,OAAO,IAAA,CAAK,IAAI,EAAE,GAAA,CAAI,CAAC,CAAA,KAAM,CAAC,YAAY,CAAC,CAAA,CAAA,EAAI,KAAK,CAAC,CAAC,CAAC,CAAC,CAAA;AACzF,EAAA,2BACG,YAAA,CAAa,QAAA,EAAb,EAAsB,KAAA,EACrB,8BAAC,KAAA,EAAA,EAAI,GAAA,EAAU,SAAA,EAAsB,KAAA,EAAc,kBAAgB,KAAA,CAAM,IAAA,EAAO,GAAG,KAAA,EAChF,UACH,CAAA,EACF,CAAA;AAEJ;AAIO,SAAS,QAAA,GAA8B;AAC5C,EAAA,MAAM,GAAA,GAAM,WAAW,YAAY,CAAA;AACnC,EAAA,IAAI,CAAC,GAAA,EAAK,MAAM,IAAI,MAAM,gDAAgD,CAAA;AAC1E,EAAA,OAAO,GAAA;AACT;AASO,SAAS,gBAAA,GAA6C;AAC3D,EAAA,OAAO,WAAW,YAAY,CAAA;AAChC;AAEA,IAAI,YAAA;AAOG,SAAS,QAAA,GAA8B;AAC5C,EAAA,MAAM,GAAA,GAAM,WAAW,YAAY,CAAA;AACnC,EAAA,IAAI,KAAK,OAAO,GAAA;AAChB,EAAA,OAAQ,YAAA,KAAiB;AAAA,IACvB,KAAA,EAAO,WAAA;AAAA,IACP,SAAA,EAAW,aAAA,CAAc,SAAA,CAAU,WAAW,CAAC,CAAA;AAAA,IAC/C,QAAA,EAAU,aAAa,WAAW,CAAA;AAAA,IAClC,KAAA,EAAO,WAAW,WAAW;AAAA,GAC/B;AACF","file":"react.js","sourcesContent":["import { useCallback, useEffect, useState } from 'react';\nimport { isColorModePreference, type ColorMode, type ColorModePreference } from './colorMode';\n\nfunction systemQuery(): MediaQueryList | null {\n if (typeof globalThis.matchMedia !== 'function') return null;\n return globalThis.matchMedia('(prefers-color-scheme: light)');\n}\n\nconst systemMode = (): ColorMode => (systemQuery()?.matches ? 'light' : 'dark');\n\n/**\n * Resolves a preference to the mode a `ThemeProvider` selection takes.\n * `'auto'` subscribes to the OS setting and follows it live; an explicit\n * choice opts out of the subscription entirely.\n */\nexport function useResolvedColorMode(preference: ColorModePreference): ColorMode {\n const [system, setSystem] = useState<ColorMode>(systemMode);\n\n useEffect(() => {\n if (preference !== 'auto') return;\n const mq = systemQuery();\n if (!mq) return;\n // eslint-disable-next-line react-hooks/set-state-in-effect -- syncs to the OS color-scheme query\n setSystem(mq.matches ? 'light' : 'dark');\n const onChange = (e: { matches: boolean }) => setSystem(e.matches ? 'light' : 'dark');\n mq.addEventListener('change', onChange);\n return () => mq.removeEventListener('change', onChange);\n }, [preference]);\n\n return preference === 'auto' ? system : preference;\n}\n\n/** The part of `Storage` {@link useColorModePreference} reads and writes. */\nexport type ColorModeStorage = Pick<Storage, 'getItem' | 'setItem'>;\n\n/** Options for {@link useColorModePreference}. */\nexport interface ColorModePreferenceOptions {\n /** Where the choice is remembered. Unset, it lasts as long as the component. */\n readonly storageKey?: string;\n /** The store `storageKey` names a slot in. Defaults to `localStorage`. */\n readonly storage?: ColorModeStorage;\n /** The preference before the user has chosen one. Defaults to `'auto'`. */\n readonly defaultPreference?: ColorModePreference;\n}\n\n/** What {@link useColorModePreference} returns. */\nexport interface ColorModePreferenceState {\n readonly preference: ColorModePreference;\n readonly setPreference: (next: ColorModePreference) => void;\n /** `preference` resolved against the OS — what to put in the theme's `mode` axis. */\n readonly mode: ColorMode;\n}\n\nfunction defaultStorage(): ColorModeStorage | undefined {\n try {\n return globalThis.localStorage ?? undefined;\n } catch {\n return undefined;\n }\n}\n\n/**\n * A user's color-mode choice, optionally remembered across loads, and the mode\n * it resolves to. Storage that is missing or throws (a private window, blocked\n * site data) degrades to an unremembered choice rather than an error.\n */\nexport function useColorModePreference(\n options: ColorModePreferenceOptions = {},\n): ColorModePreferenceState {\n const { storageKey, storage, defaultPreference = 'auto' } = options;\n\n const [preference, setState] = useState<ColorModePreference>(() => {\n if (storageKey === undefined) return defaultPreference;\n try {\n const stored = (storage ?? defaultStorage())?.getItem(storageKey);\n return isColorModePreference(stored) ? stored : defaultPreference;\n } catch {\n return defaultPreference;\n }\n });\n\n const setPreference = useCallback(\n (next: ColorModePreference) => {\n setState(next);\n if (storageKey === undefined) return;\n try {\n (storage ?? defaultStorage())?.setItem(storageKey, next);\n } catch {\n // Unremembered, but still applied.\n }\n },\n [storageKey, storage],\n );\n\n return { preference, setPreference, mode: useResolvedColorMode(preference) };\n}\n","import React, { createContext, useContext, useMemo, useRef, useLayoutEffect } from 'react';\nimport { applyTheme } from './applyTheme';\nimport { themeTones, type ColorList } from './colorList';\nimport { fullSelection, selectionKey, type Selection } from './axes';\nimport { resolveTheme, themeAxes, type ResolvedTheme } from './resolveTheme';\nimport { weaselTheme, type Theme } from './theme';\n\nexport {\n useColorModePreference,\n useResolvedColorMode,\n type ColorModePreferenceOptions,\n type ColorModePreferenceState,\n type ColorModeStorage,\n} from './useColorMode';\nexport { isColorModePreference, type ColorMode, type ColorModePreference } from './colorMode';\n\n/** What `<ThemeProvider>` publishes: the theme, the full selection in force, the\n * fully resolved token record for that pair, and the tone list in force. */\nexport interface ThemeContextValue {\n readonly theme: Theme;\n readonly selection: Selection;\n readonly resolved: ResolvedTheme;\n readonly tones: ColorList;\n}\n\n/** What `<ThemeProvider>` publishes. Provide `null` to hide an outer theme from a subtree, as a\n * boundary that renders its children the way a page with no provider would. */\nexport const ThemeContext = createContext<ThemeContextValue | null>(null);\n\n/** Props for `<ThemeProvider>`. */\nexport interface ThemeProviderProps {\n readonly theme?: Theme;\n /** Axis values, e.g. `{ mode: 'light' }`. A missing axis takes its default. */\n readonly selection?: Selection;\n /** The colors a `tone` index picks from in this subtree. Absent: the theme's own. */\n readonly tones?: ColorList;\n /** Applied to the wrapper element, so it can be the layout element too. */\n readonly className?: string;\n readonly style?: React.CSSProperties;\n readonly children: React.ReactNode;\n}\n\n/**\n * Applies a theme to a wrapper element and publishes the resolved record.\n *\n * Consumers that draw outside the DOM (the WebGL HUD) read `resolved` and\n * never touch `getComputedStyle`.\n *\n * The wrapper is a real element in the layout. Pass `className` rather than\n * nesting your own div inside — an anonymous div between a flex parent and\n * its child breaks percentage heights.\n */\nexport function ThemeProvider({\n theme = weaselTheme,\n selection,\n tones,\n className,\n style,\n children,\n}: ThemeProviderProps): React.ReactElement {\n const ref = useRef<HTMLDivElement>(null);\n const axes = themeAxes(theme);\n const full = fullSelection(axes, selection);\n // Keyed on the selection's content: callers pass a fresh object every render.\n const key = selectionKey(axes, full);\n\n const value = useMemo<ThemeContextValue>(\n () => ({ theme, selection: full, resolved: resolveTheme(theme, full), tones: tones ?? themeTones(theme) }),\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [theme, key, tones],\n );\n\n useLayoutEffect(() => {\n if (ref.current) applyTheme(ref.current, theme, value.selection);\n }, [theme, value]);\n\n const attrs = Object.fromEntries(Object.keys(axes).map((a) => [`data-wzl-${a}`, full[a]]));\n return (\n <ThemeContext.Provider value={value}>\n <div ref={ref} className={className} style={style} data-wzl-theme={theme.name} {...attrs}>\n {children}\n </div>\n </ThemeContext.Provider>\n );\n}\n\n/** The theme in force. Throws outside a `<ThemeProvider>`; use\n * `useThemeOptional` where the provider is not guaranteed. */\nexport function useTheme(): ThemeContextValue {\n const ctx = useContext(ThemeContext);\n if (!ctx) throw new Error('useTheme must be used inside a <ThemeProvider>');\n return ctx;\n}\n\n/**\n * The theme if one is provided, `null` otherwise.\n *\n * For kit internals that should follow a theme when the app supplies one but\n * must still work in an app that never mounted a provider — `useHud` is the\n * motivating case.\n */\nexport function useThemeOptional(): ThemeContextValue | null {\n return useContext(ThemeContext);\n}\n\nlet defaultTones: ThemeContextValue | undefined;\n\n/**\n * The tone list in force and what it resolves against — the nearest\n * `<ThemeProvider>`'s, else the built-in theme's at its defaults. Pass the\n * result as `colorAt`'s context.\n */\nexport function useTones(): ThemeContextValue {\n const ctx = useContext(ThemeContext);\n if (ctx) return ctx;\n return (defaultTones ??= {\n theme: weaselTheme,\n selection: fullSelection(themeAxes(weaselTheme)),\n resolved: resolveTheme(weaselTheme),\n tones: themeTones(weaselTheme),\n });\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@weasel-js/theme",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.9.1",
|
|
4
4
|
"description": "Design tokens shared by weasel-ui and weasel-hud — CSS variables + a parallel TS export.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"./package.json": "./package.json"
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
34
|
-
"@weasel-js/paint": "1.
|
|
34
|
+
"@weasel-js/paint": "1.9.1"
|
|
35
35
|
},
|
|
36
36
|
"peerDependencies": {
|
|
37
37
|
"react": ">=18"
|