@flood-md/react 0.2.5 → 0.2.8

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 (85) hide show
  1. package/README.md +42 -1
  2. package/dist/alert-dialog.js +1 -1
  3. package/dist/calendar-period-grid.d.ts +17 -0
  4. package/dist/calendar-period-grid.js +13 -0
  5. package/dist/calendar-value.d.ts +10 -0
  6. package/dist/calendar-value.js +48 -0
  7. package/dist/calendar.d.ts +21 -0
  8. package/dist/calendar.js +259 -0
  9. package/dist/checkbox.js +1 -1
  10. package/dist/choice-group.js +1 -1
  11. package/dist/collection-cards.d.ts +4 -0
  12. package/dist/collection-cards.js +21 -0
  13. package/dist/color-controls.d.ts +5 -0
  14. package/dist/color-controls.js +110 -0
  15. package/dist/combobox.js +22 -13
  16. package/dist/command-list.d.ts +3 -0
  17. package/dist/command-list.js +93 -0
  18. package/dist/content-elements.js +7 -7
  19. package/dist/context-menu.d.ts +2 -0
  20. package/dist/context-menu.js +76 -0
  21. package/dist/data-display.js +2 -2
  22. package/dist/data-table.d.ts +2 -0
  23. package/dist/data-table.js +58 -0
  24. package/dist/date-field.d.ts +2 -0
  25. package/dist/date-field.js +172 -0
  26. package/dist/date-picker.d.ts +3 -0
  27. package/dist/date-picker.js +108 -0
  28. package/dist/date-range-field.d.ts +3 -0
  29. package/dist/date-range-field.js +84 -0
  30. package/dist/date-range-picker.d.ts +2 -0
  31. package/dist/date-range-picker.js +81 -0
  32. package/dist/date-range-value.d.ts +8 -0
  33. package/dist/date-range-value.js +13 -0
  34. package/dist/date-time-picker.d.ts +3 -0
  35. package/dist/date-time-picker.js +31 -0
  36. package/dist/date-time-range-picker.d.ts +2 -0
  37. package/dist/date-time-range-picker.js +51 -0
  38. package/dist/date-value.d.ts +10 -0
  39. package/dist/date-value.js +31 -0
  40. package/dist/disclosure.js +1 -1
  41. package/dist/editable.d.ts +3 -0
  42. package/dist/editable.js +135 -0
  43. package/dist/error-summary.d.ts +3 -0
  44. package/dist/error-summary.js +39 -0
  45. package/dist/feedback-actions.js +2 -2
  46. package/dist/feedback.js +2 -2
  47. package/dist/file-input.js +1 -1
  48. package/dist/file-records.d.ts +5 -0
  49. package/dist/file-records.js +55 -0
  50. package/dist/form-controls.js +25 -13
  51. package/dist/foundations.css +388 -2
  52. package/dist/hover-card.d.ts +3 -0
  53. package/dist/hover-card.js +144 -0
  54. package/dist/index.d.ts +661 -2
  55. package/dist/index.js +190 -86
  56. package/dist/menu.d.ts +27 -1
  57. package/dist/menu.js +327 -89
  58. package/dist/navigation-layout.js +6 -6
  59. package/dist/navigation-tree.d.ts +3 -0
  60. package/dist/navigation-tree.js +98 -0
  61. package/dist/navigation.js +3 -3
  62. package/dist/numeric-input.js +1 -1
  63. package/dist/overlays.js +11 -10
  64. package/dist/range-calendar.d.ts +2 -0
  65. package/dist/range-calendar.js +35 -0
  66. package/dist/resizable-panels.d.ts +3 -0
  67. package/dist/resizable-panels.js +205 -0
  68. package/dist/scroll-area.js +1 -1
  69. package/dist/select.js +1 -1
  70. package/dist/slider.js +1 -1
  71. package/dist/tag-input.d.ts +3 -0
  72. package/dist/tag-input.js +181 -0
  73. package/dist/text-inputs.js +2 -2
  74. package/dist/time-field.d.ts +2 -0
  75. package/dist/time-field.js +176 -0
  76. package/dist/time-picker.d.ts +2 -0
  77. package/dist/time-picker.js +83 -0
  78. package/dist/time-value.d.ts +12 -0
  79. package/dist/time-value.js +36 -0
  80. package/dist/toolbar.js +1 -1
  81. package/dist/tree-select.d.ts +3 -0
  82. package/dist/tree-select.js +113 -0
  83. package/dist/tree.d.ts +3 -0
  84. package/dist/tree.js +206 -0
  85. package/package.json +2 -2
@@ -3,7 +3,7 @@ import { forwardRef, useEffect, useId, useImperativeHandle, useLayoutEffect, use
3
3
  import { Button, IconSlot, Link, ScrollArea } from "./index.js";
4
4
  import { pageWindow } from "./page-window.js";
5
5
  const useBrowserLayoutEffect = typeof window === "undefined" ? useEffect : useLayoutEffect;
6
- export const TabNavigation = forwardRef(function TabNavigation({ label, items, value, defaultValue, onValueChange, activationMode = "automatic", orientation = "horizontal", controlSize = "md", disabled = false, iconOnly = false, className, onKeyDown, id: explicitId, ...props }, ref) {
6
+ export const TabNavigation = /* @__PURE__ */ forwardRef(function TabNavigation({ label, items, value, defaultValue, onValueChange, activationMode = "automatic", orientation = "horizontal", controlSize = "md", disabled = false, iconOnly = false, className, onKeyDown, id: explicitId, ...props }, ref) {
7
7
  const generated = useId(), id = explicitId ?? `flood-tabs-${generated}`;
8
8
  const [local, setLocal] = useState(defaultValue), [focused, setFocused] = useState();
9
9
  const root = useRef(null), list = useRef(null), focusedPanel = useRef(undefined);
@@ -49,10 +49,10 @@ export const TabNavigation = forwardRef(function TabNavigation({ label, items, v
49
49
  setFocused(undefined); }, children: items.map((item, index) => _jsxs(Button, { variant: "ghost", size: controlSize, id: `${id}-tab-${index}`, type: "button", role: "tab", "aria-selected": item.value === selected, "aria-controls": `${id}-panel-${index}`, "data-value": item.value, disabled: disabled || item.disabled, tabIndex: item.value === tabStop ? 0 : -1, "aria-label": iconOnly ? item.label : undefined, className: `flood-tab${iconOnly ? " flood-icon-button" : ""}`, onFocus: () => setFocused(item.value), onClick: () => select(item.value), children: [item.icon && _jsx(IconSlot, { size: controlSize, children: item.icon }), !iconOnly && item.label] }, item.value)) }) }), items.map((item, index) => _jsx("div", { id: `${id}-panel-${index}`, role: "tabpanel", "aria-labelledby": `${id}-tab-${index}`, tabIndex: 0, hidden: item.value !== selected, className: "flood-tabs__panel", onFocusCapture: () => { focusedPanel.current = item.value; }, onBlurCapture: event => { if (!event.currentTarget.hidden)
50
50
  focusedPanel.current = undefined; }, children: item.content }, item.value))] });
51
51
  });
52
- export const BreadcrumbNavigation = forwardRef(function BreadcrumbNavigation({ items, label = "Breadcrumb", separator = "/", className, ...props }, ref) {
52
+ export const BreadcrumbNavigation = /* @__PURE__ */ forwardRef(function BreadcrumbNavigation({ items, label = "Breadcrumb", separator = "/", className, ...props }, ref) {
53
53
  return _jsx("nav", { ...props, ref: ref, "aria-label": label, className: ["flood-breadcrumb", className].filter(Boolean).join(" "), children: _jsx("ol", { children: items.map((item, index) => _jsxs("li", { children: [index > 0 && _jsx("span", { className: "flood-breadcrumb__separator", "aria-hidden": "true", children: separator }), item.href !== undefined ? _jsx(Link, { href: item.href, "aria-current": index === items.length - 1 ? "page" : undefined, children: item.label }) : _jsx("span", { "aria-current": index === items.length - 1 ? "page" : undefined, children: item.label })] }, index)) }) });
54
54
  });
55
- export const PageNavigation = forwardRef(function PageNavigation({ page, pageCount, onPageChange, getHref, siblingCount = 1, controlSize = "sm", disabled = false, label = "Pagination", previousLabel = "Previous page", nextLabel = "Next page", pageLabel = page => `Page ${page}`, className, ...props }, ref) {
55
+ export const PageNavigation = /* @__PURE__ */ forwardRef(function PageNavigation({ page, pageCount, onPageChange, getHref, siblingCount = 1, controlSize = "sm", disabled = false, label = "Pagination", previousLabel = "Previous page", nextLabel = "Next page", pageLabel = page => `Page ${page}`, className, ...props }, ref) {
56
56
  const pages = pageWindow(page, pageCount, siblingCount);
57
57
  const control = (target, text, name, unavailable = false) => {
58
58
  const inactive = disabled || unavailable, current = target === page;
@@ -1,7 +1,7 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { forwardRef, useEffect, useImperativeHandle, useRef, useState } from "react";
3
3
  /** The browser owns decimal stepping, validity, editing and form submission. */
4
- export const NumericStepper = forwardRef(function NumericStepper({ inputComponent: Group, buttonComponent: Action, value, defaultValue = 0, onValueChange, min, max, step = 1, disabled, readOnly, controlSize = "md", decreaseLabel = "Decrease value", increaseLabel = "Increase value", className, onBlur, onKeyDown, ...props }, ref) {
4
+ export const NumericStepper = /* @__PURE__ */ forwardRef(function NumericStepper({ inputComponent: Group, buttonComponent: Action, value, defaultValue = 0, onValueChange, min, max, step = 1, disabled, readOnly, controlSize = "md", decreaseLabel = "Decrease value", increaseLabel = "Increase value", className, onBlur, onKeyDown, ...props }, ref) {
5
5
  if (!Number.isFinite(step) || step <= 0 || (min !== undefined && !Number.isFinite(min)) || (max !== undefined && !Number.isFinite(max)) || (min !== undefined && max !== undefined && min > max))
6
6
  throw new Error("Flood NumberInput requires finite bounds and a positive step");
7
7
  for (const number of [value, defaultValue])
package/dist/overlays.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { forwardRef, useCallback, useEffect, useId, useImperativeHandle, useLayoutEffect, useRef, useState } from "react";
3
- import { IconButton, ScrollArea } from "./index.js";
3
+ import { IconButton, ScrollArea, VisuallyHidden } from "./index.js";
4
4
  import { positionLayer, registerLayer, returnFocus, lockScroll, tabbables } from "./overlay-runtime.js";
5
5
  import { mergeDescriptions } from "./field-state.js";
6
6
  const useBrowserLayoutEffect = typeof window === "undefined" ? useEffect : useLayoutEffect;
@@ -21,7 +21,7 @@ function focusContent(panel, preferred) {
21
21
  function OverlayClose({ label, onClick }) {
22
22
  return _jsx(IconButton, { label: label, size: "sm", variant: "subtle", className: "flood-overlay-close", onClick: onClick, children: _jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.75", strokeLinecap: "round", "aria-hidden": "true", focusable: "false", children: _jsx("path", { d: "m4 4 16 16M20 4 4 20" }) }) });
23
23
  }
24
- export const AnchoredOverlay = forwardRef(function AnchoredOverlay(allProps, forwardedRef) {
24
+ export const AnchoredOverlay = /* @__PURE__ */ forwardRef(function AnchoredOverlay(allProps, forwardedRef) {
25
25
  const { kind, trigger, side = "bottom", align = "start", disabled = false, id: explicitId, className, style, ...native } = allProps;
26
26
  const pop = allProps, tip = allProps;
27
27
  const [local, setLocal] = useState(kind === "popover" ? Boolean(pop.defaultOpen) : false);
@@ -38,7 +38,8 @@ export const AnchoredOverlay = forwardRef(function AnchoredOverlay(allProps, for
38
38
  suppressed.current = true;
39
39
  overPanel.current = false;
40
40
  } request(false); };
41
- const current = useCurrent({ close, request, disabled, open, initialFocusRef: pop.initialFocusRef });
41
+ const current = useCurrent({ close, request, disabled, open, initialFocusRef: pop.initialFocusRef, positionReference: pop.positionReference });
42
+ const positionReference = () => { const node = current.current.positionReference?.current; return node?.isConnected && node.ownerDocument === panel.current?.ownerDocument ? node : anchor.current; };
42
43
  useImperativeHandle(forwardedRef, () => panel.current, []);
43
44
  useEffect(() => () => clearTimeout(timer.current), []);
44
45
  useEffect(() => { if (kind === "tooltip" && disabled) {
@@ -71,13 +72,13 @@ export const AnchoredOverlay = forwardRef(function AnchoredOverlay(allProps, for
71
72
  throw new Error("Flood overlays require the native Popover API");
72
73
  element.style.visibility = "hidden";
73
74
  element.showPopover();
74
- positionLayer(anchor.current, element, side, align);
75
+ positionLayer(positionReference(), element, side, align);
75
76
  unregister = registerLayer(document, { panel: element, anchor: () => anchor.current, close: () => current.current.close(), tooltip: kind === "tooltip" });
76
77
  if (kind === "popover")
77
78
  focusContent(element, current.current.initialFocusRef?.current);
78
79
  const update = () => { if (!alive)
79
80
  return; if (anchor.current?.isConnected)
80
- positionLayer(anchor.current, element, side, align); frame = view.requestAnimationFrame(update); };
81
+ positionLayer(positionReference(), element, side, align); frame = view.requestAnimationFrame(update); };
81
82
  frame = view.requestAnimationFrame(update);
82
83
  };
83
84
  start();
@@ -87,7 +88,7 @@ export const AnchoredOverlay = forwardRef(function AnchoredOverlay(allProps, for
87
88
  current.current.request(false);
88
89
  queueMicrotask(() => { if (alive && current.current.open && element.isConnected && !element.matches(":popover-open") && anchor.current?.isConnected) {
89
90
  element.showPopover();
90
- positionLayer(anchor.current, element, side, align);
91
+ positionLayer(positionReference(), element, side, align);
91
92
  if (kind === "popover")
92
93
  focusContent(element, current.current.initialFocusRef?.current);
93
94
  } });
@@ -104,16 +105,16 @@ export const AnchoredOverlay = forwardRef(function AnchoredOverlay(allProps, for
104
105
  onPointerLeave: () => { hovered.current = false; leave(); }, onFocus: () => { focused.current = true; enter(true); }, onBlur: () => { focused.current = false; leave(); },
105
106
  } : { disabled, "aria-haspopup": "dialog", "aria-expanded": open, "aria-controls": id, onClick: () => request(!open) }) };
106
107
  // Remove component-only props before forwarding native div attributes.
107
- const { title: unusedTitle, content: unusedContent, children: unusedChildren, open: unusedOpen, defaultOpen: unusedDefault, onOpenChange: unusedChange, delay: unusedDelay, closeLabel: unusedClose, initialFocusRef: unusedFocus, ...attributes } = native;
108
- return _jsxs(_Fragment, { children: [trigger(triggerProps), _jsx("div", { ...attributes, ref: panel, id: id, popover: "manual", role: kind === "tooltip" ? "tooltip" : "dialog", "aria-labelledby": kind === "popover" ? `${id}-title` : undefined, tabIndex: kind === "popover" ? -1 : undefined, className: ["flood-overlay-panel", kind === "tooltip" ? "flood-tooltip" : "flood-popover", className].filter(Boolean).join(" "), style: style, onPointerEnter: event => { attributes.onPointerEnter?.(event); if (kind === "tooltip") {
108
+ const { title: unusedTitle, chrome: unusedChrome, content: unusedContent, children: unusedChildren, open: unusedOpen, defaultOpen: unusedDefault, onOpenChange: unusedChange, delay: unusedDelay, closeLabel: unusedClose, initialFocusRef: unusedFocus, positionReference: unusedPosition, ...attributes } = native;
109
+ return _jsxs(_Fragment, { children: [trigger(triggerProps), _jsx("div", { ...attributes, ref: panel, id: id, popover: "manual", role: kind === "tooltip" ? "tooltip" : "dialog", "aria-labelledby": kind === "popover" ? `${id}-title` : undefined, tabIndex: kind === "popover" ? -1 : undefined, "data-chrome": kind === "popover" ? pop.chrome : undefined, className: ["flood-overlay-panel", kind === "tooltip" ? "flood-tooltip" : "flood-popover", className].filter(Boolean).join(" "), style: style, onPointerEnter: event => { attributes.onPointerEnter?.(event); if (kind === "tooltip") {
109
110
  overPanel.current = true;
110
111
  clear();
111
112
  } }, onPointerLeave: event => { attributes.onPointerLeave?.(event); if (kind === "tooltip") {
112
113
  overPanel.current = false;
113
114
  leave();
114
- } }, children: kind === "tooltip" ? tip.content : _jsxs(_Fragment, { children: [_jsxs("div", { className: "flood-overlay-header", children: [_jsx("h2", { id: `${id}-title`, className: "flood-overlay-title", children: pop.title }), _jsx(OverlayClose, { label: pop.closeLabel ?? "Close", onClick: close })] }), open && _jsx("div", { className: "flood-overlay-body", children: pop.children })] }) })] });
115
+ } }, children: kind === "tooltip" ? tip.content : _jsxs(_Fragment, { children: [pop.chrome === "minimal" ? _jsx(VisuallyHidden, { children: _jsx("h2", { id: `${id}-title`, children: pop.title }) }) : _jsxs("div", { className: "flood-overlay-header", children: [_jsx("h2", { id: `${id}-title`, className: "flood-overlay-title", children: pop.title }), _jsx(OverlayClose, { label: pop.closeLabel ?? "Close", onClick: close })] }), open && _jsx("div", { className: "flood-overlay-body", children: pop.children })] }) })] });
115
116
  });
116
- export const ModalOverlay = forwardRef(function ModalOverlay({ showCloseButton = true, scrollBody = false, footer, title, description, trigger, children, open: controlled, defaultOpen = false, onOpenChange, disabled = false, closeLabel = "Close", closeOnOutsideClick = true, initialFocusRef, id: explicitId, className, onKeyDown, "aria-describedby": describedBy, ...props }, forwardedRef) {
117
+ export const ModalOverlay = /* @__PURE__ */ forwardRef(function ModalOverlay({ showCloseButton = true, scrollBody = false, footer, title, description, trigger, children, open: controlled, defaultOpen = false, onOpenChange, disabled = false, closeLabel = "Close", closeOnOutsideClick = true, initialFocusRef, id: explicitId, className, onKeyDown, "aria-describedby": describedBy, ...props }, forwardedRef) {
117
118
  const [local, setLocal] = useState(defaultOpen), open = controlled ?? local;
118
119
  const dialog = useRef(null), anchor = useRef(null), generated = useId(), id = explicitId ?? `flood-dialog-${generated}`;
119
120
  const request = (next) => { if (controlled === undefined)
@@ -0,0 +1,2 @@
1
+ import type { RangeCalendarProps } from "./index.js";
2
+ export declare const DateRangeCalendar: import("react").ForwardRefExoticComponent<RangeCalendarProps & import("react").RefAttributes<HTMLDivElement>>;
@@ -0,0 +1,35 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { forwardRef, useEffect, useState } from "react";
3
+ import { CalendarGrid } from "./calendar.js";
4
+ import { availableDateRange, checkDateRange, completeDateRange, emptyDateRange, orderedDateRange, sameDateRange } from "./date-range-value.js";
5
+ export const DateRangeCalendar = /* @__PURE__ */ forwardRef(function DateRangeCalendar({ value, defaultValue = emptyDateRange(), onValueChange, onRangeSelect, isRangeDisabled, rangeLabels, min, max, isDateDisabled, className, ...props }, ref) {
6
+ checkDateRange(defaultValue, "defaultValue");
7
+ if (value !== undefined)
8
+ checkDateRange(value);
9
+ const [local, setLocal] = useState(defaultValue), range = value ?? local;
10
+ const [preview, setPreview] = useState();
11
+ const anchor = range.start || range.end, partial = !range.start || !range.end;
12
+ const tentative = anchor && partial && preview ? orderedDateRange(anchor, preview) : undefined;
13
+ const available = (next) => availableDateRange(next, min, max, isDateDisabled, isRangeDisabled);
14
+ const unavailable = !!tentative && !available(tentative);
15
+ const labels = { chooseStart: rangeLabels?.chooseStart ?? "Choose a start date.", chooseEnd: rangeLabels?.chooseEnd ?? "Choose an end date.", unavailable: rangeLabels?.unavailable ?? "This date range is unavailable." };
16
+ if (!Object.values(labels).every(text => text.trim()))
17
+ throw new Error("Flood RangeCalendar labels must be nonempty");
18
+ useEffect(() => setPreview(undefined), [range.start, range.end]);
19
+ const visual = tentative ?? (completeDateRange(range) ? range : anchor ? { start: anchor, end: anchor } : undefined);
20
+ return _jsx(CalendarGrid, { ...props, ref: ref, min: min, max: max, isDateDisabled: isDateDisabled, className: ["flood-range-calendar", className].filter(Boolean).join(" "), selectedDate: range.start || range.end, multiple: true, isSelected: date => completeDateRange(range) ? date >= range.start && date <= range.end : date === anchor, dayMetadata: date => ({ rangeStart: visual?.start === date, rangeEnd: visual?.end === date, inRange: !!visual && date >= visual.start && date <= visual.end, preview: !!tentative && date >= tentative.start && date <= tentative.end, unavailable: unavailable && !!tentative && date >= tentative.start && date <= tentative.end }), onDayPreview: setPreview, onDayActivate: date => {
21
+ const next = anchor && partial ? orderedDateRange(anchor, date) : { start: date, end: "" };
22
+ if (next.end && !available(next)) {
23
+ setPreview(date);
24
+ return;
25
+ }
26
+ setPreview(undefined);
27
+ if (!sameDateRange(next, range)) {
28
+ if (value === undefined)
29
+ setLocal(next);
30
+ onValueChange?.(next);
31
+ }
32
+ if (next.end)
33
+ onRangeSelect?.(next);
34
+ }, footer: _jsx("div", { className: "flood-range-calendar__instruction", role: "status", "aria-live": "polite", children: unavailable ? labels.unavailable : anchor && partial ? labels.chooseEnd : labels.chooseStart }) });
35
+ });
@@ -0,0 +1,3 @@
1
+ import type { ResizablePanelsProps } from "./index.js";
2
+ /** Two bounded panes with a dedicated separator track; percentages exclude that track. */
3
+ export declare const ResizablePaneGroup: import("react").ForwardRefExoticComponent<ResizablePanelsProps & import("react").RefAttributes<HTMLDivElement>>;
@@ -0,0 +1,205 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { forwardRef, useEffect, useId, useImperativeHandle, useLayoutEffect, useRef, useState } from "react";
3
+ const clamp = (value, min, max) => Math.max(min, Math.min(max, Math.round(value * 1e6) / 1e6));
4
+ const useBrowserLayoutEffect = typeof window === "undefined" ? useEffect : useLayoutEffect;
5
+ const resizeKeys = new Set(["ArrowLeft", "ArrowRight", "ArrowUp", "ArrowDown", "Home", "End"]);
6
+ function lockDocument(document, cursor) {
7
+ const style = document.documentElement.style;
8
+ const entries = ["user-select", "-webkit-user-select", "cursor"].map(name => ({ name, value: style.getPropertyValue(name), priority: style.getPropertyPriority(name), owned: name === "cursor" ? cursor : "none" }));
9
+ for (const entry of entries)
10
+ style.setProperty(entry.name, entry.owned, "important");
11
+ return () => { for (const entry of entries) {
12
+ if (style.getPropertyValue(entry.name) !== entry.owned || style.getPropertyPriority(entry.name) !== "important")
13
+ continue;
14
+ if (entry.value)
15
+ style.setProperty(entry.name, entry.value, entry.priority);
16
+ else
17
+ style.removeProperty(entry.name);
18
+ } };
19
+ }
20
+ /** Two bounded panes with a dedicated separator track; percentages exclude that track. */
21
+ export const ResizablePaneGroup = /* @__PURE__ */ forwardRef(function ResizablePaneGroup({ label, main, secondary, orientation = "horizontal", value, defaultValue = 50, onValueChange, onValueCommit, min = 10, max = 90, step = 1, largeStep = 10, disabled = false, id: explicitId, className, style, onPointerDown, onPointerMove, onPointerUp, onPointerCancel, onLostPointerCapture, onKeyDown, onKeyUp, onBlur, ...props }, forwardedRef) {
22
+ if (!label.trim())
23
+ throw new Error("Flood ResizablePanels requires a nonempty separator label");
24
+ if (orientation !== "horizontal" && orientation !== "vertical")
25
+ throw new Error("Flood ResizablePanels orientation must be horizontal or vertical");
26
+ if (![min, max].every(Number.isFinite) || min < 0 || max > 100 || min >= max)
27
+ throw new Error("Flood ResizablePanels bounds must satisfy 0 <= min < max <= 100");
28
+ if (![step, largeStep].every(number => Number.isFinite(number) && number > 0))
29
+ throw new Error("Flood ResizablePanels keyboard steps must be positive finite percentages");
30
+ for (const entry of [value, defaultValue])
31
+ if (entry !== undefined && (!Number.isFinite(entry) || entry < 0 || entry > 100))
32
+ throw new Error("Flood ResizablePanels values must be finite percentages from 0 to 100");
33
+ const generated = useId(), id = explicitId ?? `flood-resizable-${generated}`, mainId = `${id}-main`, secondaryId = `${id}-secondary`;
34
+ const root = useRef(null), handle = useRef(null), first = useRef(null), second = useRef(null);
35
+ useImperativeHandle(forwardedRef, () => root.current, []);
36
+ const [local, setLocal] = useState(defaultValue), [dragging, setDragging] = useState(false), percent = clamp(value ?? local, min, max);
37
+ const drag = useRef(undefined), keyboard = useRef(false), alive = useRef(true), restoreSeparatorFocus = useRef(false);
38
+ const current = useRef({ percent, value, min, max, orientation, disabled, onValueChange, onValueCommit });
39
+ current.current = { percent, value, min, max, orientation, disabled, onValueChange, onValueCommit };
40
+ const bindPane = (node, reference, inactive) => {
41
+ reference.current = node;
42
+ if (!node)
43
+ return;
44
+ if (inactive && node.contains(node.ownerDocument.activeElement))
45
+ restoreSeparatorFocus.current = true;
46
+ // Set the native property so React 18 does not receive an unknown boolean prop.
47
+ node.inert = inactive;
48
+ };
49
+ useBrowserLayoutEffect(() => {
50
+ if (restoreSeparatorFocus.current) {
51
+ restoreSeparatorFocus.current = false;
52
+ handle.current?.focus({ preventScroll: true });
53
+ }
54
+ });
55
+ const publish = (next) => {
56
+ const state = current.current, bounded = clamp(next, state.min, state.max);
57
+ if (state.value === undefined) {
58
+ current.current.percent = bounded;
59
+ setLocal(bounded);
60
+ }
61
+ state.onValueChange?.(bounded);
62
+ };
63
+ const commitAccepted = () => queueMicrotask(() => { if (alive.current)
64
+ current.current.onValueCommit?.(current.current.percent); });
65
+ const finish = (commit, restore = true) => {
66
+ const gesture = drag.current;
67
+ if (!gesture)
68
+ return;
69
+ drag.current = undefined;
70
+ gesture.restore();
71
+ try {
72
+ if (gesture.handle.hasPointerCapture(gesture.pointerId))
73
+ gesture.handle.releasePointerCapture(gesture.pointerId);
74
+ }
75
+ catch { /* Capture may already be released by the browser or node removal. */ }
76
+ if (alive.current)
77
+ setDragging(false);
78
+ if (!alive.current)
79
+ return;
80
+ if (!commit && restore && current.current.value === undefined) {
81
+ const start = clamp(gesture.initial, current.current.min, current.current.max);
82
+ if (start !== current.current.percent)
83
+ publish(start);
84
+ }
85
+ if (commit)
86
+ commitAccepted();
87
+ };
88
+ const move = (event) => {
89
+ const gesture = drag.current;
90
+ if (!gesture || gesture.pointerId !== event.pointerId || current.current.disabled)
91
+ return;
92
+ const a = first.current?.getBoundingClientRect(), b = second.current?.getBoundingClientRect();
93
+ if (!a || !b)
94
+ return;
95
+ const available = gesture.orientation === "horizontal" ? a.width + b.width : a.height + b.height;
96
+ if (!Number.isFinite(available) || available <= 0)
97
+ return;
98
+ const coordinate = gesture.orientation === "horizontal" ? event.clientX : event.clientY;
99
+ if (!Number.isFinite(coordinate))
100
+ return;
101
+ const direction = gesture.orientation === "horizontal" && gesture.rtl ? -1 : 1;
102
+ const next = clamp(gesture.initial + (coordinate - gesture.origin) * direction / available * 100, current.current.min, current.current.max);
103
+ if (next !== gesture.requested) {
104
+ gesture.requested = next;
105
+ publish(next);
106
+ }
107
+ };
108
+ useEffect(() => {
109
+ alive.current = true;
110
+ return () => { alive.current = false; keyboard.current = false; finish(false, false); };
111
+ }, []);
112
+ useEffect(() => { if (drag.current)
113
+ finish(false); keyboard.current = false; }, [disabled, orientation, min, max]);
114
+ useEffect(() => {
115
+ if (!dragging || !root.current)
116
+ return;
117
+ const document = root.current.ownerDocument, view = document.defaultView;
118
+ const cancel = () => finish(false), visibility = () => { if (document.hidden)
119
+ cancel(); };
120
+ view?.addEventListener("blur", cancel);
121
+ document.addEventListener("visibilitychange", visibility);
122
+ return () => { view?.removeEventListener("blur", cancel); document.removeEventListener("visibilitychange", visibility); };
123
+ }, [dragging]);
124
+ return _jsxs("div", { ...props, id: id, ref: root, className: ["flood-resizable-panels", className].filter(Boolean).join(" "), "data-orientation": orientation, "data-disabled": disabled || undefined, "data-dragging": dragging || undefined, style: { ...style, "--flood-main-weight": `${percent}fr`, "--flood-secondary-weight": `${100 - percent}fr` }, onPointerDown: event => {
125
+ onPointerDown?.(event);
126
+ const separator = handle.current, node = root.current;
127
+ if (event.defaultPrevented || current.current.disabled || drag.current || event.button !== 0 || event.isPrimary === false || !separator?.contains(event.target) || !node)
128
+ return;
129
+ const a = first.current?.getBoundingClientRect(), b = second.current?.getBoundingClientRect(), horizontal = orientation === "horizontal";
130
+ if (!a || !b || (horizontal ? a.width + b.width : a.height + b.height) <= 0 || !Number.isFinite(horizontal ? event.clientX : event.clientY))
131
+ return;
132
+ separator.focus({ preventScroll: true });
133
+ if (current.current.disabled || current.current.orientation !== orientation || !separator.isConnected)
134
+ return;
135
+ try {
136
+ separator.setPointerCapture(event.pointerId);
137
+ }
138
+ catch {
139
+ return;
140
+ }
141
+ event.preventDefault();
142
+ keyboard.current = false;
143
+ drag.current = { pointerId: event.pointerId, origin: horizontal ? event.clientX : event.clientY, initial: current.current.percent, requested: current.current.percent, orientation, rtl: node.ownerDocument.defaultView?.getComputedStyle(node).direction === "rtl", handle: separator, restore: lockDocument(node.ownerDocument, horizontal ? "col-resize" : "row-resize") };
144
+ setDragging(true);
145
+ }, onPointerMove: event => { onPointerMove?.(event); if (!event.defaultPrevented)
146
+ move(event); }, onPointerUp: event => {
147
+ let accepted = false;
148
+ try {
149
+ onPointerUp?.(event);
150
+ accepted = !event.defaultPrevented;
151
+ if (accepted)
152
+ move(event);
153
+ }
154
+ finally {
155
+ if (drag.current?.pointerId === event.pointerId)
156
+ finish(accepted);
157
+ }
158
+ }, onPointerCancel: event => { try {
159
+ onPointerCancel?.(event);
160
+ }
161
+ finally {
162
+ if (drag.current?.pointerId === event.pointerId)
163
+ finish(false);
164
+ } }, onLostPointerCapture: event => { try {
165
+ onLostPointerCapture?.(event);
166
+ }
167
+ finally {
168
+ if (drag.current?.pointerId === event.pointerId)
169
+ finish(false);
170
+ } }, onKeyDown: event => {
171
+ onKeyDown?.(event);
172
+ if (event.defaultPrevented || event.target !== handle.current || event.nativeEvent.isComposing || event.keyCode === 229 || event.altKey || event.ctrlKey || event.metaKey)
173
+ return;
174
+ if (event.key === "Escape" && drag.current) {
175
+ event.preventDefault();
176
+ finish(false);
177
+ return;
178
+ }
179
+ if (current.current.disabled || drag.current || !resizeKeys.has(event.key))
180
+ return;
181
+ const horizontal = orientation === "horizontal", rtl = root.current?.ownerDocument.defaultView?.getComputedStyle(root.current).direction === "rtl";
182
+ const direction = horizontal ? event.key === "ArrowRight" ? (rtl ? -1 : 1) : event.key === "ArrowLeft" ? (rtl ? 1 : -1) : 0 : event.key === "ArrowDown" ? 1 : event.key === "ArrowUp" ? -1 : 0;
183
+ if (!direction && event.key !== "Home" && event.key !== "End")
184
+ return;
185
+ event.preventDefault();
186
+ const state = current.current, next = clamp(event.key === "Home" ? min : event.key === "End" ? max : state.percent + direction * (event.shiftKey ? largeStep : step), min, max);
187
+ if (next !== state.percent) {
188
+ keyboard.current = true;
189
+ publish(next);
190
+ }
191
+ }, onKeyUp: event => { onKeyUp?.(event); if (event.target === handle.current && (event.key === "Home" || event.key === "End" || (orientation === "horizontal" ? ["ArrowLeft", "ArrowRight"] : ["ArrowUp", "ArrowDown"]).includes(event.key)) && keyboard.current) {
192
+ keyboard.current = false;
193
+ if (!event.defaultPrevented)
194
+ commitAccepted();
195
+ } }, onBlur: event => { try {
196
+ onBlur?.(event);
197
+ }
198
+ finally {
199
+ if (event.target === handle.current && keyboard.current) {
200
+ keyboard.current = false;
201
+ if (!event.defaultPrevented)
202
+ commitAccepted();
203
+ }
204
+ } }, children: [_jsx("div", { ref: node => bindPane(node, first, percent === 0), id: mainId, "aria-hidden": percent === 0 || undefined, className: "flood-resizable-panels__panel", children: main }), _jsx("div", { ref: handle, role: "separator", tabIndex: disabled ? -1 : 0, "aria-disabled": disabled || undefined, "aria-label": label, "aria-orientation": orientation === "horizontal" ? "vertical" : "horizontal", "aria-controls": `${mainId} ${secondaryId}`, "aria-valuemin": min, "aria-valuemax": max, "aria-valuenow": percent, "aria-valuetext": `${Number(percent.toFixed(2))}%`, className: "flood-resizable-panels__separator" }), _jsx("div", { ref: node => bindPane(node, second, percent === 100), id: secondaryId, "aria-hidden": percent === 100 || undefined, className: "flood-resizable-panels__panel", children: secondary })] });
205
+ });
@@ -84,7 +84,7 @@ function cornerBounds(node, axis, crossStart, crossEnd, start, end, inset) {
84
84
  return { start, end: Math.max(start, end) };
85
85
  }
86
86
  /** Original overlay chrome; the viewport itself remains a browser-native scroll container. */
87
- export const OverlayScrollArea = forwardRef(function OverlayScrollArea({ orientation = "vertical", contentInset = "none", children, className, style, tabIndex = 0, role, onFocus, ...props }, forwardedRef) {
87
+ export const OverlayScrollArea = /* @__PURE__ */ forwardRef(function OverlayScrollArea({ orientation = "vertical", contentInset = "none", children, className, style, tabIndex = 0, role, onFocus, ...props }, forwardedRef) {
88
88
  const id = useId();
89
89
  const viewport = useRef(null), content = useRef(null);
90
90
  const vertical = useRef(null), horizontal = useRef(null);
package/dist/select.js CHANGED
@@ -20,7 +20,7 @@ function initialChoices(children, group, groupDisabled = false, groupHidden = fa
20
20
  });
21
21
  }
22
22
  /** Internal implementation. The public entry retains the native SelectProps/ref contract. */
23
- export const SingleSelect = forwardRef(function SingleSelect({ children, value, defaultValue, onChange, onInvalid, onFocus, onBlur, onKeyDown, onKeyUp, onClick, disabled, required, id, className, style, title, autoFocus, invalid, status, controlSize = "md", "aria-label": ariaLabel, "aria-labelledby": labelledBy, "aria-describedby": describedBy, "aria-invalid": ariaInvalid, ...nativeProps }, forwardedRef) {
23
+ export const SingleSelect = /* @__PURE__ */ forwardRef(function SingleSelect({ children, value, defaultValue, onChange, onInvalid, onFocus, onBlur, onKeyDown, onKeyUp, onClick, disabled, required, id, className, style, title, autoFocus, invalid, status, controlSize = "md", "aria-label": ariaLabel, "aria-labelledby": labelledBy, "aria-describedby": describedBy, "aria-invalid": ariaInvalid, ...nativeProps }, forwardedRef) {
24
24
  const generatedId = useId();
25
25
  const triggerId = id ?? `flood-select-${generatedId}`;
26
26
  const menuId = `${triggerId}-options`, validationId = `${triggerId}-validation`;
package/dist/slider.js CHANGED
@@ -2,7 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { forwardRef, useEffect, useId, useRef, useState } from "react";
3
3
  import { hasFieldContent, mergeDescriptions, resolveFieldStatus } from "./field-state.js";
4
4
  /** Native range inputs keep keyboard/AT interaction; the shared rail handles pointer selection. */
5
- export const RangeControl = forwardRef(function RangeControl({ fieldsetComponent: Group, label, value, defaultValue = [50], onValueChange, min = 0, max = 100, step = 1, thumbLabels, showValue = true, readOnly = false, controlSize = "md", disabled, className, name, onKeyDown, ...props }, ref) {
5
+ export const RangeControl = /* @__PURE__ */ forwardRef(function RangeControl({ fieldsetComponent: Group, label, value, defaultValue = [50], onValueChange, min = 0, max = 100, step = 1, thumbLabels, showValue = true, readOnly = false, controlSize = "md", disabled, className, name, onKeyDown, ...props }, ref) {
6
6
  if (![min, max, step, max - min, (max - min) / step].every(Number.isFinite) || min >= max || step <= 0)
7
7
  throw new Error("Flood Slider requires finite increasing bounds and a positive representable step");
8
8
  if (!label.trim())
@@ -0,0 +1,3 @@
1
+ import { type TagInputProps } from "./index.js";
2
+ /** Free text tokens. Known-option IDs and suggestion layers belong to MultiSelect. */
3
+ export declare const FreeTagEntry: import("react").ForwardRefExoticComponent<TagInputProps & import("react").RefAttributes<HTMLInputElement>>;
@@ -0,0 +1,181 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { forwardRef, useEffect, useId, useImperativeHandle, useLayoutEffect, useRef, useState } from "react";
3
+ import { Input, Label, Tag } from "./index.js";
4
+ import { mergeDescriptions, resolveFieldStatus } from "./field-state.js";
5
+ const useBrowserLayoutEffect = typeof window === "undefined" ? useEffect : useLayoutEffect;
6
+ const normalize = (values) => [...new Set(values.map(value => value.trim()).filter(Boolean))];
7
+ const same = (a, b) => a.length === b.length && a.every((value, index) => value === b[index]);
8
+ /** Free text tokens. Known-option IDs and suggestion layers belong to MultiSelect. */
9
+ export const FreeTagEntry = /* @__PURE__ */ forwardRef(function FreeTagEntry({ label, hideLabel = false, value, defaultValue = [], onValueChange, delimiters = [","], removeLabel = "Remove", requiredMessage = "Add at least one tag.", controlSize = "md", disabled, readOnly, required, name, form, id: explicitId, className, style, onKeyDown, onPaste, onInvalid, onCompositionStart, onCompositionEnd, ...props }, forwardedRef) {
10
+ if (!label.trim() || !removeLabel.trim() || !requiredMessage.trim())
11
+ throw new Error("Flood TagInput requires nonempty accessible labels and validation message");
12
+ if (delimiters.some(delimiter => [...delimiter].length !== 1 || delimiter === "\n" || delimiter === "\r"))
13
+ throw new Error("Flood TagInput delimiters must be single characters other than newlines");
14
+ for (const values of [value, defaultValue])
15
+ if (values !== undefined && (!Array.isArray(values) || values.some(item => typeof item !== "string")))
16
+ throw new Error("Flood TagInput values must be arrays of strings");
17
+ const generated = useId(), id = explicitId ?? `flood-tag-input-${generated}`;
18
+ const input = useRef(null), root = useRef(null), composing = useRef(false);
19
+ useImperativeHandle(forwardedRef, () => input.current, []);
20
+ const initial = useRef(normalize(defaultValue));
21
+ const [local, setLocal] = useState(initial.current), [draft, setDraft] = useState(""), [validation, setValidation] = useState("");
22
+ const tags = value === undefined ? local : normalize(value);
23
+ const controlled = useRef(value !== undefined);
24
+ controlled.current = value !== undefined;
25
+ const pending = useRef(undefined);
26
+ const blocked = () => Boolean(disabled || readOnly || input.current?.matches(":disabled"));
27
+ const publish = (next) => {
28
+ if (same(tags, next))
29
+ return;
30
+ if (value === undefined)
31
+ setLocal(next);
32
+ onValueChange?.([...next]);
33
+ setValidation("");
34
+ };
35
+ const add = (parts, remaining = "", source = draft) => {
36
+ const next = normalize([...tags, ...parts]);
37
+ if (same(tags, next)) {
38
+ pending.current = undefined;
39
+ setDraft(remaining);
40
+ return;
41
+ }
42
+ if (value === undefined)
43
+ setDraft(remaining);
44
+ else {
45
+ pending.current = { tags: next, draft: source, remaining };
46
+ setDraft(source);
47
+ }
48
+ publish(next);
49
+ };
50
+ useBrowserLayoutEffect(() => {
51
+ const request = pending.current;
52
+ if (request && request.tags.every(tag => tags.includes(tag))) {
53
+ pending.current = undefined;
54
+ setDraft(current => current === request.draft ? request.remaining : current);
55
+ }
56
+ const control = input.current;
57
+ control?.setCustomValidity(required && tags.length === 0 ? requiredMessage : "");
58
+ if (validation && control?.validity.valid)
59
+ setValidation("");
60
+ });
61
+ useEffect(() => {
62
+ const owner = input.current?.form;
63
+ if (!owner)
64
+ return;
65
+ const timers = new Set();
66
+ const reset = (event) => {
67
+ // React's delegated onReset may cancel after this native listener returns.
68
+ const timer = setTimeout(() => {
69
+ timers.delete(timer);
70
+ if (event.defaultPrevented || !input.current)
71
+ return;
72
+ if (!controlled.current)
73
+ setLocal([...initial.current]);
74
+ pending.current = undefined;
75
+ setDraft("");
76
+ setValidation("");
77
+ }, 0);
78
+ timers.add(timer);
79
+ };
80
+ owner.addEventListener("reset", reset);
81
+ return () => { owner.removeEventListener("reset", reset); timers.forEach(clearTimeout); };
82
+ }, [form]);
83
+ const remove = (tag) => {
84
+ if (blocked())
85
+ return;
86
+ // Removal is a separate edit, not acceptance of a pending addition.
87
+ pending.current = undefined;
88
+ publish(tags.filter(value => value !== tag));
89
+ input.current?.focus({ preventScroll: true });
90
+ };
91
+ const split = (text, includeNewlines = false) => {
92
+ const separators = new Set([...delimiters, ...(includeNewlines ? ["\n", "\r"] : [])]);
93
+ const parts = [""];
94
+ for (const character of text) {
95
+ if (separators.has(character))
96
+ parts.push("");
97
+ else
98
+ parts[parts.length - 1] += character;
99
+ }
100
+ return parts;
101
+ };
102
+ const removeButtons = () => [...root.current?.querySelectorAll(".flood-tag__remove:not(:disabled)") ?? []];
103
+ const rtl = () => input.current?.ownerDocument.defaultView?.getComputedStyle(input.current).direction === "rtl";
104
+ return _jsxs("div", { ref: root, className: "flood-tag-input", hidden: props.hidden, dir: props.dir, lang: props.lang, children: [_jsx(Label, { htmlFor: id, required: required, className: hideLabel ? "flood-visually-hidden" : undefined, children: label }), _jsxs("div", { className: ["flood-input-group", "flood-tag-input__control", `flood-control--${controlSize}`, className].filter(Boolean).join(" "), style: style, "data-disabled": disabled || undefined, "data-readonly": readOnly || undefined, "data-status": resolveFieldStatus(props.status, props.invalid || Boolean(validation), props["aria-invalid"]), onClick: event => { if (!blocked() && !event.target.closest("button,input"))
105
+ input.current?.focus(); }, children: [tags.map(tag => _jsx(Tag, { size: controlSize === "sm" ? "sm" : controlSize === "md" ? "md" : "lg", disabled: disabled, onRemove: readOnly ? undefined : () => remove(tag), removeLabel: `${removeLabel} ${tag}`, removeButtonProps: { tabIndex: -1, onKeyDown: event => {
106
+ if (event.defaultPrevented || event.altKey || event.ctrlKey || event.metaKey || event.nativeEvent.isComposing || blocked())
107
+ return;
108
+ const buttons = removeButtons(), index = buttons.indexOf(event.currentTarget);
109
+ if (event.key === "Backspace" || event.key === "Delete") {
110
+ event.preventDefault();
111
+ remove(tag);
112
+ }
113
+ else if (event.key === "Escape") {
114
+ event.preventDefault();
115
+ event.stopPropagation();
116
+ input.current?.focus();
117
+ }
118
+ else if (event.key === (rtl() ? "ArrowLeft" : "ArrowRight")) {
119
+ event.preventDefault();
120
+ (buttons[index + 1] ?? input.current)?.focus();
121
+ }
122
+ else if (event.key === (rtl() ? "ArrowRight" : "ArrowLeft")) {
123
+ event.preventDefault();
124
+ buttons[Math.max(0, index - 1)]?.focus();
125
+ }
126
+ } }, children: tag }, tag)), _jsx(Input, { ...props, ref: input, id: id, type: "text", form: form, value: draft, controlSize: controlSize, disabled: disabled, readOnly: readOnly, required: required && tags.length === 0, "aria-required": required || undefined, "aria-describedby": mergeDescriptions(props["aria-describedby"], tags.length > 0 && `${id}-selection`, Boolean(validation) && `${id}-validation`), invalid: props.invalid || Boolean(validation), onChange: event => {
127
+ if (blocked())
128
+ return;
129
+ pending.current = undefined;
130
+ const text = event.currentTarget.value;
131
+ if (composing.current || ("isComposing" in event.nativeEvent && event.nativeEvent.isComposing)) {
132
+ setDraft(text);
133
+ return;
134
+ }
135
+ const parts = split(text);
136
+ if (parts.length > 1)
137
+ add(parts.slice(0, -1), parts.at(-1), text);
138
+ else
139
+ setDraft(text);
140
+ }, onCompositionStart: event => { composing.current = true; onCompositionStart?.(event); }, onCompositionEnd: event => { composing.current = false; onCompositionEnd?.(event); }, onInvalid: event => { onInvalid?.(event); if (!event.defaultPrevented)
141
+ setValidation(event.currentTarget.validationMessage); }, onKeyDown: event => {
142
+ onKeyDown?.(event);
143
+ if (event.defaultPrevented || event.altKey || event.ctrlKey || event.metaKey || event.nativeEvent.isComposing || composing.current)
144
+ return;
145
+ if (readOnly && event.key === "Backspace") {
146
+ event.preventDefault();
147
+ return;
148
+ }
149
+ if (blocked())
150
+ return;
151
+ if (event.key === "Enter" && draft.trim()) {
152
+ event.preventDefault();
153
+ add([draft]);
154
+ }
155
+ else if (delimiters.includes(event.key)) {
156
+ event.preventDefault();
157
+ add([draft]);
158
+ }
159
+ else if (event.key === "Backspace" && !draft && tags.length && !event.shiftKey) {
160
+ event.preventDefault();
161
+ remove(tags.at(-1));
162
+ }
163
+ else if (event.key === (rtl() ? "ArrowRight" : "ArrowLeft") && !event.shiftKey && event.currentTarget.selectionStart === 0 && event.currentTarget.selectionEnd === 0) {
164
+ const last = removeButtons().at(-1);
165
+ if (last) {
166
+ event.preventDefault();
167
+ last.focus();
168
+ }
169
+ }
170
+ }, onPaste: event => {
171
+ onPaste?.(event);
172
+ if (event.defaultPrevented || blocked() || composing.current)
173
+ return;
174
+ const pasted = event.clipboardData.getData("text/plain");
175
+ if (split(pasted, true).length === 1)
176
+ return;
177
+ event.preventDefault();
178
+ const node = event.currentTarget, text = draft.slice(0, node.selectionStart ?? draft.length) + pasted + draft.slice(node.selectionEnd ?? draft.length);
179
+ add(split(text, true), "", text);
180
+ } })] }), name && tags.map(tag => _jsx("input", { type: "hidden", name: name, value: tag, form: form, disabled: disabled }, tag)), tags.length > 0 && _jsx("span", { id: `${id}-selection`, className: "flood-visually-hidden", children: tags.join(", ") }), validation && _jsx("span", { id: `${id}-validation`, role: "alert", className: "flood-error", children: validation })] });
181
+ });
@@ -1,7 +1,7 @@
1
1
  import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { forwardRef, useEffect, useImperativeHandle, useRef, useState } from "react";
3
3
  const glyph = (kind, visible = false) => _jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.75", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: kind === "search" ? _jsxs(_Fragment, { children: [_jsx("circle", { cx: "10.5", cy: "10.5", r: "6.5" }), _jsx("path", { d: "m16 16 4 4" })] }) : kind === "clear" ? _jsx("path", { d: "m6 6 12 12M18 6 6 18" }) : _jsxs(_Fragment, { children: [_jsx("path", { d: "M2 12s3.5-7 10-7 10 7 10 7-3.5 7-10 7S2 12 2 12Z" }), _jsx("circle", { cx: "12", cy: "12", r: "3" }), visible && _jsx("path", { d: "m3 3 18 18" })] }) });
4
- export const SearchField = forwardRef(function SearchField({ group: Group, action: Action, icon: Icon, clearable = true, clearLabel = "Clear search", controlSize = "md", className, value, defaultValue, onChange, disabled, readOnly, ...props }, ref) {
4
+ export const SearchField = /* @__PURE__ */ forwardRef(function SearchField({ group: Group, action: Action, icon: Icon, clearable = true, clearLabel = "Clear search", controlSize = "md", className, value, defaultValue, onChange, disabled, readOnly, ...props }, ref) {
5
5
  const input = useRef(null);
6
6
  useImperativeHandle(ref, () => input.current, []);
7
7
  const [text, setText] = useState(String(defaultValue ?? ""));
@@ -26,7 +26,7 @@ export const SearchField = forwardRef(function SearchField({ group: Group, actio
26
26
  };
27
27
  return _jsx(Group, { ...props, ref: input, type: "search", controlSize: controlSize, className: ["flood-search-input", className].filter(Boolean).join(" "), value: value, defaultValue: defaultValue, disabled: disabled, readOnly: readOnly, onChange: event => { setText(event.currentTarget.value); onChange?.(event); }, start: _jsx(Icon, { size: controlSize, children: glyph("search") }), end: clearable && current.length > 0 && !readOnly ? _jsx(Action, { label: clearLabel, variant: "ghost", size: controlSize, disabled: disabled, onPointerDown: event => event.preventDefault(), onClick: clear, children: glyph("clear") }) : undefined });
28
28
  });
29
- export const PasswordField = forwardRef(function PasswordField({ group: Group, action: Action, icon: _Icon, visible, defaultVisible = false, onVisibleChange, showPasswordLabel = "Show password", hidePasswordLabel = "Hide password", controlSize = "md", className, disabled, ...props }, ref) {
29
+ export const PasswordField = /* @__PURE__ */ forwardRef(function PasswordField({ group: Group, action: Action, icon: _Icon, visible, defaultVisible = false, onVisibleChange, showPasswordLabel = "Show password", hidePasswordLabel = "Hide password", controlSize = "md", className, disabled, ...props }, ref) {
30
30
  const input = useRef(null);
31
31
  useImperativeHandle(ref, () => input.current, []);
32
32
  const [local, setLocal] = useState(defaultVisible);
@@ -0,0 +1,2 @@
1
+ import { type TimeFieldProps } from "./index.js";
2
+ export declare const SegmentedTimeField: import("react").ForwardRefExoticComponent<TimeFieldProps & import("react").RefAttributes<HTMLDivElement>>;