@orianatech/pire 0.22.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.js CHANGED
@@ -865,6 +865,12 @@ function useMessage(key, override) {
865
865
  return override ?? messages[key];
866
866
  }
867
867
 
868
+ // src/components/utils/focusField.ts
869
+ function focusFieldOf(button) {
870
+ const control = button?.closest?.(".pire-control");
871
+ control?.querySelector(".pire-datesegment, .pire-input, input, textarea")?.focus();
872
+ }
873
+
868
874
  // src/components/forms/Input.tsx
869
875
  import { jsx as jsx17, jsxs as jsxs10 } from "react/jsx-runtime";
870
876
  function Input({
@@ -920,7 +926,17 @@ function Input({
920
926
  children: [
921
927
  icon ? /* @__PURE__ */ jsx17(Icon, { name: icon, size: 16, className: "pire-leading-icon" }) : null,
922
928
  /* @__PURE__ */ jsx17(AriaInput, { ref: inputRef, className: "pire-input", "data-numeric": numeric ? "true" : void 0 }),
923
- showsClear ? /* @__PURE__ */ jsx17(AriaButton4, { className: "pire-input-clear", onPress: () => handleChange(""), children: clearLabel ?? msg.inputClear }) : null,
929
+ showsClear ? /* @__PURE__ */ jsx17(
930
+ AriaButton4,
931
+ {
932
+ className: "pire-input-clear",
933
+ onPress: (event) => {
934
+ handleChange("");
935
+ focusFieldOf(event.target);
936
+ },
937
+ children: clearLabel ?? msg.inputClear
938
+ }
939
+ ) : null,
924
940
  suffix ? /* @__PURE__ */ jsx17("span", { className: "pire-affix", children: suffix }) : null
925
941
  ]
926
942
  }
@@ -1240,7 +1256,18 @@ function ComboBox({
1240
1256
  children: [
1241
1257
  /* @__PURE__ */ jsx23(Icon, { name: "search", size: 16, className: "pire-leading-icon" }),
1242
1258
  /* @__PURE__ */ jsx23(AriaInput2, { className: "pire-input", placeholder: rest.placeholder }),
1243
- showsClear ? /* @__PURE__ */ jsx23(AriaButton6, { className: "pire-input-clear", slot: null, onPress: clear, children: clearLabel ?? msg.inputClear }) : null,
1259
+ showsClear ? /* @__PURE__ */ jsx23(
1260
+ AriaButton6,
1261
+ {
1262
+ className: "pire-input-clear",
1263
+ slot: null,
1264
+ onPress: (event) => {
1265
+ clear();
1266
+ focusFieldOf(event.target);
1267
+ },
1268
+ children: clearLabel ?? msg.inputClear
1269
+ }
1270
+ ) : null,
1244
1271
  /* @__PURE__ */ jsx23(AriaButton6, { className: "pire-iconbtn", "data-size": "sm", "aria-label": msg.comboBoxShowSuggestions, style: { width: 20, height: 20 }, children: /* @__PURE__ */ jsx23(Icon, { name: "chevron-down", size: 16 }) })
1245
1272
  ]
1246
1273
  }
@@ -1424,6 +1451,7 @@ function MultiComboBox({
1424
1451
  }
1425
1452
 
1426
1453
  // src/components/forms/NumberField.tsx
1454
+ import * as React14 from "react";
1427
1455
  import {
1428
1456
  NumberField as AriaNumberField,
1429
1457
  Label as Label7,
@@ -1432,7 +1460,8 @@ import {
1432
1460
  Button as AriaButton8,
1433
1461
  Text as Text9,
1434
1462
  FieldError as FieldError7,
1435
- useLocale
1463
+ useLocale,
1464
+ NumberFieldStateContext
1436
1465
  } from "react-aria-components";
1437
1466
  import { jsx as jsx25, jsxs as jsxs18 } from "react/jsx-runtime";
1438
1467
  function decimalSeparatorFor(locale) {
@@ -1447,6 +1476,17 @@ function typeIntoInput(el, text) {
1447
1476
  const caret = start + text.length;
1448
1477
  el.setSelectionRange(caret, caret);
1449
1478
  }
1479
+ var SINGLE_DOT = /^(-?\d+)\.(\d+)$/;
1480
+ function couldBeGrouped(integerPart, fractionPart) {
1481
+ return fractionPart.length === 3 && integerPart.replace("-", "").length <= 3;
1482
+ }
1483
+ function localiseMachineDecimal(text, separator) {
1484
+ const match = SINGLE_DOT.exec(text.trim());
1485
+ if (!match || couldBeGrouped(match[1], match[2])) {
1486
+ return text;
1487
+ }
1488
+ return `${match[1]}${separator}${match[2]}`;
1489
+ }
1450
1490
  function toAriaNumber(value) {
1451
1491
  if (value === void 0) {
1452
1492
  return void 0;
@@ -1456,6 +1496,24 @@ function toAriaNumber(value) {
1456
1496
  }
1457
1497
  return typeof value === "number" ? value : Number(value);
1458
1498
  }
1499
+ function ClearButton({ label, onClear }) {
1500
+ const state = React14.useContext(NumberFieldStateContext);
1501
+ if (!state?.inputValue) {
1502
+ return null;
1503
+ }
1504
+ return /* @__PURE__ */ jsx25(
1505
+ AriaButton8,
1506
+ {
1507
+ className: "pire-input-clear",
1508
+ slot: null,
1509
+ onPress: (event) => {
1510
+ onClear();
1511
+ focusFieldOf(event.target);
1512
+ },
1513
+ children: label
1514
+ }
1515
+ );
1516
+ }
1459
1517
  function NumberField({
1460
1518
  label,
1461
1519
  suffix,
@@ -1470,12 +1528,24 @@ function NumberField({
1470
1528
  defaultValue,
1471
1529
  onChange,
1472
1530
  emptyValue,
1531
+ isClearable,
1532
+ clearLabel,
1473
1533
  ...rest
1474
1534
  }) {
1475
1535
  const msg = usePireMessages();
1476
1536
  const { locale } = useLocale();
1477
1537
  const empty = emptyValue === void 0 ? NaN : emptyValue;
1478
- const handleChange = onChange && ((next) => onChange(Number.isNaN(next) ? empty : next));
1538
+ const isControlled = value !== void 0;
1539
+ const [ownValue, setOwnValue] = React14.useState(() => toAriaNumber(defaultValue) ?? NaN);
1540
+ const takesOverValue = Boolean(isClearable) && !isControlled;
1541
+ const valueProps = takesOverValue ? { value: ownValue, defaultValue: void 0 } : { value: toAriaNumber(value), defaultValue: toAriaNumber(defaultValue) };
1542
+ const handleChange = (next) => {
1543
+ if (takesOverValue) {
1544
+ setOwnValue(next);
1545
+ }
1546
+ onChange?.(Number.isNaN(next) ? empty : next);
1547
+ };
1548
+ const showsClear = Boolean(isClearable) && !rest.isDisabled && !rest.isReadOnly;
1479
1549
  const handleDecimalKey = (event) => {
1480
1550
  if (event.key !== "." || event.ctrlKey || event.metaKey || event.altKey) {
1481
1551
  return;
@@ -1494,16 +1564,36 @@ function NumberField({
1494
1564
  event.preventDefault();
1495
1565
  typeIntoInput(event.currentTarget, separator);
1496
1566
  };
1567
+ const handlePaste = (event) => {
1568
+ if (rest.isReadOnly || rest.isDisabled) {
1569
+ return;
1570
+ }
1571
+ const separator = decimalSeparatorFor(locale);
1572
+ if (separator === ".") {
1573
+ return;
1574
+ }
1575
+ const { maximumFractionDigits } = new Intl.NumberFormat(locale, rest.formatOptions).resolvedOptions();
1576
+ if (!maximumFractionDigits) {
1577
+ return;
1578
+ }
1579
+ const pasted = event.clipboardData.getData("text");
1580
+ const localised = localiseMachineDecimal(pasted, separator);
1581
+ if (localised === pasted) {
1582
+ return;
1583
+ }
1584
+ event.preventDefault();
1585
+ event.stopPropagation();
1586
+ typeIntoInput(event.currentTarget, localised);
1587
+ };
1497
1588
  return /* @__PURE__ */ jsxs18(
1498
1589
  AriaNumberField,
1499
1590
  {
1500
1591
  className: cx("pire-field", className),
1501
1592
  style,
1502
1593
  validationBehavior: "aria",
1503
- value: toAriaNumber(value),
1504
- defaultValue: toAriaNumber(defaultValue),
1505
1594
  onChange: handleChange,
1506
1595
  ...rest,
1596
+ ...valueProps,
1507
1597
  children: [
1508
1598
  label ? /* @__PURE__ */ jsxs18(Label7, { className: "pire-field-label", children: [
1509
1599
  label,
@@ -1518,8 +1608,17 @@ function NumberField({
1518
1608
  "data-invalid": rest.isInvalid ? "true" : void 0,
1519
1609
  "data-readonly": rest.isReadOnly ? "true" : void 0,
1520
1610
  children: [
1521
- /* @__PURE__ */ jsx25(AriaInput4, { className: "pire-input", "data-numeric": "true", onKeyDown: handleDecimalKey }),
1611
+ /* @__PURE__ */ jsx25(
1612
+ AriaInput4,
1613
+ {
1614
+ className: "pire-input",
1615
+ "data-numeric": "true",
1616
+ onKeyDown: handleDecimalKey,
1617
+ onPasteCapture: handlePaste
1618
+ }
1619
+ ),
1522
1620
  suffix ? /* @__PURE__ */ jsx25("span", { className: "pire-affix", children: suffix }) : null,
1621
+ showsClear ? /* @__PURE__ */ jsx25(ClearButton, { label: clearLabel ?? msg.inputClear, onClear: () => handleChange(NaN) }) : null,
1523
1622
  hideStepper ? null : /* @__PURE__ */ jsxs18("span", { className: "pire-stepper", children: [
1524
1623
  /* @__PURE__ */ jsx25(AriaButton8, { slot: "increment", className: "pire-stepper-btn", "aria-label": msg.numberFieldIncrease, children: /* @__PURE__ */ jsx25(Icon, { name: "plus", size: 10 }) }),
1525
1624
  /* @__PURE__ */ jsx25(AriaButton8, { slot: "decrement", className: "pire-stepper-btn", "aria-label": msg.numberFieldDecrease, children: /* @__PURE__ */ jsx25(Icon, { name: "minus", size: 10 }) })
@@ -1535,6 +1634,7 @@ function NumberField({
1535
1634
  }
1536
1635
 
1537
1636
  // src/components/forms/DatePicker.tsx
1637
+ import * as React15 from "react";
1538
1638
  import {
1539
1639
  DatePicker as AriaDatePicker,
1540
1640
  Label as Label8,
@@ -1552,10 +1652,12 @@ import {
1552
1652
  CalendarCell,
1553
1653
  Heading as Heading2,
1554
1654
  Text as Text10,
1555
- FieldError as FieldError8
1655
+ FieldError as FieldError8,
1656
+ DatePickerStateContext
1556
1657
  } from "react-aria-components";
1658
+
1659
+ // src/components/forms/dateValues.ts
1557
1660
  import { parseDate } from "@internationalized/date";
1558
- import { jsx as jsx26, jsxs as jsxs19 } from "react/jsx-runtime";
1559
1661
  var ISO_DATE = /^(\d{4}-\d{2}-\d{2})(?:[T ].*)?$/;
1560
1662
  function coerceDateValue(value) {
1561
1663
  if (value === void 0) {
@@ -1577,6 +1679,30 @@ function coerceDateValue(value) {
1577
1679
  return null;
1578
1680
  }
1579
1681
  }
1682
+ function coerceBound(value) {
1683
+ return coerceDateValue(value) ?? void 0;
1684
+ }
1685
+
1686
+ // src/components/forms/DatePicker.tsx
1687
+ import { jsx as jsx26, jsxs as jsxs19 } from "react/jsx-runtime";
1688
+ function ClearButton2({ label }) {
1689
+ const state = React15.useContext(DatePickerStateContext);
1690
+ if (!state?.value) {
1691
+ return null;
1692
+ }
1693
+ return /* @__PURE__ */ jsx26(
1694
+ AriaButton9,
1695
+ {
1696
+ className: "pire-input-clear",
1697
+ slot: null,
1698
+ onPress: (event) => {
1699
+ state.setValue(null);
1700
+ focusFieldOf(event.target);
1701
+ },
1702
+ children: label
1703
+ }
1704
+ );
1705
+ }
1580
1706
  function DatePicker({
1581
1707
  label,
1582
1708
  description,
@@ -1589,18 +1715,25 @@ function DatePicker({
1589
1715
  defaultValue,
1590
1716
  onChange,
1591
1717
  valueFormat,
1718
+ minValue,
1719
+ maxValue,
1720
+ isClearable,
1721
+ clearLabel,
1592
1722
  ...rest
1593
1723
  }) {
1594
1724
  const msg = usePireMessages();
1595
- const handleChange = onChange && ((next) => onChange(
1725
+ const showsClear = Boolean(isClearable) && !rest.isDisabled && !rest.isReadOnly;
1726
+ const handleChange = (next) => onChange?.(
1596
1727
  valueFormat === "iso" && next ? next.toString() : next
1597
- ));
1728
+ );
1598
1729
  return /* @__PURE__ */ jsxs19(
1599
1730
  AriaDatePicker,
1600
1731
  {
1601
1732
  className: cx("pire-field", className),
1602
1733
  style,
1603
1734
  validationBehavior: "aria",
1735
+ minValue: coerceBound(minValue),
1736
+ maxValue: coerceBound(maxValue),
1604
1737
  value: coerceDateValue(value),
1605
1738
  defaultValue: coerceDateValue(defaultValue),
1606
1739
  onChange: handleChange,
@@ -1620,6 +1753,7 @@ function DatePicker({
1620
1753
  "data-readonly": rest.isReadOnly ? "true" : void 0,
1621
1754
  children: [
1622
1755
  /* @__PURE__ */ jsx26(DateInput, { className: "pire-datefield", children: (segment) => /* @__PURE__ */ jsx26(DateSegment, { segment, className: "pire-datesegment" }) }),
1756
+ showsClear ? /* @__PURE__ */ jsx26(ClearButton2, { label: clearLabel ?? msg.inputClear }) : null,
1623
1757
  /* @__PURE__ */ jsx26(AriaButton9, { className: "pire-iconbtn", "data-size": "sm", "aria-label": msg.datePickerOpenCalendar, style: { width: 24, height: 24 }, children: /* @__PURE__ */ jsx26(Icon, { name: "calendar", size: 16 }) })
1624
1758
  ]
1625
1759
  }
@@ -1643,6 +1777,7 @@ function DatePicker({
1643
1777
  }
1644
1778
 
1645
1779
  // src/components/forms/DateRangePicker.tsx
1780
+ import * as React16 from "react";
1646
1781
  import {
1647
1782
  DateRangePicker as AriaDateRangePicker,
1648
1783
  Label as Label9,
@@ -1660,11 +1795,46 @@ import {
1660
1795
  CalendarCell as CalendarCell2,
1661
1796
  Heading as Heading3,
1662
1797
  Text as Text11,
1663
- FieldError as FieldError9
1798
+ FieldError as FieldError9,
1799
+ DateRangePickerStateContext
1664
1800
  } from "react-aria-components";
1665
1801
  import { jsx as jsx27, jsxs as jsxs20 } from "react/jsx-runtime";
1802
+ function coerceRange(range) {
1803
+ if (range === void 0) {
1804
+ return void 0;
1805
+ }
1806
+ if (range === null) {
1807
+ return null;
1808
+ }
1809
+ const start = coerceDateValue(range.start) ?? null;
1810
+ const end = coerceDateValue(range.end) ?? null;
1811
+ return start === null && end === null ? null : { start, end };
1812
+ }
1666
1813
  function rangeLengthInDays(range) {
1667
- return range.end.toDate("UTC").getTime() / 864e5 - range.start.toDate("UTC").getTime() / 864e5 + 1;
1814
+ const start = coerceDateValue(range?.start) ?? null;
1815
+ const end = coerceDateValue(range?.end) ?? null;
1816
+ if (start === null || end === null) {
1817
+ return null;
1818
+ }
1819
+ return end.toDate("UTC").getTime() / 864e5 - start.toDate("UTC").getTime() / 864e5 + 1;
1820
+ }
1821
+ function ClearButton3({ label }) {
1822
+ const state = React16.useContext(DateRangePickerStateContext);
1823
+ if (!state?.value?.start && !state?.value?.end) {
1824
+ return null;
1825
+ }
1826
+ return /* @__PURE__ */ jsx27(
1827
+ AriaButton10,
1828
+ {
1829
+ className: "pire-input-clear",
1830
+ slot: null,
1831
+ onPress: (event) => {
1832
+ state.setValue(null);
1833
+ focusFieldOf(event.target);
1834
+ },
1835
+ children: label
1836
+ }
1837
+ );
1668
1838
  }
1669
1839
  function DateRangePicker({
1670
1840
  label,
@@ -1678,11 +1848,30 @@ function DateRangePicker({
1678
1848
  endName,
1679
1849
  className,
1680
1850
  style,
1851
+ value,
1852
+ defaultValue,
1853
+ onChange,
1854
+ valueFormat,
1855
+ minValue,
1856
+ maxValue,
1857
+ isClearable,
1858
+ clearLabel,
1681
1859
  ...rest
1682
1860
  }) {
1683
1861
  const msg = usePireMessages();
1684
- const current = rest.value ?? rest.defaultValue ?? null;
1685
- const tooLong = maxRangeDays != null && current != null && rangeLengthInDays(current) > maxRangeDays;
1862
+ const current = coerceRange(value) ?? coerceRange(defaultValue) ?? null;
1863
+ const span = current === null ? null : rangeLengthInDays(current);
1864
+ const tooLong = maxRangeDays != null && span != null && span > maxRangeDays;
1865
+ const emit = (next) => {
1866
+ if (!onChange) {
1867
+ return;
1868
+ }
1869
+ if (!next?.start || !next.end) {
1870
+ onChange(null);
1871
+ return;
1872
+ }
1873
+ onChange(valueFormat === "iso" ? { start: next.start.toString(), end: next.end.toString() } : { start: next.start, end: next.end });
1874
+ };
1686
1875
  return /* @__PURE__ */ jsxs20(
1687
1876
  AriaDateRangePicker,
1688
1877
  {
@@ -1691,8 +1880,13 @@ function DateRangePicker({
1691
1880
  startName,
1692
1881
  endName,
1693
1882
  validationBehavior: "aria",
1883
+ value: coerceRange(value),
1884
+ defaultValue: coerceRange(defaultValue),
1885
+ minValue: coerceBound(minValue),
1886
+ maxValue: coerceBound(maxValue),
1887
+ onChange: emit,
1694
1888
  ...rest,
1695
- isInvalid: rest.isInvalid || tooLong,
1889
+ isInvalid: rest.isInvalid || tooLong || void 0,
1696
1890
  children: [
1697
1891
  label ? /* @__PURE__ */ jsxs20(Label9, { className: "pire-field-label", children: [
1698
1892
  label,
@@ -1710,6 +1904,7 @@ function DateRangePicker({
1710
1904
  /* @__PURE__ */ jsx27(DateInput2, { slot: "start", className: "pire-datefield", children: (segment) => /* @__PURE__ */ jsx27(DateSegment2, { segment, className: "pire-datesegment" }) }),
1711
1905
  /* @__PURE__ */ jsx27("span", { "aria-hidden": "true", className: "pire-daterange-dash", children: "\u2013" }),
1712
1906
  /* @__PURE__ */ jsx27(DateInput2, { slot: "end", className: "pire-datefield", children: (segment) => /* @__PURE__ */ jsx27(DateSegment2, { segment, className: "pire-datesegment" }) }),
1907
+ isClearable && !rest.isDisabled && !rest.isReadOnly ? /* @__PURE__ */ jsx27(ClearButton3, { label: clearLabel ?? msg.inputClear }) : null,
1713
1908
  /* @__PURE__ */ jsx27(
1714
1909
  AriaButton10,
1715
1910
  {
@@ -1733,7 +1928,7 @@ function DateRangePicker({
1733
1928
  AriaButton10,
1734
1929
  {
1735
1930
  className: "pire-daterange-preset",
1736
- onPress: () => rest.onChange?.(preset.range()),
1931
+ onPress: () => emit(coerceRange(preset.range()) ?? null),
1737
1932
  children: preset.label
1738
1933
  },
1739
1934
  preset.id
@@ -1775,13 +1970,13 @@ function DateRangePicker({
1775
1970
  }
1776
1971
 
1777
1972
  // src/components/forms/ValueHelpField.tsx
1778
- import * as React16 from "react";
1973
+ import * as React19 from "react";
1779
1974
 
1780
1975
  // src/components/patterns/ValueHelpDialog.tsx
1781
- import * as React15 from "react";
1976
+ import * as React18 from "react";
1782
1977
 
1783
1978
  // src/components/feedback/Dialog.tsx
1784
- import * as React14 from "react";
1979
+ import * as React17 from "react";
1785
1980
  import { ModalOverlay, Modal, Dialog as AriaDialog3, Heading as Heading4 } from "react-aria-components";
1786
1981
  import { jsx as jsx28, jsxs as jsxs21 } from "react/jsx-runtime";
1787
1982
  function DialogTitle({ children, className }) {
@@ -1815,8 +2010,8 @@ function Dialog({
1815
2010
  onClose?.();
1816
2011
  onOpenChange?.(false);
1817
2012
  };
1818
- const parts = React14.Children.toArray(children);
1819
- const slotOf = (type) => parts.find((node) => React14.isValidElement(node) && node.type === type);
2013
+ const parts = React17.Children.toArray(children);
2014
+ const slotOf = (type) => parts.find((node) => React17.isValidElement(node) && node.type === type);
1820
2015
  const titleSlot = slotOf(DialogTitle);
1821
2016
  const descriptionSlot = slotOf(DialogDescription);
1822
2017
  const bodySlot = slotOf(DialogBody);
@@ -2073,11 +2268,11 @@ function ValueHelpDialog({
2073
2268
  onClose
2074
2269
  }) {
2075
2270
  const msg = usePireMessages();
2076
- const [query, setQuery] = React15.useState("");
2077
- React15.useEffect(() => {
2271
+ const [query, setQuery] = React18.useState("");
2272
+ React18.useEffect(() => {
2078
2273
  if (isOpen) setQuery("");
2079
2274
  }, [isOpen]);
2080
- const filtered = React15.useMemo(() => {
2275
+ const filtered = React18.useMemo(() => {
2081
2276
  if (isFilteredExternally) return rows;
2082
2277
  const q = query.trim().toLowerCase();
2083
2278
  if (!q) return rows;
@@ -2159,7 +2354,7 @@ function ValueHelpField({
2159
2354
  style
2160
2355
  }) {
2161
2356
  const msg = usePireMessages();
2162
- const [open, setOpen] = React16.useState(false);
2357
+ const [open, setOpen] = React19.useState(false);
2163
2358
  return /* @__PURE__ */ jsxs24("div", { className: cx(className), style, children: [
2164
2359
  /* @__PURE__ */ jsx32(
2165
2360
  Input,
@@ -2215,7 +2410,7 @@ function ValueHelpField({
2215
2410
  }
2216
2411
 
2217
2412
  // src/components/forms/FileUpload.tsx
2218
- import * as React17 from "react";
2413
+ import * as React20 from "react";
2219
2414
  import { FileTrigger, Text as Text12 } from "react-aria-components";
2220
2415
 
2221
2416
  // src/components/feedback/ProgressBar.tsx
@@ -2306,10 +2501,10 @@ function FileUpload({
2306
2501
  ...rest
2307
2502
  }) {
2308
2503
  const msg = usePireMessages();
2309
- const [uncontrolled, setUncontrolled] = React17.useState(defaultValue ?? []);
2504
+ const [uncontrolled, setUncontrolled] = React20.useState(defaultValue ?? []);
2310
2505
  const files = value ?? uncontrolled;
2311
- const created = React17.useRef([]);
2312
- React17.useEffect(() => () => {
2506
+ const created = React20.useRef([]);
2507
+ React20.useEffect(() => () => {
2313
2508
  for (const url of created.current) URL.revokeObjectURL(url);
2314
2509
  }, []);
2315
2510
  const commit = (next) => {
@@ -2421,7 +2616,7 @@ function ShellBar({
2421
2616
  }
2422
2617
 
2423
2618
  // src/components/navigation/SideNav.tsx
2424
- import * as React18 from "react";
2619
+ import * as React21 from "react";
2425
2620
  import {
2426
2621
  Button as AriaButton12,
2427
2622
  Disclosure,
@@ -2521,10 +2716,10 @@ function SideNav({
2521
2716
  const msg = usePireMessages();
2522
2717
  const activeParent = findActiveParent(groups, value);
2523
2718
  const isControlled = expandedIds != null;
2524
- const [ownExpanded, setOwnExpanded] = React18.useState(
2719
+ const [ownExpanded, setOwnExpanded] = React21.useState(
2525
2720
  () => new Set(defaultExpandedIds ?? (activeParent ? [activeParent] : []))
2526
2721
  );
2527
- const [lastValue, setLastValue] = React18.useState(value);
2722
+ const [lastValue, setLastValue] = React21.useState(value);
2528
2723
  if (value !== lastValue) {
2529
2724
  setLastValue(value);
2530
2725
  if (!isControlled && activeParent && !ownExpanded.has(activeParent)) {
@@ -2788,7 +2983,7 @@ function InlineMessage({ kind = "info", title, action, onClose, children, classN
2788
2983
  }
2789
2984
 
2790
2985
  // src/components/feedback/Toast.tsx
2791
- import * as React19 from "react";
2986
+ import * as React22 from "react";
2792
2987
  import { jsx as jsx42, jsxs as jsxs34 } from "react/jsx-runtime";
2793
2988
  var KIND_ICON2 = {
2794
2989
  info: "info",
@@ -2814,26 +3009,26 @@ function Toast({ kind = "success", onClose, position = "bottom-center", children
2814
3009
  }
2815
3010
  );
2816
3011
  }
2817
- var ToastContext = React19.createContext(null);
3012
+ var ToastContext = React22.createContext(null);
2818
3013
  function ToastProvider({ children, timeout = 6e3 }) {
2819
3014
  const msg = usePireMessages();
2820
- const [toasts, setToasts] = React19.useState([]);
2821
- const close = React19.useCallback((id) => setToasts((t) => t.filter((x) => x.id !== id)), []);
2822
- const add = React19.useCallback((toast) => {
3015
+ const [toasts, setToasts] = React22.useState([]);
3016
+ const close = React22.useCallback((id) => setToasts((t) => t.filter((x) => x.id !== id)), []);
3017
+ const add = React22.useCallback((toast) => {
2823
3018
  const id = Math.random().toString(36).slice(2);
2824
3019
  setToasts((t) => [...t, { ...toast, id }]);
2825
3020
  const ms = toast.timeout ?? timeout;
2826
3021
  if (ms > 0) setTimeout(() => close(id), ms);
2827
3022
  return id;
2828
3023
  }, [close, timeout]);
2829
- const value = React19.useMemo(() => ({ add, close }), [add, close]);
3024
+ const value = React22.useMemo(() => ({ add, close }), [add, close]);
2830
3025
  return /* @__PURE__ */ jsxs34(ToastContext.Provider, { value, children: [
2831
3026
  children,
2832
3027
  /* @__PURE__ */ jsx42("div", { className: "pire-toast-region", role: "region", "aria-label": msg.toastRegionLabel, children: toasts.map((t) => /* @__PURE__ */ jsx42(Toast, { kind: t.kind, onClose: () => close(t.id), style: { position: "static", transform: "none" }, children: t.message }, t.id)) })
2833
3028
  ] });
2834
3029
  }
2835
3030
  function useToast() {
2836
- const ctx = React19.useContext(ToastContext);
3031
+ const ctx = React22.useContext(ToastContext);
2837
3032
  if (!ctx) throw new Error("useToast must be used inside <ToastProvider>");
2838
3033
  return ctx;
2839
3034
  }
@@ -3183,14 +3378,14 @@ function BarChart({
3183
3378
  }
3184
3379
 
3185
3380
  // src/components/data/LineChart.tsx
3186
- import * as React20 from "react";
3381
+ import * as React23 from "react";
3187
3382
  import { jsx as jsx50, jsxs as jsxs42 } from "react/jsx-runtime";
3188
3383
  var PLOT_INSET = { top: 10, right: 14, bottom: 24, left: 58 };
3189
3384
  var MARKER_LIMIT = 20;
3190
- var useIsomorphicLayoutEffect2 = typeof window === "undefined" ? React20.useEffect : React20.useLayoutEffect;
3385
+ var useIsomorphicLayoutEffect2 = typeof window === "undefined" ? React23.useEffect : React23.useLayoutEffect;
3191
3386
  function useMeasuredWidth() {
3192
- const ref = React20.useRef(null);
3193
- const [width, setWidth] = React20.useState(0);
3387
+ const ref = React23.useRef(null);
3388
+ const [width, setWidth] = React23.useState(0);
3194
3389
  useIsomorphicLayoutEffect2(() => {
3195
3390
  const element = ref.current;
3196
3391
  if (!element) return;
@@ -3353,7 +3548,7 @@ function LineChart({
3353
3548
  }
3354
3549
 
3355
3550
  // src/components/data/DonutChart.tsx
3356
- import * as React21 from "react";
3551
+ import * as React24 from "react";
3357
3552
  import { jsx as jsx51, jsxs as jsxs43 } from "react/jsx-runtime";
3358
3553
  var SEGMENT_GAP_DEGREES = 1.5;
3359
3554
  function DonutChart({
@@ -3374,7 +3569,7 @@ function DonutChart({
3374
3569
  }) {
3375
3570
  const label = rest["aria-label"];
3376
3571
  const msg = usePireMessages();
3377
- const [hoveredIndex, setHoveredIndex] = React21.useState(null);
3572
+ const [hoveredIndex, setHoveredIndex] = React24.useState(null);
3378
3573
  warnOnDonutShape(series, categories.length, label);
3379
3574
  const values = (series[0]?.values ?? []).slice(0, categories.length);
3380
3575
  const segments = categories.map((category, index) => ({ category, index, value: values[index] ?? 0 })).filter((segment) => segment.value > 0);
@@ -3611,7 +3806,7 @@ function ConfirmDialog({
3611
3806
  }
3612
3807
 
3613
3808
  // src/components/patterns/CommandPalette.tsx
3614
- import * as React22 from "react";
3809
+ import * as React25 from "react";
3615
3810
  import {
3616
3811
  Autocomplete,
3617
3812
  ModalOverlay as ModalOverlay2,
@@ -3645,17 +3840,17 @@ function CommandPalette({
3645
3840
  style
3646
3841
  }) {
3647
3842
  const msg = usePireMessages();
3648
- const [query, setQuery] = React22.useState("");
3843
+ const [query, setQuery] = React25.useState("");
3649
3844
  const { contains } = useFilter({ sensitivity: "base" });
3650
- React22.useEffect(() => {
3845
+ React25.useEffect(() => {
3651
3846
  if (isOpen) setQuery("");
3652
3847
  }, [isOpen]);
3653
- const searchableById = React22.useMemo(() => {
3848
+ const searchableById = React25.useMemo(() => {
3654
3849
  const byId = /* @__PURE__ */ new Map();
3655
3850
  for (const group of groups) for (const item of group.items) byId.set(item.id, searchableText(item));
3656
3851
  return byId;
3657
3852
  }, [groups]);
3658
- const filter = React22.useCallback((textValue, inputValue, node) => {
3853
+ const filter = React25.useCallback((textValue, inputValue, node) => {
3659
3854
  if (node.parentKey === RESULTS_SECTION_KEY) return true;
3660
3855
  return contains(searchableById.get(String(node.key)) ?? textValue, inputValue);
3661
3856
  }, [contains, searchableById]);
@@ -3738,9 +3933,9 @@ function CommandPalette({
3738
3933
  );
3739
3934
  }
3740
3935
  function useCommandPaletteShortcut(onTrigger, { key = "k", isDisabled } = {}) {
3741
- const handler = React22.useRef(onTrigger);
3936
+ const handler = React25.useRef(onTrigger);
3742
3937
  handler.current = onTrigger;
3743
- React22.useEffect(() => {
3938
+ React25.useEffect(() => {
3744
3939
  if (isDisabled) return;
3745
3940
  const onKeyDown = (event) => {
3746
3941
  if (!(event.metaKey || event.ctrlKey) || event.altKey) return;
@@ -3794,7 +3989,7 @@ function EmptyState({ icon = "file-text", title, action, compact, children, clas
3794
3989
  }
3795
3990
 
3796
3991
  // src/components/patterns/FileDropZone.tsx
3797
- import * as React23 from "react";
3992
+ import * as React26 from "react";
3798
3993
  import { DropZone, FileTrigger as FileTrigger2, isFileDropItem } from "react-aria-components";
3799
3994
  import { jsx as jsx63, jsxs as jsxs54 } from "react/jsx-runtime";
3800
3995
  var isImage2 = (file) => file.type.startsWith("image/");
@@ -3824,10 +4019,10 @@ function FileDropZone({
3824
4019
  ...rest
3825
4020
  }) {
3826
4021
  const msg = usePireMessages();
3827
- const [uncontrolled, setUncontrolled] = React23.useState(defaultValue ?? []);
4022
+ const [uncontrolled, setUncontrolled] = React26.useState(defaultValue ?? []);
3828
4023
  const files = value ?? uncontrolled;
3829
- const created = React23.useRef([]);
3830
- React23.useEffect(() => () => {
4024
+ const created = React26.useRef([]);
4025
+ React26.useEffect(() => () => {
3831
4026
  for (const url of created.current) URL.revokeObjectURL(url);
3832
4027
  }, []);
3833
4028
  const commit = (next) => {