eidos-ui 3.0.0 → 3.1.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.
@@ -1 +1 @@
1
- {"version":3,"sources":["/home/runner/work/eidos-ui/eidos-ui/dist/chunk-SE7MWOC7.cjs","../src/components/ThemeEditor/ThemeEditor.component.tsx"],"names":["ratio","grade"],"mappings":"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","file":"/home/runner/work/eidos-ui/eidos-ui/dist/chunk-SE7MWOC7.cjs","sourcesContent":[null,"import React, { useCallback, useEffect, useMemo, useReducer, useRef, useState } from 'react';\nimport { CircleCheck, CircleX, TriangleAlert } from 'lucide-react';\nimport { Alert } from '../Alert';\nimport { Button, IconButton } from '../Button';\nimport { Card } from '../Card';\nimport { ColorPicker } from '../ColorPicker';\nimport { Select } from '../Select';\nimport { Slider } from '../Slider';\nimport { Tooltip } from '../Tooltip';\nimport { useTheme } from '../ThemeProvider';\nimport type { ThemeColorKey, ThemeFontOption } from '../ThemeProvider';\nimport {\n THEME_COLOR_KEYS,\n FONT_SCALE_MAX,\n FONT_SCALE_MIN,\n defaultTheme,\n} from '../ThemeProvider/ThemeProvider.tokens';\nimport {\n CONTRAST_DARK,\n CONTRAST_LIGHT,\n contrastRatio,\n pickContrast,\n} from '../ThemeProvider/ThemeProvider.color';\nimport {\n FONT_ACCEPT,\n isFontStackAvailable,\n registerFontFile,\n subscribeToFontLoads,\n toFontStack,\n} from '../ThemeProvider/ThemeProvider.fonts';\nimport type { ThemeEditorProps } from './ThemeEditor.types';\n\n/** The page surface every diagnostic is measured against - `--white`. */\nconst PAGE_SURFACE = CONTRAST_LIGHT;\n\n/** Below this, a fill is effectively invisible against the page behind it. */\nconst EDGE_MIN_RATIO = 1.5;\n\nconst COLOR_LABELS: Record<ThemeColorKey, string> = {\n primary: 'Primary',\n secondary: 'Secondary',\n success: 'Success',\n danger: 'Danger',\n warning: 'Warning',\n info: 'Info',\n hyperlink: 'Hyperlink',\n};\n\n/** `hyperlink` is only ever text on a surface, so it is graded differently. */\nconst isFill = (key: ThemeColorKey): boolean => key !== 'hyperlink';\n\n/**\n * Built-in stacks, deliberately limited to fonts that can be relied on.\n *\n * A dropdown implies its entries are available, but a font stack is only a list\n * of *names* - nothing here can install a font. So the list is restricted to\n * generic families (guaranteed by CSS), `system-ui` (whatever the OS provides),\n * and the handful of genuinely web-safe faces. Fonts like Inter, which are not\n * installed anywhere by default, were removed: offering one is offering a\n * choice that silently does nothing on most machines.\n *\n * The preset is included because it is the shipped default and therefore has to\n * be representable - and note that `eidos-ui` does not ship the Plus Jakarta\n * Sans file either, so it renders only where the consumer provides it. That is\n * exactly why unavailable options are labelled as such.\n *\n * Anything beyond this belongs to the consuming app, via `fontOptions`.\n */\nconst FONT_STACKS: ThemeFontOption[] = [\n // Taken from the preset rather than retyped, so the shipped default always\n // matches an option exactly. A hand-copied shorter stack did not match, and\n // the row fell back to showing a synthetic \"custom\" entry instead of the\n // name of the font actually in use.\n { label: 'Plus Jakarta Sans (theme default)', value: defaultTheme.typography.fontFamily! },\n { label: 'System UI', value: \"system-ui, -apple-system, 'Segoe UI', sans-serif\" },\n { label: 'Helvetica / Arial', value: 'Helvetica, Arial, sans-serif' },\n { label: 'Georgia', value: \"Georgia, 'Times New Roman', serif\" },\n { label: 'Sans-serif (generic)', value: 'sans-serif' },\n { label: 'Serif (generic)', value: 'serif' },\n];\n\nconst MONO_STACKS: ThemeFontOption[] = [\n { label: 'JetBrains Mono (theme default)', value: defaultTheme.typography.monoFamily! },\n { label: 'Menlo / Consolas', value: 'Menlo, Consolas, monospace' },\n { label: 'Courier New', value: \"'Courier New', Courier, monospace\" },\n { label: 'Monospace (generic)', value: 'monospace' },\n];\n\ntype Grade = 'aa' | 'aa-large' | 'fail';\n\nconst gradeFor = (ratio: number): Grade => (ratio >= 4.5 ? 'aa' : ratio >= 3 ? 'aa-large' : 'fail');\n\n/** Spelled out for the middle grade, which is the one that misleads. */\nconst GRADE_CAVEAT: Partial<Record<Grade, string>> = {\n 'aa-large':\n ' WCAG AA needs 4.5:1 for normal text and allows 3:1 only at 24px or larger - button and label text here is around 12px, so this does not pass.',\n fail: ' Below 3:1, which fails WCAG AA at every text size.',\n};\n\n/**\n * Severity is carried by an icon and the label text, not by colour.\n *\n * These badges cannot use `Pill`, or any themed `--x-color`: the theme being\n * edited also styles this editor, so a washed-out `success` colour would render\n * the \"passes AA\" badge illegible at precisely the moment it is reporting that\n * a colour is illegible. The badge styling comes from the gray ramp, which is\n * deliberately not themeable.\n *\n * Not relying on colour alone also satisfies WCAG 1.4.1 (Use of Colour).\n */\nconst GRADE_BADGE: Record<\n Grade,\n { label: string; icon: React.ComponentType<{ className?: string }> }\n> = {\n aa: { label: 'AA', icon: CircleCheck },\n // Named for what it means in practice, not just for the threshold it clears.\n // WCAG's relaxed 3:1 tier applies only to text at 24px (or 18.7px bold) and\n // above, and nothing in this library is that large - `--font-size-sm` is\n // ~12.25px at the default root size. A bare \"AA large only\" reads as a pass\n // when for every component here it is a fail.\n 'aa-large': { label: 'Too low for body text', icon: TriangleAlert },\n fail: { label: 'Fails AA', icon: CircleX },\n};\n\ninterface Diagnostic {\n grade: Grade;\n ratio: number;\n /** What the ratio describes, for the tooltip. */\n detail: string;\n /** Set when the fill is too close to the page surface to be seen. */\n edgeWarning?: string;\n}\n\nconst diagnose = (key: ThemeColorKey, base: string, explicitContrast?: string): Diagnostic => {\n if (!isFill(key)) {\n // Link text sits on the page, so the page is what it must contrast with.\n const ratio = contrastRatio(base, PAGE_SURFACE);\n const grade = gradeFor(ratio);\n return {\n grade,\n ratio,\n detail: `Link text on the page background: ${ratio.toFixed(2)}:1 against white.${GRADE_CAVEAT[grade] ?? ''}`,\n };\n }\n\n const foreground = explicitContrast ?? pickContrast(base);\n const ratio = contrastRatio(base, foreground);\n const name = foreground.toLowerCase() === CONTRAST_DARK ? 'dark' : 'white';\n const edge = contrastRatio(base, PAGE_SURFACE);\n\n const grade = gradeFor(ratio);\n\n return {\n grade,\n ratio,\n detail: `${name === 'white' ? 'White' : 'Near-black'} text on this fill is ${ratio.toFixed(2)}:1.${\n explicitContrast ? ' Foreground pinned via the theme.' : ''\n }${GRADE_CAVEAT[grade] ?? ''}`,\n edgeWarning:\n edge < EDGE_MIN_RATIO\n ? `Only ${edge.toFixed(2)}:1 against the page background - this fill is barely distinguishable from the page it sits on.`\n : undefined,\n };\n};\n\ninterface FontRowProps {\n label: string;\n /** Stack currently applied, used to pick the matching option. */\n value: string;\n options: ThemeFontOption[];\n mono: boolean;\n allowUpload: boolean;\n onChange: (stack: string) => void;\n onUploaded: (option: ThemeFontOption, file: File, family: string) => void;\n}\n\n/** Pulls the first family out of a stack, unquoted. */\nconst firstFamily = (stack: string): string =>\n stack\n .split(',')[0]\n ?.trim()\n .replace(/^['\"]|['\"]$/g, '') || 'Custom';\n\n/**\n * One font row: a `Select` over the known stacks, an optional upload, and an\n * availability warning.\n *\n * Uses `Select` rather than `Combobox` deliberately. `Combobox` filters its\n * options against the text in the field, and the field holds a full font stack\n * (`'Plus Jakarta Sans', -apple-system, …`) which matches no option label - so\n * the list read \"No results found\" until the text was cleared by hand. A font\n * is a choice from a known set, not a search.\n */\nconst FontRow: React.FC<FontRowProps> = ({\n label,\n value,\n options,\n mono,\n allowUpload,\n onChange,\n onUploaded,\n}) => {\n const inputRef = useRef<HTMLInputElement>(null);\n const [error, setError] = useState<string | null>(null);\n\n // Web fonts load asynchronously, so the first render always sees them as\n // unavailable. Without re-checking once they arrive, the row would sit on a\n // stale \"not installed\" warning for a font that is rendering perfectly.\n // The reducer exists only to schedule that re-render; its value is unused.\n const [, onFontsLoaded] = useReducer((count: number) => count + 1, 0);\n useEffect(() => subscribeToFontLoads(onFontsLoaded), [onFontsLoaded]);\n\n const baseOptions = useMemo(() => {\n // A stack the theme carries but that isn't in the list (loaded by the host\n // app, or set programmatically) still needs to be selectable, labelled with\n // its own first family so the row names the font actually in use.\n const known = options.some((o) => o.value === value);\n return known ? options : [...options, { label: `${firstFamily(value)} (custom)`, value }];\n }, [options, value]);\n\n // Probed on every render rather than memoised: availability changes over time\n // as fonts load, so a cached answer is a wrong answer. Four canvas\n // measurements are far cheaper than getting this wrong.\n //\n // Availability is annotated per option rather than filtered or disabled. A\n // theme is shared across machines, so a font present here may be absent for\n // the next user - and vice versa, which would mean hiding a font someone\n // legitimately wants. Annotating keeps every choice selectable while making\n // the list stop implying that a name is a guarantee.\n const selectOptions = baseOptions.map((option) => ({\n id: option.value,\n label: isFontStackAvailable(option.value) ? option.label : `${option.label} - not installed`,\n value: option.value,\n }));\n\n const handleFile = useCallback(\n async (file: File | undefined) => {\n if (!file) return;\n setError(null);\n try {\n const family = await registerFontFile(file);\n const stack = toFontStack(family, mono);\n onUploaded({ label: `${family} (this session)`, value: stack }, file, family);\n onChange(stack);\n } catch (cause) {\n setError(\n cause instanceof Error ? cause.message : 'That file could not be loaded as a font.',\n );\n }\n },\n [mono, onChange, onUploaded],\n );\n\n const unavailable = !isFontStackAvailable(value);\n\n return (\n <div className=\"eidos-theme-editor-row eidos-theme-editor-row--wide\">\n <span className=\"eidos-theme-editor-row__label\">{label}</span>\n\n <div className=\"eidos-theme-editor-row__control\">\n <Select\n options={selectOptions}\n value={value}\n onChange={(next) => onChange(Array.isArray(next) ? next[0] : next)}\n fullWidth\n />\n </div>\n\n {/* Occasional - lives before the action so it can never displace it. */}\n <div className=\"eidos-theme-editor-row__diagnostics\">\n {unavailable && (\n <Tooltip\n message={`${firstFamily(value)} isn't available here, so the browser is rendering the next family in the stack instead. Upload the file, or have your app serve it, to use it.`}\n >\n <span className=\"eidos-theme-editor-badge eidos-theme-editor-badge--aa-large\">\n <TriangleAlert className=\"eidos-theme-editor-badge__icon\" />\n Not installed\n </span>\n </Tooltip>\n )}\n {error && (\n <Tooltip message={error}>\n <span className=\"eidos-theme-editor-badge eidos-theme-editor-badge--fail\">\n <CircleX className=\"eidos-theme-editor-badge__icon\" />\n Upload failed\n </span>\n </Tooltip>\n )}\n </div>\n\n {allowUpload && (\n <div className=\"eidos-theme-editor-row__actions\">\n <Button\n variant=\"text\"\n color=\"secondary\"\n size=\"sm\"\n preIcon=\"Upload\"\n onClick={() => inputRef.current?.click()}\n tooltip=\"Load a font file to use it right away. Not saved - see onFontUpload to persist it.\"\n >\n Upload\n </Button>\n {/* Plain input rather than FileUpload: this is a single inline\n action in a dense row, not a drop zone. */}\n <input\n ref={inputRef}\n type=\"file\"\n accept={FONT_ACCEPT}\n className=\"eidos-theme-editor-file\"\n onChange={(event) => {\n void handleFile(event.target.files?.[0]);\n // Allow re-picking the same file after a failure.\n event.target.value = '';\n }}\n />\n </div>\n )}\n </div>\n );\n};\n\n/**\n * Live editor for the active `ThemeProvider` theme.\n *\n * Renders no preview of its own: the provider writes tokens to\n * `document.documentElement`, so the surrounding page is the preview.\n *\n * Contrast readouts are diagnostics only - a colour that fails is still\n * applied. Automatically \"correcting\" a chosen colour would mean silently\n * rendering something other than what was picked.\n */\nexport const ThemeEditor: React.FC<ThemeEditorProps> = ({\n colors = THEME_COLOR_KEYS as ThemeColorKey[],\n fontOptions = FONT_STACKS,\n monoFontOptions = MONO_STACKS,\n allowFontUpload = true,\n onFontUpload,\n hideTypography = false,\n hideReset = false,\n hideCssExport = false,\n className,\n}) => {\n const { theme, resolvedTheme, setTheme, updateTheme, resetTheme, isDefault, toCss } = useTheme();\n const [copied, setCopied] = useState(false);\n\n // Fonts registered through the upload control this session. Kept local\n // because font *data* cannot live in a ThemeConfig - see the .mdx.\n const [uploaded, setUploaded] = useState<ThemeFontOption[]>([]);\n const [monoUploaded, setMonoUploaded] = useState<ThemeFontOption[]>([]);\n\n const setColor = useCallback(\n (key: ThemeColorKey, hex: string) => {\n const current = theme.colors?.[key];\n // Only the base is written here. A `contrast` pinned in the theme is\n // preserved rather than dropped, since the editor never sets one.\n const pinned = typeof current === 'object' ? current.contrast : undefined;\n updateTheme({ colors: { [key]: pinned ? { base: hex, contrast: pinned } : hex } });\n },\n [theme.colors, updateTheme],\n );\n\n const resetColor = useCallback(\n (key: ThemeColorKey) => {\n // Must go through setTheme, not updateTheme: updateTheme merges, so a key\n // left out of the patch keeps its existing value instead of being removed.\n // Removal is what restores the preset, since resolveTheme fills gaps.\n const nextColors = { ...theme.colors };\n delete nextColors[key];\n setTheme({ ...theme, colors: nextColors });\n },\n [theme, setTheme],\n );\n\n const diagnostics = useMemo(\n () =>\n colors.map((key) => {\n const { base, contrast } = resolvedTheme.colors[key];\n return { key, base, ...diagnose(key, base, contrast) };\n }),\n [colors, resolvedTheme],\n );\n\n const failing = diagnostics.filter((d) => d.grade === 'fail');\n const invisible = diagnostics.filter((d) => d.edgeWarning);\n\n const copyCss = useCallback(() => {\n const css = toCss();\n const done = () => {\n setCopied(true);\n setTimeout(() => setCopied(false), 1600);\n };\n if (navigator.clipboard?.writeText) {\n navigator.clipboard.writeText(css).then(done, done);\n return;\n }\n done();\n }, [toCss]);\n\n return (\n <div className={['eidos-theme-editor', className].filter(Boolean).join(' ')}>\n {(failing.length > 0 || invisible.length > 0) && (\n <Alert variant=\"warning\" title=\"Contrast issues\">\n {failing.length > 0 && (\n <p>\n Cannot reach WCAG AA (4.5:1) with either foreground:{' '}\n {failing.map((d) => COLOR_LABELS[d.key]).join(', ')}.\n </p>\n )}\n {invisible.length > 0 && (\n <p>\n Too close to the page background to read as a filled surface:{' '}\n {invisible.map((d) => COLOR_LABELS[d.key]).join(', ')}.\n </p>\n )}\n </Alert>\n )}\n\n <Card variant=\"outlined\" padding=\"lg\" className=\"eidos-theme-editor-section\">\n <div className=\"eidos-theme-editor-section__header\">\n <span className=\"eidos-theme-editor-section__title\">Colours</span>\n <span className=\"eidos-theme-editor-section__hint\">\n Shades, tints and foregrounds are derived from each base colour.\n </span>\n </div>\n\n <div className=\"eidos-theme-editor-rows\">\n {diagnostics.map(({ key, base, grade, ratio, detail, edgeWarning }) => {\n const badge = GRADE_BADGE[grade];\n const overridden = theme.colors?.[key] !== undefined;\n return (\n <div className=\"eidos-theme-editor-row\" key={key}>\n <span className=\"eidos-theme-editor-row__label\">{COLOR_LABELS[key]}</span>\n\n {/* `md` rather than `sm`: the size drives the panel's\n saturation/brightness canvas, and 120px is cramped for\n picking a lightness precisely. */}\n <ColorPicker value={base} onChange={(hex) => setColor(key, hex)} size=\"md\" />\n\n <div className=\"eidos-theme-editor-row__diagnostics\">\n <Tooltip message={detail}>\n <span className={`eidos-theme-editor-badge eidos-theme-editor-badge--${grade}`}>\n <badge.icon className=\"eidos-theme-editor-badge__icon\" />\n {`${ratio.toFixed(2)}:1 ${badge.label}`}\n </span>\n </Tooltip>\n {edgeWarning && (\n <Tooltip message={edgeWarning}>\n <span className=\"eidos-theme-editor-badge eidos-theme-editor-badge--fail\">\n <CircleX className=\"eidos-theme-editor-badge__icon\" />\n Invisible on page\n </span>\n </Tooltip>\n )}\n </div>\n\n <div className=\"eidos-theme-editor-row__actions\">\n <IconButton\n icon=\"RotateCcw\"\n variant=\"text\"\n color=\"secondary\"\n size=\"sm\"\n disabled={!overridden}\n onClick={() => resetColor(key)}\n tooltip={overridden ? `Reset ${COLOR_LABELS[key]} to the preset` : 'Unchanged'}\n aria-label={`Reset ${COLOR_LABELS[key]}`}\n />\n </div>\n </div>\n );\n })}\n </div>\n </Card>\n\n {!hideTypography && (\n <Card variant=\"outlined\" padding=\"lg\" className=\"eidos-theme-editor-section\">\n <div className=\"eidos-theme-editor-section__header\">\n <span className=\"eidos-theme-editor-section__title\">Typography</span>\n <span className=\"eidos-theme-editor-section__hint\">\n A stack only names a font - upload the file to use one the browser doesn&apos;t\n already have. Spacing is declared in <code>em</code>, so the scale moves padding with\n the text.\n </span>\n </div>\n\n <div className=\"eidos-theme-editor-rows\">\n <FontRow\n label=\"Body font\"\n value={resolvedTheme.typography.fontFamily}\n options={[...fontOptions, ...uploaded]}\n mono={false}\n allowUpload={allowFontUpload}\n onChange={(fontFamily) => updateTheme({ typography: { fontFamily } })}\n onUploaded={(option, file, family) => {\n setUploaded((prev) => [...prev, option]);\n onFontUpload?.(file, family, false);\n }}\n />\n\n <FontRow\n label=\"Mono font\"\n value={resolvedTheme.typography.monoFamily}\n options={[...monoFontOptions, ...monoUploaded]}\n mono\n allowUpload={allowFontUpload}\n onChange={(monoFamily) => updateTheme({ typography: { monoFamily } })}\n onUploaded={(option, file, family) => {\n setMonoUploaded((prev) => [...prev, option]);\n onFontUpload?.(file, family, true);\n }}\n />\n\n <div className=\"eidos-theme-editor-row eidos-theme-editor-row--wide\">\n <span className=\"eidos-theme-editor-row__label\">Scale</span>\n <div className=\"eidos-theme-editor-row__control\">\n <Slider\n value={resolvedTheme.typography.fontScale}\n min={FONT_SCALE_MIN}\n max={FONT_SCALE_MAX}\n step={0.025}\n showValue\n showMinMax\n unit=\"×\"\n onChange={(value) => updateTheme({ typography: { fontScale: value } })}\n />\n </div>\n </div>\n </div>\n </Card>\n )}\n\n {(!hideReset || !hideCssExport) && (\n <div className=\"eidos-theme-editor-actions\">\n {!hideReset && (\n <Button variant=\"outlined\" color=\"secondary\" disabled={isDefault} onClick={resetTheme}>\n Reset all\n </Button>\n )}\n {!hideCssExport && (\n <Button\n variant=\"outlined\"\n preIcon={copied ? 'Check' : 'Clipboard'}\n onClick={copyCss}\n tooltip=\"Copy the resolved tokens as a :root block you can paste into your own stylesheet\"\n >\n {copied ? 'Copied' : 'Copy as CSS'}\n </Button>\n )}\n </div>\n )}\n </div>\n );\n};\n\nThemeEditor.displayName = 'ThemeEditor';\n"]}
1
+ {"version":3,"sources":["/home/runner/work/eidos-ui/eidos-ui/dist/chunk-JRYTZ5B2.cjs","../src/components/ThemeEditor/ThemeEditor.component.tsx"],"names":["ratio","grade"],"mappings":"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","file":"/home/runner/work/eidos-ui/eidos-ui/dist/chunk-JRYTZ5B2.cjs","sourcesContent":[null,"import React, { useCallback, useEffect, useMemo, useReducer, useRef, useState } from 'react';\nimport { CircleCheck, CircleX, TriangleAlert } from 'lucide-react';\nimport { Alert } from '../Alert';\nimport { Button, IconButton } from '../Button';\nimport { Card } from '../Card';\nimport { ColorPicker } from '../ColorPicker';\nimport { Select } from '../Select';\nimport { Slider } from '../Slider';\nimport { Tooltip } from '../Tooltip';\nimport { useTheme } from '../ThemeProvider';\nimport type { ThemeColorKey, ThemeFontOption } from '../ThemeProvider';\nimport {\n THEME_COLOR_KEYS,\n FONT_SCALE_MAX,\n FONT_SCALE_MIN,\n defaultTheme,\n} from '../ThemeProvider/ThemeProvider.tokens';\nimport {\n CONTRAST_DARK,\n CONTRAST_LIGHT,\n contrastRatio,\n pickContrast,\n} from '../ThemeProvider/ThemeProvider.color';\nimport {\n FONT_ACCEPT,\n isFontStackAvailable,\n registerFontFile,\n subscribeToFontLoads,\n toFontStack,\n} from '../ThemeProvider/ThemeProvider.fonts';\nimport type { ThemeEditorProps } from './ThemeEditor.types';\n\n/** The page surface every diagnostic is measured against - `--white`. */\nconst PAGE_SURFACE = CONTRAST_LIGHT;\n\n/** Below this, a fill is effectively invisible against the page behind it. */\nconst EDGE_MIN_RATIO = 1.5;\n\nconst COLOR_LABELS: Record<ThemeColorKey, string> = {\n primary: 'Primary',\n secondary: 'Secondary',\n success: 'Success',\n danger: 'Danger',\n warning: 'Warning',\n info: 'Info',\n hyperlink: 'Hyperlink',\n};\n\n/** `hyperlink` is only ever text on a surface, so it is graded differently. */\nconst isFill = (key: ThemeColorKey): boolean => key !== 'hyperlink';\n\n/**\n * Built-in stacks, deliberately limited to fonts that can be relied on.\n *\n * A dropdown implies its entries are available, but a font stack is only a list\n * of *names* - nothing here can install a font. So the list is restricted to\n * generic families (guaranteed by CSS), `system-ui` (whatever the OS provides),\n * and the handful of genuinely web-safe faces. Fonts like Inter, which are not\n * installed anywhere by default, were removed: offering one is offering a\n * choice that silently does nothing on most machines.\n *\n * The preset is included because it is the shipped default and therefore has to\n * be representable - and note that `eidos-ui` does not ship the Plus Jakarta\n * Sans file either, so it renders only where the consumer provides it. That is\n * exactly why unavailable options are labelled as such.\n *\n * Anything beyond this belongs to the consuming app, via `fontOptions`.\n */\nconst FONT_STACKS: ThemeFontOption[] = [\n // Taken from the preset rather than retyped, so the shipped default always\n // matches an option exactly. A hand-copied shorter stack did not match, and\n // the row fell back to showing a synthetic \"custom\" entry instead of the\n // name of the font actually in use.\n { label: 'Plus Jakarta Sans (theme default)', value: defaultTheme.typography.fontFamily! },\n { label: 'System UI', value: \"system-ui, -apple-system, 'Segoe UI', sans-serif\" },\n { label: 'Helvetica / Arial', value: 'Helvetica, Arial, sans-serif' },\n { label: 'Georgia', value: \"Georgia, 'Times New Roman', serif\" },\n { label: 'Sans-serif (generic)', value: 'sans-serif' },\n { label: 'Serif (generic)', value: 'serif' },\n];\n\nconst MONO_STACKS: ThemeFontOption[] = [\n { label: 'JetBrains Mono (theme default)', value: defaultTheme.typography.monoFamily! },\n { label: 'Menlo / Consolas', value: 'Menlo, Consolas, monospace' },\n { label: 'Courier New', value: \"'Courier New', Courier, monospace\" },\n { label: 'Monospace (generic)', value: 'monospace' },\n];\n\ntype Grade = 'aa' | 'aa-large' | 'fail';\n\nconst gradeFor = (ratio: number): Grade => (ratio >= 4.5 ? 'aa' : ratio >= 3 ? 'aa-large' : 'fail');\n\n/** Spelled out for the middle grade, which is the one that misleads. */\nconst GRADE_CAVEAT: Partial<Record<Grade, string>> = {\n 'aa-large':\n ' WCAG AA needs 4.5:1 for normal text and allows 3:1 only at 24px or larger - button and label text here is around 12px, so this does not pass.',\n fail: ' Below 3:1, which fails WCAG AA at every text size.',\n};\n\n/**\n * Severity is carried by an icon and the label text, not by colour.\n *\n * These badges cannot use `Pill`, or any themed `--x-color`: the theme being\n * edited also styles this editor, so a washed-out `success` colour would render\n * the \"passes AA\" badge illegible at precisely the moment it is reporting that\n * a colour is illegible. The badge styling comes from the gray ramp, which is\n * deliberately not themeable.\n *\n * Not relying on colour alone also satisfies WCAG 1.4.1 (Use of Colour).\n */\nconst GRADE_BADGE: Record<\n Grade,\n { label: string; icon: React.ComponentType<{ className?: string }> }\n> = {\n aa: { label: 'AA', icon: CircleCheck },\n // Named for what it means in practice, not just for the threshold it clears.\n // WCAG's relaxed 3:1 tier applies only to text at 24px (or 18.7px bold) and\n // above, and nothing in this library is that large - `--font-size-sm` is\n // ~12.25px at the default root size. A bare \"AA large only\" reads as a pass\n // when for every component here it is a fail.\n 'aa-large': { label: 'Too low for body text', icon: TriangleAlert },\n fail: { label: 'Fails AA', icon: CircleX },\n};\n\ninterface Diagnostic {\n grade: Grade;\n ratio: number;\n /** What the ratio describes, for the tooltip. */\n detail: string;\n /** Set when the fill is too close to the page surface to be seen. */\n edgeWarning?: string;\n}\n\nconst diagnose = (key: ThemeColorKey, base: string, explicitContrast?: string): Diagnostic => {\n if (!isFill(key)) {\n // Link text sits on the page, so the page is what it must contrast with.\n const ratio = contrastRatio(base, PAGE_SURFACE);\n const grade = gradeFor(ratio);\n return {\n grade,\n ratio,\n detail: `Link text on the page background: ${ratio.toFixed(2)}:1 against white.${GRADE_CAVEAT[grade] ?? ''}`,\n };\n }\n\n const foreground = explicitContrast ?? pickContrast(base);\n const ratio = contrastRatio(base, foreground);\n const name = foreground.toLowerCase() === CONTRAST_DARK ? 'dark' : 'white';\n const edge = contrastRatio(base, PAGE_SURFACE);\n\n const grade = gradeFor(ratio);\n\n return {\n grade,\n ratio,\n detail: `${name === 'white' ? 'White' : 'Near-black'} text on this fill is ${ratio.toFixed(2)}:1.${\n explicitContrast ? ' Foreground pinned via the theme.' : ''\n }${GRADE_CAVEAT[grade] ?? ''}`,\n edgeWarning:\n edge < EDGE_MIN_RATIO\n ? `Only ${edge.toFixed(2)}:1 against the page background - this fill is barely distinguishable from the page it sits on.`\n : undefined,\n };\n};\n\ninterface FontRowProps {\n label: string;\n /** Stack currently applied, used to pick the matching option. */\n value: string;\n options: ThemeFontOption[];\n mono: boolean;\n allowUpload: boolean;\n onChange: (stack: string) => void;\n onUploaded: (option: ThemeFontOption, file: File, family: string) => void;\n}\n\n/** Pulls the first family out of a stack, unquoted. */\nconst firstFamily = (stack: string): string =>\n stack\n .split(',')[0]\n ?.trim()\n .replace(/^['\"]|['\"]$/g, '') || 'Custom';\n\n/**\n * One font row: a `Select` over the known stacks, an optional upload, and an\n * availability warning.\n *\n * Uses `Select` rather than `Combobox` deliberately. `Combobox` filters its\n * options against the text in the field, and the field holds a full font stack\n * (`'Plus Jakarta Sans', -apple-system, …`) which matches no option label - so\n * the list read \"No results found\" until the text was cleared by hand. A font\n * is a choice from a known set, not a search.\n */\nconst FontRow: React.FC<FontRowProps> = ({\n label,\n value,\n options,\n mono,\n allowUpload,\n onChange,\n onUploaded,\n}) => {\n const inputRef = useRef<HTMLInputElement>(null);\n const [error, setError] = useState<string | null>(null);\n\n // Web fonts load asynchronously, so the first render always sees them as\n // unavailable. Without re-checking once they arrive, the row would sit on a\n // stale \"not installed\" warning for a font that is rendering perfectly.\n // The reducer exists only to schedule that re-render; its value is unused.\n const [, onFontsLoaded] = useReducer((count: number) => count + 1, 0);\n useEffect(() => subscribeToFontLoads(onFontsLoaded), [onFontsLoaded]);\n\n const baseOptions = useMemo(() => {\n // A stack the theme carries but that isn't in the list (loaded by the host\n // app, or set programmatically) still needs to be selectable, labelled with\n // its own first family so the row names the font actually in use.\n const known = options.some((o) => o.value === value);\n return known ? options : [...options, { label: `${firstFamily(value)} (custom)`, value }];\n }, [options, value]);\n\n // Probed on every render rather than memoised: availability changes over time\n // as fonts load, so a cached answer is a wrong answer. Four canvas\n // measurements are far cheaper than getting this wrong.\n //\n // Availability is annotated per option rather than filtered or disabled. A\n // theme is shared across machines, so a font present here may be absent for\n // the next user - and vice versa, which would mean hiding a font someone\n // legitimately wants. Annotating keeps every choice selectable while making\n // the list stop implying that a name is a guarantee.\n const selectOptions = baseOptions.map((option) => ({\n id: option.value,\n label: isFontStackAvailable(option.value) ? option.label : `${option.label} - not installed`,\n value: option.value,\n }));\n\n const handleFile = useCallback(\n async (file: File | undefined) => {\n if (!file) return;\n setError(null);\n try {\n const family = await registerFontFile(file);\n const stack = toFontStack(family, mono);\n onUploaded({ label: `${family} (this session)`, value: stack }, file, family);\n onChange(stack);\n } catch (cause) {\n setError(\n cause instanceof Error ? cause.message : 'That file could not be loaded as a font.',\n );\n }\n },\n [mono, onChange, onUploaded],\n );\n\n const unavailable = !isFontStackAvailable(value);\n\n return (\n <div className=\"eidos-theme-editor-row eidos-theme-editor-row--wide\">\n <span className=\"eidos-theme-editor-row__label\">{label}</span>\n\n <div className=\"eidos-theme-editor-row__control\">\n <Select\n options={selectOptions}\n value={value}\n onChange={(next) => onChange(Array.isArray(next) ? next[0] : next)}\n fullWidth\n />\n </div>\n\n {/* Occasional - lives before the action so it can never displace it. */}\n <div className=\"eidos-theme-editor-row__diagnostics\">\n {unavailable && (\n <Tooltip\n message={`${firstFamily(value)} isn't available here, so the browser is rendering the next family in the stack instead. Upload the file, or have your app serve it, to use it.`}\n >\n <span className=\"eidos-theme-editor-badge eidos-theme-editor-badge--aa-large\">\n <TriangleAlert className=\"eidos-theme-editor-badge__icon\" />\n Not installed\n </span>\n </Tooltip>\n )}\n {error && (\n <Tooltip message={error}>\n <span className=\"eidos-theme-editor-badge eidos-theme-editor-badge--fail\">\n <CircleX className=\"eidos-theme-editor-badge__icon\" />\n Upload failed\n </span>\n </Tooltip>\n )}\n </div>\n\n {allowUpload && (\n <div className=\"eidos-theme-editor-row__actions\">\n <Button\n variant=\"text\"\n color=\"secondary\"\n size=\"sm\"\n preIcon=\"Upload\"\n onClick={() => inputRef.current?.click()}\n tooltip=\"Load a font file to use it right away. Not saved - see onFontUpload to persist it.\"\n >\n Upload\n </Button>\n {/* Plain input rather than FileUpload: this is a single inline\n action in a dense row, not a drop zone. */}\n <input\n ref={inputRef}\n type=\"file\"\n accept={FONT_ACCEPT}\n className=\"eidos-theme-editor-file\"\n onChange={(event) => {\n void handleFile(event.target.files?.[0]);\n // Allow re-picking the same file after a failure.\n event.target.value = '';\n }}\n />\n </div>\n )}\n </div>\n );\n};\n\n/**\n * Live editor for the active `ThemeProvider` theme.\n *\n * Renders no preview of its own: the provider writes tokens to\n * `document.documentElement`, so the surrounding page is the preview.\n *\n * Contrast readouts are diagnostics only - a colour that fails is still\n * applied. Automatically \"correcting\" a chosen colour would mean silently\n * rendering something other than what was picked.\n */\nexport const ThemeEditor: React.FC<ThemeEditorProps> = ({\n colors = THEME_COLOR_KEYS as ThemeColorKey[],\n fontOptions = FONT_STACKS,\n monoFontOptions = MONO_STACKS,\n allowFontUpload = true,\n onFontUpload,\n hideTypography = false,\n hideReset = false,\n hideCssExport = false,\n className,\n}) => {\n const { theme, resolvedTheme, setTheme, updateTheme, resetTheme, isDefault, toCss } = useTheme();\n const [copied, setCopied] = useState(false);\n\n // Fonts registered through the upload control this session. Kept local\n // because font *data* cannot live in a ThemeConfig - see the .mdx.\n const [uploaded, setUploaded] = useState<ThemeFontOption[]>([]);\n const [monoUploaded, setMonoUploaded] = useState<ThemeFontOption[]>([]);\n\n const setColor = useCallback(\n (key: ThemeColorKey, hex: string) => {\n const current = theme.colors?.[key];\n // Only the base is written here. A `contrast` pinned in the theme is\n // preserved rather than dropped, since the editor never sets one.\n const pinned = typeof current === 'object' ? current.contrast : undefined;\n updateTheme({ colors: { [key]: pinned ? { base: hex, contrast: pinned } : hex } });\n },\n [theme.colors, updateTheme],\n );\n\n const resetColor = useCallback(\n (key: ThemeColorKey) => {\n // Must go through setTheme, not updateTheme: updateTheme merges, so a key\n // left out of the patch keeps its existing value instead of being removed.\n // Removal is what restores the preset, since resolveTheme fills gaps.\n const nextColors = { ...theme.colors };\n delete nextColors[key];\n setTheme({ ...theme, colors: nextColors });\n },\n [theme, setTheme],\n );\n\n const diagnostics = useMemo(\n () =>\n colors.map((key) => {\n const { base, contrast } = resolvedTheme.colors[key];\n return { key, base, ...diagnose(key, base, contrast) };\n }),\n [colors, resolvedTheme],\n );\n\n const failing = diagnostics.filter((d) => d.grade === 'fail');\n const invisible = diagnostics.filter((d) => d.edgeWarning);\n\n const copyCss = useCallback(() => {\n const css = toCss();\n const done = () => {\n setCopied(true);\n setTimeout(() => setCopied(false), 1600);\n };\n if (navigator.clipboard?.writeText) {\n navigator.clipboard.writeText(css).then(done, done);\n return;\n }\n done();\n }, [toCss]);\n\n return (\n <div className={['eidos-theme-editor', className].filter(Boolean).join(' ')}>\n {(failing.length > 0 || invisible.length > 0) && (\n <Alert variant=\"warning\" title=\"Contrast issues\">\n {failing.length > 0 && (\n <p>\n Cannot reach WCAG AA (4.5:1) with either foreground:{' '}\n {failing.map((d) => COLOR_LABELS[d.key]).join(', ')}.\n </p>\n )}\n {invisible.length > 0 && (\n <p>\n Too close to the page background to read as a filled surface:{' '}\n {invisible.map((d) => COLOR_LABELS[d.key]).join(', ')}.\n </p>\n )}\n </Alert>\n )}\n\n <Card variant=\"outlined\" padding=\"lg\" className=\"eidos-theme-editor-section\">\n <div className=\"eidos-theme-editor-section__header\">\n <span className=\"eidos-theme-editor-section__title\">Colours</span>\n <span className=\"eidos-theme-editor-section__hint\">\n Shades, tints and foregrounds are derived from each base colour.\n </span>\n </div>\n\n <div className=\"eidos-theme-editor-rows\">\n {diagnostics.map(({ key, base, grade, ratio, detail, edgeWarning }) => {\n const badge = GRADE_BADGE[grade];\n const overridden = theme.colors?.[key] !== undefined;\n return (\n <div className=\"eidos-theme-editor-row\" key={key}>\n <span className=\"eidos-theme-editor-row__label\">{COLOR_LABELS[key]}</span>\n\n {/* `md` rather than `sm`: the size drives the panel's\n saturation/brightness canvas, and 120px is cramped for\n picking a lightness precisely. */}\n <ColorPicker value={base} onChange={(hex) => setColor(key, hex)} size=\"md\" />\n\n <div className=\"eidos-theme-editor-row__diagnostics\">\n <Tooltip message={detail}>\n <span className={`eidos-theme-editor-badge eidos-theme-editor-badge--${grade}`}>\n <badge.icon className=\"eidos-theme-editor-badge__icon\" />\n {`${ratio.toFixed(2)}:1 ${badge.label}`}\n </span>\n </Tooltip>\n {edgeWarning && (\n <Tooltip message={edgeWarning}>\n <span className=\"eidos-theme-editor-badge eidos-theme-editor-badge--fail\">\n <CircleX className=\"eidos-theme-editor-badge__icon\" />\n Invisible on page\n </span>\n </Tooltip>\n )}\n </div>\n\n <div className=\"eidos-theme-editor-row__actions\">\n <IconButton\n icon=\"RotateCcw\"\n variant=\"text\"\n color=\"secondary\"\n size=\"sm\"\n disabled={!overridden}\n onClick={() => resetColor(key)}\n tooltip={overridden ? `Reset ${COLOR_LABELS[key]} to the preset` : 'Unchanged'}\n aria-label={`Reset ${COLOR_LABELS[key]}`}\n />\n </div>\n </div>\n );\n })}\n </div>\n </Card>\n\n {!hideTypography && (\n <Card variant=\"outlined\" padding=\"lg\" className=\"eidos-theme-editor-section\">\n <div className=\"eidos-theme-editor-section__header\">\n <span className=\"eidos-theme-editor-section__title\">Typography</span>\n <span className=\"eidos-theme-editor-section__hint\">\n A stack only names a font - upload the file to use one the browser doesn&apos;t\n already have. Spacing is declared in <code>em</code>, so the scale moves padding with\n the text.\n </span>\n </div>\n\n <div className=\"eidos-theme-editor-rows\">\n <FontRow\n label=\"Body font\"\n value={resolvedTheme.typography.fontFamily}\n options={[...fontOptions, ...uploaded]}\n mono={false}\n allowUpload={allowFontUpload}\n onChange={(fontFamily) => updateTheme({ typography: { fontFamily } })}\n onUploaded={(option, file, family) => {\n setUploaded((prev) => [...prev, option]);\n onFontUpload?.(file, family, false);\n }}\n />\n\n <FontRow\n label=\"Mono font\"\n value={resolvedTheme.typography.monoFamily}\n options={[...monoFontOptions, ...monoUploaded]}\n mono\n allowUpload={allowFontUpload}\n onChange={(monoFamily) => updateTheme({ typography: { monoFamily } })}\n onUploaded={(option, file, family) => {\n setMonoUploaded((prev) => [...prev, option]);\n onFontUpload?.(file, family, true);\n }}\n />\n\n <div className=\"eidos-theme-editor-row eidos-theme-editor-row--wide\">\n <span className=\"eidos-theme-editor-row__label\">Scale</span>\n <div className=\"eidos-theme-editor-row__control\">\n <Slider\n value={resolvedTheme.typography.fontScale}\n min={FONT_SCALE_MIN}\n max={FONT_SCALE_MAX}\n step={0.025}\n showValue\n showMinMax\n unit=\"×\"\n onChange={(value) => updateTheme({ typography: { fontScale: value } })}\n />\n </div>\n </div>\n </div>\n </Card>\n )}\n\n {(!hideReset || !hideCssExport) && (\n <div className=\"eidos-theme-editor-actions\">\n {!hideReset && (\n <Button variant=\"outlined\" color=\"secondary\" disabled={isDefault} onClick={resetTheme}>\n Reset all\n </Button>\n )}\n {!hideCssExport && (\n <Button\n variant=\"outlined\"\n preIcon={copied ? 'Check' : 'Clipboard'}\n onClick={copyCss}\n tooltip=\"Copy the resolved tokens as a :root block you can paste into your own stylesheet\"\n >\n {copied ? 'Copied' : 'Copy as CSS'}\n </Button>\n )}\n </div>\n )}\n </div>\n );\n};\n\nThemeEditor.displayName = 'ThemeEditor';\n"]}
@@ -4,6 +4,7 @@ var _chunkGEHL4LKKcjs = require('./chunk-GEHL4LKK.cjs');
4
4
 
5
5
  // src/components/Tabs/Tabs.component.tsx
6
6
  var _react = require('react'); var _react2 = _interopRequireDefault(_react);
7
+ var _lucidereact = require('lucide-react');
7
8
  var _jsxruntime = require('react/jsx-runtime');
8
9
  var TabsContext = _react2.default.createContext(null);
9
10
  var useTabsContext = () => {
@@ -93,12 +94,23 @@ var Tabs = ({
93
94
  size = "md",
94
95
  color = "primary",
95
96
  fullWidth = false,
97
+ scrollButtons = "auto",
96
98
  className = "",
97
99
  children
98
100
  }) => {
99
101
  const isControlled = value !== void 0;
100
102
  const [localValue, setLocalValue] = _react.useState.call(void 0, _nullishCoalesce(defaultValue, () => ( "")));
101
103
  const activeValue = isControlled ? value : localValue;
104
+ const tabChildren = [];
105
+ const panelChildren = [];
106
+ _react2.default.Children.forEach(children, (child) => {
107
+ if (!_react2.default.isValidElement(child)) return;
108
+ if (child.type === Tab) {
109
+ tabChildren.push(child);
110
+ } else if (child.type === TabPanel) {
111
+ panelChildren.push(child);
112
+ }
113
+ });
102
114
  const listRef = _react.useRef.call(void 0, null);
103
115
  const indicatorRef = _react.useRef.call(void 0, null);
104
116
  const hasPositioned = _react.useRef.call(void 0, false);
@@ -120,28 +132,73 @@ var Tabs = ({
120
132
  hasPositioned.current = true;
121
133
  }
122
134
  }, [activeValue, variant, fullWidth]);
135
+ _react.useLayoutEffect.call(void 0, () => {
136
+ const list = listRef.current;
137
+ if (!list) return;
138
+ const activeTab = list.querySelector('[aria-selected="true"]');
139
+ if (!activeTab) return;
140
+ const left = activeTab.offsetLeft;
141
+ const right = left + activeTab.offsetWidth;
142
+ if (left < list.scrollLeft) {
143
+ list.scrollLeft = left;
144
+ } else if (right > list.scrollLeft + list.clientWidth) {
145
+ list.scrollLeft = right - list.clientWidth;
146
+ }
147
+ }, [activeValue]);
148
+ const [scrollState, setScrollState] = _react.useState.call(void 0, { canScrollPrev: false, canScrollNext: false });
149
+ const hasScrollButtons = scrollButtons !== "none";
150
+ _react.useEffect.call(void 0, () => {
151
+ if (!hasScrollButtons) return;
152
+ const list = listRef.current;
153
+ if (!list) return;
154
+ const update = () => {
155
+ const maxScroll = list.scrollWidth - list.clientWidth;
156
+ const canScrollPrev = list.scrollLeft > 1;
157
+ const canScrollNext = list.scrollLeft < maxScroll - 1;
158
+ setScrollState(
159
+ (previous) => previous.canScrollPrev === canScrollPrev && previous.canScrollNext === canScrollNext ? previous : { canScrollPrev, canScrollNext }
160
+ );
161
+ };
162
+ update();
163
+ list.addEventListener("scroll", update, { passive: true });
164
+ const observer = new ResizeObserver(update);
165
+ observer.observe(list);
166
+ Array.from(list.children).forEach((child) => observer.observe(child));
167
+ return () => {
168
+ list.removeEventListener("scroll", update);
169
+ observer.disconnect();
170
+ };
171
+ }, [hasScrollButtons, tabChildren.length, size, variant, fullWidth]);
172
+ const scrollByStep = (direction) => {
173
+ const list = listRef.current;
174
+ if (!list) return;
175
+ const step = list.clientWidth * 0.8;
176
+ const reducedMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
177
+ list.scrollBy({ left: direction * step, behavior: reducedMotion ? "auto" : "smooth" });
178
+ };
123
179
  const onSelect = (newValue) => {
124
180
  if (!isControlled) {
125
181
  setLocalValue(newValue);
126
182
  }
127
183
  _optionalChain([onChange, 'optionalCall', _ => _(newValue)]);
128
184
  };
129
- const tabChildren = [];
130
- const panelChildren = [];
131
- _react2.default.Children.forEach(children, (child) => {
132
- if (!_react2.default.isValidElement(child)) return;
133
- if (child.type === Tab) {
134
- tabChildren.push(child);
135
- } else if (child.type === TabPanel) {
136
- panelChildren.push(child);
137
- }
138
- });
139
185
  const rootClasses = ["eidos-tabs", `eidos-tabs--${variant}`, `eidos-tabs--${color}`, className].filter(Boolean).join(" ");
140
186
  const listClasses = [
141
187
  "eidos-tabs-list",
142
188
  `eidos-tabs-list--${size}`,
143
- fullWidth && "eidos-tabs-list--fullWidth"
189
+ fullWidth && "eidos-tabs-list--fullWidth",
190
+ // With buttons, they are the affordance - a native scrollbar underneath
191
+ // them is redundant, and on a space-taking one it would also sit over the
192
+ // `line` variant's rule.
193
+ hasScrollButtons && "eidos-tabs-list--hideScrollbar"
144
194
  ].filter(Boolean).join(" ");
195
+ const showPrevButton = hasScrollButtons && scrollState.canScrollPrev;
196
+ const showNextButton = hasScrollButtons && scrollState.canScrollNext;
197
+ const scrollButtonClasses = (direction) => [
198
+ "eidos-tabs-scroll-button",
199
+ `eidos-tabs-scroll-button--${direction}`,
200
+ `eidos-tabs-scroll-button--${size}`
201
+ ].join(" ");
145
202
  const contextValue = {
146
203
  activeValue,
147
204
  onSelect,
@@ -152,9 +209,33 @@ var Tabs = ({
152
209
  listRef
153
210
  };
154
211
  return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, TabsContext.Provider, { value: contextValue, children: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: rootClasses, children: [
155
- /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { ref: listRef, className: listClasses, role: "tablist", children: [
156
- tabChildren,
157
- variant === "line" && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { ref: indicatorRef, className: "eidos-tabs-indicator", "aria-hidden": "true" })
212
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "eidos-tabs-bar", children: [
213
+ showPrevButton && /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
214
+ "button",
215
+ {
216
+ type: "button",
217
+ className: scrollButtonClasses("prev"),
218
+ onClick: () => scrollByStep(-1),
219
+ tabIndex: -1,
220
+ "aria-hidden": "true",
221
+ children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _lucidereact.ChevronLeft, { className: "eidos-tabs-scroll-button-icon" })
222
+ }
223
+ ),
224
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { ref: listRef, className: listClasses, role: "tablist", children: [
225
+ tabChildren,
226
+ variant === "line" && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { ref: indicatorRef, className: "eidos-tabs-indicator", "aria-hidden": "true" })
227
+ ] }),
228
+ showNextButton && /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
229
+ "button",
230
+ {
231
+ type: "button",
232
+ className: scrollButtonClasses("next"),
233
+ onClick: () => scrollByStep(1),
234
+ tabIndex: -1,
235
+ "aria-hidden": "true",
236
+ children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _lucidereact.ChevronRight, { className: "eidos-tabs-scroll-button-icon" })
237
+ }
238
+ )
158
239
  ] }),
159
240
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "eidos-tabs-panels", children: panelChildren })
160
241
  ] }) });
@@ -166,4 +247,4 @@ Tabs.displayName = "Tabs";
166
247
 
167
248
 
168
249
  exports.Tab = Tab; exports.TabPanel = TabPanel; exports.Tabs = Tabs;
169
- //# sourceMappingURL=chunk-VHQTDITV.cjs.map
250
+ //# sourceMappingURL=chunk-KQD7CQ4Z.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["/home/runner/work/eidos-ui/eidos-ui/dist/chunk-KQD7CQ4Z.cjs","../src/components/Tabs/Tabs.component.tsx"],"names":[],"mappings":"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","file":"/home/runner/work/eidos-ui/eidos-ui/dist/chunk-KQD7CQ4Z.cjs","sourcesContent":[null,"import React, { useState, useRef, useContext, useEffect, useLayoutEffect } from 'react';\nimport { ChevronLeft, ChevronRight } from 'lucide-react';\nimport { renderIcon } from '../../utils';\nimport type { TabsProps, TabProps, TabPanelProps, TabsContextValue } from './Tabs.types';\n\n// ============================================================================\n// CONTEXT\n// ============================================================================\n\nconst TabsContext = React.createContext<TabsContextValue | null>(null);\n\nconst useTabsContext = (): TabsContextValue => {\n const ctx = useContext(TabsContext);\n if (!ctx) throw new Error('Tab/TabPanel must be used inside <Tabs>');\n return ctx;\n};\n\n// ============================================================================\n// TAB - individual tab trigger button\n// Defined before Tabs so Tabs can use `child.type === Tab` for child separation.\n// ============================================================================\n\nexport const Tab: React.FC<TabProps> = ({\n value,\n children,\n disabled = false,\n icon,\n className = '',\n}) => {\n const { activeValue, onSelect, size, fullWidth, listRef } = useTabsContext();\n const isActive = activeValue === value;\n\n const handleKeyDown = (e: React.KeyboardEvent<HTMLButtonElement>) => {\n const list = listRef.current;\n if (!list) return;\n\n const allTabs = Array.from(\n list.querySelectorAll('[role=\"tab\"]:not([disabled])'),\n ) as HTMLElement[];\n const currentIndex = allTabs.indexOf(e.currentTarget);\n if (currentIndex === -1) return;\n\n let targetIndex = -1;\n if (e.key === 'ArrowRight') targetIndex = (currentIndex + 1) % allTabs.length;\n else if (e.key === 'ArrowLeft')\n targetIndex = (currentIndex - 1 + allTabs.length) % allTabs.length;\n else if (e.key === 'Home') targetIndex = 0;\n else if (e.key === 'End') targetIndex = allTabs.length - 1;\n\n if (targetIndex !== -1) {\n e.preventDefault();\n allTabs[targetIndex].focus();\n }\n };\n\n const tabClasses = [\n 'eidos-tab',\n `eidos-tab--${size}`,\n isActive && 'eidos-tab--active',\n disabled && 'eidos-tab--disabled',\n fullWidth && 'eidos-tab--fullWidth',\n className,\n ]\n .filter(Boolean)\n .join(' ');\n\n return (\n <button\n id={`eidos-tab-${value}`}\n role=\"tab\"\n aria-selected={isActive}\n aria-controls={`eidos-tabpanel-${value}`}\n tabIndex={isActive ? 0 : -1}\n disabled={disabled}\n className={tabClasses}\n onClick={() => !disabled && onSelect(value)}\n onKeyDown={handleKeyDown}\n type=\"button\"\n >\n {icon && <span className=\"eidos-tab-icon\">{renderIcon(icon, 'eidos-tab-icon-svg')}</span>}\n <span className=\"eidos-tab-label\">{children}</span>\n </button>\n );\n};\n\nTab.displayName = 'Tab';\n\n// ============================================================================\n// TAB PANEL - content region shown when its value matches the active tab\n// Defined before Tabs for the same `child.type === TabPanel` check.\n// ============================================================================\n\nexport const TabPanel: React.FC<TabPanelProps> = ({ value, children, className = '' }) => {\n const { activeValue } = useTabsContext();\n const isActive = activeValue === value;\n\n if (!isActive) return null;\n\n return (\n <div\n id={`eidos-tabpanel-${value}`}\n role=\"tabpanel\"\n aria-labelledby={`eidos-tab-${value}`}\n tabIndex={0}\n className={['eidos-tab-panel', className].filter(Boolean).join(' ')}\n >\n {children}\n </div>\n );\n};\n\nTabPanel.displayName = 'TabPanel';\n\n// ============================================================================\n// TABS - root container; provides context, separates tab/panel children\n// ============================================================================\n\nexport const Tabs: React.FC<TabsProps> = ({\n value,\n defaultValue,\n onChange,\n variant = 'line',\n size = 'md',\n color = 'primary',\n fullWidth = false,\n scrollButtons = 'auto',\n className = '',\n children,\n}) => {\n const isControlled = value !== undefined;\n const [localValue, setLocalValue] = useState(defaultValue ?? '');\n const activeValue = isControlled ? value! : localValue;\n\n // Separate Tab and TabPanel children so the list and panels render in\n // distinct DOM regions, regardless of how the consumer orders them.\n const tabChildren: React.ReactElement[] = [];\n const panelChildren: React.ReactElement[] = [];\n\n React.Children.forEach(children, (child) => {\n if (!React.isValidElement(child)) return;\n if (child.type === Tab) {\n tabChildren.push(child);\n } else if (child.type === TabPanel) {\n panelChildren.push(child);\n }\n });\n\n const listRef = useRef<HTMLDivElement>(null);\n const indicatorRef = useRef<HTMLSpanElement>(null);\n // Tracks whether the indicator has been positioned at least once so we can\n // skip the CSS transition on the very first render and avoid it animating\n // from the top-left corner to the initial tab.\n const hasPositioned = useRef(false);\n\n useLayoutEffect(() => {\n if (variant !== 'line') return;\n const list = listRef.current;\n const indicator = indicatorRef.current;\n if (!list || !indicator) return;\n\n const activeTab = list.querySelector('[aria-selected=\"true\"]') as HTMLElement | null;\n if (!activeTab) return;\n\n // Suppress the CSS transition on the initial paint so the indicator snaps\n // into place rather than sliding from 0.\n if (!hasPositioned.current) {\n indicator.style.transition = 'none';\n }\n\n indicator.style.left = `${activeTab.offsetLeft}px`;\n indicator.style.width = `${activeTab.offsetWidth}px`;\n\n if (!hasPositioned.current) {\n // Force a synchronous reflow so the browser commits the no-transition\n // position before we re-enable the transition for future clicks.\n void indicator.offsetWidth;\n indicator.style.transition = '';\n hasPositioned.current = true;\n }\n }, [activeValue, variant, fullWidth]);\n\n // The list scrolls horizontally when the strip is wider than its container,\n // so an activated tab can sit outside the scrollport - reachable by keyboard\n // (`End`) or by a controlled `value` change, but invisible. `scrollLeft` is\n // adjusted directly rather than via `scrollIntoView`, which walks the whole\n // ancestor chain and would scroll the page vertically when the strip is below\n // the fold.\n useLayoutEffect(() => {\n const list = listRef.current;\n if (!list) return;\n\n const activeTab = list.querySelector('[aria-selected=\"true\"]') as HTMLElement | null;\n if (!activeTab) return;\n\n const left = activeTab.offsetLeft;\n const right = left + activeTab.offsetWidth;\n\n if (left < list.scrollLeft) {\n list.scrollLeft = left;\n } else if (right > list.scrollLeft + list.clientWidth) {\n list.scrollLeft = right - list.clientWidth;\n }\n }, [activeValue]);\n\n // Whether either end of the strip has content hidden past it. Drives both\n // the presence of the scroll buttons (neither end scrollable = the strip\n // fits, so no buttons) and their disabled state at the extremes.\n const [scrollState, setScrollState] = useState({ canScrollPrev: false, canScrollNext: false });\n const hasScrollButtons = scrollButtons !== 'none';\n\n useEffect(() => {\n if (!hasScrollButtons) return;\n const list = listRef.current;\n if (!list) return;\n\n const update = () => {\n const maxScroll = list.scrollWidth - list.clientWidth;\n // 1px of tolerance: fractional layout widths leave a sub-pixel remainder\n // at either end that would otherwise keep a button enabled for a scroll\n // that can no longer move anything.\n const canScrollPrev = list.scrollLeft > 1;\n const canScrollNext = list.scrollLeft < maxScroll - 1;\n setScrollState((previous) =>\n previous.canScrollPrev === canScrollPrev && previous.canScrollNext === canScrollNext\n ? previous\n : { canScrollPrev, canScrollNext },\n );\n };\n\n update();\n list.addEventListener('scroll', update, { passive: true });\n\n // The list's own box stays the same width when a tab's label reflows or a\n // font finishes loading, so the tabs have to be observed as well as the\n // container - only their sizes tell us the strip started overflowing.\n const observer = new ResizeObserver(update);\n observer.observe(list);\n Array.from(list.children).forEach((child) => observer.observe(child));\n\n return () => {\n list.removeEventListener('scroll', update);\n observer.disconnect();\n };\n }, [hasScrollButtons, tabChildren.length, size, variant, fullWidth]);\n\n const scrollByStep = (direction: -1 | 1) => {\n const list = listRef.current;\n if (!list) return;\n\n // A step just short of a full page keeps a tab of context on screen, the\n // same way a paged scrollbar click does.\n const step = list.clientWidth * 0.8;\n const reducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;\n list.scrollBy({ left: direction * step, behavior: reducedMotion ? 'auto' : 'smooth' });\n };\n\n const onSelect = (newValue: string) => {\n if (!isControlled) {\n setLocalValue(newValue);\n }\n onChange?.(newValue);\n };\n\n const rootClasses = ['eidos-tabs', `eidos-tabs--${variant}`, `eidos-tabs--${color}`, className]\n .filter(Boolean)\n .join(' ');\n\n const listClasses = [\n 'eidos-tabs-list',\n `eidos-tabs-list--${size}`,\n fullWidth && 'eidos-tabs-list--fullWidth',\n // With buttons, they are the affordance - a native scrollbar underneath\n // them is redundant, and on a space-taking one it would also sit over the\n // `line` variant's rule.\n hasScrollButtons && 'eidos-tabs-list--hideScrollbar',\n ]\n .filter(Boolean)\n .join(' ');\n\n // Each button exists only while its own direction has somewhere to go: none\n // at all when the strip fits, and none at the end you have reached. A\n // disabled button would be pointing at nothing. They are positioned\n // absolutely, so appearing and disappearing never moves the tabs.\n const showPrevButton = hasScrollButtons && scrollState.canScrollPrev;\n const showNextButton = hasScrollButtons && scrollState.canScrollNext;\n\n const scrollButtonClasses = (direction: 'prev' | 'next') =>\n [\n 'eidos-tabs-scroll-button',\n `eidos-tabs-scroll-button--${direction}`,\n `eidos-tabs-scroll-button--${size}`,\n ].join(' ');\n\n const contextValue: TabsContextValue = {\n activeValue,\n onSelect,\n variant,\n size,\n color,\n fullWidth,\n listRef,\n };\n\n return (\n <TabsContext.Provider value={contextValue}>\n <div className={rootClasses}>\n {/* The buttons are siblings of the tab list, never children of it:\n `role=\"tablist\"` only accepts tabs. They are also hidden from\n assistive tech and skipped by Tab, because they are a pointer\n affordance for something the keyboard already does - arrow keys\n move between tabs and scroll the active one into view. */}\n <div className=\"eidos-tabs-bar\">\n {showPrevButton && (\n <button\n type=\"button\"\n className={scrollButtonClasses('prev')}\n onClick={() => scrollByStep(-1)}\n tabIndex={-1}\n aria-hidden=\"true\"\n >\n <ChevronLeft className=\"eidos-tabs-scroll-button-icon\" />\n </button>\n )}\n\n <div ref={listRef} className={listClasses} role=\"tablist\">\n {tabChildren}\n {variant === 'line' && (\n <span ref={indicatorRef} className=\"eidos-tabs-indicator\" aria-hidden=\"true\" />\n )}\n </div>\n\n {showNextButton && (\n <button\n type=\"button\"\n className={scrollButtonClasses('next')}\n onClick={() => scrollByStep(1)}\n tabIndex={-1}\n aria-hidden=\"true\"\n >\n <ChevronRight className=\"eidos-tabs-scroll-button-icon\" />\n </button>\n )}\n </div>\n\n <div className=\"eidos-tabs-panels\">{panelChildren}</div>\n </div>\n </TabsContext.Provider>\n );\n};\n\nTabs.displayName = 'Tabs';\n"]}
@@ -115,10 +115,12 @@ var MIN_STEP_DELTA = 0.012;
115
115
  var buildRamp = (base) => {
116
116
  const { l: baseL, c: baseC, h } = hexToOklch(base);
117
117
  const anchor = RAMP_CURVE[BASE_STEP].l;
118
- const lightSpan = RAMP_CURVE[50].l - anchor;
119
- const darkSpan = anchor - RAMP_CURVE[900].l;
120
- const lightScale = lightSpan > 0 ? Math.min(1, Math.max(0, LIGHT_CEILING - baseL) / lightSpan) : 0;
121
- const darkScale = darkSpan > 0 ? Math.min(1, Math.max(0, baseL - DARK_FLOOR) / darkSpan) : 0;
118
+ const curveLight = RAMP_CURVE[50].l;
119
+ const curveDark = RAMP_CURVE[900].l;
120
+ const lightTarget = baseL >= curveLight ? Math.min(LIGHT_CEILING, baseL + 0.02) : curveLight;
121
+ const darkTarget = baseL <= curveDark ? Math.max(DARK_FLOOR * 0.2, baseL - 0.02) : curveDark;
122
+ const lightSpan = curveLight - anchor;
123
+ const darkSpan = anchor - curveDark;
122
124
  const out = {};
123
125
  for (const step of RAMP_STEPS) {
124
126
  if (step === BASE_STEP) {
@@ -127,7 +129,8 @@ var buildRamp = (base) => {
127
129
  }
128
130
  const { l: curveL, chroma } = RAMP_CURVE[step];
129
131
  const offset = curveL - anchor;
130
- const l = baseL + offset * (offset > 0 ? lightScale : darkScale);
132
+ const t = offset > 0 ? offset / lightSpan : -offset / darkSpan;
133
+ const l = offset > 0 ? baseL + t * (lightTarget - baseL) : baseL - t * (baseL - darkTarget);
131
134
  out[step] = oklchToHex({ l, c: baseC * chroma, h });
132
135
  }
133
136
  return enforceSeparation(out, h, baseC);
@@ -518,4 +521,4 @@ var isFontStackAvailable = (stack) => isFontAvailable(_nullishCoalesce(stack.spl
518
521
 
519
522
 
520
523
  exports.useTheme = useTheme; exports.contrastRatio = contrastRatio; exports.CONTRAST_LIGHT = CONTRAST_LIGHT; exports.CONTRAST_DARK = CONTRAST_DARK; exports.pickContrast = pickContrast; exports.THEME_COLOR_KEYS = THEME_COLOR_KEYS; exports.defaultTheme = defaultTheme; exports.FONT_SCALE_MIN = FONT_SCALE_MIN; exports.FONT_SCALE_MAX = FONT_SCALE_MAX; exports.ThemeProvider = ThemeProvider; exports.familyNameFromFile = familyNameFromFile; exports.toFontStack = toFontStack; exports.FONT_ACCEPT = FONT_ACCEPT; exports.registerFontFace = registerFontFace; exports.registerFontFile = registerFontFile; exports.subscribeToFontLoads = subscribeToFontLoads; exports.isFontAvailable = isFontAvailable; exports.isFontStackAvailable = isFontStackAvailable;
521
- //# sourceMappingURL=chunk-U5FHQ73U.cjs.map
524
+ //# sourceMappingURL=chunk-SEDKUITE.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["/home/runner/work/eidos-ui/eidos-ui/dist/chunk-SEDKUITE.cjs","../src/components/ThemeProvider/ThemeProvider.component.tsx","../src/components/ThemeProvider/ThemeProvider.context.tsx","../src/components/ThemeProvider/ThemeProvider.color.ts","../src/components/ThemeProvider/ThemeProvider.tokens.ts","../src/components/ThemeProvider/ThemeProvider.fonts.ts"],"names":["value"],"mappings":"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","file":"/home/runner/work/eidos-ui/eidos-ui/dist/chunk-SEDKUITE.cjs","sourcesContent":[null,"import React, { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';\nimport { devWarn } from '../../utils';\nimport { ThemeContext } from './ThemeProvider.context';\nimport { buildTokens, diffFromDefault, resolveTheme, themeToCss } from './ThemeProvider.tokens';\nimport type { ThemeConfig, ThemeContextValue, ThemeProviderProps } from './ThemeProvider.types';\n\nconst EMPTY_THEME: ThemeConfig = {};\n\n/**\n * Number of providers currently mounted.\n *\n * Because tokens are written to `document.documentElement` (see the component\n * comment for why), providers do not compose: two of them target the same\n * element, so whichever applied a given token last wins for the entire page -\n * including for the subtree of the other one. That looks like a broken theme\n * rather than a misuse, so it is worth saying out loud.\n */\nlet mountedProviders = 0;\n\n/** Merges `patch` over `base` one level into `colors`/`typography`. */\nconst mergeTheme = (base: ThemeConfig, patch: ThemeConfig): ThemeConfig => ({\n colors: { ...base.colors, ...patch.colors },\n typography: { ...base.typography, ...patch.typography },\n});\n\n/**\n * Applies a theme by writing CSS custom properties to `document.documentElement`.\n *\n * Two deliberate choices worth knowing:\n *\n * **The target is `documentElement`, not a wrapper element.** Every overlay in\n * this library (`Dropdown`, `Modal`, `Drawer`, `Snackbar`, `Tooltip`, …) portals\n * to `document.body`, so tokens scoped to a wrapper `<div>` would leave every\n * popup rendering the preset instead.\n *\n * **Properties are set through the CSSOM** (`style.setProperty`), which no CSP\n * directive gates - `style-src` and `style-src-attr` only cover the `style`\n * HTML attribute and stylesheet loads. Nothing here needs a nonce, and no\n * `<style>` element is ever created. See `ContentSecurityPolicy.mdx`.\n *\n * Under SSR the server sends the preset from `dist/index.css` and the theme is\n * applied on hydration; use `toCss()` if you need a themed first paint.\n */\nexport const ThemeProvider: React.FC<ThemeProviderProps> = ({\n children,\n theme: controlledTheme,\n defaultTheme: initialTheme,\n onThemeChange,\n}) => {\n const isControlled = controlledTheme !== undefined;\n const [uncontrolledTheme, setUncontrolledTheme] = useState<ThemeConfig>(\n initialTheme ?? EMPTY_THEME,\n );\n const theme = isControlled ? controlledTheme : uncontrolledTheme;\n\n useEffect(() => {\n mountedProviders += 1;\n if (mountedProviders > 1) {\n devWarn(\n 'theme-multiple-providers',\n `ThemeProvider: ${mountedProviders} providers are mounted at once. They all write to document.documentElement, so the last one to apply each token wins for the whole page - nested or sibling providers cannot theme separate subtrees. Use a single provider at your app root.`,\n );\n }\n return () => {\n mountedProviders -= 1;\n };\n }, []);\n\n const resolvedTheme = useMemo(() => resolveTheme(theme), [theme]);\n const tokens = useMemo(() => diffFromDefault(buildTokens(resolvedTheme)), [resolvedTheme]);\n\n // Names written on the previous pass, so properties dropped from the theme\n // are removed rather than left behind at their last value.\n const appliedRef = useRef<string[]>([]);\n\n useLayoutEffect(() => {\n if (typeof document === 'undefined') return;\n const root = document.documentElement;\n\n for (const name of appliedRef.current) {\n if (!(name in tokens)) root.style.removeProperty(name);\n }\n for (const [name, value] of Object.entries(tokens)) {\n root.style.setProperty(name, value);\n }\n appliedRef.current = Object.keys(tokens);\n\n return () => {\n for (const name of appliedRef.current) {\n root.style.removeProperty(name);\n }\n appliedRef.current = [];\n };\n }, [tokens]);\n\n const commit = useCallback(\n (next: ThemeConfig) => {\n if (!isControlled) setUncontrolledTheme(next);\n onThemeChange?.(next);\n },\n [isControlled, onThemeChange],\n );\n\n const setTheme = useCallback((next: ThemeConfig) => commit(next), [commit]);\n\n const updateTheme = useCallback(\n (patch: ThemeConfig) => commit(mergeTheme(theme, patch)),\n [commit, theme],\n );\n\n const resetTheme = useCallback(() => commit(EMPTY_THEME), [commit]);\n\n const toCss = useCallback(() => themeToCss(resolvedTheme), [resolvedTheme]);\n\n const value = useMemo<ThemeContextValue>(\n () => ({\n theme,\n resolvedTheme,\n setTheme,\n updateTheme,\n resetTheme,\n isDefault: Object.keys(tokens).length === 0,\n toCss,\n }),\n [theme, resolvedTheme, setTheme, updateTheme, resetTheme, tokens, toCss],\n );\n\n return <ThemeContext.Provider value={value}>{children}</ThemeContext.Provider>;\n};\n\nThemeProvider.displayName = 'ThemeProvider';\n","import { createContext, useContext } from 'react';\nimport type { ThemeContextValue } from './ThemeProvider.types';\n\nexport const ThemeContext = createContext<ThemeContextValue | undefined>(undefined);\n\n/**\n * Reads the active theme. Throws outside a `ThemeProvider`, matching\n * `useSnackbarContext` - a silent fallback would make a missing provider look\n * like a theme that simply is not applying.\n */\nexport const useTheme = (): ThemeContextValue => {\n const context = useContext(ThemeContext);\n if (!context) {\n throw new Error('useTheme must be used within a ThemeProvider');\n }\n return context;\n};\n","/**\n * Colour maths for the theme system.\n *\n * All conversions are done in JS and emitted as `#rrggbb`, so browser support\n * for `oklch()` is irrelevant here - nothing in the generated CSS uses a modern\n * colour function.\n *\n * OKLab is used rather than HSL for every interpolation. HSL lightness\n * interpolation is ~40 lines shorter but produces muddy, desaturated ramps for\n * exactly the saturated brand hues this feature exists to support, because its\n * \"lightness\" is not perceptual.\n */\n\n// ============================================================================\n// sRGB <-> OKLab\n// ============================================================================\n\nexport interface Oklch {\n /** Perceptual lightness, 0-1. */\n l: number;\n /** Chroma (colourfulness), 0 to ~0.4 in sRGB. */\n c: number;\n /** Hue angle in radians. */\n h: number;\n}\n\nconst srgbToLinear = (v: number): number =>\n v <= 0.04045 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4);\n\nconst linearToSrgb = (v: number): number =>\n v <= 0.0031308 ? 12.92 * v : 1.055 * Math.pow(v, 1 / 2.4) - 0.055;\n\nconst clamp01 = (v: number): number => (v < 0 ? 0 : v > 1 ? 1 : v);\n\n/** Parses `#rgb` / `#rrggbb` into 0-1 sRGB channels. Returns null if unparseable. */\nexport const parseHex = (hex: string): [number, number, number] | null => {\n if (typeof hex !== 'string') return null;\n const raw = hex.trim().replace(/^#/, '');\n const full =\n raw.length === 3\n ? raw\n .split('')\n .map((c) => c + c)\n .join('')\n : raw;\n if (!/^[0-9a-fA-F]{6}$/.test(full)) return null;\n const n = parseInt(full, 16);\n return [((n >> 16) & 255) / 255, ((n >> 8) & 255) / 255, (n & 255) / 255];\n};\n\nexport const isValidHex = (hex: string): boolean => parseHex(hex) !== null;\n\nconst toHex = ([r, g, b]: [number, number, number]): string =>\n '#' +\n [r, g, b]\n .map((v) =>\n Math.round(clamp01(v) * 255)\n .toString(16)\n .padStart(2, '0'),\n )\n .join('');\n\n/** `#rrggbb` -> the comma-separated triple the `--x-rgb` tokens use. */\nexport const hexToRgbTriple = (hex: string): string => {\n const rgb = parseHex(hex);\n if (!rgb) return '0, 0, 0';\n return rgb.map((v) => Math.round(v * 255)).join(', ');\n};\n\nconst linearToOklab = ([r, g, b]: [number, number, number]): [number, number, number] => {\n const l = Math.cbrt(0.4122214708 * r + 0.5363325363 * g + 0.0514459929 * b);\n const m = Math.cbrt(0.2119034982 * r + 0.6806995451 * g + 0.1073969566 * b);\n const s = Math.cbrt(0.0883024619 * r + 0.2817188376 * g + 0.6299787005 * b);\n return [\n 0.2104542553 * l + 0.793617785 * m - 0.0040720468 * s,\n 1.9779984951 * l - 2.428592205 * m + 0.4505937099 * s,\n 0.0259040371 * l + 0.7827717662 * m - 0.808675766 * s,\n ];\n};\n\nconst oklabToLinear = ([L, A, B]: [number, number, number]): [number, number, number] => {\n const l = (L + 0.3963377774 * A + 0.2158037573 * B) ** 3;\n const m = (L - 0.1055613458 * A - 0.0638541728 * B) ** 3;\n const s = (L - 0.0894841775 * A - 1.291485548 * B) ** 3;\n return [\n 4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s,\n -1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s,\n -0.0041960863 * l - 0.7034186147 * m + 1.707614701 * s,\n ];\n};\n\nexport const hexToOklch = (hex: string): Oklch => {\n const rgb = parseHex(hex) ?? [0, 0, 0];\n const [l, a, b] = linearToOklab(rgb.map(srgbToLinear) as [number, number, number]);\n return { l, c: Math.hypot(a, b), h: Math.atan2(b, a) };\n};\n\nconst inGamut = ([r, g, b]: [number, number, number]): boolean =>\n [r, g, b].every((v) => v >= -1e-4 && v <= 1 + 1e-4);\n\n/**\n * Largest chroma that still fits in sRGB at this lightness and hue.\n *\n * Needed because OKLab is a much larger space than sRGB: naively keeping a\n * colour's chroma while changing its lightness frequently lands outside the\n * gamut, where channels clip and the hue visibly shifts.\n */\nconst maxChroma = (l: number, h: number): number => {\n let lo = 0;\n let hi = 0.45;\n for (let i = 0; i < 24; i++) {\n const mid = (lo + hi) / 2;\n if (inGamut(oklabToLinear([l, mid * Math.cos(h), mid * Math.sin(h)]))) lo = mid;\n else hi = mid;\n }\n return lo;\n};\n\nexport const oklchToHex = ({ l, c, h }: Oklch): string => {\n const lc = clamp01(l);\n const cc = Math.min(Math.max(c, 0), maxChroma(lc, h));\n return toHex(\n oklabToLinear([lc, cc * Math.cos(h), cc * Math.sin(h)]).map(linearToSrgb) as [\n number,\n number,\n number,\n ],\n );\n};\n\n// ============================================================================\n// WCAG 2.1 contrast\n// ============================================================================\n\n/**\n * WCAG 2.1 is used rather than APCA. APCA models perception better but is\n * still a draft (targeted at WCAG 3), while WCAG 2 is what consumers are\n * actually audited against. Its known weakness - mis-ranking some mid-tone\n * blues and greens - is accepted for that reason.\n */\nexport const relativeLuminance = (hex: string): number => {\n const [r, g, b] = (parseHex(hex) ?? [0, 0, 0]).map(srgbToLinear);\n return 0.2126 * r + 0.7152 * g + 0.0722 * b;\n};\n\n/** Contrast ratio between two colours, 1 (identical) to 21 (black on white). */\nexport const contrastRatio = (a: string, b: string): number => {\n const [x, y] = [relativeLuminance(a), relativeLuminance(b)];\n return (Math.max(x, y) + 0.05) / (Math.min(x, y) + 0.05);\n};\n\n/** The two foreground candidates, matching `--white` and `--gray-900`. */\nexport const CONTRAST_LIGHT = '#ffffff';\nexport const CONTRAST_DARK = '#0f172a';\n\n/**\n * Foreground to place on `fill`.\n *\n * White wins unless it drops below 3:1, at which point the better of the two\n * candidates is used. The threshold is deliberately 3:1 rather than \"whichever\n * scores higher\": on mid-tone reds the dark candidate often scores marginally\n * better while looking plainly wrong, and every preset base clears 4.5:1\n * against white anyway.\n */\nexport const pickContrast = (fill: string): string => {\n const onLight = contrastRatio(fill, CONTRAST_LIGHT);\n if (onLight >= 3) return CONTRAST_LIGHT;\n return onLight >= contrastRatio(fill, CONTRAST_DARK) ? CONTRAST_LIGHT : CONTRAST_DARK;\n};\n\n// ============================================================================\n// Ramp generation\n// ============================================================================\n\n/** The ramp steps, in the order `--x-50` … `--x-900` are declared. */\nexport const RAMP_STEPS = [50, 100, 200, 300, 400, 500, 600, 700, 800, 900] as const;\n\nexport type RampStep = (typeof RAMP_STEPS)[number];\n\n/** The step the supplied base colour occupies; the ramp is built around it. */\nconst BASE_STEP = 500;\n\n/**\n * Target lightness per step, and how much of the base's chroma to keep.\n *\n * Measured from the preset indigo ramp, so feeding the preset primary through\n * `buildRamp` lands close to the shipped `--primary-*` values rather than\n * something arbitrary. It is deliberately not an exact reproduction - see the\n * note on `buildRamp`.\n */\nconst RAMP_CURVE: Record<RampStep, { l: number; chroma: number }> = {\n 50: { l: 0.962, chroma: 0.09 },\n 100: { l: 0.93, chroma: 0.16 },\n 200: { l: 0.87, chroma: 0.3 },\n 300: { l: 0.785, chroma: 0.51 },\n 400: { l: 0.68, chroma: 0.77 },\n 500: { l: 0.558, chroma: 1.0 },\n 600: { l: 0.511, chroma: 1.12 },\n 700: { l: 0.457, chroma: 1.05 },\n 800: { l: 0.398, chroma: 0.87 },\n 900: { l: 0.359, chroma: 0.66 },\n};\n\n/** Ceiling/floor the generated ends aim for, leaving the base itself untouched. */\nconst LIGHT_CEILING = 0.985;\nconst DARK_FLOOR = 0.22;\n\n/** Minimum lightness gap between adjacent steps, so none can collapse together. */\nconst MIN_STEP_DELTA = 0.012;\n\n/**\n * Generates a 50-900 ramp around `base`, which is placed at the 500 step.\n *\n * The base's own hue is always preserved, and `--x-500` is returned as the base\n * hex verbatim, so the colour a consumer picked is never silently replaced.\n *\n * The curve above is *rescaled* to whatever lightness headroom the base leaves,\n * rather than being applied absolutely or simply shifted:\n *\n * - Applying it absolutely would reassign the base - pick a pale yellow and\n * `--x-500` would come back a mid-olive, contradicting `--x-color`.\n * - Shifting it so 500 sits at the base's lightness pushes the light end past\n * `l = 1`, where it clips and 50/100/200/300 all come out pure white.\n *\n * The ends are **absolute**, not relative to the base: step 50 lands near-white\n * and step 900 near-dark whatever lightness the base has, with the remaining\n * steps interpolated between the base and those anchors. Only a base that is\n * already past an anchor compresses that side into what headroom is left.\n *\n * Getting this wrong is subtle. An earlier version offset every step *from the\n * base* by the curve's own distances, capped so the preset reproduced exactly.\n * That looks right - and reproduces the preset ramp perfectly - but it makes the\n * tints relative: a dark navy base (`#1a1a2e`) returned `--primary-50: #8a8a8c`,\n * a mid grey rather than a tint, because 50 sits only 0.404 lightness above 500\n * on the curve. Every consumer of the tint end broke visibly - `Navigation`'s\n * active item paints `--primary-50` and turned into a charcoal pill.\n *\n * Two inputs are genuinely degenerate and are handled rather than solved: a\n * base at or above `LIGHT_CEILING` has no room for tints, and one with zero\n * chroma (pure white, black, any gray) has no saturation to separate them with\n * either. For those the light end converges, and `isRampViable` reports it so\n * the caller can warn. Realistic light picks are fine - `#fde047` still yields\n * five distinct tints.\n */\nexport const buildRamp = (base: string): Record<RampStep, string> => {\n const { l: baseL, c: baseC, h } = hexToOklch(base);\n const anchor = RAMP_CURVE[BASE_STEP].l;\n\n // Where the ends of the scale sit, independent of the base. A base already\n // lighter than the curve's own 50 (or darker than its 900) has nowhere left\n // to go, so that side compresses into the remaining headroom instead.\n const curveLight = RAMP_CURVE[50].l;\n const curveDark = RAMP_CURVE[900].l;\n const lightTarget = baseL >= curveLight ? Math.min(LIGHT_CEILING, baseL + 0.02) : curveLight;\n const darkTarget = baseL <= curveDark ? Math.max(DARK_FLOOR * 0.2, baseL - 0.02) : curveDark;\n\n const lightSpan = curveLight - anchor;\n const darkSpan = anchor - curveDark;\n\n const out = {} as Record<RampStep, string>;\n for (const step of RAMP_STEPS) {\n if (step === BASE_STEP) {\n out[step] = base;\n continue;\n }\n const { l: curveL, chroma } = RAMP_CURVE[step];\n const offset = curveL - anchor;\n // How far along its half of the curve this step sits: 0 at the base, 1 at\n // the end. Interpolating that fraction between the base and the target\n // keeps the curve's shape while pinning where it ends up.\n const t = offset > 0 ? offset / lightSpan : -offset / darkSpan;\n const l = offset > 0 ? baseL + t * (lightTarget - baseL) : baseL - t * (baseL - darkTarget);\n out[step] = oklchToHex({ l, c: baseC * chroma, h });\n }\n\n return enforceSeparation(out, h, baseC);\n};\n\n/**\n * Walks outward from the base step, pushing any step that ended up too close to\n * its neighbour further away. Only bites for extreme inputs (near-white,\n * near-black, zero-chroma), where the rescale above has almost no room left.\n */\nconst enforceSeparation = (\n ramp: Record<RampStep, string>,\n h: number,\n baseC: number,\n): Record<RampStep, string> => {\n const baseIndex = RAMP_STEPS.indexOf(BASE_STEP);\n const lightness = RAMP_STEPS.map((s) => hexToOklch(ramp[s]).l);\n\n // Lighter steps: walk from the base toward 50, each must be lighter.\n for (let i = baseIndex - 1; i >= 0; i--) {\n const limit = lightness[i + 1] + MIN_STEP_DELTA;\n if (lightness[i] < limit) lightness[i] = Math.min(1, limit);\n }\n // Darker steps: walk from the base toward 900, each must be darker.\n for (let i = baseIndex + 1; i < RAMP_STEPS.length; i++) {\n const limit = lightness[i - 1] - MIN_STEP_DELTA;\n if (lightness[i] > limit) lightness[i] = Math.max(0, limit);\n }\n\n const out = {} as Record<RampStep, string>;\n RAMP_STEPS.forEach((step, i) => {\n if (step === BASE_STEP) {\n out[step] = ramp[step];\n return;\n }\n out[step] = oklchToHex({\n l: lightness[i],\n c: baseC * RAMP_CURVE[step].chroma,\n h,\n });\n });\n return out;\n};\n\n/** How far `--x-dark` / `--x-light` sit from the base, in OKLab lightness. */\nconst DARK_DELTA = 0.086;\nconst LIGHT_DELTA = 0.169;\n\n/** Below this lightness there is no room left to darken, so the hover lightens. */\nconst HOVER_INVERT_BELOW = DARK_DELTA * 1.5;\n\n/**\n * The hover/active colour. Must differ *visibly* from the base and stay legible\n * as text on white, since `--x-dark` is used both as a fill (Button, Chip\n * hovers) and as text (Snackbar, link hover in `global.scss`).\n *\n * A fixed ramp step is not good enough: for a near-white base the 600 step\n * lands barely darker than the base and the hover reads as no change at all.\n * Hence a guaranteed delta.\n *\n * For a base that is already near-black the delta is applied *upwards* instead.\n * Clamping to a floor would return a colour lighter than the base while still\n * being named \"dark\", and returning the base unchanged would leave the control\n * with no hover affordance at all; lightening is what dark-surface design\n * systems do anyway.\n */\nexport const deriveDark = (base: string): string => {\n const { l, c, h } = hexToOklch(base);\n const target = l < HOVER_INVERT_BELOW ? l + DARK_DELTA : l - DARK_DELTA;\n return oklchToHex({ l: target, c, h });\n};\n\n/**\n * The light companion. Lightening has a hard ceiling, so for an already-pale\n * base this lands very close to it - acceptable, as `--x-light` is a public\n * token that nothing inside the library currently consumes.\n */\nexport const deriveLight = (base: string): string => {\n const { l, c, h } = hexToOklch(base);\n return oklchToHex({ l: Math.min(LIGHT_CEILING, l + LIGHT_DELTA), c, h });\n};\n\n/**\n * Whether `base` leaves enough lightness headroom to generate a usable 50-900\n * ramp. False only for near-white and near-black, where the tint or shade end\n * necessarily converges - the caller surfaces that as a developer warning\n * rather than silently shipping identical tokens.\n *\n * Deliberately does not test chroma: an unsaturated gray separates perfectly\n * well by lightness alone, and a saturated light colour like `#fde047` still\n * produces five distinct tints. Only the extremes genuinely fail.\n */\nexport const isRampViable = (base: string): boolean => {\n const { l } = hexToOklch(base);\n return l < 0.95 && l > 0.12;\n};\n","import { devWarn } from '../../utils';\nimport {\n RAMP_STEPS,\n buildRamp,\n deriveDark,\n deriveLight,\n hexToRgbTriple,\n isValidHex,\n isRampViable,\n pickContrast,\n} from './ThemeProvider.color';\nimport type {\n ResolvedTheme,\n ThemeColorKey,\n ThemeColorValue,\n ThemeConfig,\n} from './ThemeProvider.types';\n\nexport const THEME_COLOR_KEYS: readonly ThemeColorKey[] = [\n 'primary',\n 'secondary',\n 'success',\n 'danger',\n 'warning',\n 'info',\n 'hyperlink',\n] as const;\n\n/**\n * `hyperlink` is link text on a surface, never a fill, so it has no\n * `--hyperlink-contrast` token - nothing in the library would read it.\n */\nconst FILL_COLOR_KEYS: readonly ThemeColorKey[] = [\n 'primary',\n 'secondary',\n 'success',\n 'danger',\n 'warning',\n 'info',\n] as const;\n\n/** Only `primary` has a 50-900 ramp in `variables.scss`. */\nconst RAMP_COLOR_KEY: ThemeColorKey = 'primary';\n\n/** The eight preset `--font-size-*` values, in declaration order. */\nconst FONT_SIZE_STEPS: readonly [string, number][] = [\n ['xs', 0.75],\n ['sm', 0.875],\n ['base', 1],\n ['md', 1.1],\n ['lg', 1.125],\n ['xl', 1.25],\n ['2xl', 1.5],\n ['3xl', 1.875],\n] as const;\n\n/**\n * The shipped values from `src/styles/variables.scss`.\n *\n * This is a literal copy, not something derived: the ramp is hand-tuned and no\n * generator reproduces it byte for byte. Keeping it literal means a theme equal\n * to the preset writes no CSS properties at all, so default rendering is\n * identical to `dist/index.css`.\n *\n * Must be kept in sync with `variables.scss` by hand. The bases are all chosen\n * to clear WCAG AA against white text; see the comment at the top of that file.\n */\nexport const defaultTheme: Required<ThemeConfig> = {\n colors: {\n primary: '#5c5de8',\n secondary: '#617087',\n success: '#007f57',\n danger: '#d6272f',\n warning: '#9c6300',\n info: '#007a90',\n hyperlink: '#007b84',\n },\n typography: {\n fontFamily:\n \"'Plus Jakarta Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif\",\n monoFamily:\n \"'JetBrains Mono', 'SF Mono', 'Monaco', 'Inconsolata', 'Roboto Mono', 'Source Code Pro', monospace\",\n fontScale: 1,\n },\n};\n\n/** The preset `--primary-*` ramp, needed so an unthemed primary emits nothing. */\nconst DEFAULT_PRIMARY_RAMP: Record<number, string> = {\n 50: '#eef2ff',\n 100: '#e0e7ff',\n 200: '#c7d2fe',\n 300: '#a5b4fc',\n 400: '#818cf8',\n 500: '#5c5de8',\n 600: '#4f46e5',\n 700: '#4338ca',\n 800: '#3730a3',\n 900: '#312e81',\n};\n\nexport const FONT_SCALE_MIN = 0.875;\nexport const FONT_SCALE_MAX = 1.25;\n\nconst normalizeColor = (value: ThemeColorValue): { base: string; contrast?: string } =>\n typeof value === 'string' ? { base: value } : value;\n\n/** Fills every absent field from the preset. */\nexport const resolveTheme = (theme: ThemeConfig): ResolvedTheme => {\n const colors = {} as ResolvedTheme['colors'];\n\n for (const key of THEME_COLOR_KEYS) {\n const supplied = theme.colors?.[key];\n const fallback = normalizeColor(defaultTheme.colors[key]!);\n if (supplied === undefined) {\n colors[key] = fallback;\n continue;\n }\n const { base, contrast } = normalizeColor(supplied);\n if (!isValidHex(base)) {\n devWarn(\n `theme-color-${key}`,\n `ThemeProvider: colors.${key} is not a valid hex colour (received ${JSON.stringify(base)}). Falling back to the preset.`,\n );\n colors[key] = fallback;\n continue;\n }\n if (contrast !== undefined && !isValidHex(contrast)) {\n devWarn(\n `theme-contrast-${key}`,\n `ThemeProvider: colors.${key}.contrast is not a valid hex colour (received ${JSON.stringify(contrast)}). It will be computed instead.`,\n );\n colors[key] = { base };\n continue;\n }\n colors[key] = contrast === undefined ? { base } : { base, contrast };\n }\n\n const scale = theme.typography?.fontScale;\n let fontScale = defaultTheme.typography.fontScale!;\n if (scale !== undefined) {\n if (!Number.isFinite(scale) || scale < FONT_SCALE_MIN || scale > FONT_SCALE_MAX) {\n devWarn(\n 'theme-font-scale',\n `ThemeProvider: typography.fontScale must be between ${FONT_SCALE_MIN} and ${FONT_SCALE_MAX} (received ${String(scale)}). Falling back to ${fontScale}.`,\n );\n } else {\n fontScale = scale;\n }\n }\n\n return {\n colors,\n typography: {\n fontFamily: theme.typography?.fontFamily || defaultTheme.typography.fontFamily!,\n monoFamily: theme.typography?.monoFamily || defaultTheme.typography.monoFamily!,\n fontScale,\n },\n };\n};\n\n/**\n * Expands a resolved theme into the flat `--token: value` map to apply.\n *\n * Every family contributes `--x-color`, `--x-rgb`, `--x-dark` and `--x-light`;\n * the six fill families also get `--x-contrast`, and `primary` additionally\n * gets the full `--primary-50…900` ramp.\n */\nexport const buildTokens = (resolved: ResolvedTheme): Record<string, string> => {\n const tokens: Record<string, string> = {};\n\n for (const key of THEME_COLOR_KEYS) {\n const { base, contrast } = resolved.colors[key];\n tokens[`--${key}-color`] = base;\n tokens[`--${key}-rgb`] = hexToRgbTriple(base);\n tokens[`--${key}-dark`] = deriveDark(base);\n tokens[`--${key}-light`] = deriveLight(base);\n\n if (FILL_COLOR_KEYS.includes(key)) {\n tokens[`--${key}-contrast`] = contrast ?? pickContrast(base);\n }\n\n if (key === RAMP_COLOR_KEY) {\n const isPreset = base === normalizeColor(defaultTheme.colors[key]!).base;\n // The preset ramp is hand-tuned, so reuse it verbatim rather than\n // regenerating a near-identical one.\n const ramp = isPreset ? DEFAULT_PRIMARY_RAMP : buildRamp(base);\n if (!isPreset && !isRampViable(base)) {\n devWarn(\n `theme-ramp-${key}`,\n `ThemeProvider: colors.${key} (${base}) is too light or too dark to generate a usable --${key}-50…900 scale; several steps will be identical. Pick a colour with more headroom.`,\n );\n }\n for (const step of RAMP_STEPS) {\n tokens[`--${key}-${step}`] = ramp[step];\n }\n }\n }\n\n const { fontFamily, monoFamily, fontScale } = resolved.typography;\n tokens['--font-family-primary'] = fontFamily;\n tokens['--font-family-mono'] = monoFamily;\n for (const [name, rem] of FONT_SIZE_STEPS) {\n // Trailing zeros trimmed so 1 * 0.75 reads as `0.75rem`, not `0.750rem`.\n tokens[`--font-size-${name}`] = `${parseFloat((rem * fontScale).toFixed(4))}rem`;\n }\n\n return tokens;\n};\n\n/** Tokens for the preset, used to tell which of them are actually overrides. */\nconst DEFAULT_TOKENS = buildTokens(resolveTheme(defaultTheme));\n\n/**\n * Only the tokens that differ from the preset.\n *\n * This is what makes an unthemed `ThemeProvider` a no-op: it writes nothing, so\n * `dist/index.css` stays authoritative and there is no inline-style churn on\n * `documentElement` for values that already match.\n */\nexport const diffFromDefault = (tokens: Record<string, string>): Record<string, string> => {\n const diff: Record<string, string> = {};\n for (const [name, value] of Object.entries(tokens)) {\n if (DEFAULT_TOKENS[name] !== value) diff[name] = value;\n }\n return diff;\n};\n\n/**\n * The resolved theme as a `:root { … }` block.\n *\n * Paste the output into your own stylesheet to bake a theme in at build time -\n * that avoids the brief flash of the preset that any runtime theme has when the\n * value arrives after first paint, and needs no JavaScript at all.\n */\nexport const themeToCss = (resolved: ResolvedTheme): string => {\n const tokens = buildTokens(resolved);\n const body = Object.entries(tokens)\n .map(([name, value]) => ` ${name}: ${value};`)\n .join('\\n');\n return `:root {\\n${body}\\n}`;\n};\n","/**\n * Runtime font registration for the theme system.\n *\n * `--font-family-primary` only *names* families - it cannot make a font\n * available. Selecting \"Plus Jakarta Sans\" on a machine that doesn't have it\n * installed silently falls through to the next stack entry, which is why a\n * theme needs a way to supply the font data itself.\n *\n * ## Why this is CSP-safe\n *\n * Fonts are registered with the CSS Font Loading API from an `ArrayBuffer`:\n *\n * ```ts\n * document.fonts.add(new FontFace(family, buffer));\n * ```\n *\n * CSP's `font-src` directive governs *fetching* a font resource by URL - it is\n * what gates `@font-face { src: url(...) }` and the string form of the\n * `FontFace` constructor. The binary form performs no fetch at all, so there is\n * no request for `font-src` to check and no allowance to configure. Notably\n * this also avoids `blob:` URLs, which *would* require `font-src blob:`.\n *\n * No `<style>` element is created either, so nothing here needs a nonce - the\n * same position as the rest of the theme system.\n */\n\n/** A font registered at runtime, as listed by the editor. */\nexport interface RegisteredFont {\n /** The family name to reference in a font stack. */\n family: string;\n /** Where it came from, so the UI can distinguish uploads from built-ins. */\n source: 'upload';\n}\n\n/**\n * Turns a filename into a usable CSS family name.\n *\n * Deliberately conservative: the real family name lives in the font's internal\n * `name` table, and parsing SFNT tables to read it would be a disproportionate\n * amount of binary handling for a label. The filename is what the user\n * recognises anyway.\n */\nexport const familyNameFromFile = (filename: string): string => {\n const base = filename.replace(/\\.[^.]+$/, '');\n const cleaned = base\n // Strip the weight/style suffixes font vendors append.\n .replace(/[-_](regular|normal|book|roman)$/i, '')\n .replace(/[-_]+/g, ' ')\n // A quoted family name may contain spaces but not quotes or control chars.\n .replace(/[\"'\\\\]/g, '')\n .replace(/\\s+/g, ' ')\n .trim();\n return cleaned || 'Custom Font';\n};\n\n/** Wraps a family name for use in a CSS font stack if it needs quoting. */\nexport const quoteFamily = (family: string): string =>\n /^[a-zA-Z][a-zA-Z0-9-]*$/.test(family) ? family : `'${family}'`;\n\n/**\n * Builds a full stack from a custom family, keeping sensible fallbacks so the\n * page stays readable if the font is ever unavailable.\n */\nexport const toFontStack = (family: string, mono = false): string =>\n mono\n ? `${quoteFamily(family)}, 'SF Mono', Monaco, monospace`\n : `${quoteFamily(family)}, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif`;\n\n/** Font formats accepted for upload, as an `accept` attribute value. */\nexport const FONT_ACCEPT = '.woff2,.woff,.ttf,.otf';\n\nconst SUPPORTED_EXTENSIONS = /\\.(woff2|woff|ttf|otf)$/i;\n\nexport const isSupportedFontFile = (filename: string): boolean =>\n SUPPORTED_EXTENSIONS.test(filename);\n\n/**\n * Registers font data with the document so a theme can reference its family.\n *\n * Resolves with the family name actually registered. Rejects if the data is not\n * a font the browser can parse, or if the Font Loading API is unavailable\n * (notably during SSR, where there is no `document` to register against).\n */\nexport const registerFontFace = async (family: string, data: ArrayBuffer): Promise<string> => {\n if (typeof document === 'undefined' || !document.fonts) {\n throw new Error('registerFontFace requires a document with the CSS Font Loading API.');\n }\n\n // ArrayBuffer source - no URL is fetched, so CSP's font-src never applies.\n const face = new FontFace(family, data);\n await face.load();\n document.fonts.add(face);\n // No cache invalidation needed: only positive results are cached, and this\n // family could not have been cached positive while it was still unavailable.\n // `document.fonts.add` also fires `loadingdone`, which wakes subscribers.\n return family;\n};\n\n/** Reads a File and registers it, returning the family name it was given. */\nexport const registerFontFile = async (file: File, family?: string): Promise<string> => {\n if (!isSupportedFontFile(file.name)) {\n throw new Error(`Unsupported font format: ${file.name}. Use ${FONT_ACCEPT}.`);\n }\n const buffer = await file.arrayBuffer();\n return registerFontFace(family ?? familyNameFromFile(file.name), buffer);\n};\n\n/**\n * CSS generic families, which always resolve and must never be reported\n * missing. They also cannot be probed by the method below, since asking for\n * `monospace, monospace` measures identically to `monospace` alone.\n */\nconst GENERIC_FAMILIES = new Set([\n 'serif',\n 'sans-serif',\n 'monospace',\n 'cursive',\n 'fantasy',\n 'system-ui',\n 'ui-serif',\n 'ui-sans-serif',\n 'ui-monospace',\n 'ui-rounded',\n 'math',\n 'emoji',\n 'fangsong',\n 'inherit',\n 'initial',\n 'unset',\n]);\n\n/**\n * Mixed-width glyphs, so a real font is very unlikely to measure identically\n * to a fallback by coincidence.\n */\nconst PROBE_TEXT = 'mmmmmmmmmmlliI0OWw@';\n\n/** Two structurally different fallbacks - a font need only differ from one. */\nconst PROBE_FALLBACKS = ['monospace', 'serif'] as const;\n\n/**\n * Cached per family - **positive results only**.\n *\n * A negative answer is not stable: web fonts load asynchronously (always, with\n * `font-display: swap`), so a family probed during the first render is\n * legitimately unavailable and becomes available moments later. Caching that\n * `false` pinned the answer forever, which showed a permanent \"not installed\"\n * warning for fonts that had in fact loaded.\n *\n * A positive answer cannot go stale the same way - a loaded font is not\n * unloaded - so those are worth keeping. Re-probing the misses costs one canvas\n * measurement each.\n */\nconst availabilityCache = new Map<string, true>();\n\n/**\n * Notifies when the set of usable fonts changes, so a caller rendering an\n * availability state can re-check.\n *\n * Both signals are needed: `loadingdone` covers fonts that begin loading after\n * subscription, while `ready` covers those already in flight when the listener\n * was attached - which is the common case for a stylesheet's `@font-face`\n * rules, since they start loading before any component mounts.\n */\nexport const subscribeToFontLoads = (onChange: () => void): (() => void) => {\n if (typeof document === 'undefined' || !document.fonts) return () => {};\n\n let active = true;\n const handler = () => {\n if (active) onChange();\n };\n\n document.fonts.addEventListener('loadingdone', handler);\n void document.fonts.ready.then(handler);\n\n return () => {\n active = false;\n document.fonts.removeEventListener('loadingdone', handler);\n };\n};\n\n/**\n * Whether a font family will actually render, rather than silently falling\n * through to the next entry in the stack.\n *\n * **`document.fonts.check` cannot answer this.** Per spec it reports whether\n * the faces *in the FontFaceSet* matching the query are loaded, so with no\n * matching face it returns `true` vacuously. Verified in a real browser: it\n * answers `true` for `'Totally Not A Real Font 12345'`, and therefore for every\n * uninstalled system font too. Using it here produced a check that could never\n * fail.\n *\n * Instead the family is measured on a canvas against two fallbacks. If it\n * resolves to anything real, the text measures differently from at least one\n * fallback alone; if it does not resolve, the browser renders that fallback and\n * the widths match exactly.\n */\nexport const isFontAvailable = (family: string): boolean => {\n const name = family.trim().replace(/^['\"]|['\"]$/g, '');\n if (!name) return true;\n if (GENERIC_FAMILIES.has(name.toLowerCase())) return true;\n if (typeof document === 'undefined') return true;\n\n if (availabilityCache.get(name)) return true;\n\n try {\n const context = document.createElement('canvas').getContext('2d');\n if (!context) return true;\n\n const available = PROBE_FALLBACKS.some((fallback) => {\n context.font = `100px ${fallback}`;\n const baseline = context.measureText(PROBE_TEXT).width;\n context.font = `100px ${quoteFamily(name)}, ${fallback}`;\n // An invalid font shorthand leaves `font` untouched, which measures as\n // the baseline and correctly reports the family as unavailable.\n return Math.abs(context.measureText(PROBE_TEXT).width - baseline) > 0.5;\n });\n\n if (available) availabilityCache.set(name, true);\n return available;\n } catch {\n // Never report a font missing because probing itself failed - and don't\n // cache a failure, so a later call can still get a real answer.\n return true;\n }\n};\n\n/** Whether the first family in a stack will render. */\nexport const isFontStackAvailable = (stack: string): boolean =>\n isFontAvailable(stack.split(',')[0] ?? '');\n"]}
@@ -13,7 +13,7 @@ import {
13
13
  subscribeToFontLoads,
14
14
  toFontStack,
15
15
  useTheme
16
- } from "./chunk-2ZY7M72Q.js";
16
+ } from "./chunk-GWQEADRJ.js";
17
17
  import {
18
18
  Slider
19
19
  } from "./chunk-2BC4M4QZ.js";
@@ -387,4 +387,4 @@ ThemeEditor.displayName = "ThemeEditor";
387
387
  export {
388
388
  ThemeEditor
389
389
  };
390
- //# sourceMappingURL=chunk-H5HWFOWZ.js.map
390
+ //# sourceMappingURL=chunk-SYWSXSHY.js.map
package/dist/index.cjs CHANGED
@@ -8,7 +8,7 @@ var _chunk6A2ZKVSTcjs = require('./chunk-6A2ZKVST.cjs');
8
8
 
9
9
 
10
10
 
11
- var _chunkVHQTDITVcjs = require('./chunk-VHQTDITV.cjs');
11
+ var _chunkKQD7CQ4Zcjs = require('./chunk-KQD7CQ4Z.cjs');
12
12
 
13
13
 
14
14
  var _chunk442U3W6Rcjs = require('./chunk-442U3W6R.cjs');
@@ -17,7 +17,7 @@ var _chunk442U3W6Rcjs = require('./chunk-442U3W6R.cjs');
17
17
  var _chunkRHOT7AEIcjs = require('./chunk-RHOT7AEI.cjs');
18
18
 
19
19
 
20
- var _chunkSE7MWOC7cjs = require('./chunk-SE7MWOC7.cjs');
20
+ var _chunkJRYTZ5B2cjs = require('./chunk-JRYTZ5B2.cjs');
21
21
 
22
22
 
23
23
 
@@ -29,7 +29,7 @@ var _chunkSE7MWOC7cjs = require('./chunk-SE7MWOC7.cjs');
29
29
 
30
30
 
31
31
 
32
- var _chunkU5FHQ73Ucjs = require('./chunk-U5FHQ73U.cjs');
32
+ var _chunkSEDKUITEcjs = require('./chunk-SEDKUITE.cjs');
33
33
 
34
34
 
35
35
  var _chunkZMDZGL5Acjs = require('./chunk-ZMDZGL5A.cjs');
@@ -291,5 +291,5 @@ require('./chunk-DZUJEN5N.cjs');
291
291
 
292
292
 
293
293
 
294
- exports.Accordion = _chunkSDEKANNLcjs.Accordion; exports.AccordionItem = _chunkSDEKANNLcjs.AccordionItem; exports.Alert = _chunkB5G5XVFJcjs.Alert; exports.Avatar = _chunkQ3GNKF74cjs.Avatar; exports.AvatarGroup = _chunkQ3GNKF74cjs.AvatarGroup; exports.Badge = _chunk3ETWFS2Ncjs.Badge; exports.Breadcrumb = _chunk5O73AALPcjs.Breadcrumb; exports.Button = _chunkAPK5OQRVcjs.Button; exports.ButtonGroup = _chunk2ROKOO22cjs.ButtonGroup; exports.Calendar = _chunk6HUHH6Y4cjs.Calendar; exports.Card = _chunkV5RS3JZNcjs.Card; exports.Checkbox = _chunkIJ4DXG7Dcjs.Checkbox; exports.Chip = _chunkTRLBYA5Gcjs.Chip; exports.ColorPicker = _chunk5BX4Q76Dcjs.ColorPicker; exports.Combobox = _chunkWUBQJVB7cjs.Combobox; exports.CommandPalette = _chunk3EPND5M2cjs.CommandPalette; exports.ContextMenu = _chunkLFWPQYWXcjs.ContextMenu; exports.DataGrid = _chunkSKTDGCWJcjs.DataGrid; exports.DatePicker = _chunk6HUHH6Y4cjs.DatePicker; exports.Divider = _chunk366R4GKRcjs.Divider; exports.Drawer = _chunkLUL6RZ4Mcjs.Drawer; exports.Dropdown = _chunkFRUOA2QUcjs.Dropdown; exports.DropdownProvider = _chunkFRUOA2QUcjs.DropdownProvider; exports.EmptyState = _chunkKV3V2CPHcjs.EmptyState; exports.FONT_ACCEPT = _chunkU5FHQ73Ucjs.FONT_ACCEPT; exports.FileUpload = _chunk3CXULV4Gcjs.FileUpload; exports.Footer = _chunkPTBBY4ZXcjs.Footer; exports.Header = _chunkZ4SLV6WGcjs.Header; exports.IconButton = _chunkAPK5OQRVcjs.IconButton; exports.InlineEdit = _chunkN2IGZIEXcjs.InlineEdit; exports.Input = _chunk5VM2256Wcjs.Input; exports.Kbd = _chunkKVIL4EP4cjs.Kbd; exports.Menu = _chunkDAXZRNNOcjs.Menu; exports.MenuPanel = _chunkDAXZRNNOcjs.MenuPanel; exports.Modal = _chunkD5IY2HUVcjs.Modal; exports.Navigation = _chunkGT22EGJPcjs.Navigation; exports.NumberInput = _chunkQF2RAUPBcjs.NumberInput; exports.OTPInput = _chunkFKXKBJUIcjs.OTPInput; exports.PageLayout = _chunkG77YR73Rcjs.PageLayout; exports.Pagination = _chunkAXBYJ2OYcjs.Pagination; exports.Pill = _chunkEQ6BNPTLcjs.Pill; exports.Popover = _chunk3E7TKBQIcjs.Popover; exports.Progress = _chunkV2S6XWIQcjs.Progress; exports.Radio = _chunkQQIALSTXcjs.Radio; exports.RadioGroup = _chunkQQIALSTXcjs.RadioGroup; exports.SegmentedControl = _chunkAAA6TR5Icjs.SegmentedControl; exports.Select = _chunkKJZUYC4Xcjs.Select; exports.Skeleton = _chunkIYWAZEFIcjs.Skeleton; exports.Slider = _chunkOZZHOEN5cjs.Slider; exports.SnackbarContainer = _chunkEZME5CLKcjs.SnackbarContainer; exports.SnackbarProvider = _chunkEZME5CLKcjs.SnackbarProvider; exports.Spinner = _chunkTROYIUDHcjs.Spinner; exports.SplitButton = _chunkSKEWTQJ7cjs.SplitButton; exports.Stepper = _chunkHFZ5FKRGcjs.Stepper; exports.Switch = _chunkIXZMKIV5cjs.Switch; exports.Tab = _chunkVHQTDITVcjs.Tab; exports.TabPanel = _chunkVHQTDITVcjs.TabPanel; exports.Table = _chunkGGSII6Y2cjs.Table; exports.TableFiltersDropdown = _chunkLIFUHQH5cjs.TableFiltersDropdown; exports.Tabs = _chunkVHQTDITVcjs.Tabs; exports.TagInput = _chunk442U3W6Rcjs.TagInput; exports.Textarea = _chunkRHOT7AEIcjs.Textarea; exports.ThemeEditor = _chunkSE7MWOC7cjs.ThemeEditor; exports.ThemeProvider = _chunkU5FHQ73Ucjs.ThemeProvider; exports.TimeInput = _chunk6HUHH6Y4cjs.TimeInput; exports.Timeline = _chunkZMDZGL5Acjs.Timeline; exports.Toolbar = _chunkGR2PMCVNcjs.Toolbar; exports.Tooltip = _chunkZ5U4NJTWcjs.Tooltip; exports.TreeView = _chunkXKQMEVODcjs.TreeView; exports.VirtualList = _chunk6A2ZKVSTcjs.VirtualList; exports.defaultTheme = _chunkU5FHQ73Ucjs.defaultTheme; exports.familyNameFromFile = _chunkU5FHQ73Ucjs.familyNameFromFile; exports.isFontAvailable = _chunkU5FHQ73Ucjs.isFontAvailable; exports.isFontStackAvailable = _chunkU5FHQ73Ucjs.isFontStackAvailable; exports.registerFontFace = _chunkU5FHQ73Ucjs.registerFontFace; exports.registerFontFile = _chunkU5FHQ73Ucjs.registerFontFile; exports.renderIcon = _chunkGEHL4LKKcjs.renderIcon; exports.toFontStack = _chunkU5FHQ73Ucjs.toFontStack; exports.useDropdownContext = _chunkFRUOA2QUcjs.useDropdownContext; exports.useSnackbar = _chunkEZME5CLKcjs.useSnackbar; exports.useSnackbarContext = _chunkEZME5CLKcjs.useSnackbarContext; exports.useTheme = _chunkU5FHQ73Ucjs.useTheme;
294
+ exports.Accordion = _chunkSDEKANNLcjs.Accordion; exports.AccordionItem = _chunkSDEKANNLcjs.AccordionItem; exports.Alert = _chunkB5G5XVFJcjs.Alert; exports.Avatar = _chunkQ3GNKF74cjs.Avatar; exports.AvatarGroup = _chunkQ3GNKF74cjs.AvatarGroup; exports.Badge = _chunk3ETWFS2Ncjs.Badge; exports.Breadcrumb = _chunk5O73AALPcjs.Breadcrumb; exports.Button = _chunkAPK5OQRVcjs.Button; exports.ButtonGroup = _chunk2ROKOO22cjs.ButtonGroup; exports.Calendar = _chunk6HUHH6Y4cjs.Calendar; exports.Card = _chunkV5RS3JZNcjs.Card; exports.Checkbox = _chunkIJ4DXG7Dcjs.Checkbox; exports.Chip = _chunkTRLBYA5Gcjs.Chip; exports.ColorPicker = _chunk5BX4Q76Dcjs.ColorPicker; exports.Combobox = _chunkWUBQJVB7cjs.Combobox; exports.CommandPalette = _chunk3EPND5M2cjs.CommandPalette; exports.ContextMenu = _chunkLFWPQYWXcjs.ContextMenu; exports.DataGrid = _chunkSKTDGCWJcjs.DataGrid; exports.DatePicker = _chunk6HUHH6Y4cjs.DatePicker; exports.Divider = _chunk366R4GKRcjs.Divider; exports.Drawer = _chunkLUL6RZ4Mcjs.Drawer; exports.Dropdown = _chunkFRUOA2QUcjs.Dropdown; exports.DropdownProvider = _chunkFRUOA2QUcjs.DropdownProvider; exports.EmptyState = _chunkKV3V2CPHcjs.EmptyState; exports.FONT_ACCEPT = _chunkSEDKUITEcjs.FONT_ACCEPT; exports.FileUpload = _chunk3CXULV4Gcjs.FileUpload; exports.Footer = _chunkPTBBY4ZXcjs.Footer; exports.Header = _chunkZ4SLV6WGcjs.Header; exports.IconButton = _chunkAPK5OQRVcjs.IconButton; exports.InlineEdit = _chunkN2IGZIEXcjs.InlineEdit; exports.Input = _chunk5VM2256Wcjs.Input; exports.Kbd = _chunkKVIL4EP4cjs.Kbd; exports.Menu = _chunkDAXZRNNOcjs.Menu; exports.MenuPanel = _chunkDAXZRNNOcjs.MenuPanel; exports.Modal = _chunkD5IY2HUVcjs.Modal; exports.Navigation = _chunkGT22EGJPcjs.Navigation; exports.NumberInput = _chunkQF2RAUPBcjs.NumberInput; exports.OTPInput = _chunkFKXKBJUIcjs.OTPInput; exports.PageLayout = _chunkG77YR73Rcjs.PageLayout; exports.Pagination = _chunkAXBYJ2OYcjs.Pagination; exports.Pill = _chunkEQ6BNPTLcjs.Pill; exports.Popover = _chunk3E7TKBQIcjs.Popover; exports.Progress = _chunkV2S6XWIQcjs.Progress; exports.Radio = _chunkQQIALSTXcjs.Radio; exports.RadioGroup = _chunkQQIALSTXcjs.RadioGroup; exports.SegmentedControl = _chunkAAA6TR5Icjs.SegmentedControl; exports.Select = _chunkKJZUYC4Xcjs.Select; exports.Skeleton = _chunkIYWAZEFIcjs.Skeleton; exports.Slider = _chunkOZZHOEN5cjs.Slider; exports.SnackbarContainer = _chunkEZME5CLKcjs.SnackbarContainer; exports.SnackbarProvider = _chunkEZME5CLKcjs.SnackbarProvider; exports.Spinner = _chunkTROYIUDHcjs.Spinner; exports.SplitButton = _chunkSKEWTQJ7cjs.SplitButton; exports.Stepper = _chunkHFZ5FKRGcjs.Stepper; exports.Switch = _chunkIXZMKIV5cjs.Switch; exports.Tab = _chunkKQD7CQ4Zcjs.Tab; exports.TabPanel = _chunkKQD7CQ4Zcjs.TabPanel; exports.Table = _chunkGGSII6Y2cjs.Table; exports.TableFiltersDropdown = _chunkLIFUHQH5cjs.TableFiltersDropdown; exports.Tabs = _chunkKQD7CQ4Zcjs.Tabs; exports.TagInput = _chunk442U3W6Rcjs.TagInput; exports.Textarea = _chunkRHOT7AEIcjs.Textarea; exports.ThemeEditor = _chunkJRYTZ5B2cjs.ThemeEditor; exports.ThemeProvider = _chunkSEDKUITEcjs.ThemeProvider; exports.TimeInput = _chunk6HUHH6Y4cjs.TimeInput; exports.Timeline = _chunkZMDZGL5Acjs.Timeline; exports.Toolbar = _chunkGR2PMCVNcjs.Toolbar; exports.Tooltip = _chunkZ5U4NJTWcjs.Tooltip; exports.TreeView = _chunkXKQMEVODcjs.TreeView; exports.VirtualList = _chunk6A2ZKVSTcjs.VirtualList; exports.defaultTheme = _chunkSEDKUITEcjs.defaultTheme; exports.familyNameFromFile = _chunkSEDKUITEcjs.familyNameFromFile; exports.isFontAvailable = _chunkSEDKUITEcjs.isFontAvailable; exports.isFontStackAvailable = _chunkSEDKUITEcjs.isFontStackAvailable; exports.registerFontFace = _chunkSEDKUITEcjs.registerFontFace; exports.registerFontFile = _chunkSEDKUITEcjs.registerFontFile; exports.renderIcon = _chunkGEHL4LKKcjs.renderIcon; exports.toFontStack = _chunkSEDKUITEcjs.toFontStack; exports.useDropdownContext = _chunkFRUOA2QUcjs.useDropdownContext; exports.useSnackbar = _chunkEZME5CLKcjs.useSnackbar; exports.useSnackbarContext = _chunkEZME5CLKcjs.useSnackbarContext; exports.useTheme = _chunkSEDKUITEcjs.useTheme;
295
295
  //# sourceMappingURL=index.cjs.map