@wordrhyme/auto-crud 0.10.0 → 1.0.1

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
@@ -35,6 +35,8 @@ let tailwind_merge = require("tailwind-merge");
35
35
  tailwind_merge = __toESM(tailwind_merge);
36
36
  let react_jsx_runtime = require("react/jsx-runtime");
37
37
  react_jsx_runtime = __toESM(react_jsx_runtime);
38
+ let nanoid = require("nanoid");
39
+ nanoid = __toESM(nanoid);
38
40
  let __dnd_kit_core = require("@dnd-kit/core");
39
41
  __dnd_kit_core = __toESM(__dnd_kit_core);
40
42
  let __dnd_kit_modifiers = require("@dnd-kit/modifiers");
@@ -47,8 +49,6 @@ let __radix_ui_react_slot = require("@radix-ui/react-slot");
47
49
  __radix_ui_react_slot = __toESM(__radix_ui_react_slot);
48
50
  let react_dom = require("react-dom");
49
51
  react_dom = __toESM(react_dom);
50
- let nanoid = require("nanoid");
51
- nanoid = __toESM(nanoid);
52
52
  let __radix_ui_react_direction = require("@radix-ui/react-direction");
53
53
  __radix_ui_react_direction = __toESM(__radix_ui_react_direction);
54
54
  let zod = require("zod");
@@ -474,6 +474,104 @@ function DataTable({ table, actionBar, children, className,...props }) {
474
474
  });
475
475
  }
476
476
 
477
+ //#endregion
478
+ //#region src/lib/id.ts
479
+ const prefixes = {};
480
+ function generateId(prefixOrOptions, inputOptions = {}) {
481
+ const finalOptions = typeof prefixOrOptions === "object" ? prefixOrOptions : inputOptions;
482
+ const prefix = typeof prefixOrOptions === "object" ? void 0 : prefixOrOptions;
483
+ const { length = 12, separator = "_" } = finalOptions;
484
+ const id = (0, nanoid.customAlphabet)("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz", length)();
485
+ return prefix && prefix in prefixes ? `${prefixes[prefix]}${separator}${id}` : id;
486
+ }
487
+
488
+ //#endregion
489
+ //#region src/lib/readable-filters.ts
490
+ const OP_SUFFIX = "__op";
491
+ const INDEX_SEPARATOR = "__";
492
+ const RANGE_VARIANTS = new Set(["range", "dateRange"]);
493
+ function getColumnId(column) {
494
+ if ("id" in column && column.id) return String(column.id);
495
+ if ("accessorKey" in column && column.accessorKey) return String(column.accessorKey);
496
+ return null;
497
+ }
498
+ function getColumnVariant(column) {
499
+ if ("columnDef" in column) return column.columnDef.meta?.variant;
500
+ return column.meta?.variant;
501
+ }
502
+ function splitKey(key) {
503
+ const match = key.match(/^(.*?)(?:__(\d+))?$/);
504
+ if (!match) return { base: key };
505
+ const index = match[2] ? Number(match[2]) : void 0;
506
+ return {
507
+ base: match[1] ?? key,
508
+ index
509
+ };
510
+ }
511
+ function isFilterKey(key, columnIds) {
512
+ const { base } = splitKey(key.endsWith(OP_SUFFIX) ? key.slice(0, -4) : key);
513
+ return columnIds.has(base);
514
+ }
515
+ function parseValueByVariant(value, variant) {
516
+ if (variant === "multiSelect") return value ? value.split(",").filter(Boolean) : [];
517
+ if (RANGE_VARIANTS.has(variant)) {
518
+ const parts = value.split(",");
519
+ return [parts[0] ?? "", parts[1] ?? ""];
520
+ }
521
+ if (variant === "select") return value.split(",").filter(Boolean)[0] ?? "";
522
+ return value;
523
+ }
524
+ function serializeValue(value, variant) {
525
+ if (Array.isArray(value)) {
526
+ if (RANGE_VARIANTS.has(variant)) return [value[0] ?? "", value[1] ?? ""].join(",");
527
+ return value.join(",");
528
+ }
529
+ return value;
530
+ }
531
+ function parseReadableFilters(params, columns) {
532
+ const columnsById = /* @__PURE__ */ new Map();
533
+ for (const column of columns) {
534
+ const id = getColumnId(column);
535
+ if (id) columnsById.set(id, column);
536
+ }
537
+ const filters = [];
538
+ for (const [key, rawValue] of params.entries()) {
539
+ if (key.endsWith(OP_SUFFIX)) continue;
540
+ const { base } = splitKey(key);
541
+ const column = columnsById.get(base);
542
+ if (!column) continue;
543
+ const variant = getColumnVariant(column) ?? "text";
544
+ const opKey = `${key}${OP_SUFFIX}`;
545
+ const operator = params.get(opKey) ?? getDefaultFilterOperator(variant);
546
+ const parsedValue = operator === "isEmpty" || operator === "isNotEmpty" ? "" : parseValueByVariant(rawValue, variant);
547
+ filters.push({
548
+ id: base,
549
+ value: parsedValue,
550
+ variant,
551
+ operator,
552
+ filterId: key || generateId({ length: 8 })
553
+ });
554
+ }
555
+ return filters;
556
+ }
557
+ function applyReadableFilters(params, columns, filters) {
558
+ const columnIds = new Set(columns.map((column) => getColumnId(column)).filter(Boolean));
559
+ for (const key of Array.from(params.keys())) if (isFilterKey(key, columnIds)) params.delete(key);
560
+ const counters = /* @__PURE__ */ new Map();
561
+ for (const filter of filters) {
562
+ const columnId = String(filter.id);
563
+ const count = (counters.get(columnId) ?? 0) + 1;
564
+ counters.set(columnId, count);
565
+ const key = count === 1 ? columnId : `${columnId}${INDEX_SEPARATOR}${count}`;
566
+ const variant = filter.variant ?? "text";
567
+ const value = serializeValue(filter.value, variant);
568
+ params.set(key, filter.operator === "isEmpty" || filter.operator === "isNotEmpty" ? "" : value);
569
+ const defaultOperator = getDefaultFilterOperator(variant);
570
+ if (filter.operator !== defaultOperator) params.set(`${key}${OP_SUFFIX}`, filter.operator);
571
+ }
572
+ return params;
573
+ }
574
+
477
575
  //#endregion
478
576
  //#region src/hooks/use-url-state.ts
479
577
  const parseAsInteger = {
@@ -724,6 +822,54 @@ function useQueryStates(parsers, options = {}) {
724
822
  }, [setValues])];
725
823
  }
726
824
 
825
+ //#endregion
826
+ //#region src/hooks/use-readable-filters.ts
827
+ const URL_CHANGE_EVENT = "urlchange";
828
+ function useReadableFilters(columns, options = {}) {
829
+ const { history = "replace", scroll = false } = options;
830
+ const columnSnapshot = react.useMemo(() => columns, [columns]);
831
+ const lastWrittenUrlRef = react.useRef("");
832
+ const filtersRef = react.useRef(null);
833
+ const [filters, setFilters] = react.useState(() => {
834
+ const initial = parseReadableFilters(getUrlParams(), columnSnapshot);
835
+ filtersRef.current = initial;
836
+ return initial;
837
+ });
838
+ react.useEffect(() => {
839
+ const handleUrlChange = () => {
840
+ if (window.location.search === lastWrittenUrlRef.current) return;
841
+ const next = parseReadableFilters(getUrlParams(), columnSnapshot);
842
+ filtersRef.current = next;
843
+ setFilters(next);
844
+ };
845
+ window.addEventListener("popstate", handleUrlChange);
846
+ window.addEventListener(URL_CHANGE_EVENT, handleUrlChange);
847
+ return () => {
848
+ window.removeEventListener("popstate", handleUrlChange);
849
+ window.removeEventListener(URL_CHANGE_EVENT, handleUrlChange);
850
+ };
851
+ }, [columnSnapshot]);
852
+ const writeToUrl = react.useCallback((next) => {
853
+ const params = getUrlParams();
854
+ applyReadableFilters(params, columnSnapshot, next);
855
+ setSearchParams(params, {
856
+ history,
857
+ scroll
858
+ });
859
+ lastWrittenUrlRef.current = `?${params.toString()}`;
860
+ }, [
861
+ columnSnapshot,
862
+ history,
863
+ scroll
864
+ ]);
865
+ return [filters, react.useCallback((updater) => {
866
+ const next = typeof updater === "function" ? updater(filtersRef.current) : updater;
867
+ filtersRef.current = next;
868
+ setFilters(next);
869
+ writeToUrl(next);
870
+ }, [writeToUrl])];
871
+ }
872
+
727
873
  //#endregion
728
874
  //#region src/components/data-table/data-table-range-filter.tsx
729
875
  function DataTableRangeFilter({ filter, column, inputId, onFilterUpdate, className,...props }) {
@@ -1291,159 +1437,6 @@ function formatDate(date, opts = {}) {
1291
1437
  }
1292
1438
  }
1293
1439
 
1294
- //#endregion
1295
- //#region src/lib/id.ts
1296
- const prefixes = {};
1297
- function generateId(prefixOrOptions, inputOptions = {}) {
1298
- const finalOptions = typeof prefixOrOptions === "object" ? prefixOrOptions : inputOptions;
1299
- const prefix = typeof prefixOrOptions === "object" ? void 0 : prefixOrOptions;
1300
- const { length = 12, separator = "_" } = finalOptions;
1301
- const id = (0, nanoid.customAlphabet)("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz", length)();
1302
- return prefix && prefix in prefixes ? `${prefixes[prefix]}${separator}${id}` : id;
1303
- }
1304
-
1305
- //#endregion
1306
- //#region src/lib/readable-filters.ts
1307
- const OP_SUFFIX = "__op";
1308
- const INDEX_SEPARATOR = "__";
1309
- const RANGE_VARIANTS = new Set(["range", "dateRange"]);
1310
- function getColumnId(column) {
1311
- if ("id" in column && column.id) return String(column.id);
1312
- if ("accessorKey" in column && column.accessorKey) return String(column.accessorKey);
1313
- return null;
1314
- }
1315
- function getColumnVariant(column) {
1316
- if ("columnDef" in column) return column.columnDef.meta?.variant;
1317
- return column.meta?.variant;
1318
- }
1319
- function splitKey(key) {
1320
- const match = key.match(/^(.*?)(?:__(\d+))?$/);
1321
- if (!match) return { base: key };
1322
- const index = match[2] ? Number(match[2]) : void 0;
1323
- return {
1324
- base: match[1] ?? key,
1325
- index
1326
- };
1327
- }
1328
- function isFilterKey(key, columnIds) {
1329
- const { base } = splitKey(key.endsWith(OP_SUFFIX) ? key.slice(0, -4) : key);
1330
- return columnIds.has(base);
1331
- }
1332
- function parseValueByVariant(value, variant) {
1333
- if (variant === "multiSelect") return value ? value.split(",").filter(Boolean) : [];
1334
- if (RANGE_VARIANTS.has(variant)) {
1335
- const parts = value.split(",");
1336
- return [parts[0] ?? "", parts[1] ?? ""];
1337
- }
1338
- if (variant === "select") return value.split(",").filter(Boolean)[0] ?? "";
1339
- return value;
1340
- }
1341
- function serializeValue(value, variant) {
1342
- if (Array.isArray(value)) {
1343
- if (RANGE_VARIANTS.has(variant)) return [value[0] ?? "", value[1] ?? ""].join(",");
1344
- return value.join(",");
1345
- }
1346
- return value;
1347
- }
1348
- function parseReadableFilters(params, columns) {
1349
- const columnsById = /* @__PURE__ */ new Map();
1350
- for (const column of columns) {
1351
- const id = getColumnId(column);
1352
- if (id) columnsById.set(id, column);
1353
- }
1354
- const filters = [];
1355
- for (const [key, rawValue] of params.entries()) {
1356
- if (key.endsWith(OP_SUFFIX)) continue;
1357
- const { base } = splitKey(key);
1358
- const column = columnsById.get(base);
1359
- if (!column) continue;
1360
- const variant = getColumnVariant(column) ?? "text";
1361
- const opKey = `${key}${OP_SUFFIX}`;
1362
- const operator = params.get(opKey) ?? getDefaultFilterOperator(variant);
1363
- if (!rawValue && operator !== "isEmpty" && operator !== "isNotEmpty") continue;
1364
- const parsedValue = operator === "isEmpty" || operator === "isNotEmpty" ? "" : parseValueByVariant(rawValue, variant);
1365
- filters.push({
1366
- id: base,
1367
- value: parsedValue,
1368
- variant,
1369
- operator,
1370
- filterId: key || generateId({ length: 8 })
1371
- });
1372
- }
1373
- return filters;
1374
- }
1375
- function applyReadableFilters(params, columns, filters) {
1376
- const columnIds = new Set(columns.map((column) => getColumnId(column)).filter(Boolean));
1377
- for (const key of Array.from(params.keys())) if (isFilterKey(key, columnIds)) params.delete(key);
1378
- const counters = /* @__PURE__ */ new Map();
1379
- for (const filter of filters) {
1380
- const columnId = String(filter.id);
1381
- const count = (counters.get(columnId) ?? 0) + 1;
1382
- counters.set(columnId, count);
1383
- const key = count === 1 ? columnId : `${columnId}${INDEX_SEPARATOR}${count}`;
1384
- const variant = filter.variant ?? "text";
1385
- const value = serializeValue(filter.value, variant);
1386
- params.set(key, filter.operator === "isEmpty" || filter.operator === "isNotEmpty" ? "" : value);
1387
- const defaultOperator = getDefaultFilterOperator(variant);
1388
- if (filter.operator !== defaultOperator) params.set(`${key}${OP_SUFFIX}`, filter.operator);
1389
- }
1390
- return params;
1391
- }
1392
-
1393
- //#endregion
1394
- //#region src/hooks/use-readable-filters.ts
1395
- const URL_CHANGE_EVENT = "urlchange";
1396
- function useReadableFilters(columns, options = {}) {
1397
- const { debounceMs = 300, history = "replace", scroll = false } = options;
1398
- const columnSnapshot = react.useMemo(() => columns, [columns]);
1399
- const skipWriteRef = react.useRef(true);
1400
- const [filters, setFilters] = react.useState(() => parseReadableFilters(getUrlParams(), columnSnapshot));
1401
- react.useEffect(() => {
1402
- const handleUrlChange = () => {
1403
- skipWriteRef.current = true;
1404
- setFilters(parseReadableFilters(getUrlParams(), columnSnapshot));
1405
- };
1406
- window.addEventListener("popstate", handleUrlChange);
1407
- window.addEventListener(URL_CHANGE_EVENT, handleUrlChange);
1408
- return () => {
1409
- window.removeEventListener("popstate", handleUrlChange);
1410
- window.removeEventListener(URL_CHANGE_EVENT, handleUrlChange);
1411
- };
1412
- }, [columnSnapshot]);
1413
- const writeFilters = react.useCallback((next) => {
1414
- const params = getUrlParams();
1415
- applyReadableFilters(params, columnSnapshot, next);
1416
- setSearchParams(params, {
1417
- history,
1418
- scroll
1419
- });
1420
- }, [
1421
- columnSnapshot,
1422
- history,
1423
- scroll
1424
- ]);
1425
- const updateFilters = react.useCallback((updater) => {
1426
- setFilters((prev) => {
1427
- return typeof updater === "function" ? updater(prev) : updater;
1428
- });
1429
- }, []);
1430
- const debouncedWrite = useDebouncedCallback(writeFilters, debounceMs);
1431
- react.useEffect(() => {
1432
- if (skipWriteRef.current) {
1433
- skipWriteRef.current = false;
1434
- return;
1435
- }
1436
- if (debounceMs > 0) debouncedWrite(filters);
1437
- else writeFilters(filters);
1438
- }, [
1439
- filters,
1440
- debounceMs,
1441
- debouncedWrite,
1442
- writeFilters
1443
- ]);
1444
- return [filters, updateFilters];
1445
- }
1446
-
1447
1440
  //#endregion
1448
1441
  //#region src/components/data-table/data-table-filter-list.tsx
1449
1442
  const DEBOUNCE_MS$2 = 300;
@@ -1457,13 +1450,14 @@ function DataTableFilterList({ table, debounceMs = DEBOUNCE_MS$2, throttleMs = T
1457
1450
  const [open, setOpen] = react.useState(false);
1458
1451
  const addButtonRef = react.useRef(null);
1459
1452
  const columns = react.useMemo(() => {
1460
- return table.getAllColumns().filter((column) => column.columnDef.enableColumnFilter);
1453
+ return table.getAllColumns().filter((column) => {
1454
+ if (!column.getCanFilter()) return false;
1455
+ const modes = column.columnDef.meta?.modes;
1456
+ if (modes && !modes.includes("advanced")) return false;
1457
+ return true;
1458
+ });
1461
1459
  }, [table]);
1462
- const queryStateOptions = table.options.meta?.queryStateOptions;
1463
- const [filters, setFilters] = useReadableFilters(columns, {
1464
- ...queryStateOptions,
1465
- debounceMs: 0
1466
- });
1460
+ const [filters, setFilters] = useReadableFilters(columns, { debounceMs });
1467
1461
  const debouncedSetFilters = useDebouncedCallback(setFilters, debounceMs);
1468
1462
  const [joinOperator, setJoinOperator] = useQueryState(table.options.meta?.queryKeys?.joinOperator ?? "", parseAsStringEnum(["and", "or"]).withDefault("and"));
1469
1463
  const onFilterAdd = react.useCallback(() => {
@@ -1499,7 +1493,7 @@ function DataTableFilterList({ table, debounceMs = DEBOUNCE_MS$2, throttleMs = T
1499
1493
  });
1500
1494
  }, [filters, setFilters]);
1501
1495
  const onFiltersReset = react.useCallback(() => {
1502
- setFilters(null);
1496
+ setFilters([]);
1503
1497
  setJoinOperator("and");
1504
1498
  }, [setFilters, setJoinOperator]);
1505
1499
  react.useEffect(() => {
@@ -1943,7 +1937,12 @@ const REMOVE_FILTER_SHORTCUTS = ["backspace", "delete"];
1943
1937
  function DataTableFilterMenu({ table, debounceMs = DEBOUNCE_MS$1, throttleMs = THROTTLE_MS$1, shallow = true, disabled,...props }) {
1944
1938
  const id = react.useId();
1945
1939
  const columns = react.useMemo(() => {
1946
- return table.getAllColumns().filter((column) => column.columnDef.enableColumnFilter);
1940
+ return table.getAllColumns().filter((column) => {
1941
+ if (!column.columnDef.enableColumnFilter) return false;
1942
+ const modes = column.columnDef.meta?.modes;
1943
+ if (modes && !modes.includes("command")) return false;
1944
+ return true;
1945
+ });
1947
1946
  }, [table]);
1948
1947
  const [open, setOpen] = react.useState(false);
1949
1948
  const [selectedColumn, setSelectedColumn] = react.useState(null);
@@ -1963,11 +1962,7 @@ function DataTableFilterMenu({ table, debounceMs = DEBOUNCE_MS$1, throttleMs = T
1963
1962
  setSelectedColumn(null);
1964
1963
  }
1965
1964
  }, [inputValue, selectedColumn]);
1966
- const queryStateOptions = table.options.meta?.queryStateOptions;
1967
- const [filters, setFilters] = useReadableFilters(columns, {
1968
- ...queryStateOptions,
1969
- debounceMs: 0
1970
- });
1965
+ const [filters, setFilters] = useReadableFilters(columns, { debounceMs });
1971
1966
  const debouncedSetFilters = useDebouncedCallback(setFilters, debounceMs);
1972
1967
  const onFilterAdd = react.useCallback((column, value) => {
1973
1968
  if (!value.trim() && column.columnDef.meta?.variant !== "boolean") return;
@@ -2704,7 +2699,7 @@ function DataTableViewOptions({ table, disabled,...props }) {
2704
2699
  //#endregion
2705
2700
  //#region src/components/auto-crud/auto-table-simple-filters.tsx
2706
2701
  function AutoTableSimpleFilters({ table, shallow = false, filters: externalFilters, onFiltersChange }) {
2707
- const columns = react.useMemo(() => table.getAllColumns().filter((col) => col.columnDef.enableColumnFilter), [table]);
2702
+ const columns = react.useMemo(() => table.getAllColumns().filter((col) => col.getCanFilter()), [table]);
2708
2703
  const queryStateOptions = table.options.meta?.queryStateOptions;
2709
2704
  const [queryFilters, setQueryFilters] = useReadableFilters(columns, {
2710
2705
  ...queryStateOptions,
@@ -2724,9 +2719,10 @@ function AutoTableSimpleFilters({ table, shallow = false, filters: externalFilte
2724
2719
  react.useEffect(() => {
2725
2720
  setMounted(true);
2726
2721
  }, []);
2727
- const getFilterValue = (columnId) => {
2728
- return filters.find((f) => f.id === columnId)?.value;
2729
- };
2722
+ const filterMap = react.useMemo(() => new Map(filters.map((f) => [f.id, f])), [filters]);
2723
+ const getFilterValue = react.useCallback((columnId) => {
2724
+ return filterMap.get(columnId)?.value;
2725
+ }, [filterMap]);
2730
2726
  const updateFilter = react.useCallback((columnId, value) => {
2731
2727
  const column = columns.find((c) => c.id === columnId);
2732
2728
  if (!column) return;
@@ -2749,56 +2745,154 @@ function AutoTableSimpleFilters({ table, shallow = false, filters: externalFilte
2749
2745
  setFilters
2750
2746
  ]);
2751
2747
  const hasFilters = filters.length > 0;
2748
+ const filterColumns = react.useMemo(() => columns.filter((col) => {
2749
+ const meta = col.columnDef.meta;
2750
+ const enableFilter = col.columnDef.enableColumnFilter !== false;
2751
+ if (!meta?.variant || !enableFilter) return false;
2752
+ const modes = meta.modes;
2753
+ if (modes && !modes.includes("simple")) return false;
2754
+ return true;
2755
+ }), [columns]);
2756
+ const [expanded, setExpanded] = react.useState(false);
2757
+ const [visibleCount, setVisibleCount] = react.useState(null);
2758
+ const containerRef = react.useRef(null);
2759
+ const calcVisibleCount = react.useCallback(() => {
2760
+ const el = containerRef.current;
2761
+ if (!el) return;
2762
+ const items = el.querySelectorAll("[data-filter-item]");
2763
+ if (items.length === 0) return;
2764
+ const restoreList = [];
2765
+ el.querySelectorAll(".hidden").forEach((node) => {
2766
+ const htmlNode = node;
2767
+ htmlNode.classList.remove("hidden");
2768
+ restoreList.push(htmlNode);
2769
+ });
2770
+ el.style.flex = "1 1 0%";
2771
+ el.offsetHeight;
2772
+ const firstTop = items[0].offsetTop;
2773
+ let count = 0;
2774
+ for (const item of Array.from(items)) {
2775
+ if (item.offsetTop > firstTop) break;
2776
+ count++;
2777
+ }
2778
+ const btn = el.querySelector("[data-filter-toggle]");
2779
+ if (btn && count < items.length && count > 0) {
2780
+ if (btn.offsetTop > firstTop) count--;
2781
+ }
2782
+ el.style.flex = "";
2783
+ restoreList.forEach((node) => node.classList.add("hidden"));
2784
+ setVisibleCount(count >= items.length ? null : count);
2785
+ }, []);
2786
+ react.useLayoutEffect(() => {
2787
+ if (!mounted) return;
2788
+ calcVisibleCount();
2789
+ }, [
2790
+ mounted,
2791
+ filterColumns.length,
2792
+ calcVisibleCount
2793
+ ]);
2794
+ react.useEffect(() => {
2795
+ const parent = containerRef.current?.closest("[data-filter-parent]");
2796
+ if (!mounted || !parent) return;
2797
+ let lastWidth = parent.offsetWidth;
2798
+ let rafId = 0;
2799
+ const ro = new ResizeObserver(() => {
2800
+ const newWidth = parent.offsetWidth;
2801
+ if (newWidth === lastWidth) return;
2802
+ lastWidth = newWidth;
2803
+ cancelAnimationFrame(rafId);
2804
+ rafId = requestAnimationFrame(calcVisibleCount);
2805
+ });
2806
+ ro.observe(parent);
2807
+ return () => {
2808
+ cancelAnimationFrame(rafId);
2809
+ ro.disconnect();
2810
+ };
2811
+ }, [
2812
+ mounted,
2813
+ filterColumns.length,
2814
+ calcVisibleCount
2815
+ ]);
2752
2816
  if (!mounted) return null;
2753
2817
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2754
- className: "flex flex-wrap items-center gap-2",
2755
- children: [columns.map((column) => {
2756
- const meta = column.columnDef.meta;
2757
- if (!meta?.variant) return null;
2758
- const value = getFilterValue(column.id);
2759
- switch (meta.variant) {
2760
- case "text": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__pixpilot_shadcn.Input, {
2761
- placeholder: meta.label ?? column.id,
2762
- value: typeof value === "string" ? value : "",
2763
- onChange: (e) => updateFilter(column.id, e.target.value || void 0),
2764
- className: "h-8 w-36"
2765
- }, column.id);
2766
- case "select":
2767
- case "multiSelect": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SimpleFacetedFilter, {
2768
- title: meta.label ?? column.id,
2769
- options: meta.options ?? [],
2770
- multiple: meta.variant === "multiSelect",
2771
- value: Array.isArray(value) ? value : value ? [value] : [],
2772
- onChange: (v) => updateFilter(column.id, v.length ? v : void 0)
2773
- }, column.id);
2774
- case "range": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SimpleSliderFilter, {
2775
- title: meta.label ?? column.id,
2776
- range: meta.range,
2777
- unit: meta.unit,
2778
- value: Array.isArray(value) ? value.map(Number) : void 0,
2779
- onChange: (v) => updateFilter(column.id, v ? v.map(String) : void 0)
2780
- }, column.id);
2781
- case "date":
2782
- case "dateRange": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SimpleDateFilter, {
2783
- title: meta.label ?? column.id,
2784
- multiple: meta.variant === "dateRange",
2785
- value,
2786
- onChange: (v) => updateFilter(column.id, v)
2787
- }, column.id);
2788
- default: return null;
2789
- }
2790
- }), hasFilters && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(__pixpilot_shadcn.Button, {
2791
- variant: "outline",
2792
- size: "sm",
2793
- className: "h-8 border-dashed",
2794
- onClick: () => setFilters([]),
2795
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.X, { className: "size-4" }), "Reset"]
2796
- })]
2818
+ ref: containerRef,
2819
+ className: "flex flex-wrap items-center gap-2 min-w-0",
2820
+ children: [
2821
+ filterColumns.map((column, index) => {
2822
+ const meta = column.columnDef.meta;
2823
+ const value = getFilterValue(column.id);
2824
+ const isHidden = !expanded && visibleCount !== null && index >= visibleCount;
2825
+ switch (meta.variant) {
2826
+ case "text": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2827
+ className: isHidden ? "hidden" : "",
2828
+ "data-filter-item": true,
2829
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__pixpilot_shadcn.Input, {
2830
+ placeholder: meta.placeholder ?? meta.label ?? column.id,
2831
+ value: typeof value === "string" ? value : "",
2832
+ onChange: (e) => updateFilter(column.id, e.target.value || void 0),
2833
+ className: "h-8 w-36 shrink-0"
2834
+ })
2835
+ }, column.id);
2836
+ case "select":
2837
+ case "multiSelect": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2838
+ className: isHidden ? "hidden" : "",
2839
+ "data-filter-item": true,
2840
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SimpleFacetedFilter, {
2841
+ title: meta.label ?? column.id,
2842
+ options: meta.options ?? [],
2843
+ multiple: meta.variant === "multiSelect",
2844
+ value: Array.isArray(value) ? value : value ? [value] : [],
2845
+ onChange: (v) => updateFilter(column.id, v.length ? v : void 0)
2846
+ })
2847
+ }, column.id);
2848
+ case "range": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2849
+ className: isHidden ? "hidden" : "",
2850
+ "data-filter-item": true,
2851
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SimpleSliderFilter, {
2852
+ title: meta.label ?? column.id,
2853
+ range: meta.range,
2854
+ unit: meta.unit,
2855
+ value: Array.isArray(value) ? value.map(Number) : void 0,
2856
+ onChange: (v) => updateFilter(column.id, v ? v.map(String) : void 0)
2857
+ })
2858
+ }, column.id);
2859
+ case "date":
2860
+ case "dateRange": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2861
+ className: isHidden ? "hidden" : "",
2862
+ "data-filter-item": true,
2863
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SimpleDateFilter, {
2864
+ title: meta.label ?? column.id,
2865
+ multiple: meta.variant === "dateRange",
2866
+ value,
2867
+ onChange: (v) => updateFilter(column.id, v)
2868
+ })
2869
+ }, column.id);
2870
+ default: return null;
2871
+ }
2872
+ }),
2873
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__pixpilot_shadcn.Button, {
2874
+ "data-filter-toggle": true,
2875
+ variant: "ghost",
2876
+ size: "icon",
2877
+ className: cn("h-8 w-8 shrink-0 text-muted-foreground hover:text-foreground", visibleCount === null && "hidden"),
2878
+ onClick: () => setExpanded((prev) => !prev),
2879
+ "aria-label": expanded ? "收起筛选" : "展开更多筛选",
2880
+ "aria-expanded": expanded,
2881
+ children: expanded ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.ChevronUp, { className: "size-4" }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.ChevronDown, { className: "size-4" })
2882
+ }),
2883
+ hasFilters && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(__pixpilot_shadcn.Button, {
2884
+ variant: "outline",
2885
+ size: "sm",
2886
+ className: "h-8 border-dashed shrink-0",
2887
+ onClick: () => setFilters([]),
2888
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.X, { className: "size-4" }), "Reset"]
2889
+ })
2890
+ ]
2797
2891
  });
2798
2892
  }
2799
2893
  function SimpleFacetedFilter({ title, options, multiple, value, onChange }) {
2800
2894
  const [open, setOpen] = react.useState(false);
2801
- const selectedValues = new Set(value);
2895
+ const selectedValues = react.useMemo(() => new Set(value), [value]);
2802
2896
  const onItemSelect = (option, isSelected) => {
2803
2897
  if (multiple) {
2804
2898
  const newValues = new Set(selectedValues);
@@ -2829,6 +2923,12 @@ function SimpleFacetedFilter({ title, options, multiple, value, onChange }) {
2829
2923
  tabIndex: 0,
2830
2924
  className: "rounded-sm opacity-70 hover:opacity-100",
2831
2925
  onClick: onReset,
2926
+ onKeyDown: (e) => {
2927
+ if (e.key === "Enter" || e.key === " ") {
2928
+ e.preventDefault();
2929
+ onReset(e);
2930
+ }
2931
+ },
2832
2932
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.XCircle, {})
2833
2933
  }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.PlusCircle, {}),
2834
2934
  title,
@@ -2936,6 +3036,12 @@ function SimpleSliderFilter({ title, range, unit, value, onChange }) {
2936
3036
  tabIndex: 0,
2937
3037
  className: "rounded-sm opacity-70 hover:opacity-100",
2938
3038
  onClick: onReset,
3039
+ onKeyDown: (e) => {
3040
+ if (e.key === "Enter" || e.key === " ") {
3041
+ e.preventDefault();
3042
+ onReset(e);
3043
+ }
3044
+ },
2939
3045
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.XCircle, {})
2940
3046
  }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.PlusCircle, {}),
2941
3047
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: title }),
@@ -3059,6 +3165,12 @@ function SimpleDateFilter({ title, multiple, value, onChange }) {
3059
3165
  tabIndex: 0,
3060
3166
  className: "rounded-sm opacity-70 hover:opacity-100",
3061
3167
  onClick: onReset,
3168
+ onKeyDown: (e) => {
3169
+ if (e.key === "Enter" || e.key === " ") {
3170
+ e.preventDefault();
3171
+ onReset(e);
3172
+ }
3173
+ },
3062
3174
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.XCircle, {})
3063
3175
  }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.CalendarIcon, {}),
3064
3176
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: title }),
@@ -3671,33 +3783,47 @@ function inferFilterVariantByFieldName(fieldName) {
3671
3783
 
3672
3784
  //#endregion
3673
3785
  //#region src/lib/schema-bridge/zod-to-columns.tsx
3786
+ const parsedFieldCache = /* @__PURE__ */ new WeakMap();
3787
+ const baseTableSchemaCache = /* @__PURE__ */ new WeakMap();
3674
3788
  /**
3675
3789
  * 解析 Zod 字段类型
3676
3790
  */
3677
3791
  function parseZodField(schema) {
3792
+ const cached = parsedFieldCache.get(schema);
3793
+ if (cached) return cached;
3794
+ const finalize = (result) => {
3795
+ parsedFieldCache.set(schema, result);
3796
+ return result;
3797
+ };
3678
3798
  const def = schema._zod?.def ?? schema._def;
3679
- const testNull = schema.safeParse(null);
3680
- const required = !schema.safeParse(void 0).success && !testNull.success;
3681
3799
  const typeName = def?.typeName;
3682
- if (def?.type === "nullable" || def?.type === "optional" || typeName === "ZodNullable" || typeName === "ZodOptional") {
3800
+ const isOptional = def?.type === "optional" || typeName === "ZodOptional";
3801
+ const isNullable = def?.type === "nullable" || typeName === "ZodNullable";
3802
+ if (isOptional || isNullable) {
3683
3803
  const innerType = def?.innerType;
3684
- if (innerType) return {
3804
+ if (innerType) return finalize({
3685
3805
  ...parseZodField(innerType),
3686
3806
  required: false
3687
- };
3807
+ });
3808
+ }
3809
+ let required;
3810
+ if (isOptional || isNullable) required = false;
3811
+ else {
3812
+ const testNull = schema.safeParse(null);
3813
+ required = !schema.safeParse(void 0).success && !testNull.success;
3688
3814
  }
3689
- if (def?.type === "boolean") return {
3815
+ if (def?.type === "boolean") return finalize({
3690
3816
  type: "boolean",
3691
3817
  required
3692
- };
3693
- if (def?.type === "number") return {
3818
+ });
3819
+ if (def?.type === "number") return finalize({
3694
3820
  type: "number",
3695
3821
  required
3696
- };
3697
- if (def?.type === "date") return {
3822
+ });
3823
+ if (def?.type === "date") return finalize({
3698
3824
  type: "date",
3699
3825
  required
3700
- };
3826
+ });
3701
3827
  if (def?.type === "enum") {
3702
3828
  let enumValues$1;
3703
3829
  if (def?.entries) {
@@ -3708,20 +3834,20 @@ function parseZodField(schema) {
3708
3834
  const values = def.values;
3709
3835
  if (Array.isArray(values)) enumValues$1 = values.map(String);
3710
3836
  }
3711
- if (enumValues$1 && enumValues$1.length > 0) return {
3837
+ if (enumValues$1 && enumValues$1.length > 0) return finalize({
3712
3838
  type: "enum",
3713
3839
  required,
3714
3840
  enumValues: enumValues$1
3715
- };
3841
+ });
3716
3842
  }
3717
- if (def?.type === "string") return {
3843
+ if (def?.type === "string") return finalize({
3718
3844
  type: "string",
3719
3845
  required
3720
- };
3721
- if (typeName === "ZodBoolean") return {
3846
+ });
3847
+ if (typeName === "ZodBoolean") return finalize({
3722
3848
  type: "boolean",
3723
3849
  required
3724
- };
3850
+ });
3725
3851
  let enumValues;
3726
3852
  if (def?.entries) {
3727
3853
  const entries = def.entries;
@@ -3731,51 +3857,51 @@ function parseZodField(schema) {
3731
3857
  const values = def.values;
3732
3858
  if (Array.isArray(values)) enumValues = values.map(String);
3733
3859
  }
3734
- if (enumValues && Array.isArray(enumValues) && enumValues.length > 0) return {
3860
+ if (enumValues && Array.isArray(enumValues) && enumValues.length > 0) return finalize({
3735
3861
  type: "enum",
3736
3862
  required,
3737
3863
  enumValues
3738
- };
3739
- if (typeName === "ZodNumber") return {
3864
+ });
3865
+ if (typeName === "ZodNumber") return finalize({
3740
3866
  type: "number",
3741
3867
  required
3742
- };
3743
- if (typeName === "ZodDate") return {
3868
+ });
3869
+ if (typeName === "ZodDate") return finalize({
3744
3870
  type: "date",
3745
3871
  required
3746
- };
3747
- if (typeName === "ZodArray") return {
3872
+ });
3873
+ if (typeName === "ZodArray") return finalize({
3748
3874
  type: "array",
3749
3875
  required
3750
- };
3751
- if (typeName === "ZodObject") return {
3876
+ });
3877
+ if (typeName === "ZodObject") return finalize({
3752
3878
  type: "object",
3753
3879
  required
3754
- };
3880
+ });
3755
3881
  const testString = schema.safeParse("test");
3756
3882
  const testNumber = schema.safeParse(123);
3757
3883
  const testBoolean = schema.safeParse(true);
3758
3884
  const testDate = schema.safeParse(17040672e5);
3759
- if (testBoolean.success && !testString.success && !testNumber.success) return {
3885
+ if (testBoolean.success && !testString.success && !testNumber.success) return finalize({
3760
3886
  type: "boolean",
3761
3887
  required
3762
- };
3763
- if (testDate.success && !testString.success) return {
3888
+ });
3889
+ if (testDate.success && !testString.success) return finalize({
3764
3890
  type: "date",
3765
3891
  required
3766
- };
3767
- if (testNumber.success && !testString.success) return {
3892
+ });
3893
+ if (testNumber.success && !testString.success) return finalize({
3768
3894
  type: "number",
3769
3895
  required
3770
- };
3771
- if (schema.safeParse([]).success && !testString.success) return {
3896
+ });
3897
+ if (schema.safeParse([]).success && !testString.success) return finalize({
3772
3898
  type: "array",
3773
3899
  required
3774
- };
3775
- return {
3900
+ });
3901
+ return finalize({
3776
3902
  type: "string",
3777
3903
  required
3778
- };
3904
+ });
3779
3905
  }
3780
3906
  /**
3781
3907
  * 渲染单元格内容
@@ -3817,9 +3943,14 @@ function renderCell(value, type) {
3817
3943
  * 从 Zod Schema 创建 Table Schema (ColumnDef 数组)
3818
3944
  */
3819
3945
  function createTableSchema(schema, options) {
3946
+ const { overrides, exclude = [] } = options ?? {};
3947
+ const canCache = !(!!overrides && Object.keys(overrides).length > 0) && exclude.length === 0;
3948
+ if (canCache) {
3949
+ const cached = baseTableSchemaCache.get(schema);
3950
+ if (cached) return [...cached];
3951
+ }
3820
3952
  const shape = schema.shape;
3821
3953
  const columns = [];
3822
- const { overrides, exclude = [] } = options ?? {};
3823
3954
  for (const [key, fieldSchema] of Object.entries(shape)) {
3824
3955
  if (exclude.includes(key)) continue;
3825
3956
  const override = overrides?.[key];
@@ -3854,6 +3985,7 @@ function createTableSchema(schema, options) {
3854
3985
  ...restOverride
3855
3986
  });
3856
3987
  }
3988
+ if (canCache) baseTableSchemaCache.set(schema, columns);
3857
3989
  return columns;
3858
3990
  }
3859
3991
  /**
@@ -4222,25 +4354,32 @@ function createParser(config) {
4222
4354
  })
4223
4355
  };
4224
4356
  }
4225
- const sortingItemSchema = zod.z.object({
4226
- id: zod.z.string(),
4227
- desc: zod.z.boolean()
4228
- });
4229
4357
  const getSortingStateParser = (columnIds) => {
4230
4358
  const validKeys = columnIds ? columnIds instanceof Set ? columnIds : new Set(columnIds) : null;
4231
4359
  return createParser({
4232
4360
  parse: (value) => {
4233
4361
  try {
4234
- const parsed = JSON.parse(value);
4235
- const result = zod.z.array(sortingItemSchema).safeParse(parsed);
4236
- if (!result.success) return null;
4237
- if (validKeys && result.data.some((item) => !validKeys.has(item.id))) return null;
4238
- return result.data;
4362
+ if (!value || value.trim() === "") return [];
4363
+ const items = value.split(",").map((item) => {
4364
+ const lastDot = item.lastIndexOf(".");
4365
+ if (lastDot === -1) return null;
4366
+ const id = item.slice(0, lastDot);
4367
+ const dir = item.slice(lastDot + 1);
4368
+ if (!id || dir !== "asc" && dir !== "desc") return null;
4369
+ return {
4370
+ id,
4371
+ desc: dir === "desc"
4372
+ };
4373
+ });
4374
+ if (items.some((item) => item === null)) return null;
4375
+ const result = items;
4376
+ if (validKeys && result.some((item) => !validKeys.has(item.id))) return null;
4377
+ return result;
4239
4378
  } catch {
4240
4379
  return null;
4241
4380
  }
4242
4381
  },
4243
- serialize: (value) => JSON.stringify(value)
4382
+ serialize: (value) => value.map((item) => `${item.id}.${item.desc ? "desc" : "asc"}`).join(",")
4244
4383
  });
4245
4384
  };
4246
4385
  const filterItemSchema = zod.z.object({
@@ -4363,6 +4502,13 @@ function useDataTable(props) {
4363
4502
  filterableColumns,
4364
4503
  enableAdvancedFilter
4365
4504
  ]);
4505
+ const getCoreRowModelFn = react.useMemo(() => (0, __tanstack_react_table.getCoreRowModel)(), []);
4506
+ const getFilteredRowModelFn = react.useMemo(() => (0, __tanstack_react_table.getFilteredRowModel)(), []);
4507
+ const getPaginationRowModelFn = react.useMemo(() => (0, __tanstack_react_table.getPaginationRowModel)(), []);
4508
+ const getSortedRowModelFn = react.useMemo(() => (0, __tanstack_react_table.getSortedRowModel)(), []);
4509
+ const getFacetedRowModelFn = react.useMemo(() => (0, __tanstack_react_table.getFacetedRowModel)(), []);
4510
+ const getFacetedUniqueValuesFn = react.useMemo(() => (0, __tanstack_react_table.getFacetedUniqueValues)(), []);
4511
+ const getFacetedMinMaxValuesFn = react.useMemo(() => (0, __tanstack_react_table.getFacetedMinMaxValues)(), []);
4366
4512
  const table = (0, __tanstack_react_table.useReactTable)({
4367
4513
  ...tableProps,
4368
4514
  columns,
@@ -4385,13 +4531,13 @@ function useDataTable(props) {
4385
4531
  onSortingChange,
4386
4532
  onColumnFiltersChange,
4387
4533
  onColumnVisibilityChange: setColumnVisibility,
4388
- getCoreRowModel: (0, __tanstack_react_table.getCoreRowModel)(),
4389
- getFilteredRowModel: (0, __tanstack_react_table.getFilteredRowModel)(),
4390
- getPaginationRowModel: (0, __tanstack_react_table.getPaginationRowModel)(),
4391
- getSortedRowModel: (0, __tanstack_react_table.getSortedRowModel)(),
4392
- getFacetedRowModel: (0, __tanstack_react_table.getFacetedRowModel)(),
4393
- getFacetedUniqueValues: (0, __tanstack_react_table.getFacetedUniqueValues)(),
4394
- getFacetedMinMaxValues: (0, __tanstack_react_table.getFacetedMinMaxValues)(),
4534
+ getCoreRowModel: getCoreRowModelFn,
4535
+ getFilteredRowModel: getFilteredRowModelFn,
4536
+ getPaginationRowModel: getPaginationRowModelFn,
4537
+ getSortedRowModel: getSortedRowModelFn,
4538
+ getFacetedRowModel: getFacetedRowModelFn,
4539
+ getFacetedUniqueValues: getFacetedUniqueValuesFn,
4540
+ getFacetedMinMaxValues: getFacetedMinMaxValuesFn,
4395
4541
  manualPagination: true,
4396
4542
  manualSorting: true,
4397
4543
  manualFiltering: true,
@@ -4422,6 +4568,11 @@ function useDataTable(props) {
4422
4568
 
4423
4569
  //#endregion
4424
4570
  //#region src/components/auto-crud/auto-table.tsx
4571
+ const DEFAULT_MODES = [
4572
+ "simple",
4573
+ "advanced",
4574
+ "command"
4575
+ ];
4425
4576
  /** 过滤模式配置 */
4426
4577
  const filterModeConfig = {
4427
4578
  simple: {
@@ -4441,12 +4592,8 @@ const filterModeConfig = {
4441
4592
  }
4442
4593
  };
4443
4594
  function AutoTable({ schema, data, pageCount = 1, overrides, enableRowSelection = true, exclude, actions, pinnedColumns, filterMode = "simple", onDeleteSelected, onUpdateSelected, batchUpdateFields, actionBarExtra, initialSorting, enableExport = true, onSelectedCountChange, getSelectedRows }) {
4444
- const modes = filterMode ? Array.isArray(filterMode) ? filterMode : [filterMode] : [
4445
- "simple",
4446
- "advanced",
4447
- "command"
4448
- ];
4449
- const [currentMode, setCurrentMode] = (0, react.useState)(modes[0] ?? "simple");
4595
+ const modes = filterMode ? Array.isArray(filterMode) ? filterMode : [filterMode] : DEFAULT_MODES;
4596
+ const [currentMode, setCurrentMode] = useQueryState("filterMode", parseAsStringEnum(modes).withDefault(modes[0] ?? "simple"));
4450
4597
  const showToggle = modes.length > 1;
4451
4598
  const { table, shallow, debounceMs, throttleMs } = useDataTable({
4452
4599
  data,
@@ -4467,13 +4614,18 @@ function AutoTable({ schema, data, pageCount = 1, overrides, enableRowSelection
4467
4614
  ]),
4468
4615
  pageCount,
4469
4616
  enableAdvancedFilter: true,
4470
- initialState: {
4617
+ initialState: (0, react.useMemo)(() => ({
4471
4618
  sorting: initialSorting,
4472
4619
  columnPinning: pinnedColumns ?? {
4473
4620
  left: enableRowSelection ? ["select"] : [],
4474
4621
  right: actions ? ["actions"] : []
4475
4622
  }
4476
- },
4623
+ }), [
4624
+ initialSorting,
4625
+ pinnedColumns,
4626
+ enableRowSelection,
4627
+ actions
4628
+ ]),
4477
4629
  shallow: false,
4478
4630
  clearOnDefault: true
4479
4631
  });
@@ -4514,7 +4666,7 @@ function AutoTable({ schema, data, pageCount = 1, overrides, enableRowSelection
4514
4666
  })
4515
4667
  })
4516
4668
  })] }) : null;
4517
- const renderFilters = () => {
4669
+ const filtersContent = (0, react.useMemo)(() => {
4518
4670
  switch (currentMode) {
4519
4671
  case "simple": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AutoTableSimpleFilters, {
4520
4672
  table,
@@ -4534,18 +4686,25 @@ function AutoTable({ schema, data, pageCount = 1, overrides, enableRowSelection
4534
4686
  align: "start"
4535
4687
  });
4536
4688
  }
4537
- };
4689
+ }, [
4690
+ currentMode,
4691
+ table,
4692
+ shallow,
4693
+ debounceMs,
4694
+ throttleMs
4695
+ ]);
4538
4696
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4539
4697
  className: "space-y-4",
4540
4698
  children: [
4541
4699
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4542
4700
  className: "flex w-full items-start justify-between gap-2 p-1",
4543
4701
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4544
- className: "flex flex-1 flex-wrap items-center gap-2",
4702
+ className: "flex flex-1 items-start gap-2 min-h-[40px]",
4703
+ "data-filter-parent": true,
4545
4704
  children: [currentMode !== "simple" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DataTableSortList, {
4546
4705
  table,
4547
4706
  align: "start"
4548
- }), renderFilters()]
4707
+ }), filtersContent]
4549
4708
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4550
4709
  className: "flex items-center gap-2",
4551
4710
  children: [
@@ -5125,92 +5284,61 @@ function ImportDialog({ open, onOpenChange, onImport, columns = [], title = "导
5125
5284
  });
5126
5285
  }
5127
5286
 
5128
- //#endregion
5129
- //#region src/components/auto-crud/export-dialog.tsx
5130
- function ExportDialog({ open, onOpenChange, selectedCount, onExport, canExportFiltered = false }) {
5131
- const [exporting, setExporting] = react.useState(null);
5132
- const [done, setDone] = react.useState(false);
5133
- const reset = react.useCallback(() => {
5134
- setExporting(null);
5135
- setDone(false);
5136
- }, []);
5137
- const handleOpenChange = react.useCallback((value) => {
5138
- if (!value) reset();
5139
- onOpenChange(value);
5140
- }, [onOpenChange, reset]);
5141
- const handleExport = react.useCallback(async (mode) => {
5142
- setExporting(mode);
5143
- try {
5144
- await onExport(mode);
5145
- setDone(true);
5146
- setTimeout(() => handleOpenChange(false), 800);
5147
- } catch {
5148
- setExporting(null);
5149
- }
5150
- }, [onExport, handleOpenChange]);
5151
- const hasSelected = selectedCount > 0;
5152
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__pixpilot_shadcn.Dialog, {
5153
- open,
5154
- onOpenChange: handleOpenChange,
5155
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(__pixpilot_shadcn.DialogContent, {
5156
- className: "sm:max-w-[400px]",
5157
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(__pixpilot_shadcn.DialogHeader, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(__pixpilot_shadcn.DialogTitle, { children: "导出数据" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__pixpilot_shadcn.DialogDescription, { children: "选择导出范围" })] }), done ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5158
- className: "flex flex-col items-center gap-3 py-6",
5159
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.CheckCircle2, { className: "h-10 w-10 text-green-500" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
5160
- className: "text-sm text-muted-foreground",
5161
- children: "导出成功"
5162
- })]
5163
- }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5164
- className: "grid gap-3 py-2",
5165
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(__pixpilot_shadcn.Button, {
5166
- variant: "outline",
5167
- className: "h-auto justify-start gap-3 px-4 py-3",
5168
- disabled: !hasSelected || exporting !== null,
5169
- onClick: () => handleExport("selected"),
5170
- children: [exporting === "selected" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Loader2, { className: "h-5 w-5 shrink-0 animate-spin" }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.FileDown, { className: "h-5 w-5 shrink-0" }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5171
- className: "flex flex-col items-start",
5172
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
5173
- className: "font-medium",
5174
- children: ["导出选中", hasSelected ? ` (${selectedCount} 条)` : ""]
5175
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5176
- className: "text-xs text-muted-foreground",
5177
- children: hasSelected ? "导出当前勾选的数据行" : "请先在表格中勾选数据行"
5178
- })]
5179
- })]
5180
- }), canExportFiltered && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(__pixpilot_shadcn.Button, {
5181
- variant: "outline",
5182
- className: "h-auto justify-start gap-3 px-4 py-3",
5183
- disabled: exporting !== null,
5184
- onClick: () => handleExport("filtered"),
5185
- children: [exporting === "filtered" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Loader2, { className: "h-5 w-5 shrink-0 animate-spin" }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.FileDown, { className: "h-5 w-5 shrink-0" }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5186
- className: "flex flex-col items-start",
5187
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5188
- className: "font-medium",
5189
- children: "导出筛选结果"
5190
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5191
- className: "text-xs text-muted-foreground",
5192
- children: "导出当前筛选条件下的所有数据"
5193
- })]
5194
- })]
5195
- })]
5196
- })]
5197
- })
5198
- });
5199
- }
5200
-
5201
5287
  //#endregion
5202
5288
  //#region src/components/auto-crud/auto-crud-table.tsx
5203
5289
  /**
5204
5290
  * 从统一配置生成表格 overrides
5291
+ * 当 filter 配置存在时,合并到 meta 中(不影响列隐藏)
5205
5292
  */
5206
5293
  function buildTableOverrides(fields, legacyOverrides) {
5207
5294
  const result = { ...legacyOverrides };
5208
- if (fields) for (const [key, config] of Object.entries(fields)) result[key] = {
5209
- ...result[key],
5210
- ...config.label && { label: config.label },
5211
- ...config.hidden && { hidden: true },
5212
- ...config.table
5213
- };
5295
+ if (fields) for (const [key, config] of Object.entries(fields)) {
5296
+ const tableMeta = config.table !== false && typeof config.table === "object" ? config.table.meta : void 0;
5297
+ let filterMeta;
5298
+ if (config.filter && typeof config.filter === "object") {
5299
+ if (config.filter.enabled !== false && config.filter.hidden !== true) {
5300
+ const { enabled: _, hidden: __,...filterProps } = config.filter;
5301
+ if (Object.keys(filterProps).length > 0) filterMeta = filterProps;
5302
+ }
5303
+ }
5304
+ if (config.filter === false) result[key] = {
5305
+ ...result[key],
5306
+ enableColumnFilter: false
5307
+ };
5308
+ else if (config.filter && typeof config.filter === "object") {
5309
+ if (config.filter.enabled === false || config.filter.hidden === true) result[key] = {
5310
+ ...result[key],
5311
+ enableColumnFilter: false
5312
+ };
5313
+ }
5314
+ if (tableMeta || filterMeta) result[key] = {
5315
+ ...result[key],
5316
+ meta: {
5317
+ ...result[key]?.meta ?? {},
5318
+ ...tableMeta ?? {},
5319
+ ...filterMeta ?? {}
5320
+ }
5321
+ };
5322
+ if (config.label) result[key] = {
5323
+ ...result[key],
5324
+ label: config.label
5325
+ };
5326
+ if (config.hidden) result[key] = {
5327
+ ...result[key],
5328
+ hidden: true
5329
+ };
5330
+ if (config.table === false) result[key] = {
5331
+ ...result[key],
5332
+ hidden: true
5333
+ };
5334
+ else if (config.table && typeof config.table === "object") {
5335
+ const { meta,...tableProps } = config.table;
5336
+ result[key] = {
5337
+ ...result[key],
5338
+ ...tableProps
5339
+ };
5340
+ }
5341
+ }
5214
5342
  return result;
5215
5343
  }
5216
5344
  /**
@@ -5223,12 +5351,24 @@ function buildFormOverrides(fields, legacyOverrides, denyFields) {
5223
5351
  ...result[field],
5224
5352
  "x-hidden": true
5225
5353
  };
5226
- if (fields) for (const [key, config] of Object.entries(fields)) result[key] = {
5227
- ...result[key],
5228
- ...config.label && { title: config.label },
5229
- ...config.hidden && { "x-hidden": true },
5230
- ...config.form
5231
- };
5354
+ if (fields) for (const [key, config] of Object.entries(fields)) {
5355
+ if (config.label) result[key] = {
5356
+ ...result[key],
5357
+ title: config.label
5358
+ };
5359
+ if (config.hidden) result[key] = {
5360
+ ...result[key],
5361
+ "x-hidden": true
5362
+ };
5363
+ if (config.form === false) result[key] = {
5364
+ ...result[key],
5365
+ "x-hidden": true
5366
+ };
5367
+ else if (config.form && typeof config.form === "object") result[key] = {
5368
+ ...result[key],
5369
+ ...config.form
5370
+ };
5371
+ }
5232
5372
  return result;
5233
5373
  }
5234
5374
  /**
@@ -5237,7 +5377,9 @@ function buildFormOverrides(fields, legacyOverrides, denyFields) {
5237
5377
  function buildHiddenColumns(fields, legacyHidden, denyFields) {
5238
5378
  const hidden = new Set([...legacyHidden ?? [], ...denyFields ?? []]);
5239
5379
  if (fields) {
5240
- for (const [key, config] of Object.entries(fields)) if (config.hidden || config.table?.hidden) hidden.add(key);
5380
+ for (const [key, config] of Object.entries(fields)) if (config.hidden) hidden.add(key);
5381
+ else if (config.table === false) hidden.add(key);
5382
+ else if (config.table && typeof config.table === "object" && config.table.hidden) hidden.add(key);
5241
5383
  }
5242
5384
  return Array.from(hidden);
5243
5385
  }
@@ -5364,8 +5506,8 @@ function AutoCrudTable({ title, description, schema, resource, fields, table: ta
5364
5506
  const canImport = can.import && !!resource.handlers.import;
5365
5507
  const canExport = can.export;
5366
5508
  const [importOpen, setImportOpen] = react.useState(false);
5367
- const [exportOpen, setExportOpen] = react.useState(false);
5368
5509
  const [selectedCount, setSelectedCount] = react.useState(0);
5510
+ const [exporting, setExporting] = react.useState(false);
5369
5511
  const getSelectedRowsRef = react.useRef(null);
5370
5512
  const importColumns = react.useMemo(() => {
5371
5513
  const shape = schema.shape;
@@ -5394,10 +5536,31 @@ function AutoCrudTable({ title, description, schema, resource, fields, table: ta
5394
5536
  title,
5395
5537
  denyFields
5396
5538
  ]);
5397
- const tableOverrides = buildTableOverrides(fields, tableConfig?.overrides);
5398
- const formOverrides = buildFormOverrides(fields, formConfig?.overrides, denyFields);
5399
- const hiddenColumns = buildHiddenColumns(fields, tableConfig?.hidden, denyFields);
5400
- const batchFields = buildBatchUpdateFields(schema, tableConfig?.batchFields, fields);
5539
+ const handleExportClick = react.useCallback(async () => {
5540
+ const mode = selectedCount > 0 ? "selected" : "filtered";
5541
+ setExporting(true);
5542
+ try {
5543
+ await handleExport(mode);
5544
+ } finally {
5545
+ setExporting(false);
5546
+ }
5547
+ }, [selectedCount, handleExport]);
5548
+ const tableOverrides = react.useMemo(() => buildTableOverrides(fields, tableConfig?.overrides), [fields, tableConfig?.overrides]);
5549
+ const formOverrides = react.useMemo(() => buildFormOverrides(fields, formConfig?.overrides, denyFields), [
5550
+ fields,
5551
+ formConfig?.overrides,
5552
+ denyFields
5553
+ ]);
5554
+ const hiddenColumns = react.useMemo(() => buildHiddenColumns(fields, tableConfig?.hidden, denyFields), [
5555
+ fields,
5556
+ tableConfig?.hidden,
5557
+ denyFields
5558
+ ]);
5559
+ const batchFields = react.useMemo(() => buildBatchUpdateFields(schema, tableConfig?.batchFields, fields), [
5560
+ schema,
5561
+ tableConfig?.batchFields,
5562
+ fields
5563
+ ]);
5401
5564
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5402
5565
  className: "space-y-4",
5403
5566
  children: [
@@ -5429,8 +5592,9 @@ function AutoCrudTable({ title, description, schema, resource, fields, table: ta
5429
5592
  canExport && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(__pixpilot_shadcn.Button, {
5430
5593
  variant: "outline",
5431
5594
  size: "sm",
5432
- onClick: () => setExportOpen(true),
5433
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Upload, { className: "mr-2 h-4 w-4" }), "导出"]
5595
+ onClick: handleExportClick,
5596
+ disabled: exporting || selectedCount === 0 && !resource.handlers.export,
5597
+ children: [exporting ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Loader2, { className: "mr-2 h-4 w-4 animate-spin" }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Upload, { className: "mr-2 h-4 w-4" }), selectedCount > 0 ? `导出(${selectedCount})` : "导出"]
5434
5598
  }),
5435
5599
  can.create && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__pixpilot_shadcn.Button, {
5436
5600
  onClick: resource.handlers.openCreate,
@@ -5497,18 +5661,84 @@ function AutoCrudTable({ title, description, schema, resource, fields, table: ta
5497
5661
  onOpenChange: setImportOpen,
5498
5662
  onImport: resource.handlers.import,
5499
5663
  columns: importColumns
5500
- }),
5501
- canExport && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ExportDialog, {
5502
- open: exportOpen,
5503
- onOpenChange: setExportOpen,
5504
- selectedCount,
5505
- onExport: handleExport,
5506
- canExportFiltered: !!resource.handlers.export
5507
5664
  })
5508
5665
  ]
5509
5666
  });
5510
5667
  }
5511
5668
 
5669
+ //#endregion
5670
+ //#region src/components/auto-crud/export-dialog.tsx
5671
+ function ExportDialog({ open, onOpenChange, selectedCount, onExport, canExportFiltered = false }) {
5672
+ const [exporting, setExporting] = react.useState(null);
5673
+ const [done, setDone] = react.useState(false);
5674
+ const reset = react.useCallback(() => {
5675
+ setExporting(null);
5676
+ setDone(false);
5677
+ }, []);
5678
+ const handleOpenChange = react.useCallback((value) => {
5679
+ if (!value) reset();
5680
+ onOpenChange(value);
5681
+ }, [onOpenChange, reset]);
5682
+ const handleExport = react.useCallback(async (mode) => {
5683
+ setExporting(mode);
5684
+ try {
5685
+ await onExport(mode);
5686
+ setDone(true);
5687
+ setTimeout(() => handleOpenChange(false), 800);
5688
+ } catch {
5689
+ setExporting(null);
5690
+ }
5691
+ }, [onExport, handleOpenChange]);
5692
+ const hasSelected = selectedCount > 0;
5693
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__pixpilot_shadcn.Dialog, {
5694
+ open,
5695
+ onOpenChange: handleOpenChange,
5696
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(__pixpilot_shadcn.DialogContent, {
5697
+ className: "sm:max-w-[400px]",
5698
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(__pixpilot_shadcn.DialogHeader, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(__pixpilot_shadcn.DialogTitle, { children: "导出数据" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__pixpilot_shadcn.DialogDescription, { children: "选择导出范围" })] }), done ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5699
+ className: "flex flex-col items-center gap-3 py-6",
5700
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.CheckCircle2, { className: "h-10 w-10 text-green-500" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
5701
+ className: "text-sm text-muted-foreground",
5702
+ children: "导出成功"
5703
+ })]
5704
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5705
+ className: "grid gap-3 py-2",
5706
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(__pixpilot_shadcn.Button, {
5707
+ variant: "outline",
5708
+ className: "h-auto justify-start gap-3 px-4 py-3",
5709
+ disabled: !hasSelected || exporting !== null,
5710
+ onClick: () => handleExport("selected"),
5711
+ children: [exporting === "selected" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Loader2, { className: "h-5 w-5 shrink-0 animate-spin" }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.FileDown, { className: "h-5 w-5 shrink-0" }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5712
+ className: "flex flex-col items-start",
5713
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
5714
+ className: "font-medium",
5715
+ children: ["导出选中", hasSelected ? ` (${selectedCount} 条)` : ""]
5716
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5717
+ className: "text-xs text-muted-foreground",
5718
+ children: hasSelected ? "导出当前勾选的数据行" : "请先在表格中勾选数据行"
5719
+ })]
5720
+ })]
5721
+ }), canExportFiltered && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(__pixpilot_shadcn.Button, {
5722
+ variant: "outline",
5723
+ className: "h-auto justify-start gap-3 px-4 py-3",
5724
+ disabled: exporting !== null,
5725
+ onClick: () => handleExport("filtered"),
5726
+ children: [exporting === "filtered" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Loader2, { className: "h-5 w-5 shrink-0 animate-spin" }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.FileDown, { className: "h-5 w-5 shrink-0" }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5727
+ className: "flex flex-col items-start",
5728
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5729
+ className: "font-medium",
5730
+ children: "导出筛选结果"
5731
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5732
+ className: "text-xs text-muted-foreground",
5733
+ children: "导出当前筛选条件下的所有数据"
5734
+ })]
5735
+ })]
5736
+ })]
5737
+ })]
5738
+ })
5739
+ });
5740
+ }
5741
+
5512
5742
  //#endregion
5513
5743
  //#region src/components/data-table/data-table-advanced-toolbar.tsx
5514
5744
  function DataTableAdvancedToolbar({ table, children, className,...props }) {
@@ -6062,6 +6292,8 @@ function DataTableToolbarFilter({ column }) {
6062
6292
  const columnMeta = column.columnDef.meta;
6063
6293
  return react.useCallback(() => {
6064
6294
  if (!columnMeta?.variant) return null;
6295
+ const modes = columnMeta.modes;
6296
+ if (modes && !modes.includes("advanced")) return null;
6065
6297
  switch (columnMeta.variant) {
6066
6298
  case "text": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__pixpilot_shadcn.Input, {
6067
6299
  placeholder: columnMeta.placeholder ?? columnMeta.label,
@@ -6183,6 +6415,10 @@ function modalReducer(state, action) {
6183
6415
  function useAutoCrudResource({ router, schema, query: queryTransform, options = {} }) {
6184
6416
  const { idKey = "id", defaultVariant = "dialog", hooks, toast: toastAdapter, exportFetcher } = options;
6185
6417
  const toast = (0, react.useMemo)(() => toastAdapter === false ? noopToastAdapter : toastAdapter ?? defaultToastAdapter, [toastAdapter]);
6418
+ const hooksRef = (0, react.useRef)(hooks);
6419
+ (0, react.useEffect)(() => {
6420
+ hooksRef.current = hooks;
6421
+ }, [hooks]);
6186
6422
  const columns = (0, react.useMemo)(() => createTableSchema(schema), [schema]);
6187
6423
  const [urlParams] = useQueryStates({
6188
6424
  page: parseAsInteger.withDefault(1),
@@ -6246,15 +6482,16 @@ function useAutoCrudResource({ router, schema, query: queryTransform, options =
6246
6482
  payload: variant
6247
6483
  }), []);
6248
6484
  const submitCreate = (0, react.useCallback)(async (values) => {
6485
+ const currentHooks = hooksRef.current;
6249
6486
  try {
6250
- if (hooks?.beforeCreate) {
6251
- const result = await hooks.beforeCreate(values);
6487
+ if (currentHooks?.beforeCreate) {
6488
+ const result = await currentHooks.beforeCreate(values);
6252
6489
  if (typeof result === "boolean") {
6253
6490
  if (result) {
6254
6491
  toast.success("创建成功");
6255
6492
  closeModals();
6256
6493
  listQuery.refetch();
6257
- hooks?.onSuccess?.("create", values);
6494
+ currentHooks?.onSuccess?.("create", values);
6258
6495
  }
6259
6496
  return;
6260
6497
  }
@@ -6265,36 +6502,36 @@ function useAutoCrudResource({ router, schema, query: queryTransform, options =
6265
6502
  toast.success("创建成功");
6266
6503
  closeModals();
6267
6504
  listQuery.refetch();
6268
- hooks?.onSuccess?.("create", data);
6505
+ currentHooks?.onSuccess?.("create", data);
6269
6506
  },
6270
6507
  onError: (error) => {
6271
6508
  const err = error;
6272
6509
  toast.error(`创建失败: ${err.message}`);
6273
- hooks?.onError?.("create", err);
6510
+ currentHooks?.onError?.("create", err);
6274
6511
  }
6275
6512
  });
6276
6513
  } catch (error) {
6277
6514
  const err = error;
6278
6515
  toast.error(`创建失败: ${err.message}`);
6279
- hooks?.onError?.("create", err);
6516
+ currentHooks?.onError?.("create", err);
6280
6517
  }
6281
6518
  }, [
6282
6519
  createMutation,
6283
6520
  closeModals,
6284
- listQuery,
6285
- hooks
6521
+ listQuery
6286
6522
  ]);
6287
6523
  const submitUpdate = (0, react.useCallback)(async (values) => {
6524
+ const currentHooks = hooksRef.current;
6288
6525
  if (!modal.selected) return;
6289
6526
  try {
6290
- if (hooks?.beforeUpdate) {
6291
- const result = await hooks.beforeUpdate(values, modal.selected);
6527
+ if (currentHooks?.beforeUpdate) {
6528
+ const result = await currentHooks.beforeUpdate(values, modal.selected);
6292
6529
  if (typeof result === "boolean") {
6293
6530
  if (result) {
6294
6531
  toast.success("更新成功");
6295
6532
  closeModals();
6296
6533
  listQuery.refetch();
6297
- hooks?.onSuccess?.("update", values);
6534
+ currentHooks?.onSuccess?.("update", values);
6298
6535
  }
6299
6536
  return;
6300
6537
  }
@@ -6309,38 +6546,38 @@ function useAutoCrudResource({ router, schema, query: queryTransform, options =
6309
6546
  toast.success("更新成功");
6310
6547
  closeModals();
6311
6548
  listQuery.refetch();
6312
- hooks?.onSuccess?.("update", data);
6549
+ currentHooks?.onSuccess?.("update", data);
6313
6550
  },
6314
6551
  onError: (error) => {
6315
6552
  const err = error;
6316
6553
  toast.error(`更新失败: ${err.message}`);
6317
- hooks?.onError?.("update", err);
6554
+ currentHooks?.onError?.("update", err);
6318
6555
  }
6319
6556
  });
6320
6557
  } catch (error) {
6321
6558
  const err = error;
6322
6559
  toast.error(`更新失败: ${err.message}`);
6323
- hooks?.onError?.("update", err);
6560
+ currentHooks?.onError?.("update", err);
6324
6561
  }
6325
6562
  }, [
6326
6563
  modal.selected,
6327
6564
  idKey,
6328
6565
  updateMutation,
6329
6566
  closeModals,
6330
- listQuery,
6331
- hooks
6567
+ listQuery
6332
6568
  ]);
6333
6569
  const confirmDelete = (0, react.useCallback)(async () => {
6570
+ const currentHooks = hooksRef.current;
6334
6571
  if (!modal.selected) return;
6335
6572
  try {
6336
- if (hooks?.beforeDelete) {
6337
- const result = await hooks.beforeDelete(modal.selected);
6573
+ if (currentHooks?.beforeDelete) {
6574
+ const result = await currentHooks.beforeDelete(modal.selected);
6338
6575
  if (typeof result === "boolean") {
6339
6576
  if (result) {
6340
6577
  toast.success("删除成功");
6341
6578
  closeModals();
6342
6579
  listQuery.refetch();
6343
- hooks?.onSuccess?.("delete", modal.selected);
6580
+ currentHooks?.onSuccess?.("delete", modal.selected);
6344
6581
  }
6345
6582
  return;
6346
6583
  }
@@ -6351,28 +6588,28 @@ function useAutoCrudResource({ router, schema, query: queryTransform, options =
6351
6588
  toast.success("删除成功");
6352
6589
  closeModals();
6353
6590
  listQuery.refetch();
6354
- hooks?.onSuccess?.("delete", data);
6591
+ currentHooks?.onSuccess?.("delete", data);
6355
6592
  },
6356
6593
  onError: (error) => {
6357
6594
  const err = error;
6358
6595
  toast.error(`删除失败: ${err.message}`);
6359
- hooks?.onError?.("delete", err);
6596
+ currentHooks?.onError?.("delete", err);
6360
6597
  }
6361
6598
  });
6362
6599
  } catch (error) {
6363
6600
  const err = error;
6364
6601
  toast.error(`删除失败: ${err.message}`);
6365
- hooks?.onError?.("delete", err);
6602
+ currentHooks?.onError?.("delete", err);
6366
6603
  }
6367
6604
  }, [
6368
6605
  modal.selected,
6369
6606
  idKey,
6370
6607
  deleteMutation,
6371
6608
  closeModals,
6372
- listQuery,
6373
- hooks
6609
+ listQuery
6374
6610
  ]);
6375
6611
  const deleteMany = (0, react.useCallback)((rows) => {
6612
+ const currentHooks = hooksRef.current;
6376
6613
  if (!deleteManyMutation) {
6377
6614
  toast.error("批量删除功能未启用");
6378
6615
  return;
@@ -6382,21 +6619,21 @@ function useAutoCrudResource({ router, schema, query: queryTransform, options =
6382
6619
  onSuccess: (data) => {
6383
6620
  toast.success(`成功删除 ${ids.length} 条记录`);
6384
6621
  listQuery.refetch();
6385
- hooks?.onSuccess?.("delete", data);
6622
+ currentHooks?.onSuccess?.("delete", data);
6386
6623
  },
6387
6624
  onError: (error) => {
6388
6625
  const err = error;
6389
6626
  toast.error(`批量删除失败: ${err.message}`);
6390
- hooks?.onError?.("delete", err);
6627
+ currentHooks?.onError?.("delete", err);
6391
6628
  }
6392
6629
  });
6393
6630
  }, [
6394
6631
  deleteManyMutation,
6395
6632
  idKey,
6396
- listQuery,
6397
- hooks
6633
+ listQuery
6398
6634
  ]);
6399
6635
  const updateMany = (0, react.useCallback)((rows, data) => {
6636
+ const currentHooks = hooksRef.current;
6400
6637
  if (!updateManyMutation) {
6401
6638
  toast.error("批量更新功能未启用");
6402
6639
  return;
@@ -6409,19 +6646,18 @@ function useAutoCrudResource({ router, schema, query: queryTransform, options =
6409
6646
  onSuccess: (result) => {
6410
6647
  toast.success(`成功更新 ${ids.length} 条记录`);
6411
6648
  listQuery.refetch();
6412
- hooks?.onSuccess?.("update", result);
6649
+ currentHooks?.onSuccess?.("update", result);
6413
6650
  },
6414
6651
  onError: (error) => {
6415
6652
  const err = error;
6416
6653
  toast.error(`批量更新失败: ${err.message}`);
6417
- hooks?.onError?.("update", err);
6654
+ currentHooks?.onError?.("update", err);
6418
6655
  }
6419
6656
  });
6420
6657
  }, [
6421
6658
  updateManyMutation,
6422
6659
  idKey,
6423
- listQuery,
6424
- hooks
6660
+ listQuery
6425
6661
  ]);
6426
6662
  const copyRow = (0, react.useCallback)((row) => {
6427
6663
  dispatch({
@@ -6742,12 +6978,12 @@ function createMemoryDataSource(initialData = []) {
6742
6978
  });
6743
6979
  });
6744
6980
  if (params.sort && params.sort.length > 0) {
6745
- const { id, desc } = params.sort[0];
6981
+ const sortItem = params.sort[0];
6746
6982
  filtered.sort((a, b) => {
6747
- const aVal = a[id];
6748
- const bVal = b[id];
6983
+ const aVal = a[sortItem.id];
6984
+ const bVal = b[sortItem.id];
6749
6985
  const result = aVal > bVal ? 1 : aVal < bVal ? -1 : 0;
6750
- return desc ? -result : result;
6986
+ return sortItem.desc ? -result : result;
6751
6987
  });
6752
6988
  }
6753
6989
  const start = (params.page - 1) * params.perPage;
@@ -6775,11 +7011,12 @@ function createMemoryDataSource(initialData = []) {
6775
7011
  async update(id, updateData) {
6776
7012
  const index = data.findIndex((item) => item.id === id);
6777
7013
  if (index === -1) throw new Error(`Item with id ${id} not found`);
6778
- data[index] = {
7014
+ const updated = {
6779
7015
  ...data[index],
6780
7016
  ...updateData
6781
7017
  };
6782
- return data[index];
7018
+ data[index] = updated;
7019
+ return updated;
6783
7020
  },
6784
7021
  async delete(id) {
6785
7022
  const index = data.findIndex((item) => item.id === id);