@mortenbrudvik/waveui 0.17.0 → 0.17.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 CHANGED
@@ -176,7 +176,7 @@ export const App = ({ children }: { children: React.ReactNode }) => <WaveProvide
176
176
  ```
177
177
 
178
178
  - **Contrast holds.** Each generated stop keeps the luminance of the default ramp's stop and takes its hue and colorfulness from the seed, so every text, fill, stroke and focus-ring pair of the light and dark themes stays above the floors the themes are tested against (4.5:1 for text, 3:1 for strokes and focus rings), whatever the seed. The cost: stop 80 (the light primary) is the seed's hue at the default's lightness, so a light seed gives a darker button. For exact brand colors pass a full ramp (`createBrandTokens({ 10: '#…', …, 160: '#…' })`, a `BrandRamp`), which is returned unchanged and checked in development: each pair the themes read from it that fails its contrast minimum logs a warning. The high-contrast theme ignores the brand.
179
- - **Any token.** `tokens` takes any token by its camelCase name, the CSS variable without `--wave-` (`primaryForeground`, `brand80`, `paletteRedTint`, `shadow8`, `fontFamilyMonospace`, `zOverlay`); TypeScript refuses a misspelled one. A value is a finite number or a string without `;`, `{` or `}` (anything else is skipped with a development warning, so a value cannot add a declaration of its own to the server-rendered `style`). The provider writes them as inline custom properties on its root (also in the server HTML) and on every portal of its subtree, so its dialogs, menus, popovers, tooltips and toasts match it; `applyStylesToPortals={false}` keeps them off the portals, an enclosing provider's overrides (a brand ramp) included; theme and direction stay.
179
+ - **Any token.** `tokens` takes any token by its camelCase name, the CSS variable without `--wave-` (`primaryForeground`, `brand80`, `paletteRedTint`, `shadow8`, `fontFamilyMonospace`, `zOverlay`); TypeScript refuses a misspelled one. A value is a finite number or a string without `;`, `{`, `}` or `url(` whose quotes, parentheses, square brackets and comments are balanced, with no `!` or backslash outside strings and comments (anything else is skipped with a development warning, so a value can neither add a declaration of its own to the server-rendered `style` nor swallow the declarations after it); it is trimmed, as React's server renderer trims it, so the server and the client render the same overrides. The provider writes them as inline custom properties on its root (also in the server HTML) and on every portal of its subtree, so its dialogs, menus, popovers, tooltips and toasts match it; `applyStylesToPortals={false}` keeps them off the portals, an enclosing provider's overrides (a brand ramp) included; theme and direction stay.
180
180
  - **Nesting.** A nested provider merges its `tokens` over the enclosing ones key by key, also when it sets another `theme`: set a semantic override in the provider that sets the theme it was chosen for. `tokens={null}` clears the inherited overrides for the theme's own tokens and for the overlays, while the theme-independent ones (the ramps, fonts, stacking, motion) still reach the subtree's own elements through the CSS cascade. A nested accent:
181
181
 
182
182
  ```tsx
@@ -1,2 +1,2 @@
1
- "use client";const e=require("../../_virtual/_rolldown/runtime.cjs"),t=require("../../lib/cn.cjs"),n=require("../../lib/dev.cjs"),r=require("../../lib/classNames.cjs"),i=require("../../lib/tokens.cjs"),a=require("../../lib/theme.cjs");let o=require("react");o=e.__toESM(o,1);let s=require("react/jsx-runtime");var c=o.createContext({theme:`light`,dir:`ltr`,themeClassName:``,portalContainer:null,inputDefaults:{},motion:`default`,tokens:{},tokenStyle:void 0,applyStylesToPortals:!0,hasProvider:!1});function l(e,t){let n=Object.keys(e),r=Object.keys(t);return n.length===r.length&&n.every((n,i)=>n===r[i]&&Object.is(e[n],t[n]))}function u(){return o.useContext(c)}var d=({theme:e,dir:u,portalContainer:d,inputDefaults:f,motion:p,tokens:m,applyStylesToPortals:h,children:g,className:_,style:v,ref:y,...b})=>{let x=o.useContext(c),S=e??x.theme,C=u??x.dir,w=p??x.motion,T=h??x.applyStylesToPortals,E=d===void 0?x.portalContainer:d,D=i.mergeTokenOverrides(x.tokens,m),[O,k]=o.useState(D);l(O,D)||k(D);let{style:A,invalid:j}=o.useMemo(()=>i.collectTokenStyle(O),[O]);o.useEffect(()=>{for(let{key:e,reason:t}of j)n.warnOnce(`WaveProvider:tokens:${t}:${e}`,`WaveProvider: \`tokens\` skips \`${e}\`: `+(t===`name`?"not a token name (the CSS variable in camelCase without `--wave-`, such as `primaryForeground`).":"its value must be a finite number or a string without `;`, `{` or `}`."))},[j]);let M=f?.size??x.inputDefaults.size,N=f?.appearance??x.inputDefaults.appearance,P=o.useMemo(()=>{let e={};return M!==void 0&&(e.size=M),N!==void 0&&(e.appearance=N),e},[M,N]),F=a.isWaveTheme(S)?S:`light`,I=a.getThemeClassName(F);o.useEffect(()=>{a.isWaveTheme(S)||n.warnOnce(`WaveProvider:theme:${String(S)}`,`WaveProvider: unknown theme "${String(S)}". Valid themes: ${a.WAVE_THEMES.join(`, `)}. Using "light".`)},[S]);let L=o.useMemo(()=>({theme:F,dir:C,themeClassName:I,portalContainer:E,inputDefaults:P,motion:w,tokens:O,tokenStyle:A,applyStylesToPortals:T,hasProvider:!0}),[F,C,I,E,P,w,O,A,T]);return(0,s.jsx)(c.Provider,{value:L,children:(0,s.jsx)(`div`,{...b,style:A?{...A,...v}:v,ref:y,dir:C,"data-wave-theme":F,className:t.cn(r.waveProviderClassNames.root,I,`bg-background text-foreground font-wave text-body-1`,_),children:g})})};d.displayName=`WaveProvider`,exports.WaveProvider=d,exports.getThemeClassName=a.getThemeClassName,exports.useWaveTheme=u;
1
+ "use client";const e=require("../../_virtual/_rolldown/runtime.cjs"),t=require("../../lib/cn.cjs"),n=require("../../lib/dev.cjs"),r=require("../../lib/classNames.cjs"),i=require("../../lib/tokens.cjs"),a=require("../../lib/theme.cjs");let o=require("react");o=e.__toESM(o,1);let s=require("react/jsx-runtime");var c=o.createContext({theme:`light`,dir:`ltr`,themeClassName:``,portalContainer:null,inputDefaults:{},motion:`default`,tokens:{},tokenStyle:void 0,applyStylesToPortals:!0,hasProvider:!1});function l(e,t){let n=Object.keys(e),r=Object.keys(t);return n.length===r.length&&n.every((n,i)=>n===r[i]&&Object.is(e[n],t[n]))}function u(){return o.useContext(c)}var d=({theme:e,dir:u,portalContainer:d,inputDefaults:f,motion:p,tokens:m,applyStylesToPortals:h,children:g,className:_,style:v,ref:y,...b})=>{let x=o.useContext(c),S=e??x.theme,C=u??x.dir,w=p??x.motion,T=h??x.applyStylesToPortals,E=d===void 0?x.portalContainer:d,D=i.mergeTokenOverrides(x.tokens,m),[O,k]=o.useState(D);l(O,D)||k(D);let{style:A,invalid:j}=o.useMemo(()=>i.collectTokenStyle(O),[O]);o.useEffect(()=>{try{if(process.env.NODE_ENV!==`production`)for(let{key:e,reason:t}of j)n.warnOnce(`WaveProvider:tokens:${t}:${e}`,`WaveProvider: \`tokens\` skips \`${e}\`: `+(t===`name`?"not a token name (the CSS variable in camelCase without `--wave-`, such as `primaryForeground`).":"its value must be a finite number, or CSS text with balanced quotes, brackets and comments, without `;`, `{`, `}` or `url(`, and without `!` or a backslash outside strings and comments."))}catch{}},[j]);let M=f?.size??x.inputDefaults.size,N=f?.appearance??x.inputDefaults.appearance,P=o.useMemo(()=>{let e={};return M!==void 0&&(e.size=M),N!==void 0&&(e.appearance=N),e},[M,N]),F=a.isWaveTheme(S)?S:`light`,I=a.getThemeClassName(F);o.useEffect(()=>{a.isWaveTheme(S)||n.warnOnce(`WaveProvider:theme:${String(S)}`,`WaveProvider: unknown theme "${String(S)}". Valid themes: ${a.WAVE_THEMES.join(`, `)}. Using "light".`)},[S]);let L=o.useMemo(()=>({theme:F,dir:C,themeClassName:I,portalContainer:E,inputDefaults:P,motion:w,tokens:O,tokenStyle:A,applyStylesToPortals:T,hasProvider:!0}),[F,C,I,E,P,w,O,A,T]);return(0,s.jsx)(c.Provider,{value:L,children:(0,s.jsx)(`div`,{...b,style:A?{...A,...v}:v,ref:y,dir:C,"data-wave-theme":F,className:t.cn(r.waveProviderClassNames.root,I,`bg-background text-foreground font-wave text-body-1`,_),children:g})})};d.displayName=`WaveProvider`,exports.WaveProvider=d,exports.getThemeClassName=a.getThemeClassName,exports.useWaveTheme=u;
2
2
  //# sourceMappingURL=WaveProvider.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"WaveProvider.cjs","names":["React","mergeTokenOverrides","collectTokenStyle","isWaveTheme","getThemeClassName","WAVE_THEMES","cn","waveProviderClassNames","require_runtime","require_cn","require_dev","require_classNames","require_tokens","require_theme","react","react_jsx_runtime","WaveContext","sameTokens","keys","a","others","b","key","index","useWaveTheme","WaveProvider","themeProp","dirProp","portalContainerProp","inputDefaultsProp","motionProp","tokensProp","applyStylesToPortalsProp","rest","parent","theme","dir","motion","applyStylesToPortals","portalContainer","mergedTokens","tokens","setTokens","tokenStyle","invalid","reason","size","appearance","inputDefaults","merged","resolvedTheme","themeClassName","value","style","className"],"sources":["../../../src/components/provider/WaveProvider.tsx"],"sourcesContent":["import * as React from 'react';\nimport { cn } from '../../lib/cn';\nimport { warnOnce } from '../../lib/dev';\nimport { waveProviderClassNames } from '../../lib/classNames';\nimport { getThemeClassName, isWaveTheme, WAVE_THEMES, type WaveTheme } from '../../lib/theme';\nimport {\n collectTokenStyle,\n mergeTokenOverrides,\n type TokenStyle,\n type WaveTokenOverrides,\n} from '../../lib/tokens';\nimport type { CoreSize, InputAppearance, MotionPreference } from '../../lib/types';\n\n// The theme helper lives in server-safe src/lib (no \"use client\"), so React Server Components can\n// call it; it stays exported from here too.\nexport { getThemeClassName } from '../../lib/theme';\nexport type { WaveTheme } from '../../lib/theme';\n\n/** Text direction for bidirectional layout support. */\nexport type WaveDir = 'ltr' | 'rtl';\n\n/** Default size and appearance of the text inputs and pickers in a subtree. */\nexport interface InputDefaults {\n /** Default `size` of Input, Textarea, Select, SearchBox, SpinButton and the pickers. */\n size?: CoreSize;\n /** Default `appearance` of the same controls. */\n appearance?: InputAppearance;\n}\n\n/** Props accepted by {@link WaveProvider}. */\nexport interface WaveProviderProps extends React.HTMLAttributes<HTMLDivElement> {\n /**\n * Visual theme applied to the subtree. Providers can be nested in any order: each root declares\n * its theme's tokens, so a light panel inside a dark app (and the reverse) renders correctly.\n * @default the enclosing WaveProvider's theme, else 'light'\n */\n theme?: WaveTheme;\n /**\n * Text direction for the subtree (also read by portaled overlays and keyboard navigation).\n * @default the enclosing WaveProvider's direction, else 'ltr'\n */\n dir?: WaveDir;\n /**\n * Element that portaled overlays (dialogs, popovers, menus, toasts) render into. `null` renders\n * them into `document.body`, also inside a provider that sets a container.\n * @default the enclosing WaveProvider's container, else document.body\n */\n portalContainer?: HTMLElement | null;\n /**\n * Default `size` and `appearance` of the text inputs and pickers in the subtree (Input,\n * Textarea, Select, SearchBox, SpinButton, Combobox, Dropdown, DatePicker, TimePicker,\n * TagPicker). Their own props and a Field's `size` win. A nested provider merges its keys over\n * the enclosing provider's; an omitted key is inherited.\n * @default the enclosing WaveProvider's defaults, else none (`medium`, `outline`)\n */\n inputDefaults?: InputDefaults;\n /**\n * Motion of the subtree. `'none'` makes every motion slot below it (a part's `motion`,\n * `backdropMotion`, `expandIconMotion` or `indeterminateMotion` left unset) behave as `null`,\n * which also stops the slide of TabList's selection line, and turns off Carousel's slide or fade\n * (a Carousel without its own `motion`) and the reveal of Nav item actions; a part's own value\n * still wins. Transitions that are no motion slot keep running: the turn of Accordion's and\n * Tree's chevrons, colour changes on hover and press. Fluent's `MotionBehaviourProvider` with `'skip'` is the counterpart, which\n * `@fluentui/react-components` does not export. A nested provider inherits an omitted `motion`\n * like `theme`.\n * @default the enclosing WaveProvider's motion, else 'default'\n */\n motion?: MotionPreference;\n /**\n * Design token overrides for the subtree: CSS values by token name, the CSS variable in camelCase\n * without its `--wave-` prefix (`brand80` for `--wave-brand-80`, `primaryForeground` for\n * `--wave-primary-foreground`; a number is written as its string). `theme` still names one of\n * the three themes, and the overrides apply over it as inline custom properties on the root and\n * on every portal of the subtree (see `applyStylesToPortals`), so its dialogs, menus, popovers,\n * tooltips and toasts take them too. Fluent's `theme` prop with a partial theme is the\n * counterpart.\n *\n * A nested provider merges its overrides over the enclosing provider's, key by key: an omitted\n * `tokens` inherits them all, and a key whose value is `undefined` inherits that key. `null`\n * clears the inherited overrides of the tokens the theme declares (the semantic colours, shadows,\n * palette) and of the subtree's overlays; theme-independent tokens (the brand and grey ramps,\n * font families, stacking order, motion durations and curves) of an enclosing provider still\n * reach the subtree's own elements through the CSS cascade, and the theme's colours built on the\n * brand ramp follow them there. A nested provider that sets another `theme` inherits the\n * overrides too, so set an override of a semantic colour (`primary`) in the provider that sets\n * the theme it was chosen for. A whole brand ramp is better made with `createBrandTokens` than\n * written by hand.\n * A key that is not a camelCase identifier, or a value that is neither a finite number nor a\n * string without `;`, `{` or `}` (such a value could add a declaration of its own to the\n * server-rendered `style` attribute), is skipped with a development warning; a well-formed name\n * is not checked against the list of tokens (TypeScript refuses a misspelled one).\n *\n * The overrides are rendered on the server too. Under a Content Security Policy that blocks\n * inline style attributes (`style-src` without `'unsafe-inline'`), the server-rendered overrides\n * do not apply and hydration does not apply them again: ship them in a stylesheet there, and pass\n * no `tokens` (nor `getTokenStyle`), since the overlays and the subtrees rendered in the client\n * would still take them and leave the page half branded.\n *\n * @example\n * <WaveProvider tokens={{ fontFamily: 'Inter, sans-serif', zOverlay: 2000 }}>\n * @default the enclosing WaveProvider's overrides, else none\n */\n tokens?: WaveTokenOverrides | null;\n /**\n * Whether the portals of the subtree (dialogs, popovers, menus, tooltips, toasts) carry the\n * overrides of `tokens`. `false` leaves them out: an overlay then shows its theme's own values\n * for the tokens the theme declares, and keeps the surrounding cascade (that of the element it\n * renders into) for the theme-independent ones. The theme classes and the direction are set on\n * every portal either way, so an overlay of a dark provider is still dark. Fluent's\n * `applyStylesToPortals` is the counterpart; unlike Fluent's, a nested provider inherits an\n * omitted value, like `theme`.\n * @default the enclosing WaveProvider's value, else true\n */\n applyStylesToPortals?: boolean;\n /** Content rendered inside the themed container. */\n children: React.ReactNode;\n /** Ref to the themed root `<div>`. */\n ref?: React.Ref<HTMLDivElement>;\n}\n\n/** Value provided by {@link WaveProvider} and returned by {@link useWaveTheme}. */\nexport interface WaveContextValue {\n /** The active theme. */\n theme: WaveTheme;\n /** The active text direction. */\n dir: WaveDir;\n /**\n * Theme classes of the nearest provider (see {@link getThemeClassName}); portals put them on\n * their wrapper so overlays inherit the theme. `''` outside a provider (the page's own theme).\n */\n themeClassName: string;\n /** Element portaled overlays render into; `null` means `document.body`. */\n portalContainer: HTMLElement | null;\n /** The merged input defaults of the providers above (`{}` outside a provider). */\n inputDefaults: InputDefaults;\n /** The motion preference of the providers above (`'default'` outside a provider). */\n motion: MotionPreference;\n /**\n * The merged token overrides of the providers above (`{}` outside a provider), as given: an\n * entry skipped for a malformed key or value stays in it, and only `tokenStyle` leaves it out.\n */\n tokens: WaveTokenOverrides;\n /**\n * The inline style that applies `tokens`, as the provider's root and, unless\n * `applyStylesToPortals` is `false`, its portals carry it; `undefined` when there is nothing to\n * set (also outside a provider).\n */\n tokenStyle: TokenStyle | undefined;\n /** Whether portals carry `tokenStyle` (`true` outside a provider). */\n applyStylesToPortals: boolean;\n /** `false` when no {@link WaveProvider} is above the caller (the defaults are returned). */\n hasProvider: boolean;\n}\n\nconst DEFAULT_CONTEXT: WaveContextValue = {\n theme: 'light',\n dir: 'ltr',\n themeClassName: '',\n portalContainer: null,\n inputDefaults: {},\n motion: 'default',\n tokens: {},\n tokenStyle: undefined,\n applyStylesToPortals: true,\n hasProvider: false,\n};\n\nconst WaveContext = React.createContext<WaveContextValue>(DEFAULT_CONTEXT);\n\n/** Whether two override maps have the same keys in the same order, with the same values. */\nfunction sameTokens(a: WaveTokenOverrides, b: WaveTokenOverrides): boolean {\n const keys = Object.keys(a) as (keyof WaveTokenOverrides)[];\n const others = Object.keys(b);\n return (\n keys.length === others.length &&\n keys.every((key, index) => key === others[index] && Object.is(a[key], b[key]))\n );\n}\n\n/**\n * Reads the theme, direction, theme classes, portal container and token overrides of the nearest\n * {@link WaveProvider}. Outside a provider it returns light/ltr defaults with `hasProvider: false`.\n *\n * @returns The {@link WaveContextValue} of the nearest provider.\n */\nexport function useWaveTheme(): WaveContextValue {\n return React.useContext(WaveContext);\n}\n\n/**\n * Applies Wave theming to an application or subtree.\n *\n * - Renders a `<div class=\"wave-root wave-<theme>\">` that declares the theme's tokens and paints\n * the themed background, text colour and font (`bg-background text-foreground font-wave\n * text-body-1`; a `className` passed by you wins). The precompiled `./styles` entry scopes its base\n * styles and native-element reset to this root, so it is required there.\n * - Sets `dir` and `data-wave-theme` on the root. An unknown `theme` value (from untyped code)\n * falls back to `'light'` — classes, `data-wave-theme` and the context value — and warns once in\n * development.\n * - Provides theme, direction, theme classes and the portal container through context, so\n * portaled overlays render with the same theme and direction.\n * - Nests: a provider inherits every prop it omits (`theme`, `dir`, `portalContainer`) from the\n * enclosing provider, so `<WaveProvider theme=\"light\">` inside an RTL app is a light panel that\n * stays right-to-left and keeps the app's portal container. At the top level the defaults are\n * `'light'`, `'ltr'` and `document.body`.\n * - `inputDefaults` nests differently: a nested provider's defined keys override the enclosing\n * provider's, and an omitted key is inherited, so setting only `appearance` in a nested provider\n * keeps the enclosing provider's `size`.\n * - `motion=\"none\"` turns the default motion of every animated part below it off (its motion\n * slots, Carousel's slide, TabList's sliding line, the reveal of Nav item actions; a part's own\n * `motion` still wins), while transitions without a slot keep running (Accordion's and Tree's\n * chevron turns, colour changes); a nested provider inherits an omitted `motion` like `theme`.\n * - `tokens` overrides design tokens in the subtree with inline custom properties on the root\n * (rendered on the server too) and on every portal of the subtree, unless\n * `applyStylesToPortals` is `false`. A nested provider merges its overrides over the enclosing\n * provider's key by key, also when it sets another `theme`; `tokens={null}` clears them for the\n * tokens the theme declares and for the overlays, while the theme-independent ones of an\n * enclosing provider still reach the subtree through the CSS cascade. An override whose key is\n * not a camelCase identifier, or whose value is neither a finite number nor a string without\n * `;`, `{` or `}`, is skipped with a development warning. Without overrides the root renders no\n * `style` attribute unless you pass `style`.\n */\nexport const WaveProvider = ({\n theme: themeProp,\n dir: dirProp,\n portalContainer: portalContainerProp,\n inputDefaults: inputDefaultsProp,\n motion: motionProp,\n tokens: tokensProp,\n applyStylesToPortals: applyStylesToPortalsProp,\n children,\n className,\n style,\n ref,\n ...rest\n}: WaveProviderProps) => {\n // Omitted props come from the enclosing provider; outside one, DEFAULT_CONTEXT supplies the\n // top-level defaults. An explicit `portalContainer={null}` means document.body, so only\n // `undefined` inherits.\n const parent = React.useContext(WaveContext);\n const theme = themeProp ?? parent.theme;\n const dir = dirProp ?? parent.dir;\n const motion = motionProp ?? parent.motion;\n const applyStylesToPortals = applyStylesToPortalsProp ?? parent.applyStylesToPortals;\n const portalContainer =\n portalContainerProp === undefined ? parent.portalContainer : portalContainerProp;\n\n // The own overrides merged over the enclosing provider's. The merged map keeps its identity while\n // its entries are equal (a previous-value state, C-HOOKS), so an inline `tokens={{ … }}` neither\n // re-renders the consumers nor rebuilds the style.\n const mergedTokens = mergeTokenOverrides(parent.tokens, tokensProp);\n const [tokens, setTokens] = React.useState(mergedTokens);\n if (!sameTokens(tokens, mergedTokens)) setTokens(mergedTokens);\n // Collected without warnings during render; the skipped entries are reported from an effect.\n const { style: tokenStyle, invalid } = React.useMemo(() => collectTokenStyle(tokens), [tokens]);\n\n React.useEffect(() => {\n for (const { key, reason } of invalid) {\n warnOnce(\n `WaveProvider:tokens:${reason}:${key}`,\n `WaveProvider: \\`tokens\\` skips \\`${key}\\`: ` +\n (reason === 'name'\n ? 'not a token name (the CSS variable in camelCase without `--wave-`, such as `primaryForeground`).'\n : 'its value must be a finite number or a string without `;`, `{` or `}`.'),\n );\n }\n }, [invalid]);\n\n // Defined keys override the enclosing provider's; the merged object keeps its identity while the\n // two values are equal, so an inline `inputDefaults={{ … }}` does not re-render every input.\n const size = inputDefaultsProp?.size ?? parent.inputDefaults.size;\n const appearance = inputDefaultsProp?.appearance ?? parent.inputDefaults.appearance;\n const inputDefaults = React.useMemo<InputDefaults>(() => {\n const merged: InputDefaults = {};\n if (size !== undefined) merged.size = size;\n if (appearance !== undefined) merged.appearance = appearance;\n return merged;\n }, [size, appearance]);\n\n // An unknown value (untyped callers) renders, and is reported, as the light theme.\n const resolvedTheme: WaveTheme = isWaveTheme(theme) ? theme : 'light';\n const themeClassName = getThemeClassName(resolvedTheme);\n\n React.useEffect(() => {\n if (!isWaveTheme(theme)) {\n warnOnce(\n `WaveProvider:theme:${String(theme)}`,\n `WaveProvider: unknown theme \"${String(theme)}\". Valid themes: ${WAVE_THEMES.join(', ')}. Using \"light\".`,\n );\n }\n }, [theme]);\n\n const value = React.useMemo<WaveContextValue>(\n () => ({\n theme: resolvedTheme,\n dir,\n themeClassName,\n portalContainer,\n inputDefaults,\n motion,\n tokens,\n tokenStyle,\n applyStylesToPortals,\n hasProvider: true,\n }),\n [\n resolvedTheme,\n dir,\n themeClassName,\n portalContainer,\n inputDefaults,\n motion,\n tokens,\n tokenStyle,\n applyStylesToPortals,\n ],\n );\n\n return (\n <WaveContext.Provider value={value}>\n <div\n {...rest}\n // The merged overrides (a nested root re-declares what its theme class would shadow), then\n // the consumer's `style`, which wins. Without overrides the consumer's `style` as given, so\n // the root has no style attribute unless the consumer passes one.\n style={tokenStyle ? { ...tokenStyle, ...style } : style}\n ref={ref}\n dir={dir}\n data-wave-theme={resolvedTheme}\n className={cn(\n waveProviderClassNames.root,\n themeClassName,\n 'bg-background text-foreground font-wave text-body-1',\n className,\n )}\n >\n {children}\n </div>\n </WaveContext.Provider>\n );\n};\n\nWaveProvider.displayName = 'WaveProvider';\n"],"mappings":"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"}
1
+ {"version":3,"file":"WaveProvider.cjs","names":["React","mergeTokenOverrides","collectTokenStyle","isWaveTheme","getThemeClassName","WAVE_THEMES","cn","waveProviderClassNames","require_runtime","require_cn","require_dev","require_classNames","require_tokens","require_theme","react","react_jsx_runtime","WaveContext","sameTokens","keys","a","others","b","key","index","useWaveTheme","WaveProvider","themeProp","dirProp","portalContainerProp","inputDefaultsProp","motionProp","tokensProp","applyStylesToPortalsProp","rest","parent","theme","dir","motion","applyStylesToPortals","portalContainer","mergedTokens","tokens","setTokens","tokenStyle","invalid","reason","size","appearance","inputDefaults","merged","resolvedTheme","themeClassName","value","style","className"],"sources":["../../../src/components/provider/WaveProvider.tsx"],"sourcesContent":["import * as React from 'react';\nimport { cn } from '../../lib/cn';\nimport { warnOnce } from '../../lib/dev';\nimport { waveProviderClassNames } from '../../lib/classNames';\nimport { getThemeClassName, isWaveTheme, WAVE_THEMES, type WaveTheme } from '../../lib/theme';\nimport {\n collectTokenStyle,\n mergeTokenOverrides,\n type TokenStyle,\n type WaveTokenOverrides,\n} from '../../lib/tokens';\nimport type { CoreSize, InputAppearance, MotionPreference } from '../../lib/types';\n\n// The theme helper lives in server-safe src/lib (no \"use client\"), so React Server Components can\n// call it; it stays exported from here too.\nexport { getThemeClassName } from '../../lib/theme';\nexport type { WaveTheme } from '../../lib/theme';\n\n/** Text direction for bidirectional layout support. */\nexport type WaveDir = 'ltr' | 'rtl';\n\n/** Default size and appearance of the text inputs and pickers in a subtree. */\nexport interface InputDefaults {\n /** Default `size` of Input, Textarea, Select, SearchBox, SpinButton and the pickers. */\n size?: CoreSize;\n /** Default `appearance` of the same controls. */\n appearance?: InputAppearance;\n}\n\n/** Props accepted by {@link WaveProvider}. */\nexport interface WaveProviderProps extends React.HTMLAttributes<HTMLDivElement> {\n /**\n * Visual theme applied to the subtree. Providers can be nested in any order: each root declares\n * its theme's tokens, so a light panel inside a dark app (and the reverse) renders correctly.\n * @default the enclosing WaveProvider's theme, else 'light'\n */\n theme?: WaveTheme;\n /**\n * Text direction for the subtree (also read by portaled overlays and keyboard navigation).\n * @default the enclosing WaveProvider's direction, else 'ltr'\n */\n dir?: WaveDir;\n /**\n * Element that portaled overlays (dialogs, popovers, menus, toasts) render into. `null` renders\n * them into `document.body`, also inside a provider that sets a container.\n * @default the enclosing WaveProvider's container, else document.body\n */\n portalContainer?: HTMLElement | null;\n /**\n * Default `size` and `appearance` of the text inputs and pickers in the subtree (Input,\n * Textarea, Select, SearchBox, SpinButton, Combobox, Dropdown, DatePicker, TimePicker,\n * TagPicker). Their own props and a Field's `size` win. A nested provider merges its keys over\n * the enclosing provider's; an omitted key is inherited.\n * @default the enclosing WaveProvider's defaults, else none (`medium`, `outline`)\n */\n inputDefaults?: InputDefaults;\n /**\n * Motion of the subtree. `'none'` makes every motion slot below it (a part's `motion`,\n * `backdropMotion`, `expandIconMotion` or `indeterminateMotion` left unset) behave as `null`,\n * which also stops the slide of TabList's selection line, and turns off Carousel's slide or fade\n * (a Carousel without its own `motion`) and the reveal of Nav item actions; a part's own value\n * still wins. Transitions that are no motion slot keep running: the turn of Accordion's and\n * Tree's chevrons, colour changes on hover and press. Fluent's `MotionBehaviourProvider` with `'skip'` is the counterpart, which\n * `@fluentui/react-components` does not export. A nested provider inherits an omitted `motion`\n * like `theme`.\n * @default the enclosing WaveProvider's motion, else 'default'\n */\n motion?: MotionPreference;\n /**\n * Design token overrides for the subtree: CSS values by token name, the CSS variable in camelCase\n * without its `--wave-` prefix (`brand80` for `--wave-brand-80`, `primaryForeground` for\n * `--wave-primary-foreground`; a number is written as its string). `theme` still names one of\n * the three themes, and the overrides apply over it as inline custom properties on the root and\n * on every portal of the subtree (see `applyStylesToPortals`), so its dialogs, menus, popovers,\n * tooltips and toasts take them too. Fluent's `theme` prop with a partial theme is the\n * counterpart.\n *\n * A nested provider merges its overrides over the enclosing provider's, key by key: an omitted\n * `tokens` inherits them all, and a key whose value is `undefined` inherits that key. `null`\n * clears the inherited overrides of the tokens the theme declares (the semantic colours, shadows,\n * palette) and of the subtree's overlays; theme-independent tokens (the brand and grey ramps,\n * font families, stacking order, motion durations and curves) of an enclosing provider still\n * reach the subtree's own elements through the CSS cascade, and the theme's colours built on the\n * brand ramp follow them there. A nested provider that sets another `theme` inherits the\n * overrides too, so set an override of a semantic colour (`primary`) in the provider that sets\n * the theme it was chosen for. A whole brand ramp is better made with `createBrandTokens` than\n * written by hand.\n * A key that is not a camelCase identifier is skipped with a development warning, and so is a\n * value unless it is a finite number or CSS text without `;`, `{`, `}` or `url(` whose quotes,\n * parentheses, square brackets and comments are balanced, with no `!` or backslash outside\n * strings and comments; a well-formed name is not checked against the list of tokens\n * (TypeScript refuses a misspelled one). In the server-rendered `style` attribute a refused\n * character could add a declaration of its own, and an open string, comment or bracket, or an\n * escape that spells `url(` or takes the semicolon after the value, would swallow the\n * declarations after it (though not in the client), while `!important` would be a priority the\n * client refuses. A value is trimmed as React's server renderer trims it, so the values that pass\n * are written and read alike by the server and the client.\n *\n * The overrides are rendered on the server too. Under a Content Security Policy that blocks\n * inline style attributes (`style-src` without `'unsafe-inline'`), the server-rendered overrides\n * do not apply and hydration does not apply them again: ship them in a stylesheet there, and pass\n * no `tokens` (nor `getTokenStyle`), since the overlays and the subtrees rendered in the client\n * would still take them and leave the page half branded.\n *\n * @example\n * <WaveProvider tokens={{ fontFamily: 'Inter, sans-serif', zOverlay: 2000 }}>\n * @default the enclosing WaveProvider's overrides, else none\n */\n tokens?: WaveTokenOverrides | null;\n /**\n * Whether the portals of the subtree (dialogs, popovers, menus, tooltips, toasts) carry the\n * overrides of `tokens`. `false` leaves them out: an overlay then shows its theme's own values\n * for the tokens the theme declares, and keeps the surrounding cascade (that of the element it\n * renders into) for the theme-independent ones. The theme classes and the direction are set on\n * every portal either way, so an overlay of a dark provider is still dark. Fluent's\n * `applyStylesToPortals` is the counterpart; unlike Fluent's, a nested provider inherits an\n * omitted value, like `theme`.\n * @default the enclosing WaveProvider's value, else true\n */\n applyStylesToPortals?: boolean;\n /** Content rendered inside the themed container. */\n children: React.ReactNode;\n /** Ref to the themed root `<div>`. */\n ref?: React.Ref<HTMLDivElement>;\n}\n\n/** Value provided by {@link WaveProvider} and returned by {@link useWaveTheme}. */\nexport interface WaveContextValue {\n /** The active theme. */\n theme: WaveTheme;\n /** The active text direction. */\n dir: WaveDir;\n /**\n * Theme classes of the nearest provider (see {@link getThemeClassName}); portals put them on\n * their wrapper so overlays inherit the theme. `''` outside a provider (the page's own theme).\n */\n themeClassName: string;\n /** Element portaled overlays render into; `null` means `document.body`. */\n portalContainer: HTMLElement | null;\n /** The merged input defaults of the providers above (`{}` outside a provider). */\n inputDefaults: InputDefaults;\n /** The motion preference of the providers above (`'default'` outside a provider). */\n motion: MotionPreference;\n /**\n * The merged token overrides of the providers above (`{}` outside a provider), as given: an\n * entry skipped for a malformed key or value stays in it, and only `tokenStyle` leaves it out.\n */\n tokens: WaveTokenOverrides;\n /**\n * The inline style that applies `tokens`, as the provider's root and, unless\n * `applyStylesToPortals` is `false`, its portals carry it; `undefined` when there is nothing to\n * set (also outside a provider).\n */\n tokenStyle: TokenStyle | undefined;\n /** Whether portals carry `tokenStyle` (`true` outside a provider). */\n applyStylesToPortals: boolean;\n /** `false` when no {@link WaveProvider} is above the caller (the defaults are returned). */\n hasProvider: boolean;\n}\n\nconst DEFAULT_CONTEXT: WaveContextValue = {\n theme: 'light',\n dir: 'ltr',\n themeClassName: '',\n portalContainer: null,\n inputDefaults: {},\n motion: 'default',\n tokens: {},\n tokenStyle: undefined,\n applyStylesToPortals: true,\n hasProvider: false,\n};\n\nconst WaveContext = React.createContext<WaveContextValue>(DEFAULT_CONTEXT);\n\n/** Whether two override maps have the same keys in the same order, with the same values. */\nfunction sameTokens(a: WaveTokenOverrides, b: WaveTokenOverrides): boolean {\n const keys = Object.keys(a) as (keyof WaveTokenOverrides)[];\n const others = Object.keys(b);\n return (\n keys.length === others.length &&\n keys.every((key, index) => key === others[index] && Object.is(a[key], b[key]))\n );\n}\n\n/**\n * Reads the theme, direction, theme classes, portal container and token overrides of the nearest\n * {@link WaveProvider}. Outside a provider it returns light/ltr defaults with `hasProvider: false`.\n *\n * @returns The {@link WaveContextValue} of the nearest provider.\n */\nexport function useWaveTheme(): WaveContextValue {\n return React.useContext(WaveContext);\n}\n\n/**\n * Applies Wave theming to an application or subtree.\n *\n * - Renders a `<div class=\"wave-root wave-<theme>\">` that declares the theme's tokens and paints\n * the themed background, text colour and font (`bg-background text-foreground font-wave\n * text-body-1`; a `className` passed by you wins). The precompiled `./styles` entry scopes its base\n * styles and native-element reset to this root, so it is required there.\n * - Sets `dir` and `data-wave-theme` on the root. An unknown `theme` value (from untyped code)\n * falls back to `'light'` — classes, `data-wave-theme` and the context value — and warns once in\n * development.\n * - Provides theme, direction, theme classes and the portal container through context, so\n * portaled overlays render with the same theme and direction.\n * - Nests: a provider inherits every prop it omits (`theme`, `dir`, `portalContainer`) from the\n * enclosing provider, so `<WaveProvider theme=\"light\">` inside an RTL app is a light panel that\n * stays right-to-left and keeps the app's portal container. At the top level the defaults are\n * `'light'`, `'ltr'` and `document.body`.\n * - `inputDefaults` nests differently: a nested provider's defined keys override the enclosing\n * provider's, and an omitted key is inherited, so setting only `appearance` in a nested provider\n * keeps the enclosing provider's `size`.\n * - `motion=\"none\"` turns the default motion of every animated part below it off (its motion\n * slots, Carousel's slide, TabList's sliding line, the reveal of Nav item actions; a part's own\n * `motion` still wins), while transitions without a slot keep running (Accordion's and Tree's\n * chevron turns, colour changes); a nested provider inherits an omitted `motion` like `theme`.\n * - `tokens` overrides design tokens in the subtree with inline custom properties on the root\n * (rendered on the server too) and on every portal of the subtree, unless\n * `applyStylesToPortals` is `false`. A nested provider merges its overrides over the enclosing\n * provider's key by key, also when it sets another `theme`; `tokens={null}` clears them for the\n * tokens the theme declares and for the overlays, while the theme-independent ones of an\n * enclosing provider still reach the subtree through the CSS cascade. An override whose key is\n * not a camelCase identifier is skipped with a development warning, and so is a value unless it\n * is a finite number or CSS text without `;`, `{`, `}` or `url(` with balanced quotes,\n * parentheses, square brackets and comments and no `!` or backslash outside strings and\n * comments; a value is trimmed as React's server renderer trims it, so the server and the client\n * write the same overrides. Without overrides the root renders no `style` attribute unless you\n * pass `style`.\n */\nexport const WaveProvider = ({\n theme: themeProp,\n dir: dirProp,\n portalContainer: portalContainerProp,\n inputDefaults: inputDefaultsProp,\n motion: motionProp,\n tokens: tokensProp,\n applyStylesToPortals: applyStylesToPortalsProp,\n children,\n className,\n style,\n ref,\n ...rest\n}: WaveProviderProps) => {\n // Omitted props come from the enclosing provider; outside one, DEFAULT_CONTEXT supplies the\n // top-level defaults. An explicit `portalContainer={null}` means document.body, so only\n // `undefined` inherits.\n const parent = React.useContext(WaveContext);\n const theme = themeProp ?? parent.theme;\n const dir = dirProp ?? parent.dir;\n const motion = motionProp ?? parent.motion;\n const applyStylesToPortals = applyStylesToPortalsProp ?? parent.applyStylesToPortals;\n const portalContainer =\n portalContainerProp === undefined ? parent.portalContainer : portalContainerProp;\n\n // The own overrides merged over the enclosing provider's. The merged map keeps its identity while\n // its entries are equal (a previous-value state, C-HOOKS), so an inline `tokens={{ … }}` neither\n // re-renders the consumers nor rebuilds the style.\n const mergedTokens = mergeTokenOverrides(parent.tokens, tokensProp);\n const [tokens, setTokens] = React.useState(mergedTokens);\n if (!sameTokens(tokens, mergedTokens)) setTokens(mergedTokens);\n // Collected without warnings during render; the skipped entries are reported from an effect.\n const { style: tokenStyle, invalid } = React.useMemo(() => collectTokenStyle(tokens), [tokens]);\n\n React.useEffect(() => {\n // The mode is read here as `lib/dev` reads it, written out so a bundler replaces it: a\n // production bundle then drops the warnings and their text. Without a bundler and without\n // `process` (an unbundled import in a browser), the read throws and the warnings are skipped.\n try {\n if (process.env.NODE_ENV !== 'production') {\n for (const { key, reason } of invalid) {\n warnOnce(\n `WaveProvider:tokens:${reason}:${key}`,\n `WaveProvider: \\`tokens\\` skips \\`${key}\\`: ` +\n (reason === 'name'\n ? 'not a token name (the CSS variable in camelCase without `--wave-`, such as `primaryForeground`).'\n : 'its value must be a finite number, or CSS text with balanced quotes, brackets and comments, without `;`, `{`, `}` or `url(`, and without `!` or a backslash outside strings and comments.'),\n );\n }\n }\n } catch {\n // No `process`: see above.\n }\n }, [invalid]);\n\n // Defined keys override the enclosing provider's; the merged object keeps its identity while the\n // two values are equal, so an inline `inputDefaults={{ … }}` does not re-render every input.\n const size = inputDefaultsProp?.size ?? parent.inputDefaults.size;\n const appearance = inputDefaultsProp?.appearance ?? parent.inputDefaults.appearance;\n const inputDefaults = React.useMemo<InputDefaults>(() => {\n const merged: InputDefaults = {};\n if (size !== undefined) merged.size = size;\n if (appearance !== undefined) merged.appearance = appearance;\n return merged;\n }, [size, appearance]);\n\n // An unknown value (untyped callers) renders, and is reported, as the light theme.\n const resolvedTheme: WaveTheme = isWaveTheme(theme) ? theme : 'light';\n const themeClassName = getThemeClassName(resolvedTheme);\n\n React.useEffect(() => {\n if (!isWaveTheme(theme)) {\n warnOnce(\n `WaveProvider:theme:${String(theme)}`,\n `WaveProvider: unknown theme \"${String(theme)}\". Valid themes: ${WAVE_THEMES.join(', ')}. Using \"light\".`,\n );\n }\n }, [theme]);\n\n const value = React.useMemo<WaveContextValue>(\n () => ({\n theme: resolvedTheme,\n dir,\n themeClassName,\n portalContainer,\n inputDefaults,\n motion,\n tokens,\n tokenStyle,\n applyStylesToPortals,\n hasProvider: true,\n }),\n [\n resolvedTheme,\n dir,\n themeClassName,\n portalContainer,\n inputDefaults,\n motion,\n tokens,\n tokenStyle,\n applyStylesToPortals,\n ],\n );\n\n return (\n <WaveContext.Provider value={value}>\n <div\n {...rest}\n // The merged overrides (a nested root re-declares what its theme class would shadow), then\n // the consumer's `style`, which wins. Without overrides the consumer's `style` as given, so\n // the root has no style attribute unless the consumer passes one.\n style={tokenStyle ? { ...tokenStyle, ...style } : style}\n ref={ref}\n dir={dir}\n data-wave-theme={resolvedTheme}\n className={cn(\n waveProviderClassNames.root,\n themeClassName,\n 'bg-background text-foreground font-wave text-body-1',\n className,\n )}\n >\n {children}\n </div>\n </WaveContext.Provider>\n );\n};\n\nWaveProvider.displayName = 'WaveProvider';\n"],"mappings":"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"}
@@ -31,7 +31,9 @@ var p = ({ theme: f, dir: p, portalContainer: m, inputDefaults: h, motion: g, to
31
31
  d(j, A) || M(A);
32
32
  let { style: N, invalid: P } = c.useMemo(() => r(j), [j]);
33
33
  c.useEffect(() => {
34
- for (let { key: e, reason: n } of P) t(`WaveProvider:tokens:${n}:${e}`, `WaveProvider: \`tokens\` skips \`${e}\`: ` + (n === "name" ? "not a token name (the CSS variable in camelCase without `--wave-`, such as `primaryForeground`)." : "its value must be a finite number or a string without `;`, `{` or `}`."));
34
+ try {
35
+ if (process.env.NODE_ENV !== "production") for (let { key: e, reason: n } of P) t(`WaveProvider:tokens:${n}:${e}`, `WaveProvider: \`tokens\` skips \`${e}\`: ` + (n === "name" ? "not a token name (the CSS variable in camelCase without `--wave-`, such as `primaryForeground`)." : "its value must be a finite number, or CSS text with balanced quotes, brackets and comments, without `;`, `{`, `}` or `url(`, and without `!` or a backslash outside strings and comments."));
36
+ } catch {}
35
37
  }, [P]);
36
38
  let F = h?.size ?? w.inputDefaults.size, I = h?.appearance ?? w.inputDefaults.appearance, L = c.useMemo(() => {
37
39
  let e = {};
@@ -1 +1 @@
1
- {"version":3,"file":"WaveProvider.mjs","names":["React","WaveContext","sameTokens","keys","a","others","b","key","index","useWaveTheme","WaveProvider","themeProp","dirProp","portalContainerProp","inputDefaultsProp","motionProp","tokensProp","applyStylesToPortalsProp","rest","parent","theme","dir","motion","applyStylesToPortals","portalContainer","mergedTokens","mergeTokenOverrides","tokens","setTokens","tokenStyle","collectTokenStyle","invalid","reason","size","appearance","inputDefaults","merged","resolvedTheme","isWaveTheme","themeClassName","getThemeClassName","warnOnce","WAVE_THEMES","value","style","cn","waveProviderClassNames","className"],"sources":["../../../src/components/provider/WaveProvider.tsx"],"sourcesContent":["import * as React from 'react';\nimport { cn } from '../../lib/cn';\nimport { warnOnce } from '../../lib/dev';\nimport { waveProviderClassNames } from '../../lib/classNames';\nimport { getThemeClassName, isWaveTheme, WAVE_THEMES, type WaveTheme } from '../../lib/theme';\nimport {\n collectTokenStyle,\n mergeTokenOverrides,\n type TokenStyle,\n type WaveTokenOverrides,\n} from '../../lib/tokens';\nimport type { CoreSize, InputAppearance, MotionPreference } from '../../lib/types';\n\n// The theme helper lives in server-safe src/lib (no \"use client\"), so React Server Components can\n// call it; it stays exported from here too.\nexport { getThemeClassName } from '../../lib/theme';\nexport type { WaveTheme } from '../../lib/theme';\n\n/** Text direction for bidirectional layout support. */\nexport type WaveDir = 'ltr' | 'rtl';\n\n/** Default size and appearance of the text inputs and pickers in a subtree. */\nexport interface InputDefaults {\n /** Default `size` of Input, Textarea, Select, SearchBox, SpinButton and the pickers. */\n size?: CoreSize;\n /** Default `appearance` of the same controls. */\n appearance?: InputAppearance;\n}\n\n/** Props accepted by {@link WaveProvider}. */\nexport interface WaveProviderProps extends React.HTMLAttributes<HTMLDivElement> {\n /**\n * Visual theme applied to the subtree. Providers can be nested in any order: each root declares\n * its theme's tokens, so a light panel inside a dark app (and the reverse) renders correctly.\n * @default the enclosing WaveProvider's theme, else 'light'\n */\n theme?: WaveTheme;\n /**\n * Text direction for the subtree (also read by portaled overlays and keyboard navigation).\n * @default the enclosing WaveProvider's direction, else 'ltr'\n */\n dir?: WaveDir;\n /**\n * Element that portaled overlays (dialogs, popovers, menus, toasts) render into. `null` renders\n * them into `document.body`, also inside a provider that sets a container.\n * @default the enclosing WaveProvider's container, else document.body\n */\n portalContainer?: HTMLElement | null;\n /**\n * Default `size` and `appearance` of the text inputs and pickers in the subtree (Input,\n * Textarea, Select, SearchBox, SpinButton, Combobox, Dropdown, DatePicker, TimePicker,\n * TagPicker). Their own props and a Field's `size` win. A nested provider merges its keys over\n * the enclosing provider's; an omitted key is inherited.\n * @default the enclosing WaveProvider's defaults, else none (`medium`, `outline`)\n */\n inputDefaults?: InputDefaults;\n /**\n * Motion of the subtree. `'none'` makes every motion slot below it (a part's `motion`,\n * `backdropMotion`, `expandIconMotion` or `indeterminateMotion` left unset) behave as `null`,\n * which also stops the slide of TabList's selection line, and turns off Carousel's slide or fade\n * (a Carousel without its own `motion`) and the reveal of Nav item actions; a part's own value\n * still wins. Transitions that are no motion slot keep running: the turn of Accordion's and\n * Tree's chevrons, colour changes on hover and press. Fluent's `MotionBehaviourProvider` with `'skip'` is the counterpart, which\n * `@fluentui/react-components` does not export. A nested provider inherits an omitted `motion`\n * like `theme`.\n * @default the enclosing WaveProvider's motion, else 'default'\n */\n motion?: MotionPreference;\n /**\n * Design token overrides for the subtree: CSS values by token name, the CSS variable in camelCase\n * without its `--wave-` prefix (`brand80` for `--wave-brand-80`, `primaryForeground` for\n * `--wave-primary-foreground`; a number is written as its string). `theme` still names one of\n * the three themes, and the overrides apply over it as inline custom properties on the root and\n * on every portal of the subtree (see `applyStylesToPortals`), so its dialogs, menus, popovers,\n * tooltips and toasts take them too. Fluent's `theme` prop with a partial theme is the\n * counterpart.\n *\n * A nested provider merges its overrides over the enclosing provider's, key by key: an omitted\n * `tokens` inherits them all, and a key whose value is `undefined` inherits that key. `null`\n * clears the inherited overrides of the tokens the theme declares (the semantic colours, shadows,\n * palette) and of the subtree's overlays; theme-independent tokens (the brand and grey ramps,\n * font families, stacking order, motion durations and curves) of an enclosing provider still\n * reach the subtree's own elements through the CSS cascade, and the theme's colours built on the\n * brand ramp follow them there. A nested provider that sets another `theme` inherits the\n * overrides too, so set an override of a semantic colour (`primary`) in the provider that sets\n * the theme it was chosen for. A whole brand ramp is better made with `createBrandTokens` than\n * written by hand.\n * A key that is not a camelCase identifier, or a value that is neither a finite number nor a\n * string without `;`, `{` or `}` (such a value could add a declaration of its own to the\n * server-rendered `style` attribute), is skipped with a development warning; a well-formed name\n * is not checked against the list of tokens (TypeScript refuses a misspelled one).\n *\n * The overrides are rendered on the server too. Under a Content Security Policy that blocks\n * inline style attributes (`style-src` without `'unsafe-inline'`), the server-rendered overrides\n * do not apply and hydration does not apply them again: ship them in a stylesheet there, and pass\n * no `tokens` (nor `getTokenStyle`), since the overlays and the subtrees rendered in the client\n * would still take them and leave the page half branded.\n *\n * @example\n * <WaveProvider tokens={{ fontFamily: 'Inter, sans-serif', zOverlay: 2000 }}>\n * @default the enclosing WaveProvider's overrides, else none\n */\n tokens?: WaveTokenOverrides | null;\n /**\n * Whether the portals of the subtree (dialogs, popovers, menus, tooltips, toasts) carry the\n * overrides of `tokens`. `false` leaves them out: an overlay then shows its theme's own values\n * for the tokens the theme declares, and keeps the surrounding cascade (that of the element it\n * renders into) for the theme-independent ones. The theme classes and the direction are set on\n * every portal either way, so an overlay of a dark provider is still dark. Fluent's\n * `applyStylesToPortals` is the counterpart; unlike Fluent's, a nested provider inherits an\n * omitted value, like `theme`.\n * @default the enclosing WaveProvider's value, else true\n */\n applyStylesToPortals?: boolean;\n /** Content rendered inside the themed container. */\n children: React.ReactNode;\n /** Ref to the themed root `<div>`. */\n ref?: React.Ref<HTMLDivElement>;\n}\n\n/** Value provided by {@link WaveProvider} and returned by {@link useWaveTheme}. */\nexport interface WaveContextValue {\n /** The active theme. */\n theme: WaveTheme;\n /** The active text direction. */\n dir: WaveDir;\n /**\n * Theme classes of the nearest provider (see {@link getThemeClassName}); portals put them on\n * their wrapper so overlays inherit the theme. `''` outside a provider (the page's own theme).\n */\n themeClassName: string;\n /** Element portaled overlays render into; `null` means `document.body`. */\n portalContainer: HTMLElement | null;\n /** The merged input defaults of the providers above (`{}` outside a provider). */\n inputDefaults: InputDefaults;\n /** The motion preference of the providers above (`'default'` outside a provider). */\n motion: MotionPreference;\n /**\n * The merged token overrides of the providers above (`{}` outside a provider), as given: an\n * entry skipped for a malformed key or value stays in it, and only `tokenStyle` leaves it out.\n */\n tokens: WaveTokenOverrides;\n /**\n * The inline style that applies `tokens`, as the provider's root and, unless\n * `applyStylesToPortals` is `false`, its portals carry it; `undefined` when there is nothing to\n * set (also outside a provider).\n */\n tokenStyle: TokenStyle | undefined;\n /** Whether portals carry `tokenStyle` (`true` outside a provider). */\n applyStylesToPortals: boolean;\n /** `false` when no {@link WaveProvider} is above the caller (the defaults are returned). */\n hasProvider: boolean;\n}\n\nconst DEFAULT_CONTEXT: WaveContextValue = {\n theme: 'light',\n dir: 'ltr',\n themeClassName: '',\n portalContainer: null,\n inputDefaults: {},\n motion: 'default',\n tokens: {},\n tokenStyle: undefined,\n applyStylesToPortals: true,\n hasProvider: false,\n};\n\nconst WaveContext = React.createContext<WaveContextValue>(DEFAULT_CONTEXT);\n\n/** Whether two override maps have the same keys in the same order, with the same values. */\nfunction sameTokens(a: WaveTokenOverrides, b: WaveTokenOverrides): boolean {\n const keys = Object.keys(a) as (keyof WaveTokenOverrides)[];\n const others = Object.keys(b);\n return (\n keys.length === others.length &&\n keys.every((key, index) => key === others[index] && Object.is(a[key], b[key]))\n );\n}\n\n/**\n * Reads the theme, direction, theme classes, portal container and token overrides of the nearest\n * {@link WaveProvider}. Outside a provider it returns light/ltr defaults with `hasProvider: false`.\n *\n * @returns The {@link WaveContextValue} of the nearest provider.\n */\nexport function useWaveTheme(): WaveContextValue {\n return React.useContext(WaveContext);\n}\n\n/**\n * Applies Wave theming to an application or subtree.\n *\n * - Renders a `<div class=\"wave-root wave-<theme>\">` that declares the theme's tokens and paints\n * the themed background, text colour and font (`bg-background text-foreground font-wave\n * text-body-1`; a `className` passed by you wins). The precompiled `./styles` entry scopes its base\n * styles and native-element reset to this root, so it is required there.\n * - Sets `dir` and `data-wave-theme` on the root. An unknown `theme` value (from untyped code)\n * falls back to `'light'` — classes, `data-wave-theme` and the context value — and warns once in\n * development.\n * - Provides theme, direction, theme classes and the portal container through context, so\n * portaled overlays render with the same theme and direction.\n * - Nests: a provider inherits every prop it omits (`theme`, `dir`, `portalContainer`) from the\n * enclosing provider, so `<WaveProvider theme=\"light\">` inside an RTL app is a light panel that\n * stays right-to-left and keeps the app's portal container. At the top level the defaults are\n * `'light'`, `'ltr'` and `document.body`.\n * - `inputDefaults` nests differently: a nested provider's defined keys override the enclosing\n * provider's, and an omitted key is inherited, so setting only `appearance` in a nested provider\n * keeps the enclosing provider's `size`.\n * - `motion=\"none\"` turns the default motion of every animated part below it off (its motion\n * slots, Carousel's slide, TabList's sliding line, the reveal of Nav item actions; a part's own\n * `motion` still wins), while transitions without a slot keep running (Accordion's and Tree's\n * chevron turns, colour changes); a nested provider inherits an omitted `motion` like `theme`.\n * - `tokens` overrides design tokens in the subtree with inline custom properties on the root\n * (rendered on the server too) and on every portal of the subtree, unless\n * `applyStylesToPortals` is `false`. A nested provider merges its overrides over the enclosing\n * provider's key by key, also when it sets another `theme`; `tokens={null}` clears them for the\n * tokens the theme declares and for the overlays, while the theme-independent ones of an\n * enclosing provider still reach the subtree through the CSS cascade. An override whose key is\n * not a camelCase identifier, or whose value is neither a finite number nor a string without\n * `;`, `{` or `}`, is skipped with a development warning. Without overrides the root renders no\n * `style` attribute unless you pass `style`.\n */\nexport const WaveProvider = ({\n theme: themeProp,\n dir: dirProp,\n portalContainer: portalContainerProp,\n inputDefaults: inputDefaultsProp,\n motion: motionProp,\n tokens: tokensProp,\n applyStylesToPortals: applyStylesToPortalsProp,\n children,\n className,\n style,\n ref,\n ...rest\n}: WaveProviderProps) => {\n // Omitted props come from the enclosing provider; outside one, DEFAULT_CONTEXT supplies the\n // top-level defaults. An explicit `portalContainer={null}` means document.body, so only\n // `undefined` inherits.\n const parent = React.useContext(WaveContext);\n const theme = themeProp ?? parent.theme;\n const dir = dirProp ?? parent.dir;\n const motion = motionProp ?? parent.motion;\n const applyStylesToPortals = applyStylesToPortalsProp ?? parent.applyStylesToPortals;\n const portalContainer =\n portalContainerProp === undefined ? parent.portalContainer : portalContainerProp;\n\n // The own overrides merged over the enclosing provider's. The merged map keeps its identity while\n // its entries are equal (a previous-value state, C-HOOKS), so an inline `tokens={{ … }}` neither\n // re-renders the consumers nor rebuilds the style.\n const mergedTokens = mergeTokenOverrides(parent.tokens, tokensProp);\n const [tokens, setTokens] = React.useState(mergedTokens);\n if (!sameTokens(tokens, mergedTokens)) setTokens(mergedTokens);\n // Collected without warnings during render; the skipped entries are reported from an effect.\n const { style: tokenStyle, invalid } = React.useMemo(() => collectTokenStyle(tokens), [tokens]);\n\n React.useEffect(() => {\n for (const { key, reason } of invalid) {\n warnOnce(\n `WaveProvider:tokens:${reason}:${key}`,\n `WaveProvider: \\`tokens\\` skips \\`${key}\\`: ` +\n (reason === 'name'\n ? 'not a token name (the CSS variable in camelCase without `--wave-`, such as `primaryForeground`).'\n : 'its value must be a finite number or a string without `;`, `{` or `}`.'),\n );\n }\n }, [invalid]);\n\n // Defined keys override the enclosing provider's; the merged object keeps its identity while the\n // two values are equal, so an inline `inputDefaults={{ … }}` does not re-render every input.\n const size = inputDefaultsProp?.size ?? parent.inputDefaults.size;\n const appearance = inputDefaultsProp?.appearance ?? parent.inputDefaults.appearance;\n const inputDefaults = React.useMemo<InputDefaults>(() => {\n const merged: InputDefaults = {};\n if (size !== undefined) merged.size = size;\n if (appearance !== undefined) merged.appearance = appearance;\n return merged;\n }, [size, appearance]);\n\n // An unknown value (untyped callers) renders, and is reported, as the light theme.\n const resolvedTheme: WaveTheme = isWaveTheme(theme) ? theme : 'light';\n const themeClassName = getThemeClassName(resolvedTheme);\n\n React.useEffect(() => {\n if (!isWaveTheme(theme)) {\n warnOnce(\n `WaveProvider:theme:${String(theme)}`,\n `WaveProvider: unknown theme \"${String(theme)}\". Valid themes: ${WAVE_THEMES.join(', ')}. Using \"light\".`,\n );\n }\n }, [theme]);\n\n const value = React.useMemo<WaveContextValue>(\n () => ({\n theme: resolvedTheme,\n dir,\n themeClassName,\n portalContainer,\n inputDefaults,\n motion,\n tokens,\n tokenStyle,\n applyStylesToPortals,\n hasProvider: true,\n }),\n [\n resolvedTheme,\n dir,\n themeClassName,\n portalContainer,\n inputDefaults,\n motion,\n tokens,\n tokenStyle,\n applyStylesToPortals,\n ],\n );\n\n return (\n <WaveContext.Provider value={value}>\n <div\n {...rest}\n // The merged overrides (a nested root re-declares what its theme class would shadow), then\n // the consumer's `style`, which wins. Without overrides the consumer's `style` as given, so\n // the root has no style attribute unless the consumer passes one.\n style={tokenStyle ? { ...tokenStyle, ...style } : style}\n ref={ref}\n dir={dir}\n data-wave-theme={resolvedTheme}\n className={cn(\n waveProviderClassNames.root,\n themeClassName,\n 'bg-background text-foreground font-wave text-body-1',\n className,\n )}\n >\n {children}\n </div>\n </WaveContext.Provider>\n );\n};\n\nWaveProvider.displayName = 'WaveProvider';\n"],"mappings":";;;;;;;;;AAuKA,IAAMC,IAAcD,EAAM,cAAgC;CAZxD,OAAO;CACP,KAAK;CACL,gBAAgB;CAChB,iBAAiB;CACjB,eAAe,CAAC;CAChB,QAAQ;CACR,QAAQ,CAAC;CACT,YAAY,KAAA;CACZ,sBAAsB;CACtB,aAAa;AAG2C,CAAe;AAGzE,SAASE,EAAW,GAAuB,GAAgC;CACzE,IAAMC,IAAO,OAAO,KAAKC,CAAC,GACpBC,IAAS,OAAO,KAAKC,CAAC;CAC5B,OACEH,EAAK,WAAWE,EAAO,UACvBF,EAAK,OAAO,GAAK,MAAUI,MAAQF,EAAOG,MAAU,OAAO,GAAGJ,EAAEG,IAAMD,EAAEC,EAAI,CAAC;AAEjF;AAQA,SAAgBE,IAAiC;CAC/C,OAAOT,EAAM,WAAWC,CAAW;AACrC;AAmCA,IAAaS,KAAgB,EAC3B,OAAOC,GACP,KAAKC,GACL,iBAAiBC,GACjB,eAAeC,GACf,QAAQC,GACR,QAAQC,GACR,sBAAsBC,GACtB,aACA,cACA,UACA,QACA,GAAGC,QACoB;CAIvB,IAAMC,IAASnB,EAAM,WAAWC,CAAW,GACrCmB,IAAQT,KAAaQ,EAAO,OAC5BE,IAAMT,KAAWO,EAAO,KACxBG,IAASP,KAAcI,EAAO,QAC9BI,IAAuBN,KAA4BE,EAAO,sBAC1DK,IACJX,MAAwB,KAAA,IAAYM,EAAO,kBAAkBN,GAKzDY,IAAeC,EAAoBP,EAAO,QAAQH,CAAU,GAC5D,CAACW,GAAQC,KAAa5B,EAAM,SAASyB,CAAY;CACvD,AAAKvB,EAAWyB,GAAQF,CAAY,KAAGG,EAAUH,CAAY;CAE7D,IAAM,EAAE,OAAOI,GAAY,eAAY7B,EAAM,cAAc8B,EAAkBH,CAAM,GAAG,CAACA,CAAM,CAAC;CAE9F,EAAM,gBAAgB;EACpB,KAAK,IAAM,EAAE,QAAK,eAAYI,GAC5B,EACE,uBAAuBC,EAAO,GAAGzB,KACjC,oCAAoCA,EAAI,SACrCyB,MAAW,SACR,qGACA,yEACR;CAEJ,GAAG,CAACD,CAAO,CAAC;CAIZ,IAAME,IAAOnB,GAAmB,QAAQK,EAAO,cAAc,MACvDe,IAAapB,GAAmB,cAAcK,EAAO,cAAc,YACnEgB,IAAgBnC,EAAM,cAA6B;EACvD,IAAMoC,IAAwB,CAAC;EAG/B,OAFIH,MAAS,KAAA,MAAW,EAAO,OAAOA,IAClCC,MAAe,KAAA,MAAW,EAAO,aAAaA,IAC3CE;CACT,GAAG,CAACH,GAAMC,CAAU,CAAC,GAGfG,IAA2BC,EAAYlB,CAAK,IAAIA,IAAQ,SACxDmB,IAAiBC,EAAkBH,CAAa;CAEtD,EAAM,gBAAgB;EACpB,AAAKC,EAAYlB,CAAK,KACpBqB,EACE,sBAAsB,OAAOrB,CAAK,KAClC,gCAAgC,OAAOA,CAAK,EAAE,mBAAmBsB,EAAY,KAAK,IAAI,EAAE,iBAC1F;CAEJ,GAAG,CAACtB,CAAK,CAAC;CAEV,IAAMuB,IAAQ3C,EAAM,eACX;EACL,OAAOqC;EACP;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,aAAa;CACf,IACA;EACEA;EACAhB;EACAkB;EACAf;EACAW;EACAb;EACAK;EACAE;EACAN;CACF,CACF;CAEA,OACE,kBAACtB,EAAY,UAAb;EAA6B;EAC3B,UAAA,kBAAC,OAAD;GACE,GAAIiB;GAIJ,OAAOW,IAAa;IAAE,GAAGA;IAAY,GAAGe;GAAM,IAAIA;GAC7C;GACA;GACL,mBAAiBP;GACjB,WAAWQ,EACTC,EAAuB,MACvBP,GACA,uDACAQ,CACF;GAEC;EACE,CAAA;CACe,CAAA;AAE1B;AAEA,EAAa,cAAc"}
1
+ {"version":3,"file":"WaveProvider.mjs","names":["React","WaveContext","sameTokens","keys","a","others","b","key","index","useWaveTheme","WaveProvider","themeProp","dirProp","portalContainerProp","inputDefaultsProp","motionProp","tokensProp","applyStylesToPortalsProp","rest","parent","theme","dir","motion","applyStylesToPortals","portalContainer","mergedTokens","mergeTokenOverrides","tokens","setTokens","tokenStyle","collectTokenStyle","invalid","reason","size","appearance","inputDefaults","merged","resolvedTheme","isWaveTheme","themeClassName","getThemeClassName","warnOnce","WAVE_THEMES","value","style","cn","waveProviderClassNames","className"],"sources":["../../../src/components/provider/WaveProvider.tsx"],"sourcesContent":["import * as React from 'react';\nimport { cn } from '../../lib/cn';\nimport { warnOnce } from '../../lib/dev';\nimport { waveProviderClassNames } from '../../lib/classNames';\nimport { getThemeClassName, isWaveTheme, WAVE_THEMES, type WaveTheme } from '../../lib/theme';\nimport {\n collectTokenStyle,\n mergeTokenOverrides,\n type TokenStyle,\n type WaveTokenOverrides,\n} from '../../lib/tokens';\nimport type { CoreSize, InputAppearance, MotionPreference } from '../../lib/types';\n\n// The theme helper lives in server-safe src/lib (no \"use client\"), so React Server Components can\n// call it; it stays exported from here too.\nexport { getThemeClassName } from '../../lib/theme';\nexport type { WaveTheme } from '../../lib/theme';\n\n/** Text direction for bidirectional layout support. */\nexport type WaveDir = 'ltr' | 'rtl';\n\n/** Default size and appearance of the text inputs and pickers in a subtree. */\nexport interface InputDefaults {\n /** Default `size` of Input, Textarea, Select, SearchBox, SpinButton and the pickers. */\n size?: CoreSize;\n /** Default `appearance` of the same controls. */\n appearance?: InputAppearance;\n}\n\n/** Props accepted by {@link WaveProvider}. */\nexport interface WaveProviderProps extends React.HTMLAttributes<HTMLDivElement> {\n /**\n * Visual theme applied to the subtree. Providers can be nested in any order: each root declares\n * its theme's tokens, so a light panel inside a dark app (and the reverse) renders correctly.\n * @default the enclosing WaveProvider's theme, else 'light'\n */\n theme?: WaveTheme;\n /**\n * Text direction for the subtree (also read by portaled overlays and keyboard navigation).\n * @default the enclosing WaveProvider's direction, else 'ltr'\n */\n dir?: WaveDir;\n /**\n * Element that portaled overlays (dialogs, popovers, menus, toasts) render into. `null` renders\n * them into `document.body`, also inside a provider that sets a container.\n * @default the enclosing WaveProvider's container, else document.body\n */\n portalContainer?: HTMLElement | null;\n /**\n * Default `size` and `appearance` of the text inputs and pickers in the subtree (Input,\n * Textarea, Select, SearchBox, SpinButton, Combobox, Dropdown, DatePicker, TimePicker,\n * TagPicker). Their own props and a Field's `size` win. A nested provider merges its keys over\n * the enclosing provider's; an omitted key is inherited.\n * @default the enclosing WaveProvider's defaults, else none (`medium`, `outline`)\n */\n inputDefaults?: InputDefaults;\n /**\n * Motion of the subtree. `'none'` makes every motion slot below it (a part's `motion`,\n * `backdropMotion`, `expandIconMotion` or `indeterminateMotion` left unset) behave as `null`,\n * which also stops the slide of TabList's selection line, and turns off Carousel's slide or fade\n * (a Carousel without its own `motion`) and the reveal of Nav item actions; a part's own value\n * still wins. Transitions that are no motion slot keep running: the turn of Accordion's and\n * Tree's chevrons, colour changes on hover and press. Fluent's `MotionBehaviourProvider` with `'skip'` is the counterpart, which\n * `@fluentui/react-components` does not export. A nested provider inherits an omitted `motion`\n * like `theme`.\n * @default the enclosing WaveProvider's motion, else 'default'\n */\n motion?: MotionPreference;\n /**\n * Design token overrides for the subtree: CSS values by token name, the CSS variable in camelCase\n * without its `--wave-` prefix (`brand80` for `--wave-brand-80`, `primaryForeground` for\n * `--wave-primary-foreground`; a number is written as its string). `theme` still names one of\n * the three themes, and the overrides apply over it as inline custom properties on the root and\n * on every portal of the subtree (see `applyStylesToPortals`), so its dialogs, menus, popovers,\n * tooltips and toasts take them too. Fluent's `theme` prop with a partial theme is the\n * counterpart.\n *\n * A nested provider merges its overrides over the enclosing provider's, key by key: an omitted\n * `tokens` inherits them all, and a key whose value is `undefined` inherits that key. `null`\n * clears the inherited overrides of the tokens the theme declares (the semantic colours, shadows,\n * palette) and of the subtree's overlays; theme-independent tokens (the brand and grey ramps,\n * font families, stacking order, motion durations and curves) of an enclosing provider still\n * reach the subtree's own elements through the CSS cascade, and the theme's colours built on the\n * brand ramp follow them there. A nested provider that sets another `theme` inherits the\n * overrides too, so set an override of a semantic colour (`primary`) in the provider that sets\n * the theme it was chosen for. A whole brand ramp is better made with `createBrandTokens` than\n * written by hand.\n * A key that is not a camelCase identifier is skipped with a development warning, and so is a\n * value unless it is a finite number or CSS text without `;`, `{`, `}` or `url(` whose quotes,\n * parentheses, square brackets and comments are balanced, with no `!` or backslash outside\n * strings and comments; a well-formed name is not checked against the list of tokens\n * (TypeScript refuses a misspelled one). In the server-rendered `style` attribute a refused\n * character could add a declaration of its own, and an open string, comment or bracket, or an\n * escape that spells `url(` or takes the semicolon after the value, would swallow the\n * declarations after it (though not in the client), while `!important` would be a priority the\n * client refuses. A value is trimmed as React's server renderer trims it, so the values that pass\n * are written and read alike by the server and the client.\n *\n * The overrides are rendered on the server too. Under a Content Security Policy that blocks\n * inline style attributes (`style-src` without `'unsafe-inline'`), the server-rendered overrides\n * do not apply and hydration does not apply them again: ship them in a stylesheet there, and pass\n * no `tokens` (nor `getTokenStyle`), since the overlays and the subtrees rendered in the client\n * would still take them and leave the page half branded.\n *\n * @example\n * <WaveProvider tokens={{ fontFamily: 'Inter, sans-serif', zOverlay: 2000 }}>\n * @default the enclosing WaveProvider's overrides, else none\n */\n tokens?: WaveTokenOverrides | null;\n /**\n * Whether the portals of the subtree (dialogs, popovers, menus, tooltips, toasts) carry the\n * overrides of `tokens`. `false` leaves them out: an overlay then shows its theme's own values\n * for the tokens the theme declares, and keeps the surrounding cascade (that of the element it\n * renders into) for the theme-independent ones. The theme classes and the direction are set on\n * every portal either way, so an overlay of a dark provider is still dark. Fluent's\n * `applyStylesToPortals` is the counterpart; unlike Fluent's, a nested provider inherits an\n * omitted value, like `theme`.\n * @default the enclosing WaveProvider's value, else true\n */\n applyStylesToPortals?: boolean;\n /** Content rendered inside the themed container. */\n children: React.ReactNode;\n /** Ref to the themed root `<div>`. */\n ref?: React.Ref<HTMLDivElement>;\n}\n\n/** Value provided by {@link WaveProvider} and returned by {@link useWaveTheme}. */\nexport interface WaveContextValue {\n /** The active theme. */\n theme: WaveTheme;\n /** The active text direction. */\n dir: WaveDir;\n /**\n * Theme classes of the nearest provider (see {@link getThemeClassName}); portals put them on\n * their wrapper so overlays inherit the theme. `''` outside a provider (the page's own theme).\n */\n themeClassName: string;\n /** Element portaled overlays render into; `null` means `document.body`. */\n portalContainer: HTMLElement | null;\n /** The merged input defaults of the providers above (`{}` outside a provider). */\n inputDefaults: InputDefaults;\n /** The motion preference of the providers above (`'default'` outside a provider). */\n motion: MotionPreference;\n /**\n * The merged token overrides of the providers above (`{}` outside a provider), as given: an\n * entry skipped for a malformed key or value stays in it, and only `tokenStyle` leaves it out.\n */\n tokens: WaveTokenOverrides;\n /**\n * The inline style that applies `tokens`, as the provider's root and, unless\n * `applyStylesToPortals` is `false`, its portals carry it; `undefined` when there is nothing to\n * set (also outside a provider).\n */\n tokenStyle: TokenStyle | undefined;\n /** Whether portals carry `tokenStyle` (`true` outside a provider). */\n applyStylesToPortals: boolean;\n /** `false` when no {@link WaveProvider} is above the caller (the defaults are returned). */\n hasProvider: boolean;\n}\n\nconst DEFAULT_CONTEXT: WaveContextValue = {\n theme: 'light',\n dir: 'ltr',\n themeClassName: '',\n portalContainer: null,\n inputDefaults: {},\n motion: 'default',\n tokens: {},\n tokenStyle: undefined,\n applyStylesToPortals: true,\n hasProvider: false,\n};\n\nconst WaveContext = React.createContext<WaveContextValue>(DEFAULT_CONTEXT);\n\n/** Whether two override maps have the same keys in the same order, with the same values. */\nfunction sameTokens(a: WaveTokenOverrides, b: WaveTokenOverrides): boolean {\n const keys = Object.keys(a) as (keyof WaveTokenOverrides)[];\n const others = Object.keys(b);\n return (\n keys.length === others.length &&\n keys.every((key, index) => key === others[index] && Object.is(a[key], b[key]))\n );\n}\n\n/**\n * Reads the theme, direction, theme classes, portal container and token overrides of the nearest\n * {@link WaveProvider}. Outside a provider it returns light/ltr defaults with `hasProvider: false`.\n *\n * @returns The {@link WaveContextValue} of the nearest provider.\n */\nexport function useWaveTheme(): WaveContextValue {\n return React.useContext(WaveContext);\n}\n\n/**\n * Applies Wave theming to an application or subtree.\n *\n * - Renders a `<div class=\"wave-root wave-<theme>\">` that declares the theme's tokens and paints\n * the themed background, text colour and font (`bg-background text-foreground font-wave\n * text-body-1`; a `className` passed by you wins). The precompiled `./styles` entry scopes its base\n * styles and native-element reset to this root, so it is required there.\n * - Sets `dir` and `data-wave-theme` on the root. An unknown `theme` value (from untyped code)\n * falls back to `'light'` — classes, `data-wave-theme` and the context value — and warns once in\n * development.\n * - Provides theme, direction, theme classes and the portal container through context, so\n * portaled overlays render with the same theme and direction.\n * - Nests: a provider inherits every prop it omits (`theme`, `dir`, `portalContainer`) from the\n * enclosing provider, so `<WaveProvider theme=\"light\">` inside an RTL app is a light panel that\n * stays right-to-left and keeps the app's portal container. At the top level the defaults are\n * `'light'`, `'ltr'` and `document.body`.\n * - `inputDefaults` nests differently: a nested provider's defined keys override the enclosing\n * provider's, and an omitted key is inherited, so setting only `appearance` in a nested provider\n * keeps the enclosing provider's `size`.\n * - `motion=\"none\"` turns the default motion of every animated part below it off (its motion\n * slots, Carousel's slide, TabList's sliding line, the reveal of Nav item actions; a part's own\n * `motion` still wins), while transitions without a slot keep running (Accordion's and Tree's\n * chevron turns, colour changes); a nested provider inherits an omitted `motion` like `theme`.\n * - `tokens` overrides design tokens in the subtree with inline custom properties on the root\n * (rendered on the server too) and on every portal of the subtree, unless\n * `applyStylesToPortals` is `false`. A nested provider merges its overrides over the enclosing\n * provider's key by key, also when it sets another `theme`; `tokens={null}` clears them for the\n * tokens the theme declares and for the overlays, while the theme-independent ones of an\n * enclosing provider still reach the subtree through the CSS cascade. An override whose key is\n * not a camelCase identifier is skipped with a development warning, and so is a value unless it\n * is a finite number or CSS text without `;`, `{`, `}` or `url(` with balanced quotes,\n * parentheses, square brackets and comments and no `!` or backslash outside strings and\n * comments; a value is trimmed as React's server renderer trims it, so the server and the client\n * write the same overrides. Without overrides the root renders no `style` attribute unless you\n * pass `style`.\n */\nexport const WaveProvider = ({\n theme: themeProp,\n dir: dirProp,\n portalContainer: portalContainerProp,\n inputDefaults: inputDefaultsProp,\n motion: motionProp,\n tokens: tokensProp,\n applyStylesToPortals: applyStylesToPortalsProp,\n children,\n className,\n style,\n ref,\n ...rest\n}: WaveProviderProps) => {\n // Omitted props come from the enclosing provider; outside one, DEFAULT_CONTEXT supplies the\n // top-level defaults. An explicit `portalContainer={null}` means document.body, so only\n // `undefined` inherits.\n const parent = React.useContext(WaveContext);\n const theme = themeProp ?? parent.theme;\n const dir = dirProp ?? parent.dir;\n const motion = motionProp ?? parent.motion;\n const applyStylesToPortals = applyStylesToPortalsProp ?? parent.applyStylesToPortals;\n const portalContainer =\n portalContainerProp === undefined ? parent.portalContainer : portalContainerProp;\n\n // The own overrides merged over the enclosing provider's. The merged map keeps its identity while\n // its entries are equal (a previous-value state, C-HOOKS), so an inline `tokens={{ … }}` neither\n // re-renders the consumers nor rebuilds the style.\n const mergedTokens = mergeTokenOverrides(parent.tokens, tokensProp);\n const [tokens, setTokens] = React.useState(mergedTokens);\n if (!sameTokens(tokens, mergedTokens)) setTokens(mergedTokens);\n // Collected without warnings during render; the skipped entries are reported from an effect.\n const { style: tokenStyle, invalid } = React.useMemo(() => collectTokenStyle(tokens), [tokens]);\n\n React.useEffect(() => {\n // The mode is read here as `lib/dev` reads it, written out so a bundler replaces it: a\n // production bundle then drops the warnings and their text. Without a bundler and without\n // `process` (an unbundled import in a browser), the read throws and the warnings are skipped.\n try {\n if (process.env.NODE_ENV !== 'production') {\n for (const { key, reason } of invalid) {\n warnOnce(\n `WaveProvider:tokens:${reason}:${key}`,\n `WaveProvider: \\`tokens\\` skips \\`${key}\\`: ` +\n (reason === 'name'\n ? 'not a token name (the CSS variable in camelCase without `--wave-`, such as `primaryForeground`).'\n : 'its value must be a finite number, or CSS text with balanced quotes, brackets and comments, without `;`, `{`, `}` or `url(`, and without `!` or a backslash outside strings and comments.'),\n );\n }\n }\n } catch {\n // No `process`: see above.\n }\n }, [invalid]);\n\n // Defined keys override the enclosing provider's; the merged object keeps its identity while the\n // two values are equal, so an inline `inputDefaults={{ … }}` does not re-render every input.\n const size = inputDefaultsProp?.size ?? parent.inputDefaults.size;\n const appearance = inputDefaultsProp?.appearance ?? parent.inputDefaults.appearance;\n const inputDefaults = React.useMemo<InputDefaults>(() => {\n const merged: InputDefaults = {};\n if (size !== undefined) merged.size = size;\n if (appearance !== undefined) merged.appearance = appearance;\n return merged;\n }, [size, appearance]);\n\n // An unknown value (untyped callers) renders, and is reported, as the light theme.\n const resolvedTheme: WaveTheme = isWaveTheme(theme) ? theme : 'light';\n const themeClassName = getThemeClassName(resolvedTheme);\n\n React.useEffect(() => {\n if (!isWaveTheme(theme)) {\n warnOnce(\n `WaveProvider:theme:${String(theme)}`,\n `WaveProvider: unknown theme \"${String(theme)}\". Valid themes: ${WAVE_THEMES.join(', ')}. Using \"light\".`,\n );\n }\n }, [theme]);\n\n const value = React.useMemo<WaveContextValue>(\n () => ({\n theme: resolvedTheme,\n dir,\n themeClassName,\n portalContainer,\n inputDefaults,\n motion,\n tokens,\n tokenStyle,\n applyStylesToPortals,\n hasProvider: true,\n }),\n [\n resolvedTheme,\n dir,\n themeClassName,\n portalContainer,\n inputDefaults,\n motion,\n tokens,\n tokenStyle,\n applyStylesToPortals,\n ],\n );\n\n return (\n <WaveContext.Provider value={value}>\n <div\n {...rest}\n // The merged overrides (a nested root re-declares what its theme class would shadow), then\n // the consumer's `style`, which wins. Without overrides the consumer's `style` as given, so\n // the root has no style attribute unless the consumer passes one.\n style={tokenStyle ? { ...tokenStyle, ...style } : style}\n ref={ref}\n dir={dir}\n data-wave-theme={resolvedTheme}\n className={cn(\n waveProviderClassNames.root,\n themeClassName,\n 'bg-background text-foreground font-wave text-body-1',\n className,\n )}\n >\n {children}\n </div>\n </WaveContext.Provider>\n );\n};\n\nWaveProvider.displayName = 'WaveProvider';\n"],"mappings":"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"}
package/dist/index.d.cts CHANGED
@@ -6844,12 +6844,17 @@ export declare function getThemeClassName(theme: WaveTheme): string;
6844
6844
  * as a Server Component's document element. Server-safe.
6845
6845
  *
6846
6846
  * Returns `undefined` for `null`, `undefined` or a map with nothing to set. An `undefined` value is
6847
- * skipped, a number is written as its string, and the properties keep the order of the input. A
6848
- * key that is not a camelCase identifier, or a value that is neither a finite number nor a string
6849
- * without `;`, `{` or `}` (which could add a declaration of its own to a server-rendered `style`
6850
- * attribute), is skipped with a development warning (once per key). Names are not checked against
6851
- * the list of tokens: TypeScript refuses a misspelled one, and in JavaScript it sets a property
6852
- * nothing reads.
6847
+ * skipped, a number is written as its string, a string is trimmed as React's server renderer trims
6848
+ * it, and the properties keep the order of the input. A key that is not a camelCase identifier is
6849
+ * skipped with a development warning (once per key), and so is a value unless it is a finite
6850
+ * number or CSS text without `;`, `{`, `}` or `url(` whose quotes, parentheses, square brackets and
6851
+ * comments are balanced, with no `!` or backslash outside strings and comments. In a
6852
+ * server-rendered `style` attribute such a character could add a declaration of its own, an open
6853
+ * string, comment or bracket (or an escape that spells `url(` or takes the semicolon after the
6854
+ * value) would swallow the declarations after it, and `!important` would set a priority the client
6855
+ * refuses; the trimmed values that pass are read alike by the attribute and by the client. Names
6856
+ * are not checked against the list of tokens: TypeScript refuses a misspelled one, and in
6857
+ * JavaScript it sets a property nothing reads.
6853
6858
  *
6854
6859
  * @example
6855
6860
  * // app/layout.tsx (a Server Component)
@@ -20842,9 +20847,12 @@ export declare type WaveDir = 'ltr' | 'rtl';
20842
20847
  * provider's key by key, also when it sets another `theme`; `tokens={null}` clears them for the
20843
20848
  * tokens the theme declares and for the overlays, while the theme-independent ones of an
20844
20849
  * enclosing provider still reach the subtree through the CSS cascade. An override whose key is
20845
- * not a camelCase identifier, or whose value is neither a finite number nor a string without
20846
- * `;`, `{` or `}`, is skipped with a development warning. Without overrides the root renders no
20847
- * `style` attribute unless you pass `style`.
20850
+ * not a camelCase identifier is skipped with a development warning, and so is a value unless it
20851
+ * is a finite number or CSS text without `;`, `{`, `}` or `url(` with balanced quotes,
20852
+ * parentheses, square brackets and comments and no `!` or backslash outside strings and
20853
+ * comments; a value is trimmed as React's server renderer trims it, so the server and the client
20854
+ * write the same overrides. Without overrides the root renders no `style` attribute unless you
20855
+ * pass `style`.
20848
20856
  */
20849
20857
  export declare const WaveProvider: {
20850
20858
  ({ theme: themeProp, dir: dirProp, portalContainer: portalContainerProp, inputDefaults: inputDefaultsProp, motion: motionProp, tokens: tokensProp, applyStylesToPortals: applyStylesToPortalsProp, children, className, style, ref, ...rest }: WaveProviderProps): React_2.JSX.Element;
@@ -20914,10 +20922,16 @@ export declare interface WaveProviderProps extends React_2.HTMLAttributes<HTMLDi
20914
20922
  * overrides too, so set an override of a semantic colour (`primary`) in the provider that sets
20915
20923
  * the theme it was chosen for. A whole brand ramp is better made with `createBrandTokens` than
20916
20924
  * written by hand.
20917
- * A key that is not a camelCase identifier, or a value that is neither a finite number nor a
20918
- * string without `;`, `{` or `}` (such a value could add a declaration of its own to the
20919
- * server-rendered `style` attribute), is skipped with a development warning; a well-formed name
20920
- * is not checked against the list of tokens (TypeScript refuses a misspelled one).
20925
+ * A key that is not a camelCase identifier is skipped with a development warning, and so is a
20926
+ * value unless it is a finite number or CSS text without `;`, `{`, `}` or `url(` whose quotes,
20927
+ * parentheses, square brackets and comments are balanced, with no `!` or backslash outside
20928
+ * strings and comments; a well-formed name is not checked against the list of tokens
20929
+ * (TypeScript refuses a misspelled one). In the server-rendered `style` attribute a refused
20930
+ * character could add a declaration of its own, and an open string, comment or bracket, or an
20931
+ * escape that spells `url(` or takes the semicolon after the value, would swallow the
20932
+ * declarations after it (though not in the client), while `!important` would be a priority the
20933
+ * client refuses. A value is trimmed as React's server renderer trims it, so the values that pass
20934
+ * are written and read alike by the server and the client.
20921
20935
  *
20922
20936
  * The overrides are rendered on the server too. Under a Content Security Policy that blocks
20923
20937
  * inline style attributes (`style-src` without `'unsafe-inline'`), the server-rendered overrides
package/dist/index.d.ts CHANGED
@@ -6844,12 +6844,17 @@ export declare function getThemeClassName(theme: WaveTheme): string;
6844
6844
  * as a Server Component's document element. Server-safe.
6845
6845
  *
6846
6846
  * Returns `undefined` for `null`, `undefined` or a map with nothing to set. An `undefined` value is
6847
- * skipped, a number is written as its string, and the properties keep the order of the input. A
6848
- * key that is not a camelCase identifier, or a value that is neither a finite number nor a string
6849
- * without `;`, `{` or `}` (which could add a declaration of its own to a server-rendered `style`
6850
- * attribute), is skipped with a development warning (once per key). Names are not checked against
6851
- * the list of tokens: TypeScript refuses a misspelled one, and in JavaScript it sets a property
6852
- * nothing reads.
6847
+ * skipped, a number is written as its string, a string is trimmed as React's server renderer trims
6848
+ * it, and the properties keep the order of the input. A key that is not a camelCase identifier is
6849
+ * skipped with a development warning (once per key), and so is a value unless it is a finite
6850
+ * number or CSS text without `;`, `{`, `}` or `url(` whose quotes, parentheses, square brackets and
6851
+ * comments are balanced, with no `!` or backslash outside strings and comments. In a
6852
+ * server-rendered `style` attribute such a character could add a declaration of its own, an open
6853
+ * string, comment or bracket (or an escape that spells `url(` or takes the semicolon after the
6854
+ * value) would swallow the declarations after it, and `!important` would set a priority the client
6855
+ * refuses; the trimmed values that pass are read alike by the attribute and by the client. Names
6856
+ * are not checked against the list of tokens: TypeScript refuses a misspelled one, and in
6857
+ * JavaScript it sets a property nothing reads.
6853
6858
  *
6854
6859
  * @example
6855
6860
  * // app/layout.tsx (a Server Component)
@@ -20842,9 +20847,12 @@ export declare type WaveDir = 'ltr' | 'rtl';
20842
20847
  * provider's key by key, also when it sets another `theme`; `tokens={null}` clears them for the
20843
20848
  * tokens the theme declares and for the overlays, while the theme-independent ones of an
20844
20849
  * enclosing provider still reach the subtree through the CSS cascade. An override whose key is
20845
- * not a camelCase identifier, or whose value is neither a finite number nor a string without
20846
- * `;`, `{` or `}`, is skipped with a development warning. Without overrides the root renders no
20847
- * `style` attribute unless you pass `style`.
20850
+ * not a camelCase identifier is skipped with a development warning, and so is a value unless it
20851
+ * is a finite number or CSS text without `;`, `{`, `}` or `url(` with balanced quotes,
20852
+ * parentheses, square brackets and comments and no `!` or backslash outside strings and
20853
+ * comments; a value is trimmed as React's server renderer trims it, so the server and the client
20854
+ * write the same overrides. Without overrides the root renders no `style` attribute unless you
20855
+ * pass `style`.
20848
20856
  */
20849
20857
  export declare const WaveProvider: {
20850
20858
  ({ theme: themeProp, dir: dirProp, portalContainer: portalContainerProp, inputDefaults: inputDefaultsProp, motion: motionProp, tokens: tokensProp, applyStylesToPortals: applyStylesToPortalsProp, children, className, style, ref, ...rest }: WaveProviderProps): React_2.JSX.Element;
@@ -20914,10 +20922,16 @@ export declare interface WaveProviderProps extends React_2.HTMLAttributes<HTMLDi
20914
20922
  * overrides too, so set an override of a semantic colour (`primary`) in the provider that sets
20915
20923
  * the theme it was chosen for. A whole brand ramp is better made with `createBrandTokens` than
20916
20924
  * written by hand.
20917
- * A key that is not a camelCase identifier, or a value that is neither a finite number nor a
20918
- * string without `;`, `{` or `}` (such a value could add a declaration of its own to the
20919
- * server-rendered `style` attribute), is skipped with a development warning; a well-formed name
20920
- * is not checked against the list of tokens (TypeScript refuses a misspelled one).
20925
+ * A key that is not a camelCase identifier is skipped with a development warning, and so is a
20926
+ * value unless it is a finite number or CSS text without `;`, `{`, `}` or `url(` whose quotes,
20927
+ * parentheses, square brackets and comments are balanced, with no `!` or backslash outside
20928
+ * strings and comments; a well-formed name is not checked against the list of tokens
20929
+ * (TypeScript refuses a misspelled one). In the server-rendered `style` attribute a refused
20930
+ * character could add a declaration of its own, and an open string, comment or bracket, or an
20931
+ * escape that spells `url(` or takes the semicolon after the value, would swallow the
20932
+ * declarations after it (though not in the client), while `!important` would be a priority the
20933
+ * client refuses. A value is trimmed as React's server renderer trims it, so the values that pass
20934
+ * are written and read alike by the server and the client.
20921
20935
  *
20922
20936
  * The overrides are rendered on the server too. Under a Content Security Policy that blocks
20923
20937
  * inline style attributes (`style-src` without `'unsafe-inline'`), the server-rendered overrides
@@ -1,2 +1,3 @@
1
- const e=require("./dev.cjs");function t(e){return`--wave-${e.replace(/([A-Za-z])(\d)/g,`$1-$2`).replace(/[A-Z]/g,e=>`-${e}`).toLowerCase()}`}var n=/^[a-z][A-Za-z0-9]*$/,r=/[;{}]/;function i(e){let i=[];if(e==null)return{style:void 0,invalid:i};let a={},o=!0;for(let[s,c]of Object.entries(e))c!==void 0&&(n.test(s)?typeof c==`string`&&!r.test(c)||typeof c==`number`&&Number.isFinite(c)?(a[t(s)]=String(c),o=!1):i.push({key:s,reason:`value`}):i.push({key:s,reason:`name`}));return{style:o?void 0:a,invalid:i}}function a(t){let{style:n,invalid:r}=i(t);for(let{key:t,reason:n}of r)n===`name`?e.warnOnce(`tokens:name:${t}`,`getTokenStyle: \`${t}\` is not a token name, so it is skipped. Token names are the CSS variable in camelCase without its \`--wave-\` prefix (\`primaryForeground\` for \`--wave-primary-foreground\`).`):e.warnOnce(`tokens:value:${t}`,`getTokenStyle: the value of \`${t}\` is skipped: it must be a finite number or a string without \`;\`, \`{\` or \`}\`.`);return n}function o(e,t){if(t===null)return{};if(t===void 0)return e;let n=Object.fromEntries(Object.entries(t).filter(([,e])=>e!==void 0));return{...e,...n}}exports.collectTokenStyle=i,exports.getTokenStyle=a,exports.mergeTokenOverrides=o,exports.tokenVariable=t;
1
+ const e=require("./dev.cjs");function t(e){return`--wave-${e.replace(/([A-Za-z])(\d)/g,`$1-$2`).replace(/[A-Z]/g,e=>`-${e}`).toLowerCase()}`}var n=/^[a-z][A-Za-z0-9]*$/,r=/[;{}]|url\(/i;function i(e){let t=[],n=``,r=0;for(;r<e.length;r++){let i=e[r];if(n){if(i===`\\`)r++;else if(i===n)n=``;else if(`
2
+ \r\f`.includes(i))return!1}else`"'`.includes(i)?n=i:i===`/`&&e[r+1]===`*`?r=e.indexOf(`*/`,r+2)+1||1/0:`([`.includes(i)?t.push(i===`(`?`)`:`]`):`)]!\\`.includes(i)&&t.pop()!==i&&(r=1/0)}return r===e.length&&!n&&!t.length}function a(e){let a=[];if(e==null)return{style:void 0,invalid:a};let o={},s=!0;for(let[c,l]of Object.entries(e))l!==void 0&&(n.test(c)?typeof l==`string`&&!r.test(l)&&i(l)||typeof l==`number`&&Number.isFinite(l)?(o[t(c)]=String(l).trim(),s=!1):a.push({key:c,reason:`value`}):a.push({key:c,reason:`name`}));return{style:s?void 0:o,invalid:a}}function o(t){let{style:n,invalid:r}=a(t);for(let{key:t,reason:n}of r)n===`name`?e.warnOnce(`tokens:name:${t}`,`getTokenStyle: \`${t}\` is not a token name, so it is skipped. Token names are the CSS variable in camelCase without its \`--wave-\` prefix (\`primaryForeground\` for \`--wave-primary-foreground\`).`):e.warnOnce(`tokens:value:${t}`,`getTokenStyle: the value of \`${t}\` is skipped: it must be a finite number, or CSS text with balanced quotes, brackets and comments, without \`;\`, \`{\`, \`}\` or \`url(\`, and without \`!\` or a backslash outside strings and comments.`);return n}function s(e,t){if(t===null)return{};if(t===void 0)return e;let n=Object.fromEntries(Object.entries(t).filter(([,e])=>e!==void 0));return{...e,...n}}exports.collectTokenStyle=a,exports.getTokenStyle=o,exports.mergeTokenOverrides=s,exports.tokenVariable=t;
2
3
  //# sourceMappingURL=tokens.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"tokens.cjs","names":["require_dev","tokenVariable","name","capital","TOKEN_NAME","DECLARATION_BREAK","collectTokenStyle","invalid","overrides","style","empty","key","value","getTokenStyle","reason","mergeTokenOverrides","own","parent","defined"],"sources":["../../src/lib/tokens.ts"],"sourcesContent":["/**\n * The design tokens in JavaScript: their names, the overrides `WaveProvider` takes and the inline\n * style that applies them. Server-safe and light enough for the provider: it imports only types\n * and the development warnings, never the list of token names (`waveTokens.ts`).\n */\nimport type * as React from 'react';\nimport { warnOnce } from './dev';\nimport type { WAVE_TOKEN_NAMES } from './waveTokens';\n\n/**\n * The name of a design token: its CSS custom property without the `--wave-` prefix, in camelCase.\n * `--wave-primary-foreground` is `primaryForeground`, `--wave-brand-80` is `brand80` and\n * `--wave-palette-red-tint` is `paletteRedTint`.\n */\nexport type WaveTokenName = (typeof WAVE_TOKEN_NAMES)[number];\n\n/** The shape of `waveTokens`: every token name mapped to the CSS value that reads the token. */\nexport type WaveTokens = { readonly [K in WaveTokenName]: string };\n\n/**\n * Token overrides: CSS values by token name, as `WaveProvider tokens` and `getTokenStyle` take\n * them. A number is written as its string (`zOverlay: 2000`); a key whose value is `undefined`\n * sets nothing.\n */\nexport type WaveTokenOverrides = { [K in WaveTokenName]?: string | number };\n\n/**\n * The inline style that applies token overrides: a React style object whose `--wave-*` custom\n * properties set the tokens, so it can be merged with other style properties.\n */\nexport type TokenStyle = React.CSSProperties & { [K in `--wave-${string}`]?: string };\n\n/**\n * The CSS custom property of a token name at the type level: `TokenVariable<'brand80'>` is\n * `'--wave-brand-80'`, by the rule of {@link tokenVariable}. It spells out the literal values of\n * `waveTokens`. It walks the name one character at a time (tail-recursive, so long names stay\n * within the compiler's depth limit): `AfterLetter` says whether the previous character was a\n * letter, `Done` holds the converted text so far.\n */\nexport type TokenVariable<\n Name extends string,\n AfterLetter extends boolean = false,\n Done extends string = '',\n> = Name extends `${infer Char}${infer Rest}`\n ? Char extends '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9'\n ? TokenVariable<Rest, false, `${Done}${AfterLetter extends true ? '-' : ''}${Char}`>\n : Char extends Lowercase<Char>\n ? TokenVariable<Rest, true, `${Done}${Char}`>\n : TokenVariable<Rest, true, `${Done}-${Lowercase<Char>}`>\n : `--wave-${Done}`;\n\n/**\n * The CSS custom property of a token name: a hyphen before each capital letter and before a digit\n * run that follows a letter, lowercased, with the `--wave-` prefix. `'paletteRedTint'` gives\n * `'--wave-palette-red-tint'` and `'brand80'` gives `'--wave-brand-80'`. The name is not checked\n * against the list of tokens.\n */\nexport function tokenVariable(name: string): string {\n return `--wave-${name\n .replace(/([A-Za-z])(\\d)/g, '$1-$2')\n .replace(/[A-Z]/g, (capital) => `-${capital}`)\n .toLowerCase()}`;\n}\n\n/** A camelCase identifier: the only key shape a token name can have. */\nconst TOKEN_NAME = /^[a-z][A-Za-z0-9]*$/;\n\n/**\n * The characters no token value may hold. React writes a custom property's value into the\n * server-rendered `style` attribute as text, where a semicolon would end the declaration and let\n * the rest of the value declare a property of its own (and a brace could close the rule around\n * it); in the client the CSSOM refuses a value with a semicolon, so the two renders would also\n * differ.\n */\nconst DECLARATION_BREAK = /[;{}]/;\n\n/**\n * The custom properties of `overrides`, and the entries it skipped. Pure: it never warns, so the\n * provider can call it during render; {@link getTokenStyle} reports the skipped entries.\n *\n * `undefined` values are skipped first (they set nothing); a key that is not a camelCase\n * identifier is skipped with the reason `'name'`, and a value that is neither a finite number nor a\n * string without `;`, `{` or `}` with the reason `'value'`. Numbers are written as strings, and the\n * style keeps the order of the input. `style` is `undefined` when nothing is left.\n */\nexport function collectTokenStyle(overrides: WaveTokenOverrides | null | undefined): {\n style: TokenStyle | undefined;\n invalid: readonly { key: string; reason: 'name' | 'value' }[];\n} {\n const invalid: { key: string; reason: 'name' | 'value' }[] = [];\n if (overrides == null) return { style: undefined, invalid };\n const style: Record<string, string> = {};\n let empty = true;\n for (const [key, value] of Object.entries(overrides as Record<string, unknown>)) {\n if (value === undefined) continue;\n if (!TOKEN_NAME.test(key)) {\n invalid.push({ key, reason: 'name' });\n } else if (\n (typeof value === 'string' && !DECLARATION_BREAK.test(value)) ||\n (typeof value === 'number' && Number.isFinite(value))\n ) {\n style[tokenVariable(key)] = String(value);\n empty = false;\n } else {\n invalid.push({ key, reason: 'value' });\n }\n }\n return { style: empty ? undefined : (style as TokenStyle), invalid };\n}\n\n/**\n * The inline style that applies token overrides: one `--wave-*` custom property per entry, the\n * object `WaveProvider` puts on its root and its portals. Use it where no provider renders, such\n * as a Server Component's document element. Server-safe.\n *\n * Returns `undefined` for `null`, `undefined` or a map with nothing to set. An `undefined` value is\n * skipped, a number is written as its string, and the properties keep the order of the input. A\n * key that is not a camelCase identifier, or a value that is neither a finite number nor a string\n * without `;`, `{` or `}` (which could add a declaration of its own to a server-rendered `style`\n * attribute), is skipped with a development warning (once per key). Names are not checked against\n * the list of tokens: TypeScript refuses a misspelled one, and in JavaScript it sets a property\n * nothing reads.\n *\n * @example\n * // app/layout.tsx (a Server Component)\n * <html style={getTokenStyle({ brand80: '#c43e00' })}>\n *\n * @example\n * <div style={{ ...getTokenStyle(overrides), padding: 16 }} />\n */\nexport function getTokenStyle(\n overrides: WaveTokenOverrides | null | undefined,\n): TokenStyle | undefined {\n const { style, invalid } = collectTokenStyle(overrides);\n for (const { key, reason } of invalid) {\n if (reason === 'name') {\n warnOnce(\n `tokens:name:${key}`,\n `getTokenStyle: \\`${key}\\` is not a token name, so it is skipped. Token names are the CSS ` +\n 'variable in camelCase without its `--wave-` prefix (`primaryForeground` for ' +\n '`--wave-primary-foreground`).',\n );\n } else {\n warnOnce(\n `tokens:value:${key}`,\n `getTokenStyle: the value of \\`${key}\\` is skipped: it must be a finite number or a string ` +\n 'without `;`, `{` or `}`.',\n );\n }\n }\n return style;\n}\n\n/**\n * The overrides of a nested provider: `own` merged key by key over `parent`. `undefined` inherits\n * `parent` as it is, `null` clears everything inherited, and a key whose own value is `undefined`\n * keeps the parent's value.\n */\nexport function mergeTokenOverrides(\n parent: WaveTokenOverrides,\n own: WaveTokenOverrides | null | undefined,\n): WaveTokenOverrides {\n if (own === null) return {};\n if (own === undefined) return parent;\n // Spread, never assignment, so a `__proto__` key is an own entry like any other.\n const defined = Object.fromEntries(\n Object.entries(own).filter(([, value]) => value !== undefined),\n );\n return { ...parent, ...defined };\n}\n"],"mappings":"6BAyDA,SAAgBC,EAAc,EAAsB,CAClD,MAAO,UAAUC,EACd,QAAQ,kBAAmB,OAAO,CAAC,CACnC,QAAQ,SAAW,GAAY,IAAIC,GAAS,CAAC,CAC7C,YAAY,GACjB,CAGA,IAAMC,EAAa,sBASbC,EAAoB,QAW1B,SAAgBC,EAAkB,EAGhC,CACA,IAAMC,EAAuD,CAAC,EAC9D,GAAIC,GAAa,KAAM,MAAO,CAAE,MAAO,IAAA,GAAW,SAAQ,EAC1D,IAAMC,EAAgC,CAAC,EACnCC,EAAQ,GACZ,IAAK,GAAM,CAACC,EAAKC,KAAU,OAAO,QAAQJ,CAAoC,EACxEI,IAAU,IAAA,KACTR,EAAW,KAAKO,CAAG,EAGrB,OAAOC,GAAU,UAAY,CAACP,EAAkB,KAAKO,CAAK,GAC1D,OAAOA,GAAU,UAAY,OAAO,SAASA,CAAK,GAEnD,EAAMX,EAAcU,CAAG,GAAK,OAAOC,CAAK,EACxC,EAAQ,IAERL,EAAQ,KAAK,CAAE,MAAK,OAAQ,OAAQ,CAAC,EARrCA,EAAQ,KAAK,CAAE,MAAK,OAAQ,MAAO,CAAC,GAWxC,MAAO,CAAE,MAAOG,EAAQ,IAAA,GAAaD,EAAsB,SAAQ,CACrE,CAsBA,SAAgBI,EACd,EACwB,CACxB,GAAM,CAAE,QAAO,WAAYP,EAAkBE,CAAS,EACtD,IAAK,GAAM,CAAE,MAAK,YAAYD,EACxBO,IAAW,OACbd,EAAA,SACE,eAAeW,IACf,oBAAoBA,EAAI,kLAG1B,EAEAX,EAAA,SACE,gBAAgBW,IAChB,iCAAiCA,EAAI,qFAEvC,EAGJ,OAAOF,CACT,CAOA,SAAgBM,EACd,EACA,EACoB,CACpB,GAAIC,IAAQ,KAAM,MAAO,CAAC,EAC1B,GAAIA,IAAQ,IAAA,GAAW,OAAOC,EAE9B,IAAMC,EAAU,OAAO,YACrB,OAAO,QAAQF,CAAG,CAAC,CAAC,QAAQ,EAAGJ,KAAWA,IAAU,IAAA,EAAS,CAC/D,EACA,MAAO,CAAE,GAAGK,EAAQ,GAAGC,CAAQ,CACjC"}
1
+ {"version":3,"file":"tokens.cjs","names":["require_dev","tokenVariable","name","capital","TOKEN_NAME","REFUSED_TEXT","isBalancedCssText","closers","quote","index","text","char","collectTokenStyle","invalid","overrides","style","empty","key","value","getTokenStyle","reason","mergeTokenOverrides","own","parent","defined"],"sources":["../../src/lib/tokens.ts"],"sourcesContent":["/**\n * The design tokens in JavaScript: their names, the overrides `WaveProvider` takes and the inline\n * style that applies them. Server-safe and light enough for the provider: it imports only types\n * and the development warnings, never the list of token names (`waveTokens.ts`).\n */\nimport type * as React from 'react';\nimport { warnOnce } from './dev';\nimport type { WAVE_TOKEN_NAMES } from './waveTokens';\n\n/**\n * The name of a design token: its CSS custom property without the `--wave-` prefix, in camelCase.\n * `--wave-primary-foreground` is `primaryForeground`, `--wave-brand-80` is `brand80` and\n * `--wave-palette-red-tint` is `paletteRedTint`.\n */\nexport type WaveTokenName = (typeof WAVE_TOKEN_NAMES)[number];\n\n/** The shape of `waveTokens`: every token name mapped to the CSS value that reads the token. */\nexport type WaveTokens = { readonly [K in WaveTokenName]: string };\n\n/**\n * Token overrides: CSS values by token name, as `WaveProvider tokens` and `getTokenStyle` take\n * them. A number is written as its string (`zOverlay: 2000`); a key whose value is `undefined`\n * sets nothing.\n */\nexport type WaveTokenOverrides = { [K in WaveTokenName]?: string | number };\n\n/**\n * The inline style that applies token overrides: a React style object whose `--wave-*` custom\n * properties set the tokens, so it can be merged with other style properties.\n */\nexport type TokenStyle = React.CSSProperties & { [K in `--wave-${string}`]?: string };\n\n/**\n * The CSS custom property of a token name at the type level: `TokenVariable<'brand80'>` is\n * `'--wave-brand-80'`, by the rule of {@link tokenVariable}. It spells out the literal values of\n * `waveTokens`. It walks the name one character at a time (tail-recursive, so long names stay\n * within the compiler's depth limit): `AfterLetter` says whether the previous character was a\n * letter, `Done` holds the converted text so far.\n */\nexport type TokenVariable<\n Name extends string,\n AfterLetter extends boolean = false,\n Done extends string = '',\n> = Name extends `${infer Char}${infer Rest}`\n ? Char extends '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9'\n ? TokenVariable<Rest, false, `${Done}${AfterLetter extends true ? '-' : ''}${Char}`>\n : Char extends Lowercase<Char>\n ? TokenVariable<Rest, true, `${Done}${Char}`>\n : TokenVariable<Rest, true, `${Done}-${Lowercase<Char>}`>\n : `--wave-${Done}`;\n\n/**\n * The CSS custom property of a token name: a hyphen before each capital letter and before a digit\n * run that follows a letter, lowercased, with the `--wave-` prefix. `'paletteRedTint'` gives\n * `'--wave-palette-red-tint'` and `'brand80'` gives `'--wave-brand-80'`. The name is not checked\n * against the list of tokens.\n */\nexport function tokenVariable(name: string): string {\n return `--wave-${name\n .replace(/([A-Za-z])(\\d)/g, '$1-$2')\n .replace(/[A-Z]/g, (capital) => `-${capital}`)\n .toLowerCase()}`;\n}\n\n/** A camelCase identifier: the only key shape a token name can have. */\nconst TOKEN_NAME = /^[a-z][A-Za-z0-9]*$/;\n\n/**\n * The text no token value may hold, anywhere in it. React writes a custom property's value into\n * the server-rendered `style` attribute as text, where a semicolon would end the declaration and\n * let the rest of the value declare a property of its own (and a brace could close the rule around\n * it); in the client the CSSOM refuses a value with a semicolon, so the two renders would also\n * differ. `url(`, in any case: no token takes a URL, and an unquoted one ends at its first `)`\n * whatever it holds, which the balance check below does not follow (`url(a\"b)c\")` would open a\n * string after that `)`); the check refuses the escapes that could spell it otherwise (`u\\72l(`).\n */\nconst REFUSED_TEXT = /[;{}]|url\\(/i;\n\n/**\n * Whether `text` closes everything it opens, read as the CSS tokenizer reads it: every string\n * closes on its own line (a backslash in it escapes the next character, a line break included),\n * every comment closes, and the parentheses and square brackets outside strings and comments pair\n * up and nest. In the server-rendered `style` attribute an open string, comment, parenthesis or\n * bracket would run on to the end of the attribute, so the declarations after it would be lost\n * there, while the client sets each property on its own. Refused as well, outside strings and\n * comments: a stray closer, which the client refuses in a custom property; a `!`, since the server\n * would read `!important` as the declaration's priority while the client refuses a `!` at the top\n * level (one nested in a function, as in `color-mix(in srgb, red !important, blue)`, is valid there, and refusing\n * it is deliberate: no token needs it); and a backslash, which no token needs there and which\n * could spell `url(` with an escape or, at the end, escape the semicolon after the value. One pass\n * over the text, each character read once and the stack an array, so the time grows with the\n * length alone and a long crafted value cannot stall a server render.\n */\nfunction isBalancedCssText(text: string): boolean {\n // The closers of the open parentheses and brackets, the innermost last.\n const closers: string[] = [];\n // The quote of the open string, '' outside one.\n let quote = '';\n let index = 0;\n for (; index < text.length; index++) {\n const char = text[index];\n if (quote) {\n // An escape takes the next character; a backslash at the end leaves the string open.\n if (char === '\\\\') index++;\n else if (char === quote) quote = '';\n else if ('\\n\\r\\f'.includes(char)) return false;\n } else if ('\"\\''.includes(char)) {\n quote = char;\n } else if (char === '/' && text[index + 1] === '*') {\n // A comment runs to its first `*/` and escapes nothing; one that never ends moves the index\n // past the end.\n index = text.indexOf('*/', index + 2) + 1 || Infinity;\n } else if ('(['.includes(char)) {\n closers.push(char === '(' ? ')' : ']');\n } else if (')]!\\\\'.includes(char) && closers.pop() !== char) {\n // A closer that is not the innermost one's, or a `!` or a backslash, which no opener\n // expects: the index moves past the end, which fails below.\n index = Infinity;\n }\n }\n return index === text.length && !quote && !closers.length;\n}\n\n/**\n * The custom properties of `overrides`, and the entries it skipped. Pure: it never warns, so the\n * provider can call it during render; {@link getTokenStyle} reports the skipped entries.\n *\n * `undefined` values are skipped first (they set nothing); a key that is not a camelCase\n * identifier is skipped with the reason `'name'`, and a value with the reason `'value'` unless it\n * is a finite number or a string without `;`, `{`, `}` or `url(` that closes every string,\n * comment, parenthesis and square bracket it opens and has no stray closer, `!` or backslash\n * outside strings and comments. Numbers are written as strings, and every value is trimmed with\n * JavaScript's `trim`, as React's server renderer trims a custom property's value (the client sets\n * the value as given), so both write the same text; a value that is empty after it is written as\n * `''`, for which React renders no declaration on either side. The style keeps the order of the\n * input. `style` is `undefined` when nothing is left.\n */\nexport function collectTokenStyle(overrides: WaveTokenOverrides | null | undefined): {\n style: TokenStyle | undefined;\n invalid: readonly { key: string; reason: 'name' | 'value' }[];\n} {\n const invalid: { key: string; reason: 'name' | 'value' }[] = [];\n if (overrides == null) return { style: undefined, invalid };\n const style: Record<string, string> = {};\n let empty = true;\n for (const [key, value] of Object.entries(overrides as Record<string, unknown>)) {\n if (value === undefined) continue;\n if (!TOKEN_NAME.test(key)) {\n invalid.push({ key, reason: 'name' });\n } else if (\n (typeof value === 'string' && !REFUSED_TEXT.test(value) && isBalancedCssText(value)) ||\n (typeof value === 'number' && Number.isFinite(value))\n ) {\n style[tokenVariable(key)] = String(value).trim();\n empty = false;\n } else {\n invalid.push({ key, reason: 'value' });\n }\n }\n return { style: empty ? undefined : (style as TokenStyle), invalid };\n}\n\n/**\n * The inline style that applies token overrides: one `--wave-*` custom property per entry, the\n * object `WaveProvider` puts on its root and its portals. Use it where no provider renders, such\n * as a Server Component's document element. Server-safe.\n *\n * Returns `undefined` for `null`, `undefined` or a map with nothing to set. An `undefined` value is\n * skipped, a number is written as its string, a string is trimmed as React's server renderer trims\n * it, and the properties keep the order of the input. A key that is not a camelCase identifier is\n * skipped with a development warning (once per key), and so is a value unless it is a finite\n * number or CSS text without `;`, `{`, `}` or `url(` whose quotes, parentheses, square brackets and\n * comments are balanced, with no `!` or backslash outside strings and comments. In a\n * server-rendered `style` attribute such a character could add a declaration of its own, an open\n * string, comment or bracket (or an escape that spells `url(` or takes the semicolon after the\n * value) would swallow the declarations after it, and `!important` would set a priority the client\n * refuses; the trimmed values that pass are read alike by the attribute and by the client. Names\n * are not checked against the list of tokens: TypeScript refuses a misspelled one, and in\n * JavaScript it sets a property nothing reads.\n *\n * @example\n * // app/layout.tsx (a Server Component)\n * <html style={getTokenStyle({ brand80: '#c43e00' })}>\n *\n * @example\n * <div style={{ ...getTokenStyle(overrides), padding: 16 }} />\n */\nexport function getTokenStyle(\n overrides: WaveTokenOverrides | null | undefined,\n): TokenStyle | undefined {\n const { style, invalid } = collectTokenStyle(overrides);\n for (const { key, reason } of invalid) {\n if (reason === 'name') {\n warnOnce(\n `tokens:name:${key}`,\n `getTokenStyle: \\`${key}\\` is not a token name, so it is skipped. Token names are the CSS ` +\n 'variable in camelCase without its `--wave-` prefix (`primaryForeground` for ' +\n '`--wave-primary-foreground`).',\n );\n } else {\n warnOnce(\n `tokens:value:${key}`,\n `getTokenStyle: the value of \\`${key}\\` is skipped: it must be a finite number, or CSS ` +\n 'text with balanced quotes, brackets and comments, without `;`, `{`, `}` or `url(`, and ' +\n 'without `!` or a backslash outside strings and comments.',\n );\n }\n }\n return style;\n}\n\n/**\n * The overrides of a nested provider: `own` merged key by key over `parent`. `undefined` inherits\n * `parent` as it is, `null` clears everything inherited, and a key whose own value is `undefined`\n * keeps the parent's value.\n */\nexport function mergeTokenOverrides(\n parent: WaveTokenOverrides,\n own: WaveTokenOverrides | null | undefined,\n): WaveTokenOverrides {\n if (own === null) return {};\n if (own === undefined) return parent;\n // Spread, never assignment, so a `__proto__` key is an own entry like any other.\n const defined = Object.fromEntries(\n Object.entries(own).filter(([, value]) => value !== undefined),\n );\n return { ...parent, ...defined };\n}\n"],"mappings":"6BAyDA,SAAgBC,EAAc,EAAsB,CAClD,MAAO,UAAUC,EACd,QAAQ,kBAAmB,OAAO,CAAC,CACnC,QAAQ,SAAW,GAAY,IAAIC,GAAS,CAAC,CAC7C,YAAY,GACjB,CAGA,IAAMC,EAAa,sBAWbC,EAAe,eAiBrB,SAASC,EAAkB,EAAuB,CAEhD,IAAMC,EAAoB,CAAC,EAEvBC,EAAQ,GACRC,EAAQ,EACZ,KAAOA,EAAQC,EAAK,OAAQ,IAAS,CACnC,IAAMC,EAAOD,EAAKD,GAClB,GAAID,EAAO,CAET,GAAIG,IAAS,KAAM,SACd,GAAIA,IAASH,EAAO,EAAQ,QAC5B,GAAI;MAAS,SAASG,CAAI,EAAG,MAAO,EAC3C,KAAW,KAAM,SAASA,CAAI,EAC5B,EAAQA,EACCA,IAAS,KAAOD,EAAKD,EAAQ,KAAO,IAG7C,EAAQC,EAAK,QAAQ,KAAMD,EAAQ,CAAC,EAAI,GAAK,IACpC,KAAK,SAASE,CAAI,EAC3BJ,EAAQ,KAAKI,IAAS,IAAM,IAAM,GAAG,EAC5B,QAAQ,SAASA,CAAI,GAAKJ,EAAQ,IAAI,IAAMI,IAGrD,EAAQ,IAEZ,CACA,OAAOF,IAAUC,EAAK,QAAU,CAACF,GAAS,CAACD,EAAQ,MACrD,CAgBA,SAAgBK,EAAkB,EAGhC,CACA,IAAMC,EAAuD,CAAC,EAC9D,GAAIC,GAAa,KAAM,MAAO,CAAE,MAAO,IAAA,GAAW,SAAQ,EAC1D,IAAMC,EAAgC,CAAC,EACnCC,EAAQ,GACZ,IAAK,GAAM,CAACC,EAAKC,KAAU,OAAO,QAAQJ,CAAoC,EACxEI,IAAU,IAAA,KACTd,EAAW,KAAKa,CAAG,EAGrB,OAAOC,GAAU,UAAY,CAACb,EAAa,KAAKa,CAAK,GAAKZ,EAAkBY,CAAK,GACjF,OAAOA,GAAU,UAAY,OAAO,SAASA,CAAK,GAEnD,EAAMjB,EAAcgB,CAAG,GAAK,OAAOC,CAAK,CAAC,CAAC,KAAK,EAC/C,EAAQ,IAERL,EAAQ,KAAK,CAAE,MAAK,OAAQ,OAAQ,CAAC,EARrCA,EAAQ,KAAK,CAAE,MAAK,OAAQ,MAAO,CAAC,GAWxC,MAAO,CAAE,MAAOG,EAAQ,IAAA,GAAaD,EAAsB,SAAQ,CACrE,CA2BA,SAAgBI,EACd,EACwB,CACxB,GAAM,CAAE,QAAO,WAAYP,EAAkBE,CAAS,EACtD,IAAK,GAAM,CAAE,MAAK,YAAYD,EACxBO,IAAW,OACbpB,EAAA,SACE,eAAeiB,IACf,oBAAoBA,EAAI,kLAG1B,EAEAjB,EAAA,SACE,gBAAgBiB,IAChB,iCAAiCA,EAAI,4MAGvC,EAGJ,OAAOF,CACT,CAOA,SAAgBM,EACd,EACA,EACoB,CACpB,GAAIC,IAAQ,KAAM,MAAO,CAAC,EAC1B,GAAIA,IAAQ,IAAA,GAAW,OAAOC,EAE9B,IAAMC,EAAU,OAAO,YACrB,OAAO,QAAQF,CAAG,CAAC,CAAC,QAAQ,EAAGJ,KAAWA,IAAU,IAAA,EAAS,CAC/D,EACA,MAAO,CAAE,GAAGK,EAAQ,GAAGC,CAAQ,CACjC"}
@@ -3,32 +3,44 @@ import { warnOnce as e } from "./dev.mjs";
3
3
  function t(e) {
4
4
  return `--wave-${e.replace(/([A-Za-z])(\d)/g, "$1-$2").replace(/[A-Z]/g, (e) => `-${e}`).toLowerCase()}`;
5
5
  }
6
- var n = /^[a-z][A-Za-z0-9]*$/, r = /[;{}]/;
6
+ var n = /^[a-z][A-Za-z0-9]*$/, r = /[;{}]|url\(/i;
7
7
  function i(e) {
8
- let i = [];
8
+ let t = [], n = "", r = 0;
9
+ for (; r < e.length; r++) {
10
+ let i = e[r];
11
+ if (n) {
12
+ if (i === "\\") r++;
13
+ else if (i === n) n = "";
14
+ else if ("\n\r\f".includes(i)) return !1;
15
+ } else "\"'".includes(i) ? n = i : i === "/" && e[r + 1] === "*" ? r = e.indexOf("*/", r + 2) + 1 || Infinity : "([".includes(i) ? t.push(i === "(" ? ")" : "]") : ")]!\\".includes(i) && t.pop() !== i && (r = Infinity);
16
+ }
17
+ return r === e.length && !n && !t.length;
18
+ }
19
+ function a(e) {
20
+ let a = [];
9
21
  if (e == null) return {
10
22
  style: void 0,
11
- invalid: i
23
+ invalid: a
12
24
  };
13
- let a = {}, o = !0;
14
- for (let [s, c] of Object.entries(e)) c !== void 0 && (n.test(s) ? typeof c == "string" && !r.test(c) || typeof c == "number" && Number.isFinite(c) ? (a[t(s)] = String(c), o = !1) : i.push({
15
- key: s,
25
+ let o = {}, s = !0;
26
+ for (let [c, l] of Object.entries(e)) l !== void 0 && (n.test(c) ? typeof l == "string" && !r.test(l) && i(l) || typeof l == "number" && Number.isFinite(l) ? (o[t(c)] = String(l).trim(), s = !1) : a.push({
27
+ key: c,
16
28
  reason: "value"
17
- }) : i.push({
18
- key: s,
29
+ }) : a.push({
30
+ key: c,
19
31
  reason: "name"
20
32
  }));
21
33
  return {
22
- style: o ? void 0 : a,
23
- invalid: i
34
+ style: s ? void 0 : o,
35
+ invalid: a
24
36
  };
25
37
  }
26
- function a(t) {
27
- let { style: n, invalid: r } = i(t);
28
- for (let { key: t, reason: n } of r) n === "name" ? e(`tokens:name:${t}`, `getTokenStyle: \`${t}\` is not a token name, so it is skipped. Token names are the CSS variable in camelCase without its \`--wave-\` prefix (\`primaryForeground\` for \`--wave-primary-foreground\`).`) : e(`tokens:value:${t}`, `getTokenStyle: the value of \`${t}\` is skipped: it must be a finite number or a string without \`;\`, \`{\` or \`}\`.`);
38
+ function o(t) {
39
+ let { style: n, invalid: r } = a(t);
40
+ for (let { key: t, reason: n } of r) n === "name" ? e(`tokens:name:${t}`, `getTokenStyle: \`${t}\` is not a token name, so it is skipped. Token names are the CSS variable in camelCase without its \`--wave-\` prefix (\`primaryForeground\` for \`--wave-primary-foreground\`).`) : e(`tokens:value:${t}`, `getTokenStyle: the value of \`${t}\` is skipped: it must be a finite number, or CSS text with balanced quotes, brackets and comments, without \`;\`, \`{\`, \`}\` or \`url(\`, and without \`!\` or a backslash outside strings and comments.`);
29
41
  return n;
30
42
  }
31
- function o(e, t) {
43
+ function s(e, t) {
32
44
  if (t === null) return {};
33
45
  if (t === void 0) return e;
34
46
  let n = Object.fromEntries(Object.entries(t).filter(([, e]) => e !== void 0));
@@ -38,6 +50,6 @@ function o(e, t) {
38
50
  };
39
51
  }
40
52
  //#endregion
41
- export { i as collectTokenStyle, a as getTokenStyle, o as mergeTokenOverrides, t as tokenVariable };
53
+ export { a as collectTokenStyle, o as getTokenStyle, s as mergeTokenOverrides, t as tokenVariable };
42
54
 
43
55
  //# sourceMappingURL=tokens.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"tokens.mjs","names":["tokenVariable","name","capital","TOKEN_NAME","DECLARATION_BREAK","collectTokenStyle","invalid","overrides","style","empty","key","value","getTokenStyle","reason","warnOnce","mergeTokenOverrides","own","parent","defined"],"sources":["../../src/lib/tokens.ts"],"sourcesContent":["/**\n * The design tokens in JavaScript: their names, the overrides `WaveProvider` takes and the inline\n * style that applies them. Server-safe and light enough for the provider: it imports only types\n * and the development warnings, never the list of token names (`waveTokens.ts`).\n */\nimport type * as React from 'react';\nimport { warnOnce } from './dev';\nimport type { WAVE_TOKEN_NAMES } from './waveTokens';\n\n/**\n * The name of a design token: its CSS custom property without the `--wave-` prefix, in camelCase.\n * `--wave-primary-foreground` is `primaryForeground`, `--wave-brand-80` is `brand80` and\n * `--wave-palette-red-tint` is `paletteRedTint`.\n */\nexport type WaveTokenName = (typeof WAVE_TOKEN_NAMES)[number];\n\n/** The shape of `waveTokens`: every token name mapped to the CSS value that reads the token. */\nexport type WaveTokens = { readonly [K in WaveTokenName]: string };\n\n/**\n * Token overrides: CSS values by token name, as `WaveProvider tokens` and `getTokenStyle` take\n * them. A number is written as its string (`zOverlay: 2000`); a key whose value is `undefined`\n * sets nothing.\n */\nexport type WaveTokenOverrides = { [K in WaveTokenName]?: string | number };\n\n/**\n * The inline style that applies token overrides: a React style object whose `--wave-*` custom\n * properties set the tokens, so it can be merged with other style properties.\n */\nexport type TokenStyle = React.CSSProperties & { [K in `--wave-${string}`]?: string };\n\n/**\n * The CSS custom property of a token name at the type level: `TokenVariable<'brand80'>` is\n * `'--wave-brand-80'`, by the rule of {@link tokenVariable}. It spells out the literal values of\n * `waveTokens`. It walks the name one character at a time (tail-recursive, so long names stay\n * within the compiler's depth limit): `AfterLetter` says whether the previous character was a\n * letter, `Done` holds the converted text so far.\n */\nexport type TokenVariable<\n Name extends string,\n AfterLetter extends boolean = false,\n Done extends string = '',\n> = Name extends `${infer Char}${infer Rest}`\n ? Char extends '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9'\n ? TokenVariable<Rest, false, `${Done}${AfterLetter extends true ? '-' : ''}${Char}`>\n : Char extends Lowercase<Char>\n ? TokenVariable<Rest, true, `${Done}${Char}`>\n : TokenVariable<Rest, true, `${Done}-${Lowercase<Char>}`>\n : `--wave-${Done}`;\n\n/**\n * The CSS custom property of a token name: a hyphen before each capital letter and before a digit\n * run that follows a letter, lowercased, with the `--wave-` prefix. `'paletteRedTint'` gives\n * `'--wave-palette-red-tint'` and `'brand80'` gives `'--wave-brand-80'`. The name is not checked\n * against the list of tokens.\n */\nexport function tokenVariable(name: string): string {\n return `--wave-${name\n .replace(/([A-Za-z])(\\d)/g, '$1-$2')\n .replace(/[A-Z]/g, (capital) => `-${capital}`)\n .toLowerCase()}`;\n}\n\n/** A camelCase identifier: the only key shape a token name can have. */\nconst TOKEN_NAME = /^[a-z][A-Za-z0-9]*$/;\n\n/**\n * The characters no token value may hold. React writes a custom property's value into the\n * server-rendered `style` attribute as text, where a semicolon would end the declaration and let\n * the rest of the value declare a property of its own (and a brace could close the rule around\n * it); in the client the CSSOM refuses a value with a semicolon, so the two renders would also\n * differ.\n */\nconst DECLARATION_BREAK = /[;{}]/;\n\n/**\n * The custom properties of `overrides`, and the entries it skipped. Pure: it never warns, so the\n * provider can call it during render; {@link getTokenStyle} reports the skipped entries.\n *\n * `undefined` values are skipped first (they set nothing); a key that is not a camelCase\n * identifier is skipped with the reason `'name'`, and a value that is neither a finite number nor a\n * string without `;`, `{` or `}` with the reason `'value'`. Numbers are written as strings, and the\n * style keeps the order of the input. `style` is `undefined` when nothing is left.\n */\nexport function collectTokenStyle(overrides: WaveTokenOverrides | null | undefined): {\n style: TokenStyle | undefined;\n invalid: readonly { key: string; reason: 'name' | 'value' }[];\n} {\n const invalid: { key: string; reason: 'name' | 'value' }[] = [];\n if (overrides == null) return { style: undefined, invalid };\n const style: Record<string, string> = {};\n let empty = true;\n for (const [key, value] of Object.entries(overrides as Record<string, unknown>)) {\n if (value === undefined) continue;\n if (!TOKEN_NAME.test(key)) {\n invalid.push({ key, reason: 'name' });\n } else if (\n (typeof value === 'string' && !DECLARATION_BREAK.test(value)) ||\n (typeof value === 'number' && Number.isFinite(value))\n ) {\n style[tokenVariable(key)] = String(value);\n empty = false;\n } else {\n invalid.push({ key, reason: 'value' });\n }\n }\n return { style: empty ? undefined : (style as TokenStyle), invalid };\n}\n\n/**\n * The inline style that applies token overrides: one `--wave-*` custom property per entry, the\n * object `WaveProvider` puts on its root and its portals. Use it where no provider renders, such\n * as a Server Component's document element. Server-safe.\n *\n * Returns `undefined` for `null`, `undefined` or a map with nothing to set. An `undefined` value is\n * skipped, a number is written as its string, and the properties keep the order of the input. A\n * key that is not a camelCase identifier, or a value that is neither a finite number nor a string\n * without `;`, `{` or `}` (which could add a declaration of its own to a server-rendered `style`\n * attribute), is skipped with a development warning (once per key). Names are not checked against\n * the list of tokens: TypeScript refuses a misspelled one, and in JavaScript it sets a property\n * nothing reads.\n *\n * @example\n * // app/layout.tsx (a Server Component)\n * <html style={getTokenStyle({ brand80: '#c43e00' })}>\n *\n * @example\n * <div style={{ ...getTokenStyle(overrides), padding: 16 }} />\n */\nexport function getTokenStyle(\n overrides: WaveTokenOverrides | null | undefined,\n): TokenStyle | undefined {\n const { style, invalid } = collectTokenStyle(overrides);\n for (const { key, reason } of invalid) {\n if (reason === 'name') {\n warnOnce(\n `tokens:name:${key}`,\n `getTokenStyle: \\`${key}\\` is not a token name, so it is skipped. Token names are the CSS ` +\n 'variable in camelCase without its `--wave-` prefix (`primaryForeground` for ' +\n '`--wave-primary-foreground`).',\n );\n } else {\n warnOnce(\n `tokens:value:${key}`,\n `getTokenStyle: the value of \\`${key}\\` is skipped: it must be a finite number or a string ` +\n 'without `;`, `{` or `}`.',\n );\n }\n }\n return style;\n}\n\n/**\n * The overrides of a nested provider: `own` merged key by key over `parent`. `undefined` inherits\n * `parent` as it is, `null` clears everything inherited, and a key whose own value is `undefined`\n * keeps the parent's value.\n */\nexport function mergeTokenOverrides(\n parent: WaveTokenOverrides,\n own: WaveTokenOverrides | null | undefined,\n): WaveTokenOverrides {\n if (own === null) return {};\n if (own === undefined) return parent;\n // Spread, never assignment, so a `__proto__` key is an own entry like any other.\n const defined = Object.fromEntries(\n Object.entries(own).filter(([, value]) => value !== undefined),\n );\n return { ...parent, ...defined };\n}\n"],"mappings":";;AAyDA,SAAgBA,EAAc,GAAsB;CAClD,OAAO,UAAUC,EACd,QAAQ,mBAAmB,OAAO,CAAC,CACnC,QAAQ,WAAW,MAAY,IAAIC,GAAS,CAAC,CAC7C,YAAY;AACjB;AAGA,IAAMC,IAAa,uBASbC,IAAoB;AAW1B,SAAgBC,EAAkB,GAGhC;CACA,IAAMC,IAAuD,CAAC;CAC9D,IAAIC,KAAa,MAAM,OAAO;EAAE,OAAO,KAAA;EAAW;CAAQ;CAC1D,IAAMC,IAAgC,CAAC,GACnCC,IAAQ;CACZ,KAAK,IAAM,CAACC,GAAKC,MAAU,OAAO,QAAQJ,CAAoC,GAC5E,AAAII,MAAU,KAAA,MACTR,EAAW,KAAKO,CAAG,IAGrB,OAAOC,KAAU,YAAY,CAACP,EAAkB,KAAKO,CAAK,KAC1D,OAAOA,KAAU,YAAY,OAAO,SAASA,CAAK,KAEnD,EAAMX,EAAcU,CAAG,KAAK,OAAOC,CAAK,GACxC,IAAQ,MAERL,EAAQ,KAAK;EAAE;EAAK,QAAQ;CAAQ,CAAC,IARrCA,EAAQ,KAAK;EAAE;EAAK,QAAQ;CAAO,CAAC;CAWxC,OAAO;EAAE,OAAOG,IAAQ,KAAA,IAAaD;EAAsB;CAAQ;AACrE;AAsBA,SAAgBI,EACd,GACwB;CACxB,IAAM,EAAE,UAAO,eAAYP,EAAkBE,CAAS;CACtD,KAAK,IAAM,EAAE,QAAK,eAAYD,GAC5B,AAAIO,MAAW,SACbC,EACE,eAAeJ,KACf,oBAAoBA,EAAI,kLAG1B,IAEAI,EACE,gBAAgBJ,KAChB,iCAAiCA,EAAI,qFAEvC;CAGJ,OAAOF;AACT;AAOA,SAAgBO,EACd,GACA,GACoB;CACpB,IAAIC,MAAQ,MAAM,OAAO,CAAC;CAC1B,IAAIA,MAAQ,KAAA,GAAW,OAAOC;CAE9B,IAAMC,IAAU,OAAO,YACrB,OAAO,QAAQF,CAAG,CAAC,CAAC,QAAQ,GAAGL,OAAWA,MAAU,KAAA,CAAS,CAC/D;CACA,OAAO;EAAE,GAAGM;EAAQ,GAAGC;CAAQ;AACjC"}
1
+ {"version":3,"file":"tokens.mjs","names":["tokenVariable","name","capital","TOKEN_NAME","REFUSED_TEXT","isBalancedCssText","closers","quote","index","text","char","collectTokenStyle","invalid","overrides","style","empty","key","value","getTokenStyle","reason","warnOnce","mergeTokenOverrides","own","parent","defined"],"sources":["../../src/lib/tokens.ts"],"sourcesContent":["/**\n * The design tokens in JavaScript: their names, the overrides `WaveProvider` takes and the inline\n * style that applies them. Server-safe and light enough for the provider: it imports only types\n * and the development warnings, never the list of token names (`waveTokens.ts`).\n */\nimport type * as React from 'react';\nimport { warnOnce } from './dev';\nimport type { WAVE_TOKEN_NAMES } from './waveTokens';\n\n/**\n * The name of a design token: its CSS custom property without the `--wave-` prefix, in camelCase.\n * `--wave-primary-foreground` is `primaryForeground`, `--wave-brand-80` is `brand80` and\n * `--wave-palette-red-tint` is `paletteRedTint`.\n */\nexport type WaveTokenName = (typeof WAVE_TOKEN_NAMES)[number];\n\n/** The shape of `waveTokens`: every token name mapped to the CSS value that reads the token. */\nexport type WaveTokens = { readonly [K in WaveTokenName]: string };\n\n/**\n * Token overrides: CSS values by token name, as `WaveProvider tokens` and `getTokenStyle` take\n * them. A number is written as its string (`zOverlay: 2000`); a key whose value is `undefined`\n * sets nothing.\n */\nexport type WaveTokenOverrides = { [K in WaveTokenName]?: string | number };\n\n/**\n * The inline style that applies token overrides: a React style object whose `--wave-*` custom\n * properties set the tokens, so it can be merged with other style properties.\n */\nexport type TokenStyle = React.CSSProperties & { [K in `--wave-${string}`]?: string };\n\n/**\n * The CSS custom property of a token name at the type level: `TokenVariable<'brand80'>` is\n * `'--wave-brand-80'`, by the rule of {@link tokenVariable}. It spells out the literal values of\n * `waveTokens`. It walks the name one character at a time (tail-recursive, so long names stay\n * within the compiler's depth limit): `AfterLetter` says whether the previous character was a\n * letter, `Done` holds the converted text so far.\n */\nexport type TokenVariable<\n Name extends string,\n AfterLetter extends boolean = false,\n Done extends string = '',\n> = Name extends `${infer Char}${infer Rest}`\n ? Char extends '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9'\n ? TokenVariable<Rest, false, `${Done}${AfterLetter extends true ? '-' : ''}${Char}`>\n : Char extends Lowercase<Char>\n ? TokenVariable<Rest, true, `${Done}${Char}`>\n : TokenVariable<Rest, true, `${Done}-${Lowercase<Char>}`>\n : `--wave-${Done}`;\n\n/**\n * The CSS custom property of a token name: a hyphen before each capital letter and before a digit\n * run that follows a letter, lowercased, with the `--wave-` prefix. `'paletteRedTint'` gives\n * `'--wave-palette-red-tint'` and `'brand80'` gives `'--wave-brand-80'`. The name is not checked\n * against the list of tokens.\n */\nexport function tokenVariable(name: string): string {\n return `--wave-${name\n .replace(/([A-Za-z])(\\d)/g, '$1-$2')\n .replace(/[A-Z]/g, (capital) => `-${capital}`)\n .toLowerCase()}`;\n}\n\n/** A camelCase identifier: the only key shape a token name can have. */\nconst TOKEN_NAME = /^[a-z][A-Za-z0-9]*$/;\n\n/**\n * The text no token value may hold, anywhere in it. React writes a custom property's value into\n * the server-rendered `style` attribute as text, where a semicolon would end the declaration and\n * let the rest of the value declare a property of its own (and a brace could close the rule around\n * it); in the client the CSSOM refuses a value with a semicolon, so the two renders would also\n * differ. `url(`, in any case: no token takes a URL, and an unquoted one ends at its first `)`\n * whatever it holds, which the balance check below does not follow (`url(a\"b)c\")` would open a\n * string after that `)`); the check refuses the escapes that could spell it otherwise (`u\\72l(`).\n */\nconst REFUSED_TEXT = /[;{}]|url\\(/i;\n\n/**\n * Whether `text` closes everything it opens, read as the CSS tokenizer reads it: every string\n * closes on its own line (a backslash in it escapes the next character, a line break included),\n * every comment closes, and the parentheses and square brackets outside strings and comments pair\n * up and nest. In the server-rendered `style` attribute an open string, comment, parenthesis or\n * bracket would run on to the end of the attribute, so the declarations after it would be lost\n * there, while the client sets each property on its own. Refused as well, outside strings and\n * comments: a stray closer, which the client refuses in a custom property; a `!`, since the server\n * would read `!important` as the declaration's priority while the client refuses a `!` at the top\n * level (one nested in a function, as in `color-mix(in srgb, red !important, blue)`, is valid there, and refusing\n * it is deliberate: no token needs it); and a backslash, which no token needs there and which\n * could spell `url(` with an escape or, at the end, escape the semicolon after the value. One pass\n * over the text, each character read once and the stack an array, so the time grows with the\n * length alone and a long crafted value cannot stall a server render.\n */\nfunction isBalancedCssText(text: string): boolean {\n // The closers of the open parentheses and brackets, the innermost last.\n const closers: string[] = [];\n // The quote of the open string, '' outside one.\n let quote = '';\n let index = 0;\n for (; index < text.length; index++) {\n const char = text[index];\n if (quote) {\n // An escape takes the next character; a backslash at the end leaves the string open.\n if (char === '\\\\') index++;\n else if (char === quote) quote = '';\n else if ('\\n\\r\\f'.includes(char)) return false;\n } else if ('\"\\''.includes(char)) {\n quote = char;\n } else if (char === '/' && text[index + 1] === '*') {\n // A comment runs to its first `*/` and escapes nothing; one that never ends moves the index\n // past the end.\n index = text.indexOf('*/', index + 2) + 1 || Infinity;\n } else if ('(['.includes(char)) {\n closers.push(char === '(' ? ')' : ']');\n } else if (')]!\\\\'.includes(char) && closers.pop() !== char) {\n // A closer that is not the innermost one's, or a `!` or a backslash, which no opener\n // expects: the index moves past the end, which fails below.\n index = Infinity;\n }\n }\n return index === text.length && !quote && !closers.length;\n}\n\n/**\n * The custom properties of `overrides`, and the entries it skipped. Pure: it never warns, so the\n * provider can call it during render; {@link getTokenStyle} reports the skipped entries.\n *\n * `undefined` values are skipped first (they set nothing); a key that is not a camelCase\n * identifier is skipped with the reason `'name'`, and a value with the reason `'value'` unless it\n * is a finite number or a string without `;`, `{`, `}` or `url(` that closes every string,\n * comment, parenthesis and square bracket it opens and has no stray closer, `!` or backslash\n * outside strings and comments. Numbers are written as strings, and every value is trimmed with\n * JavaScript's `trim`, as React's server renderer trims a custom property's value (the client sets\n * the value as given), so both write the same text; a value that is empty after it is written as\n * `''`, for which React renders no declaration on either side. The style keeps the order of the\n * input. `style` is `undefined` when nothing is left.\n */\nexport function collectTokenStyle(overrides: WaveTokenOverrides | null | undefined): {\n style: TokenStyle | undefined;\n invalid: readonly { key: string; reason: 'name' | 'value' }[];\n} {\n const invalid: { key: string; reason: 'name' | 'value' }[] = [];\n if (overrides == null) return { style: undefined, invalid };\n const style: Record<string, string> = {};\n let empty = true;\n for (const [key, value] of Object.entries(overrides as Record<string, unknown>)) {\n if (value === undefined) continue;\n if (!TOKEN_NAME.test(key)) {\n invalid.push({ key, reason: 'name' });\n } else if (\n (typeof value === 'string' && !REFUSED_TEXT.test(value) && isBalancedCssText(value)) ||\n (typeof value === 'number' && Number.isFinite(value))\n ) {\n style[tokenVariable(key)] = String(value).trim();\n empty = false;\n } else {\n invalid.push({ key, reason: 'value' });\n }\n }\n return { style: empty ? undefined : (style as TokenStyle), invalid };\n}\n\n/**\n * The inline style that applies token overrides: one `--wave-*` custom property per entry, the\n * object `WaveProvider` puts on its root and its portals. Use it where no provider renders, such\n * as a Server Component's document element. Server-safe.\n *\n * Returns `undefined` for `null`, `undefined` or a map with nothing to set. An `undefined` value is\n * skipped, a number is written as its string, a string is trimmed as React's server renderer trims\n * it, and the properties keep the order of the input. A key that is not a camelCase identifier is\n * skipped with a development warning (once per key), and so is a value unless it is a finite\n * number or CSS text without `;`, `{`, `}` or `url(` whose quotes, parentheses, square brackets and\n * comments are balanced, with no `!` or backslash outside strings and comments. In a\n * server-rendered `style` attribute such a character could add a declaration of its own, an open\n * string, comment or bracket (or an escape that spells `url(` or takes the semicolon after the\n * value) would swallow the declarations after it, and `!important` would set a priority the client\n * refuses; the trimmed values that pass are read alike by the attribute and by the client. Names\n * are not checked against the list of tokens: TypeScript refuses a misspelled one, and in\n * JavaScript it sets a property nothing reads.\n *\n * @example\n * // app/layout.tsx (a Server Component)\n * <html style={getTokenStyle({ brand80: '#c43e00' })}>\n *\n * @example\n * <div style={{ ...getTokenStyle(overrides), padding: 16 }} />\n */\nexport function getTokenStyle(\n overrides: WaveTokenOverrides | null | undefined,\n): TokenStyle | undefined {\n const { style, invalid } = collectTokenStyle(overrides);\n for (const { key, reason } of invalid) {\n if (reason === 'name') {\n warnOnce(\n `tokens:name:${key}`,\n `getTokenStyle: \\`${key}\\` is not a token name, so it is skipped. Token names are the CSS ` +\n 'variable in camelCase without its `--wave-` prefix (`primaryForeground` for ' +\n '`--wave-primary-foreground`).',\n );\n } else {\n warnOnce(\n `tokens:value:${key}`,\n `getTokenStyle: the value of \\`${key}\\` is skipped: it must be a finite number, or CSS ` +\n 'text with balanced quotes, brackets and comments, without `;`, `{`, `}` or `url(`, and ' +\n 'without `!` or a backslash outside strings and comments.',\n );\n }\n }\n return style;\n}\n\n/**\n * The overrides of a nested provider: `own` merged key by key over `parent`. `undefined` inherits\n * `parent` as it is, `null` clears everything inherited, and a key whose own value is `undefined`\n * keeps the parent's value.\n */\nexport function mergeTokenOverrides(\n parent: WaveTokenOverrides,\n own: WaveTokenOverrides | null | undefined,\n): WaveTokenOverrides {\n if (own === null) return {};\n if (own === undefined) return parent;\n // Spread, never assignment, so a `__proto__` key is an own entry like any other.\n const defined = Object.fromEntries(\n Object.entries(own).filter(([, value]) => value !== undefined),\n );\n return { ...parent, ...defined };\n}\n"],"mappings":";;AAyDA,SAAgBA,EAAc,GAAsB;CAClD,OAAO,UAAUC,EACd,QAAQ,mBAAmB,OAAO,CAAC,CACnC,QAAQ,WAAW,MAAY,IAAIC,GAAS,CAAC,CAC7C,YAAY;AACjB;AAGA,IAAMC,IAAa,uBAWbC,IAAe;AAiBrB,SAASC,EAAkB,GAAuB;CAEhD,IAAMC,IAAoB,CAAC,GAEvBC,IAAQ,IACRC,IAAQ;CACZ,OAAOA,IAAQC,EAAK,QAAQ,KAAS;EACnC,IAAMC,IAAOD,EAAKD;EAClB,IAAID,GAAO;GAET,IAAIG,MAAS,MAAM;QACd,IAAIA,MAASH,GAAO,IAAQ;QAC5B,IAAI,SAAS,SAASG,CAAI,GAAG,OAAO;EAC3C,OAAO,AAAI,MAAM,SAASA,CAAI,IAC5B,IAAQA,IACCA,MAAS,OAAOD,EAAKD,IAAQ,OAAO,MAG7C,IAAQC,EAAK,QAAQ,MAAMD,IAAQ,CAAC,IAAI,KAAK,WACpC,KAAK,SAASE,CAAI,IAC3BJ,EAAQ,KAAKI,MAAS,MAAM,MAAM,GAAG,IAC5B,QAAQ,SAASA,CAAI,KAAKJ,EAAQ,IAAI,MAAMI,MAGrD,IAAQ;CAEZ;CACA,OAAOF,MAAUC,EAAK,UAAU,CAACF,KAAS,CAACD,EAAQ;AACrD;AAgBA,SAAgBK,EAAkB,GAGhC;CACA,IAAMC,IAAuD,CAAC;CAC9D,IAAIC,KAAa,MAAM,OAAO;EAAE,OAAO,KAAA;EAAW;CAAQ;CAC1D,IAAMC,IAAgC,CAAC,GACnCC,IAAQ;CACZ,KAAK,IAAM,CAACC,GAAKC,MAAU,OAAO,QAAQJ,CAAoC,GAC5E,AAAII,MAAU,KAAA,MACTd,EAAW,KAAKa,CAAG,IAGrB,OAAOC,KAAU,YAAY,CAACb,EAAa,KAAKa,CAAK,KAAKZ,EAAkBY,CAAK,KACjF,OAAOA,KAAU,YAAY,OAAO,SAASA,CAAK,KAEnD,EAAMjB,EAAcgB,CAAG,KAAK,OAAOC,CAAK,CAAC,CAAC,KAAK,GAC/C,IAAQ,MAERL,EAAQ,KAAK;EAAE;EAAK,QAAQ;CAAQ,CAAC,IARrCA,EAAQ,KAAK;EAAE;EAAK,QAAQ;CAAO,CAAC;CAWxC,OAAO;EAAE,OAAOG,IAAQ,KAAA,IAAaD;EAAsB;CAAQ;AACrE;AA2BA,SAAgBI,EACd,GACwB;CACxB,IAAM,EAAE,UAAO,eAAYP,EAAkBE,CAAS;CACtD,KAAK,IAAM,EAAE,QAAK,eAAYD,GAC5B,AAAIO,MAAW,SACbC,EACE,eAAeJ,KACf,oBAAoBA,EAAI,kLAG1B,IAEAI,EACE,gBAAgBJ,KAChB,iCAAiCA,EAAI,4MAGvC;CAGJ,OAAOF;AACT;AAOA,SAAgBO,EACd,GACA,GACoB;CACpB,IAAIC,MAAQ,MAAM,OAAO,CAAC;CAC1B,IAAIA,MAAQ,KAAA,GAAW,OAAOC;CAE9B,IAAMC,IAAU,OAAO,YACrB,OAAO,QAAQF,CAAG,CAAC,CAAC,QAAQ,GAAGL,OAAWA,MAAU,KAAA,CAAS,CAC/D;CACA,OAAO;EAAE,GAAGM;EAAQ,GAAGC;CAAQ;AACjC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mortenbrudvik/waveui",
3
- "version": "0.17.0",
3
+ "version": "0.17.1",
4
4
  "type": "module",
5
5
  "description": "A comprehensive React component library with modern design tokens — accessible, composable components built with TypeScript and Tailwind CSS",
6
6
  "license": "MIT",