@santi020k/lumen-react 0.4.0 → 1.2.0

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,10 +1,17 @@
1
1
  'use client';
2
2
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
- /* eslint-disable @eslint-react/no-children-only, @eslint-react/no-clone-element, @eslint-react/no-context-provider, @eslint-react/no-use-context -- Lumen uses React 19 ref props and context provider syntax that remains explicit for readability, and intentionally clones one child for polymorphic composition. */
4
- import { alignLumenChartSeries, composeClassName, createLumenBarGeometry, createLumenLineGeometry, createLumenPieGeometry, getLumenChartCategories, getLumenChartDomain, getLumenChartTicks, getLumenIcon, hasLumenChartData, hasLumenPieData, lumenCodeTokenClassNames, resolveLumenChartTone, scaleLumenChartValue, tokenizeLumenCode } from '@santi020k/lumen-core';
5
3
  import { Children, cloneElement, createContext, createElement, Fragment, isValidElement, useContext, useEffect, useId, useMemo, useRef, useState } from 'react';
4
+ import { alignLumenChartSeries, composeClassName, createLumenBarGeometry, createLumenLineGeometry, createLumenPieGeometry, getLumenChartCategories, getLumenChartDomain, getLumenChartTicks, getLumenChartToneClassName, getLumenIcon, getLumenPieChartVariantClassName, hasLumenChartData, hasLumenPieData, lumenCodeTokenClassNames, normalizeLumenFormErrors, resolveLumenChartTone, scaleLumenChartValue, tokenizeLumenCode } from '@santi020k/lumen-core';
6
5
  import { renderSVG } from 'uqr';
7
6
  import { useCalendar, useDialog, useDropdownMenu, useInputOTP, usePopover, useResizable, useSelect, useTabs, useTooltip } from './hooks.js';
7
+ const setRefValue = (ref, value) => {
8
+ if (typeof ref === 'function') {
9
+ ref(value);
10
+ }
11
+ else if (ref) {
12
+ ref.current = value;
13
+ }
14
+ };
8
15
  const emptyOptions = [];
9
16
  const emptyStringOptions = [];
10
17
  const emptyDataTableColumns = [];
@@ -16,7 +23,7 @@ const emptyPieSeries = {
16
23
  label: 'Values'
17
24
  };
18
25
  const emptyChartValues = [];
19
- const variantClass = (base, variant, defaultVariant = 'default') => variant === defaultVariant ? false : `${base}--${variant}`;
26
+ const variantClass = (base, variant, defaultVariant = 'default') => (variant === defaultVariant ? false : `${base}--${variant}`);
20
27
  const iconSizeClass = (size) => size === 'default' ? undefined : `ui-icon--${size}`;
21
28
  const reactSvgAttributeNames = {
22
29
  'clip-rule': 'clipRule',
@@ -29,7 +36,10 @@ const reactSvgAttributeNames = {
29
36
  'stroke-width': 'strokeWidth',
30
37
  tabindex: 'tabIndex'
31
38
  };
32
- const toReactSvgAttributes = (attributes) => Object.fromEntries(Object.entries(attributes).map(([name, value]) => [reactSvgAttributeNames[name] ?? name, value]));
39
+ const toReactSvgAttributes = (attributes) => Object.fromEntries(Object.entries(attributes).map(([name, value]) => [
40
+ reactSvgAttributeNames[name] ?? name,
41
+ value
42
+ ]));
33
43
  const renderIconNode = ([tagName, attributes, children], index) => createElement(tagName, {
34
44
  ...toReactSvgAttributes(attributes),
35
45
  key: attributes.key ?? index
@@ -60,18 +70,24 @@ const renderLucideIcon = (name, className) => {
60
70
  const icon = getLumenIcon(name);
61
71
  return icon ? renderIconSvg(icon, className) : null;
62
72
  };
63
- const resolveSurface = (surface, glass) => glass || surface === 'glass' ? 'glass' : surface;
64
- const glassIntensityClass = (glass) => glass === 'subtle' ? 'ui-glass-subtle' : glass === 'strong' && 'ui-glass-strong';
65
- const glassSurfaceClass = (base, surface, glass) => resolveSurface(surface, glass) === 'glass' && composeClassName(`${base}--glass`, glassIntensityClass(glass));
66
- const glassClass = (base, glass) => Boolean(glass) && composeClassName(`${base}--glass`, glassIntensityClass(glass));
73
+ const resolveSurface = (glass) => glass ? 'glass' : 'default';
74
+ const glassIntensityClass = (glass) => glass === 'subtle' ?
75
+ 'ui-glass-subtle' :
76
+ glass === 'strong' && 'ui-glass-strong';
77
+ const glassSurfaceClass = (base, glass) => resolveSurface(glass) === 'glass' &&
78
+ composeClassName(`${base}--glass`, glassIntensityClass(glass));
79
+ const glassClass = (base, glass) => Boolean(glass) &&
80
+ composeClassName(`${base}--glass`, glassIntensityClass(glass));
67
81
  const normalizeOption = (option) => typeof option === 'string' ? { label: option, value: option } : option;
68
82
  const isDataTableCellObject = (cell) => typeof cell === 'object' && cell !== null;
69
83
  const formatDataTableCell = (cell) => {
70
- const value = isDataTableCellObject(cell) ? cell.label ?? cell.value : cell;
84
+ const value = isDataTableCellObject(cell) ? (cell.label ?? cell.value) : cell;
71
85
  return value === undefined || value === null ? '' : String(value);
72
86
  };
73
87
  const getDataTableSortValue = (cell) => {
74
- if (!isDataTableCellObject(cell) || cell.sortValue === undefined || cell.sortValue === null)
88
+ if (!isDataTableCellObject(cell) ||
89
+ cell.sortValue === undefined ||
90
+ cell.sortValue === null)
75
91
  return undefined;
76
92
  return String(cell.sortValue);
77
93
  };
@@ -88,7 +104,7 @@ const composeHandlers = (userHandler, lumenHandler) => (event) => {
88
104
  const Primitive = ({ as, className, uiClassName, ...props }) => {
89
105
  const Tag = as ?? 'div';
90
106
  const safeClassName = typeof className === 'string' ? className : undefined;
91
- return _jsx(Tag, { className: composeClassName(uiClassName, safeClassName), ...props });
107
+ return (_jsx(Tag, { className: composeClassName(uiClassName, safeClassName), ...props }));
92
108
  };
93
109
  const DropdownMenuContext = createContext(null);
94
110
  const PopoverContext = createContext(null);
@@ -102,18 +118,18 @@ const requireContext = (context, componentName) => {
102
118
  };
103
119
  export const Accordion = ({ className, variant = 'default', ...props }) => (_jsx("div", { className: composeClassName('ui-accordion', variant === 'flush' && 'ui-accordion--flush', className), "data-variant": variant, ...props }));
104
120
  export const Alert = ({ className, glass = false, variant = 'default', ...props }) => (_jsx("aside", { className: composeClassName('ui-alert', variantClass('ui-alert', variant), glassClass('ui-alert', glass), className), "data-variant": variant, ...props }));
105
- export const AlertDialog = ({ className, defaultOpen, glass = false, onClick, onClose, onOpenChange, open, surface = 'default', ...props }) => {
121
+ export const AlertDialog = ({ className, defaultOpen, glass = false, onClick, onClose, onOpenChange, open, ...props }) => {
106
122
  const dialog = useDialog({ alert: true, defaultOpen, onOpenChange, open });
107
- return (_jsx("dialog", { ...dialog.dialogProps, ...props, className: composeClassName('ui-dialog ui-alert-dialog', glassSurfaceClass('ui-dialog', surface, glass), className), "data-surface": resolveSurface(surface, glass), onClick: composeHandlers(onClick, dialog.dialogProps.onClick), onClose: composeHandlers(onClose, dialog.dialogProps.onClose) }));
123
+ return (_jsx("dialog", { ...dialog.dialogProps, ...props, className: composeClassName('ui-dialog ui-alert-dialog', glassSurfaceClass('ui-dialog', glass), className), "data-surface": resolveSurface(glass), onClick: composeHandlers(onClick, dialog.dialogProps.onClick), onClose: composeHandlers(onClose, dialog.dialogProps.onClose) }));
108
124
  };
109
125
  export const Agenda = ({ className, glass = false, ...props }) => (_jsx("section", { className: composeClassName('ui-agenda', glassClass('ui-agenda', glass), className), ...props }));
110
126
  export const AspectRatio = ({ className, ratio = '16 / 9', style, ...props }) => (_jsx("div", { className: composeClassName('ui-aspect-ratio', className), style: { aspectRatio: ratio, ...style }, ...props }));
111
127
  export const Autocomplete = ({ className, type = 'search', ...props }) => (_jsx("input", { "aria-autocomplete": "list", className: composeClassName('ui-input ui-autocomplete', className), role: "combobox", type: type, ...props }));
112
128
  export const Attachment = ({ className, glass = false, href, ...props }) => {
113
129
  const nextClassName = composeClassName('ui-attachment', glassClass('ui-attachment', glass), className);
114
- return href
115
- ? _jsx("a", { className: nextClassName, href: href, ...props })
116
- : _jsx("article", { className: nextClassName, ...props });
130
+ return href ?
131
+ (_jsx("a", { className: nextClassName, href: href, ...props })) :
132
+ (_jsx("article", { className: nextClassName, ...props }));
117
133
  };
118
134
  export const Avatar = ({ alt = '', children, className, fallback, src, ...props }) => (_jsxs("span", { className: composeClassName('ui-avatar', className), ...props, children: [src ? _jsx("img", { alt: alt, src: src }) : _jsx("span", { children: fallback }), children] }));
119
135
  export const Badge = ({ className, variant = 'default', ...props }) => (_jsx("span", { className: composeClassName('ui-badge', `ui-badge--${variant}`, className), "data-variant": variant, ...props }));
@@ -127,16 +143,10 @@ export const Button = ({ asChild = false, children, className, disabled, loading
127
143
  if (!isValidElement(child)) {
128
144
  throw new TypeError('Button with asChild requires exactly one React element child.');
129
145
  }
130
- return cloneElement(child, {
131
- ...props,
132
- 'aria-busy': loading ? true : undefined,
133
- 'aria-disabled': disabled ? true : undefined,
134
- className: composeClassName(buttonClassName, child.props.className),
135
- ref: ref,
136
- children: (_jsxs(_Fragment, { children: [loading && _jsx("span", { "aria-hidden": "true", className: "ui-spinner" }), child.props.children] }))
137
- });
146
+ const ChildComponent = child.type;
147
+ return (_jsxs(ChildComponent, { ...child.props, ...props, "aria-busy": loading ? true : undefined, "aria-disabled": disabled ? true : undefined, className: composeClassName(buttonClassName, child.props.className), "data-slot": "button", ref: ref, children: [loading && _jsx("span", { "aria-hidden": "true", className: "ui-spinner" }), child.props.children] }));
138
148
  }
139
- return (_jsxs("button", { "aria-busy": loading || undefined, className: buttonClassName, disabled: disabled, ref: ref, type: type, ...props, children: [loading && _jsx("span", { "aria-hidden": "true", className: "ui-spinner" }), children] }));
149
+ return (_jsxs("button", { "aria-busy": loading || undefined, className: buttonClassName, "data-slot": "button", disabled: disabled, ref: ref, type: type, ...props, children: [loading && _jsx("span", { "aria-hidden": "true", className: "ui-spinner" }), children] }));
140
150
  };
141
151
  export const ButtonGroup = ({ className, ...props }) => (_jsx("div", { className: composeClassName('ui-button-group', className), ...props }));
142
152
  export const Calendar = ({ className, defaultValue, disabled = false, glass = false, locale, max, min, month, name, onValueChange, value, ...props }) => {
@@ -153,16 +163,30 @@ export const Calendar = ({ className, defaultValue, disabled = false, glass = fa
153
163
  });
154
164
  return (_jsxs("div", { ...props, ...calendar.rootProps, className: composeClassName(calendar.rootProps.className, glassClass('ui-calendar', glass), className), "data-ui-glass-track": glass ? true : undefined, children: [_jsx("input", { ...calendar.inputProps }), _jsxs("div", { className: "ui-calendar__header", children: [_jsx("button", { ...calendar.previousProps, type: "button", children: _jsx("span", { "aria-hidden": "true", children: "\u2039" }) }), _jsx("strong", { ...calendar.labelProps, children: calendar.label }), _jsx("button", { ...calendar.nextProps, type: "button", children: _jsx("span", { "aria-hidden": "true", children: "\u203A" }) })] }), _jsxs("table", { ...calendar.gridProps, children: [_jsx("thead", { children: _jsx("tr", { role: "row", children: calendar.weekdays.map(weekday => (_jsx("th", { role: "columnheader", scope: "col", children: weekday }, weekday))) }) }), _jsx("tbody", { children: calendar.weeks.map(week => (_jsx("tr", { role: "row", children: week.map(day => (_jsx("td", { ...calendar.getDayProps(day), children: day.day }, day.date))) }, week[0]?.date ?? 'week'))) })] })] }));
155
165
  };
156
- export const Card = ({ as, className, glass = false, variant = 'default', ...props }) => (_jsx(Primitive, { ...(as ? { as } : {}), className: composeClassName(variant === 'muted' && 'ui-card--muted', variant === 'interactive' && 'ui-card--interactive', variant === 'unstyled' && 'ui-card--unstyled', (glass || variant === 'glass') && composeClassName('ui-card--glass', glassIntensityClass(glass)), className), "data-variant": variant, ...props, uiClassName: "ui-card" }));
166
+ export const Card = ({ as, className, glass = false, variant = 'default', ...props }) => (_jsx(Primitive, { ...(as ? { as } : {}), className: composeClassName(variant === 'muted' && 'ui-card--muted', variant === 'interactive' && 'ui-card--interactive', variant === 'unstyled' && 'ui-card--unstyled', (glass || variant === 'glass') &&
167
+ composeClassName('ui-card--glass', glassIntensityClass(glass)), className), "data-slot": "card", "data-variant": variant, ...props, uiClassName: "ui-card" }));
168
+ export const CardHeader = ({ as, ...props }) => (_jsx(Primitive, { ...(as ? { as } : {}), "data-slot": "card-header", ...props, uiClassName: "ui-card__header" }));
169
+ export const CardTitle = ({ as, ...props }) => (_jsx(Primitive, { ...(as ? { as } : {}), "data-slot": "card-title", ...props, uiClassName: "ui-card__title" }));
170
+ export const CardDescription = ({ as, ...props }) => (_jsx(Primitive, { ...(as ? { as } : {}), "data-slot": "card-description", ...props, uiClassName: "ui-card__description" }));
171
+ export const CardContent = ({ as, ...props }) => (_jsx(Primitive, { ...(as ? { as } : {}), "data-slot": "card-content", ...props, uiClassName: "ui-card__content" }));
172
+ export const CardFooter = ({ as, ...props }) => (_jsx(Primitive, { ...(as ? { as } : {}), "data-slot": "card-footer", ...props, uiClassName: "ui-card__footer" }));
157
173
  export const Carousel = ({ className, glass = false, ...props }) => (_jsx("section", { className: composeClassName('ui-carousel', glassClass('ui-carousel', glass), className), "data-ui-carousel": true, ...props }));
158
- export const Chart = ({ caption, children, className, description, glass = false, heading, value, ...props }) => (_jsxs("figure", { className: composeClassName('ui-chart', glassClass('ui-chart', glass), className), ...props, children: [(heading || description || value) && (_jsxs("header", { children: [_jsxs("div", { className: "ui-chart__heading", children: [heading && _jsx("h3", { children: heading }), description && _jsx("p", { children: description })] }), value && _jsx("strong", { "data-ui-chart-value": true, children: value })] })), children, caption && _jsx("figcaption", { children: caption })] }));
159
- const ChartLegend = ({ series }) => (_jsx("ul", { "aria-label": "Chart legend", className: "ui-chart__legend", children: series.map((item, index) => (_jsxs("li", { className: `ui-chart-tone--${resolveLumenChartTone(item.tone, index)}`, children: [_jsx("span", { "aria-hidden": "true" }), item.label] }, item.id))) }));
160
- const ChartDataTable = ({ categories, series }) => (_jsxs("details", { className: "ui-chart__data", children: [_jsx("summary", { children: "View chart data" }), _jsx("div", { children: _jsxs("table", { children: [_jsx("thead", { children: _jsxs("tr", { children: [_jsx("th", { scope: "col", children: "Category" }), series.map(item => _jsx("th", { scope: "col", children: item.label }, item.id))] }) }), _jsx("tbody", { children: categories.map(category => (_jsxs("tr", { children: [_jsx("th", { scope: "row", children: String(category) }), series.map(item => {
174
+ const ChartHeader = ({ description, heading, value }) => {
175
+ if (!heading && !description && !value)
176
+ return null;
177
+ return (_jsxs("header", { children: [_jsxs("div", { className: "ui-chart__heading", children: [heading && _jsx("h3", { children: heading }), description && _jsx("p", { children: description })] }), value && _jsx("strong", { "data-ui-chart-value": true, children: value })] }));
178
+ };
179
+ export const Chart = ({ caption, children, className, description, glass = false, heading, presentation = 'default', value, ...props }) => (_jsxs("figure", { className: composeClassName('ui-chart', presentation === 'bare' && 'ui-chart--bare', glassClass('ui-chart', glass), className), ...props, children: [_jsx(ChartHeader, { description: description, heading: heading, value: value }), children, caption && _jsx("figcaption", { children: caption })] }));
180
+ const ChartLegend = ({ series }) => (_jsx("ul", { "aria-label": "Chart legend", className: "ui-chart__legend", children: series.map((item, index) => (_jsxs("li", { className: getLumenChartToneClassName(item.tone, index), children: [_jsx("span", { "aria-hidden": "true" }), item.label] }, item.id))) }));
181
+ const ChartDataTable = ({ categories, formatCategory = String, formatValue = String, series }) => (_jsxs("details", { className: "ui-chart__data", children: [_jsx("summary", { children: "View chart data" }), _jsx("div", { children: _jsxs("table", { children: [_jsx("thead", { children: _jsxs("tr", { children: [_jsx("th", { scope: "col", children: "Category" }), series.map(item => (_jsx("th", { scope: "col", children: item.label }, item.id)))] }) }), _jsx("tbody", { children: categories.map(category => (_jsxs("tr", { children: [_jsx("th", { scope: "row", children: series
182
+ .flatMap(item => item.data)
183
+ .find(datum => datum.x === category)?.xLabel ??
184
+ formatCategory(category) }), series.map(item => {
161
185
  const datum = item.data.find(candidate => candidate.x === category);
162
186
  return (_jsx("td", { children: datum?.label ??
163
- (datum?.y === null || datum === undefined
164
- ? 'Not available'
165
- : String(datum.y)) }, item.id));
187
+ (datum?.y === null || datum === undefined ?
188
+ 'Not available' :
189
+ formatValue(datum.y)) }, item.id));
166
190
  })] }, `${typeof category}:${String(category)}`))) })] }) })] }));
167
191
  const formatChartPercentage = (percentage) => new Intl.NumberFormat(undefined, {
168
192
  maximumFractionDigits: percentage < 0.01 ? 1 : 0,
@@ -172,25 +196,49 @@ export const Sparkline = ({ area = false, className, label = 'Trend', showEndpoi
172
196
  const geometry = createLumenLineGeometry(values.map((value, index) => ({ x: index, y: value })), { height: 40, padding: 3, width: 120 });
173
197
  const resolvedTone = resolveLumenChartTone(tone);
174
198
  const endpoint = geometry.points.at(-1);
175
- return (_jsxs("span", { "aria-label": label, className: composeClassName('ui-sparkline', `ui-chart-tone--${resolvedTone}`, className), role: "img", ...props, children: [_jsxs("svg", { "aria-hidden": "true", preserveAspectRatio: "none", viewBox: "0 0 120 40", children: [area && geometry.areaPaths.map(path => (_jsx("path", { className: "ui-sparkline__area", d: path }, path))), _jsx("path", { className: "ui-sparkline__line", d: geometry.path }), showEndpoint && endpoint && (_jsx("circle", { className: "ui-sparkline__endpoint", cx: endpoint.xCoordinate, cy: endpoint.yCoordinate, r: "2.5" }))] }), _jsx("span", { className: "ui-sr-only", children: label })] }));
199
+ return (_jsxs("span", { "aria-label": label, className: composeClassName('ui-sparkline', `ui-chart-tone--${resolvedTone}`, className), role: "img", ...props, children: [_jsxs("svg", { "aria-hidden": "true", preserveAspectRatio: "none", viewBox: "0 0 120 40", children: [area &&
200
+ geometry.areaPaths.map(path => (_jsx("path", { className: "ui-sparkline__area", d: path }, path))), _jsx("path", { className: "ui-sparkline__line", d: geometry.path }), showEndpoint && endpoint && (_jsx("circle", { className: "ui-sparkline__endpoint", cx: endpoint.xCoordinate, cy: endpoint.yCoordinate, r: "2.5" }))] }), _jsx("span", { className: "ui-sr-only", children: label })] }));
176
201
  };
177
202
  /* eslint-disable complexity -- Chart renderers keep optional semantic and SVG layers colocated. */
178
- export const BarChart = ({ className, layout = 'grouped', orientation = 'vertical', series = emptyChartSeries, showLegend = series.length > 1, showTable = true, ...props }) => {
179
- const geometry = createLumenBarGeometry(series, { layout, orientation });
203
+ export const BarChart = ({ categoryWidth, className, emptyLabel = 'No chart data available.', formatCategory = String, formatValue = String, layout = 'grouped', orientation = 'vertical', series = emptyChartSeries, showLegend = series.length > 1, showTable = true, ...props }) => {
204
+ const geometry = createLumenBarGeometry(series, {
205
+ ...(categoryWidth === undefined ? {} : { categoryWidth }),
206
+ layout,
207
+ orientation
208
+ });
180
209
  const hasData = hasLumenChartData(series);
181
210
  const ticks = getLumenChartTicks(geometry.domain);
182
- const categories = geometry.categories.map(category => category.label);
183
- const margin = orientation === 'horizontal'
184
- ? { bottom: 24, left: 112, right: 20, top: 16 }
185
- : { bottom: 52, left: 52, right: 16, top: 16 };
186
- return (_jsxs(Chart, { className: composeClassName('ui-bar-chart', className), ...props, children: [showLegend && hasData && _jsx(ChartLegend, { series: series }), !hasData && (_jsx("p", { className: "ui-chart__empty", role: "status", children: "No chart data available." })), _jsx("div", { className: "ui-chart__plot", hidden: !hasData, children: _jsxs("svg", { "aria-hidden": "true", preserveAspectRatio: "xMidYMid meet", viewBox: `0 0 ${geometry.width} ${geometry.height}`, children: [_jsx("g", { className: "ui-chart__grid", children: ticks.map(tick => {
187
- const coordinate = orientation === 'horizontal'
188
- ? scaleLumenChartValue(tick, geometry.domain, margin.left, geometry.width - margin.right)
189
- : scaleLumenChartValue(tick, geometry.domain, geometry.height - margin.bottom, margin.top);
190
- return orientation === 'horizontal' ? (_jsx("line", { x1: coordinate, x2: coordinate, y1: margin.top, y2: geometry.height - margin.bottom }, tick)) : (_jsx("line", { x1: margin.left, x2: geometry.width - margin.right, y1: coordinate, y2: coordinate }, tick));
191
- }) }), _jsx("g", { className: "ui-chart__axis-labels", children: geometry.categories.map(category => (_jsx("text", { dominantBaseline: orientation === 'horizontal' ? 'middle' : undefined, textAnchor: orientation === 'horizontal' ? 'end' : 'middle', x: category.x, y: category.y, children: String(category.label) }, `${typeof category.label}:${String(category.label)}`))) }), _jsx("g", { className: "ui-bar-chart__marks", children: geometry.marks.map(mark => (_jsx("rect", { className: `ui-chart-tone--${mark.tone}`, height: mark.height, rx: "4", width: mark.width, x: mark.x, y: mark.y, children: _jsx("title", { children: `${String(mark.category)} · ${mark.seriesLabel}: ${mark.value}` }) }, `${mark.seriesId}:${String(mark.category)}`))) })] }) }), showTable && hasData && (_jsx(ChartDataTable, { categories: categories, series: series }))] }));
192
- };
193
- export const LineChart = ({ area = false, className, markers = true, referenceValue, series = emptyChartSeries, showLegend = series.length > 1, showTable = true, ...props }) => {
211
+ const categories = geometry.categories.map(category => category.category);
212
+ const margin = orientation === 'horizontal' ?
213
+ {
214
+ bottom: 24,
215
+ left: Math.max(64, Math.min(240, categoryWidth ?? 112)),
216
+ right: 20,
217
+ top: 16
218
+ } :
219
+ { bottom: 52, left: 52, right: 16, top: 16 };
220
+ return (_jsxs(Chart, { className: composeClassName('ui-bar-chart', className), ...props, children: [showLegend && hasData && _jsx(ChartLegend, { series: series }), !hasData && (_jsx("p", { className: "ui-chart__empty", role: "status", children: emptyLabel })), _jsx("div", { className: "ui-chart__plot", hidden: !hasData, children: _jsxs("svg", { "aria-hidden": "true", preserveAspectRatio: "xMidYMid meet", viewBox: `0 0 ${geometry.width} ${geometry.height}`, children: [_jsx("g", { className: "ui-chart__grid", children: ticks.map(tick => {
221
+ const coordinate = orientation === 'horizontal' ?
222
+ scaleLumenChartValue(tick, geometry.domain, margin.left, geometry.width - margin.right) :
223
+ scaleLumenChartValue(tick, geometry.domain, geometry.height - margin.bottom, margin.top);
224
+ return orientation === 'horizontal' ?
225
+ (_jsx("line", { x1: coordinate, x2: coordinate, y1: margin.top, y2: geometry.height - margin.bottom }, tick)) :
226
+ (_jsx("line", { x1: margin.left, x2: geometry.width - margin.right, y1: coordinate, y2: coordinate }, tick));
227
+ }) }), _jsx("g", { className: "ui-chart__axis-labels", children: geometry.categories.map(category => (_jsx("text", { dominantBaseline: orientation === 'horizontal' ? 'middle' : undefined, textAnchor: orientation === 'horizontal' ? 'end' : 'middle', x: category.x, y: category.y, children: String(category.label) }, `${typeof category.label}:${String(category.label)}`))) }), _jsx("g", { className: "ui-bar-chart__marks", children: geometry.marks.map(mark => (_jsx("rect", { className: getLumenChartToneClassName(mark.tone), height: mark.height, rx: "4", width: mark.width, x: mark.x, y: mark.y, children: _jsx("title", { children: `${series
228
+ .flatMap(item => item.data)
229
+ .find(datum => datum.x === mark.category)?.xLabel ??
230
+ formatCategory(mark.category)} · ${mark.seriesLabel}: ${formatValue(mark.value)}` }) }, `${mark.seriesId}:${String(mark.category)}`))) })] }) }), showTable && hasData && (_jsx(ChartDataTable, { categories: categories, formatCategory: formatCategory, formatValue: formatValue, series: series }))] }));
231
+ };
232
+ const getLineChartMarkerStep = (markers, categoryCount) => {
233
+ if (markers === false || markers === 'none')
234
+ return Number.POSITIVE_INFINITY;
235
+ if (typeof markers === 'number')
236
+ return Math.max(1, Math.round(markers));
237
+ if (markers === 'auto')
238
+ return Math.max(1, Math.ceil(categoryCount / 24));
239
+ return 1;
240
+ };
241
+ export const LineChart = ({ area = false, className, emptyLabel = 'No chart data available.', formatCategory = String, formatValue = String, markers = 'auto', referenceValue, series = emptyChartSeries, showLegend = series.length > 1, showTable = true, ...props }) => {
194
242
  const width = 640;
195
243
  const height = 320;
196
244
  const padding = 44;
@@ -210,39 +258,50 @@ export const LineChart = ({ area = false, className, markers = true, referenceVa
210
258
  }));
211
259
  const ticks = getLumenChartTicks(domain);
212
260
  const labelStep = Math.max(1, Math.ceil(categories.length / 8));
213
- const referenceY = referenceValue === undefined
214
- ? undefined
215
- : scaleLumenChartValue(referenceValue, domain, height - padding, padding);
216
- return (_jsxs(Chart, { className: composeClassName('ui-line-chart', className), ...props, children: [showLegend && hasData && _jsx(ChartLegend, { series: series }), !hasData && (_jsx("p", { className: "ui-chart__empty", role: "status", children: "No chart data available." })), _jsx("div", { className: "ui-chart__plot", hidden: !hasData, children: _jsxs("svg", { "aria-hidden": "true", preserveAspectRatio: "xMidYMid meet", viewBox: `0 0 ${width} ${height}`, children: [_jsx("g", { className: "ui-chart__grid", children: ticks.map(tick => {
261
+ const markerStep = getLineChartMarkerStep(markers, categories.length);
262
+ const referenceY = referenceValue === undefined ?
263
+ undefined :
264
+ scaleLumenChartValue(referenceValue, domain, height - padding, padding);
265
+ return (_jsxs(Chart, { className: composeClassName('ui-line-chart', className), ...props, children: [showLegend && hasData && _jsx(ChartLegend, { series: series }), !hasData && (_jsx("p", { className: "ui-chart__empty", role: "status", children: emptyLabel })), _jsx("div", { className: "ui-chart__plot", hidden: !hasData, children: _jsxs("svg", { "aria-hidden": "true", preserveAspectRatio: "xMidYMid meet", viewBox: `0 0 ${width} ${height}`, children: [_jsx("g", { className: "ui-chart__grid", children: ticks.map(tick => {
217
266
  const y = scaleLumenChartValue(tick, domain, height - padding, padding);
218
- return (_jsxs(Fragment, { children: [_jsx("line", { x1: padding, x2: width - padding, y1: y, y2: y }), _jsx("text", { x: padding - 8, y: y, children: Number(tick.toPrecision(4)) })] }, tick));
267
+ return (_jsxs(Fragment, { children: [_jsx("line", { x1: padding, x2: width - padding, y1: y, y2: y }), _jsx("text", { x: padding - 8, y: y, children: formatValue(tick) })] }, tick));
219
268
  }) }), _jsx("g", { className: "ui-chart__axis-labels", children: categories.map((category, index) => {
220
269
  if (index % labelStep !== 0 && index !== categories.length - 1)
221
270
  return null;
222
271
  const denominator = Math.max(1, categories.length - 1);
223
272
  const x = padding + (index / denominator) * (width - padding * 2);
224
- return (_jsx("text", { textAnchor: "middle", x: x, y: height - 14, children: String(category) }, `${typeof category}:${String(category)}`));
273
+ return (_jsx("text", { textAnchor: "middle", x: x, y: height - 14, children: series
274
+ .flatMap(item => item.data)
275
+ .find(datum => datum.x === category)?.xLabel ??
276
+ formatCategory(category) }, `${typeof category}:${String(category)}`));
225
277
  }) }), referenceY !== undefined && (_jsx("line", { className: "ui-chart__reference", x1: padding, x2: width - padding, y1: referenceY, y2: referenceY })), geometries.map((geometry, index) => {
226
278
  const item = series[index];
227
279
  if (!item)
228
280
  return null;
229
281
  const tone = resolveLumenChartTone(item.tone, index);
230
- return (_jsxs("g", { className: `ui-line-chart__series ui-chart-tone--${tone}`, children: [area && geometry.areaPaths.map(path => (_jsx("path", { className: "ui-line-chart__area", d: path }, path))), _jsx("path", { className: "ui-line-chart__line", d: geometry.path }), markers && geometry.points.map(point => (_jsx("circle", { className: "ui-line-chart__point", cx: point.xCoordinate, cy: point.yCoordinate, r: "3", children: _jsx("title", { children: `${point.label ?? String(point.x)} · ${item.label}: ${String(point.y)}` }) }, `${typeof point.x}:${String(point.x)}`)))] }, item.id));
231
- })] }) }), showTable && hasData && (_jsx(ChartDataTable, { categories: categories, series: series }))] }));
282
+ return (_jsxs("g", { className: composeClassName('ui-line-chart__series', getLumenChartToneClassName(tone)), children: [area &&
283
+ geometry.areaPaths.map(path => (_jsx("path", { className: "ui-line-chart__area", d: path }, path))), _jsx("path", { className: "ui-line-chart__line", d: geometry.path }), Number.isFinite(markerStep) &&
284
+ geometry.points.map((point, pointIndex) => pointIndex % markerStep === 0 && (_jsx("circle", { className: "ui-line-chart__point", cx: point.xCoordinate, cy: point.yCoordinate, r: "3", children: _jsx("title", { children: `${point.xLabel ?? formatCategory(point.x)} · ${item.label}: ${formatValue(point.y ?? 0)}` }) }, `${typeof point.x}:${String(point.x)}`)))] }, item.id));
285
+ })] }) }), showTable && hasData && (_jsx(ChartDataTable, { categories: categories, formatCategory: formatCategory, formatValue: formatValue, series: series }))] }));
232
286
  };
233
287
  export const PieChart = ({ centerLabel, centerValue, className, series = emptyPieSeries, showLegend = true, showTable = true, valueFormatter = String, variant = 'donut', ...props }) => {
234
288
  const geometry = createLumenPieGeometry(series.data, { variant });
235
289
  const hasData = hasLumenPieData(series.data);
236
- return (_jsxs(Chart, { className: composeClassName('ui-pie-chart', `ui-pie-chart--${variant}`, className), ...props, children: [showLegend && hasData && (_jsx("ul", { "aria-label": "Chart legend", className: "ui-chart__legend", children: geometry.slices.map(slice => (_jsxs("li", { className: `ui-chart-tone--${slice.tone}`, children: [_jsx("span", { "aria-hidden": "true" }), slice.label] }, `${typeof slice.x}:${String(slice.x)}`))) })), !hasData && (_jsx("p", { className: "ui-chart__empty", role: "status", children: "No chart data available." })), _jsxs("div", { className: "ui-chart__plot ui-pie-chart__plot", hidden: !hasData, children: [_jsx("svg", { "aria-hidden": "true", preserveAspectRatio: "xMidYMid meet", viewBox: `0 0 ${geometry.size} ${geometry.size}`, children: _jsx("g", { className: "ui-pie-chart__slices", children: geometry.slices.map(slice => (_jsx("path", { className: `ui-chart-tone--${slice.tone}`, d: slice.path, fillRule: "evenodd", children: _jsx("title", { children: `${slice.label}: ${valueFormatter(slice.value)} (${formatChartPercentage(slice.percentage)})` }) }, `${typeof slice.x}:${String(slice.x)}`))) }) }), variant === 'donut' && (centerLabel != null || centerValue != null) && (_jsxs(_Fragment, { children: [_jsxs("div", { "aria-hidden": "true", className: "ui-pie-chart__center", children: [centerValue != null && _jsx("strong", { children: centerValue }), centerLabel != null && _jsx("span", { children: centerLabel })] }), _jsxs("div", { className: "ui-sr-only", children: [centerValue, " ", centerLabel] })] }))] }), showTable && hasData && (_jsxs("details", { className: "ui-chart__data", children: [_jsx("summary", { children: "View chart data" }), _jsx("div", { children: _jsxs("table", { children: [_jsx("thead", { children: _jsxs("tr", { children: [_jsx("th", { scope: "col", children: "Category" }), _jsx("th", { scope: "col", children: "Value" }), _jsx("th", { scope: "col", children: "Share" })] }) }), _jsx("tbody", { children: geometry.slices.map(slice => (_jsxs("tr", { children: [_jsx("th", { scope: "row", children: slice.label }), _jsx("td", { children: valueFormatter(slice.value) }), _jsx("td", { children: formatChartPercentage(slice.percentage) })] }, `${typeof slice.x}:${String(slice.x)}`))) })] }) })] }))] }));
290
+ const hasCenterLabel = centerLabel !== null && centerLabel !== undefined;
291
+ const hasCenterValue = centerValue !== null && centerValue !== undefined;
292
+ return (_jsxs(Chart, { className: composeClassName('ui-pie-chart', getLumenPieChartVariantClassName(variant), className), ...props, children: [showLegend && hasData && (_jsx("ul", { "aria-label": "Chart legend", className: "ui-chart__legend", children: geometry.slices.map(slice => (_jsxs("li", { className: getLumenChartToneClassName(slice.tone), children: [_jsx("span", { "aria-hidden": "true" }), slice.label] }, `${typeof slice.x}:${String(slice.x)}`))) })), !hasData && (_jsx("p", { className: "ui-chart__empty", role: "status", children: "No chart data available." })), _jsxs("div", { className: "ui-chart__plot ui-pie-chart__plot", hidden: !hasData, children: [_jsx("svg", { "aria-hidden": "true", preserveAspectRatio: "xMidYMid meet", viewBox: `0 0 ${geometry.size} ${geometry.size}`, children: _jsx("g", { className: "ui-pie-chart__slices", children: geometry.slices.map(slice => (_jsx("path", { className: getLumenChartToneClassName(slice.tone), d: slice.path, fillRule: "evenodd", children: _jsx("title", { children: `${slice.label}: ${valueFormatter(slice.value)} (${formatChartPercentage(slice.percentage)})` }) }, `${typeof slice.x}:${String(slice.x)}`))) }) }), variant === 'donut' && (hasCenterLabel || hasCenterValue) && (_jsxs(_Fragment, { children: [_jsxs("div", { "aria-hidden": "true", className: "ui-pie-chart__center", children: [hasCenterValue && _jsx("strong", { children: centerValue }), hasCenterLabel && _jsx("span", { children: centerLabel })] }), _jsxs("div", { className: "ui-sr-only", children: [centerValue, ' ', centerLabel] })] }))] }), showTable && hasData && (_jsxs("details", { className: "ui-chart__data", children: [_jsx("summary", { children: "View chart data" }), _jsx("div", { children: _jsxs("table", { children: [_jsx("thead", { children: _jsxs("tr", { children: [_jsx("th", { scope: "col", children: "Category" }), _jsx("th", { scope: "col", children: "Value" }), _jsx("th", { scope: "col", children: "Share" })] }) }), _jsx("tbody", { children: geometry.slices.map(slice => (_jsxs("tr", { children: [_jsx("th", { scope: "row", children: slice.label }), _jsx("td", { children: valueFormatter(slice.value) }), _jsx("td", { children: formatChartPercentage(slice.percentage) })] }, `${typeof slice.x}:${String(slice.x)}`))) })] }) })] }))] }));
237
293
  };
238
- export const Checkbox = ({ className, ...props }) => (_jsx("input", { className: composeClassName('ui-checkbox', className), type: "checkbox", ...props }));
294
+ export const Checkbox = ({ className, ref, ...props }) => (_jsx("input", { className: composeClassName('ui-checkbox', className), ref: ref, type: "checkbox", ...props }));
295
+ export const CheckboxGroup = ({ children, className, invalid = false, legend, orientation = 'vertical', ...props }) => (_jsxs("fieldset", { "aria-invalid": invalid || undefined, className: composeClassName('ui-checkbox-group', `ui-checkbox-group--${orientation}`, className), "data-invalid": invalid ? 'true' : undefined, "data-orientation": orientation, "data-ui-checkbox-group": true, ...props, children: [legend && _jsx("legend", { children: legend }), children] }));
239
296
  export const Collapsible = ({ className, ...props }) => (_jsx("details", { className: composeClassName('ui-collapsible', className), "data-ui-collapsible": true, ...props }));
240
297
  const renderCodeToken = (token) => {
241
298
  if (!token.kind)
242
299
  return token.value;
243
300
  return (_jsx("span", { className: lumenCodeTokenClassNames[token.kind], children: token.value }, `${token.start}-${token.kind}`));
244
301
  };
245
- const renderCodeChildren = (code, children, language) => code === undefined ? children : tokenizeLumenCode(code, language).map(renderCodeToken);
302
+ const renderCodeChildren = (code, children, language) => code === undefined ?
303
+ children :
304
+ tokenizeLumenCode(code, language).map(renderCodeToken);
246
305
  const renderCodeCopyButton = () => (_jsxs("button", { "aria-label": "Copy code to clipboard", className: "ui-code__copy", "data-ui-code-copy": true, type: "button", children: [renderLucideIcon('copy', 'ui-code__copy-icon'), renderLucideIcon('check', 'ui-code__check-icon')] }));
247
306
  const renderCodeHeader = ({ copy, label, language }) => {
248
307
  if (!copy && !label && !language)
@@ -252,9 +311,59 @@ const renderCodeHeader = ({ copy, label, language }) => {
252
311
  export const Code = ({ children, className, code, copy = false, highlighted = false, label, language, theme = 'auto', variant = 'inline', wrap = false, ...props }) => {
253
312
  const codeChildren = renderCodeChildren(code, children, language);
254
313
  if (variant === 'block') {
255
- return (_jsxs("figure", { className: composeClassName('ui-code ui-code--block', wrap && 'ui-code--wrap', className), "data-code-theme": theme, "data-language": language, "data-ui-code": true, ...props, children: [renderCodeHeader({ copy, label, language }), highlighted ? children : _jsx("pre", { children: _jsx("code", { children: codeChildren }) })] }));
314
+ return (_jsxs("figure", { className: composeClassName('ui-code ui-code--block', wrap && 'ui-code--wrap', className), "data-code-theme": theme, "data-language": language, "data-slot": "code", "data-ui-code": true, ...props, children: [renderCodeHeader({ copy, label, language }), highlighted ?
315
+ (children) :
316
+ (_jsx("pre", { children: _jsx("code", { children: codeChildren }) }))] }));
256
317
  }
257
- return (_jsx("code", { className: composeClassName('ui-code ui-code--inline', className), "data-code-theme": theme, "data-language": language, ...props, children: code ?? children }));
318
+ return (_jsx("code", { className: composeClassName('ui-code ui-code--inline', className), "data-code-theme": theme, "data-language": language, "data-slot": "code", ...props, children: code ?? children }));
319
+ };
320
+ const dispatchCopyToast = (enabled, title, variant) => {
321
+ if (!enabled)
322
+ return;
323
+ document.dispatchEvent(new CustomEvent('ui:toast', {
324
+ detail: { title, variant }
325
+ }));
326
+ };
327
+ const getCopyButtonLabel = (state, copiedLabel, errorLabel, label) => ({ copied: copiedLabel, error: errorLabel, idle: label })[state];
328
+ export const CopyButton = ({ children = 'Copy', className, copiedLabel = 'Copied to clipboard', errorLabel = 'Could not copy to clipboard', label = 'Copy to clipboard', onClick, resetAfter = 2000, target, toast = false, type = 'button', value, ...props }) => {
329
+ const [state, setState] = useState('idle');
330
+ const resetTimerRef = useRef(undefined);
331
+ useEffect(() => () => {
332
+ globalThis.clearTimeout(resetTimerRef.current);
333
+ }, []);
334
+ const handleClick = async (event) => {
335
+ onClick?.(event);
336
+ if (event.defaultPrevented)
337
+ return;
338
+ const button = event.currentTarget;
339
+ const targetElement = target ? document.querySelector(target) : null;
340
+ const text = value ?? targetElement?.innerText ?? targetElement?.textContent;
341
+ try {
342
+ if (text === undefined)
343
+ throw new Error(errorLabel);
344
+ await navigator.clipboard.writeText(text);
345
+ setState('copied');
346
+ button.dispatchEvent(new CustomEvent('ui:copy-success', {
347
+ bubbles: true,
348
+ detail: { value: text }
349
+ }));
350
+ dispatchCopyToast(toast, copiedLabel, 'success');
351
+ }
352
+ catch (error) {
353
+ setState('error');
354
+ button.dispatchEvent(new CustomEvent('ui:copy-error', {
355
+ bubbles: true,
356
+ detail: { error }
357
+ }));
358
+ dispatchCopyToast(toast, errorLabel, 'destructive');
359
+ }
360
+ globalThis.clearTimeout(resetTimerRef.current);
361
+ resetTimerRef.current = globalThis.setTimeout(() => {
362
+ setState('idle');
363
+ }, resetAfter);
364
+ };
365
+ const accessibleLabel = getCopyButtonLabel(state, copiedLabel, errorLabel, label);
366
+ return (_jsx("button", { "aria-label": accessibleLabel, className: composeClassName('ui-button ui-button--outline ui-copy-button', className), "data-state": state, "data-ui-copy-button": true, onClick: handleClick, type: type, ...props, children: children }));
258
367
  };
259
368
  export const Combobox = ({ className, defaultValue, id, label, list, onBlur, onChange, onFocus, onKeyDown, options = emptyStringOptions, type = 'text', value: valueProp, wrapperClassName, ...props }) => {
260
369
  const inputId = id ?? `${list}-input`;
@@ -274,7 +383,7 @@ export const Combobox = ({ className, defaultValue, id, label, list, onBlur, onC
274
383
  if (!nextTarget || !event.currentTarget.contains(nextTarget)) {
275
384
  setOpen(false);
276
385
  }
277
- }, children: [label && _jsx("label", { className: "ui-label", htmlFor: inputId, children: label }), _jsx("input", { "aria-autocomplete": "list", "aria-controls": list, "aria-expanded": open, className: composeClassName('ui-input', className), id: inputId, role: "combobox", type: type, value: renderedValue, onBlur: onBlur, onChange: event => {
386
+ }, children: [label && (_jsx("label", { className: "ui-label", htmlFor: inputId, children: label })), _jsx("input", { "aria-autocomplete": "list", "aria-controls": list, "aria-expanded": open, className: composeClassName('ui-input', className), id: inputId, role: "combobox", type: type, value: renderedValue, onBlur: onBlur, onChange: event => {
278
387
  if (valueProp === undefined) {
279
388
  setValue(event.currentTarget.value);
280
389
  }
@@ -292,14 +401,18 @@ export const Combobox = ({ className, defaultValue, id, label, list, onBlur, onC
292
401
  selectOption(visibleOptions[0]);
293
402
  }
294
403
  onKeyDown?.(event);
295
- }, ...props }), _jsx("div", { className: "ui-combobox__list", hidden: !open, id: list, role: "listbox", children: visibleOptions.map(option => (_jsx("button", { "data-ui-combobox-option": true, "data-value": option, role: "option", type: "button", onClick: () => { selectOption(option); }, onMouseDown: event => { event.preventDefault(); }, children: option }, option))) })] }));
404
+ }, ...props }), _jsx("div", { className: "ui-combobox__list", hidden: !open, id: list, role: "listbox", children: visibleOptions.map(option => (_jsx("button", { "data-ui-combobox-option": true, "data-value": option, role: "option", type: "button", onClick: () => {
405
+ selectOption(option);
406
+ }, onMouseDown: event => {
407
+ event.preventDefault();
408
+ }, children: option }, option))) })] }));
296
409
  };
297
410
  export const Command = ({ className, glass = false, ...props }) => (_jsx("div", { className: composeClassName('ui-command', glassClass('ui-command', glass), className), "data-ui-command": true, ...props }));
298
- export const ContextMenu = ({ className, glass = false, surface = 'default', ...props }) => (_jsx("menu", { className: composeClassName('ui-menu', glassSurfaceClass('ui-menu', surface, glass), className), "data-surface": resolveSurface(surface, glass), "data-ui-context-menu": true, role: props.role ?? 'menu', ...props }));
411
+ export const ContextMenu = ({ className, glass = false, ...props }) => (_jsx("menu", { className: composeClassName('ui-menu', glassSurfaceClass('ui-menu', glass), className), "data-surface": resolveSurface(glass), "data-ui-context-menu": true, role: props.role ?? 'menu', ...props }));
299
412
  export const ColorPicker = ({ className, type = 'color', ...props }) => (_jsx("input", { className: composeClassName('ui-color-picker', className), type: type, ...props }));
300
- export const DataTable = ({ children, className, columns = emptyDataTableColumns, glass = false, name, rows = emptyDataTableRows, selectable = false, ...props }) => (_jsx("div", { className: composeClassName('ui-data-table', glassClass('ui-data-table', glass), className), "data-ui-datatable": true, "data-ui-datatable-name": name, "data-ui-datatable-selectable": selectable ? 'true' : undefined, "data-ui-glass-track": glass ? true : undefined, ...props, children: columns.length > 0
301
- ? (_jsxs("table", { children: [_jsx("thead", { children: _jsx("tr", { children: columns.map(column => (_jsx("th", { "data-ui-datatable-sort-type": column.sort, "data-ui-datatable-sortable": column.sortable ? 'true' : undefined, scope: "col", children: column.header ?? column.label ?? column.key }, column.key))) }) }), _jsx("tbody", { children: rows.map((row, rowIndex) => (_jsx("tr", { "data-ui-datatable-row": true, "data-value": getDataTableRowValue(row, rowIndex), children: columns.map(column => (_jsx("td", { "data-sort-value": getDataTableSortValue(row[column.key]), children: formatDataTableCell(row[column.key]) }, column.key))) }, getDataTableRowValue(row, rowIndex)))) })] }))
302
- : children }));
413
+ export const DataTable = ({ children, className, columns = emptyDataTableColumns, glass = false, name, rows = emptyDataTableRows, selectable = false, ...props }) => (_jsx("div", { className: composeClassName('ui-data-table', glassClass('ui-data-table', glass), className), "data-ui-datatable": true, "data-ui-datatable-name": name, "data-ui-datatable-selectable": selectable ? 'true' : undefined, "data-ui-glass-track": glass ? true : undefined, ...props, children: columns.length > 0 ?
414
+ (_jsxs("table", { children: [_jsx("thead", { children: _jsx("tr", { children: columns.map(column => (_jsx("th", { "data-ui-datatable-sort-type": column.sort, "data-ui-datatable-sortable": column.sortable ? 'true' : undefined, scope: "col", children: column.header ?? column.label ?? column.key }, column.key))) }) }), _jsx("tbody", { children: rows.map((row, rowIndex) => (_jsx("tr", { "data-ui-datatable-row": true, "data-value": getDataTableRowValue(row, rowIndex), children: columns.map(column => (_jsx("td", { "data-sort-value": getDataTableSortValue(row[column.key]), children: formatDataTableCell(row[column.key]) }, column.key))) }, getDataTableRowValue(row, rowIndex)))) })] })) :
415
+ (children) }));
303
416
  const formatDatePickerDisplayValue = (value, placeholder) => {
304
417
  if (!value || !/^\d{4}-\d{2}-\d{2}$/.test(value))
305
418
  return placeholder;
@@ -313,15 +426,15 @@ const formatDatePickerDisplayValue = (value, placeholder) => {
313
426
  year: 'numeric'
314
427
  }).format(date);
315
428
  };
316
- const stringifyDatePickerConstraint = (value) => value === undefined ? undefined : String(value);
429
+ const stringifyDatePickerConstraint = (value) => (value === undefined ? undefined : String(value));
317
430
  /* eslint-disable complexity -- DatePicker coordinates controlled input, disclosure, Calendar, and native form contracts. */
318
- export const DatePicker = ({ className, defaultValue, disabled, glass = false, id, max, min, name, onChange, onInvalid, onValueChange, placeholder, required, value, ...props }) => {
431
+ export const DatePicker = ({ className, defaultValue, disabled, glass = false, id, inputRef, max, min, name, onChange, onInvalid, onValueChange, placeholder, required, value, ...props }) => {
319
432
  const generatedId = useId();
320
433
  const datePickerId = id ?? generatedId;
321
434
  const triggerId = `${datePickerId}-trigger`;
322
435
  const popoverId = `${datePickerId}-popover`;
323
436
  const rootRef = useRef(null);
324
- const inputRef = useRef(null);
437
+ const nativeInputRef = useRef(null);
325
438
  const triggerRef = useRef(null);
326
439
  const [internalValue, setInternalValue] = useState(defaultValue ?? '');
327
440
  const [open, setOpen] = useState(false);
@@ -335,7 +448,8 @@ export const DatePicker = ({ className, defaultValue, disabled, glass = false, i
335
448
  if (!open)
336
449
  return;
337
450
  const handlePointerDown = (event) => {
338
- if (event.target instanceof Node && !rootRef.current?.contains(event.target))
451
+ if (event.target instanceof Node &&
452
+ !rootRef.current?.contains(event.target))
339
453
  setOpen(false);
340
454
  };
341
455
  const handleKeyDown = (event) => {
@@ -355,10 +469,10 @@ export const DatePicker = ({ className, defaultValue, disabled, glass = false, i
355
469
  if (value === undefined)
356
470
  setInternalValue(nextValue);
357
471
  onValueChange?.(nextValue);
358
- if (inputRef.current) {
359
- inputRef.current.value = nextValue;
360
- inputRef.current.dispatchEvent(new Event('input', { bubbles: true }));
361
- inputRef.current.dispatchEvent(new Event('change', { bubbles: true }));
472
+ if (nativeInputRef.current) {
473
+ nativeInputRef.current.value = nextValue;
474
+ nativeInputRef.current.dispatchEvent(new Event('input', { bubbles: true }));
475
+ nativeInputRef.current.dispatchEvent(new Event('change', { bubbles: true }));
362
476
  }
363
477
  setOpen(false);
364
478
  globalThis.setTimeout(() => triggerRef.current?.focus());
@@ -372,7 +486,12 @@ export const DatePicker = ({ className, defaultValue, disabled, glass = false, i
372
486
  triggerRef.current?.setAttribute('aria-invalid', 'true');
373
487
  triggerRef.current?.focus();
374
488
  onInvalid?.(event);
375
- }, ref: inputRef, required: required, tabIndex: -1, type: "date", value: selectedValue, ...props }), _jsxs("div", { className: "ui-date-picker__control", "data-ui-date-picker-control": true, children: [_jsxs("button", { "aria-controls": popoverId, "aria-expanded": open, "aria-haspopup": "dialog", "aria-label": accessibleLabel, "aria-required": required ?? undefined, className: "ui-input ui-date-picker ui-date-picker__trigger", "data-ui-date-picker-trigger": true, disabled: disabled, id: triggerId, onClick: () => { setOpen(current => !current); }, onKeyDown: event => {
489
+ }, ref: node => {
490
+ nativeInputRef.current = node;
491
+ setRefValue(inputRef, node);
492
+ }, required: required, tabIndex: -1, type: "date", value: selectedValue, ...props }), _jsxs("div", { className: "ui-date-picker__control", "data-ui-date-picker-control": true, children: [_jsxs("button", { "aria-controls": popoverId, "aria-expanded": open, "aria-haspopup": "dialog", "aria-label": accessibleLabel, "aria-required": required ?? undefined, className: "ui-input ui-date-picker ui-date-picker__trigger", "data-ui-date-picker-trigger": true, disabled: disabled, id: triggerId, onClick: () => {
493
+ setOpen(current => !current);
494
+ }, onKeyDown: event => {
376
495
  if (event.key !== 'ArrowDown')
377
496
  return;
378
497
  event.preventDefault();
@@ -391,7 +510,9 @@ const getDateRangeState = (start, end) => {
391
510
  return end ? 'complete' : 'selecting-end';
392
511
  };
393
512
  const syncDateRangePickerElement = (root) => {
394
- const inputs = [...root.querySelectorAll('[data-ui-date-picker-native]')];
513
+ const inputs = [
514
+ ...root.querySelectorAll('[data-ui-date-picker-native]')
515
+ ];
395
516
  const start = inputs[0];
396
517
  const end = inputs.at(-1);
397
518
  if (!(start && end) || start === end)
@@ -420,15 +541,41 @@ export const DateRangePicker = ({ className, onInput, onRangeChange, ...props })
420
541
  onRangeChange?.(syncDateRangePickerElement(event.currentTarget));
421
542
  }, ref: rootRef, ...props }));
422
543
  };
423
- export const Dialog = ({ className, defaultOpen, glass = false, layout = 'centered', onClick, onClose, onOpenChange, open, surface = 'default', ...props }) => {
544
+ export const Dialog = ({ className, defaultOpen, glass = false, layout = 'centered', onClick, onClose, onOpenChange, open, ...props }) => {
424
545
  const dialog = useDialog({ defaultOpen, onOpenChange, open });
425
- return (_jsx("dialog", { ...dialog.dialogProps, ...props, className: composeClassName('ui-dialog', layout === 'fullscreen' && 'ui-dialog--fullscreen', glassSurfaceClass('ui-dialog', surface, glass), className), "data-layout": layout, "data-surface": resolveSurface(surface, glass), onClick: composeHandlers(onClick, dialog.dialogProps.onClick), onClose: composeHandlers(onClose, dialog.dialogProps.onClose) }));
546
+ return (_jsx("dialog", { ...dialog.dialogProps, ...props, className: composeClassName('ui-dialog', layout === 'fullscreen' && 'ui-dialog--fullscreen', glassSurfaceClass('ui-dialog', glass), className), "data-layout": layout, "data-surface": resolveSurface(glass), onClick: composeHandlers(onClick, dialog.dialogProps.onClick), onClose: composeHandlers(onClose, dialog.dialogProps.onClose) }));
426
547
  };
427
548
  export const Direction = ({ className, dir = 'ltr', ...props }) => (_jsx("div", { className: composeClassName('ui-direction', className), dir: dir, ...props }));
428
- export const Drawer = ({ className, glass = false, surface = 'default', ...props }) => (_jsx("dialog", { className: composeClassName('ui-drawer', glassSurfaceClass('ui-drawer', surface, glass), className), "data-surface": resolveSurface(surface, glass), "data-ui-drawer": true, ...props }));
429
- export const DropdownMenu = ({ children, className, defaultOpen, glass = false, onOpenChange, open, surface = 'default', ...props }) => {
549
+ export const Container = ({ as = 'div', className, size = 'lg', ...props }) => createElement(as, {
550
+ className: composeClassName('ui-container', `ui-container--${size}`, className),
551
+ 'data-size': size,
552
+ 'data-ui-container': true,
553
+ ...props
554
+ });
555
+ export const Grid = ({ as = 'div', className, columns = 'auto', gap = 'md', minItemWidth, style, ...props }) => {
556
+ const gridStyle = {
557
+ ...style,
558
+ '--ui-grid-min': minItemWidth
559
+ };
560
+ return createElement(as, {
561
+ className: composeClassName('ui-grid', `ui-grid--columns-${columns}`, `ui-grid--gap-${gap}`, className),
562
+ 'data-columns': columns,
563
+ 'data-ui-grid': true,
564
+ style: gridStyle,
565
+ ...props
566
+ });
567
+ };
568
+ export const Stack = ({ align = 'stretch', as = 'div', className, direction = 'vertical', gap = 'md', justify = 'start', wrap = false, ...props }) => createElement(as, {
569
+ className: composeClassName('ui-stack', `ui-stack--${direction}`, `ui-stack--gap-${gap}`, `ui-stack--align-${align}`, `ui-stack--justify-${justify}`, wrap && 'ui-stack--wrap', className),
570
+ 'data-direction': direction,
571
+ 'data-ui-stack': true,
572
+ ...props
573
+ });
574
+ export const VisuallyHidden = ({ className, focusable = false, ...props }) => (_jsx("span", { className: composeClassName('ui-visually-hidden', focusable && 'ui-visually-hidden--focusable', className), "data-ui-visually-hidden": true, ...props }));
575
+ export const Drawer = ({ className, glass = false, ...props }) => (_jsx("dialog", { className: composeClassName('ui-drawer', glassSurfaceClass('ui-drawer', glass), className), "data-surface": resolveSurface(glass), "data-ui-drawer": true, ...props }));
576
+ export const DropdownMenu = ({ children, className, defaultOpen, glass = false, onOpenChange, open, ...props }) => {
430
577
  const menu = useDropdownMenu({ defaultOpen, onOpenChange, open });
431
- return (_jsx(DropdownMenuContext.Provider, { value: menu, children: _jsx("menu", { ...menu.rootProps, ...props, className: composeClassName('ui-menu', glassSurfaceClass('ui-menu', surface, glass), className), "data-surface": resolveSurface(surface, glass), "data-ui-dropdown-menu": true, children: children }) }));
578
+ return (_jsx(DropdownMenuContext.Provider, { value: menu, children: _jsx("menu", { ...menu.rootProps, ...props, className: composeClassName('ui-menu', glassSurfaceClass('ui-menu', glass), className), "data-surface": resolveSurface(glass), "data-ui-dropdown-menu": true, children: children }) }));
432
579
  };
433
580
  export const DropdownMenuTrigger = ({ onClick, onKeyDown, ...props }) => {
434
581
  const menu = requireContext(useContext(DropdownMenuContext), 'DropdownMenuTrigger');
@@ -438,24 +585,96 @@ export const DropdownMenuContent = ({ className, onKeyDown, ...props }) => {
438
585
  const menu = requireContext(useContext(DropdownMenuContext), 'DropdownMenuContent');
439
586
  return (_jsx("div", { ...menu.panelProps, ...props, className: composeClassName('ui-menu__content', className), onKeyDown: composeHandlers(onKeyDown, menu.panelProps.onKeyDown), role: props.role ?? 'menu' }));
440
587
  };
441
- export const Empty = ({ className, glass = false, ...props }) => (_jsx("section", { className: composeClassName('ui-empty', glassClass('ui-empty', glass), className), ...props }));
442
- export const Field = ({ 'aria-describedby': ariaDescribedby, className, controlId, describedBy, glass = false, ...props }) => {
588
+ export const Empty = ({ className, glass = false, variant = 'default', ...props }) => (_jsx("section", { className: composeClassName('ui-empty', variant === 'compact' && 'ui-empty--compact', glassClass('ui-empty', glass), className), "data-variant": variant, ...props }));
589
+ export const KanbanBoard = ({ className, tabIndex = 0, ...props }) => (_jsx("section", { "aria-orientation": "horizontal", className: composeClassName('ui-kanban-board ui-kanban', className), "data-ui-kanban": true, tabIndex: tabIndex, ...props }));
590
+ export const KanbanColumn = ({ className, value, ...props }) => (_jsx("section", { className: composeClassName('ui-kanban__column', className), "data-ui-kanban-column": value, ...props }));
591
+ export const ErrorSummary = ({ className, errors, heading = 'There is a problem', id = 'ui-error-summary', ...props }) => {
592
+ const normalizedErrors = normalizeLumenFormErrors(errors);
593
+ const allErrors = [
594
+ ...normalizedErrors.form.map(message => ({ message })),
595
+ ...normalizedErrors.fields
596
+ ];
597
+ const headingId = `${id}-heading`;
598
+ return (_jsxs("section", { "aria-labelledby": headingId, className: composeClassName('ui-error-summary', className), "data-ui-error-summary": true, hidden: allErrors.length === 0, id: id, tabIndex: -1, ...props, children: [_jsx("h2", { id: headingId, children: heading }), _jsx("ul", { children: allErrors.map(error => (_jsx("li", { children: error.controlId ?
599
+ (_jsx("a", { href: `#${error.controlId}`, children: error.message })) :
600
+ (error.message) }, `${error.name ?? 'form'}:${error.controlId ?? ''}:${error.message}`))) })] }));
601
+ };
602
+ export const Field = ({ 'aria-describedby': ariaDescribedby, className, controlId, describedBy, glass = false, invalid = false, ...props }) => {
443
603
  const fieldDescribedBy = [ariaDescribedby, describedBy].filter(Boolean).join(' ') || undefined;
444
- return (_jsx("div", { "aria-describedby": ariaDescribedby, className: composeClassName('ui-field', glassClass('ui-field', glass), className), "data-ui-field": true, "data-ui-field-control": controlId, "data-ui-field-describedby": fieldDescribedBy, ...props }));
604
+ return (_jsx("div", { "aria-describedby": ariaDescribedby, className: composeClassName('ui-field', glassClass('ui-field', glass), className), "data-invalid": invalid ? 'true' : undefined, "data-ui-field": true, "data-ui-field-control": controlId, "data-ui-field-describedby": fieldDescribedBy, ...props }));
445
605
  };
446
- export const HoverCard = ({ className, glass = false, surface = 'default', ...props }) => (_jsx("aside", { className: composeClassName('ui-hover-card', glassSurfaceClass('ui-hover-card', surface, glass), className), "data-surface": resolveSurface(surface, glass), "data-ui-hover-card": true, ...props }));
606
+ export const FieldError = ({ children, className, hidden, message, ...props }) => {
607
+ const content = message ?? children;
608
+ return (_jsx("p", { className: composeClassName('ui-field-error', className), "data-ui-field-error": true, hidden: hidden ?? !content, ...props, children: content }));
609
+ };
610
+ export const Form = ({ 'aria-busy': ariaBusy, className, enhance = false, status = 'idle', ...props }) => (_jsx("form", { "aria-busy": ariaBusy ?? (status === 'submitting' ? true : undefined), className: composeClassName('ui-form', className), "data-status": status, "data-ui-form": enhance ? true : undefined, ...props }));
611
+ export const HoverCard = ({ className, glass = false, ...props }) => (_jsx("aside", { className: composeClassName('ui-hover-card', glassSurfaceClass('ui-hover-card', glass), className), "data-surface": resolveSurface(glass), "data-ui-hover-card": true, ...props }));
447
612
  export const Icon = ({ 'aria-hidden': ariaHidden, 'aria-label': ariaLabel, children, className, decorative, label, name, role, size = 'default', ...props }) => {
448
613
  const icon = name ? getLumenIcon(name) : undefined;
449
- const accessibility = getIconAccessibility({ ariaHidden, ariaLabel, decorative, label, role });
614
+ const accessibility = getIconAccessibility({
615
+ ariaHidden,
616
+ ariaLabel,
617
+ decorative,
618
+ label,
619
+ role
620
+ });
450
621
  return (_jsx("span", { "aria-hidden": accessibility.ariaHidden, "aria-label": accessibility.ariaLabel, className: composeClassName('ui-icon', iconSizeClass(size), className), role: accessibility.role, ...props, children: icon ? renderNamedIcon(icon) : children }));
451
622
  };
452
623
  export const Input = ({ className, ref, type = 'text', visualSize = 'default', ...props }) => (_jsx("input", { className: composeClassName('ui-input', visualSize === 'sm' && 'ui-input--sm', visualSize === 'lg' && 'ui-input--lg', className), ref: ref, type: type, ...props }));
624
+ export const Label = ({ className, ...props }) => (_jsx("label", { className: composeClassName('ui-label', className), ...props }));
625
+ const getPasswordFieldDescribedBy = (ariaDescribedBy, hintId, errorId) => [ariaDescribedBy, hintId, errorId].filter(Boolean).join(' ') || undefined;
626
+ const usePasswordVisibility = (inputRef) => {
627
+ const [visible, setVisible] = useState(false);
628
+ useEffect(() => {
629
+ const form = inputRef.current?.form;
630
+ if (!form)
631
+ return;
632
+ const handleReset = () => {
633
+ setVisible(false);
634
+ };
635
+ const hideAfterSuccess = () => {
636
+ if (form.dataset.status === 'success')
637
+ setVisible(false);
638
+ };
639
+ form.addEventListener('reset', handleReset);
640
+ const observer = new MutationObserver(hideAfterSuccess);
641
+ observer.observe(form, {
642
+ attributeFilter: ['data-status'],
643
+ attributes: true
644
+ });
645
+ return () => {
646
+ form.removeEventListener('reset', handleReset);
647
+ observer.disconnect();
648
+ };
649
+ }, [inputRef]);
650
+ const toggleVisibility = () => {
651
+ setVisible(current => !current);
652
+ inputRef.current?.focus({ preventScroll: true });
653
+ };
654
+ return { toggleVisibility, visible };
655
+ };
656
+ const PasswordFieldControl = ({ controlId, describedBy, error, forwardedRef, hideLabel, inputProps, inputRef, name, showLabel, toggleVisibility, visible }) => (_jsxs("div", { className: "ui-password-field__control", "data-ui-password-field": true, children: [_jsx(Input, { ...inputProps, "aria-describedby": describedBy, "aria-invalid": error ? true : undefined, id: controlId, name: name, ref: node => {
657
+ inputRef.current = node;
658
+ setRefValue(forwardedRef, node);
659
+ }, type: visible ? 'text' : 'password' }), _jsx(Button, { "aria-controls": controlId, "aria-label": visible ? hideLabel : showLabel, "aria-pressed": visible, "data-ui-password-toggle": true, onClick: toggleVisibility, size: "icon", type: "button", variant: "ghost", children: _jsx("span", { "aria-hidden": "true", children: visible ? 'Hide' : 'Show' }) })] }));
660
+ const PasswordFieldMessages = ({ error, errorId, hint, hintId }) => (_jsxs(_Fragment, { children: [hint && (_jsx("p", { className: "ui-field__hint", "data-ui-field-hint": true, id: hintId, children: hint })), _jsx(FieldError, { id: errorId, message: error })] }));
661
+ export const PasswordField = ({ 'aria-describedby': ariaDescribedby, className, error, hideLabel = 'Hide password', hint, id, label = 'Password', name, ref, showLabel = 'Show password', ...props }) => {
662
+ const generatedId = useId();
663
+ const controlId = id ?? `ui-password-${generatedId}`;
664
+ const hintId = hint ? `${controlId}-hint` : undefined;
665
+ const errorId = `${controlId}-error`;
666
+ const describedBy = getPasswordFieldDescribedBy(ariaDescribedby, hintId, error ? errorId : undefined);
667
+ const inputRef = useRef(null);
668
+ const { toggleVisibility, visible } = usePasswordVisibility(inputRef);
669
+ return (_jsxs(Field, { className: composeClassName('ui-password-field', className), controlId: controlId, invalid: Boolean(error), ...(describedBy ? { describedBy } : {}), children: [_jsx(Label, { htmlFor: controlId, children: label }), _jsx(PasswordFieldControl, { controlId: controlId, describedBy: describedBy, error: error, forwardedRef: ref, hideLabel: hideLabel, inputProps: props, inputRef: inputRef, name: name, showLabel: showLabel, toggleVisibility: toggleVisibility, visible: visible }), _jsx(PasswordFieldMessages, { error: error, errorId: errorId, hint: hint, hintId: hintId })] }));
670
+ };
453
671
  export const InputGroup = ({ className, ...props }) => (_jsx("div", { className: composeClassName('ui-input-group', className), ...props }));
454
- export const InputOTP = ({ 'aria-invalid': ariaInvalid, className, defaultValue, disabled = false, inputClassName, inputMode = 'numeric', length = 6, maxLength = length, pattern = '[0-9]*', value, ...props }) => {
672
+ export const InputOTP = ({ 'aria-invalid': ariaInvalid, className, defaultValue, disabled = false, inputClassName, inputMode = 'numeric', length = 6, maxLength = length, pattern = '[0-9]*', ref, value, ...props }) => {
455
673
  const otp = useInputOTP({
456
674
  defaultValue: toInputValue(defaultValue),
457
675
  disabled,
458
676
  inputMode,
677
+ inputRef: ref,
459
678
  invalid: ariaInvalid === true || ariaInvalid === 'true',
460
679
  length,
461
680
  pattern,
@@ -471,17 +690,22 @@ export const InputOTP = ({ 'aria-invalid': ariaInvalid, className, defaultValue,
471
690
  pattern
472
691
  }) }), _jsx("div", { ...otp.segmentsProps, children: otp.segmentIndexes.map(index => (_jsx("button", { ...otp.getSegmentProps(index), type: "button", children: _jsx("span", { "data-ui-input-otp-char": true, children: otp.getSegmentChar(index) }) }, index))) })] }));
473
692
  };
474
- export const NumberField = ({ className, type = 'number', ...props }) => (_jsx("input", { className: composeClassName('ui-input ui-number-field', className), type: type, ...props }));
475
- export const Item = ({ className, glass = false, ...props }) => (_jsx("div", { className: composeClassName('ui-item', glassClass('ui-item', glass), className), ...props }));
693
+ export const NumberField = ({ className, ref, type = 'number', ...props }) => (_jsx("input", { className: composeClassName('ui-input ui-number-field', className), ref: ref, type: type, ...props }));
694
+ export const Item = ({ as = 'div', className, glass = false, ...props }) => createElement(as, {
695
+ className: composeClassName('ui-item', glassClass('ui-item', glass), className),
696
+ 'data-slot': 'item',
697
+ ...props
698
+ });
476
699
  export const Kbd = ({ className, ...props }) => (_jsx("kbd", { className: composeClassName('ui-kbd', className), ...props }));
477
- export const Label = ({ className, ...props }) => (_jsx("label", { className: composeClassName('ui-label', className), ...props }));
478
700
  export const Marker = ({ className, variant = 'default', ...props }) => (_jsx("span", { className: composeClassName('ui-marker', variantClass('ui-marker', variant), className), "data-variant": variant, ...props }));
479
- export const Menubar = ({ className, glass = false, surface = 'default', ...props }) => (_jsx("nav", { className: composeClassName('ui-menubar', glassSurfaceClass('ui-menubar', surface, glass), className), "data-surface": resolveSurface(surface, glass), "data-ui-menubar": true, ...props }));
701
+ export const Menubar = ({ className, glass = false, ...props }) => (_jsx("nav", { className: composeClassName('ui-menubar', glassSurfaceClass('ui-menubar', glass), className), "data-surface": resolveSurface(glass), "data-ui-menubar": true, ...props }));
480
702
  export const Message = ({ className, from = 'assistant', ...props }) => (_jsx("article", { className: composeClassName('ui-message', `ui-message--${from}`, className), "data-from": from, ...props }));
481
703
  export const MessageScroller = ({ className, glass = false, ...props }) => (_jsx("div", { className: composeClassName('ui-message-scroller', glassClass('ui-message-scroller', glass), className), ...props }));
482
- export const NativeSelect = ({ children, className, defaultValue, options = emptyOptions, placeholder, value, ...props }) => {
483
- const placeholderDefaultValue = placeholder && value === undefined && defaultValue === undefined ? '' : undefined;
484
- return (_jsxs("select", { className: composeClassName('ui-select', className), defaultValue: defaultValue ?? placeholderDefaultValue, value: value, ...props, children: [placeholder && _jsx("option", { disabled: true, value: "", children: placeholder }), children, options.map(normalizeOption).map(option => (_jsx("option", { disabled: option.disabled, value: option.value, children: option.label }, option.value)))] }));
704
+ export const NativeSelect = ({ children, className, defaultValue, options = emptyOptions, placeholder, ref, value, visualSize = 'default', ...props }) => {
705
+ const placeholderDefaultValue = placeholder && value === undefined && defaultValue === undefined ?
706
+ '' :
707
+ undefined;
708
+ return (_jsxs("select", { className: composeClassName('ui-select', visualSize === 'sm' && 'ui-select--sm', visualSize === 'lg' && 'ui-select--lg', className), defaultValue: defaultValue ?? placeholderDefaultValue, ref: ref, value: value, ...props, children: [placeholder && (_jsx("option", { disabled: true, value: "", children: placeholder })), children, options.map(normalizeOption).map(option => (_jsx("option", { disabled: option.disabled, value: option.value, children: option.label }, option.value)))] }));
485
709
  };
486
710
  const phoneInputSizeModifiers = (size) => {
487
711
  if (size === 'sm') {
@@ -496,11 +720,12 @@ export const PhoneInput = ({ className, countries = emptyOptions, countryLabel =
496
720
  const { selectClass, inputClass } = phoneInputSizeModifiers(size);
497
721
  return (_jsxs("div", { className: composeClassName('ui-phone-input ui-input-group', className), ...props, children: [_jsx("select", { "aria-label": countryLabel, className: composeClassName('ui-select ui-phone-input__country', selectClass), defaultValue: defaultCountryValue, name: countryName, children: countries.map(normalizeOption).map(option => (_jsx("option", { disabled: option.disabled, value: option.value, children: option.label }, option.value))) }), _jsx("input", { autoComplete: "tel", className: composeClassName('ui-input ui-phone-input__number', inputClass), defaultValue: defaultValue, inputMode: "tel", name: name, placeholder: placeholder, type: "tel" })] }));
498
722
  };
499
- export const NavigationMenu = ({ className, glass = false, surface = 'default', variant = 'default', ...props }) => (_jsx("nav", { className: composeClassName('ui-navigation-menu', variant === 'unstyled' && 'ui-navigation-menu--unstyled', glassSurfaceClass('ui-navigation-menu', surface, glass), className), "data-surface": resolveSurface(surface, glass), "data-ui-navigation-menu": true, "data-variant": variant, ...props }));
723
+ export const NavigationMenu = ({ className, glass = false, variant = 'default', ...props }) => (_jsx("nav", { className: composeClassName('ui-navigation-menu', variant === 'unstyled' && 'ui-navigation-menu--unstyled', glassSurfaceClass('ui-navigation-menu', glass), className), "data-slot": "navigation-menu", "data-surface": resolveSurface(glass), "data-ui-navigation-menu": true, "data-variant": variant, ...props }));
724
+ export const ContextNavigation = ({ children, className, context, variant = 'default', ...props }) => (_jsxs("div", { className: composeClassName('ui-context-navigation', variant === 'unstyled' && 'ui-context-navigation--unstyled', className), "data-slot": "context-navigation", "data-variant": variant, ...props, children: [context && (_jsx("div", { className: "ui-context-navigation__context", "data-slot": "context-navigation-context", children: context })), children] }));
500
725
  export const Pagination = ({ 'aria-label': ariaLabel = 'Pagination', className, ...props }) => (_jsx("nav", { "aria-label": ariaLabel, className: composeClassName('ui-pagination', className), ...props }));
501
- export const Popover = ({ children, className, defaultOpen, glass = false, onOpenChange, open, surface = 'default', ...props }) => {
726
+ export const Popover = ({ children, className, defaultOpen, glass = false, onOpenChange, open, ...props }) => {
502
727
  const popover = usePopover({ defaultOpen, onOpenChange, open });
503
- return (_jsx(PopoverContext.Provider, { value: popover, children: _jsx("div", { ...popover.rootProps, ...props, className: composeClassName('ui-popover', glassSurfaceClass('ui-popover', surface, glass), className), "data-surface": resolveSurface(surface, glass), "data-ui-popover": true, children: children }) }));
728
+ return (_jsx(PopoverContext.Provider, { value: popover, children: _jsx("div", { ...popover.rootProps, ...props, className: composeClassName('ui-popover', glassSurfaceClass('ui-popover', glass), className), "data-surface": resolveSurface(glass), "data-ui-popover": true, children: children }) }));
504
729
  };
505
730
  export const PopoverTrigger = ({ onClick, onKeyDown, ...props }) => {
506
731
  const popover = requireContext(useContext(PopoverContext), 'PopoverTrigger');
@@ -514,7 +739,7 @@ export const Progress = ({ className, max = 100, value = 0, ...props }) => {
514
739
  const safeMax = max > 0 ? max : 100;
515
740
  const safeValue = Math.min(safeMax, Math.max(0, value));
516
741
  const percentage = (safeValue / safeMax) * 100;
517
- return (_jsx("div", { "aria-valuemax": safeMax, "aria-valuemin": 0, "aria-valuenow": safeValue, className: composeClassName('ui-progress', className), role: "progressbar", ...props, children: _jsx("span", { className: "ui-progress__bar", style: { width: `${percentage}%` } }) }));
742
+ return (_jsx("div", { "aria-valuemax": safeMax, "aria-valuemin": 0, "aria-valuenow": safeValue, className: composeClassName('ui-progress', className), role: "progressbar", ...props, children: _jsx("span", { className: "ui-progress__bar", "data-slot": "progress-indicator", style: { width: `${percentage}%` } }) }));
518
743
  };
519
744
  export const RadioGroup = ({ className, ...props }) => (_jsx("fieldset", { className: composeClassName('ui-radio-group', className), "data-ui-radio-group": true, ...props }));
520
745
  const renderResizablePane = (child, index, panelProps) => {
@@ -556,9 +781,9 @@ export const ScrollProgress = ({ 'aria-label': ariaLabel = 'Reading progress', c
556
781
  frame = 0;
557
782
  const scrollingElement = document.scrollingElement ?? document.documentElement;
558
783
  const maximum = scrollingElement.scrollHeight - scrollingElement.clientHeight;
559
- const percentage = maximum > 0
560
- ? Math.min(100, Math.max(0, (scrollingElement.scrollTop / maximum) * 100))
561
- : 0;
784
+ const percentage = maximum > 0 ?
785
+ Math.min(100, Math.max(0, (scrollingElement.scrollTop / maximum) * 100)) :
786
+ 0;
562
787
  const bar = root.querySelector('.ui-scroll-progress__bar');
563
788
  root.setAttribute('aria-valuenow', `${Math.round(percentage)}`);
564
789
  if (bar)
@@ -582,7 +807,7 @@ export const ScrollProgress = ({ 'aria-label': ariaLabel = 'Reading progress', c
582
807
  return (_jsx("div", { "aria-label": ariaLabel, "aria-valuemax": 100, "aria-valuemin": 0, "aria-valuenow": 0, className: composeClassName('ui-scroll-progress', position === 'bottom' && 'ui-scroll-progress--bottom', className), "data-position": position, "data-ui-scroll-progress": true, ref: rootRef, role: "progressbar", ...props, children: _jsx("span", { className: "ui-scroll-progress__bar" }) }));
583
808
  };
584
809
  export const Schedule = ({ className, glass = false, ...props }) => (_jsx("section", { className: composeClassName('ui-schedule', glassClass('ui-schedule', glass), className), "data-ui-schedule": true, ...props }));
585
- export const SearchField = ({ className, type = 'search', ...props }) => (_jsx("input", { className: composeClassName('ui-input ui-search-field', className), type: type, ...props }));
810
+ export const SearchField = ({ className, ref, type = 'search', ...props }) => (_jsx("input", { className: composeClassName('ui-input ui-search-field', className), ref: ref, type: type, ...props }));
586
811
  const getSelectSizeClass = (size) => {
587
812
  if (size === 'sm')
588
813
  return 'ui-select--sm';
@@ -590,7 +815,7 @@ const getSelectSizeClass = (size) => {
590
815
  return 'ui-select--lg';
591
816
  return false;
592
817
  };
593
- export const Select = ({ children, className, defaultValue, disabled = false, glass = false, id, name, onChange, onValueChange, options = emptyOptions, placeholder, required = false, size = 'default', value, ...props }) => {
818
+ export const Select = ({ children, className, defaultValue, disabled = false, glass = false, id, inputRef, name, onChange, onValueChange, options = emptyOptions, placeholder, required = false, size = 'default', value, ...props }) => {
594
819
  const select = useSelect({
595
820
  defaultValue,
596
821
  disabled,
@@ -603,23 +828,236 @@ export const Select = ({ children, className, defaultValue, disabled = false, gl
603
828
  value
604
829
  });
605
830
  const sizeClass = getSelectSizeClass(size);
606
- return (_jsxs("div", { ...select.rootProps, className: composeClassName('ui-select-field', glassClass('ui-select-field', glass), className), "data-ui-glass-track": glass ? true : undefined, children: [_jsxs("select", { ...select.nativeSelectProps, ...props, className: composeClassName('ui-select ui-select__native', sizeClass), onChange: composeHandlers(onChange, select.nativeSelectProps.onChange), children: [placeholder && _jsx("option", { "data-ui-select-placeholder": true, disabled: true, value: "", children: placeholder }), children, select.options.map(option => (_jsx("option", { disabled: option.disabled, value: option.value, children: option.label }, option.value)))] }), _jsxs("div", { ...select.controlProps, className: "ui-select__control", children: [_jsx("button", { ...select.triggerProps, className: composeClassName('ui-select ui-select__trigger', sizeClass), type: select.triggerProps.type ?? 'button', children: _jsx("span", { "data-ui-select-value": true, children: select.triggerText }) }), _jsx("div", { ...select.listProps, className: "ui-select__list", children: select.options.map(option => {
831
+ return (_jsxs("div", { ...select.rootProps, className: composeClassName('ui-select-field', glassClass('ui-select-field', glass), className), "data-ui-glass-track": glass ? true : undefined, children: [_jsxs("select", { ...select.nativeSelectProps, ...props, className: composeClassName('ui-select ui-select__native', sizeClass), onChange: composeHandlers(onChange, select.nativeSelectProps.onChange), ref: inputRef, children: [placeholder && (_jsx("option", { "data-ui-select-placeholder": true, disabled: true, value: "", children: placeholder })), children, select.options.map(option => (_jsx("option", { disabled: option.disabled, value: option.value, children: option.label }, option.value)))] }), _jsxs("div", { ...select.controlProps, className: "ui-select__control", children: [_jsx("button", { ...select.triggerProps, className: composeClassName('ui-select ui-select__trigger', sizeClass), type: select.triggerProps.type ?? 'button', children: _jsx("span", { "data-ui-select-value": true, children: select.triggerText }) }), _jsx("div", { ...select.listProps, className: "ui-select__list", children: select.options.map(option => {
607
832
  const optionProps = select.getOptionProps(option);
608
833
  return (_jsx("button", { ...optionProps, type: optionProps.type ?? 'button', children: option.label }, option.value));
609
834
  }) })] })] }));
610
835
  };
836
+ const groupListBoxOptions = (options) => {
837
+ const groups = [];
838
+ options.forEach((option, index) => {
839
+ const currentGroup = groups.at(-1);
840
+ if (!currentGroup || currentGroup.label !== option.section) {
841
+ groups.push({
842
+ ...(option.section ? { label: option.section } : {}),
843
+ options: [{ ...option, index }]
844
+ });
845
+ return;
846
+ }
847
+ currentGroup.options.push({ ...option, index });
848
+ });
849
+ return groups;
850
+ };
851
+ const getListBoxGroupKey = (group) => {
852
+ const values = group.options.map(option => option.value).join('|');
853
+ return group.label ? `${group.label}:${values}` : values;
854
+ };
855
+ const optionalTrue = (value) => value ? true : undefined;
856
+ const ListBoxNativeOptions = ({ groups }) => groups.map(group => {
857
+ const options = group.options.map(option => (_jsx("option", { disabled: option.disabled, value: option.value, children: option.label }, option.value)));
858
+ return group.label ?
859
+ (_jsx("optgroup", { label: group.label, children: options }, getListBoxGroupKey(group))) :
860
+ (_jsx(Fragment, { children: options }, getListBoxGroupKey(group)));
861
+ });
862
+ const ListBoxVisibleOptions = ({ activeIndex, controlId, emptyMessage, groups, loading, onSelect, selectedValues }) => {
863
+ if (loading)
864
+ return null;
865
+ if (!groups.length) {
866
+ return (_jsx("div", { "aria-disabled": "true", className: "ui-list-box__empty", role: "option", children: emptyMessage }));
867
+ }
868
+ return groups.map(group => {
869
+ const groupKey = getListBoxGroupKey(group);
870
+ const safeGroupKey = groupKey.replaceAll(/[^a-zA-Z0-9_-]/g, '-');
871
+ const groupLabelId = group.label ? `${controlId}-group-${safeGroupKey}` : undefined;
872
+ const options = group.options.map(option => (_jsx("div", { "aria-disabled": option.disabled ? true : undefined, "aria-selected": selectedValues.has(option.value), className: composeClassName('ui-list-box__option', option.index === activeIndex && 'ui-list-box__option--active'), "data-active": option.index === activeIndex ? 'true' : undefined, "data-ui-list-box-option": true, "data-value": option.value, onClick: () => {
873
+ onSelect(option.index);
874
+ }, role: "option", children: option.label }, option.value)));
875
+ if (!group.label)
876
+ return _jsx(Fragment, { children: options }, groupKey);
877
+ return (_jsxs("div", { "aria-labelledby": groupLabelId, role: "group", children: [_jsx("div", { className: "ui-list-box__section", id: groupLabelId, children: group.label }), options] }, groupKey));
878
+ });
879
+ };
880
+ const normalizeListBoxValues = (value) => {
881
+ if (value === undefined)
882
+ return [];
883
+ return Array.isArray(value) ? value : [value];
884
+ };
885
+ const findLastEnabledOptionIndex = (options) => {
886
+ let lastEnabledIndex = -1;
887
+ for (const [index, option] of options.entries()) {
888
+ if (!option.disabled)
889
+ lastEnabledIndex = index;
890
+ }
891
+ return lastEnabledIndex;
892
+ };
893
+ const getListBoxSelectedValues = (controlledValue, internalValues) => controlledValue === undefined ?
894
+ internalValues :
895
+ normalizeListBoxValues(controlledValue);
896
+ const getListBoxSelectValue = (multiple, selectedValues) => (multiple ? selectedValues : (selectedValues[0] ?? ''));
897
+ const ListBoxLabel = ({ controlId, label }) => label ?
898
+ (_jsx("label", { className: "ui-label", htmlFor: controlId, children: label })) :
899
+ null;
900
+ const normalizeListBoxProps = ({ disabled = false, emptyMessage = 'No options available', loading = false, loadingMessage = 'Loading options', multiple = false, options = emptyOptions, required = false, selectionFollowsFocus = false, ...props }) => ({
901
+ disabled,
902
+ emptyMessage,
903
+ loading,
904
+ loadingMessage,
905
+ multiple,
906
+ options,
907
+ required,
908
+ selectionFollowsFocus,
909
+ ...props
910
+ });
911
+ export const ListBox = (inputProps) => {
912
+ const { className, defaultValue, disabled, emptyMessage, id, label, loading, loadingMessage, multiple, name, onValueChange, options, ref, required, selectionFollowsFocus, value, ...props } = normalizeListBoxProps(inputProps);
913
+ const generatedId = useId();
914
+ const controlId = id ?? `ui-list-box-${generatedId}`;
915
+ const normalizedOptions = options.map(option => typeof option === 'string' ? { label: option, value: option } : option);
916
+ const optionGroups = groupListBoxOptions(normalizedOptions);
917
+ const [internalValues, setInternalValues] = useState(() => normalizeListBoxValues(defaultValue));
918
+ const selectedValues = getListBoxSelectedValues(value, internalValues);
919
+ const selectedSet = new Set(selectedValues);
920
+ const firstEnabledIndex = Math.max(0, normalizedOptions.findIndex(option => !option.disabled));
921
+ const [activeIndex, setActiveIndex] = useState(firstEnabledIndex);
922
+ const selectRef = useRef(null);
923
+ const typeaheadRef = useRef('');
924
+ const typeaheadTimerRef = useRef(undefined);
925
+ const commitValues = (nextValues) => {
926
+ if (value === undefined) {
927
+ setInternalValues(nextValues);
928
+ }
929
+ onValueChange?.(nextValues);
930
+ const select = selectRef.current;
931
+ if (!select)
932
+ return;
933
+ for (const option of select.options) {
934
+ option.selected = nextValues.includes(option.value);
935
+ }
936
+ select.dispatchEvent(new Event('input', { bubbles: true }));
937
+ select.dispatchEvent(new Event('change', { bubbles: true }));
938
+ };
939
+ const selectOption = (index) => {
940
+ const option = normalizedOptions[index];
941
+ if (!option || option.disabled || disabled || loading)
942
+ return;
943
+ const optionValue = option.value;
944
+ let nextValues;
945
+ if (!multiple) {
946
+ nextValues = [optionValue];
947
+ }
948
+ else if (selectedSet.has(optionValue)) {
949
+ nextValues = selectedValues.filter(selectedValue => selectedValue !== optionValue);
950
+ }
951
+ else {
952
+ nextValues = [...selectedValues, optionValue];
953
+ }
954
+ commitValues(nextValues);
955
+ };
956
+ const moveActive = (direction) => {
957
+ if (!normalizedOptions.length || loading)
958
+ return;
959
+ let nextIndex = activeIndex;
960
+ for (const _option of normalizedOptions) {
961
+ nextIndex =
962
+ (nextIndex + direction + normalizedOptions.length) %
963
+ normalizedOptions.length;
964
+ if (!normalizedOptions[nextIndex]?.disabled) {
965
+ setActiveIndex(nextIndex);
966
+ if (selectionFollowsFocus)
967
+ selectOption(nextIndex);
968
+ return;
969
+ }
970
+ }
971
+ };
972
+ useEffect(() => () => {
973
+ if (typeaheadTimerRef.current)
974
+ clearTimeout(typeaheadTimerRef.current);
975
+ }, []);
976
+ useEffect(() => {
977
+ const select = selectRef.current;
978
+ const form = select?.form;
979
+ if (!form || value !== undefined)
980
+ return;
981
+ const handleReset = () => {
982
+ setInternalValues(normalizeListBoxValues(defaultValue));
983
+ setActiveIndex(firstEnabledIndex);
984
+ };
985
+ form.addEventListener('reset', handleReset);
986
+ return () => {
987
+ form.removeEventListener('reset', handleReset);
988
+ };
989
+ }, [defaultValue, firstEnabledIndex, value]);
990
+ const handleNavigationKey = (key) => {
991
+ if (key === 'ArrowDown')
992
+ moveActive(1);
993
+ else if (key === 'ArrowUp')
994
+ moveActive(-1);
995
+ else if (key === 'Home') {
996
+ setActiveIndex(firstEnabledIndex);
997
+ if (selectionFollowsFocus)
998
+ selectOption(firstEnabledIndex);
999
+ }
1000
+ else if (key === 'End') {
1001
+ const lastEnabledIndex = findLastEnabledOptionIndex(normalizedOptions);
1002
+ if (lastEnabledIndex >= 0) {
1003
+ setActiveIndex(lastEnabledIndex);
1004
+ if (selectionFollowsFocus)
1005
+ selectOption(lastEnabledIndex);
1006
+ }
1007
+ }
1008
+ else if (key === 'Enter' || key === ' ')
1009
+ selectOption(activeIndex);
1010
+ else
1011
+ return false;
1012
+ return true;
1013
+ };
1014
+ const handleTypeaheadKey = (event) => {
1015
+ if (event.key.length !== 1 ||
1016
+ event.ctrlKey ||
1017
+ event.metaKey ||
1018
+ event.altKey)
1019
+ return false;
1020
+ typeaheadRef.current += event.key.toLowerCase();
1021
+ if (typeaheadTimerRef.current)
1022
+ clearTimeout(typeaheadTimerRef.current);
1023
+ typeaheadTimerRef.current = setTimeout(() => {
1024
+ typeaheadRef.current = '';
1025
+ }, 700);
1026
+ const nextIndex = normalizedOptions.findIndex(option => !option.disabled &&
1027
+ option.label.toLowerCase().startsWith(typeaheadRef.current));
1028
+ if (nextIndex >= 0) {
1029
+ setActiveIndex(nextIndex);
1030
+ if (selectionFollowsFocus)
1031
+ selectOption(nextIndex);
1032
+ }
1033
+ return true;
1034
+ };
1035
+ const handleListKeyDown = (event) => {
1036
+ if (handleNavigationKey(event.key) || handleTypeaheadKey(event))
1037
+ event.preventDefault();
1038
+ };
1039
+ return (_jsxs("div", { className: composeClassName('ui-list-box', className), "data-multiple": multiple ? 'true' : undefined, "data-ui-list-box": true, children: [_jsx(ListBoxLabel, { controlId: controlId, label: label }), _jsx("select", { ...props, className: "ui-list-box__native", "data-ui-enhanced": true, "data-ui-list-box-native": true, disabled: disabled || loading, id: controlId, multiple: multiple, name: name, ref: node => {
1040
+ selectRef.current = node;
1041
+ setRefValue(ref, node);
1042
+ }, required: required, value: getListBoxSelectValue(multiple, selectedValues), children: _jsx(ListBoxNativeOptions, { groups: optionGroups }) }), _jsx("div", { "aria-busy": optionalTrue(loading), "aria-disabled": optionalTrue(disabled || loading), "aria-multiselectable": optionalTrue(multiple), className: "ui-list-box__list", "data-ui-list-box-list": true, onKeyDown: handleListKeyDown, role: "listbox", tabIndex: disabled || loading ? undefined : 0, children: _jsx(ListBoxVisibleOptions, { activeIndex: activeIndex, controlId: controlId, emptyMessage: emptyMessage, groups: optionGroups, loading: loading, onSelect: index => {
1043
+ setActiveIndex(index);
1044
+ selectOption(index);
1045
+ }, selectedValues: selectedSet }) }), loading ?
1046
+ (_jsx("p", { className: "ui-list-box__status", role: "status", children: loadingMessage })) :
1047
+ null] }));
1048
+ };
611
1049
  export const Separator = ({ className, orientation = 'horizontal', ...props }) => (_jsx("hr", { "aria-orientation": orientation, className: composeClassName('ui-separator', `ui-separator--${orientation}`, className), "data-orientation": orientation, ...props }));
612
- export const Sheet = ({ className, glass = false, surface = 'default', ...props }) => (_jsx("dialog", { className: composeClassName('ui-sheet', glassSurfaceClass('ui-sheet', surface, glass), className), "data-surface": resolveSurface(surface, glass), "data-ui-sheet": true, ...props }));
613
- export const Sidebar = ({ className, glass = false, surface = 'default', variant = 'default', ...props }) => (_jsx("aside", { className: composeClassName('ui-sidebar', variant === 'unstyled' && 'ui-sidebar--unstyled', glassSurfaceClass('ui-sidebar', surface, glass), className), "data-surface": resolveSurface(surface, glass), "data-variant": variant, ...props }));
1050
+ export const Sheet = ({ className, glass = false, ...props }) => (_jsx("dialog", { className: composeClassName('ui-sheet', glassSurfaceClass('ui-sheet', glass), className), "data-surface": resolveSurface(glass), "data-ui-sheet": true, ...props }));
1051
+ export const Sidebar = ({ className, glass = false, variant = 'default', ...props }) => (_jsx("aside", { className: composeClassName('ui-sidebar', variant === 'unstyled' && 'ui-sidebar--unstyled', glassSurfaceClass('ui-sidebar', glass), className), "data-slot": "sidebar", "data-surface": resolveSurface(glass), "data-variant": variant, ...props }));
614
1052
  export const Skeleton = ({ className, ...props }) => (_jsx("div", { className: composeClassName('ui-skeleton', className), ...props }));
615
- export const Slider = ({ className, type = 'range', ...props }) => (_jsx("input", { className: composeClassName('ui-slider', className), type: type, ...props }));
1053
+ export const Slider = ({ className, ref, type = 'range', ...props }) => (_jsx("input", { className: composeClassName('ui-slider', className), ref: ref, type: type, ...props }));
616
1054
  export const Sonner = ({ className, ...props }) => (_jsx("div", { className: composeClassName('ui-sonner', className), "data-ui-sonner": true, ...props }));
617
1055
  export const Spinner = ({ 'aria-hidden': ariaHidden, 'aria-label': ariaLabel, 'aria-labelledby': ariaLabelledby, className, role, ...props }) => {
618
1056
  const isAccessible = Boolean(ariaLabel || ariaLabelledby);
619
1057
  return (_jsx("span", { "aria-hidden": ariaHidden ?? (isAccessible ? undefined : true), "aria-label": ariaLabel, "aria-labelledby": ariaLabelledby, className: composeClassName('ui-spinner', className), role: role ?? (isAccessible ? 'status' : undefined), ...props }));
620
1058
  };
621
- export const Switch = ({ className, role = 'switch', ...props }) => (_jsx("input", { className: composeClassName('ui-switch', className), role: role, type: "checkbox", ...props }));
622
- export const Table = ({ className, glass = false, ...props }) => (_jsx("div", { className: composeClassName('ui-table-wrap', glassClass('ui-table-wrap', glass), className), ...props }));
1059
+ export const Switch = ({ className, ref, role = 'switch', ...props }) => (_jsx("input", { className: composeClassName('ui-switch', className), ref: ref, role: role, type: "checkbox", ...props }));
1060
+ export const Table = ({ className, glass = false, ...props }) => (_jsx("div", { className: composeClassName('ui-table-wrap', glassClass('ui-table-wrap', glass), className), "data-slot": "table", ...props }));
623
1061
  export const Tabs = ({ children, className, defaultValue, glass = false, onValueChange, orientation, value, ...props }) => {
624
1062
  const tabs = useTabs({ defaultValue, onValueChange, orientation, value });
625
1063
  return (_jsx(TabsContext.Provider, { value: tabs, children: _jsx("div", { ...tabs.rootProps, ...props, className: composeClassName('ui-tabs', glassClass('ui-tabs', glass), className), children: children }) }));
@@ -649,9 +1087,9 @@ export const CodeTabs = ({ ariaLabel = 'Code examples', className, copy = true,
649
1087
  // Storage can be unavailable in privacy modes; tab selection should still work.
650
1088
  }
651
1089
  }
652
- return storedValue && items.some(item => item.value === storedValue)
653
- ? storedValue
654
- : (selectedValue ?? '');
1090
+ return storedValue && items.some(item => item.value === storedValue) ?
1091
+ storedValue :
1092
+ (selectedValue ?? '');
655
1093
  });
656
1094
  useEffect(() => {
657
1095
  const synchronize = (event) => {
@@ -682,13 +1120,15 @@ export const CodeTabs = ({ ariaLabel = 'Code examples', className, copy = true,
682
1120
  detail: { storageKey, value: nextValue }
683
1121
  }));
684
1122
  };
685
- return (_jsxs(Tabs, { className: composeClassName('ui-code-tabs', className), "data-storage-key": storageKey, onValueChange: selectValue, value: value, ...props, children: [_jsx(TabsList, { "aria-label": ariaLabel, className: "ui-code-tabs__list", children: items.map(item => (_jsx(TabsTrigger, { className: "ui-code-tabs__tab", value: item.value, children: item.label }, item.value))) }), items.map(item => (_jsx(TabsPanel, { className: "ui-code-tabs__panel", value: item.value, children: _jsx(Code, { code: item.code, copy: copy, ...(item.language === undefined ? {} : { language: item.language }), theme: theme, variant: "block", wrap: wrap }) }, item.value)))] }));
1123
+ return (_jsxs(Tabs, { className: composeClassName('ui-code-tabs', className), "data-slot": "code-tabs", "data-storage-key": storageKey, onValueChange: selectValue, value: value, ...props, children: [_jsx(TabsList, { "aria-label": ariaLabel, className: "ui-code-tabs__list", children: items.map(item => (_jsx(TabsTrigger, { className: "ui-code-tabs__tab", value: item.value, children: item.label }, item.value))) }), items.map(item => (_jsx(TabsPanel, { className: "ui-code-tabs__panel", value: item.value, children: _jsx(Code, { code: item.code, copy: copy, ...(item.language === undefined ?
1124
+ {} :
1125
+ { language: item.language }), theme: theme, variant: "block", wrap: wrap }) }, item.value)))] }));
686
1126
  };
687
1127
  export const TagGroup = ({ className, role = 'list', ...props }) => (_jsx("div", { className: composeClassName('ui-tag-group', className), role: role, ...props }));
688
- export const Textarea = ({ className, rows = 4, ...props }) => (_jsx("textarea", { className: composeClassName('ui-textarea', className), rows: rows, ...props }));
1128
+ export const Textarea = ({ className, ref, rows = 4, ...props }) => (_jsx("textarea", { className: composeClassName('ui-textarea', className), ref: ref, rows: rows, ...props }));
689
1129
  export const ThemeBuilder = ({ className, glass = false, ...props }) => (_jsx("section", { className: composeClassName('ui-theme-builder', glassClass('ui-theme-builder', glass), className), "data-ui-theme-builder": true, ...props }));
690
- export const TimeField = ({ className, type = 'time', ...props }) => (_jsx("input", { className: composeClassName('ui-input ui-time-field', className), type: type, ...props }));
691
- export const Toast = ({ 'aria-live': ariaLive, className, glass = false, role, surface = 'default', variant = 'default', ...props }) => (_jsx("aside", { "aria-live": ariaLive ?? (variant === 'destructive' ? 'assertive' : 'polite'), className: composeClassName('ui-toast', variantClass('ui-toast', variant), glassSurfaceClass('ui-toast', surface, glass), className), "data-surface": resolveSurface(surface, glass), "data-ui-toast": true, "data-variant": variant, role: role ?? (variant === 'destructive' ? 'alert' : 'status'), ...props }));
1130
+ export const TimeField = ({ className, ref, type = 'time', ...props }) => (_jsx("input", { className: composeClassName('ui-input ui-time-field', className), ref: ref, type: type, ...props }));
1131
+ export const Toast = ({ 'aria-live': ariaLive, className, glass = false, role, variant = 'default', ...props }) => (_jsx("aside", { "aria-live": ariaLive ?? (variant === 'destructive' ? 'assertive' : 'polite'), className: composeClassName('ui-toast', variantClass('ui-toast', variant), glassSurfaceClass('ui-toast', glass), className), "data-surface": resolveSurface(glass), "data-ui-toast": true, "data-variant": variant, role: role ?? (variant === 'destructive' ? 'alert' : 'status'), ...props }));
692
1132
  export const Toggle = ({ className, controlled = false, pressed = false, type = 'button', ...props }) => (_jsx("button", { "aria-pressed": pressed, className: composeClassName('ui-toggle', className), "data-ui-controlled": controlled || undefined, "data-ui-toggle": true, type: type, ...props }));
693
1133
  export const ToggleGroup = ({ className, ...props }) => (_jsx("div", { className: composeClassName('ui-toggle-group', className), "data-ui-toggle-group": true, ...props }));
694
1134
  export const Tooltip = ({ children, className, defaultOpen, delay, onKeyDown, onMouseEnter, onMouseLeave, onOpenChange, open, ...props }) => {
@@ -717,16 +1157,20 @@ export const Image = ({ as, className, invertOnDark = false, loading, ...props }
717
1157
  ...(resolvedLoading ? { loading: resolvedLoading } : {})
718
1158
  });
719
1159
  };
720
- export const Link = ({ className, newTab = false, ref, rel, target, variant = 'default', ...props }) => (_jsx("a", { className: composeClassName('ui-link', variant === 'inherit' && 'ui-link--inherit', className), "data-variant": variant, ref: ref, rel: newTab ? [...new Set(`${rel ?? ''} noopener noreferrer`.trim().split(/\s+/))].join(' ') : rel, target: newTab ? '_blank' : target, ...props }));
1160
+ export const Link = ({ className, newTab = false, ref, rel, target, variant = 'default', ...props }) => (_jsx("a", { className: composeClassName('ui-link', variant === 'inherit' && 'ui-link--inherit', className), "data-slot": "link", "data-variant": variant, ref: ref, rel: newTab ?
1161
+ [
1162
+ ...new Set(`${rel ?? ''} noopener noreferrer`.trim().split(/\s+/))
1163
+ ].join(' ') :
1164
+ rel, target: newTab ? '_blank' : target, ...props }));
721
1165
  export const Pill = ({ className, count, children, href, variant = 'neutral', ...props }) => {
722
1166
  const content = (_jsxs(_Fragment, { children: [children, count !== undefined && _jsx("span", { className: "ui-pill__count", children: count })] }));
723
- return href
724
- ? (_jsx("a", { className: composeClassName('ui-pill', variantClass('ui-pill', variant, 'neutral'), className), "data-variant": variant, href: href, ...props, children: content }))
725
- : (_jsx("span", { className: composeClassName('ui-pill', variantClass('ui-pill', variant, 'neutral'), className), "data-variant": variant, ...props, children: content }));
1167
+ return href ?
1168
+ (_jsx("a", { className: composeClassName('ui-pill', variantClass('ui-pill', variant, 'neutral'), className), "data-variant": variant, href: href, ...props, children: content })) :
1169
+ (_jsx("span", { className: composeClassName('ui-pill', variantClass('ui-pill', variant, 'neutral'), className), "data-variant": variant, ...props, children: content }));
726
1170
  };
727
1171
  export const Prose = ({ className, ...props }) => (_jsx("div", { className: composeClassName('ui-prose', className), ...props }));
728
1172
  export const SkipLink = ({ className, ...props }) => (_jsx("a", { className: composeClassName('ui-skip-link', className), ...props }));
729
- export const ThemeToggle = ({ className, type = 'button', ...props }) => (_jsxs("button", { className: composeClassName('ui-theme-toggle', className), type: type, ...props, children: [_jsx("span", { className: "ui-sr-only", children: "Toggle color theme" }), _jsx(Icon, { className: "ui-theme-toggle__sun", name: "sun" }), _jsx(Icon, { className: "ui-theme-toggle__moon", name: "moon" })] }));
1173
+ export const ThemeToggle = ({ className, type = 'button', ...props }) => (_jsxs("button", { className: composeClassName('ui-theme-toggle', className), "data-slot": "theme-toggle", type: type, ...props, children: [_jsx("span", { className: "ui-sr-only", children: "Toggle color theme" }), _jsx(Icon, { className: "ui-theme-toggle__sun", name: "sun" }), _jsx(Icon, { className: "ui-theme-toggle__moon", name: "moon" })] }));
730
1174
  export const LanguageToggle = ({ className, type = 'button', ...props }) => (_jsx("button", { className: composeClassName('ui-language-toggle', className), type: type, ...props }));
731
1175
  const particleDensityConfig = { high: 40, low: 15, medium: 25 };
732
1176
  const particleTones = [
@@ -759,7 +1203,7 @@ export const Particles = ({ children, className, density = 'medium', ...props })
759
1203
  // eslint-disable-next-line @eslint-react/set-state-in-effect
760
1204
  setParticles(createParticles(density));
761
1205
  }, [density]);
762
- return (_jsxs("div", { "aria-hidden": "true", className: composeClassName('ui-particles', className), "data-ui-particles": density, ...props, children: [children, particles.map(particle => _jsx("span", { className: "ui-particles__particle", style: particle.style }, particle.id))] }));
1206
+ return (_jsxs("div", { "aria-hidden": "true", className: composeClassName('ui-particles', className), "data-ui-particles": density, ...props, children: [children, particles.map(particle => (_jsx("span", { className: "ui-particles__particle", style: particle.style }, particle.id)))] }));
763
1207
  };
764
1208
  const parseMotionDuration = (value) => {
765
1209
  const parsedDuration = Number.parseFloat(value);
@@ -774,8 +1218,8 @@ export const ScrollReveal = ({ animation = 'fade', className, delay = 0, duratio
774
1218
  if (!root)
775
1219
  return;
776
1220
  root.dataset.uiScrollRevealBound = 'true';
777
- if (window.matchMedia('(prefers-reduced-motion: reduce)').matches
778
- || typeof IntersectionObserver === 'undefined') {
1221
+ if (window.matchMedia('(prefers-reduced-motion: reduce)').matches ||
1222
+ typeof IntersectionObserver === 'undefined') {
779
1223
  root.classList.add('is-revealed');
780
1224
  return;
781
1225
  }
@@ -787,9 +1231,14 @@ export const ScrollReveal = ({ animation = 'fade', className, delay = 0, duratio
787
1231
  observer.disconnect();
788
1232
  }, { threshold: Math.min(1, Math.max(0, threshold)) });
789
1233
  observer.observe(root);
790
- return () => { observer.disconnect(); };
1234
+ return () => {
1235
+ observer.disconnect();
1236
+ };
791
1237
  }, [once, threshold]);
792
- return (_jsx("div", { className: composeClassName('ui-scroll-reveal', `ui-scroll-reveal-${animation}`, `ui-motion-duration-${duration}`, className), "data-ui-reveal-once": String(once), "data-ui-reveal-threshold": Math.min(1, Math.max(0, threshold)), "data-ui-scroll-reveal": true, ref: rootRef, style: { ...style, '--ui-reveal-delay': `${Math.max(0, delay)}ms` }, ...props }));
1238
+ return (_jsx("div", { className: composeClassName('ui-scroll-reveal', `ui-scroll-reveal-${animation}`, `ui-motion-duration-${duration}`, className), "data-ui-reveal-once": String(once), "data-ui-reveal-threshold": Math.min(1, Math.max(0, threshold)), "data-ui-scroll-reveal": true, ref: rootRef, style: {
1239
+ ...style,
1240
+ '--ui-reveal-delay': `${Math.max(0, delay)}ms`
1241
+ }, ...props }));
793
1242
  };
794
1243
  export const RevealGroup = ({ animation = 'slide-up', className, delay = 0, duration = 'slow', once = true, stagger = 80, style, threshold = 0.15, ...props }) => {
795
1244
  const rootRef = useRef(null);
@@ -803,8 +1252,8 @@ export const RevealGroup = ({ animation = 'slide-up', className, delay = 0, dura
803
1252
  child.style.setProperty('--ui-reveal-index', String(index));
804
1253
  }
805
1254
  }
806
- if (window.matchMedia('(prefers-reduced-motion: reduce)').matches
807
- || typeof IntersectionObserver === 'undefined') {
1255
+ if (window.matchMedia('(prefers-reduced-motion: reduce)').matches ||
1256
+ typeof IntersectionObserver === 'undefined') {
808
1257
  root.classList.add('is-revealed');
809
1258
  return;
810
1259
  }
@@ -816,7 +1265,9 @@ export const RevealGroup = ({ animation = 'slide-up', className, delay = 0, dura
816
1265
  observer.disconnect();
817
1266
  }, { threshold: Math.min(1, Math.max(0, threshold)) });
818
1267
  observer.observe(root);
819
- return () => { observer.disconnect(); };
1268
+ return () => {
1269
+ observer.disconnect();
1270
+ };
820
1271
  }, [once, threshold]);
821
1272
  return (_jsx("div", { className: composeClassName('ui-reveal-group', `ui-reveal-group-${animation}`, `ui-motion-duration-${duration}`, className), "data-ui-reveal-group": true, "data-ui-reveal-once": String(once), "data-ui-reveal-threshold": Math.min(1, Math.max(0, threshold)), ref: rootRef, style: {
822
1273
  ...style,
@@ -837,8 +1288,8 @@ export const AnimatedNumber = ({ className, decimals = 0, duration = 'slow', fro
837
1288
  if (!output)
838
1289
  return;
839
1290
  const format = (current) => `${prefix}${formatter.format(current)}${suffix}`;
840
- if (window.matchMedia('(prefers-reduced-motion: reduce)').matches
841
- || typeof IntersectionObserver === 'undefined') {
1291
+ if (window.matchMedia('(prefers-reduced-motion: reduce)').matches ||
1292
+ typeof IntersectionObserver === 'undefined') {
842
1293
  output.textContent = format(value);
843
1294
  return;
844
1295
  }
@@ -850,13 +1301,15 @@ export const AnimatedNumber = ({ className, decimals = 0, duration = 'slow', fro
850
1301
  if (!entries.some(entry => entry.isIntersecting))
851
1302
  return;
852
1303
  observer.disconnect();
853
- const durationValue = getComputedStyle(root).getPropertyValue('--ui-motion-duration').trim();
1304
+ const durationValue = getComputedStyle(root)
1305
+ .getPropertyValue('--ui-motion-duration')
1306
+ .trim();
854
1307
  const durationMs = parseMotionDuration(durationValue);
855
1308
  const startedAt = performance.now();
856
1309
  const update = (now) => {
857
1310
  const progress = Math.min(1, (now - startedAt) / Math.max(1, durationMs));
858
1311
  const eased = 1 - Math.pow(1 - progress, 3);
859
- output.textContent = format(from + ((value - from) * eased));
1312
+ output.textContent = format(from + (value - from) * eased);
860
1313
  if (progress < 1) {
861
1314
  requestAnimationFrame(update);
862
1315
  }
@@ -867,34 +1320,54 @@ export const AnimatedNumber = ({ className, decimals = 0, duration = 'slow', fro
867
1320
  requestAnimationFrame(update);
868
1321
  }, { threshold: 0.15 });
869
1322
  observer.observe(root);
870
- return () => { observer.disconnect(); };
1323
+ return () => {
1324
+ observer.disconnect();
1325
+ };
871
1326
  }, [formatter, from, prefix, suffix, value]);
872
1327
  return (_jsxs("span", { className: composeClassName('ui-animated-number', `ui-motion-duration-${duration}`, className), "data-ui-animated-number": true, ...props, children: [_jsx("span", { "aria-hidden": "true", "data-ui-animated-number-output": true, ref: outputRef, children: formattedValue }), _jsx("span", { className: "ui-sr-only", children: formattedValue })] }));
873
1328
  };
874
- export const Stat = ({ as, className, label, value, variant = 'default', children, ...props }) => (_jsxs(Primitive, { ...(as ? { as } : {}), className: composeClassName(variant !== 'default' && `ui-stat--${variant}`, className), "data-variant": variant, ...props, uiClassName: "ui-stat", children: [label && _jsx("div", { className: "ui-stat-label", children: label }), value && _jsx("div", { className: "ui-stat-value", children: value }), children] }));
1329
+ export const Stat = ({ as, className, label, value, variant = 'default', children, ...props }) => (_jsxs(Primitive, { ...(as ? { as } : {}), className: composeClassName(variant !== 'default' && `ui-stat--${variant}`, className), "data-slot": "stat", "data-variant": variant, ...props, uiClassName: "ui-stat", children: [label && (_jsx("div", { className: "ui-stat-label", "data-slot": "stat-label", children: label })), value && (_jsx("div", { className: "ui-stat-value", "data-slot": "stat-value", children: value })), children] }));
1330
+ export const StatLabel = ({ as, ...props }) => (_jsx(Primitive, { ...(as ? { as } : {}), "data-slot": "stat-label", ...props, uiClassName: "ui-stat-label" }));
1331
+ export const StatValue = ({ as, ...props }) => (_jsx(Primitive, { ...(as ? { as } : {}), "data-slot": "stat-value", ...props, uiClassName: "ui-stat-value" }));
1332
+ export const StatDescription = ({ as, ...props }) => (_jsx(Primitive, { ...(as ? { as } : {}), "data-slot": "stat-description", ...props, uiClassName: "ui-stat-description" }));
1333
+ export const StatIcon = ({ className, ...props }) => (_jsx("span", { className: composeClassName('ui-stat-icon', className), "data-slot": "stat-icon", ...props }));
1334
+ export const StatTrend = ({ className, tone = 'neutral', ...props }) => (_jsx("span", { className: composeClassName('ui-stat-trend', `ui-stat-trend--${tone}`, className), "data-slot": "stat-trend", "data-tone": tone, ...props }));
875
1335
  export const Meter = ({ className, ...props }) => (_jsx("meter", { className: composeClassName('ui-meter', className), ...props }));
876
1336
  export const Note = ({ className, label, borderPosition = 'left', children, ...props }) => (_jsxs("div", { className: composeClassName('ui-note', `ui-note--border-${borderPosition}`, className), ...props, children: [label && _jsx("p", { className: "ui-note__label", children: label }), _jsx("div", { className: "ui-note__content", children: children })] }));
877
1337
  export const Rating = ({ className, value = 0, max = 5, readonly = false, ...props }) => {
878
1338
  const stars = Array.from({ length: max }, (_, i) => i + 1);
879
- return (_jsx("div", { className: composeClassName('ui-rating', readonly && 'ui-rating--readonly', className), "data-ui-rating": true, "data-value": value, "data-max": max, "data-readonly": readonly ? 'true' : 'false', ...props, children: stars.map((star) => (_jsx("button", { type: "button", className: "ui-rating__star", "data-active": star <= value ? 'true' : 'false', disabled: readonly, "aria-label": `Rate ${star} out of ${max}`, children: _jsx("svg", { viewBox: "0 0 24 24", width: "24", height: "24", stroke: "currentColor", strokeWidth: "2", fill: "none", strokeLinecap: "round", strokeLinejoin: "round", className: "ui-icon", children: _jsx("polygon", { points: "12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2" }) }) }, star))) }));
1339
+ return (_jsx("div", { className: composeClassName('ui-rating', readonly && 'ui-rating--readonly', className), "data-ui-rating": true, "data-value": value, "data-max": max, "data-readonly": readonly ? 'true' : 'false', ...props, children: stars.map(star => (_jsx("button", { type: "button", className: "ui-rating__star", "data-active": star <= value ? 'true' : 'false', disabled: readonly, "aria-label": `Rate ${star} out of ${max}`, children: _jsx("svg", { viewBox: "0 0 24 24", width: "24", height: "24", stroke: "currentColor", strokeWidth: "2", fill: "none", strokeLinecap: "round", strokeLinejoin: "round", className: "ui-icon", children: _jsx("polygon", { points: "12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2" }) }) }, star))) }));
880
1340
  };
881
1341
  export const Timeline = ({ className, ...props }) => (_jsx("ol", { className: composeClassName('ui-timeline', className), ...props }));
882
1342
  export const TimelineItem = ({ className, children, dot, ...props }) => (_jsxs("li", { className: composeClassName('ui-timeline-item', className), ...props, children: [_jsx("div", { className: "ui-timeline-item__tail" }), _jsx("div", { className: "ui-timeline-item__dot", children: dot }), _jsx("div", { className: "ui-timeline-item__content", children: children })] }));
883
1343
  export const AnimatedLogo = ({ animation = 'reveal', className, ...props }) => (_jsx("span", { className: composeClassName('ui-animated-logo', className), "data-animation": animation, "data-ui-animated-logo": true, ...props }));
884
1344
  export const AnimatedPortrait = ({ className, ...props }) => (_jsx("div", { className: composeClassName('ui-animated-portrait relative isolate w-full animate-reveal-lcp motion-reduce:animate-none', className), ...props }));
885
- export const ButtonLink = ({ className, ref, shape = 'default', variant = 'primary', ...props }) => {
886
- let variantClass = `ui-button ui-button--${variant} min-h-11 rounded-full border`;
887
- if (variant === 'unstyled') {
888
- variantClass = 'ui-button-link--unstyled';
889
- }
890
- else if (variant === 'inline') {
891
- variantClass = 'ui-button ui-button--inline';
1345
+ const getButtonLinkVariantClass = (variant) => {
1346
+ if (variant === 'unstyled')
1347
+ return 'ui-button-link--unstyled';
1348
+ if (variant === 'inline')
1349
+ return 'ui-button ui-button--inline';
1350
+ return `ui-button ui-button--${variant} min-h-11 rounded-full border`;
1351
+ };
1352
+ export const ButtonLink = ({ asChild = false, children, className, ref, shape = 'default', variant = 'primary', ...props }) => {
1353
+ const variantClass = getButtonLinkVariantClass(variant);
1354
+ const shapeClass = shape === 'icon' && variant !== 'inline' ?
1355
+ 'ui-button-link--icon' :
1356
+ undefined;
1357
+ const buttonLinkClassName = composeClassName('ui-button-link', variant !== 'unstyled' &&
1358
+ 'group inline-flex cursor-pointer items-center justify-center gap-2 text-sm font-semibold tracking-[0.01em]', variantClass, shapeClass, className);
1359
+ if (asChild) {
1360
+ const child = Children.only(children);
1361
+ if (!isValidElement(child)) {
1362
+ throw new Error('ButtonLink with asChild requires exactly one valid React element child.');
1363
+ }
1364
+ const ChildComponent = child.type;
1365
+ return (_jsx(ChildComponent, { ...child.props, ...props, className: composeClassName(buttonLinkClassName, child.props.className), "data-slot": "button-link", ref: ref, children: child.props.children }));
892
1366
  }
893
- const shapeClass = shape === 'icon' && variant !== 'inline' ? 'ui-button-link--icon' : undefined;
894
- return (_jsx("a", { className: composeClassName('ui-button-link', variant !== 'unstyled' && 'group inline-flex cursor-pointer items-center justify-center gap-2 text-sm font-semibold tracking-[0.01em]', variantClass, shapeClass, className), ref: ref, ...props }));
1367
+ return (_jsx("a", { className: buttonLinkClassName, "data-slot": "button-link", ref: ref, ...props, children: children }));
895
1368
  };
896
- export const CoverImage = ({ className, hover = false, showBottomGradient = false, children, ...props }) => (_jsxs("div", { className: composeClassName('ui-cover-image relative overflow-hidden bg-surface-muted', hover && 'ui-cover-image--hover', className), ...props, children: [children, showBottomGradient && _jsx("div", { "aria-hidden": "true", className: "ui-cover-image__bottom-gradient" })] }));
897
- export const GradientDivider = ({ className, ...props }) => (_jsx("div", { "aria-hidden": "true", className: composeClassName('ui-gradient-divider pointer-events-none flex justify-center', className), ...props, children: _jsx("div", { className: "h-px w-full max-w-5xl bg-gradient-to-r from-transparent via-brand/40 to-transparent" }) }));
1369
+ export const CoverImage = ({ className, hover = false, showBottomGradient = false, children, ...props }) => (_jsxs("div", { className: composeClassName('ui-cover-image relative overflow-hidden bg-surface-muted', hover && 'ui-cover-image--hover', className), ...props, children: [children, showBottomGradient && (_jsx("div", { "aria-hidden": "true", className: "ui-cover-image__bottom-gradient" }))] }));
1370
+ export const GradientDivider = ({ className, ...props }) => (_jsx("div", { "aria-hidden": "true", className: composeClassName('ui-gradient-divider pointer-events-none flex justify-center', className), ...props, children: _jsx("div", { className: "\n via-brand/40 h-px w-full max-w-5xl bg-linear-to-r from-transparent\n to-transparent\n " }) }));
898
1371
  const emptySteps = [];
899
1372
  const normalizeStep = (step) => typeof step === 'string' ? { description: undefined, title: step } : step;
900
1373
  export const Stepper = ({ children, className, currentStep = 0, orientation = 'horizontal', steps = emptySteps, ...props }) => (_jsxs("ol", { className: composeClassName('ui-stepper', orientation === 'vertical' && 'ui-stepper--vertical', className), "data-orientation": orientation, ...props, children: [steps.map(normalizeStep).map((step, index) => {
@@ -905,24 +1378,104 @@ export const Stepper = ({ children, className, currentStep = 0, orientation = 'h
905
1378
  else if (index === currentStep) {
906
1379
  state = 'current';
907
1380
  }
908
- return (_jsxs("li", { "aria-current": state === 'current' ? 'step' : undefined, className: "ui-stepper__step", "data-state": state, children: [_jsx("span", { className: "ui-stepper__marker", children: index + 1 }), _jsxs("span", { className: "ui-stepper__content", children: [_jsx("span", { className: "ui-stepper__title", children: step.title }), step.description && _jsx("span", { className: "ui-stepper__description", children: step.description })] })] }, step.title));
1381
+ return (_jsxs("li", { "aria-current": state === 'current' ? 'step' : undefined, className: "ui-stepper__step", "data-state": state, children: [_jsx("span", { className: "ui-stepper__marker", children: index + 1 }), _jsxs("span", { className: "ui-stepper__content", children: [_jsx("span", { className: "ui-stepper__title", children: step.title }), step.description && (_jsx("span", { className: "ui-stepper__description", children: step.description }))] })] }, step.title));
909
1382
  }), children] }));
910
- export const FileUpload = ({ children, className, hint, id, inputClassName, label = 'Choose a file or drag it here', onChange, ...props }) => {
1383
+ export const FileUpload = ({ children, className, hint, id, inputClassName, label = 'Choose a file or drag it here', onChange, ref, ...props }) => {
911
1384
  const generatedId = useId();
912
1385
  const inputId = id ?? `ui-file-upload-${generatedId.replaceAll(':', '')}`;
913
1386
  const [selectedFiles, setSelectedFiles] = useState([]);
914
- const selectedFileText = selectedFiles.length > 1
915
- ? `${selectedFiles.length} files selected`
916
- : selectedFiles[0] ?? '';
1387
+ const selectedFileText = selectedFiles.length > 1 ?
1388
+ `${selectedFiles.length} files selected` :
1389
+ (selectedFiles[0] ?? '');
917
1390
  return (_jsxs("label", { className: composeClassName('ui-file-upload', className), "data-state": selectedFiles.length > 0 ? 'selected' : 'idle', "data-ui-file-upload": true, htmlFor: inputId, children: [_jsx("input", { className: composeClassName('ui-file-upload__input', inputClassName), "data-ui-file-upload-input": true, id: inputId, onChange: event => {
918
1391
  setSelectedFiles([...(event.currentTarget.files ?? [])].map(file => file.name));
919
1392
  onChange?.(event);
920
- }, type: "file", ...props }), _jsxs("svg", { "aria-hidden": "true", className: "ui-file-upload__icon", fill: "none", focusable: "false", viewBox: "0 0 24 24", children: [_jsx("path", { d: "M12 16V4m0 0L8 8m4-4 4 4" }), _jsx("path", { d: "M5 14v4a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2v-4" })] }), _jsx("span", { className: "ui-file-upload__prompt", children: children ?? label }), hint && _jsx("span", { className: "ui-file-upload__hint", children: hint }), _jsx("span", { "aria-live": "polite", className: "ui-file-upload__files", "data-ui-file-upload-files": true, children: selectedFileText })] }));
1393
+ }, ref: ref, type: "file", ...props }), _jsxs("svg", { "aria-hidden": "true", className: "ui-file-upload__icon", fill: "none", focusable: "false", viewBox: "0 0 24 24", children: [_jsx("path", { d: "M12 16V4m0 0L8 8m4-4 4 4" }), _jsx("path", { d: "M5 14v4a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2v-4" })] }), _jsx("span", { className: "ui-file-upload__prompt", children: children ?? label }), hint && _jsx("span", { className: "ui-file-upload__hint", children: hint }), _jsx("span", { "aria-live": "polite", className: "ui-file-upload__files", "data-ui-file-upload-files": true, children: selectedFileText })] }));
921
1394
  };
922
1395
  const emptyTourSteps = [];
923
1396
  export const Tour = ({ children, className, closeLabel = 'Done', hidden = true, nextLabel = 'Next', steps = emptyTourSteps, ...props }) => (_jsxs("div", { className: composeClassName('ui-tour', className), "data-ui-tour": true, hidden: hidden, ...props, children: [_jsx("div", { "aria-hidden": "true", className: "ui-tour__backdrop", "data-ui-tour-backdrop": true }), _jsxs("div", { className: "ui-tour__popover", "data-ui-tour-popover": true, role: "dialog", children: [steps.map((step, index) => (_jsxs("div", { className: "ui-tour__step", "data-target": step.target, "data-ui-tour-step": true, hidden: index !== 0, children: [step.title && _jsx("p", { className: "ui-tour__title", children: step.title }), _jsx("p", { className: "ui-tour__content", children: step.content }), _jsxs("div", { className: "ui-tour__actions", children: [_jsx("button", { className: "ui-button ui-button--ghost ui-button--sm", "data-ui-tour-close": true, type: "button", children: closeLabel }), index < steps.length - 1 && (_jsx("button", { className: "ui-button ui-button--default ui-button--sm", "data-ui-tour-next": true, type: "button", children: nextLabel }))] })] }, step.target))), children] })] }));
924
1397
  const emptyAnchorLinks = [];
925
- export const Anchor = ({ 'aria-label': ariaLabel = 'On this page', children, className, items = emptyAnchorLinks, ...props }) => (_jsxs("nav", { "aria-label": ariaLabel, className: composeClassName('ui-anchor', className), "data-ui-anchor": true, ...props, children: [items.length > 0 && (_jsx("ol", { children: items.map((item, index) => (_jsx("li", { children: _jsx("a", { "data-active": index === 0 ? 'true' : 'false', "data-depth": item.depth, href: item.href, children: item.label }) }, item.href))) })), children] }));
1398
+ const createAnchorRef = () => {
1399
+ let cleanup;
1400
+ return (root) => {
1401
+ cleanup?.();
1402
+ cleanup = undefined;
1403
+ if (!root)
1404
+ return;
1405
+ const links = [...root.querySelectorAll('a[href^="#"]')];
1406
+ const targets = links.flatMap(link => {
1407
+ const id = decodeURIComponent(link.getAttribute('href')?.slice(1) ?? '');
1408
+ const target = id ? document.getElementById(id) : null;
1409
+ return target ? [{ link, target }] : [];
1410
+ });
1411
+ if (!targets.length)
1412
+ return;
1413
+ const abortController = new AbortController();
1414
+ let frame = 0;
1415
+ const setActive = (active) => {
1416
+ for (const link of links) {
1417
+ const isActive = link === active;
1418
+ link.dataset.active = String(isActive);
1419
+ if (isActive) {
1420
+ link.setAttribute('aria-current', 'location');
1421
+ }
1422
+ else {
1423
+ link.removeAttribute('aria-current');
1424
+ }
1425
+ }
1426
+ };
1427
+ const update = () => {
1428
+ frame = 0;
1429
+ const scrollingElement = document.scrollingElement ?? document.documentElement;
1430
+ const atEnd = scrollingElement.scrollTop + scrollingElement.clientHeight >=
1431
+ scrollingElement.scrollHeight - 1;
1432
+ const offset = Number(root.dataset.uiAnchorOffset) || 0;
1433
+ let active = targets[0]?.link;
1434
+ if (atEnd) {
1435
+ active = targets.at(-1)?.link;
1436
+ }
1437
+ else {
1438
+ for (const item of targets) {
1439
+ if (item.target.getBoundingClientRect().top > offset)
1440
+ break;
1441
+ active = item.link;
1442
+ }
1443
+ }
1444
+ if (active)
1445
+ setActive(active);
1446
+ };
1447
+ const requestUpdate = () => {
1448
+ if (frame)
1449
+ return;
1450
+ frame = requestAnimationFrame(update);
1451
+ };
1452
+ for (const { link } of targets) {
1453
+ link.addEventListener('click', () => {
1454
+ setActive(link);
1455
+ }, {
1456
+ signal: abortController.signal
1457
+ });
1458
+ }
1459
+ window.addEventListener('resize', requestUpdate, {
1460
+ passive: true,
1461
+ signal: abortController.signal
1462
+ });
1463
+ window.addEventListener('scroll', requestUpdate, {
1464
+ passive: true,
1465
+ signal: abortController.signal
1466
+ });
1467
+ update();
1468
+ cleanup = () => {
1469
+ abortController.abort();
1470
+ if (frame)
1471
+ cancelAnimationFrame(frame);
1472
+ };
1473
+ };
1474
+ };
1475
+ export const Anchor = ({ 'aria-label': ariaLabel = 'On this page', activationOffset = 96, children, className, items = emptyAnchorLinks, ...props }) => {
1476
+ const anchorRef = createAnchorRef();
1477
+ return (_jsxs("nav", { "aria-label": ariaLabel, className: composeClassName('ui-anchor', className), "data-ui-anchor": true, "data-ui-anchor-offset": Math.max(0, activationOffset), ref: anchorRef, ...props, children: [items.length > 0 && (_jsx("ol", { children: items.map((item, index) => (_jsx("li", { children: _jsxs("a", { "aria-current": index === 0 ? 'location' : undefined, "data-active": index === 0 ? 'true' : 'false', "data-depth": item.depth, href: item.href, children: [item.index !== undefined && (_jsx("span", { className: "ui-anchor__index", children: item.index })), _jsxs("span", { className: "ui-anchor__content", children: [_jsx("span", { className: "ui-anchor__label", children: item.label }), item.description && (_jsx("span", { className: "ui-anchor__description", children: item.description }))] })] }) }, item.href))) })), children] }));
1478
+ };
926
1479
  export const Segmented = ({ children, className, defaultValue, name = 'segmented', onValueChange, options = emptyOptions, size = 'default', value = defaultValue, ...props }) => (_jsxs("div", { className: composeClassName('ui-segmented', size === 'sm' && 'ui-segmented--sm', size === 'lg' && 'ui-segmented--lg', className), role: "group", ...props, children: [options.map(normalizeOption).map(option => (_jsxs("label", { className: "ui-segmented__option", children: [_jsx("input", { defaultChecked: value === option.value, className: "ui-segmented__input", disabled: option.disabled, name: name, type: "radio", value: option.value, onChange: () => onValueChange?.(option.value) }), _jsx("span", { className: "ui-segmented__label", children: option.label })] }, option.value))), children] }));
927
1480
  export const Toolbar = ({ 'aria-label': ariaLabel = 'Toolbar', className, orientation = 'horizontal', ...props }) => (_jsx("div", { "aria-label": ariaLabel, "aria-orientation": orientation, className: composeClassName('ui-toolbar', orientation === 'vertical' && 'ui-toolbar--vertical', className), "data-orientation": orientation, "data-ui-toolbar": true, role: "toolbar", ...props }));
928
1481
  const emptyDescriptionsItems = [];
@@ -972,13 +1525,93 @@ const flattenTreeSelect = (nodes, depth = 0, acc = []) => {
972
1525
  };
973
1526
  const emptyTreeSelectNodes = [];
974
1527
  export const TreeSelect = ({ children, className, name, placeholder = 'Select…', treeData = emptyTreeSelectNodes, ...props }) => (_jsxs("div", { className: composeClassName('ui-tree-select', className), "data-surface": "default", "data-ui-popover": true, "data-ui-tree-select": true, ...props, children: [_jsx("button", { "aria-expanded": "false", "aria-haspopup": "tree", className: "ui-select ui-select__trigger ui-tree-select__trigger", "data-ui-trigger": true, type: "button", children: _jsx("span", { "data-ui-tree-select-placeholder": placeholder, "data-ui-tree-select-value": true, children: placeholder }) }), _jsxs("div", { className: "ui-tree-select__panel", "data-ui-panel": true, hidden: true, children: [_jsx("div", { className: "ui-tree-select__tree", role: "tree", children: flattenTreeSelect(treeData).map(row => (_jsx("button", { className: "ui-tree-select__option", "data-value": row.value, role: "treeitem", style: { ['--ui-tree-depth']: row.depth }, type: "button", children: row.label }, row.value))) }), children] }), name && _jsx("input", { "data-ui-tree-select-input": true, name: name, type: "hidden" })] }));
975
- export const Mentions = ({ className, name, options = emptyOptions, placeholder, trigger = '@', ...props }) => (_jsxs("div", { className: composeClassName('ui-mentions', className), "data-ui-mentions": true, "data-ui-mentions-trigger": trigger, ...props, children: [_jsx("textarea", { className: "ui-textarea ui-mentions__input", "data-ui-mentions-input": true, name: name, placeholder: placeholder, rows: 3 }), _jsx("ul", { className: "ui-mentions__list", "data-ui-mentions-list": true, hidden: true, role: "listbox", children: options.map(normalizeOption).map(option => (_jsx("li", { children: _jsx("button", { className: "ui-mentions__option", "data-ui-mentions-option": true, "data-value": option.value, role: "option", type: "button", children: option.label }) }, option.value))) })] }));
1528
+ export const Mentions = ({ className, defaultValue = '', label = 'Mentions', name, onValueChange, options = emptyOptions, placeholder, trigger = '@', value, ...props }) => {
1529
+ const generatedId = useId();
1530
+ const inputRef = useRef(null);
1531
+ const [activeIndex, setActiveIndex] = useState(-1);
1532
+ const [internalValue, setInternalValue] = useState(defaultValue);
1533
+ const [query, setQuery] = useState(null);
1534
+ const currentValue = value ?? internalValue;
1535
+ const listId = `ui-mentions-${generatedId}-list`;
1536
+ const escapedTrigger = trigger.replaceAll(/[.*+?^${}()|[\]\\]/g, '\\$&');
1537
+ const normalizedOptions = options.map(normalizeOption);
1538
+ const visibleOptions = query === null ?
1539
+ [] :
1540
+ normalizedOptions.filter(option => option.value.toLowerCase().startsWith(query));
1541
+ const expanded = visibleOptions.length > 0;
1542
+ const resolvedActiveIndex = expanded ?
1543
+ Math.min(Math.max(activeIndex, 0), visibleOptions.length - 1) :
1544
+ -1;
1545
+ const activeOption = visibleOptions[resolvedActiveIndex];
1546
+ const setNextValue = (nextValue) => {
1547
+ if (value === undefined)
1548
+ setInternalValue(nextValue);
1549
+ onValueChange?.(nextValue);
1550
+ };
1551
+ const closeList = () => {
1552
+ setQuery(null);
1553
+ setActiveIndex(-1);
1554
+ };
1555
+ const insertMention = (mention) => {
1556
+ const caret = inputRef.current?.selectionStart ?? currentValue.length;
1557
+ const before = currentValue
1558
+ .slice(0, caret)
1559
+ .replace(new RegExp(`${escapedTrigger}\\w*$`), `${trigger}${mention} `);
1560
+ const nextValue = before + currentValue.slice(caret);
1561
+ setNextValue(nextValue);
1562
+ closeList();
1563
+ requestAnimationFrame(() => {
1564
+ inputRef.current?.focus();
1565
+ inputRef.current?.setSelectionRange(before.length, before.length);
1566
+ });
1567
+ };
1568
+ const handleKeyDown = (event) => {
1569
+ if (event.key === 'Escape' && expanded) {
1570
+ event.preventDefault();
1571
+ closeList();
1572
+ return;
1573
+ }
1574
+ if (expanded && (event.key === 'ArrowDown' || event.key === 'ArrowUp' ||
1575
+ event.key === 'End' || event.key === 'Home')) {
1576
+ event.preventDefault();
1577
+ setActiveIndex(current => {
1578
+ if (event.key === 'Home')
1579
+ return 0;
1580
+ if (event.key === 'End')
1581
+ return visibleOptions.length - 1;
1582
+ if (event.key === 'ArrowDown')
1583
+ return (current + 1) % visibleOptions.length;
1584
+ return (current - 1 + visibleOptions.length) % visibleOptions.length;
1585
+ });
1586
+ return;
1587
+ }
1588
+ if (event.key === 'Enter' && activeOption) {
1589
+ event.preventDefault();
1590
+ insertMention(activeOption.value);
1591
+ }
1592
+ };
1593
+ return (_jsxs("div", { className: composeClassName('ui-mentions', className), "data-ui-mentions": true, "data-ui-mentions-trigger": trigger, ...props, children: [_jsx("textarea", { "aria-activedescendant": activeOption ? `${listId}-option-${resolvedActiveIndex}` : undefined, "aria-autocomplete": "list", "aria-controls": listId, "aria-expanded": expanded, "aria-label": label, className: "ui-textarea ui-mentions__input", "data-ui-mentions-input": true, name: name, onBlur: closeList, onChange: event => {
1594
+ const nextValue = event.currentTarget.value;
1595
+ const match = new RegExp(`${escapedTrigger}(\\w*)$`)
1596
+ .exec(nextValue.slice(0, event.currentTarget.selectionStart));
1597
+ const nextQuery = match ? (match[1] ?? '').toLowerCase() : null;
1598
+ setNextValue(nextValue);
1599
+ setQuery(nextQuery);
1600
+ setActiveIndex(nextQuery === null ? -1 : 0);
1601
+ }, onKeyDown: handleKeyDown, placeholder: placeholder, ref: inputRef, role: "combobox", rows: 3, value: currentValue }), _jsx("ul", { className: "ui-mentions__list", "data-ui-mentions-list": true, hidden: !expanded, id: listId, role: "listbox", children: visibleOptions.map((option, index) => (_jsx("li", { children: _jsx("button", { "aria-selected": index === resolvedActiveIndex, className: "ui-mentions__option", "data-ui-mentions-option": true, "data-value": option.value, id: `${listId}-option-${index}`, onClick: () => {
1602
+ insertMention(option.value);
1603
+ }, onMouseDown: event => {
1604
+ event.preventDefault();
1605
+ }, role: "option", type: "button", children: option.label }) }, option.value))) })] }));
1606
+ };
976
1607
  export const QRCode = ({ children, className, size = 160, src, style, value, ...props }) => {
977
1608
  const qrSvg = !src && value ? renderSVG(value) : null;
978
- const qrSrc = qrSvg ? `data:image/svg+xml;utf8,${encodeURIComponent(qrSvg)}` : src;
979
- return (_jsxs("figure", { className: composeClassName('ui-qr-code', className), "data-ui-qr-code": true, style: { ['--ui-qr-code-size']: `${size}px`, ...style }, ...props, children: [_jsx("div", { className: "ui-qr-code__frame", children: qrSrc
980
- ? _jsx("img", { alt: value ? `QR code for ${value}` : 'QR code', className: "ui-qr-code__image", height: size, src: qrSrc, width: size })
981
- : children }), value && _jsx("figcaption", { className: "ui-qr-code__value", children: value })] }));
1609
+ const qrSrc = qrSvg ?
1610
+ `data:image/svg+xml;utf8,${encodeURIComponent(qrSvg)}` :
1611
+ src;
1612
+ return (_jsxs("figure", { className: composeClassName('ui-qr-code', className), "data-ui-qr-code": true, style: { ['--ui-qr-code-size']: `${size}px`, ...style }, ...props, children: [_jsx("div", { className: "ui-qr-code__frame", children: qrSrc ?
1613
+ (_jsx("img", { alt: value ? `QR code for ${value}` : 'QR code', className: "ui-qr-code__image", height: size, src: qrSrc, width: size })) :
1614
+ (children) }), value && _jsx("figcaption", { className: "ui-qr-code__value", children: value })] }));
982
1615
  };
983
1616
  export const Watermark = ({ children, className, content, gap = 120, rotate = -22, style, text = 'Confidential', ...props }) => {
984
1617
  const watermarkText = content ?? text;
@@ -988,11 +1621,12 @@ export const Watermark = ({ children, className, content, gap = 120, rotate = -2
988
1621
  '<': '&lt;',
989
1622
  '>': '&gt;',
990
1623
  '"': '&quot;',
991
- "'": '&apos;'
1624
+ '\'': '&apos;'
992
1625
  };
993
1626
  const escapedWatermarkText = watermarkText.replaceAll(/[&<>"']/g, character => watermarkXmlEntities[character] ?? character);
994
1627
  const watermarkSvg = [
995
- `<svg xmlns="http://www.w3.org/2000/svg" width="${tileSize}" height="${tileSize}" viewBox="0 0 ${tileSize} ${tileSize}">`,
1628
+ '<svg xmlns="http://www.w3.org/2000/svg"',
1629
+ ` width="${tileSize}" height="${tileSize}" viewBox="0 0 ${tileSize} ${tileSize}">`,
996
1630
  `<text x="${tileSize / 2}" y="${tileSize / 2}" dominant-baseline="middle" text-anchor="middle"`,
997
1631
  ` transform="rotate(${rotate} ${tileSize / 2} ${tileSize / 2})"`,
998
1632
  ' fill="black" font-family="Montserrat, Avenir Next, Segoe UI, sans-serif" font-size="16" font-weight="500" letter-spacing="1.6">',
@@ -1008,10 +1642,20 @@ export const Watermark = ({ children, className, content, gap = 120, rotate = -2
1008
1642
  export const Affix = ({ className, offset = 0, offsetBottom, offsetTop, position: positionProp, style, ...props }) => {
1009
1643
  const position = positionProp ?? (offsetBottom === undefined ? 'top' : 'bottom');
1010
1644
  const resolvedOffset = offsetBottom ?? offsetTop ?? offset;
1011
- return (_jsx("div", { className: composeClassName('ui-affix', position === 'bottom' && 'ui-affix--bottom', className), "data-position": position, style: { ['--ui-affix-offset']: `${resolvedOffset}px`, ...style }, ...props }));
1645
+ return (_jsx("div", { className: composeClassName('ui-affix', position === 'bottom' && 'ui-affix--bottom', className), "data-position": position, style: {
1646
+ ['--ui-affix-offset']: `${resolvedOffset}px`,
1647
+ ...style
1648
+ }, ...props }));
1012
1649
  };
1013
1650
  const emptySpeedDialActions = [];
1014
- const speedDialNavigationKeys = ['ArrowDown', 'ArrowLeft', 'ArrowRight', 'ArrowUp', 'End', 'Home'];
1651
+ const speedDialNavigationKeys = [
1652
+ 'ArrowDown',
1653
+ 'ArrowLeft',
1654
+ 'ArrowRight',
1655
+ 'ArrowUp',
1656
+ 'End',
1657
+ 'Home'
1658
+ ];
1015
1659
  const getSpeedDialNavigationIndex = (key, currentIndex, itemCount) => {
1016
1660
  if (key === 'Home')
1017
1661
  return 0;
@@ -1060,7 +1704,9 @@ export const SpeedDial = ({ actions = emptySpeedDialActions, children, className
1060
1704
  if (event.target.closest('[role="menuitem"], button, a'))
1061
1705
  close();
1062
1706
  }, onKeyDown: event => {
1063
- const items = [...(menuRef.current?.querySelectorAll('[role="menuitem"], button:not([disabled]), a[href]') ?? [])];
1707
+ const items = [
1708
+ ...(menuRef.current?.querySelectorAll('[role="menuitem"], button:not([disabled]), a[href]') ?? [])
1709
+ ];
1064
1710
  if (event.key === 'Escape') {
1065
1711
  event.preventDefault();
1066
1712
  close(true);