@orianatech/pire 0.23.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -982,6 +982,12 @@ function useMessage(key, override) {
982
982
  return override ?? messages[key];
983
983
  }
984
984
 
985
+ // src/components/utils/focusField.ts
986
+ function focusFieldOf(button) {
987
+ const control = button?.closest?.(".pire-control");
988
+ control?.querySelector(".pire-datesegment, .pire-input, input, textarea")?.focus();
989
+ }
990
+
985
991
  // src/components/forms/Input.tsx
986
992
  var import_jsx_runtime17 = require("react/jsx-runtime");
987
993
  function Input({
@@ -1037,7 +1043,17 @@ function Input({
1037
1043
  children: [
1038
1044
  icon ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(Icon, { name: icon, size: 16, className: "pire-leading-icon" }) : null,
1039
1045
  /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_react_aria_components9.Input, { ref: inputRef, className: "pire-input", "data-numeric": numeric ? "true" : void 0 }),
1040
- showsClear ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_react_aria_components9.Button, { className: "pire-input-clear", onPress: () => handleChange(""), children: clearLabel ?? msg.inputClear }) : null,
1046
+ showsClear ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
1047
+ import_react_aria_components9.Button,
1048
+ {
1049
+ className: "pire-input-clear",
1050
+ onPress: (event) => {
1051
+ handleChange("");
1052
+ focusFieldOf(event.target);
1053
+ },
1054
+ children: clearLabel ?? msg.inputClear
1055
+ }
1056
+ ) : null,
1041
1057
  suffix ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { className: "pire-affix", children: suffix }) : null
1042
1058
  ]
1043
1059
  }
@@ -1330,7 +1346,18 @@ function ComboBox({
1330
1346
  children: [
1331
1347
  /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(Icon, { name: "search", size: 16, className: "pire-leading-icon" }),
1332
1348
  /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_react_aria_components15.Input, { className: "pire-input", placeholder: rest.placeholder }),
1333
- showsClear ? /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_react_aria_components15.Button, { className: "pire-input-clear", slot: null, onPress: clear, children: clearLabel ?? msg.inputClear }) : null,
1349
+ showsClear ? /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
1350
+ import_react_aria_components15.Button,
1351
+ {
1352
+ className: "pire-input-clear",
1353
+ slot: null,
1354
+ onPress: (event) => {
1355
+ clear();
1356
+ focusFieldOf(event.target);
1357
+ },
1358
+ children: clearLabel ?? msg.inputClear
1359
+ }
1360
+ ) : null,
1334
1361
  /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_react_aria_components15.Button, { className: "pire-iconbtn", "data-size": "sm", "aria-label": msg.comboBoxShowSuggestions, style: { width: 20, height: 20 }, children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(Icon, { name: "chevron-down", size: 16 }) })
1335
1362
  ]
1336
1363
  }
@@ -1501,6 +1528,7 @@ function MultiComboBox({
1501
1528
  }
1502
1529
 
1503
1530
  // src/components/forms/NumberField.tsx
1531
+ var React14 = __toESM(require("react"), 1);
1504
1532
  var import_react_aria_components17 = require("react-aria-components");
1505
1533
  var import_jsx_runtime25 = require("react/jsx-runtime");
1506
1534
  function decimalSeparatorFor(locale) {
@@ -1515,6 +1543,17 @@ function typeIntoInput(el, text) {
1515
1543
  const caret = start + text.length;
1516
1544
  el.setSelectionRange(caret, caret);
1517
1545
  }
1546
+ var SINGLE_DOT = /^(-?\d+)\.(\d+)$/;
1547
+ function couldBeGrouped(integerPart, fractionPart) {
1548
+ return fractionPart.length === 3 && integerPart.replace("-", "").length <= 3;
1549
+ }
1550
+ function localiseMachineDecimal(text, separator) {
1551
+ const match = SINGLE_DOT.exec(text.trim());
1552
+ if (!match || couldBeGrouped(match[1], match[2])) {
1553
+ return text;
1554
+ }
1555
+ return `${match[1]}${separator}${match[2]}`;
1556
+ }
1518
1557
  function toAriaNumber(value) {
1519
1558
  if (value === void 0) {
1520
1559
  return void 0;
@@ -1524,6 +1563,24 @@ function toAriaNumber(value) {
1524
1563
  }
1525
1564
  return typeof value === "number" ? value : Number(value);
1526
1565
  }
1566
+ function ClearButton({ label, onClear }) {
1567
+ const state = React14.useContext(import_react_aria_components17.NumberFieldStateContext);
1568
+ if (!state?.inputValue) {
1569
+ return null;
1570
+ }
1571
+ return /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
1572
+ import_react_aria_components17.Button,
1573
+ {
1574
+ className: "pire-input-clear",
1575
+ slot: null,
1576
+ onPress: (event) => {
1577
+ onClear();
1578
+ focusFieldOf(event.target);
1579
+ },
1580
+ children: label
1581
+ }
1582
+ );
1583
+ }
1527
1584
  function NumberField({
1528
1585
  label,
1529
1586
  suffix,
@@ -1538,12 +1595,24 @@ function NumberField({
1538
1595
  defaultValue,
1539
1596
  onChange,
1540
1597
  emptyValue,
1598
+ isClearable,
1599
+ clearLabel,
1541
1600
  ...rest
1542
1601
  }) {
1543
1602
  const msg = usePireMessages();
1544
1603
  const { locale } = (0, import_react_aria_components17.useLocale)();
1545
1604
  const empty = emptyValue === void 0 ? NaN : emptyValue;
1546
- const handleChange = onChange && ((next) => onChange(Number.isNaN(next) ? empty : next));
1605
+ const isControlled = value !== void 0;
1606
+ const [ownValue, setOwnValue] = React14.useState(() => toAriaNumber(defaultValue) ?? NaN);
1607
+ const takesOverValue = Boolean(isClearable) && !isControlled;
1608
+ const valueProps = takesOverValue ? { value: ownValue, defaultValue: void 0 } : { value: toAriaNumber(value), defaultValue: toAriaNumber(defaultValue) };
1609
+ const handleChange = (next) => {
1610
+ if (takesOverValue) {
1611
+ setOwnValue(next);
1612
+ }
1613
+ onChange?.(Number.isNaN(next) ? empty : next);
1614
+ };
1615
+ const showsClear = Boolean(isClearable) && !rest.isDisabled && !rest.isReadOnly;
1547
1616
  const handleDecimalKey = (event) => {
1548
1617
  if (event.key !== "." || event.ctrlKey || event.metaKey || event.altKey) {
1549
1618
  return;
@@ -1562,16 +1631,36 @@ function NumberField({
1562
1631
  event.preventDefault();
1563
1632
  typeIntoInput(event.currentTarget, separator);
1564
1633
  };
1634
+ const handlePaste = (event) => {
1635
+ if (rest.isReadOnly || rest.isDisabled) {
1636
+ return;
1637
+ }
1638
+ const separator = decimalSeparatorFor(locale);
1639
+ if (separator === ".") {
1640
+ return;
1641
+ }
1642
+ const { maximumFractionDigits } = new Intl.NumberFormat(locale, rest.formatOptions).resolvedOptions();
1643
+ if (!maximumFractionDigits) {
1644
+ return;
1645
+ }
1646
+ const pasted = event.clipboardData.getData("text");
1647
+ const localised = localiseMachineDecimal(pasted, separator);
1648
+ if (localised === pasted) {
1649
+ return;
1650
+ }
1651
+ event.preventDefault();
1652
+ event.stopPropagation();
1653
+ typeIntoInput(event.currentTarget, localised);
1654
+ };
1565
1655
  return /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)(
1566
1656
  import_react_aria_components17.NumberField,
1567
1657
  {
1568
1658
  className: cx("pire-field", className),
1569
1659
  style,
1570
1660
  validationBehavior: "aria",
1571
- value: toAriaNumber(value),
1572
- defaultValue: toAriaNumber(defaultValue),
1573
1661
  onChange: handleChange,
1574
1662
  ...rest,
1663
+ ...valueProps,
1575
1664
  children: [
1576
1665
  label ? /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)(import_react_aria_components17.Label, { className: "pire-field-label", children: [
1577
1666
  label,
@@ -1586,8 +1675,17 @@ function NumberField({
1586
1675
  "data-invalid": rest.isInvalid ? "true" : void 0,
1587
1676
  "data-readonly": rest.isReadOnly ? "true" : void 0,
1588
1677
  children: [
1589
- /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(import_react_aria_components17.Input, { className: "pire-input", "data-numeric": "true", onKeyDown: handleDecimalKey }),
1678
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
1679
+ import_react_aria_components17.Input,
1680
+ {
1681
+ className: "pire-input",
1682
+ "data-numeric": "true",
1683
+ onKeyDown: handleDecimalKey,
1684
+ onPasteCapture: handlePaste
1685
+ }
1686
+ ),
1590
1687
  suffix ? /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("span", { className: "pire-affix", children: suffix }) : null,
1688
+ showsClear ? /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(ClearButton, { label: clearLabel ?? msg.inputClear, onClear: () => handleChange(NaN) }) : null,
1591
1689
  hideStepper ? null : /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("span", { className: "pire-stepper", children: [
1592
1690
  /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(import_react_aria_components17.Button, { slot: "increment", className: "pire-stepper-btn", "aria-label": msg.numberFieldIncrease, children: /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(Icon, { name: "plus", size: 10 }) }),
1593
1691
  /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(import_react_aria_components17.Button, { slot: "decrement", className: "pire-stepper-btn", "aria-label": msg.numberFieldDecrease, children: /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(Icon, { name: "minus", size: 10 }) })
@@ -1603,6 +1701,7 @@ function NumberField({
1603
1701
  }
1604
1702
 
1605
1703
  // src/components/forms/DatePicker.tsx
1704
+ var React15 = __toESM(require("react"), 1);
1606
1705
  var import_react_aria_components18 = require("react-aria-components");
1607
1706
 
1608
1707
  // src/components/forms/dateValues.ts
@@ -1634,6 +1733,24 @@ function coerceBound(value) {
1634
1733
 
1635
1734
  // src/components/forms/DatePicker.tsx
1636
1735
  var import_jsx_runtime26 = require("react/jsx-runtime");
1736
+ function ClearButton2({ label }) {
1737
+ const state = React15.useContext(import_react_aria_components18.DatePickerStateContext);
1738
+ if (!state?.value) {
1739
+ return null;
1740
+ }
1741
+ return /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
1742
+ import_react_aria_components18.Button,
1743
+ {
1744
+ className: "pire-input-clear",
1745
+ slot: null,
1746
+ onPress: (event) => {
1747
+ state.setValue(null);
1748
+ focusFieldOf(event.target);
1749
+ },
1750
+ children: label
1751
+ }
1752
+ );
1753
+ }
1637
1754
  function DatePicker({
1638
1755
  label,
1639
1756
  description,
@@ -1648,22 +1765,25 @@ function DatePicker({
1648
1765
  valueFormat,
1649
1766
  minValue,
1650
1767
  maxValue,
1768
+ isClearable,
1769
+ clearLabel,
1651
1770
  ...rest
1652
1771
  }) {
1653
1772
  const msg = usePireMessages();
1654
- const handleChange = onChange && ((next) => onChange(
1773
+ const showsClear = Boolean(isClearable) && !rest.isDisabled && !rest.isReadOnly;
1774
+ const handleChange = (next) => onChange?.(
1655
1775
  valueFormat === "iso" && next ? next.toString() : next
1656
- ));
1776
+ );
1657
1777
  return /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)(
1658
1778
  import_react_aria_components18.DatePicker,
1659
1779
  {
1660
1780
  className: cx("pire-field", className),
1661
1781
  style,
1662
1782
  validationBehavior: "aria",
1663
- value: coerceDateValue(value),
1664
- defaultValue: coerceDateValue(defaultValue),
1665
1783
  minValue: coerceBound(minValue),
1666
1784
  maxValue: coerceBound(maxValue),
1785
+ value: coerceDateValue(value),
1786
+ defaultValue: coerceDateValue(defaultValue),
1667
1787
  onChange: handleChange,
1668
1788
  ...rest,
1669
1789
  children: [
@@ -1681,6 +1801,7 @@ function DatePicker({
1681
1801
  "data-readonly": rest.isReadOnly ? "true" : void 0,
1682
1802
  children: [
1683
1803
  /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(import_react_aria_components18.DateInput, { className: "pire-datefield", children: (segment) => /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(import_react_aria_components18.DateSegment, { segment, className: "pire-datesegment" }) }),
1804
+ showsClear ? /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(ClearButton2, { label: clearLabel ?? msg.inputClear }) : null,
1684
1805
  /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(import_react_aria_components18.Button, { className: "pire-iconbtn", "data-size": "sm", "aria-label": msg.datePickerOpenCalendar, style: { width: 24, height: 24 }, children: /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(Icon, { name: "calendar", size: 16 }) })
1685
1806
  ]
1686
1807
  }
@@ -1704,6 +1825,7 @@ function DatePicker({
1704
1825
  }
1705
1826
 
1706
1827
  // src/components/forms/DateRangePicker.tsx
1828
+ var React16 = __toESM(require("react"), 1);
1707
1829
  var import_react_aria_components19 = require("react-aria-components");
1708
1830
  var import_jsx_runtime27 = require("react/jsx-runtime");
1709
1831
  function coerceRange(range) {
@@ -1725,6 +1847,24 @@ function rangeLengthInDays(range) {
1725
1847
  }
1726
1848
  return end.toDate("UTC").getTime() / 864e5 - start.toDate("UTC").getTime() / 864e5 + 1;
1727
1849
  }
1850
+ function ClearButton3({ label }) {
1851
+ const state = React16.useContext(import_react_aria_components19.DateRangePickerStateContext);
1852
+ if (!state?.value?.start && !state?.value?.end) {
1853
+ return null;
1854
+ }
1855
+ return /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
1856
+ import_react_aria_components19.Button,
1857
+ {
1858
+ className: "pire-input-clear",
1859
+ slot: null,
1860
+ onPress: (event) => {
1861
+ state.setValue(null);
1862
+ focusFieldOf(event.target);
1863
+ },
1864
+ children: label
1865
+ }
1866
+ );
1867
+ }
1728
1868
  function DateRangePicker({
1729
1869
  label,
1730
1870
  description,
@@ -1743,6 +1883,8 @@ function DateRangePicker({
1743
1883
  valueFormat,
1744
1884
  minValue,
1745
1885
  maxValue,
1886
+ isClearable,
1887
+ clearLabel,
1746
1888
  ...rest
1747
1889
  }) {
1748
1890
  const msg = usePireMessages();
@@ -1771,7 +1913,7 @@ function DateRangePicker({
1771
1913
  defaultValue: coerceRange(defaultValue),
1772
1914
  minValue: coerceBound(minValue),
1773
1915
  maxValue: coerceBound(maxValue),
1774
- onChange: onChange && emit,
1916
+ onChange: emit,
1775
1917
  ...rest,
1776
1918
  isInvalid: rest.isInvalid || tooLong || void 0,
1777
1919
  children: [
@@ -1791,6 +1933,7 @@ function DateRangePicker({
1791
1933
  /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_react_aria_components19.DateInput, { slot: "start", className: "pire-datefield", children: (segment) => /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_react_aria_components19.DateSegment, { segment, className: "pire-datesegment" }) }),
1792
1934
  /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { "aria-hidden": "true", className: "pire-daterange-dash", children: "\u2013" }),
1793
1935
  /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_react_aria_components19.DateInput, { slot: "end", className: "pire-datefield", children: (segment) => /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_react_aria_components19.DateSegment, { segment, className: "pire-datesegment" }) }),
1936
+ isClearable && !rest.isDisabled && !rest.isReadOnly ? /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(ClearButton3, { label: clearLabel ?? msg.inputClear }) : null,
1794
1937
  /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
1795
1938
  import_react_aria_components19.Button,
1796
1939
  {
@@ -1856,13 +1999,13 @@ function DateRangePicker({
1856
1999
  }
1857
2000
 
1858
2001
  // src/components/forms/ValueHelpField.tsx
1859
- var React16 = __toESM(require("react"), 1);
2002
+ var React19 = __toESM(require("react"), 1);
1860
2003
 
1861
2004
  // src/components/patterns/ValueHelpDialog.tsx
1862
- var React15 = __toESM(require("react"), 1);
2005
+ var React18 = __toESM(require("react"), 1);
1863
2006
 
1864
2007
  // src/components/feedback/Dialog.tsx
1865
- var React14 = __toESM(require("react"), 1);
2008
+ var React17 = __toESM(require("react"), 1);
1866
2009
  var import_react_aria_components20 = require("react-aria-components");
1867
2010
  var import_jsx_runtime28 = require("react/jsx-runtime");
1868
2011
  function DialogTitle({ children, className }) {
@@ -1896,8 +2039,8 @@ function Dialog({
1896
2039
  onClose?.();
1897
2040
  onOpenChange?.(false);
1898
2041
  };
1899
- const parts = React14.Children.toArray(children);
1900
- const slotOf = (type) => parts.find((node) => React14.isValidElement(node) && node.type === type);
2042
+ const parts = React17.Children.toArray(children);
2043
+ const slotOf = (type) => parts.find((node) => React17.isValidElement(node) && node.type === type);
1901
2044
  const titleSlot = slotOf(DialogTitle);
1902
2045
  const descriptionSlot = slotOf(DialogDescription);
1903
2046
  const bodySlot = slotOf(DialogBody);
@@ -2146,11 +2289,11 @@ function ValueHelpDialog({
2146
2289
  onClose
2147
2290
  }) {
2148
2291
  const msg = usePireMessages();
2149
- const [query, setQuery] = React15.useState("");
2150
- React15.useEffect(() => {
2292
+ const [query, setQuery] = React18.useState("");
2293
+ React18.useEffect(() => {
2151
2294
  if (isOpen) setQuery("");
2152
2295
  }, [isOpen]);
2153
- const filtered = React15.useMemo(() => {
2296
+ const filtered = React18.useMemo(() => {
2154
2297
  if (isFilteredExternally) return rows;
2155
2298
  const q = query.trim().toLowerCase();
2156
2299
  if (!q) return rows;
@@ -2232,7 +2375,7 @@ function ValueHelpField({
2232
2375
  style
2233
2376
  }) {
2234
2377
  const msg = usePireMessages();
2235
- const [open, setOpen] = React16.useState(false);
2378
+ const [open, setOpen] = React19.useState(false);
2236
2379
  return /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("div", { className: cx(className), style, children: [
2237
2380
  /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
2238
2381
  Input,
@@ -2288,7 +2431,7 @@ function ValueHelpField({
2288
2431
  }
2289
2432
 
2290
2433
  // src/components/forms/FileUpload.tsx
2291
- var React17 = __toESM(require("react"), 1);
2434
+ var React20 = __toESM(require("react"), 1);
2292
2435
  var import_react_aria_components23 = require("react-aria-components");
2293
2436
 
2294
2437
  // src/components/feedback/ProgressBar.tsx
@@ -2379,10 +2522,10 @@ function FileUpload({
2379
2522
  ...rest
2380
2523
  }) {
2381
2524
  const msg = usePireMessages();
2382
- const [uncontrolled, setUncontrolled] = React17.useState(defaultValue ?? []);
2525
+ const [uncontrolled, setUncontrolled] = React20.useState(defaultValue ?? []);
2383
2526
  const files = value ?? uncontrolled;
2384
- const created = React17.useRef([]);
2385
- React17.useEffect(() => () => {
2527
+ const created = React20.useRef([]);
2528
+ React20.useEffect(() => () => {
2386
2529
  for (const url of created.current) URL.revokeObjectURL(url);
2387
2530
  }, []);
2388
2531
  const commit = (next) => {
@@ -2494,7 +2637,7 @@ function ShellBar({
2494
2637
  }
2495
2638
 
2496
2639
  // src/components/navigation/SideNav.tsx
2497
- var React18 = __toESM(require("react"), 1);
2640
+ var React21 = __toESM(require("react"), 1);
2498
2641
  var import_react_aria_components26 = require("react-aria-components");
2499
2642
 
2500
2643
  // src/components/navigation/Menu.tsx
@@ -2581,10 +2724,10 @@ function SideNav({
2581
2724
  const msg = usePireMessages();
2582
2725
  const activeParent = findActiveParent(groups, value);
2583
2726
  const isControlled = expandedIds != null;
2584
- const [ownExpanded, setOwnExpanded] = React18.useState(
2727
+ const [ownExpanded, setOwnExpanded] = React21.useState(
2585
2728
  () => new Set(defaultExpandedIds ?? (activeParent ? [activeParent] : []))
2586
2729
  );
2587
- const [lastValue, setLastValue] = React18.useState(value);
2730
+ const [lastValue, setLastValue] = React21.useState(value);
2588
2731
  if (value !== lastValue) {
2589
2732
  setLastValue(value);
2590
2733
  if (!isControlled && activeParent && !ownExpanded.has(activeParent)) {
@@ -2848,7 +2991,7 @@ function InlineMessage({ kind = "info", title, action, onClose, children, classN
2848
2991
  }
2849
2992
 
2850
2993
  // src/components/feedback/Toast.tsx
2851
- var React19 = __toESM(require("react"), 1);
2994
+ var React22 = __toESM(require("react"), 1);
2852
2995
  var import_jsx_runtime42 = require("react/jsx-runtime");
2853
2996
  var KIND_ICON2 = {
2854
2997
  info: "info",
@@ -2874,26 +3017,26 @@ function Toast({ kind = "success", onClose, position = "bottom-center", children
2874
3017
  }
2875
3018
  );
2876
3019
  }
2877
- var ToastContext = React19.createContext(null);
3020
+ var ToastContext = React22.createContext(null);
2878
3021
  function ToastProvider({ children, timeout = 6e3 }) {
2879
3022
  const msg = usePireMessages();
2880
- const [toasts, setToasts] = React19.useState([]);
2881
- const close = React19.useCallback((id) => setToasts((t) => t.filter((x) => x.id !== id)), []);
2882
- const add = React19.useCallback((toast) => {
3023
+ const [toasts, setToasts] = React22.useState([]);
3024
+ const close = React22.useCallback((id) => setToasts((t) => t.filter((x) => x.id !== id)), []);
3025
+ const add = React22.useCallback((toast) => {
2883
3026
  const id = Math.random().toString(36).slice(2);
2884
3027
  setToasts((t) => [...t, { ...toast, id }]);
2885
3028
  const ms = toast.timeout ?? timeout;
2886
3029
  if (ms > 0) setTimeout(() => close(id), ms);
2887
3030
  return id;
2888
3031
  }, [close, timeout]);
2889
- const value = React19.useMemo(() => ({ add, close }), [add, close]);
3032
+ const value = React22.useMemo(() => ({ add, close }), [add, close]);
2890
3033
  return /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(ToastContext.Provider, { value, children: [
2891
3034
  children,
2892
3035
  /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("div", { className: "pire-toast-region", role: "region", "aria-label": msg.toastRegionLabel, children: toasts.map((t) => /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(Toast, { kind: t.kind, onClose: () => close(t.id), style: { position: "static", transform: "none" }, children: t.message }, t.id)) })
2893
3036
  ] });
2894
3037
  }
2895
3038
  function useToast() {
2896
- const ctx = React19.useContext(ToastContext);
3039
+ const ctx = React22.useContext(ToastContext);
2897
3040
  if (!ctx) throw new Error("useToast must be used inside <ToastProvider>");
2898
3041
  return ctx;
2899
3042
  }
@@ -3243,14 +3386,14 @@ function BarChart({
3243
3386
  }
3244
3387
 
3245
3388
  // src/components/data/LineChart.tsx
3246
- var React20 = __toESM(require("react"), 1);
3389
+ var React23 = __toESM(require("react"), 1);
3247
3390
  var import_jsx_runtime50 = require("react/jsx-runtime");
3248
3391
  var PLOT_INSET = { top: 10, right: 14, bottom: 24, left: 58 };
3249
3392
  var MARKER_LIMIT = 20;
3250
- var useIsomorphicLayoutEffect2 = typeof window === "undefined" ? React20.useEffect : React20.useLayoutEffect;
3393
+ var useIsomorphicLayoutEffect2 = typeof window === "undefined" ? React23.useEffect : React23.useLayoutEffect;
3251
3394
  function useMeasuredWidth() {
3252
- const ref = React20.useRef(null);
3253
- const [width, setWidth] = React20.useState(0);
3395
+ const ref = React23.useRef(null);
3396
+ const [width, setWidth] = React23.useState(0);
3254
3397
  useIsomorphicLayoutEffect2(() => {
3255
3398
  const element = ref.current;
3256
3399
  if (!element) return;
@@ -3413,7 +3556,7 @@ function LineChart({
3413
3556
  }
3414
3557
 
3415
3558
  // src/components/data/DonutChart.tsx
3416
- var React21 = __toESM(require("react"), 1);
3559
+ var React24 = __toESM(require("react"), 1);
3417
3560
  var import_jsx_runtime51 = require("react/jsx-runtime");
3418
3561
  var SEGMENT_GAP_DEGREES = 1.5;
3419
3562
  function DonutChart({
@@ -3434,7 +3577,7 @@ function DonutChart({
3434
3577
  }) {
3435
3578
  const label = rest["aria-label"];
3436
3579
  const msg = usePireMessages();
3437
- const [hoveredIndex, setHoveredIndex] = React21.useState(null);
3580
+ const [hoveredIndex, setHoveredIndex] = React24.useState(null);
3438
3581
  warnOnDonutShape(series, categories.length, label);
3439
3582
  const values = (series[0]?.values ?? []).slice(0, categories.length);
3440
3583
  const segments = categories.map((category, index) => ({ category, index, value: values[index] ?? 0 })).filter((segment) => segment.value > 0);
@@ -3671,7 +3814,7 @@ function ConfirmDialog({
3671
3814
  }
3672
3815
 
3673
3816
  // src/components/patterns/CommandPalette.tsx
3674
- var React22 = __toESM(require("react"), 1);
3817
+ var React25 = __toESM(require("react"), 1);
3675
3818
  var import_react_aria_components31 = require("react-aria-components");
3676
3819
  var import_jsx_runtime60 = require("react/jsx-runtime");
3677
3820
  var RESULTS_SECTION_KEY = "__pire_results__";
@@ -3694,17 +3837,17 @@ function CommandPalette({
3694
3837
  style
3695
3838
  }) {
3696
3839
  const msg = usePireMessages();
3697
- const [query, setQuery] = React22.useState("");
3840
+ const [query, setQuery] = React25.useState("");
3698
3841
  const { contains } = (0, import_react_aria_components31.useFilter)({ sensitivity: "base" });
3699
- React22.useEffect(() => {
3842
+ React25.useEffect(() => {
3700
3843
  if (isOpen) setQuery("");
3701
3844
  }, [isOpen]);
3702
- const searchableById = React22.useMemo(() => {
3845
+ const searchableById = React25.useMemo(() => {
3703
3846
  const byId = /* @__PURE__ */ new Map();
3704
3847
  for (const group of groups) for (const item of group.items) byId.set(item.id, searchableText(item));
3705
3848
  return byId;
3706
3849
  }, [groups]);
3707
- const filter = React22.useCallback((textValue, inputValue, node) => {
3850
+ const filter = React25.useCallback((textValue, inputValue, node) => {
3708
3851
  if (node.parentKey === RESULTS_SECTION_KEY) return true;
3709
3852
  return contains(searchableById.get(String(node.key)) ?? textValue, inputValue);
3710
3853
  }, [contains, searchableById]);
@@ -3787,9 +3930,9 @@ function CommandPalette({
3787
3930
  );
3788
3931
  }
3789
3932
  function useCommandPaletteShortcut(onTrigger, { key = "k", isDisabled } = {}) {
3790
- const handler = React22.useRef(onTrigger);
3933
+ const handler = React25.useRef(onTrigger);
3791
3934
  handler.current = onTrigger;
3792
- React22.useEffect(() => {
3935
+ React25.useEffect(() => {
3793
3936
  if (isDisabled) return;
3794
3937
  const onKeyDown = (event) => {
3795
3938
  if (!(event.metaKey || event.ctrlKey) || event.altKey) return;
@@ -3843,7 +3986,7 @@ function EmptyState({ icon = "file-text", title, action, compact, children, clas
3843
3986
  }
3844
3987
 
3845
3988
  // src/components/patterns/FileDropZone.tsx
3846
- var React23 = __toESM(require("react"), 1);
3989
+ var React26 = __toESM(require("react"), 1);
3847
3990
  var import_react_aria_components33 = require("react-aria-components");
3848
3991
  var import_jsx_runtime63 = require("react/jsx-runtime");
3849
3992
  var isImage2 = (file) => file.type.startsWith("image/");
@@ -3873,10 +4016,10 @@ function FileDropZone({
3873
4016
  ...rest
3874
4017
  }) {
3875
4018
  const msg = usePireMessages();
3876
- const [uncontrolled, setUncontrolled] = React23.useState(defaultValue ?? []);
4019
+ const [uncontrolled, setUncontrolled] = React26.useState(defaultValue ?? []);
3877
4020
  const files = value ?? uncontrolled;
3878
- const created = React23.useRef([]);
3879
- React23.useEffect(() => () => {
4021
+ const created = React26.useRef([]);
4022
+ React26.useEffect(() => () => {
3880
4023
  for (const url of created.current) URL.revokeObjectURL(url);
3881
4024
  }, []);
3882
4025
  const commit = (next) => {