@dynostack/react-grid 0.1.0 → 0.2.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
@@ -8,6 +8,7 @@ var tailwindMerge = require('tailwind-merge');
8
8
  var radixUi = require('radix-ui');
9
9
  var jsxRuntime = require('react/jsx-runtime');
10
10
  var classVarianceAuthority = require('class-variance-authority');
11
+ var reactDom = require('react-dom');
11
12
 
12
13
  function cn(...inputs) {
13
14
  return tailwindMerge.twMerge(clsx.clsx(inputs));
@@ -1717,14 +1718,34 @@ function DataTableRowActions({
1717
1718
  onCancelEdit
1718
1719
  }) {
1719
1720
  const [open, setOpen] = react.useState(false);
1721
+ const [menuPosition, setMenuPosition] = react.useState({ top: 0, left: 0 });
1722
+ const triggerRef = react.useRef(null);
1720
1723
  const menuRef = react.useRef(null);
1724
+ react.useLayoutEffect(() => {
1725
+ if (!open || !triggerRef.current) return;
1726
+ const rect = triggerRef.current.getBoundingClientRect();
1727
+ setMenuPosition({
1728
+ top: rect.bottom + 4,
1729
+ left: Math.max(8, rect.right - 160)
1730
+ });
1731
+ }, [open]);
1721
1732
  react.useEffect(() => {
1722
1733
  if (!open) return;
1723
1734
  const onPointerDown = (event) => {
1724
- if (!menuRef.current?.contains(event.target)) setOpen(false);
1735
+ const target = event.target;
1736
+ if (!menuRef.current?.contains(target) && !triggerRef.current?.contains(target)) {
1737
+ setOpen(false);
1738
+ }
1725
1739
  };
1740
+ const onDismiss = () => setOpen(false);
1726
1741
  document.addEventListener("pointerdown", onPointerDown);
1727
- return () => document.removeEventListener("pointerdown", onPointerDown);
1742
+ window.addEventListener("scroll", onDismiss, true);
1743
+ window.addEventListener("resize", onDismiss);
1744
+ return () => {
1745
+ document.removeEventListener("pointerdown", onPointerDown);
1746
+ window.removeEventListener("scroll", onDismiss, true);
1747
+ window.removeEventListener("resize", onDismiss);
1748
+ };
1728
1749
  }, [open]);
1729
1750
  if (isEditing) {
1730
1751
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-end gap-1", children: [
@@ -1755,6 +1776,7 @@ function DataTableRowActions({
1755
1776
  /* @__PURE__ */ jsxRuntime.jsx(
1756
1777
  Button,
1757
1778
  {
1779
+ ref: triggerRef,
1758
1780
  variant: "ghost",
1759
1781
  size: "icon-xs",
1760
1782
  "aria-label": "Open row actions",
@@ -1763,41 +1785,52 @@ function DataTableRowActions({
1763
1785
  children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.MoreHorizontalIcon, {})
1764
1786
  }
1765
1787
  ),
1766
- open && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "absolute top-7 right-0 z-50 w-40 rounded-md border bg-popover p-1 text-popover-foreground shadow-md", children: [
1767
- actions.map((a) => /* @__PURE__ */ jsxRuntime.jsxs(
1768
- RowActionButton,
1769
- {
1770
- danger: a === "delete",
1771
- onClick: () => {
1772
- setOpen(false);
1773
- onAction?.(a, row);
1774
- },
1775
- children: [
1776
- builtinIcons[a],
1777
- " ",
1778
- builtinLabels[a]
1779
- ]
1780
- },
1781
- a
1782
- )),
1783
- customActions.length > 0 && actions.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "-mx-1 my-1 h-px bg-border" }),
1784
- customActions.filter((c) => c.show ? c.show(row) : true).map((c) => /* @__PURE__ */ jsxRuntime.jsxs(
1785
- RowActionButton,
1788
+ open && reactDom.createPortal(
1789
+ /* @__PURE__ */ jsxRuntime.jsxs(
1790
+ "div",
1786
1791
  {
1787
- danger: c.danger,
1788
- onClick: () => {
1789
- setOpen(false);
1790
- onAction?.(c.id, row);
1791
- },
1792
+ ref: menuRef,
1793
+ className: "fixed z-[1000] w-40 rounded-md border bg-popover p-1 text-popover-foreground shadow-lg",
1794
+ style: { top: menuPosition.top, left: menuPosition.left },
1792
1795
  children: [
1793
- c.icon,
1794
- " ",
1795
- c.label
1796
+ actions.map((a) => /* @__PURE__ */ jsxRuntime.jsxs(
1797
+ RowActionButton,
1798
+ {
1799
+ danger: a === "delete",
1800
+ onClick: () => {
1801
+ setOpen(false);
1802
+ onAction?.(a, row);
1803
+ },
1804
+ children: [
1805
+ builtinIcons[a],
1806
+ " ",
1807
+ builtinLabels[a]
1808
+ ]
1809
+ },
1810
+ a
1811
+ )),
1812
+ customActions.length > 0 && actions.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "-mx-1 my-1 h-px bg-border" }),
1813
+ customActions.filter((c) => c.show ? c.show(row) : true).map((c) => /* @__PURE__ */ jsxRuntime.jsxs(
1814
+ RowActionButton,
1815
+ {
1816
+ danger: c.danger,
1817
+ onClick: () => {
1818
+ setOpen(false);
1819
+ onAction?.(c.id, row);
1820
+ },
1821
+ children: [
1822
+ c.icon,
1823
+ " ",
1824
+ c.label
1825
+ ]
1826
+ },
1827
+ c.id
1828
+ ))
1796
1829
  ]
1797
- },
1798
- c.id
1799
- ))
1800
- ] })
1830
+ }
1831
+ ),
1832
+ document.body
1833
+ )
1801
1834
  ] });
1802
1835
  }
1803
1836
  function RowActionButton({
@@ -1993,57 +2026,148 @@ var TOKEN_TO_VAR = {
1993
2026
  radius: "--radius",
1994
2027
  fontFamily: "--font-family"
1995
2028
  };
1996
- function themeToStyle(theme) {
1997
- if (!theme) return {};
2029
+ function isModed(theme) {
2030
+ if (!theme) return false;
2031
+ return "light" in theme || "dark" in theme;
2032
+ }
2033
+ function tokensToStyle(tokens) {
2034
+ if (!tokens) return {};
1998
2035
  const out = {};
1999
- for (const [k, v] of Object.entries(theme)) {
2036
+ for (const [k, v] of Object.entries(tokens)) {
2000
2037
  if (v == null) continue;
2001
2038
  const cssVar = TOKEN_TO_VAR[k];
2002
2039
  if (cssVar) out[cssVar] = v;
2003
2040
  }
2004
2041
  return out;
2005
2042
  }
2006
- var themePresets = {
2043
+ function themeToStyle(theme) {
2044
+ if (!theme) return {};
2045
+ if (isModed(theme)) return tokensToStyle(theme.light);
2046
+ return tokensToStyle(theme);
2047
+ }
2048
+ function splitTheme(theme) {
2049
+ if (!theme) return { light: void 0, dark: void 0 };
2050
+ if (isModed(theme)) return { light: theme.light, dark: theme.dark };
2051
+ return { light: theme, dark: void 0 };
2052
+ }
2053
+ function buildPreset(hue, chroma = 0.03) {
2054
+ const c = chroma;
2055
+ return {
2056
+ light: {
2057
+ background: `oklch(0.99 ${c * 0.2} ${hue})`,
2058
+ foreground: `oklch(0.18 ${c * 1.2} ${hue})`,
2059
+ card: `oklch(1 0 0)`,
2060
+ cardForeground: `oklch(0.18 ${c * 1.2} ${hue})`,
2061
+ popover: `oklch(1 0 0)`,
2062
+ popoverForeground: `oklch(0.18 ${c * 1.2} ${hue})`,
2063
+ primary: `oklch(0.55 0.2 ${hue})`,
2064
+ primaryForeground: `oklch(0.99 0 0)`,
2065
+ secondary: `oklch(0.96 ${c} ${hue})`,
2066
+ secondaryForeground: `oklch(0.25 ${c * 2} ${hue})`,
2067
+ muted: `oklch(0.96 ${c} ${hue})`,
2068
+ mutedForeground: `oklch(0.5 ${c * 1.5} ${hue})`,
2069
+ accent: `oklch(0.94 ${c * 1.2} ${hue})`,
2070
+ accentForeground: `oklch(0.3 ${c * 3} ${hue})`,
2071
+ destructive: `oklch(0.577 0.245 27.325)`,
2072
+ destructiveForeground: `oklch(0.985 0 0)`,
2073
+ border: `oklch(0.9 ${c * 0.8} ${hue})`,
2074
+ input: `oklch(0.9 ${c * 0.8} ${hue})`,
2075
+ ring: `oklch(0.55 0.2 ${hue} / 0.5)`,
2076
+ radius: "0.625rem"
2077
+ },
2078
+ dark: {
2079
+ background: `oklch(0.16 ${c * 0.6} ${hue})`,
2080
+ foreground: `oklch(0.97 ${c * 0.2} ${hue})`,
2081
+ card: `oklch(0.21 ${c * 0.8} ${hue})`,
2082
+ cardForeground: `oklch(0.97 ${c * 0.2} ${hue})`,
2083
+ popover: `oklch(0.21 ${c * 0.8} ${hue})`,
2084
+ popoverForeground: `oklch(0.97 ${c * 0.2} ${hue})`,
2085
+ primary: `oklch(0.7 0.18 ${hue})`,
2086
+ primaryForeground: `oklch(0.16 ${c * 0.6} ${hue})`,
2087
+ secondary: `oklch(0.27 ${c * 0.8} ${hue})`,
2088
+ secondaryForeground: `oklch(0.97 ${c * 0.2} ${hue})`,
2089
+ muted: `oklch(0.27 ${c * 0.8} ${hue})`,
2090
+ mutedForeground: `oklch(0.7 ${c * 0.5} ${hue})`,
2091
+ accent: `oklch(0.3 ${c * 1.5} ${hue})`,
2092
+ accentForeground: `oklch(0.97 ${c * 0.2} ${hue})`,
2093
+ destructive: `oklch(0.577 0.245 27.325 / 0.6)`,
2094
+ destructiveForeground: `oklch(0.985 0 0)`,
2095
+ border: `oklch(0.27 ${c * 0.8} ${hue})`,
2096
+ input: `oklch(0.27 ${c * 0.8} ${hue})`,
2097
+ ring: `oklch(0.7 0.18 ${hue} / 0.5)`,
2098
+ radius: "0.625rem"
2099
+ }
2100
+ };
2101
+ }
2102
+ function tokensToCssBlock(tokens) {
2103
+ if (!tokens) return "";
2104
+ const parts = [];
2105
+ for (const [k, v] of Object.entries(tokens)) {
2106
+ if (v == null) continue;
2107
+ const cssVar = TOKEN_TO_VAR[k];
2108
+ if (cssVar) parts.push(`${cssVar}:${v}`);
2109
+ }
2110
+ return parts.join(";");
2111
+ }
2112
+ var neutral = {
2007
2113
  light: {
2008
2114
  background: "oklch(1 0 0)",
2009
2115
  foreground: "oklch(0.145 0 0)",
2010
2116
  card: "oklch(1 0 0)",
2011
2117
  cardForeground: "oklch(0.145 0 0)",
2118
+ popover: "oklch(1 0 0)",
2119
+ popoverForeground: "oklch(0.145 0 0)",
2012
2120
  primary: "oklch(0.205 0 0)",
2013
2121
  primaryForeground: "oklch(0.985 0 0)",
2014
- border: "oklch(0.922 0 0)"
2122
+ secondary: "oklch(0.97 0 0)",
2123
+ secondaryForeground: "oklch(0.205 0 0)",
2124
+ muted: "oklch(0.97 0 0)",
2125
+ mutedForeground: "oklch(0.556 0 0)",
2126
+ accent: "oklch(0.97 0 0)",
2127
+ accentForeground: "oklch(0.205 0 0)",
2128
+ destructive: "oklch(0.577 0.245 27.325)",
2129
+ destructiveForeground: "oklch(0.985 0 0)",
2130
+ border: "oklch(0.922 0 0)",
2131
+ input: "oklch(0.922 0 0)",
2132
+ ring: "oklch(0.708 0 0)",
2133
+ radius: "0.625rem"
2015
2134
  },
2016
2135
  dark: {
2017
2136
  background: "oklch(0.145 0 0)",
2018
2137
  foreground: "oklch(0.985 0 0)",
2019
2138
  card: "oklch(0.205 0 0)",
2020
2139
  cardForeground: "oklch(0.985 0 0)",
2140
+ popover: "oklch(0.205 0 0)",
2141
+ popoverForeground: "oklch(0.985 0 0)",
2021
2142
  primary: "oklch(0.985 0 0)",
2022
2143
  primaryForeground: "oklch(0.205 0 0)",
2023
- border: "oklch(0.269 0 0)"
2024
- },
2025
- emerald: {
2026
- primary: "oklch(0.66 0.15 162)",
2027
- primaryForeground: "oklch(1 0 0)",
2028
- accent: "oklch(0.94 0.04 162)",
2029
- accentForeground: "oklch(0.3 0.1 162)",
2030
- ring: "oklch(0.66 0.15 162 / 0.5)"
2031
- },
2032
- violet: {
2033
- primary: "oklch(0.55 0.22 285)",
2034
- primaryForeground: "oklch(1 0 0)",
2035
- accent: "oklch(0.94 0.04 285)",
2036
- accentForeground: "oklch(0.3 0.12 285)",
2037
- ring: "oklch(0.55 0.22 285 / 0.5)"
2038
- },
2039
- amber: {
2040
- primary: "oklch(0.7 0.18 65)",
2041
- primaryForeground: "oklch(0.2 0 0)",
2042
- accent: "oklch(0.94 0.05 65)",
2043
- accentForeground: "oklch(0.3 0.12 65)",
2044
- ring: "oklch(0.7 0.18 65 / 0.5)"
2144
+ secondary: "oklch(0.269 0 0)",
2145
+ secondaryForeground: "oklch(0.985 0 0)",
2146
+ muted: "oklch(0.269 0 0)",
2147
+ mutedForeground: "oklch(0.708 0 0)",
2148
+ accent: "oklch(0.269 0 0)",
2149
+ accentForeground: "oklch(0.985 0 0)",
2150
+ destructive: "oklch(0.577 0.245 27.325 / 0.6)",
2151
+ destructiveForeground: "oklch(0.985 0 0)",
2152
+ border: "oklch(0.269 0 0)",
2153
+ input: "oklch(0.269 0 0)",
2154
+ ring: "oklch(0.439 0 0)",
2155
+ radius: "0.625rem"
2045
2156
  }
2046
2157
  };
2158
+ var themePresets = {
2159
+ neutral,
2160
+ light: { light: neutral.light },
2161
+ dark: { light: neutral.dark },
2162
+ violet: buildPreset(285),
2163
+ emerald: buildPreset(162),
2164
+ amber: buildPreset(65),
2165
+ rose: buildPreset(15),
2166
+ sky: buildPreset(235),
2167
+ slate: buildPreset(240, 0.015)
2168
+ };
2169
+ var ISOLATE_LIGHT_TOKENS = neutral.light;
2170
+ var ISOLATE_DARK_TOKENS = neutral.dark;
2047
2171
  function getPinningStyles(column) {
2048
2172
  const isPinned = column.getIsPinned();
2049
2173
  if (!isPinned) return {};
@@ -2166,6 +2290,8 @@ var DEFAULT_LABELS = {
2166
2290
  total: "Total",
2167
2291
  noData: "No data.",
2168
2292
  noResults: "No rows match the current filters.",
2293
+ loadError: "Could not load rows.",
2294
+ retry: "Retry",
2169
2295
  refreshing: "Refreshing",
2170
2296
  rowsPerPage: "Rows per page",
2171
2297
  page: "Page",
@@ -2179,6 +2305,7 @@ var DENSITY_CLASS = {
2179
2305
  function DataTable({
2180
2306
  data,
2181
2307
  columns,
2308
+ dataSource,
2182
2309
  isLoading,
2183
2310
  isFetching,
2184
2311
  onRefresh,
@@ -2205,12 +2332,13 @@ function DataTable({
2205
2332
  features,
2206
2333
  labels: labelsProp,
2207
2334
  density = "default",
2208
- theme
2335
+ theme,
2336
+ isolate = false
2209
2337
  }) {
2210
2338
  const labels = { ...DEFAULT_LABELS, ...labelsProp };
2211
2339
  const f = {
2212
2340
  search: features?.search ?? true,
2213
- refresh: features?.refresh ?? !!onRefresh,
2341
+ refresh: features?.refresh ?? !!(onRefresh || dataSource),
2214
2342
  columnVisibility: features?.columnVisibility ?? true,
2215
2343
  export: features?.export ?? true,
2216
2344
  addRow: features?.addRow ?? !!onAddRow,
@@ -2221,7 +2349,20 @@ function DataTable({
2221
2349
  reordering: features?.reordering ?? true,
2222
2350
  pinning: features?.pinning ?? true
2223
2351
  };
2224
- const themeStyle = themeToStyle(theme);
2352
+ const rawInstanceId = react.useId();
2353
+ const instanceId = react.useMemo(
2354
+ () => `dyno-grid-${rawInstanceId.replace(/[^a-zA-Z0-9]/g, "")}`,
2355
+ [rawInstanceId]
2356
+ );
2357
+ const { lightStyle, darkCss } = react.useMemo(() => {
2358
+ const { light, dark } = splitTheme(theme);
2359
+ const lightTokens = isolate ? { ...ISOLATE_LIGHT_TOKENS, ...light ?? {} } : light;
2360
+ const darkTokens = isolate ? { ...ISOLATE_DARK_TOKENS, ...dark ?? light ?? {} } : dark;
2361
+ return {
2362
+ lightStyle: tokensToStyle(lightTokens),
2363
+ darkCss: darkTokens ? tokensToCssBlock(darkTokens) : null
2364
+ };
2365
+ }, [theme, isolate]);
2225
2366
  const [sorting, setSorting] = react.useState([]);
2226
2367
  const [columnFilters, setColumnFilters] = react.useState([]);
2227
2368
  const [internalGlobalFilter, setInternalGlobalFilter] = react.useState("");
@@ -2254,14 +2395,80 @@ function DataTable({
2254
2395
  pageIndex: 0,
2255
2396
  pageSize: initialPageSize
2256
2397
  });
2398
+ const [sourceRows, setSourceRows] = react.useState(
2399
+ () => dataSource?.initialData ?? []
2400
+ );
2401
+ const [sourceTotalRecords, setSourceTotalRecords] = react.useState(void 0);
2402
+ const [sourceError, setSourceError] = react.useState(null);
2403
+ const [sourceLoading, setSourceLoading] = react.useState(false);
2404
+ const [sourceFetching, setSourceFetching] = react.useState(false);
2405
+ const [sourceRefreshKey, setSourceRefreshKey] = react.useState(0);
2257
2406
  const [editingCell, setEditingCell] = react.useState(null);
2258
2407
  const [editingRowId, setEditingRowId] = react.useState(null);
2259
2408
  const [rowDraft, setRowDraft] = react.useState({});
2409
+ const dataSourceEnabled = !!dataSource && dataSource.enabled !== false;
2410
+ const dataSourceMode = dataSource?.mode ?? "client";
2411
+ const isServerSideDataSource = dataSourceEnabled && dataSourceMode === "server";
2412
+ const dataSourceDeps = dataSource?.deps ?? [];
2413
+ react.useEffect(() => {
2414
+ if (!dataSourceEnabled || !dataSource) return;
2415
+ let cancelled = false;
2416
+ const isInitialLoad = sourceRows.length === 0;
2417
+ setSourceError(null);
2418
+ setSourceLoading(isInitialLoad);
2419
+ setSourceFetching(!isInitialLoad);
2420
+ dataSource.fetchRows({
2421
+ pageIndex: pagination.pageIndex,
2422
+ pageSize: pagination.pageSize,
2423
+ sorting,
2424
+ columnFilters,
2425
+ globalFilter
2426
+ }).then((result) => {
2427
+ if (cancelled) return;
2428
+ const rows2 = Array.isArray(result) ? result : result.rows;
2429
+ setSourceRows(rows2);
2430
+ setSourceTotalRecords(
2431
+ Array.isArray(result) ? rows2.length : result.totalRecords ?? rows2.length
2432
+ );
2433
+ }).catch((error) => {
2434
+ if (cancelled) return;
2435
+ setSourceError(error);
2436
+ dataSource.onError?.(error, {
2437
+ type: isInitialLoad ? "load" : "refresh",
2438
+ message: isInitialLoad ? labels.loadError : labels.refreshing
2439
+ });
2440
+ }).finally(() => {
2441
+ if (cancelled) return;
2442
+ setSourceLoading(false);
2443
+ setSourceFetching(false);
2444
+ });
2445
+ return () => {
2446
+ cancelled = true;
2447
+ };
2448
+ }, [
2449
+ dataSourceEnabled,
2450
+ dataSource,
2451
+ sourceRefreshKey,
2452
+ pagination.pageIndex,
2453
+ pagination.pageSize,
2454
+ sorting,
2455
+ columnFilters,
2456
+ globalFilter,
2457
+ labels.loadError,
2458
+ labels.refreshing,
2459
+ // eslint-disable-next-line react-hooks/exhaustive-deps
2460
+ ...dataSourceDeps
2461
+ ]);
2462
+ const baseData = dataSourceEnabled ? sourceRows : data ?? [];
2463
+ const effectiveIsLoading = dataSourceEnabled ? sourceLoading : isLoading;
2464
+ const effectiveIsFetching = dataSourceEnabled ? sourceFetching : isFetching;
2465
+ const effectiveOnRefresh = dataSourceEnabled ? () => setSourceRefreshKey((key) => key + 1) : onRefresh;
2260
2466
  const [extraRows, setExtraRows] = react.useState([]);
2261
2467
  const mergedData = react.useMemo(
2262
- () => extraRows.length ? [...extraRows, ...data] : data,
2263
- [extraRows, data]
2468
+ () => extraRows.length ? [...extraRows, ...baseData] : baseData,
2469
+ [extraRows, baseData]
2264
2470
  );
2471
+ const effectiveTotalRecords = totalRecords ?? (dataSourceEnabled ? sourceTotalRecords ?? mergedData.length : mergedData.length);
2265
2472
  const augmentedColumns = react.useMemo(() => {
2266
2473
  const wrap = (col) => {
2267
2474
  const meta = col.meta;
@@ -2411,6 +2618,10 @@ function DataTable({
2411
2618
  enableRowSelection: true,
2412
2619
  enableExpanding: !!(renderSubRow || getSubRows),
2413
2620
  enablePinning: f.pinning,
2621
+ manualPagination: isServerSideDataSource,
2622
+ manualSorting: isServerSideDataSource,
2623
+ manualFiltering: isServerSideDataSource,
2624
+ pageCount: isServerSideDataSource ? Math.max(1, Math.ceil(effectiveTotalRecords / pagination.pageSize)) : void 0,
2414
2625
  getRowId: (row) => String(row.id),
2415
2626
  getSubRows,
2416
2627
  getRowCanExpand: (row) => {
@@ -2435,27 +2646,56 @@ function DataTable({
2435
2646
  getExpandedRowModel: reactTable.getExpandedRowModel(),
2436
2647
  getPaginationRowModel: reactTable.getPaginationRowModel()
2437
2648
  });
2438
- const dragState = react.useRef({ src: null });
2439
- const onHeaderDragStart = (id, e) => {
2440
- dragState.current.src = id;
2441
- e.dataTransfer.effectAllowed = "move";
2442
- e.dataTransfer.setData("text/plain", id);
2649
+ const reorderState = react.useRef(null);
2650
+ const [activeReorderId, setActiveReorderId] = react.useState(null);
2651
+ const [dragOverColId, setDragOverColId] = react.useState(null);
2652
+ const isInteractiveTarget = (target) => {
2653
+ const el = target;
2654
+ if (!el || !el.closest) return false;
2655
+ return !!el.closest(
2656
+ 'button, a, input, select, textarea, [role="menuitem"], [data-no-drag]'
2657
+ );
2443
2658
  };
2444
- const onHeaderDragOver = (e) => {
2445
- e.preventDefault();
2446
- e.dataTransfer.dropEffect = "move";
2659
+ const onHeaderPointerDown = (id) => (e) => {
2660
+ if (e.button !== 0) return;
2661
+ if (isInteractiveTarget(e.target)) return;
2662
+ reorderState.current = { x: e.clientX, y: e.clientY, id };
2447
2663
  };
2448
- const onHeaderDrop = (targetId) => {
2449
- const src = dragState.current.src;
2450
- dragState.current.src = null;
2451
- if (!src || src === targetId) return;
2664
+ const onHeaderPointerMove = (e) => {
2665
+ const start = reorderState.current;
2666
+ if (!start) return;
2667
+ if (!activeReorderId) {
2668
+ const dx = e.clientX - start.x;
2669
+ const dy = e.clientY - start.y;
2670
+ if (Math.hypot(dx, dy) < 6) return;
2671
+ setActiveReorderId(start.id);
2672
+ e.currentTarget.setPointerCapture(e.pointerId);
2673
+ }
2674
+ const el = document.elementFromPoint(e.clientX, e.clientY);
2675
+ const th = el ? el.closest("th[data-col-id]") : null;
2676
+ const overId = th?.dataset.colId ?? null;
2677
+ const next = overId && overId !== start.id ? overId : null;
2678
+ if (next !== dragOverColId) setDragOverColId(next);
2679
+ };
2680
+ const onHeaderPointerUp = (e) => {
2681
+ const start = reorderState.current;
2682
+ reorderState.current = null;
2683
+ if (!activeReorderId) return;
2684
+ try {
2685
+ e.currentTarget.releasePointerCapture(e.pointerId);
2686
+ } catch {
2687
+ }
2688
+ const target = dragOverColId;
2689
+ setActiveReorderId(null);
2690
+ setDragOverColId(null);
2691
+ if (!start || !target || target === start.id) return;
2452
2692
  setColumnOrder((order) => {
2453
2693
  const next = [...order];
2454
- const from = next.indexOf(src);
2455
- const to = next.indexOf(targetId);
2694
+ const from = next.indexOf(start.id);
2695
+ const to = next.indexOf(target);
2456
2696
  if (from === -1 || to === -1) return order;
2457
2697
  next.splice(from, 1);
2458
- next.splice(to, 0, src);
2698
+ next.splice(to, 0, start.id);
2459
2699
  return next;
2460
2700
  });
2461
2701
  };
@@ -2501,20 +2741,26 @@ function DataTable({
2501
2741
  children: /* @__PURE__ */ jsxRuntime.jsxs(
2502
2742
  "div",
2503
2743
  {
2744
+ "data-dyno-grid": instanceId,
2504
2745
  className: cn(
2505
2746
  "flex flex-col rounded-lg border bg-card text-card-foreground shadow-sm",
2506
2747
  DENSITY_CLASS[density],
2507
2748
  className
2508
2749
  ),
2509
- style: themeStyle,
2750
+ style: lightStyle,
2510
2751
  children: [
2752
+ darkCss && /* @__PURE__ */ jsxRuntime.jsxs("style", { children: [
2753
+ `@media (prefers-color-scheme: dark){[data-dyno-grid="${instanceId}"]{${darkCss}}}`,
2754
+ `.dark [data-dyno-grid="${instanceId}"]{${darkCss}}`,
2755
+ `[data-dyno-grid="${instanceId}"].dark{${darkCss}}`
2756
+ ] }),
2511
2757
  /* @__PURE__ */ jsxRuntime.jsx(
2512
2758
  DataTableToolbar,
2513
2759
  {
2514
2760
  table,
2515
- totalRecords: totalRecords ?? mergedData.length,
2516
- isFetching,
2517
- onRefresh: f.refresh ? onRefresh : void 0,
2761
+ totalRecords: effectiveTotalRecords,
2762
+ isFetching: effectiveIsFetching,
2763
+ onRefresh: f.refresh ? effectiveOnRefresh : void 0,
2518
2764
  onAddRow: f.addRow ? handleAddRow : void 0,
2519
2765
  onBulkDelete,
2520
2766
  exportFileName,
@@ -2542,32 +2788,38 @@ function DataTable({
2542
2788
  const meta = header.column.columnDef.meta;
2543
2789
  const isUtility = header.column.id === "__select" || header.column.id === "__expand" || header.column.id === "__actions";
2544
2790
  const isPinned = !!header.column.getIsPinned();
2791
+ const canReorder = !isUtility && f.reordering;
2792
+ const isDropTarget = canReorder && dragOverColId === header.column.id;
2793
+ const isBeingDragged = canReorder && activeReorderId === header.column.id;
2545
2794
  return /* @__PURE__ */ jsxRuntime.jsxs(
2546
2795
  "th",
2547
2796
  {
2548
2797
  colSpan: header.colSpan,
2798
+ "data-col-id": header.column.id,
2799
+ onPointerDown: canReorder ? onHeaderPointerDown(header.column.id) : void 0,
2800
+ onPointerMove: canReorder ? onHeaderPointerMove : void 0,
2801
+ onPointerUp: canReorder ? onHeaderPointerUp : void 0,
2802
+ onPointerCancel: canReorder ? onHeaderPointerUp : void 0,
2549
2803
  style: {
2550
2804
  width: header.getSize(),
2551
2805
  ...getPinningStyles(header.column),
2552
2806
  ...isPinned ? { zIndex: 2 } : {}
2553
2807
  },
2554
2808
  className: cn(
2555
- "group/th relative h-9 px-2 text-left align-middle text-xs font-medium text-muted-foreground",
2809
+ "group/th relative h-9 px-2 text-left align-middle text-xs font-medium text-muted-foreground select-none",
2810
+ canReorder && "cursor-grab active:cursor-grabbing",
2556
2811
  isPinned && "bg-card",
2812
+ isBeingDragged && "opacity-50",
2813
+ isDropTarget && "bg-primary/10 ring-2 ring-inset ring-primary/40",
2557
2814
  meta?.headerClassName
2558
2815
  ),
2559
2816
  children: [
2560
2817
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1", children: [
2561
- !isUtility && f.reordering && /* @__PURE__ */ jsxRuntime.jsx(
2562
- "span",
2818
+ canReorder && /* @__PURE__ */ jsxRuntime.jsx(
2819
+ lucideReact.GripVerticalIcon,
2563
2820
  {
2564
- draggable: true,
2565
- onDragStart: (e) => onHeaderDragStart(header.column.id, e),
2566
- onDragOver: onHeaderDragOver,
2567
- onDrop: () => onHeaderDrop(header.column.id),
2568
- className: "-ml-1 cursor-grab rounded p-0.5 text-muted-foreground/40 opacity-0 transition group-hover/th:opacity-100 hover:bg-muted hover:text-foreground active:cursor-grabbing",
2569
- title: "Drag to reorder",
2570
- children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.GripVerticalIcon, { className: "size-3" })
2821
+ className: "-ml-1 size-3 shrink-0 text-muted-foreground/40 opacity-0 transition group-hover/th:opacity-100",
2822
+ "aria-hidden": "true"
2571
2823
  }
2572
2824
  ),
2573
2825
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "min-w-0 flex-1", children: header.isPlaceholder ? null : reactTable.flexRender(header.column.columnDef.header, header.getContext()) })
@@ -2575,6 +2827,8 @@ function DataTable({
2575
2827
  header.column.getCanResize() && /* @__PURE__ */ jsxRuntime.jsx(
2576
2828
  "span",
2577
2829
  {
2830
+ "data-no-drag": true,
2831
+ onPointerDown: (e) => e.stopPropagation(),
2578
2832
  onMouseDown: header.getResizeHandler(),
2579
2833
  onTouchStart: header.getResizeHandler(),
2580
2834
  onDoubleClick: () => header.column.resetSize(),
@@ -2589,7 +2843,25 @@ function DataTable({
2589
2843
  header.id
2590
2844
  );
2591
2845
  }) }, hg.id)) }),
2592
- /* @__PURE__ */ jsxRuntime.jsx("tbody", { children: isLoading ? renderSkeleton(visibleLeafCount, pagination.pageSize) : rows.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("tr", { children: /* @__PURE__ */ jsxRuntime.jsx(
2846
+ /* @__PURE__ */ jsxRuntime.jsx("tbody", { children: effectiveIsLoading ? renderSkeleton(visibleLeafCount, pagination.pageSize) : sourceError && rows.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("tr", { children: /* @__PURE__ */ jsxRuntime.jsx(
2847
+ "td",
2848
+ {
2849
+ colSpan: visibleLeafCount,
2850
+ className: "h-32 text-center text-sm text-muted-foreground",
2851
+ children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col items-center gap-2", children: [
2852
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: labels.loadError }),
2853
+ effectiveOnRefresh && /* @__PURE__ */ jsxRuntime.jsx(
2854
+ "button",
2855
+ {
2856
+ type: "button",
2857
+ onClick: effectiveOnRefresh,
2858
+ className: "rounded-md border px-2 py-1 text-xs text-foreground hover:bg-muted",
2859
+ children: labels.retry
2860
+ }
2861
+ )
2862
+ ] })
2863
+ }
2864
+ ) }) : rows.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("tr", { children: /* @__PURE__ */ jsxRuntime.jsx(
2593
2865
  "td",
2594
2866
  {
2595
2867
  colSpan: visibleLeafCount,
@@ -2608,7 +2880,7 @@ function DataTable({
2608
2880
  ]
2609
2881
  }
2610
2882
  ),
2611
- isFetching && !isLoading && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pointer-events-none absolute top-1.5 right-2 flex items-center gap-1.5 rounded-md bg-background/80 px-2 py-1 text-xs text-muted-foreground shadow-sm backdrop-blur", children: [
2883
+ effectiveIsFetching && !effectiveIsLoading && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pointer-events-none absolute top-1.5 right-2 flex items-center gap-1.5 rounded-md bg-background/80 px-2 py-1 text-xs text-muted-foreground shadow-sm backdrop-blur", children: [
2612
2884
  /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2Icon, { className: "size-3 animate-spin" }),
2613
2885
  " ",
2614
2886
  labels.refreshing
@@ -2675,15 +2947,21 @@ exports.DataTablePagination = DataTablePagination;
2675
2947
  exports.DataTableRowActions = DataTableRowActions;
2676
2948
  exports.DataTableToolbar = DataTableToolbar;
2677
2949
  exports.EditableCell = EditableCell;
2950
+ exports.ISOLATE_DARK_TOKENS = ISOLATE_DARK_TOKENS;
2951
+ exports.ISOLATE_LIGHT_TOKENS = ISOLATE_LIGHT_TOKENS;
2678
2952
  exports.booleanFilterFn = booleanFilterFn;
2953
+ exports.buildPreset = buildPreset;
2679
2954
  exports.dateFilterFn = dateFilterFn;
2680
2955
  exports.exportToCsv = exportToCsv;
2681
2956
  exports.exportToExcel = exportToExcel;
2682
2957
  exports.isFilterActive = isFilterActive;
2683
2958
  exports.numberFilterFn = numberFilterFn;
2684
2959
  exports.setFilterFn = setFilterFn;
2960
+ exports.splitTheme = splitTheme;
2685
2961
  exports.textFilterFn = textFilterFn;
2686
2962
  exports.themePresets = themePresets;
2687
2963
  exports.themeToStyle = themeToStyle;
2964
+ exports.tokensToCssBlock = tokensToCssBlock;
2965
+ exports.tokensToStyle = tokensToStyle;
2688
2966
  //# sourceMappingURL=index.cjs.map
2689
2967
  //# sourceMappingURL=index.cjs.map