@flood-md/react 0.2.11 → 0.2.13

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.
@@ -0,0 +1,80 @@
1
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { forwardRef, useId, useState } from "react";
3
+ import { Inline, Surface, Text, VisuallyHidden } from "./index.js";
4
+ const number = new Intl.NumberFormat("en", { maximumSignificantDigits: 21 });
5
+ export const DumbbellPlot = /* @__PURE__ */ forwardRef(function DumbbellPlot({ label, items, domain, beforeLabel = "Before", afterLabel = "After", missingLabel = "No data", emptyLabel = "No observations", formatValue = number.format, navigationLabel = "Use arrow keys to explore groups, or Home and End to jump.", className, ...props }, ref) {
6
+ const instructions = useId();
7
+ const [cursor, setCursor] = useState(0);
8
+ const [hover, setHover] = useState();
9
+ const [tap, setTap] = useState();
10
+ const [focused, setFocused] = useState(false);
11
+ const ids = new Set();
12
+ let low = Infinity;
13
+ let high = -Infinity;
14
+ for (const item of items) {
15
+ if (!item.id.trim() || ids.has(item.id) || !item.label.trim())
16
+ throw new Error("Flood DumbbellChart needs unique IDs and nonempty labels");
17
+ ids.add(item.id);
18
+ for (const value of [item.before, item.after]) {
19
+ if (value === null)
20
+ continue;
21
+ if (!Number.isFinite(value))
22
+ throw new RangeError("Flood DumbbellChart endpoints must be finite");
23
+ low = Math.min(low, value);
24
+ high = Math.max(high, value);
25
+ }
26
+ }
27
+ if (domain && (!Number.isFinite(domain[0]) || !Number.isFinite(domain[1]) ||
28
+ domain[0] >= domain[1] || domain[0] > low || domain[1] < high)) {
29
+ throw new RangeError("Flood DumbbellChart domain must contain all observed endpoints and have finite increasing bounds");
30
+ }
31
+ const [min, max] = domain ?? [low, high];
32
+ const span = max - min;
33
+ const position = (value) => span === 0 ? 50 : 100 * (Number.isFinite(span)
34
+ ? (value - min) / span : (value / 2 - min / 2) / (max / 2 - min / 2));
35
+ const last = items.length - 1;
36
+ const active = Math.min(last, hover ?? tap ?? cursor);
37
+ const item = items[active];
38
+ const inspecting = hover !== undefined || tap !== undefined || focused;
39
+ const values = item ? [item.before, item.after].map(value => value === null ? missingLabel : formatValue(value)) : [];
40
+ const valueText = item && `${item.label}, ${beforeLabel}: ${values[0]}, ${afterLabel}: ${values[1]}`;
41
+ const navigate = (event) => {
42
+ let next;
43
+ switch (event.key) {
44
+ case "ArrowDown":
45
+ case "ArrowRight":
46
+ next = active + 1;
47
+ break;
48
+ case "ArrowUp":
49
+ case "ArrowLeft":
50
+ next = active - 1;
51
+ break;
52
+ case "Home":
53
+ next = 0;
54
+ break;
55
+ case "End":
56
+ next = last;
57
+ break;
58
+ case "Escape":
59
+ if (inspecting)
60
+ event.preventDefault();
61
+ setHover(undefined);
62
+ setTap(undefined);
63
+ setFocused(false);
64
+ return;
65
+ default: return;
66
+ }
67
+ event.preventDefault();
68
+ setHover(undefined);
69
+ setTap(undefined);
70
+ setFocused(true);
71
+ setCursor(Math.max(0, Math.min(last, next)));
72
+ };
73
+ return _jsx("div", { ...props, ref: ref, className: ["flood-dumbbell-chart", className].filter(Boolean).join(" "), role: "group", "aria-label": label, children: low <= high ? _jsxs(_Fragment, { children: [last > 0 && _jsx(VisuallyHidden, { id: instructions, children: navigationLabel }), _jsxs("div", { className: "flood-dumbbell-chart__plot", dir: "ltr", role: last > 0 ? "slider" : "img", tabIndex: 0, "aria-label": last > 0 ? label : `${label}. ${valueText}`, "aria-describedby": last > 0 ? instructions : undefined, "aria-valuemin": last > 0 ? 1 : undefined, "aria-valuemax": last > 0 ? items.length : undefined, "aria-valuenow": last > 0 ? active + 1 : undefined, "aria-valuetext": last > 0 ? valueText : undefined, onFocus: event => setFocused(event.currentTarget.matches(":focus-visible")), onBlur: () => setFocused(false), onKeyDown: navigate, onPointerLeave: () => setHover(undefined), children: [_jsx("div", { className: "flood-dumbbell-chart__rows", "aria-hidden": "true", children: items.map(({ id, label: name, before, after }, index) => {
74
+ const start = before === null ? undefined : position(before);
75
+ const end = after === null ? undefined : position(after);
76
+ return _jsxs("div", { className: "flood-dumbbell-chart__row", "data-active": inspecting && index === active || undefined, onPointerEnter: event => { if (event.pointerType !== "touch")
77
+ setHover(index); }, onPointerUp: event => { if (event.pointerType === "touch")
78
+ setTap(previous => previous === index ? undefined : index); }, children: [_jsx(Text, { className: "flood-dumbbell-chart__name", size: "helper", tone: "subtle", dir: "auto", title: name, children: name }), _jsxs("div", { className: "flood-dumbbell-chart__track", children: [start !== undefined && end !== undefined && _jsx("span", { className: "flood-dumbbell-chart__connector", style: { left: `${Math.min(start, end)}%`, width: `${Math.abs(end - start)}%` } }), start !== undefined && _jsx("span", { className: "flood-dumbbell-chart__point", "data-point": "before", style: { left: `${start}%` } }), end !== undefined && _jsx("span", { className: "flood-dumbbell-chart__point", "data-point": "after", style: { left: `${end}%` } })] }), inspecting && index === active && _jsxs(Surface, { className: "flood-chart__tooltip flood-dumbbell-chart__tooltip", padding: "2", elevation: "raised", "data-side": index < items.length / 2 ? "below" : "above", style: { "--flood-dumbbell-anchor": (end ?? start ?? 50) / 100 }, children: [_jsx(Text, { size: "helper", dir: "auto", children: name }), _jsxs(Inline, { gap: "2", className: "flood-dumbbell-chart__reading", children: [_jsx(Text, { size: "helper", tone: "subtle", children: beforeLabel }), _jsx(Text, { size: "helper", children: values[0] })] }), _jsxs(Inline, { gap: "2", className: "flood-dumbbell-chart__reading", children: [_jsx(Text, { size: "helper", tone: "subtle", children: afterLabel }), _jsx(Text, { size: "helper", children: values[1] })] })] })] }, id);
79
+ }) }), _jsxs("div", { className: "flood-dumbbell-chart__axis", "aria-hidden": "true", children: [_jsx(Text, { size: "helper", tone: "subtle", children: formatValue(min) }), min !== max && _jsx(Text, { size: "helper", tone: "subtle", children: formatValue(max) })] })] }), _jsxs(Inline, { gap: "4", className: "flood-dumbbell-chart__legend", "aria-hidden": "true", children: [_jsxs(Inline, { gap: "2", align: "center", children: [_jsx("span", { className: "flood-dumbbell-chart__key", "data-point": "before" }), _jsx(Text, { size: "helper", tone: "subtle", children: beforeLabel })] }), _jsxs(Inline, { gap: "2", align: "center", children: [_jsx("span", { className: "flood-dumbbell-chart__key", "data-point": "after" }), _jsx(Text, { size: "helper", tone: "subtle", children: afterLabel })] })] })] }) : _jsx(Text, { size: "compact", tone: "subtle", children: emptyLabel }) });
80
+ });
package/dist/editable.js CHANGED
@@ -1,10 +1,12 @@
1
1
  import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { forwardRef, useEffect, useId, useImperativeHandle, useLayoutEffect, useRef, useState } from "react";
3
- import { Button, IconButton, IconSlot, Input, InputGroup } from "./index.js";
3
+ import { Button, IconButton, IconSlot, Input, InputGroup, Text } from "./index.js";
4
+ import { mergeDescriptions } from "./field-state.js";
5
+ import { positionLayer, registerLayer } from "./overlay-runtime.js";
4
6
  const useBrowserLayoutEffect = typeof window === "undefined" ? useEffect : useLayoutEffect;
5
7
  const cx = (...values) => values.filter(Boolean).join(" ");
6
8
  /** A draft is private until explicitly committed; native forms receive committed text. */
7
- export const InlineEditable = /* @__PURE__ */ forwardRef(function InlineEditable({ label, hideLabel = false, value, defaultValue = "", onValueChange, editing, defaultEditing = false, onEditingChange, onCommit, onCancel, blurAction = "commit", readOnly = false, disabled = false, placeholder = "Empty", controlSize = "md", showActions = true, editLabel = "Edit", saveLabel = "Save", cancelLabel = "Cancel", inputRef, name, form, id: explicitId, className, onBlur, onKeyDown, ...props }, forwardedRef) {
9
+ export const InlineEditable = /* @__PURE__ */ forwardRef(function InlineEditable({ label, hideLabel = false, value, defaultValue = "", onValueChange, editing, defaultEditing = false, onEditingChange, onCommit, onCancel, validate, inputMode, previewVariant = "subtle", editorOverlayWidth, blurAction = "commit", readOnly = false, disabled = false, placeholder = "Empty", controlSize = "md", showActions = true, editLabel = "Edit", saveLabel = "Save", cancelLabel = "Cancel", inputRef, name, form, id: explicitId, className, onBlur, onKeyDown, ...props }, forwardedRef) {
8
10
  if (![label, editLabel, saveLabel, cancelLabel].every(text => text.trim()))
9
11
  throw new Error("Flood Editable requires nonempty control and action labels");
10
12
  const initial = useRef(defaultValue), [local, setLocal] = useState(defaultValue);
@@ -12,9 +14,13 @@ export const InlineEditable = /* @__PURE__ */ forwardRef(function InlineEditable
12
14
  const [localEditing, setLocalEditing] = useState(defaultEditing);
13
15
  const requested = editing ?? localEditing, active = requested && !disabled && !readOnly;
14
16
  const [draft, setDraft] = useState(committed);
17
+ const [validationError, setValidationError] = useState();
15
18
  const root = useRef(null), editor = useRef(null), bridge = useRef(null), preview = useRef(null);
16
- const current = useRef({ committed, active, draft, value, editing, onValueChange, onEditingChange, onCommit, onCancel });
17
- current.current = { committed, active, draft, value, editing, onValueChange, onEditingChange, onCommit, onCancel };
19
+ const layer = useRef(null);
20
+ const floating = editorOverlayWidth !== undefined;
21
+ const fitDraft = editorOverlayWidth === "fit-content";
22
+ const current = useRef({ committed, active, draft, value, editing, onValueChange, onEditingChange, onCommit, onCancel, validate });
23
+ current.current = { committed, active, draft, value, editing, onValueChange, onEditingChange, onCommit, onCancel, validate };
18
24
  const composing = useRef(false), resolved = useRef(false), wasActive = useRef(false);
19
25
  const generated = useId(), id = explicitId ?? `flood-editable-${generated}`, inputId = `${id}-input`, previewId = `${id}-preview`;
20
26
  useImperativeHandle(forwardedRef, () => root.current, []);
@@ -34,8 +40,12 @@ export const InlineEditable = /* @__PURE__ */ forwardRef(function InlineEditable
34
40
  const finish = (action, restoreFocus) => {
35
41
  if (!current.current.active || resolved.current || (composing.current && action === "commit") || editor.current?.matches(":disabled"))
36
42
  return;
37
- resolved.current = true;
38
43
  const next = current.current.draft;
44
+ const message = action === "commit" ? current.current.validate?.(next) : undefined;
45
+ setValidationError(message);
46
+ if (message)
47
+ return;
48
+ resolved.current = true;
39
49
  if (action === "commit") {
40
50
  if (next !== current.current.committed) {
41
51
  if (current.current.value === undefined)
@@ -62,10 +72,35 @@ export const InlineEditable = /* @__PURE__ */ forwardRef(function InlineEditable
62
72
  // A controlled host may refuse to close and allow a later deliberate attempt.
63
73
  queueMicrotask(() => { resolved.current = false; });
64
74
  };
75
+ useBrowserLayoutEffect(() => {
76
+ const panel = layer.current, anchor = preview.current;
77
+ if (!active || !floating || !panel || !anchor)
78
+ return;
79
+ const document = panel.ownerDocument, view = document.defaultView;
80
+ panel.showPopover();
81
+ let frame = 0;
82
+ const position = () => {
83
+ positionLayer(anchor, panel, "bottom", "end", true);
84
+ frame = view.requestAnimationFrame(position);
85
+ };
86
+ position();
87
+ const unregister = registerLayer(document, {
88
+ panel, anchor: () => anchor, close: () => finish("cancel", true), closeOnFocusOutside: false,
89
+ outside: () => { if (blurAction !== "none")
90
+ finish(blurAction, false); },
91
+ });
92
+ return () => {
93
+ unregister();
94
+ view.cancelAnimationFrame(frame);
95
+ if (panel.matches(":popover-open"))
96
+ panel.hidePopover();
97
+ };
98
+ }, [active, floating, blurAction]);
65
99
  useBrowserLayoutEffect(() => {
66
100
  let frame = 0;
67
101
  if (active && !wasActive.current) {
68
102
  setDraft(current.current.committed);
103
+ setValidationError(undefined);
69
104
  resolved.current = false;
70
105
  composing.current = false;
71
106
  const view = editor.current?.ownerDocument.defaultView;
@@ -106,7 +141,20 @@ export const InlineEditable = /* @__PURE__ */ forwardRef(function InlineEditable
106
141
  return () => { owner.removeEventListener("reset", reset); clearTimeout(timer); };
107
142
  }, [form]);
108
143
  const glyph = (kind) => _jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.75", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: kind === "save" ? _jsx("path", { d: "m5 12 4 4L19 6" }) : kind === "cancel" ? _jsx("path", { d: "m6 6 12 12M18 6 6 18" }) : _jsx(_Fragment, { children: _jsx("path", { d: "m15 5 4 4M4 20l4-1L20 7a2.1 2.1 0 0 0-3-3L5 16l-1 4Z" }) }) });
109
- const editorProps = { id: inputId, "aria-describedby": props["aria-describedby"], "aria-invalid": props["aria-invalid"], controlSize, disabled };
144
+ const errorId = `${inputId}-error`;
145
+ const editorProps = { id: inputId, inputMode, "aria-describedby": mergeDescriptions(props["aria-describedby"], active && validationError ? errorId : undefined),
146
+ "aria-invalid": active && validationError ? true : props["aria-invalid"], controlSize, disabled };
147
+ const editButton = _jsxs(Button, { ref: preview, id: previewId, type: "button", variant: previewVariant, size: controlSize, disabled: disabled, className: "flood-editable__preview", "aria-describedby": props["aria-describedby"], "aria-label": `${editLabel}: ${label}. ${committed || placeholder}`, "aria-hidden": active && floating || undefined, tabIndex: active && floating ? -1 : undefined, onClick: start, children: [_jsx("span", { className: "flood-editable__value", "data-empty": !committed || undefined, children: committed || placeholder }), _jsx(IconSlot, { size: controlSize, children: glyph("edit") })] });
148
+ const editorContent = active && _jsxs(_Fragment, { children: [_jsx(InputGroup, { ...editorProps, ref: editor, value: draft, placeholder: placeholder, size: fitDraft ? Math.max(4, (draft || placeholder).length + 1) : undefined, onChange: event => { setValidationError(undefined); setDraft(event.currentTarget.value); current.current.draft = event.currentTarget.value; }, onCompositionStart: () => { composing.current = true; }, onCompositionEnd: event => {
149
+ composing.current = false;
150
+ setDraft(event.currentTarget.value);
151
+ current.current.draft = event.currentTarget.value;
152
+ const document = event.currentTarget.ownerDocument;
153
+ if (blurAction !== "none" && !root.current?.contains(document.activeElement))
154
+ queueMicrotask(() => finish(blurAction, false));
155
+ }, end: showActions ? _jsxs("div", { className: "flood-editable__actions", children: [_jsx(IconButton, { type: "button", label: saveLabel, size: controlSize, variant: "ghost", onPointerDown: event => { if (event.pointerType === "mouse")
156
+ event.preventDefault(); }, onMouseDown: event => event.preventDefault(), onClick: () => finish("commit", true), children: glyph("save") }), _jsx(IconButton, { type: "button", label: cancelLabel, size: controlSize, variant: "ghost", onPointerDown: event => { if (event.pointerType === "mouse")
157
+ event.preventDefault(); }, onMouseDown: event => event.preventDefault(), onClick: () => finish("cancel", true), children: glyph("cancel") })] }) : undefined }), validationError && _jsx(Text, { as: "p", id: errorId, role: "alert", size: "helper", tone: "error", className: "flood-editable__error", children: validationError })] });
110
158
  return _jsxs("div", { ...props, ref: root, id: id, className: cx("flood-editable", className), "data-editing": active || undefined, "data-disabled": disabled || undefined, "data-readonly": readOnly || undefined, onKeyDown: event => {
111
159
  onKeyDown?.(event);
112
160
  if (event.defaultPrevented || !current.current.active || event.target !== editor.current || event.nativeEvent.isComposing || composing.current || event.keyCode === 229 || event.altKey || event.ctrlKey || event.metaKey || event.shiftKey)
@@ -120,16 +168,7 @@ export const InlineEditable = /* @__PURE__ */ forwardRef(function InlineEditable
120
168
  if (event.defaultPrevented || !current.current.active || event.currentTarget.contains(event.relatedTarget) || blurAction === "none")
121
169
  return;
122
170
  finish(blurAction, false);
123
- }, children: [_jsx("label", { htmlFor: active || readOnly ? inputId : previewId, className: cx("flood-label", hideLabel && "flood-visually-hidden"), children: label }), _jsx("input", { ref: bridge, type: "hidden", name: name, form: form, value: committed, disabled: disabled }), active ? _jsx(InputGroup, { ...editorProps, ref: editor, value: draft, placeholder: placeholder, onChange: event => { setDraft(event.currentTarget.value); current.current.draft = event.currentTarget.value; }, onCompositionStart: () => { composing.current = true; }, onCompositionEnd: event => {
124
- composing.current = false;
125
- setDraft(event.currentTarget.value);
126
- current.current.draft = event.currentTarget.value;
127
- const document = event.currentTarget.ownerDocument;
128
- if (blurAction !== "none" && !root.current?.contains(document.activeElement))
129
- queueMicrotask(() => finish(blurAction, false));
130
- }, end: showActions ? _jsxs("div", { className: "flood-editable__actions", children: [_jsx(IconButton, { type: "button", label: saveLabel, size: controlSize, variant: "ghost", onPointerDown: event => { if (event.pointerType === "mouse")
131
- event.preventDefault(); }, onMouseDown: event => event.preventDefault(), onClick: () => finish("commit", true), children: glyph("save") }), _jsx(IconButton, { type: "button", label: cancelLabel, size: controlSize, variant: "ghost", onPointerDown: event => { if (event.pointerType === "mouse")
132
- event.preventDefault(); }, onMouseDown: event => event.preventDefault(), onClick: () => finish("cancel", true), children: glyph("cancel") })] }) : undefined })
133
- : readOnly ? _jsx(Input, { ...editorProps, ref: editor, value: committed, readOnly: true })
134
- : _jsxs(Button, { ref: preview, id: previewId, type: "button", variant: "subtle", size: controlSize, disabled: disabled, className: "flood-editable__preview", "aria-describedby": props["aria-describedby"], "aria-label": `${editLabel}: ${label}. ${committed || placeholder}`, onClick: start, children: [_jsx("span", { className: "flood-editable__value", "data-empty": !committed || undefined, children: committed || placeholder }), _jsx(IconSlot, { size: controlSize, children: glyph("edit") })] })] });
171
+ }, children: [_jsx("label", { htmlFor: active || readOnly ? inputId : previewId, className: cx("flood-label", hideLabel && "flood-visually-hidden"), children: label }), _jsx("input", { ref: bridge, type: "hidden", name: name, form: form, value: committed, disabled: disabled }), readOnly ? _jsx(Input, { ...editorProps, ref: editor, value: committed, readOnly: true }) : (!active || floating) && editButton, active && (floating
172
+ ? _jsx("div", { ref: layer, popover: "manual", className: "flood-overlay-panel flood-editable__overlay", "data-fit-draft": fitDraft || undefined, style: { inlineSize: editorOverlayWidth }, children: editorContent })
173
+ : editorContent)] });
135
174
  });
@@ -1,7 +1,12 @@
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
  import { Button, IconButton, IconSlot, Spinner, VisuallyHidden } from "./index.js";
4
- export const ClipboardAction = /* @__PURE__ */ forwardRef(function ClipboardAction({ value, copyLabel = "Copy", copiedLabel = "Copied", errorLabel = "Failed", resetDelay = 2000, onCopied, onCopyError, className, disabled, size = "md", variant = "subtle", "aria-label": accessibleLabel, ...props }, ref) {
4
+ function ClipboardGlyph({ index, pending }) {
5
+ if (pending)
6
+ return _jsx(Spinner, { decorative: true });
7
+ return _jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.75", strokeLinecap: "round", strokeLinejoin: "round", children: index === 1 ? _jsx("path", { d: "m5 12 4 4L19 6" }) : index === 2 ? _jsx("path", { d: "m6 6 12 12M18 6 6 18" }) : _jsxs(_Fragment, { children: [_jsx("rect", { x: "8", y: "8", width: "12", height: "12", rx: "2" }), _jsx("path", { d: "M15 8V5a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2v8a2 2 0 0 0 2 2h3" })] }) });
8
+ }
9
+ export const ClipboardAction = /* @__PURE__ */ forwardRef(function ClipboardAction({ value, iconOnly, copyLabel = "Copy", copiedLabel = "Copied", errorLabel = "Failed", resetDelay = 2000, onCopied, onCopyError, className, disabled, size = "md", variant = "subtle", "aria-label": accessibleLabel, ...props }, ref) {
5
10
  if (!Number.isFinite(resetDelay) || resetDelay < 0)
6
11
  throw new Error("Flood CopyButton resetDelay must be finite and nonnegative");
7
12
  const [phase, setPhase] = useState("idle");
@@ -9,7 +14,9 @@ export const ClipboardAction = /* @__PURE__ */ forwardRef(function ClipboardActi
9
14
  const timer = useRef(undefined);
10
15
  const alive = useRef(true);
11
16
  useEffect(() => { alive.current = true; return () => { alive.current = false; clearTimeout(timer.current); }; }, []);
12
- const label = phase === "copied" ? copiedLabel : phase === "error" ? errorLabel : copyLabel;
17
+ const phaseIndex = phase === "copied" ? 1 : phase === "error" ? 2 : 0;
18
+ const labels = [copyLabel, copiedLabel, errorLabel];
19
+ const label = labels[phaseIndex];
13
20
  const copy = async (node) => {
14
21
  if (pending || node.matches(":disabled"))
15
22
  return;
@@ -38,7 +45,15 @@ export const ClipboardAction = /* @__PURE__ */ forwardRef(function ClipboardActi
38
45
  }
39
46
  onCopied?.();
40
47
  };
41
- return _jsxs(_Fragment, { children: [_jsx(Button, { ...props, ref: ref, type: "button", size: size, variant: variant, disabled: disabled || pending, "aria-label": accessibleLabel ? (phase === "idle" ? accessibleLabel : `${label}: ${accessibleLabel}`) : label, className: ["flood-copy-button", className].filter(Boolean).join(" "), onClick: event => { void copy(event.currentTarget); }, children: _jsx("span", { className: "flood-copy-button__labels", "aria-hidden": "true", children: [copyLabel, copiedLabel, errorLabel].map((text, index) => _jsxs("span", { "data-visible": index === (phase === "copied" ? 1 : phase === "error" ? 2 : 0) || undefined, children: [_jsx(IconSlot, { size: size, children: pending ? _jsx(Spinner, { decorative: true }) : _jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.75", strokeLinecap: "round", strokeLinejoin: "round", children: index === 1 ? _jsx("path", { d: "m5 12 4 4L19 6" }) : index === 2 ? _jsx("path", { d: "m6 6 12 12M18 6 6 18" }) : _jsxs(_Fragment, { children: [_jsx("rect", { x: "8", y: "8", width: "12", height: "12", rx: "2" }), _jsx("path", { d: "M15 8V5a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2v8a2 2 0 0 0 2 2h3" })] }) }) }), _jsx("span", { children: text })] }, index)) }) }), _jsx(VisuallyHidden, { role: "status", children: phase === "idle" ? "" : label })] });
48
+ const buttonLabel = accessibleLabel ? (phase === "idle" ? accessibleLabel : `${label}: ${accessibleLabel}`) : label;
49
+ const buttonProps = {
50
+ ...props, ref, type: "button", size, variant, disabled: disabled || pending,
51
+ className: ["flood-copy-button", className].filter(Boolean).join(" "),
52
+ onClick: (event) => { void copy(event.currentTarget); },
53
+ };
54
+ return _jsxs(_Fragment, { children: [iconOnly
55
+ ? _jsx(IconButton, { ...buttonProps, label: buttonLabel, title: props.title ?? buttonLabel, children: _jsx(ClipboardGlyph, { index: phaseIndex, pending: pending }) })
56
+ : _jsx(Button, { ...buttonProps, "aria-label": buttonLabel, children: _jsx("span", { className: "flood-copy-button__labels", "aria-hidden": "true", children: labels.map((text, index) => _jsxs("span", { "data-visible": index === phaseIndex || undefined, children: [_jsx(IconSlot, { size: size, children: _jsx(ClipboardGlyph, { index: index, pending: pending }) }), _jsx("span", { children: text })] }, index)) }) }), _jsx(VisuallyHidden, { role: "status", children: phase === "idle" ? "" : label })] });
42
57
  });
43
58
  function TimedToast({ item, closeLabel, onDismiss }) {
44
59
  const duration = item.duration ?? (item.action || item.status === "error" ? 0 : 5000);