@geomak/ui 1.7.3 → 1.7.5

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
@@ -1059,16 +1059,28 @@ function FadingBase({
1059
1059
  );
1060
1060
  }
1061
1061
  function List2({ items, onItemClick, activeKey }) {
1062
- return /* @__PURE__ */ jsxRuntime.jsx("div", { role: "listbox", children: items.map((item) => /* @__PURE__ */ jsxRuntime.jsx(
1063
- "div",
1064
- {
1065
- role: "option",
1066
- "aria-selected": activeKey === item.key,
1067
- className: `hover:bg-ice-dark dark:hover:bg-independence cursor-pointer p-3 border-b border-b-ice-dark dark:border-b-independence transition-all duration-300 ${activeKey === item.key ? "bg-ice-dark dark:bg-independence" : ""}`,
1068
- onClick: () => onItemClick(item),
1069
- children: item.label
1070
- },
1071
- item.key
1062
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { role: "listbox", children: items.map((item) => (
1063
+ // tabIndex + Enter/Space onKeyDown makes each option
1064
+ // keyboard-activatable. Previously the items were only mouse-
1065
+ // clickable — keyboard-only users couldn't select anything.
1066
+ /* @__PURE__ */ jsxRuntime.jsx(
1067
+ "div",
1068
+ {
1069
+ role: "option",
1070
+ "aria-selected": activeKey === item.key,
1071
+ tabIndex: 0,
1072
+ className: `hover:bg-ice-dark dark:hover:bg-independence cursor-pointer p-3 border-b border-b-ice-dark dark:border-b-independence transition-all duration-300 focus:outline-none focus-visible:ring-2 focus-visible:ring-accent ${activeKey === item.key ? "bg-ice-dark dark:bg-independence" : ""}`,
1073
+ onClick: () => onItemClick(item),
1074
+ onKeyDown: (e) => {
1075
+ if (e.key === "Enter" || e.key === " ") {
1076
+ e.preventDefault();
1077
+ onItemClick(item);
1078
+ }
1079
+ },
1080
+ children: item.label
1081
+ },
1082
+ item.key
1083
+ )
1072
1084
  )) });
1073
1085
  }
1074
1086
  function ScalableContainer({
@@ -1574,6 +1586,8 @@ function Dropdown({
1574
1586
  const [hoveredItem, setHoveredItem] = React9.useState(null);
1575
1587
  const [searchTerm, setSearchTerm] = React9.useState("");
1576
1588
  const [innerItems, setInnerItems] = React9.useState([]);
1589
+ const errorId = React9.useId();
1590
+ const hasError = errorMessage != null;
1577
1591
  React9.useEffect(() => {
1578
1592
  setInnerItems(items);
1579
1593
  }, [items]);
@@ -1626,10 +1640,18 @@ function Dropdown({
1626
1640
  role: "combobox",
1627
1641
  "aria-expanded": open,
1628
1642
  "aria-haspopup": "listbox",
1643
+ "aria-invalid": hasError || void 0,
1644
+ "aria-describedby": hasError ? errorId : void 0,
1629
1645
  style,
1630
1646
  className: `flex items-center justify-between relative h-9 rounded-lg cursor-pointer select-none ${disabled ? "cursor-not-allowed bg-disabled" : "bg-white"}`,
1631
1647
  tabIndex: disabled ? -1 : 0,
1632
- onKeyDown: (e) => e.key === "Enter" && !disabled && setOpen(true),
1648
+ onKeyDown: (e) => {
1649
+ if (disabled) return;
1650
+ if (e.key === "Enter" || e.key === " " || e.key === "ArrowDown" || e.key === "ArrowUp") {
1651
+ e.preventDefault();
1652
+ setOpen(true);
1653
+ }
1654
+ },
1633
1655
  children: [
1634
1656
  /* @__PURE__ */ jsxRuntime.jsx(
1635
1657
  "div",
@@ -1671,34 +1693,49 @@ function Dropdown({
1671
1693
  placeholder: "Search..."
1672
1694
  }
1673
1695
  ) }),
1674
- /* @__PURE__ */ jsxRuntime.jsx("div", { role: "listbox", "aria-multiselectable": isMultiselect, className: "max-h-40 overflow-y-auto", children: innerItems.map((item, idx) => /* @__PURE__ */ jsxRuntime.jsxs(
1675
- "div",
1676
- {
1677
- role: "option",
1678
- "aria-selected": isSelected(item.key),
1679
- "aria-rowindex": idx,
1680
- className: `flex items-center justify-between p-2 hover:bg-prussian-blue hover:text-white transition-all duration-150 text-sm text-prussian-blue rounded-lg cursor-pointer ${selectedItems.includes(item.key) ? "bg-ice-dark" : ""}`,
1681
- onClick: () => selectItem(item.key),
1682
- onMouseEnter: () => setHoveredItem(item.key),
1683
- onMouseLeave: () => setHoveredItem(null),
1684
- children: [
1685
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 text-xs", children: [
1686
- item.icon && /* @__PURE__ */ jsxRuntime.jsx("div", { children: item.icon }),
1687
- item.label
1688
- ] }),
1689
- isSelected(item.key) && /* @__PURE__ */ jsxRuntime.jsx("svg", { width: "16", height: "16", viewBox: "0 0 20 20", fill: "none", children: /* @__PURE__ */ jsxRuntime.jsx(
1690
- "path",
1691
- {
1692
- d: "M4 10l4.5 4.5L16 6",
1693
- stroke: hoveredItem === item.key ? "#fff" : chunk255PCZIW_cjs.colors_default.PALETTE["prussian-blue"],
1694
- strokeWidth: "2",
1695
- strokeLinecap: "round",
1696
- strokeLinejoin: "round"
1696
+ /* @__PURE__ */ jsxRuntime.jsx("div", { role: "listbox", "aria-multiselectable": isMultiselect, className: "max-h-40 overflow-y-auto", children: innerItems.map((item) => (
1697
+ // aria-rowindex was previously set here but
1698
+ // it's invalid ARIA on role="option" (it
1699
+ // belongs on rows of a grid/treegrid). Dropped.
1700
+ // tabIndex={0} + Enter/Space handler makes the
1701
+ // option keyboard-activatable; the full
1702
+ // combobox roving-tabindex pattern is deferred
1703
+ // until the planned Phase-5 rewrite.
1704
+ /* @__PURE__ */ jsxRuntime.jsxs(
1705
+ "div",
1706
+ {
1707
+ role: "option",
1708
+ "aria-selected": isSelected(item.key),
1709
+ tabIndex: 0,
1710
+ className: `flex items-center justify-between p-2 hover:bg-prussian-blue hover:text-white transition-all duration-150 text-sm text-prussian-blue rounded-lg cursor-pointer focus:outline-none focus-visible:ring-2 focus-visible:ring-accent ${selectedItems.includes(item.key) ? "bg-ice-dark" : ""}`,
1711
+ onClick: () => selectItem(item.key),
1712
+ onKeyDown: (e) => {
1713
+ if (e.key === "Enter" || e.key === " ") {
1714
+ e.preventDefault();
1715
+ selectItem(item.key);
1697
1716
  }
1698
- ) })
1699
- ]
1700
- },
1701
- item.key
1717
+ },
1718
+ onMouseEnter: () => setHoveredItem(item.key),
1719
+ onMouseLeave: () => setHoveredItem(null),
1720
+ children: [
1721
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 text-xs", children: [
1722
+ item.icon && /* @__PURE__ */ jsxRuntime.jsx("div", { children: item.icon }),
1723
+ item.label
1724
+ ] }),
1725
+ isSelected(item.key) && /* @__PURE__ */ jsxRuntime.jsx("svg", { width: "16", height: "16", viewBox: "0 0 20 20", fill: "none", children: /* @__PURE__ */ jsxRuntime.jsx(
1726
+ "path",
1727
+ {
1728
+ d: "M4 10l4.5 4.5L16 6",
1729
+ stroke: hoveredItem === item.key ? "#fff" : chunk255PCZIW_cjs.colors_default.PALETTE["prussian-blue"],
1730
+ strokeWidth: "2",
1731
+ strokeLinecap: "round",
1732
+ strokeLinejoin: "round"
1733
+ }
1734
+ ) })
1735
+ ]
1736
+ },
1737
+ item.key
1738
+ )
1702
1739
  )) })
1703
1740
  ]
1704
1741
  }
@@ -1707,7 +1744,7 @@ function Dropdown({
1707
1744
  ]
1708
1745
  }
1709
1746
  ),
1710
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-center text-error dark:text-prussian-blue min-h-0", children: errorMessage })
1747
+ hasError && /* @__PURE__ */ jsxRuntime.jsx("div", { id: errorId, className: "text-center text-error dark:text-prussian-blue min-h-0", children: errorMessage })
1711
1748
  ] });
1712
1749
  }
1713
1750
  var DEFAULT_PICKER = [
@@ -1877,10 +1914,11 @@ function Pagination({
1877
1914
  isMultiselect: false,
1878
1915
  value: displayPerPageKey,
1879
1916
  onChange: ({ target: { value } }) => {
1880
- const key = typeof value === "string" && /^\d+$/.test(value) ? Number(value) : value;
1881
- if (!serverSide) setPerPageKey(key);
1882
- const opt = picker.find((o) => o.key === key);
1883
- onPerPageChange(opt?.label ?? opt?.value ?? key);
1917
+ if (Array.isArray(value)) return;
1918
+ const numKey = typeof value === "number" ? value : Number(value);
1919
+ if (!serverSide) setPerPageKey(numKey);
1920
+ const opt = picker.find((o) => o.key === numKey);
1921
+ onPerPageChange(opt?.label ?? opt?.value ?? numKey);
1884
1922
  }
1885
1923
  }
1886
1924
  )
@@ -2360,8 +2398,24 @@ function toCssVars(theme) {
2360
2398
  }
2361
2399
  return out;
2362
2400
  }
2401
+ var CSS_VALUE_REJECT_RE = /[;{}<>\\]|\*\/|\/\*/;
2402
+ function isSafeCssValue(v) {
2403
+ if (typeof v !== "string") return false;
2404
+ if (v.length > 500) return false;
2405
+ return !CSS_VALUE_REJECT_RE.test(v);
2406
+ }
2363
2407
  function varsToStyleString(vars2) {
2364
- return Object.entries(vars2).map(([k, v]) => `${k}: ${v};`).join(" ");
2408
+ const out = [];
2409
+ for (const [k, v] of Object.entries(vars2)) {
2410
+ if (!isSafeCssValue(v)) {
2411
+ console.warn(
2412
+ `[ThemeProvider] Dropping unsafe value for "${k}". Theme values may contain letters, digits, and CSS punctuation but must not include: ; { } < > \\ /* */`
2413
+ );
2414
+ continue;
2415
+ }
2416
+ out.push(`${k}: ${v};`);
2417
+ }
2418
+ return out.join(" ");
2365
2419
  }
2366
2420
  function ThemeProvider({
2367
2421
  theme,
@@ -2517,6 +2571,8 @@ function TextInput({
2517
2571
  errorMessage,
2518
2572
  labelColor
2519
2573
  }) {
2574
+ const errorId = React9.useId();
2575
+ const hasError = errorMessage != null;
2520
2576
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative flex flex-col items-center justify-center", children: [
2521
2577
  /* @__PURE__ */ jsxRuntime.jsxs(
2522
2578
  "div",
@@ -2547,7 +2603,9 @@ function TextInput({
2547
2603
  type: "text",
2548
2604
  name,
2549
2605
  id: htmlFor,
2550
- className: `${errorMessage !== void 0 ? "border border-error" : ""} focus:outline-oxford-blue-700-opaque p-2 h-9 w-60 outline-offset-2 text-prussian-blue mt-1 rounded-lg disabled:bg-disabled disabled:cursor-not-allowed transition-all`,
2606
+ "aria-invalid": hasError || void 0,
2607
+ "aria-describedby": hasError ? errorId : void 0,
2608
+ className: `${hasError ? "border border-error" : ""} focus:outline-oxford-blue-700-opaque p-2 h-9 w-60 outline-offset-2 text-prussian-blue mt-1 rounded-lg disabled:bg-disabled disabled:cursor-not-allowed transition-all`,
2551
2609
  style: inputStyle ?? {},
2552
2610
  placeholder: placeholder ?? ""
2553
2611
  }
@@ -2555,7 +2613,7 @@ function TextInput({
2555
2613
  ]
2556
2614
  }
2557
2615
  ),
2558
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-center text-error dark:text-prussian-blue min-h-0", children: errorMessage })
2616
+ hasError && /* @__PURE__ */ jsxRuntime.jsx("div", { id: errorId, className: "text-center text-error dark:text-prussian-blue min-h-0", children: errorMessage })
2559
2617
  ] });
2560
2618
  }
2561
2619
  function NumberInput({
@@ -2671,6 +2729,8 @@ function Password({
2671
2729
  }) {
2672
2730
  const [passwordVisible, setPasswordVisible] = React9.useState(false);
2673
2731
  const color = iconColor ?? chunk255PCZIW_cjs.colors_default.PALETTE["prussian-blue"];
2732
+ const errorId = React9.useId();
2733
+ const hasError = errorMessage != null;
2674
2734
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative flex flex-col items-center justify-center", style: style ?? {}, children: [
2675
2735
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `flex ${layout === "vertical" ? "flex-col" : "flex-row items-center gap-2"}`, children: [
2676
2736
  label && // Render <label> only when a label is provided. An empty
@@ -2697,7 +2757,9 @@ function Password({
2697
2757
  type: passwordVisible ? "text" : "password",
2698
2758
  name,
2699
2759
  id: htmlFor,
2700
- className: `${errorMessage !== void 0 ? "border border-error" : ""} focus:outline-oxford-blue-700-opaque p-2 h-9 w-52 outline-offset-2 text-prussian-blue mt-1 rounded-lg disabled:bg-disabled disabled:cursor-not-allowed transition-all`,
2760
+ "aria-invalid": hasError || void 0,
2761
+ "aria-describedby": hasError ? errorId : void 0,
2762
+ className: `${hasError ? "border border-error" : ""} focus:outline-oxford-blue-700-opaque p-2 h-9 w-52 outline-offset-2 text-prussian-blue mt-1 rounded-lg disabled:bg-disabled disabled:cursor-not-allowed transition-all`,
2701
2763
  style: inputStyle ?? {},
2702
2764
  placeholder: placeholder ?? ""
2703
2765
  }
@@ -2727,7 +2789,7 @@ function Password({
2727
2789
  )
2728
2790
  ] })
2729
2791
  ] }),
2730
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-center text-error dark:text-prussian-blue min-h-0", children: errorMessage })
2792
+ hasError && /* @__PURE__ */ jsxRuntime.jsx("div", { id: errorId, className: "text-center text-error dark:text-prussian-blue min-h-0", children: errorMessage })
2731
2793
  ] });
2732
2794
  }
2733
2795
  function Checkbox({
@@ -2894,23 +2956,36 @@ function AutoComplete({
2894
2956
  sideOffset: 4,
2895
2957
  onOpenAutoFocus: (e) => e.preventDefault(),
2896
2958
  className: "w-64 bg-ice dark:bg-midnight-green-eagle-900 rounded-lg mt-1 shadow-md z-50 overflow-y-auto max-h-36 animate-in fade-in-0 zoom-in-95",
2897
- children: foundItems.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-full w-full flex flex-col items-center justify-center py-4 text-sm text-prussian-blue dark:text-white", children: emptyText }) : /* @__PURE__ */ jsxRuntime.jsx("div", { role: "listbox", children: foundItems.map((item) => /* @__PURE__ */ jsxRuntime.jsxs(
2898
- "div",
2899
- {
2900
- role: "option",
2901
- className: "text-sm flex items-center gap-2 p-2 transition-all duration-150 hover:bg-ice-dark dark:hover:bg-prussian-blue cursor-pointer text-prussian-blue dark:text-white",
2902
- onClick: () => handleSelect(item),
2903
- children: [
2904
- item.icon,
2905
- /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
2906
- item.label,
2907
- " (",
2908
- item.value,
2909
- ")"
2910
- ] })
2911
- ]
2912
- },
2913
- item.key
2959
+ children: foundItems.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-full w-full flex flex-col items-center justify-center py-4 text-sm text-prussian-blue dark:text-white", children: emptyText }) : /* @__PURE__ */ jsxRuntime.jsx("div", { role: "listbox", children: foundItems.map((item) => (
2960
+ // tabIndex + Enter/Space onKeyDown
2961
+ // makes each option keyboard-activatable.
2962
+ // Full roving-tabindex / arrow-key nav
2963
+ // is deferred to the Phase-5 rewrite.
2964
+ /* @__PURE__ */ jsxRuntime.jsxs(
2965
+ "div",
2966
+ {
2967
+ role: "option",
2968
+ tabIndex: 0,
2969
+ className: "text-sm flex items-center gap-2 p-2 transition-all duration-150 hover:bg-ice-dark dark:hover:bg-prussian-blue cursor-pointer text-prussian-blue dark:text-white focus:outline-none focus-visible:ring-2 focus-visible:ring-accent",
2970
+ onClick: () => handleSelect(item),
2971
+ onKeyDown: (e) => {
2972
+ if (e.key === "Enter" || e.key === " ") {
2973
+ e.preventDefault();
2974
+ handleSelect(item);
2975
+ }
2976
+ },
2977
+ children: [
2978
+ item.icon,
2979
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
2980
+ item.label,
2981
+ " (",
2982
+ item.value,
2983
+ ")"
2984
+ ] })
2985
+ ]
2986
+ },
2987
+ item.key
2988
+ )
2914
2989
  )) })
2915
2990
  }
2916
2991
  ) })