@wordrhyme/auto-crud 1.8.2 → 1.10.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/README.md CHANGED
@@ -264,6 +264,8 @@ interface AutoCrudTableProps<TSchema> {
264
264
  table?: {
265
265
  hidden?: string[]; // 隐藏的列
266
266
  overrides?: Record<string, any>; // 列覆盖配置
267
+ getRowId?: (record: z.output<TSchema>) => string; // 稳定行标识;展开时必填
268
+ expandedRow?: (record: z.output<TSchema>) => React.ReactNode; // 展开行内容
267
269
  filterModes?: FilterMode | FilterMode[]; // 过滤模式
268
270
  batchFields?: (string | BatchUpdateField)[]; // 批量更新字段
269
271
  /** @deprecated 新代码请使用 actions.batch */
@@ -294,6 +296,30 @@ interface AutoCrudTableProps<TSchema> {
294
296
  }
295
297
  ```
296
298
 
299
+ #### 展开行详情
300
+
301
+ 配置 `table.expandedRow` 后,表格自动添加展开箭头。默认全部收起,允许同时展开多行。
302
+ 必须提供 TanStack Table 的 `getRowId`,使用稳定且唯一的业务主键,避免分页、排序后展开错位。
303
+
304
+ ```tsx
305
+ <AutoCrudTable
306
+ schema={orderSchema}
307
+ resource={resource}
308
+ table={{
309
+ getRowId: (order) => order.id,
310
+ expandedRow: (order) => <OrderDetails orderId={order.id} />,
311
+ }}
312
+ />
313
+ ```
314
+
315
+ 回调接收当前行的原始记录,并同步返回 React 内容。详情组件只在展开时挂载,收起时卸载;
316
+ 可由详情组件按订单 ID 查询数据并处理加载、错误和重试,也可直接使用记录中已有的明细。
317
+ 明细数据及其字段权限由业务组件和现有服务负责。
318
+
319
+ 详情区域跨全部可见列,不计入分页条数,也不参与主表选择或导出。
320
+ 数据更新时,展开状态遵循 TanStack Table 默认重置行为。
321
+ `AutoTable` 同样支持 `getRowId` 和 `expandedRow`;直接使用 `DataTable` 时,需自行配置 TanStack 展开能力及触发按钮。
322
+
297
323
  ### `useAutoCrudResource()`
298
324
 
299
325
  连接数据源到组件的 Hook。
@@ -1651,6 +1677,14 @@ MIT © [wordrhyme](https://github.com/denvey/wordrhyme-components)
1651
1677
  - [Notion](https://notion.so) - 高级筛选器设计
1652
1678
  - [Linear](https://linear.app) - 命令面板设计
1653
1679
 
1680
+ ### Status badge colors
1681
+
1682
+ For `table.display: "badge"`, a scalar field's `table.options` entries may set `badgeTone` to `neutral`, `success`, `warning`, or `info`. This renders a tinted, outlined rounded label with a decorative dot. Labels remain readable without color; omitted tones retain the default badge. `display: "text"` is unaffected. Domain-specific state-to-tone mappings belong in the consuming plugin metadata.
1683
+
1684
+ Registered custom actions may set `before: "delete"` (or another builtin action type) to appear immediately before that action. If the builtin is absent, the existing `position` fallback applies. This only controls ordering and does not add or grant the target action.
1685
+
1686
+ Custom row components receive `MenuItem` in their action context. Use this supplied primitive instead of importing a menu item from another UI bundle: the menu root and item must share the same Radix runtime context. Prevent the select event's default dismissal when the component owns an open dialog, so the menu does not unmount the workflow.
1687
+
1654
1688
  ### Detail presentation
1655
1689
 
1656
1690
  Details automatically share field labels, `enum`/`dataSource` mappings and
@@ -1711,6 +1745,31 @@ is pinned to the already-published alpha.19 for reproducible ESM tests and type
1711
1745
  checks until alpha.20 is published; it is not the supported CommonJS runtime.
1712
1746
  The CommonJS export/identity regression belongs to the UI provider repository.
1713
1747
 
1748
+ ### Filter-only fields
1749
+
1750
+ Use `table: false` with an explicit `filter` to keep a field in the standard
1751
+ filter bar without rendering a data column. Shared `hidden`, permission `deny`,
1752
+ and explicit table exclusions still hide the filter. Pass the same filter
1753
+ configuration to the resource so URL values use the same variants/operators:
1754
+
1755
+ ```tsx
1756
+ const fields = {
1757
+ customerId: {
1758
+ label: 'Customer',
1759
+ table: false as const,
1760
+ form: false as const,
1761
+ filter: { variant: 'select' as const, dataSource: 'customers', index: 20 },
1762
+ },
1763
+ };
1764
+ const resource = useAutoCrudResource({ router, schema, fields });
1765
+ return <AutoCrudTable schema={schema} resource={resource} fields={fields} />;
1766
+ ```
1767
+
1768
+ The field must exist in the schema or resource metadata, and the server must
1769
+ resolve its filter. A data source loads options; it does not translate query
1770
+ conditions. Standard filters share URL state, Reset, and pagination. Dynamically
1771
+ registered schema fields hydrate existing URL values without resetting the page.
1772
+
1714
1773
  ### 按 ID 隐藏或替换自定义操作
1715
1774
 
1716
1775
  拥有方为 custom 操作声明稳定 `id` 后,扩展可以在相同 CRUD target 和操作区域
package/dist/index.cjs CHANGED
@@ -566,7 +566,7 @@ function formatDate(date, opts = {}, preset) {
566
566
 
567
567
  //#endregion
568
568
  //#region src/components/data-table/data-table.tsx
569
- function DataTable({ table, actionBar, pagination, children, className, classNames,...props }) {
569
+ function DataTable({ table, actionBar, pagination, expandedRow, children, className, classNames,...props }) {
570
570
  useDateFormatterVersion();
571
571
  const columns = [
572
572
  ...table.getLeftVisibleLeafColumns(),
@@ -606,7 +606,7 @@ function DataTable({ table, actionBar, pagination, children, className, classNam
606
606
  }),
607
607
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__wordrhyme_shadcn.TableBody, {
608
608
  className: classNames?.tbody,
609
- children: table.getRowModel().rows?.length ? table.getRowModel().rows.map((row) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__wordrhyme_shadcn.TableRow, {
609
+ children: table.getRowModel().rows?.length ? table.getRowModel().rows.map((row) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(__wordrhyme_shadcn.TableRow, {
610
610
  className: classNames?.tr,
611
611
  "data-state": row.getIsSelected() && "selected",
612
612
  children: row.getVisibleCells().map((cell) => {
@@ -617,7 +617,14 @@ function DataTable({ table, actionBar, pagination, children, className, classNam
617
617
  children: (0, __tanstack_react_table.flexRender)(cell.column.columnDef.cell, cell.getContext())
618
618
  }, cell.id);
619
619
  })
620
- }, row.id)) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__wordrhyme_shadcn.TableRow, {
620
+ }), expandedRow && row.getCanExpand() && row.getIsExpanded() && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__wordrhyme_shadcn.TableRow, {
621
+ className: classNames?.tr,
622
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__wordrhyme_shadcn.TableCell, {
623
+ colSpan: row.getVisibleCells().length,
624
+ className: classNames?.td,
625
+ children: expandedRow(row.original)
626
+ })
627
+ })] }, row.id)) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__wordrhyme_shadcn.TableRow, {
621
628
  className: classNames?.tr,
622
629
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__wordrhyme_shadcn.TableCell, {
623
630
  colSpan: table.getAllColumns().length,
@@ -1012,6 +1019,11 @@ function useReadableFilters(columns, options = {}) {
1012
1019
  filtersRef.current = initial;
1013
1020
  return initial;
1014
1021
  });
1022
+ react.useEffect(() => {
1023
+ const next = parseReadableFilters(getUrlParams(), columnSnapshot);
1024
+ filtersRef.current = next;
1025
+ setFilters((previous) => JSON.stringify(previous) === JSON.stringify(next) ? previous : next);
1026
+ }, [columnSnapshot]);
1015
1027
  react.useEffect(() => {
1016
1028
  const handleUrlChange = () => {
1017
1029
  if (lastWrittenUrlRef.current !== null && window.location.search === lastWrittenUrlRef.current) {
@@ -3901,9 +3913,12 @@ function coerceRowValues(rows, schema) {
3901
3913
 
3902
3914
  //#endregion
3903
3915
  //#region src/lib/export.ts
3916
+ function getExportColumnIds(table) {
3917
+ return table.getAllLeafColumns().filter((column) => column.accessorFn).map((column) => column.id);
3918
+ }
3904
3919
  function exportTableToCSV(table, opts = {}) {
3905
3920
  const { filename = "table", excludeColumns = [], onlySelected = false } = opts;
3906
- const headers = table.getAllLeafColumns().map((column) => column.id).filter((id) => !excludeColumns.includes(id));
3921
+ const headers = getExportColumnIds(table).filter((id) => !excludeColumns.includes(id));
3907
3922
  downloadCSV([headers.join(","), ...(onlySelected ? table.getFilteredSelectedRowModel().rows : table.getRowModel().rows).map((row) => headers.map((header) => {
3908
3923
  const cellValue = row.getValue(header);
3909
3924
  return typeof cellValue === "string" ? `"${cellValue.replace(/"/g, "\"\"")}"` : cellValue;
@@ -4414,7 +4429,9 @@ const filterModeConfig = {
4414
4429
  tooltip: "Linear-style command filters"
4415
4430
  }
4416
4431
  };
4417
- function AutoTable({ schema, data, pageCount = 1, pagination, total, overrides, classNames, enableRowSelection = true, exclude, actions, pinnedColumns, filterMode, search = false, onDeleteSelected, onUpdateSelected, batchUpdateFields, actionBarExtra, actionBarActions, deleteConfirmation, initialSorting, enableExport = true, showDefaultExport = true, onSelectedCountChange, onSelectedRowsChange, getSelectedRows }) {
4432
+ function AutoTable({ schema, data, getRowId, expandedRow, pageCount = 1, pagination, total, overrides, classNames, enableRowSelection = true, exclude, filterOnly, actions, pinnedColumns, filterMode, search = false, onDeleteSelected, onUpdateSelected, batchUpdateFields, actionBarExtra, actionBarActions, deleteConfirmation, initialSorting, enableExport = true, showDefaultExport = true, onSelectedCountChange, onSelectedRowsChange, getSelectedRows }) {
4433
+ if (expandedRow && !getRowId) throw new Error("AutoTable requires getRowId when expandedRow is provided.");
4434
+ const enableExpansion = !!expandedRow;
4418
4435
  const modes = filterMode ? Array.isArray(filterMode) ? filterMode : [filterMode] : DEFAULT_MODES;
4419
4436
  const [currentMode, setCurrentMode] = useQueryState("filterMode", parseAsStringEnum(modes).withDefault(modes[0] ?? "simple"));
4420
4437
  const showToggle = modes.length > 1;
@@ -4428,36 +4445,85 @@ function AutoTable({ schema, data, pageCount = 1, pagination, total, overrides,
4428
4445
  });
4429
4446
  const searchConfig = search && typeof search === "object" ? search : {};
4430
4447
  const enableAdvancedFilter = true;
4431
- const columns = (0, react.useMemo)(() => {
4448
+ const { columns, expandColumnId } = (0, react.useMemo)(() => {
4449
+ const columnOverrides = Object.assign({}, overrides);
4450
+ for (const key of filterOnly ?? []) {
4451
+ const field = key;
4452
+ Object.assign(columnOverrides, { [field]: {
4453
+ ...columnOverrides[field],
4454
+ hidden: false
4455
+ } });
4456
+ }
4432
4457
  const dataColumns = createTableSchema(schema, {
4433
- overrides,
4458
+ overrides: columnOverrides,
4434
4459
  exclude
4460
+ }).map((column) => {
4461
+ const key = column.id ?? ("accessorKey" in column ? String(column.accessorKey) : "");
4462
+ return filterOnly?.includes(key) ? {
4463
+ ...column,
4464
+ enableHiding: false,
4465
+ enableSorting: false
4466
+ } : column;
4435
4467
  });
4436
4468
  const result = enableRowSelection ? [createSelectColumn(), ...dataColumns] : dataColumns;
4469
+ let expandColumnId$1 = "expand";
4470
+ if (enableExpansion) {
4471
+ while (expandColumnId$1 in schema.shape || result.some((column) => column.id === expandColumnId$1)) expandColumnId$1 = `_${expandColumnId$1}`;
4472
+ result.unshift({
4473
+ id: expandColumnId$1,
4474
+ header: () => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4475
+ className: "sr-only",
4476
+ children: "Row details"
4477
+ }),
4478
+ cell: ({ row }) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__wordrhyme_shadcn.Button, {
4479
+ type: "button",
4480
+ variant: "ghost",
4481
+ size: "icon-sm",
4482
+ "aria-label": row.getIsExpanded() ? "Collapse row" : "Expand row",
4483
+ "aria-expanded": row.getIsExpanded(),
4484
+ onClick: row.getToggleExpandedHandler(),
4485
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.ChevronRightIcon, { className: row.getIsExpanded() ? "size-4 rotate-90" : "size-4" })
4486
+ }),
4487
+ enableHiding: false,
4488
+ enableSorting: false,
4489
+ size: 40
4490
+ });
4491
+ }
4437
4492
  if (actions) result.push(createActionsColumn(actions));
4438
- return result;
4493
+ return {
4494
+ columns: result,
4495
+ expandColumnId: expandColumnId$1
4496
+ };
4439
4497
  }, [
4440
4498
  schema,
4441
4499
  overrides,
4442
4500
  enableRowSelection,
4501
+ enableExpansion,
4443
4502
  exclude,
4444
- actions
4503
+ actions,
4504
+ filterOnly
4445
4505
  ]);
4446
4506
  const stableInitialState = (0, react.useMemo)(() => ({
4447
4507
  sorting: initialSorting,
4448
4508
  columnPinning: pinnedColumns ?? {
4449
- left: enableRowSelection ? ["select"] : [],
4509
+ left: [...enableExpansion ? [expandColumnId] : [], ...enableRowSelection ? ["select"] : []],
4450
4510
  right: actions ? ["actions"] : []
4451
4511
  }
4452
4512
  }), [
4453
4513
  initialSorting,
4454
4514
  pinnedColumns,
4455
4515
  enableRowSelection,
4516
+ enableExpansion,
4517
+ expandColumnId,
4456
4518
  actions
4457
4519
  ]);
4520
+ const expandedRowModel = (0, react.useMemo)(() => (0, __tanstack_react_table.getExpandedRowModel)(), []);
4458
4521
  const { table, shallow, debounceMs, throttleMs } = useDataTable({
4459
4522
  data,
4460
4523
  columns,
4524
+ getRowId,
4525
+ getRowCanExpand: () => enableExpansion,
4526
+ getExpandedRowModel: enableExpansion ? expandedRowModel : void 0,
4461
4527
  pageCount,
4462
4528
  ...total !== void 0 ? { rowCount: total } : {},
4463
4529
  enableAdvancedFilter,
@@ -4465,6 +4531,16 @@ function AutoTable({ schema, data, pageCount = 1, pagination, total, overrides,
4465
4531
  shallow: false,
4466
4532
  clearOnDefault: true
4467
4533
  });
4534
+ (0, react.useEffect)(() => {
4535
+ if (!filterOnly?.length) return;
4536
+ table.setColumnVisibility((previous) => {
4537
+ if (filterOnly.every((key) => previous[key] === false)) return previous;
4538
+ return {
4539
+ ...previous,
4540
+ ...Object.fromEntries(filterOnly.map((key) => [key, false]))
4541
+ };
4542
+ });
4543
+ }, [table, filterOnly]);
4468
4544
  const rowSelection = table.getState().rowSelection;
4469
4545
  const columnFilters = table.getState().columnFilters;
4470
4546
  const selectedRows = (0, react.useMemo)(() => table.getFilteredSelectedRowModel().rows.map((row) => row.original), [
@@ -4579,7 +4655,8 @@ function AutoTable({ schema, data, pageCount = 1, pagination, total, overrides,
4579
4655
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DataTable, {
4580
4656
  pagination,
4581
4657
  table,
4582
- classNames
4658
+ classNames,
4659
+ expandedRow
4583
4660
  }),
4584
4661
  enableRowSelection && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AutoTableActionBar, {
4585
4662
  table,
@@ -4905,6 +4982,30 @@ function normalizeHasMore(result) {
4905
4982
 
4906
4983
  //#endregion
4907
4984
  //#region src/lib/field-config.ts
4985
+ function mergeFieldPart(base, override) {
4986
+ if (override === void 0) return base;
4987
+ if (override === false || base === false || typeof base !== "object" || typeof override !== "object" || base === null || override === null || Array.isArray(base) || Array.isArray(override)) return override;
4988
+ return {
4989
+ ...base,
4990
+ ...override
4991
+ };
4992
+ }
4993
+ function mergeFieldConfig(base, override) {
4994
+ return {
4995
+ ...base,
4996
+ ...override,
4997
+ enum: override?.enum ?? base?.enum,
4998
+ dataSource: override?.dataSource ?? base?.dataSource,
4999
+ table: mergeFieldPart(base?.table, override?.table),
5000
+ filter: mergeFieldPart(base?.filter, override?.filter),
5001
+ form: mergeFieldPart(base?.form, override?.form)
5002
+ };
5003
+ }
5004
+ function mergeFields(base, override) {
5005
+ if (!base && !override) return void 0;
5006
+ const keys = new Set([...Object.keys(base ?? {}), ...Object.keys(override ?? {})]);
5007
+ return Object.fromEntries(Array.from(keys).map((key) => [key, mergeFieldConfig(base?.[key], override?.[key])]));
5008
+ }
4908
5009
  function normalizeFieldOptions$1(options) {
4909
5010
  if (!options || options.length === 0) return void 0;
4910
5011
  return options.map((option) => ({
@@ -6292,7 +6393,6 @@ function resolveActions$1(targetId, zone, ownerActions) {
6292
6393
  const ownerCustomIds = new Set(ownerActions.flatMap((action$1) => isCustomAction$1(action$1) && action$1.id ? [action$1.id] : []));
6293
6394
  const baseActions = ownerActions.filter((action$1) => !action$1.hidden && !isMaskedCustom(action$1)).map((action$1) => ({ ...isCustomAction$1(action$1) && action$1.id ? customOverrides.get(action$1.id)?.action ?? action$1 : action$1 }));
6294
6395
  const isReplacedCustom = ({ action: action$1, seq: seq$1 }) => isCustomAction$1(action$1) && Boolean(action$1.id && (ownerCustomIds.has(action$1.id) || customOverrides.get(action$1.id)?.seq !== seq$1));
6295
- if (registered.length === 0) return require_position.positionActions(baseActions).map(withoutRegistryMeta);
6296
6396
  const nextActions = [...baseActions];
6297
6397
  const extraActions = [];
6298
6398
  const groups = /* @__PURE__ */ new Map();
@@ -6319,7 +6419,13 @@ function resolveActions$1(targetId, zone, ownerActions) {
6319
6419
  const items = group.sort((left, right) => left.seq - right.seq).flatMap((entry) => {
6320
6420
  const { action: action$1 } = entry;
6321
6421
  if (action$1.hidden || isReplacedCustom(entry)) return [];
6322
- if (isCustomAction$1(action$1)) return [{ ...action$1 }];
6422
+ if (isCustomAction$1(action$1)) {
6423
+ if (action$1.before) {
6424
+ extraActions.push({ ...action$1 });
6425
+ return [];
6426
+ }
6427
+ return [{ ...action$1 }];
6428
+ }
6323
6429
  const builtin = nextActions.find((item) => item.type === action$1.type);
6324
6430
  return builtin ? [builtin] : [];
6325
6431
  });
@@ -6340,6 +6446,15 @@ function resolveActions$1(targetId, zone, ownerActions) {
6340
6446
  }
6341
6447
  } else extraActions.push(...items);
6342
6448
  }
6449
+ for (const action$1 of [...nextActions, ...extraActions]) {
6450
+ if (!isCustomAction$1(action$1) || !action$1.before) continue;
6451
+ const anchor = nextActions.find((item) => !isCustomAction$1(item) && item.type === action$1.before);
6452
+ if (anchor) action$1.position = {
6453
+ anchor: anchor.id ?? anchor.type,
6454
+ side: "before"
6455
+ };
6456
+ else if (typeof action$1.position === "object") action$1.position = "end";
6457
+ }
6343
6458
  return require_position.positionActions(nextActions, extraActions).map(withoutRegistryMeta);
6344
6459
  }
6345
6460
  const crudActions = {
@@ -6489,30 +6604,6 @@ function resolveToolbarActionsWithResolver(targetId, ownerActions, context) {
6489
6604
  const resolver = autoCrudToolbarResolver;
6490
6605
  return targetId !== void 0 && targetId.length > 0 && resolver !== null ? resolver(targetId, ownerActions, context) : ownerActions;
6491
6606
  }
6492
- function mergeFieldPart(base, override) {
6493
- if (override === void 0) return base;
6494
- if (override === false || base === false || typeof base !== "object" || typeof override !== "object" || base === null || override === null || Array.isArray(base) || Array.isArray(override)) return override;
6495
- return {
6496
- ...base,
6497
- ...override
6498
- };
6499
- }
6500
- function mergeFieldConfig(base, override) {
6501
- return {
6502
- ...base,
6503
- ...override,
6504
- enum: override?.enum ?? base?.enum,
6505
- dataSource: override?.dataSource ?? base?.dataSource,
6506
- table: mergeFieldPart(base?.table, override?.table),
6507
- filter: mergeFieldPart(base?.filter, override?.filter),
6508
- form: mergeFieldPart(base?.form, override?.form)
6509
- };
6510
- }
6511
- function mergeFields(base, override) {
6512
- if (!base && !override) return void 0;
6513
- const keys = new Set([...Object.keys(base ?? {}), ...Object.keys(override ?? {})]);
6514
- return Object.fromEntries(Array.from(keys).map((key) => [key, mergeFieldConfig(base?.[key], override?.[key])]));
6515
- }
6516
6607
  function applyResourceTablePresentation(fields, resourceFields) {
6517
6608
  if (!fields || !resourceFields) return fields;
6518
6609
  return Object.fromEntries(Object.entries(fields).map(([key, config]) => {
@@ -7414,6 +7505,12 @@ function AutoCrudTable({ id, title, description, schema, resource, fields, table
7414
7505
  const [exporting, setExporting] = react.useState(false);
7415
7506
  const getSelectedRowsRef = react.useRef(null);
7416
7507
  const dynamicFilterOptions = useDynamicFilterOptions(resolvedFields);
7508
+ const filterOnly = react.useMemo(() => Object.entries(resolvedFields).filter(([key, config]) => config.table === false && config.filter && config.hidden !== true && !denyFields?.includes(key) && !tableConfig?.hidden?.includes(key) && tableConfig?.overrides?.[key]?.hidden !== true).map(([key]) => key), [
7509
+ resolvedFields,
7510
+ denyFields,
7511
+ tableConfig?.hidden,
7512
+ tableConfig?.overrides
7513
+ ]);
7417
7514
  const hiddenColumns = react.useMemo(() => buildHiddenColumns(resolvedFields, tableConfig?.hidden, denyFields, tableConfig?.overrides), [
7418
7515
  resolvedFields,
7419
7516
  tableConfig?.hidden,
@@ -7502,7 +7599,7 @@ function AutoCrudTable({ id, title, description, schema, resource, fields, table
7502
7599
  return {
7503
7600
  ...action$1,
7504
7601
  onClick: (_rows, { table }) => {
7505
- const headers = table.getAllLeafColumns().map((column) => column.id).filter((key) => key !== "select" && key !== "actions" && !denyFields?.includes(key));
7602
+ const headers = getExportColumnIds(table).filter((key) => key !== "select" && key !== "actions" && !denyFields?.includes(key));
7506
7603
  exportAllToCSV(table.getFilteredSelectedRowModel().rows.map((row) => formatExportRow(row.original, resolvedFields, Object.fromEntries(headers.map((key) => [key, row.getValue(key)])))), {
7507
7604
  filename: "table",
7508
7605
  headers
@@ -7762,9 +7859,12 @@ function AutoCrudTable({ id, title, description, schema, resource, fields, table
7762
7859
  ...pagination
7763
7860
  } : void 0,
7764
7861
  ...resource.tableData.total !== void 0 ? { total: resource.tableData.total } : {},
7862
+ getRowId: tableConfig?.getRowId,
7863
+ expandedRow: tableConfig?.expandedRow,
7765
7864
  overrides: tableOverrides,
7766
7865
  classNames: tableConfig?.classNames,
7767
- exclude: hiddenColumns,
7866
+ exclude: hiddenColumns.filter((key) => !filterOnly.includes(key)),
7867
+ filterOnly,
7768
7868
  filterMode: tableConfig?.filterModes,
7769
7869
  search: searchConfig,
7770
7870
  actions: tableRowActions,
@@ -8597,7 +8697,7 @@ function modalReducer(state, action$1) {
8597
8697
  /**
8598
8698
  * Hook 主函数
8599
8699
  */
8600
- function useAutoCrudResource({ router, schema, query: queryTransform, options = {} }) {
8700
+ function useAutoCrudResource({ router, schema, query: queryTransform, fields, options = {} }) {
8601
8701
  const { idKey = "id", defaultVariant = "dialog", hooks, toast: toastAdapter, exportFetcher, defaultSort: defaultSortOption } = options;
8602
8702
  const toast = (0, react.useMemo)(() => toastAdapter === false ? noopToastAdapter : toastAdapter ?? defaultToastAdapter, [toastAdapter]);
8603
8703
  const hooksRef = (0, react.useRef)(hooks);
@@ -8609,7 +8709,14 @@ function useAutoCrudResource({ router, schema, query: queryTransform, options =
8609
8709
  const defaultSorting = (0, react.useMemo)(() => defaultSortOption === false ? [] : defaultSortOption ?? getDefaultSortingForSchema(resolvedSchema), [defaultSortOption, resolvedSchema]);
8610
8710
  const metadataFields = (0, react.useMemo)(() => readMetadataFields(metadata?.fields), [metadata?.fields]);
8611
8711
  const capabilities = metadata?.capabilities;
8612
- const columns = (0, react.useMemo)(() => createTableSchema(resolvedSchema), [resolvedSchema]);
8712
+ const columns = (0, react.useMemo)(() => createTableSchema(resolvedSchema, { overrides: Object.fromEntries(Object.entries(mergeFields(metadataFields, fields) ?? {}).flatMap(([key, field]) => {
8713
+ const filter = field.filter;
8714
+ return filter && typeof filter === "object" && filter.variant ? [[key, { meta: { variant: filter.variant } }]] : [];
8715
+ })) }), [
8716
+ resolvedSchema,
8717
+ fields,
8718
+ metadataFields
8719
+ ]);
8613
8720
  const [urlParams] = useQueryStates({
8614
8721
  page: parseAsInteger.withDefault(1),
8615
8722
  perPage: parseAsInteger.withDefault(10),