@geomak/ui 1.7.2 → 1.7.4

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.d.cts CHANGED
@@ -885,11 +885,34 @@ interface WizardProps {
885
885
  declare function Wizard({ children, steps, storageKey }: WizardProps): react_jsx_runtime.JSX.Element;
886
886
 
887
887
  /** ─────────────────── types ─────────────────── */
888
- interface TableColumn {
889
- key: string | number;
888
+ /**
889
+ * Column descriptor for the Table.
890
+ *
891
+ * The generic `T` is the shape of a row — `keyBind` must be one of T's
892
+ * string-keyed properties, and `component(cellValue, row)` receives the
893
+ * matching value with full type inference. When used without a generic
894
+ * (`TableColumn[]`), `T` falls back to `Record<string, any>` for backwards
895
+ * compatibility — narrower typing is preferred whenever possible:
896
+ *
897
+ * ```ts
898
+ * type Vessel = { id: number; name: string; status: 'At Sea' | 'In Port' }
899
+ * const cols: TableColumn<Vessel>[] = [
900
+ * { key: 'name', label: 'Name', keyBind: 'name' }, // cellValue inferred as string
901
+ * ]
902
+ * ```
903
+ */
904
+ interface TableColumn<T extends Record<string, any> = Record<string, any>> {
905
+ /** React reconciliation key for the column itself. */
906
+ key: React$1.Key;
890
907
  label: React$1.ReactNode;
891
- keyBind: string;
892
- component?: (cellValue: any, row: any) => React$1.ReactNode;
908
+ /** Property on the row to read for this column. */
909
+ keyBind: keyof T & string;
910
+ /** Custom cell renderer. Receives the cell value and the full row. */
911
+ component?: (cellValue: T[keyof T], row: T) => React$1.ReactNode;
912
+ /** Explicit column width (CSS length or px number). Optional — defaults to auto. */
913
+ width?: string | number;
914
+ /** Text alignment for both header and cells. Defaults to `'center'`. */
915
+ align?: 'left' | 'center' | 'right';
893
916
  }
894
917
  interface PaginationOptions {
895
918
  enabled?: boolean;
@@ -910,36 +933,70 @@ interface PaginationOptions {
910
933
  onPageChange?: (page: number) => void;
911
934
  onPerPageChange?: (perPage: number) => void;
912
935
  }
913
- interface ExpandRowOptions {
936
+ interface ExpandRowOptions<T extends Record<string, any> = Record<string, any>> {
914
937
  enabled?: boolean;
915
938
  expandIcon?: React$1.ReactNode;
916
- expandComponent?: (row: any) => React$1.ReactNode;
939
+ expandComponent?: (row: T) => React$1.ReactNode;
917
940
  }
918
- interface TableProps {
919
- columns?: TableColumn[];
920
- rows?: any[];
941
+ interface TableProps<T extends Record<string, any> = Record<string, any>> {
942
+ columns?: TableColumn<T>[];
943
+ rows?: T[];
944
+ /**
945
+ * Returns a stable key for each row, used for React reconciliation AND
946
+ * for tracking expanded state when `expandRow.enabled` is true.
947
+ * Defaults to the row index — fine for static lists, but pass an
948
+ * explicit getter (e.g. `(row) => row.id`) if rows can be reordered or
949
+ * filtered while expand state should persist.
950
+ */
951
+ getRowKey?: (row: T, index: number) => React$1.Key;
921
952
  pagination?: PaginationOptions;
922
- expandRow?: ExpandRowOptions;
953
+ expandRow?: ExpandRowOptions<T>;
923
954
  hasSearch?: boolean;
924
955
  footer?: React$1.ReactNode;
925
956
  header?: React$1.ReactNode;
926
- tableRef?: React$1.Ref<any>;
927
- [key: string]: any;
928
957
  }
929
958
  /** ─────────────────── main component ─────────────────── */
930
959
  /**
931
960
  * Data table with optional search, pagination, and expandable rows.
932
961
  *
933
- * Supports both client-side and server-side pagination.
962
+ * - **Typed rows**: pass a generic `T` for full type inference on columns
963
+ * and cell renderers (`<Table<Vessel> ... />`).
964
+ * - **Real `<table>` semantics**: keeps row / col / cell context intact for
965
+ * screen readers and lets the browser handle column sizing natively.
966
+ * Per-column widths via `column.width`.
967
+ * - **Search**: client-side filter across ALL row values; result is
968
+ * memoized so each keystroke costs O(n) once per term change, not per
969
+ * render. Set `pagination.serverSide` to skip client-side filter and
970
+ * pagination entirely.
971
+ * - **Expand**: each row gets a real `<button>` with `aria-expanded`.
972
+ * Expand state is keyed by `getRowKey(row, i)` so it survives reorders.
973
+ *
974
+ * @example Static, fully typed
975
+ * ```tsx
976
+ * type Vessel = { id: number; name: string; status: string }
977
+ * <Table<Vessel>
978
+ * columns={[
979
+ * { key: 'name', label: 'Name', keyBind: 'name' },
980
+ * { key: 'status', label: 'Status', keyBind: 'status', width: 120 },
981
+ * ]}
982
+ * rows={vessels}
983
+ * getRowKey={(row) => row.id}
984
+ * />
985
+ * ```
934
986
  *
935
- * @example
987
+ * @example Server-side pagination
988
+ * ```tsx
936
989
  * <Table
937
- * columns={[{ key: 'name', label: 'Name', keyBind: 'name' }]}
938
- * rows={data}
939
- * pagination={{ enabled: true, perPage: 15 }}
990
+ * columns={cols}
991
+ * rows={pageRows}
992
+ * pagination={{
993
+ * enabled: true, serverSide: true, perPage: 20,
994
+ * page: currentPage, totalCount, onPageChange, onPerPageChange,
995
+ * }}
940
996
  * />
997
+ * ```
941
998
  */
942
- declare function Table({ columns, rows, pagination, expandRow, hasSearch, footer, header, }: TableProps): react_jsx_runtime.JSX.Element;
999
+ declare function Table<T extends Record<string, any> = Record<string, any>>({ columns, rows, getRowKey, pagination, expandRow, hasSearch, footer, header, }: TableProps<T>): react_jsx_runtime.JSX.Element;
943
1000
 
944
1001
  interface ThemeSwitchProps {
945
1002
  checked: boolean;
package/dist/index.d.ts CHANGED
@@ -885,11 +885,34 @@ interface WizardProps {
885
885
  declare function Wizard({ children, steps, storageKey }: WizardProps): react_jsx_runtime.JSX.Element;
886
886
 
887
887
  /** ─────────────────── types ─────────────────── */
888
- interface TableColumn {
889
- key: string | number;
888
+ /**
889
+ * Column descriptor for the Table.
890
+ *
891
+ * The generic `T` is the shape of a row — `keyBind` must be one of T's
892
+ * string-keyed properties, and `component(cellValue, row)` receives the
893
+ * matching value with full type inference. When used without a generic
894
+ * (`TableColumn[]`), `T` falls back to `Record<string, any>` for backwards
895
+ * compatibility — narrower typing is preferred whenever possible:
896
+ *
897
+ * ```ts
898
+ * type Vessel = { id: number; name: string; status: 'At Sea' | 'In Port' }
899
+ * const cols: TableColumn<Vessel>[] = [
900
+ * { key: 'name', label: 'Name', keyBind: 'name' }, // cellValue inferred as string
901
+ * ]
902
+ * ```
903
+ */
904
+ interface TableColumn<T extends Record<string, any> = Record<string, any>> {
905
+ /** React reconciliation key for the column itself. */
906
+ key: React$1.Key;
890
907
  label: React$1.ReactNode;
891
- keyBind: string;
892
- component?: (cellValue: any, row: any) => React$1.ReactNode;
908
+ /** Property on the row to read for this column. */
909
+ keyBind: keyof T & string;
910
+ /** Custom cell renderer. Receives the cell value and the full row. */
911
+ component?: (cellValue: T[keyof T], row: T) => React$1.ReactNode;
912
+ /** Explicit column width (CSS length or px number). Optional — defaults to auto. */
913
+ width?: string | number;
914
+ /** Text alignment for both header and cells. Defaults to `'center'`. */
915
+ align?: 'left' | 'center' | 'right';
893
916
  }
894
917
  interface PaginationOptions {
895
918
  enabled?: boolean;
@@ -910,36 +933,70 @@ interface PaginationOptions {
910
933
  onPageChange?: (page: number) => void;
911
934
  onPerPageChange?: (perPage: number) => void;
912
935
  }
913
- interface ExpandRowOptions {
936
+ interface ExpandRowOptions<T extends Record<string, any> = Record<string, any>> {
914
937
  enabled?: boolean;
915
938
  expandIcon?: React$1.ReactNode;
916
- expandComponent?: (row: any) => React$1.ReactNode;
939
+ expandComponent?: (row: T) => React$1.ReactNode;
917
940
  }
918
- interface TableProps {
919
- columns?: TableColumn[];
920
- rows?: any[];
941
+ interface TableProps<T extends Record<string, any> = Record<string, any>> {
942
+ columns?: TableColumn<T>[];
943
+ rows?: T[];
944
+ /**
945
+ * Returns a stable key for each row, used for React reconciliation AND
946
+ * for tracking expanded state when `expandRow.enabled` is true.
947
+ * Defaults to the row index — fine for static lists, but pass an
948
+ * explicit getter (e.g. `(row) => row.id`) if rows can be reordered or
949
+ * filtered while expand state should persist.
950
+ */
951
+ getRowKey?: (row: T, index: number) => React$1.Key;
921
952
  pagination?: PaginationOptions;
922
- expandRow?: ExpandRowOptions;
953
+ expandRow?: ExpandRowOptions<T>;
923
954
  hasSearch?: boolean;
924
955
  footer?: React$1.ReactNode;
925
956
  header?: React$1.ReactNode;
926
- tableRef?: React$1.Ref<any>;
927
- [key: string]: any;
928
957
  }
929
958
  /** ─────────────────── main component ─────────────────── */
930
959
  /**
931
960
  * Data table with optional search, pagination, and expandable rows.
932
961
  *
933
- * Supports both client-side and server-side pagination.
962
+ * - **Typed rows**: pass a generic `T` for full type inference on columns
963
+ * and cell renderers (`<Table<Vessel> ... />`).
964
+ * - **Real `<table>` semantics**: keeps row / col / cell context intact for
965
+ * screen readers and lets the browser handle column sizing natively.
966
+ * Per-column widths via `column.width`.
967
+ * - **Search**: client-side filter across ALL row values; result is
968
+ * memoized so each keystroke costs O(n) once per term change, not per
969
+ * render. Set `pagination.serverSide` to skip client-side filter and
970
+ * pagination entirely.
971
+ * - **Expand**: each row gets a real `<button>` with `aria-expanded`.
972
+ * Expand state is keyed by `getRowKey(row, i)` so it survives reorders.
973
+ *
974
+ * @example Static, fully typed
975
+ * ```tsx
976
+ * type Vessel = { id: number; name: string; status: string }
977
+ * <Table<Vessel>
978
+ * columns={[
979
+ * { key: 'name', label: 'Name', keyBind: 'name' },
980
+ * { key: 'status', label: 'Status', keyBind: 'status', width: 120 },
981
+ * ]}
982
+ * rows={vessels}
983
+ * getRowKey={(row) => row.id}
984
+ * />
985
+ * ```
934
986
  *
935
- * @example
987
+ * @example Server-side pagination
988
+ * ```tsx
936
989
  * <Table
937
- * columns={[{ key: 'name', label: 'Name', keyBind: 'name' }]}
938
- * rows={data}
939
- * pagination={{ enabled: true, perPage: 15 }}
990
+ * columns={cols}
991
+ * rows={pageRows}
992
+ * pagination={{
993
+ * enabled: true, serverSide: true, perPage: 20,
994
+ * page: currentPage, totalCount, onPageChange, onPerPageChange,
995
+ * }}
940
996
  * />
997
+ * ```
941
998
  */
942
- declare function Table({ columns, rows, pagination, expandRow, hasSearch, footer, header, }: TableProps): react_jsx_runtime.JSX.Element;
999
+ declare function Table<T extends Record<string, any> = Record<string, any>>({ columns, rows, getRowKey, pagination, expandRow, hasSearch, footer, header, }: TableProps<T>): react_jsx_runtime.JSX.Element;
943
1000
 
944
1001
  interface ThemeSwitchProps {
945
1002
  checked: boolean;
package/dist/index.js CHANGED
@@ -1691,9 +1691,7 @@ var DEFAULT_PAGINATION = {
1691
1691
  pickerOptions: DEFAULT_PICKER
1692
1692
  };
1693
1693
  var DEFAULT_EXPAND = {
1694
- enabled: false,
1695
- expandIcon: /* @__PURE__ */ jsx(Fragment, {}),
1696
- expandComponent: () => /* @__PURE__ */ jsx(Fragment, {})
1694
+ enabled: false
1697
1695
  };
1698
1696
  function createDatasets(rows, perPage) {
1699
1697
  if (!perPage) return [rows.slice()];
@@ -1703,78 +1701,95 @@ function createDatasets(rows, perPage) {
1703
1701
  }
1704
1702
  return all;
1705
1703
  }
1706
- function TableHeader({ columns }) {
1707
- return /* @__PURE__ */ jsx("thead", { className: "bg-surface-raised min-h-[50px] border-b border-b-border flex items-center", children: /* @__PURE__ */ jsx("tr", { className: "flex w-full items-center justify-center", children: columns.map((col) => /* @__PURE__ */ jsx(
1708
- "th",
1709
- {
1710
- className: "text-center w-full text-[13px] text-foreground",
1711
- children: col.label
1712
- },
1713
- col.key
1714
- )) }) });
1704
+ var defaultGetRowKey = (_row, index) => index;
1705
+ var cellAlign = (align) => align === "left" ? "text-left" : align === "right" ? "text-right" : "text-center";
1706
+ function TableHeader({
1707
+ columns,
1708
+ hasExpand
1709
+ }) {
1710
+ return /* @__PURE__ */ jsx("thead", { className: "bg-surface-raised border-b border-border", children: /* @__PURE__ */ jsxs("tr", { children: [
1711
+ hasExpand && /* @__PURE__ */ jsx("th", { "aria-hidden": "true", className: "w-9" }),
1712
+ columns.map((col) => /* @__PURE__ */ jsx(
1713
+ "th",
1714
+ {
1715
+ scope: "col",
1716
+ className: `${cellAlign(col.align)} text-sm font-semibold text-foreground py-3 px-3`,
1717
+ style: col.width != null ? { width: col.width } : void 0,
1718
+ children: col.label
1719
+ },
1720
+ col.key
1721
+ ))
1722
+ ] }) });
1715
1723
  }
1724
+ var DefaultExpandIcon = /* @__PURE__ */ jsx(
1725
+ "svg",
1726
+ {
1727
+ xmlns: "http://www.w3.org/2000/svg",
1728
+ viewBox: "0 0 24 24",
1729
+ fill: "currentColor",
1730
+ className: "w-5 h-5 text-foreground-muted",
1731
+ "aria-hidden": "true",
1732
+ children: /* @__PURE__ */ jsx(
1733
+ "path",
1734
+ {
1735
+ fillRule: "evenodd",
1736
+ d: "M12 2.25c-5.385 0-9.75 4.365-9.75 9.75s4.365 9.75 9.75 9.75 9.75-4.365 9.75-9.75S17.385 2.25 12 2.25zM12.75 9a.75.75 0 00-1.5 0v2.25H9a.75.75 0 000 1.5h2.25V15a.75.75 0 001.5 0v-2.25H15a.75.75 0 000-1.5h-2.25V9z",
1737
+ clipRule: "evenodd"
1738
+ }
1739
+ )
1740
+ }
1741
+ );
1716
1742
  function TableBody({
1717
1743
  columns,
1718
1744
  rows,
1719
- expandRow
1745
+ expandRow,
1746
+ getRowKey
1720
1747
  }) {
1721
- const [visibleRows, setVisibleRows] = useState({});
1748
+ const [expanded, setExpanded] = useState(() => /* @__PURE__ */ new Set());
1722
1749
  const toggleRow = (rowKey) => {
1723
- setVisibleRows((prev) => ({
1724
- ...prev,
1725
- [rowKey]: { visible: !prev[rowKey]?.visible }
1726
- }));
1750
+ setExpanded((prev) => {
1751
+ const next = new Set(prev);
1752
+ if (next.has(rowKey)) next.delete(rowKey);
1753
+ else next.add(rowKey);
1754
+ return next;
1755
+ });
1727
1756
  };
1728
- useEffect(() => {
1729
- if (rows.length && Object.keys(visibleRows).length === 0) {
1730
- const initial = {};
1731
- rows.forEach((row) => {
1732
- initial[row.key] = { visible: false };
1733
- });
1734
- setVisibleRows(initial);
1735
- }
1736
- }, [rows]);
1737
- return /* @__PURE__ */ jsx("tbody", { className: "w-full", children: rows.map((row, i) => /* @__PURE__ */ jsxs(React9.Fragment, { children: [
1738
- /* @__PURE__ */ jsxs(
1739
- "tr",
1740
- {
1741
- className: `border-b border-b-border flex min-w-max hover:bg-surface-raised transition-all duration-150 ${i % 2 === 0 ? "bg-surface" : "bg-surface-raised"}`,
1742
- children: [
1743
- expandRow.enabled && /* @__PURE__ */ jsx("td", { className: "flex items-center", children: /* @__PURE__ */ jsx(
1744
- "span",
1745
- {
1746
- onClick: () => toggleRow(row.key),
1747
- className: `p-2 cursor-pointer origin-center transition-all duration-200 ${visibleRows[row.key]?.visible ? "rotate-180" : "rotate-0"}`,
1748
- children: expandRow.expandIcon ?? /* PlusCircle */
1749
- /* @__PURE__ */ jsx("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 24 24", fill: "currentColor", className: "w-5 h-5 text-foreground-muted", children: /* @__PURE__ */ jsx("path", { fillRule: "evenodd", d: "M12 2.25c-5.385 0-9.75 4.365-9.75 9.75s4.365 9.75 9.75 9.75 9.75-4.365 9.75-9.75S17.385 2.25 12 2.25zM12.75 9a.75.75 0 00-1.5 0v2.25H9a.75.75 0 000 1.5h2.25V15a.75.75 0 001.5 0v-2.25H15a.75.75 0 000-1.5h-2.25V9z", clipRule: "evenodd" }) })
1750
- }
1751
- ) }),
1752
- columns.map((col, index) => /* @__PURE__ */ jsx(
1753
- "td",
1754
- {
1755
- className: `text-center min-h-[40px] w-full flex items-center justify-center p-1 border-border ${index !== columns.length - 1 ? "border-r-2" : ""}`,
1756
- children: "component" in col && col.component ? col.component(row[col.keyBind], row) : row[col.keyBind]
1757
- },
1758
- index
1759
- ))
1760
- ]
1761
- }
1762
- ),
1763
- expandRow.enabled && /* @__PURE__ */ jsx(
1764
- "tr",
1765
- {
1766
- className: `overflow-hidden w-full transition-all duration-300 ${visibleRows[row.key]?.visible ? "max-h-[2000px]" : "max-h-0"}`,
1767
- children: /* @__PURE__ */ jsx("td", { colSpan: columns.length, className: "p-0 pb-1", children: /* @__PURE__ */ jsx(
1768
- "div",
1769
- {
1770
- className: `overflow-hidden w-full transition-[max-height] duration-300 ${visibleRows[row.key]?.visible ? "max-h-[2000px]" : "max-h-0"}`,
1771
- children: expandRow.expandComponent?.(row)
1772
- }
1773
- ) })
1774
- },
1775
- `extra-${i}`
1776
- )
1777
- ] }, row.key)) });
1757
+ const hasExpand = !!expandRow.enabled;
1758
+ const expandColCount = columns.length + (hasExpand ? 1 : 0);
1759
+ return /* @__PURE__ */ jsx("tbody", { children: rows.map((row, i) => {
1760
+ const rowKey = getRowKey(row, i);
1761
+ const isExpanded = expanded.has(rowKey);
1762
+ return /* @__PURE__ */ jsxs(React9.Fragment, { children: [
1763
+ /* @__PURE__ */ jsxs(
1764
+ "tr",
1765
+ {
1766
+ className: `border-b border-border hover:bg-surface-raised transition-colors duration-150 ${i % 2 === 0 ? "bg-surface" : "bg-surface-raised"}`,
1767
+ children: [
1768
+ hasExpand && /* @__PURE__ */ jsx("td", { className: "p-0 align-middle w-9", children: /* @__PURE__ */ jsx(
1769
+ "button",
1770
+ {
1771
+ type: "button",
1772
+ onClick: () => toggleRow(rowKey),
1773
+ "aria-expanded": isExpanded,
1774
+ "aria-label": isExpanded ? "Collapse row" : "Expand row",
1775
+ className: `w-9 h-9 inline-flex items-center justify-center rounded-md hover:bg-surface/80 transition-transform duration-200 focus:outline-none focus-visible:ring-2 focus-visible:ring-accent ${isExpanded ? "rotate-180" : ""}`,
1776
+ children: expandRow.expandIcon ?? DefaultExpandIcon
1777
+ }
1778
+ ) }),
1779
+ columns.map((col) => /* @__PURE__ */ jsx(
1780
+ "td",
1781
+ {
1782
+ className: `${cellAlign(col.align)} text-sm text-foreground py-2 px-3 align-middle`,
1783
+ children: col.component ? col.component(row[col.keyBind], row) : row[col.keyBind]
1784
+ },
1785
+ col.key
1786
+ ))
1787
+ ]
1788
+ }
1789
+ ),
1790
+ hasExpand && isExpanded && /* @__PURE__ */ jsx("tr", { className: "bg-surface", children: /* @__PURE__ */ jsx("td", { colSpan: expandColCount, className: "p-0 border-b border-border", children: /* @__PURE__ */ jsx("div", { className: "p-3", children: expandRow.expandComponent?.(row) }) }) })
1791
+ ] }, rowKey);
1792
+ }) });
1778
1793
  }
1779
1794
  function Pagination({
1780
1795
  activePage,
@@ -1797,29 +1812,27 @@ function Pagination({
1797
1812
  }
1798
1813
  }, [serverSide, options.perPage, picker]);
1799
1814
  const navBtn = (icon, disabled, onClick) => /* @__PURE__ */ jsx(IconButton, { disabled, onClick, icon });
1800
- const chevronRight = (color) => /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: color, strokeWidth: 2, className: "h-5 w-5", children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M9 5l7 7-7 7" }) });
1801
- const doubleChevronRight = (color) => /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: color, strokeWidth: 2, className: "h-5 w-5", children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M13 5l7 7-7 7M5 5l7 7-7 7" }) });
1802
- const disabledColor = "var(--color-foreground-muted)";
1803
- const enabledColor = "var(--color-foreground)";
1815
+ const chevronRight = /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, className: "h-5 w-5", children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M9 5l7 7-7 7" }) });
1816
+ const doubleChevronRight = /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, className: "h-5 w-5", children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M13 5l7 7-7 7M5 5l7 7-7 7" }) });
1804
1817
  return /* @__PURE__ */ jsxs("div", { className: "flex gap-2 items-center justify-end pt-2", children: [
1805
1818
  navBtn(
1806
- /* @__PURE__ */ jsx("span", { className: "rotate-180 inline-flex", children: doubleChevronRight(activePage === 0 ? disabledColor : enabledColor) }),
1819
+ /* @__PURE__ */ jsx("span", { className: "rotate-180 inline-flex", children: doubleChevronRight }),
1807
1820
  activePage === 0,
1808
1821
  () => onPageChange(0)
1809
1822
  ),
1810
1823
  navBtn(
1811
- /* @__PURE__ */ jsx("span", { className: "rotate-180 inline-flex", children: chevronRight(activePage === 0 ? disabledColor : enabledColor) }),
1824
+ /* @__PURE__ */ jsx("span", { className: "rotate-180 inline-flex", children: chevronRight }),
1812
1825
  activePage === 0,
1813
1826
  () => activePage > 0 && onPageChange(activePage - 1)
1814
1827
  ),
1815
1828
  /* @__PURE__ */ jsx("span", { className: "bg-surface-raised rounded-lg ml-2 mr-2 shadow-sm p-2 w-10 text-center select-none text-foreground", children: activePage + 1 }),
1816
1829
  navBtn(
1817
- chevronRight(activePage === maxPage ? disabledColor : enabledColor),
1830
+ chevronRight,
1818
1831
  activePage === maxPage,
1819
1832
  () => activePage < maxPage && onPageChange(activePage + 1)
1820
1833
  ),
1821
1834
  navBtn(
1822
- doubleChevronRight(activePage === maxPage ? disabledColor : enabledColor),
1835
+ doubleChevronRight,
1823
1836
  activePage === maxPage,
1824
1837
  () => onPageChange(maxPage)
1825
1838
  ),
@@ -1844,6 +1857,7 @@ function Pagination({
1844
1857
  function Table({
1845
1858
  columns = [],
1846
1859
  rows = [],
1860
+ getRowKey = defaultGetRowKey,
1847
1861
  pagination = DEFAULT_PAGINATION,
1848
1862
  expandRow = DEFAULT_EXPAND,
1849
1863
  hasSearch = true,
@@ -1856,8 +1870,20 @@ function Table({
1856
1870
  typeof pagination.perPage === "number" ? pagination.perPage : 15
1857
1871
  );
1858
1872
  const [activePage, setActivePage] = useState(0);
1859
- const [datasets, setDatasets] = useState([]);
1860
1873
  const isServerSide = !!(pagination.enabled && pagination.serverSide);
1874
+ const filteredRows = useMemo(() => {
1875
+ if (isServerSide || !searchTerm) return rows;
1876
+ const term = searchTerm.toLowerCase();
1877
+ return rows.filter(
1878
+ (row) => Object.values(row).some(
1879
+ (v) => v != null && String(v).toLowerCase().includes(term)
1880
+ )
1881
+ );
1882
+ }, [rows, searchTerm, isServerSide]);
1883
+ const datasets = useMemo(() => {
1884
+ if (isServerSide) return [rows];
1885
+ return createDatasets(filteredRows, pagination.enabled ? perPage : null);
1886
+ }, [filteredRows, perPage, pagination.enabled, isServerSide, rows]);
1861
1887
  const MAX_PAGE = useMemo(() => {
1862
1888
  if (isServerSide && typeof pagination.maxPage === "number") return Math.max(0, pagination.maxPage);
1863
1889
  if (isServerSide && typeof pagination.totalCount === "number")
@@ -1866,32 +1892,22 @@ function Table({
1866
1892
  }, [isServerSide, pagination.maxPage, pagination.totalCount, perPage, datasets.length]);
1867
1893
  const currentPageRows = useMemo(() => {
1868
1894
  if (isServerSide) return rows;
1869
- return datasets.length ? datasets[activePage] ?? [] : [];
1895
+ return datasets[activePage] ?? [];
1870
1896
  }, [isServerSide, rows, datasets, activePage]);
1871
1897
  useEffect(() => {
1872
- if (pagination.enabled && !isServerSide) setPerPage(pagination.perPage ?? 15);
1873
- }, [pagination, isServerSide]);
1898
+ if (pagination.enabled && !isServerSide && typeof pagination.perPage === "number") {
1899
+ setPerPage(pagination.perPage);
1900
+ }
1901
+ }, [pagination.enabled, pagination.perPage, isServerSide]);
1874
1902
  useEffect(() => {
1875
1903
  if (isServerSide && typeof pagination.perPage === "number") setPerPage(pagination.perPage);
1876
1904
  }, [isServerSide, pagination.perPage]);
1877
- useEffect(() => {
1878
- if (isServerSide) return;
1879
- setDatasets(createDatasets(rows, pagination.enabled ? perPage : null));
1880
- }, [rows, perPage, pagination, isServerSide]);
1881
1905
  useEffect(() => {
1882
1906
  if (isServerSide && typeof pagination.page === "number" && pagination.page >= 1)
1883
1907
  setActivePage(pagination.page - 1);
1884
1908
  }, [isServerSide, pagination.page]);
1885
1909
  const onSearchChange = (e) => {
1886
- const term = e.target.value;
1887
- setSearchTerm(term);
1888
- if (isServerSide) return;
1889
- const filtered = rows.filter(
1890
- (row) => Object.values(row).some(
1891
- (v) => !!v && String(v).toLowerCase().includes(term.toLowerCase())
1892
- )
1893
- );
1894
- setDatasets(createDatasets(filtered, pagination.enabled ? perPage : null));
1910
+ setSearchTerm(e.target.value);
1895
1911
  setActivePage(0);
1896
1912
  };
1897
1913
  const onPaginationChange = (perPageValue) => {
@@ -1930,9 +1946,17 @@ function Table({
1930
1946
  )
1931
1947
  ] }),
1932
1948
  /* @__PURE__ */ jsx("div", { children: header }),
1933
- /* @__PURE__ */ jsx("div", { className: "overflow-x-auto rounded-lg", children: /* @__PURE__ */ jsxs("table", { className: "w-full", children: [
1934
- /* @__PURE__ */ jsx(TableHeader, { columns }),
1935
- /* @__PURE__ */ jsx(TableBody, { columns, rows: currentPageRows, expandRow })
1949
+ /* @__PURE__ */ jsx("div", { className: "overflow-x-auto rounded-lg", children: /* @__PURE__ */ jsxs("table", { className: "w-full border-collapse", children: [
1950
+ /* @__PURE__ */ jsx(TableHeader, { columns, hasExpand: !!expandRow.enabled }),
1951
+ /* @__PURE__ */ jsx(
1952
+ TableBody,
1953
+ {
1954
+ columns,
1955
+ rows: currentPageRows,
1956
+ expandRow,
1957
+ getRowKey
1958
+ }
1959
+ )
1936
1960
  ] }) }),
1937
1961
  /* @__PURE__ */ jsx("div", { children: footer })
1938
1962
  ] });
@@ -2304,8 +2328,24 @@ function toCssVars(theme) {
2304
2328
  }
2305
2329
  return out;
2306
2330
  }
2331
+ var CSS_VALUE_REJECT_RE = /[;{}<>\\]|\*\/|\/\*/;
2332
+ function isSafeCssValue(v) {
2333
+ if (typeof v !== "string") return false;
2334
+ if (v.length > 500) return false;
2335
+ return !CSS_VALUE_REJECT_RE.test(v);
2336
+ }
2307
2337
  function varsToStyleString(vars2) {
2308
- return Object.entries(vars2).map(([k, v]) => `${k}: ${v};`).join(" ");
2338
+ const out = [];
2339
+ for (const [k, v] of Object.entries(vars2)) {
2340
+ if (!isSafeCssValue(v)) {
2341
+ console.warn(
2342
+ `[ThemeProvider] Dropping unsafe value for "${k}". Theme values may contain letters, digits, and CSS punctuation but must not include: ; { } < > \\ /* */`
2343
+ );
2344
+ continue;
2345
+ }
2346
+ out.push(`${k}: ${v};`);
2347
+ }
2348
+ return out.join(" ");
2309
2349
  }
2310
2350
  function ThemeProvider({
2311
2351
  theme,