@flood-md/react 0.2.10 → 0.2.11

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 +239 -1
  2. package/dist/app-shell.d.ts +3 -0
  3. package/dist/app-shell.js +66 -0
  4. package/dist/bulk-action-bar.d.ts +3 -0
  5. package/dist/bulk-action-bar.js +17 -0
  6. package/dist/collection-toolbar.d.ts +3 -0
  7. package/dist/collection-toolbar.js +26 -0
  8. package/dist/collection-view-switcher.d.ts +3 -0
  9. package/dist/collection-view-switcher.js +19 -0
  10. package/dist/combobox.d.ts +6 -1
  11. package/dist/combobox.js +77 -29
  12. package/dist/command-list.d.ts +5 -1
  13. package/dist/command-list.js +2 -2
  14. package/dist/data-display.js +1 -1
  15. package/dist/data-table.js +44 -9
  16. package/dist/date-picker.js +2 -2
  17. package/dist/date-range-picker.js +2 -2
  18. package/dist/date-time-picker.js +2 -2
  19. package/dist/date-time-range-picker.js +2 -2
  20. package/dist/disclosure.d.ts +2 -1
  21. package/dist/disclosure.js +4 -4
  22. package/dist/filter-condition.d.ts +3 -0
  23. package/dist/filter-condition.js +21 -0
  24. package/dist/filter-group.d.ts +2 -0
  25. package/dist/filter-group.js +62 -0
  26. package/dist/foundations.css +752 -76
  27. package/dist/index.d.ts +399 -3
  28. package/dist/index.js +61 -7
  29. package/dist/menu.js +5 -4
  30. package/dist/menubar.d.ts +2 -0
  31. package/dist/menubar.js +85 -0
  32. package/dist/navigation-group.d.ts +3 -0
  33. package/dist/navigation-group.js +13 -0
  34. package/dist/navigation-item.d.ts +3 -0
  35. package/dist/navigation-item.js +68 -0
  36. package/dist/navigation-layout.d.ts +2 -1
  37. package/dist/navigation-layout.js +12 -4
  38. package/dist/navigation-tree.js +51 -7
  39. package/dist/navigation.js +2 -2
  40. package/dist/overflow-actions.d.ts +2 -0
  41. package/dist/overflow-actions.js +75 -0
  42. package/dist/overlay-runtime.d.ts +3 -0
  43. package/dist/overlay-runtime.js +35 -1
  44. package/dist/overlays.js +32 -16
  45. package/dist/select.js +4 -1
  46. package/dist/sidebar.d.ts +2 -0
  47. package/dist/sidebar.js +58 -0
  48. package/dist/toolbar.js +2 -2
  49. package/package.json +2 -2
package/dist/combobox.js CHANGED
@@ -1,39 +1,68 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { forwardRef, useEffect, useId, useImperativeHandle, useLayoutEffect, useRef, useState } from "react";
3
- import { Input, InputGroup, IconButton, ScrollArea, Tag } from "./index.js";
3
+ import { Input, InputGroup, IconButton, ScrollArea, Tag, Button } from "./index.js";
4
4
  import { mergeDescriptions, resolveFieldStatus } from "./field-state.js";
5
5
  import { positionLayer, registerLayer } from "./overlay-runtime.js";
6
6
  const useBrowserLayoutEffect = typeof window === "undefined" ? useEffect : useLayoutEffect;
7
7
  const searchable = (text) => text.normalize("NFKD").replace(/\p{M}/gu, "").toLowerCase();
8
8
  /** Shared search/layer model. Public single and multiple value contracts stay distinct. */
9
- export const SearchSelection = /* @__PURE__ */ forwardRef(function SearchSelection({ options, selectedValues: value, initialValues: defaultValue, onSelectionChange: onValueChange, multiple = false, removeItemLabel = "Remove", clearable = false, emptyMessage = "No results found.", listLabel = "Options", toggleLabel = "Show options", clearLabel = "Clear selection", invalidSelectionMessage = "Select an option.", controlSize = "md", readOnly, disabled, required, name, id: explicitId, className, onKeyDown, onBlur, onClick, onInvalid, autoComplete = "off", ...props }, forwardedRef) {
9
+ export const SearchSelection = /* @__PURE__ */ forwardRef(function SearchSelection({ options, selectedOptions = [], query: controlledQuery, onQueryChange, filtering = "local", optionsQuery, loadState = "ready", loadingMessage = "Loading options…", errorMessage = "Could not load options.", onRetry, retryLabel = "Try again", freeText = false, textValue, initialText = "", onTextChange, onOptionSelect, selectedValues: value, initialValues: defaultValue, onSelectionChange: onValueChange, multiple = false, removeItemLabel = "Remove", clearable = false, emptyMessage = "No results found.", listLabel = "Options", toggleLabel = "Show options", clearLabel = "Clear selection", invalidSelectionMessage = "Select an option.", controlSize = "md", readOnly, disabled, required, name, id: explicitId, className, onKeyDown, onBlur, onClick, onInvalid, autoComplete = "off", ...props }, forwardedRef) {
10
10
  if (new Set(options.map(option => option.value)).size !== options.length || options.some(option => !option.value || !option.label.trim()))
11
11
  throw new Error("Flood Combobox options need unique nonempty values and labels");
12
- if (![emptyMessage, listLabel, toggleLabel, clearLabel, invalidSelectionMessage].every(text => text.trim()))
12
+ if (![emptyMessage, listLabel, toggleLabel, clearLabel, invalidSelectionMessage, loadingMessage, errorMessage, retryLabel].every(text => text.trim()))
13
13
  throw new Error("Flood Combobox messages and action labels must be nonempty");
14
+ if (new Set(selectedOptions.map(option => option.value)).size !== selectedOptions.length || selectedOptions.some(option => !option.value || !option.label.trim()))
15
+ throw new Error("Flood selectedOptions requires unique named records");
14
16
  for (const values of [defaultValue, value])
15
17
  if (values !== undefined && (!Array.isArray(values) || new Set(values).size !== values.length || values.some(item => typeof item !== "string" || !item)))
16
18
  throw new Error("Flood selection values must be unique nonempty strings");
17
19
  if (multiple && !removeItemLabel.trim())
18
20
  throw new Error("Flood MultiSelect requires an accessible removal label");
21
+ if (filtering === "manual" && ((freeText ? textValue : controlledQuery) === undefined || optionsQuery === undefined))
22
+ throw new Error("Flood manual search requires controlled text/query and optionsQuery");
19
23
  const generatedId = useId(), id = explicitId ?? `flood-combobox-${generatedId}`;
20
24
  const menuId = `${id}-options`, validationId = `${id}-validation`;
21
25
  const input = useRef(null), root = useRef(null), panel = useRef(null), viewport = useRef(null);
22
26
  useImperativeHandle(forwardedRef, () => input.current, []);
23
- const initial = useRef([...defaultValue]);
24
- const [local, setLocal] = useState(() => [...defaultValue]), [query, setQuery] = useState(null);
27
+ const initial = useRef([...defaultValue]), initialFreeText = useRef(initialText);
28
+ const [local, setLocal] = useState(() => [...defaultValue]), [draftQuery, setDraftQuery] = useState(null);
29
+ const [localText, setLocalText] = useState(initialText);
30
+ const text = textValue ?? localText;
31
+ const query = freeText ? text : draftQuery === null ? null : controlledQuery ?? draftQuery;
32
+ const setQuery = (next) => { setDraftQuery(next); if (!freeText && next !== null)
33
+ onQueryChange?.(next); };
34
+ const publishText = (next) => { if (textValue === undefined)
35
+ setLocalText(next); if (next !== text)
36
+ onTextChange?.(next); };
25
37
  const [open, setOpen] = useState(false), [activeValue, setActiveValue] = useState();
26
38
  const [validation, setValidation] = useState("");
27
- const selections = (value ?? local).map(id => options.find(option => option.value === id)).filter((option) => Boolean(option));
39
+ const selections = (value ?? local).map(id => selectedOptions.find(option => option.value === id) ?? options.find(option => option.value === id)).filter((option) => Boolean(option));
28
40
  const selected = selections[0], selectedIds = selections.map(option => option.value);
29
- const displayed = query ?? (multiple ? "" : selected?.label ?? "");
30
- const filtered = query === null ? options : options.filter(option => searchable([option.label, ...(option.keywords ?? [])].join(" ")).includes(searchable(query)));
41
+ const displayed = freeText ? text : query ?? (multiple ? "" : selected?.label ?? "");
42
+ const resultQuery = freeText ? text : controlledQuery ?? draftQuery ?? "";
43
+ const pending = loadState === "loading" || (filtering === "manual" && optionsQuery !== resultQuery);
44
+ const failed = loadState === "error" && !pending;
45
+ const resultMessage = pending ? loadingMessage : failed ? errorMessage : emptyMessage;
46
+ const filtered = pending || failed ? [] : filtering === "manual" || query === null ? options : options.filter(option => searchable([option.label, ...(option.keywords ?? [])].join(" ")).includes(searchable(query)));
31
47
  const enabled = filtered.filter(option => !option.disabled);
48
+ const groups = [];
49
+ for (const option of filtered) {
50
+ const previous = groups.at(-1);
51
+ if (previous && previous.label === option.group)
52
+ previous.options.push(option);
53
+ else
54
+ groups.push({ label: option.group, options: [option] });
55
+ }
32
56
  const active = enabled.find(option => option.value === activeValue) ?? enabled[0];
33
57
  const optionId = (optionValue) => `${menuId}-${options.findIndex(option => option.value === optionValue)}`;
34
- const current = useRef({ value, selected, readOnly, disabled });
35
- current.current = { value, selected, readOnly, disabled };
36
- const close = () => { setOpen(false); setQuery(null); };
58
+ const current = useRef({ value, textValue, selected, readOnly, disabled });
59
+ current.current = { value, textValue, selected, readOnly, disabled };
60
+ const close = () => {
61
+ if (panel.current?.contains(panel.current.ownerDocument.activeElement))
62
+ input.current?.focus({ preventScroll: true });
63
+ setOpen(false);
64
+ setQuery(null);
65
+ };
37
66
  const inert = () => Boolean(readOnly || disabled || input.current?.matches(":disabled"));
38
67
  const publish = (next) => {
39
68
  if (value === undefined)
@@ -43,14 +72,23 @@ export const SearchSelection = /* @__PURE__ */ forwardRef(function SearchSelecti
43
72
  setValidation("");
44
73
  };
45
74
  const remove = (id) => {
46
- if (inert() || options.find(option => option.value === id)?.disabled)
75
+ if (inert() || selections.find(option => option.value === id)?.disabled)
47
76
  return;
48
77
  publish(selectedIds.filter(value => value !== id));
49
78
  input.current?.focus({ preventScroll: true });
50
79
  };
51
80
  const select = (next) => {
52
- if (inert() || (next !== "" && !options.some(option => option.value === next && !option.disabled)))
81
+ if (inert() || (next !== "" && !filtered.some(option => option.value === next && !option.disabled)))
53
82
  return;
83
+ if (freeText) {
84
+ const option = filtered.find(option => option.value === next);
85
+ publishText(option?.label ?? "");
86
+ if (option)
87
+ onOptionSelect?.(option);
88
+ close();
89
+ input.current?.focus({ preventScroll: true });
90
+ return;
91
+ }
54
92
  if (multiple) {
55
93
  publish(next === "" ? selections.filter(option => option.disabled).map(option => option.value) : selectedIds.includes(next) ? selectedIds.filter(id => id !== next) : [...selectedIds, next]);
56
94
  if (next) {
@@ -75,7 +113,7 @@ export const SearchSelection = /* @__PURE__ */ forwardRef(function SearchSelecti
75
113
  };
76
114
  useBrowserLayoutEffect(() => {
77
115
  const control = input.current;
78
- control?.setCustomValidity(required && !selections.length ? invalidSelectionMessage : "");
116
+ control?.setCustomValidity(!freeText && required && !selections.length ? invalidSelectionMessage : "");
79
117
  if (validation && control?.validity.valid)
80
118
  setValidation("");
81
119
  });
@@ -92,6 +130,8 @@ export const SearchSelection = /* @__PURE__ */ forwardRef(function SearchSelecti
92
130
  return;
93
131
  if (current.current.value === undefined)
94
132
  setLocal(initial.current);
133
+ if (current.current.textValue === undefined)
134
+ setLocalText(initialFreeText.current);
95
135
  setQuery(null);
96
136
  setOpen(false);
97
137
  setValidation("");
@@ -167,7 +207,7 @@ export const SearchSelection = /* @__PURE__ */ forwardRef(function SearchSelecti
167
207
  popup.style.visibility = "hidden";
168
208
  popup.showPopover();
169
209
  update();
170
- unregister = registerLayer(document, { panel: popup, anchor: () => anchor, close: () => { setOpen(false); setQuery(null); } });
210
+ unregister = registerLayer(document, { panel: popup, anchor: () => anchor, close });
171
211
  };
172
212
  const toggle = (event) => { if (alive && event.newState === "closed" && !popup.matches(":popover-open")) {
173
213
  setOpen(false);
@@ -192,7 +232,8 @@ export const SearchSelection = /* @__PURE__ */ forwardRef(function SearchSelecti
192
232
  }, [open, active?.value, query, options]);
193
233
  const glyph = (clear) => _jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.75", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: clear ? _jsx("path", { d: "m6 6 12 12M18 6 6 18" }) : _jsx("path", { d: "m6 9 6 6 6-6" }) });
194
234
  const inputProps = {
195
- ...props, id, type: "text", value: displayed, disabled, readOnly, required: required && (!multiple || !selections.length), controlSize, autoComplete,
235
+ ...props, id, type: "text", value: displayed, placeholder: multiple && selections.length ? undefined : props.placeholder,
236
+ name: freeText ? name : undefined, disabled, readOnly, required: required && (!multiple || !selections.length), controlSize, autoComplete,
196
237
  role: "combobox", "aria-autocomplete": "list", "aria-haspopup": "listbox", "aria-expanded": open, "aria-controls": open ? menuId : undefined,
197
238
  "aria-required": required || undefined, "aria-activedescendant": open && active ? optionId(active.value) : undefined,
198
239
  "aria-describedby": mergeDescriptions(props["aria-describedby"], validation && validationId, multiple && Boolean(selections.length) && `${id}-selection`),
@@ -201,11 +242,14 @@ export const SearchSelection = /* @__PURE__ */ forwardRef(function SearchSelecti
201
242
  if (inert())
202
243
  return;
203
244
  const text = event.currentTarget.value;
204
- setQuery(text);
245
+ if (freeText)
246
+ publishText(text);
247
+ else
248
+ setQuery(text);
205
249
  setActiveValue(undefined);
206
250
  setOpen(true);
207
251
  setValidation("");
208
- if (!multiple && text === "" && selected)
252
+ if (!freeText && !multiple && text === "" && selected)
209
253
  publish([]);
210
254
  },
211
255
  onClick: event => { onClick?.(event); if (!event.defaultPrevented)
@@ -255,25 +299,28 @@ export const SearchSelection = /* @__PURE__ */ forwardRef(function SearchSelecti
255
299
  }
256
300
  }
257
301
  else if (event.key === "Enter" && open) {
258
- event.preventDefault();
302
+ if (!freeText || active)
303
+ event.preventDefault();
259
304
  if (active)
260
305
  select(active.value);
306
+ else
307
+ close();
261
308
  }
262
309
  else if (event.key === "Escape" && open) {
263
310
  event.preventDefault();
264
311
  event.stopPropagation();
265
312
  close();
266
313
  }
267
- else if (event.key === "Tab")
314
+ else if (event.key === "Tab" && !(failed && onRetry && !event.shiftKey))
268
315
  close();
269
316
  },
270
317
  };
271
318
  const removable = selections.some(option => !option.disabled);
272
- const actions = _jsxs("span", { className: "flood-combobox__actions", children: [clearable && _jsx(IconButton, { label: clearLabel, variant: "ghost", size: controlSize, className: !selected ? "flood-combobox__clear-empty" : undefined, disabled: disabled || readOnly || !selected || (multiple && !removable), "aria-hidden": !selected || undefined, tabIndex: -1, onPointerDown: event => event.preventDefault(), onClick: () => select(""), children: glyph(true) }), _jsx(IconButton, { label: toggleLabel, variant: "ghost", size: controlSize, disabled: disabled || readOnly, tabIndex: -1, "aria-expanded": open, "aria-controls": open ? menuId : undefined, onPointerDown: event => event.preventDefault(), onClick: () => { if (open)
319
+ const actions = _jsxs("span", { className: "flood-combobox__actions", children: [clearable && _jsx(IconButton, { label: clearLabel, variant: "ghost", size: controlSize, className: !(freeText ? text : selected) ? "flood-combobox__clear-empty" : undefined, disabled: disabled || readOnly || !(freeText ? text : selected) || (multiple && !removable), "aria-hidden": !(freeText ? text : selected) || undefined, tabIndex: -1, onPointerDown: event => event.preventDefault(), onClick: () => select(""), children: glyph(true) }), _jsx(IconButton, { label: toggleLabel, variant: "ghost", size: controlSize, disabled: disabled || readOnly, tabIndex: -1, "aria-expanded": open, "aria-controls": open ? menuId : undefined, onPointerDown: event => event.preventDefault(), onClick: () => { if (open)
273
320
  close();
274
321
  else
275
322
  show(); input.current?.focus({ preventScroll: true }); }, children: glyph(false) })] });
276
- return _jsxs("div", { className: multiple ? "flood-combobox flood-multi-select" : "flood-combobox", ref: root, hidden: props.hidden, dir: props.dir, lang: props.lang, children: [multiple ? _jsxs("div", { className: ["flood-input-group", "flood-multi-select__control", `flood-control--${controlSize}`, className].filter(Boolean).join(" "), style: props.style, "data-disabled": disabled || undefined, "data-readonly": readOnly || undefined, "data-status": resolveFieldStatus(props.status, props.invalid || Boolean(validation), props["aria-invalid"]), onClick: event => { if (event.target === event.currentTarget && !inert()) {
323
+ return _jsxs("div", { className: multiple ? "flood-combobox flood-multi-select" : "flood-combobox", ref: root, hidden: props.hidden, dir: props.dir, lang: props.lang, children: [multiple ? _jsxs("div", { className: ["flood-input-group", "flood-multi-select__control", `flood-control--${controlSize}`, className].filter(Boolean).join(" "), style: props.style, "data-disabled": disabled || undefined, "data-readonly": readOnly || undefined, "data-status": resolveFieldStatus(props.status, props.invalid || Boolean(validation), props["aria-invalid"]), "data-has-selection": Boolean(selections.length) || undefined, "data-query": Boolean(displayed) || undefined, "data-clearable": clearable || undefined, onClick: event => { if (event.target === event.currentTarget && !inert()) {
277
324
  input.current?.focus();
278
325
  show();
279
326
  } }, children: [selections.map(option => _jsx(Tag, { size: controlSize === "sm" ? "sm" : controlSize === "md" ? "md" : "lg", disabled: disabled || option.disabled, onRemove: readOnly ? undefined : () => remove(option.value), removeLabel: `${removeItemLabel} ${option.label}`, removeButtonProps: { tabIndex: -1, onKeyDown: event => {
@@ -300,11 +347,12 @@ export const SearchSelection = /* @__PURE__ */ forwardRef(function SearchSelecti
300
347
  event.preventDefault();
301
348
  (buttons[Math.max(0, index - 1)])?.focus();
302
349
  }
303
- } }, children: option.label }, option.value)), _jsxs("div", { className: "flood-multi-select__editor", children: [_jsx(Input, { ...inputProps, ref: input, style: undefined }), actions] })] }) : _jsx(InputGroup, { ...inputProps, ref: input, className: ["flood-combobox__control", className].filter(Boolean).join(" "), end: actions }), (multiple ? selections : [selected]).map((option, index) => _jsx("input", { type: "hidden", name: name, value: option?.value ?? "", disabled: disabled, form: props.form }, option?.value ?? index)), multiple && Boolean(selections.length) && _jsx("span", { id: `${id}-selection`, className: "flood-visually-hidden", children: selections.map(option => option.label).join(", ") }), open && _jsx("div", { ref: panel, popover: "manual", className: "flood-select__menu flood-combobox__menu", onPointerDown: event => { if (event.pointerType === "mouse")
304
- event.preventDefault(); }, children: _jsxs(ScrollArea, { ref: viewport, id: menuId, role: "listbox", "aria-label": listLabel, "aria-multiselectable": multiple || undefined, orientation: "vertical", tabIndex: -1, className: "flood-select__viewport", children: [!filtered.length && _jsx("div", { className: "flood-select__empty", role: "option", "aria-disabled": "true", "aria-selected": "false", children: emptyMessage }), filtered.map(option => _jsxs("div", { id: optionId(option.value), role: "option", "aria-selected": selectedIds.includes(option.value), "aria-disabled": option.disabled || undefined, className: "flood-select__option", "data-active": option.value === active?.value || undefined, onPointerMove: event => { if (event.pointerType === "mouse" && !option.disabled)
305
- setActiveValue(option.value); }, onPointerDown: event => {
306
- // Touch must retain its compatibility click and native scrolling.
307
- if (event.pointerType === "mouse")
308
- event.preventDefault();
309
- }, onMouseDown: event => event.preventDefault(), onClick: () => select(option.value), children: [_jsxs("span", { className: "flood-combobox__option-text", children: [_jsx("span", { children: option.label }), option.description && _jsx("span", { className: "flood-combobox__description", children: option.description })] }), _jsx("span", { className: "flood-select__selected", "aria-hidden": "true", children: selectedIds.includes(option.value) ? "✓" : "" })] }, option.value))] }) }), open && !filtered.length && _jsx("span", { className: "flood-visually-hidden", role: "status", children: emptyMessage }), validation && _jsx("span", { id: validationId, className: "flood-select__validation", role: "alert", children: validation })] });
350
+ } }, children: option.label }, option.value)), _jsxs("div", { className: "flood-multi-select__editor", children: [_jsx(Input, { ...inputProps, ref: input, style: undefined }), actions] })] }) : _jsx(InputGroup, { ...inputProps, ref: input, className: ["flood-combobox__control", className].filter(Boolean).join(" "), end: actions }), !freeText && (multiple ? selections : [selected]).map((option, index) => _jsx("input", { type: "hidden", name: name, value: option?.value ?? "", disabled: disabled, form: props.form }, option?.value ?? index)), multiple && Boolean(selections.length) && _jsx("span", { id: `${id}-selection`, className: "flood-visually-hidden", children: selections.map(option => option.label).join(", ") }), open && _jsxs("div", { ref: panel, popover: "manual", className: "flood-select__menu flood-combobox__menu", onPointerDown: event => { if (event.pointerType === "mouse")
351
+ event.preventDefault(); }, children: [_jsxs(ScrollArea, { ref: viewport, id: menuId, role: "listbox", "aria-label": listLabel, "aria-busy": pending || undefined, "aria-multiselectable": multiple || undefined, orientation: "vertical", tabIndex: -1, className: "flood-select__viewport", children: [!filtered.length && _jsx("div", { className: "flood-select__empty", role: "option", "aria-disabled": "true", "aria-selected": "false", children: resultMessage }), groups.map((group, index) => _jsxs("div", { role: group.label ? "group" : "presentation", "aria-label": group.label, children: [group.label && _jsx("div", { className: "flood-select__group", "aria-hidden": "true", children: group.label }), group.options.map(option => _jsxs("div", { id: optionId(option.value), role: "option", "aria-selected": selectedIds.includes(option.value), "aria-disabled": option.disabled || undefined, className: "flood-select__option", "data-active": option.value === active?.value || undefined, onPointerMove: event => { if (event.pointerType === "mouse" && !option.disabled)
352
+ setActiveValue(option.value); }, onPointerDown: event => {
353
+ // Touch must retain its compatibility click and native scrolling.
354
+ if (event.pointerType === "mouse")
355
+ event.preventDefault();
356
+ }, onMouseDown: event => event.preventDefault(), onClick: () => select(option.value), children: [_jsxs("span", { className: "flood-combobox__option-text", children: [_jsx("span", { children: option.label }), option.description && _jsx("span", { className: "flood-combobox__description", children: option.description })] }), _jsx("span", { className: "flood-select__selected", "aria-hidden": "true", children: selectedIds.includes(option.value) ? "✓" : "" })] }, option.value))] }, index))] }), failed && onRetry && _jsx(Button, { variant: "subtle", size: "sm", onClick: () => { input.current?.focus({ preventScroll: true }); onRetry(); }, onBlur: event => { if (!root.current?.contains(event.relatedTarget))
357
+ close(); }, children: retryLabel })] }), open && !filtered.length && _jsx("span", { className: "flood-visually-hidden", role: "status", children: resultMessage }), validation && _jsx("span", { id: validationId, className: "flood-select__validation", role: "alert", children: validation })] });
310
358
  });
@@ -1,3 +1,7 @@
1
1
  import { type CommandListProps } from "./index.js";
2
+ interface SearchCommandListProps extends CommandListProps {
3
+ integrated?: boolean;
4
+ }
2
5
  /** Searchable action suggestions. No selected form value or modal lifecycle. */
3
- export declare const SearchCommandList: import("react").ForwardRefExoticComponent<CommandListProps & import("react").RefAttributes<HTMLInputElement>>;
6
+ export declare const SearchCommandList: import("react").ForwardRefExoticComponent<SearchCommandListProps & import("react").RefAttributes<HTMLInputElement>>;
7
+ export {};
@@ -3,7 +3,7 @@ import { forwardRef, useEffect, useId, useImperativeHandle, useRef, useState } f
3
3
  import { Input, Label, ScrollArea, Surface, Text, VisuallyHidden } from "./index.js";
4
4
  const searchable = (value) => value.normalize("NFKD").replace(/\p{M}/gu, "").toLowerCase();
5
5
  /** Searchable action suggestions. No selected form value or modal lifecycle. */
6
- export const SearchCommandList = /* @__PURE__ */ forwardRef(function SearchCommandList({ label, hideLabel = false, items, query, defaultQuery = "", onQueryChange, onAction, disabled = false, loading = false, placeholder = "Search commands…", emptyMessage = "No commands found.", loadingMessage = "Loading commands…", controlSize = "md", id: explicitId, className, onKeyDown, ...props }, forwardedRef) {
6
+ export const SearchCommandList = /* @__PURE__ */ forwardRef(function SearchCommandList({ label, hideLabel = false, items, query, defaultQuery = "", onQueryChange, onAction, disabled = false, loading = false, placeholder = "Search commands…", emptyMessage = "No commands found.", loadingMessage = "Loading commands…", controlSize = "md", integrated = false, id: explicitId, className, onKeyDown, ...props }, forwardedRef) {
7
7
  if (![label, emptyMessage, loadingMessage].every(value => value.trim()))
8
8
  throw new Error("Flood CommandList requires nonempty accessible labels and state messages");
9
9
  if (new Set(items.map(item => item.id)).size !== items.length || items.some(item => !item.id.trim() || !item.label.trim()))
@@ -84,7 +84,7 @@ export const SearchCommandList = /* @__PURE__ */ forwardRef(function SearchComma
84
84
  setActiveId(enabled[next].id);
85
85
  }, children: [_jsx(Label, { htmlFor: inputId, className: hideLabel ? "flood-visually-hidden" : undefined, children: label }), _jsx(Input, { ref: input, id: inputId, value: text, disabled: disabled, type: "text", controlSize: controlSize, placeholder: placeholder, autoComplete: "off", spellCheck: false, role: "combobox", "aria-autocomplete": "list", "aria-haspopup": "listbox", "aria-expanded": "true", "aria-controls": listId, "aria-activedescendant": active ? optionId(active) : undefined, onCompositionStart: () => { composing.current = true; }, onCompositionEnd: () => { composing.current = false; }, onChange: event => { const next = event.currentTarget.value; if (disabled || event.currentTarget.matches(":disabled"))
86
86
  return; if (query === undefined)
87
- setLocalQuery(next); onQueryChange?.(next); } }), _jsx(Surface, { padding: "1", className: "flood-command-list__surface", children: _jsx(ScrollArea, { ref: viewport, id: listId, role: "listbox", "aria-label": label, "aria-busy": loading || undefined, "aria-disabled": effectiveDisabled || undefined, tabIndex: -1, className: "flood-command-list__viewport", style: { maxHeight: "18rem" }, children: status ? _jsx("div", { role: "option", "aria-disabled": "true", "aria-selected": "false", className: "flood-command-list__empty", children: _jsx(Text, { size: "compact", tone: "subtle", children: status }) }) : [...groups].map(([group, entries], groupIndex) => {
87
+ setLocalQuery(next); onQueryChange?.(next); } }), _jsx(Surface, { elevation: integrated ? "overlay" : "default", padding: integrated ? "2" : "1", style: integrated ? { background: "transparent" } : undefined, className: "flood-command-list__surface", children: _jsx(ScrollArea, { ref: viewport, id: listId, role: "listbox", "aria-label": label, "aria-busy": loading || undefined, "aria-disabled": effectiveDisabled || undefined, tabIndex: -1, className: "flood-command-list__viewport", children: status ? _jsx("div", { role: "option", "aria-disabled": "true", "aria-selected": "false", className: "flood-command-list__empty", children: _jsx(Text, { size: "compact", tone: "subtle", children: status }) }) : [...groups].map(([group, entries], groupIndex) => {
88
88
  const content = entries.map(item => _jsxs("div", { id: optionId(item), role: "option", "aria-disabled": item.disabled || effectiveDisabled || undefined, "aria-selected": item.id === active?.id, "data-active": item.id === active?.id || undefined, className: "flood-command-list__option", onPointerMove: event => { if (event.pointerType === "mouse" && !blocked() && !item.disabled)
89
89
  setActiveId(item.id); }, onPointerDown: event => { if (event.pointerType === "mouse")
90
90
  event.preventDefault(); }, onMouseDown: event => event.preventDefault(), onClick: () => invoke(item), children: [_jsx("span", { className: "flood-command-list__label", children: item.label }), item.description && _jsx("span", { className: "flood-command-list__description", children: item.description })] }, item.id));
@@ -2,7 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { forwardRef } from "react";
3
3
  import { ScrollArea } from "./index.js";
4
4
  export const DescribedData = /* @__PURE__ */ forwardRef(function DescribedData({ items, layout, density, dividers, className, ...props }, ref) {
5
- if (items.some(item => !item.id.trim()) || new Set(items.map(item => item.id)).size !== items.length)
5
+ if (items.some(item => typeof item.id !== "string" || !item.id.trim()) || new Set(items.map(item => item.id)).size !== items.length)
6
6
  throw new Error("Flood description items require unique nonempty IDs");
7
7
  return _jsx("dl", { ...props, ref: ref, className: ["flood-description-list", className].filter(Boolean).join(" "), "data-layout": layout, "data-density": density, "data-dividers": dividers, children: items.map(item => _jsxs("div", { className: "flood-description-list__item", children: [_jsx("dt", { children: item.term }), _jsx("dd", { children: item.description })] }, item.id)) });
8
8
  });
@@ -1,24 +1,54 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { forwardRef, useMemo, useState } from "react";
3
3
  import { Table, Checkbox, Button, IconSlot, VisuallyHidden, Pagination, Text, Spinner, Alert, Stack } from "./index.js";
4
- export const ManagedDataTable = /* @__PURE__ */ forwardRef(function ManagedDataTable({ caption, hideCaption = false, columns, rows, density = "comfortable", striped = false, minWidth = "40rem", sort: controlledSort, defaultSort = null, onSortChange, locale = "en", selectable = false, selectedIds, defaultSelectedIds = [], onSelectionChange, page: controlledPage, defaultPage = 1, pageSize = 10, onPageChange, state = "ready", onRetry, labels = {}, className, ...props }, ref) {
4
+ function activatePrimaryRowAction(event) {
5
+ const target = event.target;
6
+ const row = event.currentTarget;
7
+ if (!(target instanceof Element) || target.closest("tr") !== row || event.defaultPrevented ||
8
+ event.altKey || event.ctrlKey || event.metaKey || event.shiftKey)
9
+ return;
10
+ const control = target.closest('button,a,input,label,select,textarea,summary,[role],[tabindex],[contenteditable]:not([contenteditable="false"])');
11
+ if (control && row.contains(control))
12
+ return;
13
+ const selection = row.ownerDocument.getSelection();
14
+ if (selection && !selection.isCollapsed && selection.containsNode(row, true))
15
+ return;
16
+ const button = row.querySelector(":scope > td > .flood-stack > .flood-data-table__primary-action");
17
+ if (!button || button.disabled || button.getAttribute("aria-disabled") === "true")
18
+ return;
19
+ button.focus({ preventScroll: true });
20
+ button.click();
21
+ }
22
+ export const ManagedDataTable = /* @__PURE__ */ forwardRef(function ManagedDataTable({ caption, hideCaption = false, columns, rows, density = "comfortable", striped = false, minWidth = "40rem", toolbar, inset = "standard", sort: controlledSort, defaultSort = null, onSortChange, locale = "en", selectable = false, selectedIds, defaultSelectedIds = [], onSelectionChange, page: controlledPage, defaultPage = 1, pageSize = 10, onPageChange, showResults = true, paginationMode = "client", totalRows, selectionScope, state = "ready", onRetry, emptyContent, labels = {}, className, ...props }, ref) {
5
23
  if (!caption.trim() || columns.length === 0 || columns.some(column => !column.id.trim() || !column.label.trim()) || new Set(columns.map(column => column.id)).size !== columns.length)
6
24
  throw new Error("Flood DataTable requires a caption and unique named columns");
7
25
  if (rows.some(row => !row.id.trim() || (row.label !== undefined && !row.label.trim())) || new Set(rows.map(row => row.id)).size !== rows.length)
8
26
  throw new Error("Flood DataTable rows require unique nonempty IDs and labels");
27
+ if (rows.some(({ primaryAction }) => primaryAction && (typeof primaryAction.title !== "string" || !primaryAction.title.trim() ||
28
+ !columns.some(column => column.id === primaryAction.column))))
29
+ throw new Error("Flood DataTable primary actions require a nonempty title and an existing column");
9
30
  if (!Number.isSafeInteger(pageSize) || pageSize < 1 || !Number.isSafeInteger(defaultPage) || defaultPage < 1 || (controlledPage !== undefined && (!Number.isSafeInteger(controlledPage) || controlledPage < 1)))
10
31
  throw new Error("Flood DataTable page and pageSize must be positive safe integers");
32
+ const server = paginationMode === "server", scope = selectionScope ?? (server ? "page" : "dataset");
33
+ if (server && (controlledPage === undefined || !onPageChange || !Number.isSafeInteger(totalRows) || (totalRows ?? -1) < 0))
34
+ throw new Error("Flood server DataTable requires page, onPageChange and a nonnegative totalRows");
35
+ if (server && columns.some(column => column.sortable) && (controlledSort === undefined || !onSortChange))
36
+ throw new Error("Flood server sorting requires sort (or null) and onSortChange");
37
+ if (server && selectable && scope === "dataset" && (selectedIds === undefined || !onSelectionChange))
38
+ throw new Error("Flood server dataset selection must be controlled");
11
39
  const [localSort, setSort] = useState(defaultSort), [localSelected, setSelected] = useState(defaultSelectedIds), [localPage, setPage] = useState(defaultPage);
12
40
  const requestedSort = controlledSort === undefined ? localSort : controlledSort;
13
41
  const sorting = requestedSort && columns.some(column => column.id === requestedSort.column && column.sortable) ? requestedSort : null;
14
42
  const sorted = useMemo(() => {
15
- if (!sorting)
43
+ if (server || !sorting)
16
44
  return [...rows];
17
45
  const collator = new Intl.Collator(locale, { numeric: true, sensitivity: "base" });
18
46
  const value = (row) => {
19
47
  const explicit = row.sortValues?.[sorting.column];
20
48
  if (explicit !== undefined)
21
49
  return explicit;
50
+ if (row.primaryAction?.column === sorting.column)
51
+ return row.primaryAction.title;
22
52
  const cell = row.cells[sorting.column];
23
53
  return typeof cell === "string" || typeof cell === "number" ? cell : null;
24
54
  };
@@ -30,14 +60,17 @@ export const ManagedDataTable = /* @__PURE__ */ forwardRef(function ManagedDataT
30
60
  const comparison = typeof left === "number" && typeof right === "number" ? left - right : collator.compare(String(left), String(right));
31
61
  return comparison === 0 ? a.index - b.index : comparison * (sorting.direction === "ascending" ? 1 : -1);
32
62
  }).map(entry => entry.row);
33
- }, [rows, sorting?.column, sorting?.direction, locale]);
34
- const pageCount = Math.max(1, Math.ceil(rows.length / pageSize)), page = Math.min(controlledPage ?? localPage, pageCount);
35
- const visible = sorted.slice((page - 1) * pageSize, page * pageSize), eligible = new Set(rows.filter(row => !row.selectionDisabled).map(row => row.id));
36
- const selection = [...new Set(selectedIds ?? localSelected)].filter(id => eligible.has(id)), chosen = new Set(selection);
63
+ }, [rows, sorting?.column, sorting?.direction, locale, server]);
64
+ const total = server ? totalRows : rows.length, pageCount = Math.max(1, Math.ceil(total / pageSize)), page = server ? controlledPage : Math.min(controlledPage ?? localPage, pageCount);
65
+ if (server && state === "ready" && (page > pageCount || rows.length > pageSize || (page - 1) * pageSize + rows.length > total))
66
+ throw new Error("Flood server rows and page must match totalRows/pageSize; reconcile the page after a query change");
67
+ const visible = server ? sorted : sorted.slice((page - 1) * pageSize, page * pageSize), eligible = new Set((scope === "page" ? visible : rows).filter(row => !row.selectionDisabled).map(row => row.id));
68
+ const accepts = (id) => server && scope === "dataset" ? !rows.some(row => row.id === id && row.selectionDisabled) : eligible.has(id);
69
+ const selection = [...new Set(selectedIds ?? localSelected)].filter(accepts), chosen = new Set(selection);
37
70
  const pageChoices = visible.filter(row => !row.selectionDisabled), all = pageChoices.length > 0 && pageChoices.every(row => chosen.has(row.id)), some = pageChoices.some(row => chosen.has(row.id));
38
71
  const blocked = state !== "ready";
39
72
  const select = (next) => { if (blocked)
40
- return; const accepted = [...new Set(next)].filter(id => eligible.has(id)); if (selectedIds === undefined)
73
+ return; const accepted = [...new Set(next)].filter(accepts); if (selectedIds === undefined)
41
74
  setSelected(accepted); onSelectionChange?.(accepted); };
42
75
  const changePage = (next) => { if (blocked)
43
76
  return; if (controlledPage === undefined)
@@ -53,6 +86,8 @@ export const ManagedDataTable = /* @__PURE__ */ forwardRef(function ManagedDataT
53
86
  changePage(1);
54
87
  };
55
88
  const resultLabel = labels.results ?? ((from, to, total) => `${from}–${to} of ${total}`);
56
- return _jsxs("div", { ...props, ref: ref, className: ["flood-data-table", className].filter(Boolean).join(" "), "aria-busy": state === "loading" || undefined, children: [_jsxs(Table, { caption: caption, hideCaption: hideCaption, density: density, striped: striped, style: { minWidth }, children: [_jsx("thead", { children: _jsxs("tr", { children: [selectable && _jsx("th", { scope: "col", className: "flood-data-table__choice", children: _jsx(Checkbox, { label: _jsx(VisuallyHidden, { children: labels.selectPage ?? "Select rows on this page" }), checked: all, indeterminate: some && !all, disabled: blocked || pageChoices.length === 0, onChange: event => { const ids = new Set(selection); for (const row of pageChoices)
57
- event.currentTarget.checked ? ids.add(row.id) : ids.delete(row.id); select([...ids]); } }) }), columns.map(column => _jsx("th", { scope: "col", "data-sortable": column.sortable || undefined, "aria-sort": sorting?.column === column.id ? sorting.direction : undefined, style: { textAlign: column.align ?? "start", width: column.width }, children: column.sortable ? _jsxs(Button, { variant: "ghost", size: "sm", disabled: blocked, className: "flood-data-table__sort", "aria-label": (labels.sort ?? ((label) => `Sort by ${label}`))(column.label), onClick: () => changeSort(column.id), children: [column.label, _jsx(IconSlot, { size: "sm", children: _jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.75", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: sorting?.column === column.id ? _jsx("path", { d: sorting.direction === "ascending" ? "m7 14 5-5 5 5" : "m7 10 5 5 5-5" }) : _jsx("path", { d: "m8 8 4-4 4 4M8 16l4 4 4-4" }) }) })] }) : column.label }, column.id))] }) }), _jsx("tbody", { children: state === "ready" && visible.length > 0 ? visible.map(row => _jsxs("tr", { "data-selected": selectable && chosen.has(row.id) || undefined, children: [selectable && _jsx("td", { className: "flood-data-table__choice", children: _jsx(Checkbox, { label: _jsx(VisuallyHidden, { children: (labels.selectRow ?? ((label) => `Select ${label}`))(row.label ?? row.id) }), checked: chosen.has(row.id), disabled: Boolean(row.selectionDisabled), onChange: event => select(event.currentTarget.checked ? [...selection, row.id] : selection.filter(id => id !== row.id)) }) }), columns.map(column => _jsx("td", { style: { textAlign: column.align ?? "start" }, children: row.cells[column.id] ?? null }, column.id))] }, row.id)) : _jsx("tr", { children: _jsx("td", { colSpan: columns.length + (selectable ? 1 : 0), className: "flood-data-table__state", children: state === "loading" ? _jsx(Spinner, { label: labels.loading ?? "Loading rows…" }) : state === "error" ? _jsxs(Stack, { gap: "3", children: [_jsx(Alert, { tone: "error", announcement: "polite", children: labels.error ?? "Could not load the rows." }), onRetry && _jsx(Button, { variant: "subtle", size: "sm", onClick: onRetry, children: labels.retry ?? "Try again" })] }) : _jsx(Text, { as: "p", size: "compact", tone: "subtle", role: "status", children: labels.empty ?? "No results found." }) }) }) })] }), _jsxs("div", { className: "flood-data-table__footer", children: [_jsx(Text, { as: "p", size: "helper", tone: "subtle", children: state === "ready" ? resultLabel(rows.length ? (page - 1) * pageSize + 1 : 0, Math.min(page * pageSize, rows.length), rows.length) : "" }), selectable && _jsx(Text, { as: "p", size: "helper", tone: "subtle", role: "status", children: (labels.selection ?? ((count, total) => `${count} of ${total} selected`))(selection.length, eligible.size) }), pageCount > 1 && _jsx(Pagination, { page: page, pageCount: pageCount, onPageChange: changePage, disabled: blocked, label: labels.pagination, previousLabel: labels.previousPage, nextLabel: labels.nextPage, pageLabel: labels.page })] })] });
89
+ return _jsxs("div", { ...props, ref: ref, className: ["flood-data-table", className].filter(Boolean).join(" "), "data-inset": inset, "aria-busy": state === "loading" || undefined, children: [toolbar != null && _jsx("div", { className: "flood-data-table__toolbar", children: toolbar }), _jsxs(Table, { caption: caption, hideCaption: hideCaption, density: density, striped: striped, style: { minWidth }, children: [_jsx("thead", { children: _jsxs("tr", { children: [selectable && _jsx("th", { scope: "col", className: "flood-data-table__choice", children: _jsx(Checkbox, { label: _jsx(VisuallyHidden, { children: labels.selectPage ?? "Select rows on this page" }), checked: all, indeterminate: some && !all, disabled: blocked || pageChoices.length === 0, onChange: event => { const ids = new Set(selection); for (const row of pageChoices)
90
+ event.currentTarget.checked ? ids.add(row.id) : ids.delete(row.id); select([...ids]); } }) }), columns.map(column => _jsx("th", { scope: "col", "data-numeric": column.numeric || undefined, "data-align": column.align, "data-sortable": column.sortable || undefined, "aria-sort": sorting?.column === column.id ? sorting.direction : undefined, style: { width: column.width }, children: column.sortable ? _jsxs(Button, { variant: "ghost", size: "sm", disabled: blocked, className: "flood-data-table__sort", "aria-label": (labels.sort ?? ((label) => `Sort by ${label}`))(column.label), onClick: () => changeSort(column.id), children: [column.label, _jsx(IconSlot, { size: "sm", children: _jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.75", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: sorting?.column === column.id ? _jsx("path", { d: "m7 14 5-5 5 5" }) : _jsx("path", { d: "m8 8 4-4 4 4M8 16l4 4 4-4" }) }) })] }) : column.label }, column.id))] }) }), _jsx("tbody", { children: state === "ready" && visible.length > 0 ? visible.map(row => _jsxs("tr", { "data-selected": selectable && chosen.has(row.id) || undefined, "data-primary-action": row.primaryAction && !row.primaryAction.buttonProps.disabled && row.primaryAction.buttonProps["aria-disabled"] !== "true" || undefined, onClick: row.primaryAction ? activatePrimaryRowAction : undefined, children: [selectable && _jsx("td", { className: "flood-data-table__choice", children: _jsx(Checkbox, { label: _jsx(VisuallyHidden, { children: (labels.selectRow ?? ((label) => `Select ${label}`))(row.label ?? row.id) }), checked: chosen.has(row.id), disabled: Boolean(row.selectionDisabled), onChange: event => select(event.currentTarget.checked ? [...selection, row.id] : selection.filter(id => id !== row.id)) }) }), columns.map(column => _jsx("td", { "data-numeric": column.numeric || undefined, "data-align": column.align, children: row.primaryAction?.column === column.id
91
+ ? _jsxs(Stack, { gap: "1", children: [_jsx(Button, { ...row.primaryAction.buttonProps, type: "button", variant: "ghost", size: "sm", className: ["flood-data-table__primary-action", row.primaryAction.buttonProps.className].filter(Boolean).join(" "), children: row.primaryAction.title }), row.primaryAction.description != null && _jsx(Text, { as: "div", size: "helper", tone: "subtle", children: row.primaryAction.description })] })
92
+ : row.cells[column.id] ?? null }, column.id))] }, row.id)) : _jsx("tr", { children: _jsx("td", { colSpan: columns.length + (selectable ? 1 : 0), className: "flood-data-table__state", children: state === "loading" ? _jsx(Spinner, { label: labels.loading ?? "Loading rows…" }) : state === "error" ? _jsxs(Stack, { gap: "3", children: [_jsx(Alert, { tone: "error", announcement: "polite", children: labels.error ?? "Could not load the rows." }), onRetry && _jsx(Button, { variant: "subtle", size: "sm", onClick: onRetry, children: labels.retry ?? "Try again" })] }) : emptyContent ?? _jsx(Text, { as: "p", size: "compact", tone: "subtle", role: "status", children: labels.empty ?? "No results found." }) }) }) })] }), (showResults || selectable || pageCount > 1) && _jsxs("div", { className: "flood-data-table__footer", children: [showResults && _jsx(Text, { as: "p", size: "helper", tone: "subtle", children: state === "ready" ? resultLabel(visible.length ? (page - 1) * pageSize + 1 : 0, visible.length ? (page - 1) * pageSize + visible.length : 0, total) : "" }), selectable && _jsx(Text, { as: "p", size: "helper", tone: "subtle", role: "status", children: (labels.selection ?? ((count, total) => `${count} of ${total} selected`))(selection.length, server && scope === "dataset" ? total : eligible.size) }), pageCount > 1 && _jsx(Pagination, { page: Math.min(page, pageCount), pageCount: pageCount, onPageChange: changePage, disabled: blocked, label: labels.pagination, previousLabel: labels.previousPage, nextLabel: labels.nextPage, pageLabel: labels.page })] })] });
58
93
  });
@@ -4,7 +4,7 @@ import { Calendar, DateField, IconButton, Popover } from "./index.js";
4
4
  const useBrowserLayoutEffect = typeof window === "undefined" ? useEffect : useLayoutEffect;
5
5
  const triggerSizes = { sm: "xs", md: "sm", lg: "md", xl: "lg" };
6
6
  /** Calendar selection and keyboard date entry share one DateField value/form bridge. */
7
- export const CalendarDatePicker = /* @__PURE__ */ forwardRef(function CalendarDatePicker({ value, defaultValue = "", onValueChange, open: controlledOpen, defaultOpen = false, onOpenChange, calendarLabel, openCalendarLabel, locale, weekStartsOn, today, previousMonthLabel, nextMonthLabel, label, disabled = false, readOnly = false, controlSize = "md", min, max, form, className, onKeyDown, ...props }, forwardedRef) {
7
+ export const CalendarDatePicker = /* @__PURE__ */ forwardRef(function CalendarDatePicker({ value, defaultValue = "", onValueChange, open: controlledOpen, defaultOpen = false, onOpenChange, calendarLabel, openCalendarLabel, viewLabels, locale, weekStartsOn, today, previousMonthLabel, nextMonthLabel, label, disabled = false, readOnly = false, controlSize = "md", min, max, form, className, onKeyDown, ...props }, forwardedRef) {
8
8
  const panelLabel = calendarLabel ?? `${label} calendar`;
9
9
  const triggerLabel = openCalendarLabel ?? `Open calendar: ${label}`;
10
10
  if (!panelLabel.trim() || !triggerLabel.trim())
@@ -100,7 +100,7 @@ export const CalendarDatePicker = /* @__PURE__ */ forwardRef(function CalendarDa
100
100
  event.preventDefault();
101
101
  requestOpen(true);
102
102
  }
103
- }, end: _jsx(Popover, { title: panelLabel, className: "flood-date-picker__panel", align: "end", positionReference: positionReference, style: { "--flood-date-picker-width": fieldWidth === undefined ? undefined : `${fieldWidth}px` }, open: !blocked && requestedOpen, onOpenChange: requestOpen, disabled: blocked, trigger: trigger => _jsx(IconButton, { ...trigger, label: triggerLabel, variant: "ghost", size: triggerSizes[controlSize], children: _jsxs("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.75", strokeLinecap: "round", strokeLinejoin: "round", children: [_jsx("rect", { x: "3", y: "5", width: "18", height: "16", rx: "2" }), _jsx("path", { d: "M16 3v4M8 3v4M3 11h18" })] }) }), children: _jsx(Calendar, { label: panelLabel, value: committed, min: min, max: max, locale: locale, weekStartsOn: weekStartsOn, today: today, previousMonthLabel: previousMonthLabel, nextMonthLabel: nextMonthLabel, autoFocus: true, onValueChange: changeDate, onDateSelect: () => {
103
+ }, end: _jsx(Popover, { title: panelLabel, chrome: "minimal", className: "flood-date-picker__panel", align: "end", positionReference: positionReference, style: { "--flood-date-picker-width": fieldWidth === undefined ? undefined : `${fieldWidth}px` }, open: !blocked && requestedOpen, onOpenChange: requestOpen, disabled: blocked, trigger: trigger => _jsx(IconButton, { ...trigger, label: triggerLabel, variant: "ghost", size: triggerSizes[controlSize], children: _jsxs("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.75", strokeLinecap: "round", strokeLinejoin: "round", children: [_jsx("rect", { x: "3", y: "5", width: "18", height: "16", rx: "2" }), _jsx("path", { d: "M16 3v4M8 3v4M3 11h18" })] }) }), children: _jsx(Calendar, { label: panelLabel, viewLabels: viewLabels, value: committed, min: min, max: max, locale: locale, weekStartsOn: weekStartsOn, today: today, previousMonthLabel: previousMonthLabel, nextMonthLabel: nextMonthLabel, autoFocus: true, onValueChange: changeDate, onDateSelect: () => {
104
104
  if (blocked || nativeDisabled())
105
105
  return;
106
106
  requestOpen(false);
@@ -4,7 +4,7 @@ import { DateRangeField, IconButton, Popover, RangeCalendar } from "./index.js";
4
4
  import { emptyDateRange } from "./date-range-value.js";
5
5
  const useBrowserLayoutEffect = typeof window === "undefined" ? useEffect : useLayoutEffect;
6
6
  const triggerSizes = { sm: "xs", md: "sm", lg: "md", xl: "lg" };
7
- export const CalendarDateRangePicker = /* @__PURE__ */ forwardRef(function CalendarDateRangePicker({ value, defaultValue = emptyDateRange(), onValueChange, open: controlledOpen, defaultOpen = false, onOpenChange, label, calendarLabel, openCalendarLabel, locale, weekStartsOn, today, previousMonthLabel, nextMonthLabel, rangeLabels, disabled = false, readOnly = false, controlSize = "md", min, max, isDateDisabled, isRangeDisabled, form, className, onKeyDown, ...props }, forwardedRef) {
7
+ export const CalendarDateRangePicker = /* @__PURE__ */ forwardRef(function CalendarDateRangePicker({ value, defaultValue = emptyDateRange(), onValueChange, open: controlledOpen, defaultOpen = false, onOpenChange, label, calendarLabel, openCalendarLabel, viewLabels, locale, weekStartsOn, today, previousMonthLabel, nextMonthLabel, rangeLabels, disabled = false, readOnly = false, controlSize = "md", min, max, isDateDisabled, isRangeDisabled, form, className, onKeyDown, ...props }, forwardedRef) {
8
8
  const panelLabel = calendarLabel ?? `${label} calendar`, triggerLabel = openCalendarLabel ?? `Open calendar: ${label}`;
9
9
  if (!panelLabel.trim() || !triggerLabel.trim())
10
10
  throw new Error("Flood DateRangePicker requires nonempty calendar and trigger labels");
@@ -76,6 +76,6 @@ export const CalendarDateRangePicker = /* @__PURE__ */ forwardRef(function Calen
76
76
  event.preventDefault();
77
77
  requestOpen(true);
78
78
  }
79
- }, end: _jsx(Popover, { title: panelLabel, className: "flood-date-range-picker__panel", align: "end", positionReference: positionReference, style: { "--flood-date-range-picker-width": fieldWidth ? `${fieldWidth}px` : undefined }, open: !blocked && requestedOpen, onOpenChange: requestOpen, disabled: blocked, trigger: trigger => _jsx(IconButton, { ...trigger, label: triggerLabel, variant: "ghost", size: triggerSizes[controlSize], children: _jsxs("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.75", strokeLinecap: "round", strokeLinejoin: "round", children: [_jsx("rect", { x: "3", y: "5", width: "18", height: "16", rx: "2" }), _jsx("path", { d: "M16 3v4M8 3v4M3 11h18" })] }) }), children: _jsx(RangeCalendar, { label: panelLabel, value: committed, min: min, max: max, isDateDisabled: isDateDisabled, isRangeDisabled: isRangeDisabled, locale: locale, weekStartsOn: weekStartsOn, today: today, previousMonthLabel: previousMonthLabel, nextMonthLabel: nextMonthLabel, rangeLabels: rangeLabels, autoFocus: true, onValueChange: change, onRangeSelect: () => { if (!blocked && !nativeDisabled())
79
+ }, end: _jsx(Popover, { title: panelLabel, chrome: "minimal", className: "flood-date-range-picker__panel", align: "end", positionReference: positionReference, style: { "--flood-date-range-picker-width": fieldWidth ? `${fieldWidth}px` : undefined }, open: !blocked && requestedOpen, onOpenChange: requestOpen, disabled: blocked, trigger: trigger => _jsx(IconButton, { ...trigger, label: triggerLabel, variant: "ghost", size: triggerSizes[controlSize], children: _jsxs("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.75", strokeLinecap: "round", strokeLinejoin: "round", children: [_jsx("rect", { x: "3", y: "5", width: "18", height: "16", rx: "2" }), _jsx("path", { d: "M16 3v4M8 3v4M3 11h18" })] }) }), children: _jsx(RangeCalendar, { label: panelLabel, viewLabels: viewLabels, value: committed, min: min, max: max, isDateDisabled: isDateDisabled, isRangeDisabled: isRangeDisabled, locale: locale, weekStartsOn: weekStartsOn, today: today, previousMonthLabel: previousMonthLabel, nextMonthLabel: nextMonthLabel, rangeLabels: rangeLabels, autoFocus: true, onValueChange: change, onRangeSelect: () => { if (!blocked && !nativeDisabled())
80
80
  requestOpen(false); } }) }) });
81
81
  });
@@ -2,7 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { forwardRef, useEffect, useImperativeHandle, useRef, useState } from "react";
3
3
  import { DatePicker, TimePicker, Fieldset } from "./index.js";
4
4
  /** A civil date and wall-clock time. The host owns timezone conversion. */
5
- export const CivilDateTimePicker = /* @__PURE__ */ forwardRef(function CivilDateTimePicker({ label, validationMessage, timeInterval, hideLabel = false, hideFieldLabels = false, value, defaultValue = { date: "", time: "" }, onValueChange, dateLabel = "Date", timeLabel = "Time", dateName, timeName, form, required = false, disabled = false, readOnly = false, controlSize = "md", precision = "minute", order = "month-day-year", locale = "en-US", minDate, maxDate, minTime, maxTime, step, status, description, className, ...props }, ref) {
5
+ export const CivilDateTimePicker = /* @__PURE__ */ forwardRef(function CivilDateTimePicker({ label, validationMessage, timeInterval, hideLabel = false, hideFieldLabels = false, value, defaultValue = { date: "", time: "" }, onValueChange, dateLabel = "Date", timeLabel = "Time", datePickerLabels, timePickerLabels, dateName, timeName, form, required = false, disabled = false, readOnly = false, controlSize = "md", precision = "minute", order = "month-day-year", locale = "en-US", minDate, maxDate, minTime, maxTime, step, status, description, className, ...props }, ref) {
6
6
  const [local, setLocal] = useState(defaultValue);
7
7
  const root = useRef(null), initial = useRef(defaultValue);
8
8
  useImperativeHandle(ref, () => root.current, []);
@@ -27,5 +27,5 @@ export const CivilDateTimePicker = /* @__PURE__ */ forwardRef(function CivilDate
27
27
  };
28
28
  // Each field owns native validity, reset and submission. Values are deliberately
29
29
  // separate: neither a timezone nor an instant is inferred from this pair.
30
- return _jsx("div", { ...props, ref: root, className: ["flood-date-time-picker", className].filter(Boolean).join(" "), children: _jsx(Fieldset, { legend: label, hideLabel: hideLabel, hint: description, disabled: disabled, children: _jsxs("div", { className: "flood-date-time-picker__fields", children: [_jsx(DatePicker, { label: dateLabel, hideLabel: hideFieldLabels, value: value === undefined ? undefined : selected.date, defaultValue: defaultValue.date, onValueChange: next => change("date", next), name: dateName, form: form, required: required, disabled: disabled, readOnly: readOnly, controlSize: controlSize, order: order, locale: locale, min: minDate, max: maxDate, status: status, validationMessage: validationMessage }), _jsx(TimePicker, { label: timeLabel, hideLabel: hideFieldLabels, value: value === undefined ? undefined : selected.time, defaultValue: defaultValue.time, onValueChange: next => change("time", next), name: timeName, form: form, required: required, disabled: disabled, readOnly: readOnly, controlSize: controlSize, precision: precision, min: minTime, max: maxTime, step: step, interval: timeInterval, status: status })] }) }) });
30
+ return _jsx("div", { ...props, ref: root, className: ["flood-date-time-picker", className].filter(Boolean).join(" "), children: _jsx(Fieldset, { legend: label, hideLabel: hideLabel, hint: description, disabled: disabled, children: _jsxs("div", { className: "flood-date-time-picker__fields", children: [_jsx(DatePicker, { ...datePickerLabels, label: dateLabel, hideLabel: hideFieldLabels, value: value === undefined ? undefined : selected.date, defaultValue: defaultValue.date, onValueChange: next => change("date", next), name: dateName, form: form, required: required, disabled: disabled, readOnly: readOnly, controlSize: controlSize, order: order, locale: locale, min: minDate, max: maxDate, status: status, validationMessage: validationMessage }), _jsx(TimePicker, { ...timePickerLabels, label: timeLabel, hideLabel: hideFieldLabels, value: value === undefined ? undefined : selected.time, defaultValue: defaultValue.time, onValueChange: next => change("time", next), name: timeName, form: form, required: required, disabled: disabled, readOnly: readOnly, controlSize: controlSize, precision: precision, min: minTime, max: maxTime, step: step, interval: timeInterval, status: status })] }) }) });
31
31
  });
@@ -6,7 +6,7 @@ import { parseTime, timeOnStep } from "./time-value.js";
6
6
  const empty = () => ({ start: { date: "", time: "" }, end: { date: "", time: "" } });
7
7
  const copy = (value) => ({ start: { ...value.start }, end: { ...value.end } });
8
8
  const signature = (value) => [value.start.date, value.start.time, value.end.date, value.end.time].join("|");
9
- export const CivilDateTimeRangePicker = /* @__PURE__ */ forwardRef(function CivilDateTimeRangePicker({ label, hideLabel = false, value, defaultValue = empty(), onValueChange, startDateName, startTimeName, endDateName, endTimeName, startLabel = "Start", endLabel = "End", startDateLabel = "Start date", startTimeLabel = "Start time", endDateLabel = "End date", endTimeLabel = "End time", form, required = false, disabled = false, readOnly = false, controlSize = "md", precision = "minute", order = "month-day-year", locale = "en-US", minDate, maxDate, step: explicitStep, interval, status, description, incompleteMessage = "Complete both dates and times.", reversedMessage = "End must not precede start.", className, onBlur, ...props }, ref) {
9
+ export const CivilDateTimeRangePicker = /* @__PURE__ */ forwardRef(function CivilDateTimeRangePicker({ label, hideLabel = false, value, defaultValue = empty(), onValueChange, startDateName, startTimeName, endDateName, endTimeName, startLabel = "Start", endLabel = "End", startDateLabel = "Start date", startTimeLabel = "Start time", endDateLabel = "End date", endTimeLabel = "End time", startDatePickerLabels, endDatePickerLabels, startTimePickerLabels, endTimePickerLabels, form, required = false, disabled = false, readOnly = false, controlSize = "md", precision = "minute", order = "month-day-year", locale = "en-US", minDate, maxDate, step: explicitStep, interval, status, description, incompleteMessage = "Complete both dates and times.", reversedMessage = "End must not precede start.", className, onBlur, ...props }, ref) {
10
10
  if (![label, startLabel, endLabel, startDateLabel, startTimeLabel, endDateLabel, endTimeLabel, incompleteMessage, reversedMessage].every(text => text.trim()))
11
11
  throw new Error("Flood DateTimeRangePicker requires nonempty labels and messages");
12
12
  const initial = useRef(copy(defaultValue)), [local, setLocal] = useState(() => copy(defaultValue)), [proposal, setProposal] = useState();
@@ -47,5 +47,5 @@ export const CivilDateTimeRangePicker = /* @__PURE__ */ forwardRef(function Civi
47
47
  onBlur?.(event);
48
48
  if (!event.defaultPrevented && !event.currentTarget.contains(event.relatedTarget))
49
49
  setProposal(undefined);
50
- }, children: [_jsx(Fieldset, { legend: label, hideLabel: hideLabel, hint: description, disabled: disabled, children: _jsxs(Stack, { gap: "4", children: [_jsx(DateTimePicker, { ...shared, label: startLabel, dateLabel: startDateLabel, timeLabel: startTimeLabel, value: selected.start, defaultValue: initial.current.start, onValueChange: next => change("start", next), validationMessage: constraint }), _jsx(DateTimePicker, { ...shared, label: endLabel, dateLabel: endDateLabel, timeLabel: endTimeLabel, value: selected.end, defaultValue: initial.current.end, onValueChange: next => change("end", next) })] }) }), [[startDateName, selected.start.date], [startTimeName, selected.start.time], [endDateName, selected.end.date], [endTimeName, selected.end.time]].map(([name, part], index) => name && _jsx("input", { type: "hidden", name: name, form: form, value: accepted ? part : "", disabled: disabled }, index))] });
50
+ }, children: [_jsx(Fieldset, { legend: label, hideLabel: hideLabel, hint: description, disabled: disabled, children: _jsxs(Stack, { gap: "4", children: [_jsx(DateTimePicker, { ...shared, datePickerLabels: startDatePickerLabels, timePickerLabels: startTimePickerLabels, label: startLabel, dateLabel: startDateLabel, timeLabel: startTimeLabel, value: selected.start, defaultValue: initial.current.start, onValueChange: next => change("start", next), validationMessage: constraint }), _jsx(DateTimePicker, { ...shared, datePickerLabels: endDatePickerLabels, timePickerLabels: endTimePickerLabels, label: endLabel, dateLabel: endDateLabel, timeLabel: endTimeLabel, value: selected.end, defaultValue: initial.current.end, onValueChange: next => change("end", next) })] }) }), [[startDateName, selected.start.date], [startTimeName, selected.start.time], [endDateName, selected.end.date], [endTimeName, selected.end.time]].map(([name, part], index) => name && _jsx("input", { type: "hidden", name: name, form: form, value: accepted ? part : "", disabled: disabled }, index))] });
51
51
  });
@@ -1,4 +1,4 @@
1
- import { type HTMLAttributes } from "react";
1
+ import { type HTMLAttributes, type ReactNode } from "react";
2
2
  interface Props extends Omit<HTMLAttributes<HTMLDivElement>, "title"> {
3
3
  title: string;
4
4
  expanded: boolean;
@@ -6,6 +6,7 @@ interface Props extends Omit<HTMLAttributes<HTMLDivElement>, "title"> {
6
6
  disabled?: boolean;
7
7
  controlSize: "sm" | "md" | "lg" | "xl";
8
8
  headingLevel?: 2 | 3 | 4 | 5 | 6;
9
+ headerAction?: ReactNode;
9
10
  }
10
11
  /** Shared original disclosure anatomy. The closed panel stays mounted but hidden. */
11
12
  export declare const Disclosure: import("react").ForwardRefExoticComponent<Props & import("react").RefAttributes<HTMLDivElement>>;
@@ -1,9 +1,9 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { createElement, forwardRef, useEffect, useId, useLayoutEffect, useRef, useImperativeHandle } from "react";
3
- import { Button } from "./index.js";
3
+ import { Button, IconSlot } from "./index.js";
4
4
  const useBrowserLayoutEffect = typeof window === "undefined" ? useEffect : useLayoutEffect;
5
5
  /** Shared original disclosure anatomy. The closed panel stays mounted but hidden. */
6
- export const Disclosure = /* @__PURE__ */ forwardRef(function Disclosure({ title, expanded, onExpandedChange, disabled, controlSize, headingLevel, className, children, id: explicitId, ...props }, ref) {
6
+ export const Disclosure = /* @__PURE__ */ forwardRef(function Disclosure({ title, expanded, onExpandedChange, disabled, controlSize, headingLevel, headerAction, className, children, id: explicitId, ...props }, ref) {
7
7
  const generatedId = useId(), id = explicitId ?? `flood-disclosure-${generatedId}`;
8
8
  const root = useRef(null), panel = useRef(null), trigger = useRef(null);
9
9
  const focusedInside = useRef(false);
@@ -15,7 +15,7 @@ export const Disclosure = /* @__PURE__ */ forwardRef(function Disclosure({ title
15
15
  if (!expanded)
16
16
  focusedInside.current = false;
17
17
  }, [expanded, disabled]);
18
- const button = _jsxs(Button, { ref: trigger, variant: "ghost", size: controlSize, id: `${id}-trigger`, type: "button", disabled: disabled, "aria-expanded": expanded, "aria-controls": `${id}-panel`, className: "flood-disclosure__trigger", onClick: () => onExpandedChange(!expanded), children: [_jsx("span", { className: "flood-disclosure__title", children: title }), _jsx("span", { className: "flood-disclosure__indicator", "aria-hidden": "true" })] });
19
- return _jsxs("div", { ...props, id: id, ref: root, "data-open": expanded, className: ["flood-disclosure", `flood-control--${controlSize}`, className].filter(Boolean).join(" "), children: [createElement(headingLevel ? `h${headingLevel}` : "div", { className: "flood-disclosure__heading" }, button), _jsx("div", { ref: panel, id: `${id}-panel`, className: "flood-disclosure__panel", hidden: !expanded, onFocusCapture: () => { focusedInside.current = true; }, onBlurCapture: event => { if (event.relatedTarget && !event.currentTarget.contains(event.relatedTarget))
18
+ const button = _jsxs(Button, { ref: trigger, variant: "ghost", size: controlSize, id: `${id}-trigger`, type: "button", disabled: disabled, "aria-expanded": expanded, "aria-controls": `${id}-panel`, className: "flood-disclosure__trigger", onClick: () => onExpandedChange(!expanded), children: [_jsx("span", { className: "flood-disclosure__title", children: title }), _jsx(IconSlot, { size: "2xs", className: "flood-disclosure__indicator", children: _jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.75", strokeLinecap: "round", strokeLinejoin: "round", children: _jsx("path", { d: "m3 8 9 8 9-8" }) }) })] });
19
+ return _jsxs("div", { ...props, id: id, ref: root, "data-open": expanded, className: ["flood-disclosure", `flood-control--${controlSize}`, className].filter(Boolean).join(" "), children: [createElement(headingLevel ? `h${headingLevel}` : "div", { className: "flood-disclosure__heading" }, button, headerAction != null && _jsx("div", { className: "flood-disclosure__action", children: headerAction })), _jsx("div", { ref: panel, id: `${id}-panel`, className: "flood-disclosure__panel", hidden: !expanded, onFocusCapture: () => { focusedInside.current = true; }, onBlurCapture: event => { if (event.relatedTarget && !event.currentTarget.contains(event.relatedTarget))
20
20
  focusedInside.current = false; }, children: children })] });
21
21
  });
@@ -0,0 +1,3 @@
1
+ import { type FilterConditionProps } from './index.js';
2
+ /** One controlled finite-choice predicate. Query execution and draft/application belong to the host. */
3
+ export declare const PredicateRow: import("react").ForwardRefExoticComponent<FilterConditionProps & import("react").RefAttributes<HTMLFieldSetElement>>;
@@ -0,0 +1,21 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { forwardRef } from 'react';
3
+ import { Field, Fieldset, Grid, IconButton, MultiSelect, Select } from './index.js';
4
+ /** One controlled finite-choice predicate. Query execution and draft/application belong to the host. */
5
+ export const PredicateRow = /* @__PURE__ */ forwardRef(function PredicateRow({ label, properties, value, onValueChange, onRemove, controlSize = 'sm', labels, disabled = false, hideLabel = true, className, ...props }, ref) {
6
+ const property = properties.find(property => property.id === value.property);
7
+ if (!label.trim() || !properties.length || new Set(properties.map(property => property.id)).size !== properties.length ||
8
+ properties.some(property => !property.id.trim() || !property.label.trim()) || !property ||
9
+ !['is', 'is-not'].includes(value.operator))
10
+ throw new Error('Flood FilterCondition requires named unique properties and a supported predicate');
11
+ const names = {
12
+ property: `${label} property`, operator: `${label} operator`, values: `${label} values`,
13
+ include: 'is any of', exclude: 'is none of', placeholder: 'Choose values…', remove: `Remove ${label.charAt(0).toLowerCase()}${label.slice(1)}`,
14
+ ...labels,
15
+ };
16
+ return _jsx(Fieldset, { ...props, ref: ref, legend: label, hideLabel: hideLabel, disabled: disabled, className: ['flood-filter-condition', className].filter(Boolean).join(' '), "data-removable": Boolean(onRemove), children: _jsxs(Grid, { gap: "2", className: "flood-filter-condition__row", children: [_jsx(Field, { label: names.property, hideLabel: true, children: field => _jsx(Select, { ...field, controlSize: controlSize, value: value.property, disabled: disabled, onChange: event => { if (!disabled)
17
+ onValueChange({ ...value, property: event.currentTarget.value, values: [] }); }, children: properties.map(property => _jsx("option", { value: property.id, disabled: property.disabled, children: property.label }, property.id)) }) }), _jsx(Field, { label: names.operator, hideLabel: true, children: field => _jsxs(Select, { ...field, controlSize: controlSize, value: value.operator, disabled: disabled, onChange: event => { if (!disabled)
18
+ onValueChange({ ...value, operator: event.currentTarget.value }); }, children: [_jsx("option", { value: "is", children: names.include }), _jsx("option", { value: "is-not", children: names.exclude })] }) }), _jsx("div", { className: "flood-filter-condition__values", children: _jsx(Field, { label: names.values, hideLabel: true, children: field => _jsx(MultiSelect, { ...field, controlSize: controlSize, options: property.options, value: value.values, placeholder: names.placeholder, disabled: disabled, onValueChange: values => { if (!disabled)
19
+ onValueChange({ ...value, values }); } }) }) }), onRemove && _jsx(IconButton, { label: names.remove, size: controlSize, variant: "subtle", disabled: disabled, className: "flood-filter-condition__remove", onClick: () => { if (!disabled)
20
+ 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" }) }) })] }) });
21
+ });
@@ -0,0 +1,2 @@
1
+ import { type FilterGroupProps } from './index.js';
2
+ export declare const PredicateGroup: import("react").ForwardRefExoticComponent<FilterGroupProps & import("react").RefAttributes<HTMLFieldSetElement>>;