@flood-md/react 0.2.3 → 0.2.5

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.
Files changed (49) hide show
  1. package/README.md +398 -3
  2. package/dist/alert-dialog.d.ts +2 -0
  3. package/dist/alert-dialog.js +23 -0
  4. package/dist/avatar.d.ts +5 -0
  5. package/dist/avatar.js +8 -0
  6. package/dist/checkbox.d.ts +4 -0
  7. package/dist/checkbox.js +15 -0
  8. package/dist/combobox.d.ts +11 -0
  9. package/dist/combobox.js +301 -0
  10. package/dist/content-elements.d.ts +8 -0
  11. package/dist/content-elements.js +47 -0
  12. package/dist/data-display.d.ts +3 -0
  13. package/dist/data-display.js +11 -0
  14. package/dist/disclosure.js +2 -1
  15. package/dist/feedback-actions.d.ts +3 -0
  16. package/dist/feedback-actions.js +106 -0
  17. package/dist/feedback.d.ts +8 -0
  18. package/dist/feedback.js +29 -0
  19. package/dist/file-input.d.ts +8 -0
  20. package/dist/file-input.js +86 -0
  21. package/dist/focusable-hidden.d.ts +3 -0
  22. package/dist/focusable-hidden.js +99 -0
  23. package/dist/fonts/InterVariable-Italic.woff2 +0 -0
  24. package/dist/fonts/README.md +9 -5
  25. package/dist/form-controls.d.ts +6 -0
  26. package/dist/form-controls.js +250 -0
  27. package/dist/foundations.css +543 -12
  28. package/dist/index.d.ts +513 -2
  29. package/dist/index.js +223 -36
  30. package/dist/menu.d.ts +2 -0
  31. package/dist/menu.js +130 -0
  32. package/dist/navigation-layout.d.ts +7 -0
  33. package/dist/navigation-layout.js +77 -0
  34. package/dist/navigation.js +8 -8
  35. package/dist/numeric-input.d.ts +8 -0
  36. package/dist/numeric-input.js +68 -0
  37. package/dist/overlay-runtime.js +43 -16
  38. package/dist/overlays.d.ts +7 -1
  39. package/dist/overlays.js +17 -6
  40. package/dist/scroll-area.js +17 -2
  41. package/dist/select.js +96 -47
  42. package/dist/slider.d.ts +7 -0
  43. package/dist/slider.js +89 -0
  44. package/dist/styles.css +9 -0
  45. package/dist/text-inputs.d.ts +9 -0
  46. package/dist/text-inputs.js +45 -0
  47. package/dist/toolbar.d.ts +2 -0
  48. package/dist/toolbar.js +80 -0
  49. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -1,23 +1,104 @@
1
1
  "use client";
2
2
  import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { forwardRef, createElement, useId, useState, } from "react";
4
+ import { useFocusableHidden } from "./focusable-hidden.js";
4
5
  import { resolveFieldStatus, hasFieldContent, mergeDescriptions } from "./field-state.js";
5
6
  import { SingleSelect } from "./select.js";
7
+ import { MixedCheckbox } from "./checkbox.js";
6
8
  import { OverlayScrollArea } from "./scroll-area.js";
7
9
  import { ChoiceGroup } from "./choice-group.js";
8
10
  import { Disclosure } from "./disclosure.js";
9
11
  import { AnchoredOverlay, ModalOverlay } from "./overlays.js";
10
12
  import { TabNavigation, BreadcrumbNavigation, PageNavigation } from "./navigation.js";
13
+ import { AvatarArtwork } from "./avatar.js";
14
+ import { ValueIndicator, FeedbackMessage } from "./feedback.js";
15
+ import { DescribedData, TabularData } from "./data-display.js";
16
+ import { ActionMenu } from "./menu.js";
17
+ import { RovingToolbar } from "./toolbar.js";
18
+ import { NumericStepper } from "./numeric-input.js";
19
+ import { RangeControl } from "./slider.js";
20
+ import { GroupedAvatars, NamedStatus, CaptionedFigure, AttributedQuote, InlineCodeText, MeasuredValue, EventTimeline } from "./content-elements.js";
21
+ import { NavigationMenu, StepSequence, ContentSection, SplitLayout, CenterLayout, SkipNavigation } from "./navigation-layout.js";
22
+ import { CollectionListBox, SegmentedPinInput, StarRating, FileDropzone, SupportingCharacterCount } from "./form-controls.js";
23
+ import { ClipboardAction, NotificationRegion } from "./feedback-actions.js";
24
+ import { ConfirmationDialog } from "./alert-dialog.js";
25
+ import { FilePicker } from "./file-input.js";
26
+ import { SearchField, PasswordField } from "./text-inputs.js";
27
+ import { SearchSelection } from "./combobox.js";
11
28
  const classes = (...values) => values.filter(Boolean).join(" ");
12
29
  export const Button = forwardRef(function Button({ variant = "solid", size = "md", loading = false, disabled, type = "button", className, children, ...props }, ref) {
13
- return (_jsxs("button", { ...props, ref: ref, type: type, disabled: disabled || loading, "aria-busy": loading || undefined, className: classes("flood-button", `flood-button--${variant}`, `flood-button--${size}`, className), children: [loading && _jsx("span", { className: "flood-spinner", "aria-hidden": "true" }), children] }));
30
+ return (_jsxs("button", { ...props, ref: ref, type: type, disabled: disabled || loading, "aria-busy": loading || undefined, className: classes("flood-button", `flood-button--${variant}`, `flood-button--${size}`, className), children: [loading && _jsx(Spinner, { decorative: true }), children] }));
31
+ });
32
+ /** @since 0.2.5 */
33
+ export const Tag = forwardRef(function Tag({ size = "md", icon, disabled = false, onRemove, removeLabel, removeButtonProps, className, children, ...props }, ref) {
34
+ const label = removeLabel ?? (typeof children === "string" || typeof children === "number" ? `Remove ${children}` : "");
35
+ if (onRemove && !label.trim())
36
+ throw new Error("Flood Tag removal needs an accessible removeLabel for rich content");
37
+ const iconSize = size === "sm" ? "2xs" : size === "md" ? "xs" : "sm";
38
+ return _jsxs("span", { ...props, ref: ref, className: classes("flood-tag", className), "data-size": size, "data-disabled": disabled || undefined, children: [hasFieldContent(icon) && _jsx(IconSlot, { size: iconSize, children: icon }), _jsx("span", { className: "flood-tag__label", children: children }), onRemove && _jsx(IconButton, { ...removeButtonProps, type: "button", className: classes("flood-tag__remove", removeButtonProps?.className), label: label, variant: "ghost", size: iconSize, disabled: disabled, onClick: () => { if (!disabled)
39
+ onRemove(); }, children: _jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.75", strokeLinecap: "round", "aria-hidden": "true", children: _jsx("path", { d: "m6 6 12 12M18 6 6 18" }) }) })] });
40
+ });
41
+ /** @since 0.2.4 */
42
+ export const Kbd = forwardRef(function Kbd({ size = "sm", className, ...props }, ref) {
43
+ return _jsx("kbd", { ...props, ref: ref, className: classes("flood-kbd", className), "data-size": size });
44
+ });
45
+ /** @since 0.2.4 */
46
+ export const Toolbar = forwardRef(function Toolbar({ label, orientation = "horizontal", loop = true, ...props }, ref) {
47
+ return _jsx(RovingToolbar, { ...props, ref: ref, label: label, orientation: orientation, loop: loop });
48
+ });
49
+ /** @since 0.2.4 */
50
+ export const DescriptionList = forwardRef(function DescriptionList({ items, layout = "inline", density = "compact", dividers = false, ...props }, ref) {
51
+ return _jsx(DescribedData, { ...props, ref: ref, items: items, layout: layout, density: density, dividers: dividers });
52
+ });
53
+ /** @since 0.2.4 */
54
+ export const Table = forwardRef(function Table({ caption, children, hideCaption = false, density = "comfortable", striped = false, scrollLabel, ...props }, ref) {
55
+ return _jsx(TabularData, { ...props, ref: ref, caption: caption, hideCaption: hideCaption, density: density, striped: striped, scrollLabel: scrollLabel, children: children });
56
+ });
57
+ /** @since 0.2.4 */
58
+ export const Badge = forwardRef(function Badge({ size = "md", tone = "neutral", variant = "subtle", className, ...props }, ref) {
59
+ return _jsx("span", { ...props, ref: ref, className: classes("flood-badge", className), "data-size": size, "data-tone": tone, "data-variant": variant });
60
+ });
61
+ /** @since 0.2.4 */
62
+ export const Spinner = forwardRef(function Spinner({ size = "md", label = "Loading", decorative = false, className, ...props }, ref) {
63
+ return _jsxs("span", { ...props, ref: ref, className: classes("flood-loading", className), "data-size": size, role: decorative ? undefined : "status", "aria-hidden": decorative || undefined, children: [_jsx("span", { className: "flood-spinner", "aria-hidden": "true" }), !decorative && _jsx(VisuallyHidden, { children: label })] });
64
+ });
65
+ /** @since 0.2.4 */
66
+ export const Skeleton = forwardRef(function Skeleton({ shape = "text", width, height, animate = true, className, style, ...props }, ref) {
67
+ return _jsx("span", { ...props, ref: ref, className: classes("flood-skeleton", className), "data-shape": shape, "data-animate": animate, "aria-hidden": "true", style: { width, height, ...style } });
68
+ });
69
+ /** @since 0.2.4 */
70
+ export const Avatar = forwardRef(function Avatar({ name, src, fallback, size = "md", shape = "circle", decorative = false, className, ...props }, ref) {
71
+ const initials = name.trim().split(/\s+/u).filter(Boolean).slice(0, 2).map(word => Array.from(word)[0]).join("").toUpperCase();
72
+ return _jsx("span", { ...props, ref: ref, className: classes("flood-avatar", className), "data-size": size, "data-shape": shape, role: decorative ? undefined : "img", "aria-label": decorative ? undefined : name, "aria-hidden": decorative || undefined, children: _jsx(AvatarArtwork, { src: src, fallback: fallback ?? (initials || "?") }, src ?? "") });
73
+ });
74
+ /** @since 0.2.4 */
75
+ export const Progress = forwardRef(function Progress({ label, value, max = 100, valueText, size = "md", tone = "accent", hideLabel = false, ...props }, ref) {
76
+ return _jsx(ValueIndicator, { ...props, ref: ref, kind: "progress", label: label, value: value, min: 0, max: max, valueText: valueText, size: size, tone: tone, hideLabel: hideLabel });
77
+ });
78
+ /** @since 0.2.4 */
79
+ export const Meter = forwardRef(function Meter({ label, value, min = 0, max = 100, valueText, size = "md", tone = "neutral", hideLabel = false, ...props }, ref) {
80
+ return _jsx(ValueIndicator, { ...props, ref: ref, kind: "meter", label: label, value: value, min: min, max: max, valueText: valueText, size: size, tone: tone, hideLabel: hideLabel });
81
+ });
82
+ /** @since 0.2.4 */
83
+ export const Alert = forwardRef(function Alert({ title, tone = "info", icon, announcement = "off", ...props }, ref) {
84
+ return _jsx(FeedbackMessage, { ...props, ref: ref, title: title, tone: tone, icon: icon, announcement: announcement });
14
85
  });
15
86
  export const Tabs = forwardRef(function Tabs({ activationMode = "automatic", orientation = "horizontal", controlSize = "md", disabled = false, iconOnly = false, ...props }, ref) { return _jsx(TabNavigation, { ...props, ref: ref, activationMode: activationMode, orientation: orientation, controlSize: controlSize, disabled: disabled, iconOnly: iconOnly }); });
16
87
  export const Breadcrumb = forwardRef(function Breadcrumb({ label = "Breadcrumb", separator = "/", ...props }, ref) { return _jsx(BreadcrumbNavigation, { ...props, ref: ref, label: label, separator: separator }); });
17
88
  export const Pagination = forwardRef(function Pagination({ siblingCount = 1, controlSize = "sm", disabled = false, label = "Pagination", previousLabel = "Previous page", nextLabel = "Next page", ...props }, ref) { return _jsx(PageNavigation, { ...props, ref: ref, siblingCount: siblingCount, controlSize: controlSize, disabled: disabled, label: label, previousLabel: previousLabel, nextLabel: nextLabel }); });
89
+ /** @since 0.2.4 */
90
+ export const Menu = forwardRef(function Menu({ defaultOpen = false, disabled = false, side = "bottom", align = "start", controlSize = "sm", ...props }, ref) {
91
+ return _jsx(ActionMenu, { ...props, ref: ref, defaultOpen: defaultOpen, disabled: disabled, side: side, align: align, controlSize: controlSize });
92
+ });
18
93
  export const Tooltip = forwardRef(function Tooltip({ side = "top", align = "center", delay = 300, disabled = false, ...props }, ref) { return _jsx(AnchoredOverlay, { ...props, ref: ref, kind: "tooltip", side: side, align: align, delay: delay, disabled: disabled }); });
19
94
  export const Popover = forwardRef(function Popover({ side = "bottom", align = "start", defaultOpen = false, disabled = false, closeLabel = "Close", ...props }, ref) { return _jsx(AnchoredOverlay, { ...props, ref: ref, kind: "popover", side: side, align: align, defaultOpen: defaultOpen, disabled: disabled, closeLabel: closeLabel }); });
20
95
  export const Dialog = forwardRef(function Dialog({ defaultOpen = false, disabled = false, closeLabel = "Close", closeOnOutsideClick = true, ...props }, ref) { return _jsx(ModalOverlay, { ...props, ref: ref, defaultOpen: defaultOpen, disabled: disabled, closeLabel: closeLabel, closeOnOutsideClick: closeOnOutsideClick }); });
96
+ export const Drawer = forwardRef(function Drawer({ side = "end", size = "md", defaultOpen = false, closeLabel = "Close", closeOnOutsideClick = true, className, footer, ...props }, ref) {
97
+ return _jsx(ModalOverlay, { ...props, ref: ref, defaultOpen: defaultOpen, closeLabel: closeLabel, closeOnOutsideClick: closeOnOutsideClick, className: classes("flood-drawer", className), "data-side": side, "data-size": size, scrollBody: true, footer: footer });
98
+ });
99
+ export const AlertDialog = forwardRef(function AlertDialog({ defaultOpen = false, confirmLabel = "Continue", cancelLabel = "Cancel", confirmDisabled = false, ...props }, ref) {
100
+ return _jsx(ConfirmationDialog, { ...props, ref: ref, defaultOpen: defaultOpen, confirmLabel: confirmLabel, cancelLabel: cancelLabel, confirmDisabled: confirmDisabled });
101
+ });
21
102
  export const Collapsible = forwardRef(function Collapsible({ open, defaultOpen = false, onOpenChange, disabled = false, controlSize = "md", ...props }, ref) {
22
103
  const [local, setLocal] = useState(defaultOpen);
23
104
  return _jsx(Disclosure, { ...props, ref: ref, expanded: open ?? local, disabled: disabled, controlSize: controlSize, onExpandedChange: next => {
@@ -35,7 +116,7 @@ export const Accordion = forwardRef(function Accordion({ label, items, expansion
35
116
  throw new Error("Flood single Accordion accepts at most one value");
36
117
  return _jsx("div", { ...props, ref: ref, role: "group", "aria-label": label, className: classes("flood-accordion", className), onKeyDown: event => {
37
118
  onKeyDown?.(event);
38
- if (event.defaultPrevented)
119
+ if (event.defaultPrevented || event.altKey || event.ctrlKey || event.metaKey || event.nativeEvent.isComposing)
39
120
  return;
40
121
  const buttons = Array.from(event.currentTarget.querySelectorAll(":scope > .flood-disclosure > .flood-disclosure__heading > .flood-disclosure__trigger:not(:disabled)"));
41
122
  const index = buttons.indexOf(event.target);
@@ -76,39 +157,64 @@ export const ToggleGroup = forwardRef(function ToggleGroup({ label, options, sel
76
157
  throw new Error("Flood single ToggleGroup accepts at most one value");
77
158
  if (iconOnly && options.some(option => !option.icon))
78
159
  throw new Error("Flood icon-only toggle options require icons");
79
- return _jsx("div", { ...props, ref: ref, role: "group", "aria-label": label, "data-orientation": orientation, "data-selection-mode": selectionMode, className: classes("flood-button-group", "flood-toggle-group", className), onKeyDown: event => {
80
- onKeyDown?.(event);
81
- if (event.defaultPrevented)
82
- return;
83
- const buttons = Array.from(event.currentTarget.querySelectorAll(":scope > .flood-toggle:not(:disabled)"));
84
- const index = buttons.indexOf(event.target);
85
- if (index < 0)
86
- return;
87
- const rtl = getComputedStyle(event.currentTarget).direction === "rtl";
88
- const nextKey = orientation === "vertical" ? "ArrowDown" : rtl ? "ArrowLeft" : "ArrowRight";
89
- const previousKey = orientation === "vertical" ? "ArrowUp" : rtl ? "ArrowRight" : "ArrowLeft";
90
- const next = event.key === "Home" ? 0 : event.key === "End" ? buttons.length - 1 : event.key === nextKey ? (index + 1) % buttons.length : event.key === previousKey ? (index - 1 + buttons.length) % buttons.length : -1;
91
- if (next >= 0) {
92
- event.preventDefault();
93
- buttons[next].focus();
94
- }
95
- }, children: options.map(option => _jsx(Toggle, { size: size, variant: variant, disabled: disabled || option.disabled, pressed: selected.includes(option.value), iconOnly: iconOnly, "aria-label": iconOnly ? option.label : undefined, onPressedChange: active => {
96
- const next = active ? selectionMode === "single" ? [option.value] : [...new Set([...selected, option.value])] : selected.filter(item => item !== option.value);
97
- if (value === undefined)
98
- setLocal(next);
99
- onValueChange?.([...next]);
100
- }, children: iconOnly ? option.icon : _jsxs(_Fragment, { children: [option.icon && _jsx(IconSlot, { size: size, children: option.icon }), option.label] }) }, option.value)) });
160
+ return _jsx(ScrollArea, { orientation: "horizontal", tabIndex: -1, role: "group", "aria-label": `${label} scrolling`, className: "flood-connected-scroll", hidden: props.hidden, dir: props.dir, lang: props.lang, style: { maxInlineSize: "100%" }, children: _jsx("div", { ...props, ref: ref, role: "group", "aria-label": label, "data-orientation": orientation, "data-selection-mode": selectionMode, className: classes("flood-button-group", "flood-toggle-group", className), onKeyDown: event => {
161
+ onKeyDown?.(event);
162
+ if (event.defaultPrevented || event.altKey || event.ctrlKey || event.metaKey || event.nativeEvent.isComposing)
163
+ return;
164
+ const buttons = Array.from(event.currentTarget.querySelectorAll(":scope > .flood-toggle:not(:disabled)"));
165
+ const index = buttons.indexOf(event.target);
166
+ if (index < 0)
167
+ return;
168
+ const rtl = getComputedStyle(event.currentTarget).direction === "rtl";
169
+ const nextKey = orientation === "vertical" ? "ArrowDown" : rtl ? "ArrowLeft" : "ArrowRight";
170
+ const previousKey = orientation === "vertical" ? "ArrowUp" : rtl ? "ArrowRight" : "ArrowLeft";
171
+ const next = event.key === "Home" ? 0 : event.key === "End" ? buttons.length - 1 : event.key === nextKey ? (index + 1) % buttons.length : event.key === previousKey ? (index - 1 + buttons.length) % buttons.length : -1;
172
+ if (next >= 0) {
173
+ event.preventDefault();
174
+ buttons[next].focus();
175
+ }
176
+ }, children: options.map(option => _jsx(Toggle, { size: size, variant: variant, disabled: disabled || option.disabled, pressed: selected.includes(option.value), iconOnly: iconOnly, "aria-label": iconOnly ? option.label : undefined, onPressedChange: active => {
177
+ const next = active ? selectionMode === "single" ? [option.value] : [...new Set([...selected, option.value])] : selected.filter(item => item !== option.value);
178
+ if (value === undefined)
179
+ setLocal(next);
180
+ onValueChange?.([...next]);
181
+ }, children: iconOnly ? option.icon : _jsxs(_Fragment, { children: [option.icon && _jsx(IconSlot, { size: size, children: option.icon }), option.label] }) }, option.value)) }) });
101
182
  });
102
183
  /** Connected actions keep their ordinary Tab order; this is not a toolbar. */
103
184
  export const ButtonGroup = forwardRef(function ButtonGroup({ label, orientation = "horizontal", className, ...props }, ref) {
104
- return _jsx("div", { ...props, ref: ref, role: "group", "aria-label": label, "data-orientation": orientation, className: classes("flood-button-group", className) });
185
+ return _jsx(ScrollArea, { orientation: "horizontal", tabIndex: -1, role: "group", "aria-label": `${label} scrolling`, className: "flood-connected-scroll", hidden: props.hidden, dir: props.dir, lang: props.lang, style: { maxInlineSize: "100%" }, children: _jsx("div", { ...props, ref: ref, role: "group", "aria-label": label, "data-orientation": orientation, className: classes("flood-button-group", className) }) });
105
186
  });
106
187
  export const Input = forwardRef(function Input({ invalid, status, controlSize = "md", className, ...props }, ref) {
107
188
  return (_jsx("input", { ...props, ref: ref, "data-status": resolveFieldStatus(status, invalid, props["aria-invalid"]), "aria-invalid": invalid || status === "error" || props["aria-invalid"], className: classes("flood-input", `flood-control--${controlSize}`, className) }));
108
189
  });
109
190
  /** One real Input with host-owned logical start/end adornments. Ref and native attributes target the input. */
110
191
  export const InputGroup = forwardRef(function InputGroup({ start, end, controlSize = "md", className, style, invalid, status, disabled, readOnly, ...props }, ref) {
111
- return _jsxs("div", { hidden: props.hidden, dir: props.dir, lang: props.lang, className: classes("flood-input-group", `flood-control--${controlSize}`, className), style: style, "data-status": resolveFieldStatus(status, invalid, props["aria-invalid"]), "data-disabled": disabled || undefined, "data-readonly": readOnly || undefined, children: [hasFieldContent(start) && _jsx("div", { className: "flood-input-group__addon flood-input-group__addon--start", children: start }), _jsx(Input, { ...props, ref: ref, controlSize: controlSize, invalid: invalid, status: status, disabled: disabled, readOnly: readOnly }), hasFieldContent(end) && _jsx("div", { className: "flood-input-group__addon flood-input-group__addon--end", children: end })] });
192
+ return _jsxs("div", { hidden: props.hidden, dir: props.dir, lang: props.lang, className: classes("flood-input-group", `flood-control--${controlSize}`, className), style: style, "data-status": resolveFieldStatus(status, invalid, props["aria-invalid"]), "data-disabled": disabled || undefined, "data-readonly": readOnly || undefined, children: [hasFieldContent(start) && _jsx("div", { className: "flood-input-group__addon flood-input-group__addon--start", children: typeof start === "string" ? _jsx("bdi", { dir: "auto", children: start }) : start }), _jsx(Input, { ...props, ref: ref, controlSize: controlSize, invalid: invalid, status: status, disabled: disabled, readOnly: readOnly }), hasFieldContent(end) && _jsx("div", { className: "flood-input-group__addon flood-input-group__addon--end", children: typeof end === "string" ? _jsx("bdi", { dir: "auto", children: end }) : end })] });
193
+ });
194
+ export const FileInput = forwardRef(function FileInput({ clearable = true, clearLabel = "Clear files", controlSize = "md", ...props }, ref) {
195
+ return _jsx(FilePicker, { ...props, ref: ref, clearable: clearable, clearLabel: clearLabel, controlSize: controlSize, actionComponent: IconButton, iconComponent: IconSlot });
196
+ });
197
+ export const SearchInput = forwardRef(function SearchInput({ clearable = true, clearLabel = "Clear search", controlSize = "md", ...props }, ref) {
198
+ return _jsx(SearchField, { ...props, ref: ref, clearable: clearable, clearLabel: clearLabel, controlSize: controlSize, group: InputGroup, action: IconButton, icon: IconSlot });
199
+ });
200
+ export const PasswordInput = forwardRef(function PasswordInput({ defaultVisible = false, showPasswordLabel = "Show password", hidePasswordLabel = "Hide password", controlSize = "md", ...props }, ref) {
201
+ return _jsx(PasswordField, { ...props, ref: ref, defaultVisible: defaultVisible, showPasswordLabel: showPasswordLabel, hidePasswordLabel: hidePasswordLabel, controlSize: controlSize, group: InputGroup, action: IconButton, icon: IconSlot });
202
+ });
203
+ /** @since 0.2.5 */
204
+ export const Combobox = forwardRef(function Combobox({ value, defaultValue = "", onValueChange, clearable = false, emptyMessage = "No results found.", listLabel = "Options", toggleLabel = "Show options", clearLabel = "Clear selection", invalidSelectionMessage = "Select an option.", controlSize = "md", ...props }, ref) {
205
+ return _jsx(SearchSelection, { ...props, ref: ref, multiple: false, selectedValues: value === undefined ? undefined : value ? [value] : [], initialValues: defaultValue ? [defaultValue] : [], onSelectionChange: next => onValueChange?.(next[0] ?? ""), clearable: clearable, emptyMessage: emptyMessage, listLabel: listLabel, toggleLabel: toggleLabel, clearLabel: clearLabel, invalidSelectionMessage: invalidSelectionMessage, controlSize: controlSize });
206
+ });
207
+ /** @since 0.2.5 */
208
+ export const MultiSelect = forwardRef(function MultiSelect({ value, defaultValue = [], onValueChange, removeItemLabel = "Remove", clearable = false, emptyMessage = "No results found.", listLabel = "Options", toggleLabel = "Show options", clearLabel = "Clear selection", invalidSelectionMessage = "Select at least one option.", controlSize = "md", ...props }, ref) {
209
+ return _jsx(SearchSelection, { ...props, ref: ref, multiple: true, selectedValues: value, initialValues: defaultValue, onSelectionChange: onValueChange, removeItemLabel: removeItemLabel, clearable: clearable, emptyMessage: emptyMessage, listLabel: listLabel, toggleLabel: toggleLabel, clearLabel: clearLabel, invalidSelectionMessage: invalidSelectionMessage, controlSize: controlSize });
210
+ });
211
+ /** @since 0.2.5 */
212
+ export const NumberInput = forwardRef(function NumberInput({ defaultValue = 0, step = 1, controlSize = "md", decreaseLabel = "Decrease value", increaseLabel = "Increase value", ...props }, ref) {
213
+ return _jsx(NumericStepper, { ...props, ref: ref, defaultValue: defaultValue, step: step, controlSize: controlSize, decreaseLabel: decreaseLabel, increaseLabel: increaseLabel, inputComponent: InputGroup, buttonComponent: IconButton });
214
+ });
215
+ /** @since 0.2.5 */
216
+ export const Slider = forwardRef(function Slider({ defaultValue = [50], min = 0, max = 100, step = 1, showValue = true, readOnly = false, controlSize = "md", ...props }, ref) {
217
+ return _jsx(RangeControl, { ...props, ref: ref, defaultValue: defaultValue, min: min, max: max, step: step, showValue: showValue, readOnly: readOnly, controlSize: controlSize, fieldsetComponent: Fieldset });
112
218
  });
113
219
  export const Textarea = forwardRef(function Textarea({ invalid, status, controlSize = "md", className, ...props }, ref) {
114
220
  return (_jsx("textarea", { ...props, ref: ref, "data-status": resolveFieldStatus(status, invalid, props["aria-invalid"]), "aria-invalid": invalid || status === "error" || props["aria-invalid"], className: classes("flood-input", "flood-textarea", `flood-control--${controlSize}`, className) }));
@@ -118,16 +224,16 @@ export const Select = forwardRef(function Select({ invalid, status, controlSize
118
224
  const listbox = multiple || (size !== undefined && size > 1);
119
225
  if (!listbox)
120
226
  return _jsx(SingleSelect, { ...props, status: status, invalid: invalid, controlSize: controlSize, className: className, size: size, ref: ref });
121
- return (_jsx("span", { className: classes("flood-select", `flood-control--${controlSize}`, listbox && "flood-select--listbox"), children: _jsx("select", { ...props, ref: ref, multiple: multiple, size: size, "data-status": resolveFieldStatus(status, invalid, props["aria-invalid"]), "aria-invalid": invalid || status === "error" || props["aria-invalid"], className: classes("flood-input", "flood-select__control", `flood-control--${controlSize}`, className) }) }));
227
+ return (_jsx("span", { hidden: props.hidden, dir: props.dir, lang: props.lang, className: classes("flood-select", `flood-control--${controlSize}`, listbox && "flood-select--listbox"), children: _jsx("select", { ...props, ref: ref, multiple: multiple, size: size, "data-status": resolveFieldStatus(status, invalid, props["aria-invalid"]), "aria-invalid": invalid || status === "error" || props["aria-invalid"], className: classes("flood-input", "flood-select__control", `flood-control--${controlSize}`, className) }) }));
122
228
  });
123
- export const Checkbox = forwardRef(function Checkbox({ label, className, ...props }, ref) {
124
- return (_jsxs("label", { className: classes("flood-choice", className), children: [_jsx("input", { ...props, ref: ref, type: "checkbox", className: "flood-check" }), _jsx("span", { children: label })] }));
229
+ export const Checkbox = forwardRef(function Checkbox({ label, indeterminate = false, className, ...props }, ref) {
230
+ return (_jsxs("label", { hidden: props.hidden, dir: props.dir, lang: props.lang, className: classes("flood-choice", className), children: [_jsx(MixedCheckbox, { ...props, ref: ref, indeterminate: indeterminate }), _jsx("span", { children: label })] }));
125
231
  });
126
232
  export const Radio = forwardRef(function Radio({ label, className, ...props }, ref) {
127
- return (_jsxs("label", { className: classes("flood-choice", className), children: [_jsx("input", { ...props, ref: ref, type: "radio", className: "flood-check" }), _jsx("span", { children: label })] }));
233
+ return (_jsxs("label", { hidden: props.hidden, dir: props.dir, lang: props.lang, className: classes("flood-choice", className), children: [_jsx("input", { ...props, ref: ref, type: "radio", className: "flood-check" }), _jsx("span", { children: label })] }));
128
234
  });
129
235
  export const Switch = forwardRef(function Switch({ label, className, ...props }, ref) {
130
- return (_jsxs("label", { className: classes("flood-choice", "flood-switch", className), children: [_jsx("span", { children: label }), _jsx("input", { ...props, ref: ref, type: "checkbox", role: "switch", className: "flood-switch-input" })] }));
236
+ return (_jsxs("label", { hidden: props.hidden, dir: props.dir, lang: props.lang, className: classes("flood-choice", "flood-switch", className), children: [_jsx("span", { children: label }), _jsx("input", { ...props, ref: ref, type: "checkbox", role: "switch", className: "flood-switch-input" })] }));
131
237
  });
132
238
  /** A native label; the required marker never substitutes for the control's required prop. */
133
239
  export const Label = forwardRef(function Label({ required, children, className, ...props }, ref) {
@@ -137,10 +243,11 @@ export const Label = forwardRef(function Label({ required, children, className,
137
243
  export const Fieldset = forwardRef(function Fieldset({ legend, hideLabel = false, hint, error, message, status, children, className, id: explicitId, "aria-describedby": describedBy, "aria-invalid": ariaInvalid, ...props }, ref) {
138
244
  const generatedId = useId();
139
245
  const id = explicitId ?? `flood-${generatedId}`;
246
+ const legendRef = useFocusableHidden(hideLabel);
140
247
  const hasHint = hasFieldContent(hint), hasError = hasFieldContent(error), hasMessage = hasFieldContent(message) && !hasError;
141
248
  const resolvedStatus = resolveFieldStatus(status, hasError, ariaInvalid);
142
249
  const description = mergeDescriptions(describedBy, hasHint && `${id}-hint`, hasError && `${id}-error`, hasMessage && `${id}-message`);
143
- return _jsxs("fieldset", { ...props, ref: ref, id: id, className: classes("flood-fieldset", className), "data-hide-label": hideLabel || undefined, "data-status": resolvedStatus, "aria-describedby": description, "aria-invalid": resolvedStatus === "error" || ariaInvalid || undefined, children: [_jsx("legend", { className: classes("flood-legend", hideLabel && "flood-visually-hidden"), children: legend }), _jsxs("div", { className: "flood-fieldset__body", children: [hasHint && _jsx("div", { id: `${id}-hint`, className: "flood-hint", children: hint }), children, hasMessage && _jsx("div", { id: `${id}-message`, className: "flood-message", role: resolvedStatus === "error" ? "alert" : undefined, children: message }), hasError && _jsx("div", { id: `${id}-error`, className: "flood-error", role: "alert", children: error })] })] });
250
+ return _jsxs("fieldset", { ...props, ref: ref, id: id, className: classes("flood-fieldset", className), "data-hide-label": hideLabel || undefined, "data-status": resolvedStatus, "aria-describedby": description, "aria-invalid": resolvedStatus === "error" || ariaInvalid || undefined, children: [_jsx("legend", { ref: legendRef, className: classes("flood-legend", hideLabel && "flood-visually-hidden--focusable"), children: legend }), _jsxs("div", { className: "flood-fieldset__body", children: [hasHint && _jsx("div", { id: `${id}-hint`, className: "flood-hint", children: hint }), children, hasMessage && _jsx("div", { id: `${id}-message`, className: "flood-message", role: resolvedStatus === "error" ? "alert" : undefined, children: message }), hasError && _jsx("div", { id: `${id}-error`, className: "flood-error", role: "alert", children: error })] })] });
144
251
  });
145
252
  export const RadioGroup = forwardRef(function RadioGroup({ value, defaultValue, onValueChange, orientation = "vertical", ...props }, ref) {
146
253
  return _jsx(ChoiceGroup, { ...props, ref: ref, kind: "radio", orientation: orientation, selected: value === undefined ? undefined : [value], initial: defaultValue === undefined ? [] : [defaultValue], onSelectionChange: next => onValueChange?.(next[0] ?? ""), fieldsetComponent: Fieldset, choiceComponent: Radio });
@@ -151,7 +258,7 @@ export const SegmentedControl = forwardRef(function SegmentedControl({ label, op
151
258
  throw new Error("Flood icon-only segments require icons");
152
259
  return _jsx(RadioGroup, { ...props, ref: ref, legend: label, orientation: orientation, className: classes("flood-segmented", `flood-control--${controlSize}`, iconOnly && "flood-segmented--icons", fullWidth && "flood-segmented--full", className), options: options.map(option => ({ ...option, label: _jsxs(_Fragment, { children: [option.icon && _jsx(IconSlot, { size: controlSize, children: option.icon }), iconOnly ? _jsx("span", { className: "flood-segmented__name", children: option.label }) : option.label] }) })), onKeyDown: event => {
153
260
  onKeyDown?.(event);
154
- if (event.defaultPrevented)
261
+ if (event.defaultPrevented || event.altKey || event.ctrlKey || event.metaKey || event.nativeEvent.isComposing)
155
262
  return;
156
263
  const inputs = Array.from(event.currentTarget.querySelectorAll(":scope > .flood-fieldset__body > .flood-choice-group > .flood-choice > input[type=radio]:not(:disabled)"));
157
264
  const index = inputs.indexOf(event.target);
@@ -174,12 +281,13 @@ export const CheckboxGroup = forwardRef(function CheckboxGroup({ value, defaultV
174
281
  export function Field({ status, message, id: explicitId, label, hideLabel = false, hint, error, required, describedBy, children, }) {
175
282
  const generatedId = useId();
176
283
  const id = explicitId ?? `flood-${generatedId}`;
284
+ const labelRef = useFocusableHidden(hideLabel);
177
285
  const hasMessage = hasFieldContent(message);
178
286
  const hasHint = hasFieldContent(hint);
179
287
  const hasError = hasFieldContent(error);
180
288
  const resolvedStatus = hasError ? "error" : status;
181
289
  const description = mergeDescriptions(describedBy, hasHint && `${id}-hint`, hasError && `${id}-error`, hasMessage && !hasError && `${id}-message`);
182
- return (_jsxs("div", { className: "flood-field", "data-hide-label": hideLabel || undefined, "data-status": resolvedStatus, children: [_jsx(Label, { htmlFor: id, required: required, className: hideLabel ? "flood-visually-hidden" : undefined, children: label }), children({
290
+ return (_jsxs("div", { className: "flood-field", "data-hide-label": hideLabel || undefined, "data-status": resolvedStatus, children: [_jsx(Label, { ref: labelRef, htmlFor: id, required: required, className: hideLabel ? "flood-visually-hidden--focusable" : undefined, children: label }), children({
183
291
  id,
184
292
  "aria-describedby": description,
185
293
  "aria-invalid": hasError || resolvedStatus === "error" ? true : undefined,
@@ -209,6 +317,57 @@ export const Inline = forwardRef(function Inline({ gap = "2", align = "center",
209
317
  export const Surface = forwardRef(function Surface({ elevation = "default", padding = "6", style, className, ...props }, ref) {
210
318
  return _jsx("div", { ...props, ref: ref, "data-flood-elevation": elevation, className: classes("flood-surface", className), style: { background: `var(--flood-sys-color-surface-${elevation})`, padding: `var(--flood-sys-space-${padding})`, ...style } });
211
319
  });
320
+ /** Centered content with logical gutters; max width includes padding. @since 0.2.4 */
321
+ export const Container = forwardRef(function Container({ size = "lg", gutter = "6", className, style, ...props }, ref) {
322
+ return _jsx("div", { ...props, ref: ref, className: classes("flood-container", className), style: {
323
+ "--flood-container-max": size === "full" ? "none" : `var(--flood-sys-size-container-${size})`,
324
+ "--flood-container-gutter": `var(--flood-sys-space-${gutter})`, ...style,
325
+ } });
326
+ });
327
+ /** Auto-fit by default; explicit columns retain their count at every width. @since 0.2.4 */
328
+ export const Grid = forwardRef(function Grid({ columns, minItemWidth, gap = "4", className, style, ...props }, ref) {
329
+ return _jsx("div", { ...props, ref: ref, className: classes("flood-grid", className), style: {
330
+ gridTemplateColumns: columns ? `repeat(${columns}, minmax(0, 1fr))` : undefined,
331
+ "--flood-grid-min": minItemWidth, "--flood-layout-gap": `var(--flood-sys-space-${gap})`, ...style,
332
+ } });
333
+ });
334
+ /** Nonvisual content, with optional reveal when a descendant receives focus. @since 0.2.4 */
335
+ export const VisuallyHidden = forwardRef(function VisuallyHidden({ as = "span", focusable = false, className, ...props }, ref) {
336
+ const hiddenRef = useFocusableHidden(focusable, ref);
337
+ return createElement(as, { ...props, ref: hiddenRef, className: classes(focusable ? "flood-visually-hidden--focusable" : "flood-visually-hidden", className) });
338
+ });
339
+ /** Reserve media geometry. Child content may overflow rather than being clipped. @since 0.2.4 */
340
+ export const AspectRatio = forwardRef(function AspectRatio({ ratio = 16 / 9, className, style, children, ...props }, ref) {
341
+ if (!Number.isFinite(ratio) || ratio <= 0)
342
+ throw new RangeError("Flood AspectRatio requires a finite positive ratio");
343
+ return _jsx("div", { ...props, ref: ref, className: classes("flood-aspect-ratio", className), style: { aspectRatio: String(ratio), ...style }, children: _jsx("div", { className: "flood-aspect-ratio__content", children: children }) });
344
+ });
345
+ /** Native image semantics, explicit alternative text and bounded media fitting. @since 0.2.4 */
346
+ export const Image = forwardRef(function Image({ alt, fit = "cover", className, ...props }, ref) {
347
+ if (typeof alt !== "string")
348
+ throw new TypeError("Flood Image requires explicit alt text (empty for decorative images)");
349
+ return _jsx("img", { ...props, ref: ref, alt: alt, "data-fit": fit, className: classes("flood-image", className) });
350
+ });
351
+ /** Native list items supplied by the host; hidden markers retain list semantics. @since 0.2.4 */
352
+ export const List = forwardRef(function List({ ordered = false, start, reversed, marker = true, gap = "2", className, style, ...props }, ref) {
353
+ if (start !== undefined && !Number.isSafeInteger(start))
354
+ throw new RangeError("Flood List start requires a safe integer");
355
+ return createElement(ordered ? "ol" : "ul", { ...props, ref, start: ordered ? start : undefined,
356
+ reversed: ordered ? reversed : undefined, role: marker ? undefined : "list", "data-marker": marker,
357
+ className: classes("flood-list", className), style: { "--flood-layout-gap": `var(--flood-sys-space-${gap})`, ...style } });
358
+ });
359
+ /** Static empty content, not loading, an error boundary or a live announcement. @since 0.2.4 */
360
+ export const EmptyState = forwardRef(function EmptyState({ title, description, icon, actions, headingLevel = 2, density = "comfortable", className, ...props }, ref) {
361
+ if (typeof title !== "string" || !title.trim())
362
+ throw new Error("Flood EmptyState requires a nonempty title");
363
+ return _jsxs(Stack, { ...props, ref: ref, gap: "4", "data-density": density, className: classes("flood-empty-state", className), children: [hasFieldContent(icon) && _jsx("div", { className: "flood-empty-state__icon", children: _jsx(IconSlot, { size: "2xl", children: icon }) }), _jsxs(Stack, { gap: "1", children: [_jsx(Heading, { level: headingLevel, size: "section", children: title }), hasFieldContent(description) && _jsx(Text, { as: "div", size: "compact", tone: "subtle", children: description })] }), hasFieldContent(actions) && _jsx(Inline, { gap: "2", className: "flood-empty-state__actions", children: actions })] });
364
+ });
365
+ /** A structured content surface, not an interactive wrapper. @since 0.2.4 */
366
+ export const Card = forwardRef(function Card({ title, description, actions, footer, headingLevel = 2, density = "comfortable", elevation = "default", children, className, ...props }, ref) {
367
+ const present = hasFieldContent;
368
+ const heading = present(title), hint = present(description), action = present(actions);
369
+ return _jsxs(Surface, { ...props, ref: ref, elevation: elevation, padding: density === "compact" ? "4" : "6", "data-density": density, className: classes("flood-card", className), children: [(heading || hint || action) && _jsxs("div", { className: "flood-card__header", children: [(heading || hint) && _jsxs("div", { className: "flood-card__intro", children: [heading && _jsx(Heading, { level: headingLevel, size: "section", children: title }), hint && _jsx(Text, { as: "div", size: "compact", tone: "subtle", children: description })] }), action && _jsx("div", { className: "flood-card__actions", children: actions })] }), present(children) && _jsx("div", { className: "flood-card__body", children: children }), present(footer) && _jsx("div", { className: "flood-card__footer", children: footer })] });
370
+ });
212
371
  /** Adapter for the host's SVG icon package; glyphs are not bundled. */
213
372
  export const IconSlot = forwardRef(function IconSlot({ size = "md", label, className, children, ...props }, ref) {
214
373
  return _jsx("span", { ...props, ref: ref, role: label ? "img" : undefined, "aria-label": label, "aria-hidden": label ? undefined : true, className: classes("flood-icon", `flood-icon--${size}`, className), children: children });
@@ -220,10 +379,38 @@ export const Divider = forwardRef(function Divider({ className, ...props }, ref)
220
379
  return _jsx("hr", { ...props, ref: ref, className: classes("flood-divider", className) });
221
380
  });
222
381
  /** Selectable code with an optional host-owned action; no clipboard side effects. */
223
- export const CodeSnippet = forwardRef(function CodeSnippet({ code, action, className, ...props }, ref) {
224
- return (_jsxs("div", { ...props, ref: ref, className: classes("flood-code-snippet", className), children: [_jsx("code", { className: "flood-code-snippet__code", children: code }), action != null && _jsx("div", { className: "flood-code-snippet__action", children: action })] }));
382
+ export const CodeSnippet = forwardRef(function CodeSnippet({ code, codeDirection = "ltr", action, className, ...props }, ref) {
383
+ return (_jsxs("div", { ...props, ref: ref, className: classes("flood-code-snippet", className), children: [_jsx("code", { dir: codeDirection, className: "flood-code-snippet__code", children: code }), action != null && _jsx("div", { className: "flood-code-snippet__action", children: action })] }));
225
384
  });
226
385
  /** Native scrolling with inset overlay tracks; the ref targets the scrollable viewport. */
227
386
  export const ScrollArea = forwardRef(function ScrollArea({ orientation = "vertical", contentInset = "scrollbar", ...props }, ref) {
228
387
  return _jsx(OverlayScrollArea, { ...props, orientation: orientation, contentInset: contentInset, ref: ref });
229
388
  });
389
+ export const CopyButton = forwardRef(function CopyButton({ copyLabel = "Copy", copiedLabel = "Copied", errorLabel = "Failed", resetDelay = 2000, size = "md", variant = "subtle", ...props }, ref) {
390
+ return _jsx(ClipboardAction, { ...props, ref: ref, copyLabel: copyLabel, copiedLabel: copiedLabel, errorLabel: errorLabel, resetDelay: resetDelay, size: size, variant: variant });
391
+ });
392
+ export const ToastRegion = forwardRef(function ToastRegion({ label = "Notifications", closeLabel = "Dismiss notification", placement = "bottom-end", limit = 3, inline = false, hotkey = true, ...props }, ref) {
393
+ return _jsx(NotificationRegion, { ...props, ref: ref, label: label, closeLabel: closeLabel, placement: placement, limit: limit, inline: inline, hotkey: hotkey });
394
+ });
395
+ export const AvatarGroup = forwardRef(function AvatarGroup({ max = 4, size = "md", ...props }, ref) { return _jsx(GroupedAvatars, { ...props, ref: ref, max: max, size: size }); });
396
+ export const StatusIndicator = forwardRef(function StatusIndicator({ tone = "neutral", size = "md", announce = false, ...props }, ref) { return _jsx(NamedStatus, { ...props, ref: ref, tone: tone, size: size, announce: announce }); });
397
+ export const Figure = forwardRef(function Figure({ captionPosition = "after", ...props }, ref) { return _jsx(CaptionedFigure, { ...props, ref: ref, captionPosition: captionPosition }); });
398
+ export const Blockquote = forwardRef(function Blockquote(props, ref) {
399
+ return _jsx(AttributedQuote, { ...props, ref: ref });
400
+ });
401
+ export const InlineCode = forwardRef(function InlineCode({ size = "md", wrap = true, ...props }, ref) { return _jsx(InlineCodeText, { ...props, ref: ref, size: size, wrap: wrap }); });
402
+ export const Metric = forwardRef(function Metric({ size = "md", ...props }, ref) { return _jsx(MeasuredValue, { ...props, ref: ref, size: size }); });
403
+ export const Timeline = forwardRef(function Timeline(props, ref) {
404
+ return _jsx(EventTimeline, { ...props, ref: ref });
405
+ });
406
+ export const NavigationList = forwardRef(function NavigationList({ size = "md", ...props }, ref) { return _jsx(NavigationMenu, { ...props, ref: ref, size: size }); });
407
+ export const Steps = forwardRef(function Steps({ orientation = "horizontal", ...props }, ref) { return _jsx(StepSequence, { ...props, ref: ref, orientation: orientation }); });
408
+ export const Section = forwardRef(function Section({ headingLevel = 2, headingSize = "section", ...props }, ref) { return _jsx(ContentSection, { ...props, ref: ref, headingLevel: headingLevel, headingSize: headingSize }); });
409
+ export const Split = forwardRef(function Split({ side = "end", ratio = "main", collapseAt = "md", gap = "6", ...props }, ref) { return _jsx(SplitLayout, { ...props, ref: ref, side: side, ratio: ratio, collapseAt: collapseAt, gap: gap }); });
410
+ export const Center = forwardRef(function Center({ inline = false, ...props }, ref) { return _jsx(CenterLayout, { ...props, ref: ref, inline: inline }); });
411
+ export const SkipLink = forwardRef(function SkipLink({ children = "Skip to content", ...props }, ref) { return _jsx(SkipNavigation, { ...props, ref: ref, children: children }); });
412
+ export const ListBox = forwardRef(function ListBox({ multiple = false, disabled = false, readOnly = false, hideLabel = false, controlSize = "md", ...props }, ref) { return _jsx(CollectionListBox, { ...props, ref: ref, multiple: multiple, disabled: disabled, readOnly: readOnly, hideLabel: hideLabel, controlSize: controlSize }); });
413
+ export const PinInput = forwardRef(function PinInput({ length = 6, mode = "numeric", mask = false, hideLabel = false, controlSize = "md", ...props }, ref) { return _jsx(SegmentedPinInput, { ...props, ref: ref, length: length, mode: mode, mask: mask, hideLabel: hideLabel, controlSize: controlSize }); });
414
+ export const Rating = forwardRef(function Rating({ max = 5, defaultValue = 0, hideLabel = false, readOnly = false, size = "md", ...props }, ref) { return _jsx(StarRating, { ...props, ref: ref, max: max, defaultValue: defaultValue, hideLabel: hideLabel, readOnly: readOnly, size: size }); });
415
+ export const Dropzone = forwardRef(function Dropzone({ description = "Drop files here or choose from your device.", multiple = true, ...props }, ref) { return _jsx(FileDropzone, { ...props, ref: ref, description: description, multiple: multiple }); });
416
+ export const CharacterCount = forwardRef(function CharacterCount({ mode = "count", label = "Characters", overLimitLabel = "Over limit", ...props }, ref) { return _jsx(SupportingCharacterCount, { ...props, ref: ref, mode: mode, label: label, overLimitLabel: overLimitLabel }); });
package/dist/menu.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ import type { MenuProps } from "./index.js";
2
+ export declare const ActionMenu: import("react").ForwardRefExoticComponent<MenuProps & import("react").RefAttributes<HTMLDivElement>>;
package/dist/menu.js ADDED
@@ -0,0 +1,130 @@
1
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { forwardRef, useCallback, useEffect, useId, useImperativeHandle, useLayoutEffect, useRef, useState } from "react";
3
+ import { Button, Kbd, ScrollArea } from "./index.js";
4
+ import { positionLayer, registerLayer, returnFocus, tabbables } from "./overlay-runtime.js";
5
+ const useBrowserLayoutEffect = typeof window === "undefined" ? useEffect : useLayoutEffect;
6
+ export const ActionMenu = forwardRef(function ActionMenu({ label, trigger, items, onAction, open: controlled, defaultOpen, onOpenChange, disabled, side = "bottom", align = "start", controlSize = "sm", id: explicitId, className, onKeyDown, ...props }, ref) {
7
+ const [local, setLocal] = useState(Boolean(defaultOpen)), open = controlled ?? local;
8
+ const anchor = useRef(null), panel = useRef(null);
9
+ const generated = useId(), id = explicitId ?? `flood-menu-${generated}`;
10
+ const pending = useRef(undefined);
11
+ const edge = useRef("first"), search = useRef({ text: "", time: 0 });
12
+ if (!items.some(item => item.type !== "separator"))
13
+ throw new Error("Flood Menu requires at least one action item");
14
+ if (new Set(items.map(item => item.id)).size !== items.length || items.some(item => !item.id.trim() || (item.type !== "separator" && !item.label.trim())))
15
+ throw new Error("Flood menu items need unique nonempty IDs and action labels");
16
+ const request = (next) => {
17
+ if (pending.current === next)
18
+ return;
19
+ pending.current = next;
20
+ queueMicrotask(() => { pending.current = undefined; });
21
+ if (controlled === undefined)
22
+ setLocal(next);
23
+ onOpenChange?.(next);
24
+ };
25
+ const current = useRef({ open, request });
26
+ useBrowserLayoutEffect(() => { current.current = { open, request }; });
27
+ const setAnchor = useCallback((node) => { anchor.current = node; }, []);
28
+ useImperativeHandle(ref, () => panel.current, []);
29
+ const buttons = () => Array.from(panel.current?.querySelectorAll('[role="menuitem"]') ?? []);
30
+ const focusEdge = () => { const all = buttons(); ((edge.current === "last" ? all.at(-1) : all[0]) ?? panel.current)?.focus({ preventScroll: true }); };
31
+ useBrowserLayoutEffect(() => {
32
+ if (!open || !panel.current)
33
+ return;
34
+ const element = panel.current, document = element.ownerDocument, view = document.defaultView;
35
+ let alive = true, frame = 0, unregister = () => { };
36
+ const start = () => {
37
+ if (!alive || !anchor.current?.isConnected)
38
+ return;
39
+ const parent = anchor.current.closest("dialog");
40
+ if (parent && !parent.open) {
41
+ frame = view.requestAnimationFrame(start);
42
+ return;
43
+ }
44
+ if (typeof element.showPopover !== "function")
45
+ throw new Error("Flood Menu requires the native Popover API");
46
+ element.style.visibility = "hidden";
47
+ element.showPopover();
48
+ positionLayer(anchor.current, element, side, align);
49
+ unregister = registerLayer(document, { panel: element, anchor: () => anchor.current, close: () => current.current.request(false) });
50
+ search.current = { text: "", time: 0 };
51
+ focusEdge();
52
+ const update = () => { if (!alive)
53
+ return; if (anchor.current?.isConnected)
54
+ positionLayer(anchor.current, element, side, align); frame = view.requestAnimationFrame(update); };
55
+ frame = view.requestAnimationFrame(update);
56
+ };
57
+ start();
58
+ const toggle = (event) => {
59
+ if (!alive || event.newState !== "closed" || element.matches(":popover-open") || !current.current.open)
60
+ return;
61
+ current.current.request(false);
62
+ queueMicrotask(() => { if (alive && current.current.open && element.isConnected && !element.matches(":popover-open") && anchor.current?.isConnected) {
63
+ element.showPopover();
64
+ positionLayer(anchor.current, element, side, align);
65
+ focusEdge();
66
+ } });
67
+ };
68
+ element.addEventListener("toggle", toggle);
69
+ return () => { alive = false; view.cancelAnimationFrame(frame); unregister(); element.removeEventListener("toggle", toggle); returnFocus(element, anchor.current); if (element.matches(":popover-open"))
70
+ element.hidePopover(); };
71
+ }, [open, side, align]);
72
+ useBrowserLayoutEffect(() => {
73
+ if (open && panel.current?.matches(":popover-open") && panel.current.ownerDocument.activeElement === panel.current.ownerDocument.body)
74
+ focusEdge();
75
+ }, [items, open]);
76
+ const triggerProps = { ref: setAnchor, type: "button", disabled, "aria-haspopup": "menu", "aria-expanded": open, "aria-controls": id,
77
+ onClick: () => { edge.current = "first"; request(!open); },
78
+ onKeyDown: event => { if (event.defaultPrevented || disabled || event.altKey || event.ctrlKey || event.metaKey || !["ArrowDown", "ArrowUp"].includes(event.key))
79
+ return; event.preventDefault(); edge.current = event.key === "ArrowUp" ? "last" : "first"; if (open && panel.current?.matches(":popover-open"))
80
+ focusEdge();
81
+ else
82
+ request(true); },
83
+ };
84
+ const hasIcons = items.some(item => item.type !== "separator" && item.icon != null);
85
+ return _jsxs(_Fragment, { children: [trigger(triggerProps), _jsx("div", { ...props, ref: panel, id: id, popover: "manual", role: "menu", "aria-label": label, "aria-orientation": "vertical", tabIndex: -1, className: ["flood-overlay-panel", "flood-menu", className].filter(Boolean).join(" "), onKeyDown: event => {
86
+ onKeyDown?.(event);
87
+ if (event.defaultPrevented)
88
+ return;
89
+ const all = buttons(), index = all.indexOf(event.target);
90
+ if (event.key === "Tab") {
91
+ const button = anchor.current;
92
+ if (button) {
93
+ const scope = button.closest("dialog[open]") ?? button.ownerDocument.body;
94
+ const stops = tabbables(scope).filter(node => !panel.current?.contains(node));
95
+ const at = stops.indexOf(button), target = event.shiftKey ? button : at >= 0 ? stops[at + 1] : undefined;
96
+ if (target) {
97
+ event.preventDefault();
98
+ target.focus();
99
+ }
100
+ else
101
+ button.focus();
102
+ }
103
+ request(false);
104
+ return;
105
+ }
106
+ if (event.altKey || event.ctrlKey || event.metaKey || event.nativeEvent.isComposing)
107
+ return;
108
+ let next = event.key === "Home" ? 0 : event.key === "End" ? all.length - 1 : event.key === "ArrowDown" ? (index + 1) % all.length : event.key === "ArrowUp" ? (index - 1 + all.length) % all.length : -1;
109
+ if (["ArrowDown", "ArrowUp", "Home", "End"].includes(event.key))
110
+ search.current = { text: "", time: 0 };
111
+ if (event.key.length === 1 && event.key !== " ") {
112
+ const now = Date.now();
113
+ search.current = { text: (now - search.current.time < 700 ? search.current.text : "") + event.key.toLocaleLowerCase(), time: now };
114
+ const text = Array.from(search.current.text).every(char => char === search.current.text[0]) ? search.current.text[0] : search.current.text;
115
+ const start = text.length === 1 ? index + 1 : Math.max(0, index);
116
+ next = Array.from({ length: all.length }, (_, offset) => (start + offset) % all.length).find(candidate => all[candidate].dataset.label?.toLocaleLowerCase().startsWith(text)) ?? -1;
117
+ event.preventDefault();
118
+ }
119
+ if (next >= 0 && all[next]) {
120
+ event.preventDefault();
121
+ all[next].focus({ preventScroll: true });
122
+ all[next].scrollIntoView({ block: "nearest", inline: "nearest" });
123
+ }
124
+ }, children: open && _jsx(ScrollArea, { orientation: "vertical", role: "group", tabIndex: -1, className: "flood-menu__viewport", children: items.map(item => item.type === "separator" ? _jsx("div", { role: "separator", className: "flood-menu__separator" }, item.id) :
125
+ _jsxs(Button, { role: "menuitem", tabIndex: -1, variant: "ghost", size: controlSize, "aria-label": item.label, "aria-disabled": item.disabled || undefined, "data-label": item.label, "data-tone": item.tone, className: "flood-menu__item", onPointerMove: event => { if (event.pointerType !== "touch" && event.currentTarget.ownerDocument.activeElement !== event.currentTarget)
126
+ event.currentTarget.focus({ preventScroll: true }); }, onClick: event => { if (item.disabled) {
127
+ event.preventDefault();
128
+ return;
129
+ } request(false); onAction?.(item.id); }, children: [hasIcons && _jsx("span", { className: "flood-menu__icon", "aria-hidden": "true", children: item.icon }), _jsx("span", { className: "flood-menu__label", children: item.label }), item.shortcut && _jsx("span", { className: "flood-menu__shortcut", "aria-hidden": "true", children: _jsx(Kbd, { children: item.shortcut }) })] }, item.id)) }) })] });
130
+ });
@@ -0,0 +1,7 @@
1
+ import type { NavigationListProps, StepsProps, SectionProps, SplitProps, CenterProps, SkipLinkProps } from "./index.js";
2
+ export declare const NavigationMenu: import("react").ForwardRefExoticComponent<NavigationListProps & import("react").RefAttributes<HTMLElement>>;
3
+ export declare const StepSequence: import("react").ForwardRefExoticComponent<StepsProps & import("react").RefAttributes<HTMLOListElement>>;
4
+ export declare const ContentSection: import("react").ForwardRefExoticComponent<SectionProps & import("react").RefAttributes<HTMLElement>>;
5
+ export declare const SplitLayout: import("react").ForwardRefExoticComponent<SplitProps & import("react").RefAttributes<HTMLDivElement>>;
6
+ export declare const CenterLayout: import("react").ForwardRefExoticComponent<CenterProps & import("react").RefAttributes<HTMLDivElement>>;
7
+ export declare const SkipNavigation: import("react").ForwardRefExoticComponent<SkipLinkProps & import("react").RefAttributes<HTMLAnchorElement>>;