@wordrhyme/auto-crud 1.10.0 → 1.11.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/README.md CHANGED
@@ -265,7 +265,10 @@ interface AutoCrudTableProps<TSchema> {
265
265
  hidden?: string[]; // 隐藏的列
266
266
  overrides?: Record<string, any>; // 列覆盖配置
267
267
  getRowId?: (record: z.output<TSchema>) => string; // 稳定行标识;展开时必填
268
- expandedRow?: (record: z.output<TSchema>) => React.ReactNode; // 展开行内容
268
+ expandedRow?: (
269
+ record: z.output<TSchema>,
270
+ row: Row<z.output<TSchema>>,
271
+ ) => React.ReactNode; // 展开行内容,row 提供已解析的单元格上下文
269
272
  filterModes?: FilterMode | FilterMode[]; // 过滤模式
270
273
  batchFields?: (string | BatchUpdateField)[]; // 批量更新字段
271
274
  /** @deprecated 新代码请使用 actions.batch */
@@ -298,8 +301,9 @@ interface AutoCrudTableProps<TSchema> {
298
301
 
299
302
  #### 展开行详情
300
303
 
301
- 配置 `table.expandedRow` 后,表格自动添加展开箭头。默认全部收起,允许同时展开多行。
304
+ 配置 `table.expandedRow` 后,表格自动添加展开箭头。默认全部收起,允许同时展开多行。设置 `table.defaultExpanded: true` 可在首次展示时展开全部行;刷新保留用户的展开选择。该选项仅用于初始化,不控制当前展开状态。
302
305
  必须提供 TanStack Table 的 `getRowId`,使用稳定且唯一的业务主键,避免分页、排序后展开错位。
306
+ 展开内容可使用 `cqw` 单位,以表格可视区域的宽度为基准,避免宽表中的详情被横向裁切。
303
307
 
304
308
  ```tsx
305
309
  <AutoCrudTable
@@ -317,9 +321,11 @@ interface AutoCrudTableProps<TSchema> {
317
321
  明细数据及其字段权限由业务组件和现有服务负责。
318
322
 
319
323
  详情区域跨全部可见列,不计入分页条数,也不参与主表选择或导出。
320
- 数据更新时,展开状态遵循 TanStack Table 默认重置行为。
324
+ 未启用 `defaultExpanded` 时,数据更新后的展开状态遵循 TanStack Table 默认重置行为。
321
325
  `AutoTable` 同样支持 `getRowId` 和 `expandedRow`;直接使用 `DataTable` 时,需自行配置 TanStack 展开能力及触发按钮。
322
326
 
327
+ `expandedRow` 的第二个参数是当前 TanStack `Row`,可通过 `row.getAllCells()` 读取已解析的列和单元格上下文,复用字段渲染器及数据源标签。只接收订单数据的单参数回调仍可使用。
328
+
323
329
  ### `useAutoCrudResource()`
324
330
 
325
331
  连接数据源到组件的 Hook。
@@ -667,10 +673,22 @@ table={{
667
673
 
668
674
  ## 🔧 工具栏操作配置
669
675
 
670
- `toolbar` 数组控制页面顶部右侧工具栏的按钮,设计思路完全对齐行操作 `actions`。旧 prop `toolbarActions` 仍兼容读取,但新代码请使用 `toolbar`。
676
+ `toolbar` 数组控制工具栏操作,设计思路完全对齐行操作 `actions`。旧 prop `toolbarActions` 仍兼容读取,但新代码请使用 `toolbar`。
671
677
 
672
678
  内置操作类型:`refresh`(刷新)、`create`(新建)、`import`(导入)、`export`(导出)。
673
679
 
680
+ 刷新固定显示在搜索筛选行右侧,顺序为 `[排序] [刷新] [列设置]`;其 `label`、`onClick`、`component` 和隐藏配置仍由 `toolbar` 控制。其余操作保留在上方工具栏,没有可见操作时不占空行。
681
+
682
+ 直接使用 `AutoTable` 时,可通过 `toolbarExtra` 插入排序与列设置之间的控件:
683
+
684
+ ```tsx
685
+ <AutoTable
686
+ schema={schema}
687
+ data={rows}
688
+ toolbarExtra={<Button onClick={refresh}>刷新</Button>}
689
+ />
690
+ ```
691
+
674
692
  ### 只添加自定义按钮(内置保持默认)
675
693
 
676
694
  只传 `type: "custom"` 项时,所有内置按钮(刷新、导入、导出、新建)保持原样,custom 项按 `position` 插入首部或尾部。
@@ -686,12 +704,13 @@ table={{
686
704
  { type: 'custom', component: <Button variant="outline">批量标签</Button> }, // 默认 position: "end"
687
705
  ]}
688
706
  />
689
- // 渲染顺序: 分类管理 · [刷新] · [导入] · [导出] · [新建] · 批量标签
707
+ // 上方工具栏: 分类管理 · [导入] · [导出] · [新建] · 批量标签
708
+ // 搜索筛选行右侧: [排序] · [刷新] · [列设置]
690
709
  ```
691
710
 
692
711
  ### 调整内置按钮顺序
693
712
 
694
- 数组中只要包含任意内置 `type`,就会**完全接管工具栏** —— 未列出的内置按钮**直接隐藏**,渲染顺序**严格按数组**。
713
+ 数组中只要包含任意内置 `type`,就会**完全接管工具栏** —— 未列出的内置按钮**直接隐藏**。刷新保持在搜索筛选行,其他按钮的渲染顺序**严格按数组**。
695
714
 
696
715
  ```tsx
697
716
  <AutoCrudTable
package/dist/index.cjs CHANGED
@@ -581,6 +581,7 @@ function DataTable({ table, actionBar, pagination, expandedRow, children, classN
581
581
  children,
582
582
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
583
583
  className: "overflow-hidden rounded-md border",
584
+ style: expandedRow ? { containerType: "inline-size" } : void 0,
584
585
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(__wordrhyme_shadcn.Table, {
585
586
  className: classNames?.table,
586
587
  style: hasWidthCap ? {
@@ -622,7 +623,7 @@ function DataTable({ table, actionBar, pagination, expandedRow, children, classN
622
623
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__wordrhyme_shadcn.TableCell, {
623
624
  colSpan: row.getVisibleCells().length,
624
625
  className: classNames?.td,
625
- children: expandedRow(row.original)
626
+ children: expandedRow(row.original, row)
626
627
  })
627
628
  })] }, row.id)) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__wordrhyme_shadcn.TableRow, {
628
629
  className: classNames?.tr,
@@ -4429,7 +4430,7 @@ const filterModeConfig = {
4429
4430
  tooltip: "Linear-style command filters"
4430
4431
  }
4431
4432
  };
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
+ function AutoTable({ schema, data, getRowId, expandedRow, defaultExpanded = false, pageCount = 1, pagination, total, overrides, classNames, enableRowSelection = true, exclude, filterOnly, actions, pinnedColumns, filterMode, search = false, toolbarExtra, onDeleteSelected, onUpdateSelected, batchUpdateFields, actionBarExtra, actionBarActions, deleteConfirmation, initialSorting, enableExport = true, showDefaultExport = true, onSelectedCountChange, onSelectedRowsChange, getSelectedRows }) {
4433
4434
  if (expandedRow && !getRowId) throw new Error("AutoTable requires getRowId when expandedRow is provided.");
4434
4435
  const enableExpansion = !!expandedRow;
4435
4436
  const modes = filterMode ? Array.isArray(filterMode) ? filterMode : [filterMode] : DEFAULT_MODES;
@@ -4505,6 +4506,7 @@ function AutoTable({ schema, data, getRowId, expandedRow, pageCount = 1, paginat
4505
4506
  ]);
4506
4507
  const stableInitialState = (0, react.useMemo)(() => ({
4507
4508
  sorting: initialSorting,
4509
+ expanded: enableExpansion && defaultExpanded ? true : {},
4508
4510
  columnPinning: pinnedColumns ?? {
4509
4511
  left: [...enableExpansion ? [expandColumnId] : [], ...enableRowSelection ? ["select"] : []],
4510
4512
  right: actions ? ["actions"] : []
@@ -4514,6 +4516,7 @@ function AutoTable({ schema, data, getRowId, expandedRow, pageCount = 1, paginat
4514
4516
  pinnedColumns,
4515
4517
  enableRowSelection,
4516
4518
  enableExpansion,
4519
+ defaultExpanded,
4517
4520
  expandColumnId,
4518
4521
  actions
4519
4522
  ]);
@@ -4524,6 +4527,7 @@ function AutoTable({ schema, data, getRowId, expandedRow, pageCount = 1, paginat
4524
4527
  getRowId,
4525
4528
  getRowCanExpand: () => enableExpansion,
4526
4529
  getExpandedRowModel: enableExpansion ? expandedRowModel : void 0,
4530
+ autoResetExpanded: defaultExpanded ? false : void 0,
4527
4531
  pageCount,
4528
4532
  ...total !== void 0 ? { rowCount: total } : {},
4529
4533
  enableAdvancedFilter,
@@ -4645,6 +4649,7 @@ function AutoTable({ schema, data, getRowId, expandedRow, pageCount = 1, paginat
4645
4649
  table,
4646
4650
  align: "end"
4647
4651
  }),
4652
+ toolbarExtra,
4648
4653
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DataTableViewOptions, {
4649
4654
  table,
4650
4655
  align: "end"
@@ -7762,6 +7767,60 @@ function AutoCrudTable({ id, title, description, schema, resource, fields, table
7762
7767
  rowActionDefaults,
7763
7768
  openComponent
7764
7769
  ]);
7770
+ const normalizedToolbarActions = resolvedToolbarActions.length > 0 ? resolveOwnerToolbarActions(resolvedToolbarActions) : [];
7771
+ const renderBuiltinButton = (type, overrides) => {
7772
+ if (type === "refresh") {
7773
+ const onRefresh = overrides?.onClick ?? toolbarRefresh;
7774
+ const label = overrides?.label ?? locale.toolbar.refresh;
7775
+ if (!onRefresh) return null;
7776
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__wordrhyme_shadcn.Button, {
7777
+ variant: "outline",
7778
+ size: "icon-sm",
7779
+ "aria-label": label,
7780
+ title: label,
7781
+ onClick: onRefresh,
7782
+ disabled: resource.tableData.isFetching,
7783
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.RefreshCw, { className: resource.tableData.isFetching ? "h-4 w-4 animate-spin" : "h-4 w-4" })
7784
+ }, "refresh");
7785
+ }
7786
+ if (type === "import" && canImport) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(__wordrhyme_shadcn.Button, {
7787
+ variant: "outline",
7788
+ size: "sm",
7789
+ onClick: overrides?.onClick ?? toolbarOpenImport,
7790
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Download, { className: "mr-2 h-4 w-4" }), overrides?.label ?? locale.toolbar.import]
7791
+ }, "import");
7792
+ if (type === "export" && canExport) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(__wordrhyme_shadcn.Button, {
7793
+ variant: "outline",
7794
+ size: "sm",
7795
+ onClick: overrides?.onClick ?? toolbarExportData,
7796
+ disabled: exporting || selectedCount === 0 && !resource.handlers.export,
7797
+ 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" }), overrides?.label ?? (selectedCount > 0 ? locale.toolbar.exportSelected(selectedCount) : locale.toolbar.export)]
7798
+ }, "export");
7799
+ if (type === "create" && can.create) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__wordrhyme_shadcn.Button, {
7800
+ onClick: overrides?.onClick ?? toolbarOpenCreate,
7801
+ children: overrides?.label ?? locale.toolbar.create
7802
+ }, "create");
7803
+ return null;
7804
+ };
7805
+ const renderToolbarComponent = (component) => typeof component === "function" ? component(toolbarContext) : component;
7806
+ const renderToolbarAction = (action$1, index$1) => {
7807
+ if (action$1.type === "custom") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react.Fragment, { children: renderToolbarComponent(action$1.component) }, `custom-${index$1}`);
7808
+ if (!(() => {
7809
+ switch (action$1.type) {
7810
+ case "refresh": return !!(resource.handlers.refresh || action$1.onClick || action$1.component);
7811
+ case "import": return canImport;
7812
+ case "export": return canExport;
7813
+ case "create": return can.create;
7814
+ }
7815
+ })()) return null;
7816
+ if (action$1.component) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react.Fragment, { children: renderToolbarComponent(action$1.component) }, action$1.type);
7817
+ return renderBuiltinButton(action$1.type, {
7818
+ onClick: action$1.onClick,
7819
+ label: action$1.label
7820
+ });
7821
+ };
7822
+ const toolbarNodes = normalizedToolbarActions.filter((action$1) => action$1.type !== "refresh").map(renderToolbarAction).filter((node) => node !== null);
7823
+ const refreshNodes = normalizedToolbarActions.filter((action$1) => action$1.type === "refresh").map(renderToolbarAction);
7765
7824
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7766
7825
  className: "space-y-4",
7767
7826
  children: [
@@ -7775,80 +7834,12 @@ function AutoCrudTable({ id, title, description, schema, resource, fields, table
7775
7834
  children: description
7776
7835
  })]
7777
7836
  }),
7778
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
7837
+ toolbarNodes.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
7779
7838
  className: "flex items-center justify-end gap-4",
7780
- children: (() => {
7781
- const renderBuiltinButton = (type, overrides) => {
7782
- if (type === "refresh") {
7783
- const onRefresh = overrides?.onClick ?? toolbarRefresh;
7784
- const label = overrides?.label ?? locale.toolbar.refresh;
7785
- if (!onRefresh) return null;
7786
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__wordrhyme_shadcn.Button, {
7787
- variant: "outline",
7788
- size: "icon-sm",
7789
- "aria-label": label,
7790
- title: label,
7791
- onClick: onRefresh,
7792
- disabled: resource.tableData.isFetching,
7793
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.RefreshCw, { className: resource.tableData.isFetching ? "h-4 w-4 animate-spin" : "h-4 w-4" })
7794
- }, "refresh");
7795
- }
7796
- if (type === "import" && canImport) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(__wordrhyme_shadcn.Button, {
7797
- variant: "outline",
7798
- size: "sm",
7799
- onClick: overrides?.onClick ?? toolbarOpenImport,
7800
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Download, { className: "mr-2 h-4 w-4" }), overrides?.label ?? locale.toolbar.import]
7801
- }, "import");
7802
- if (type === "export" && canExport) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(__wordrhyme_shadcn.Button, {
7803
- variant: "outline",
7804
- size: "sm",
7805
- onClick: overrides?.onClick ?? toolbarExportData,
7806
- disabled: exporting || selectedCount === 0 && !resource.handlers.export,
7807
- 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" }), overrides?.label ?? (selectedCount > 0 ? locale.toolbar.exportSelected(selectedCount) : locale.toolbar.export)]
7808
- }, "export");
7809
- if (type === "create" && can.create) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__wordrhyme_shadcn.Button, {
7810
- onClick: overrides?.onClick ?? toolbarOpenCreate,
7811
- children: overrides?.label ?? locale.toolbar.create
7812
- }, "create");
7813
- return null;
7814
- };
7815
- const renderToolbarComponent = (component) => typeof component === "function" ? component(toolbarContext) : component;
7816
- if (!resolvedToolbarActions || resolvedToolbarActions.length === 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "flex items-center gap-2" });
7817
- if (!resolvedToolbarActions.some((i) => i.type !== "custom")) {
7818
- const startNodes = resolvedToolbarActions.filter((i) => i.type === "custom" && i.position === "start").map((a, i) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react.Fragment, { children: renderToolbarComponent(a.component) }, `start-${i}`));
7819
- const endNodes = resolvedToolbarActions.filter((i) => i.type === "custom" && i.position !== "start").map((a, i) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react.Fragment, { children: renderToolbarComponent(a.component) }, `end-${i}`));
7820
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7821
- className: "flex items-center gap-2",
7822
- children: [
7823
- startNodes,
7824
- renderBuiltinButton("refresh"),
7825
- renderBuiltinButton("import"),
7826
- renderBuiltinButton("export"),
7827
- renderBuiltinButton("create"),
7828
- endNodes
7829
- ]
7830
- });
7831
- }
7832
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
7833
- className: "flex items-center gap-2",
7834
- children: resolvedToolbarActions.map((action$1, index$1) => {
7835
- if (action$1.type === "custom") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react.Fragment, { children: renderToolbarComponent(action$1.component) }, `custom-${index$1}`);
7836
- if (!(() => {
7837
- switch (action$1.type) {
7838
- case "refresh": return !!(resource.handlers.refresh || action$1.onClick || action$1.component);
7839
- case "import": return canImport;
7840
- case "export": return canExport;
7841
- case "create": return can.create;
7842
- }
7843
- })()) return null;
7844
- if (action$1.component) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react.Fragment, { children: renderToolbarComponent(action$1.component) }, action$1.type);
7845
- return renderBuiltinButton(action$1.type, {
7846
- onClick: action$1.onClick,
7847
- label: action$1.label
7848
- });
7849
- })
7850
- });
7851
- })()
7839
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
7840
+ className: "flex items-center gap-2",
7841
+ children: toolbarNodes
7842
+ })
7852
7843
  }),
7853
7844
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AutoTable, {
7854
7845
  data: resource.tableData.data,
@@ -7861,12 +7852,14 @@ function AutoCrudTable({ id, title, description, schema, resource, fields, table
7861
7852
  ...resource.tableData.total !== void 0 ? { total: resource.tableData.total } : {},
7862
7853
  getRowId: tableConfig?.getRowId,
7863
7854
  expandedRow: tableConfig?.expandedRow,
7855
+ defaultExpanded: tableConfig?.defaultExpanded,
7864
7856
  overrides: tableOverrides,
7865
7857
  classNames: tableConfig?.classNames,
7866
7858
  exclude: hiddenColumns.filter((key) => !filterOnly.includes(key)),
7867
7859
  filterOnly,
7868
7860
  filterMode: tableConfig?.filterModes,
7869
7861
  search: searchConfig,
7862
+ toolbarExtra: refreshNodes,
7870
7863
  actions: tableRowActions,
7871
7864
  onDeleteSelected: can.delete ? resource.handlers.deleteMany : void 0,
7872
7865
  onUpdateSelected: can.update ? resource.handlers.updateMany : void 0,
package/dist/index.d.cts CHANGED
@@ -658,8 +658,8 @@ interface DataTableProps<TData> extends ComponentProps<'div'> {
658
658
  pagination?: TablePaginationOptions;
659
659
  classNames?: DataTableClassNames;
660
660
  actionBar?: ReactNode;
661
- /** Content mounted below an expanded row. */
662
- expandedRow?: (record: TData) => ReactNode;
661
+ /** Content mounted below an expanded row; row provides resolved cell contexts. */
662
+ expandedRow?: (record: TData, row: Row<TData>) => ReactNode;
663
663
  }
664
664
  declare function DataTable<TData>({
665
665
  table,
@@ -873,6 +873,8 @@ interface AutoTableProps<T extends z.ZodObject<z.ZodRawShape>> extends Pick<Data
873
873
  data: z.infer<T>[];
874
874
  /** Stable row identity; required when expandedRow is provided. */
875
875
  getRowId?: TableOptions<z.infer<T>>['getRowId'];
876
+ /** Initially expand all rows. Defaults to false; only applies with expandedRow. */
877
+ defaultExpanded?: boolean;
876
878
  pageCount?: number;
877
879
  pagination?: TablePaginationOptions;
878
880
  /** Total rows matching the current search and filters. */
@@ -900,6 +902,8 @@ interface AutoTableProps<T extends z.ZodObject<z.ZodRawShape>> extends Pick<Data
900
902
  search?: boolean | {
901
903
  placeholder?: string;
902
904
  };
905
+ /** Extra table controls rendered between sorting and column visibility. */
906
+ toolbarExtra?: React.ReactNode;
903
907
  /** 批量删除回调 */
904
908
  onDeleteSelected?: (rows: z.infer<T>[]) => void;
905
909
  /** 批量更新回调 */
@@ -930,6 +934,7 @@ declare function AutoTable<T extends z.ZodObject<z.ZodRawShape>>({
930
934
  data,
931
935
  getRowId,
932
936
  expandedRow,
937
+ defaultExpanded,
933
938
  pageCount,
934
939
  pagination,
935
940
  total,
@@ -942,6 +947,7 @@ declare function AutoTable<T extends z.ZodObject<z.ZodRawShape>>({
942
947
  pinnedColumns,
943
948
  filterMode,
944
949
  search,
950
+ toolbarExtra,
945
951
  onDeleteSelected,
946
952
  onUpdateSelected,
947
953
  batchUpdateFields,
@@ -1426,7 +1432,7 @@ interface AutoCrudTableProps<TSchema extends z.ZodObject<z.ZodRawShape>> {
1426
1432
  batchActions?: BatchActionConfig<z.output<TSchema>>;
1427
1433
  /** 默认排序 */
1428
1434
  defaultSort?: any[];
1429
- } & Pick<AutoTableProps<TSchema>, 'getRowId' | 'expandedRow'>;
1435
+ } & Pick<AutoTableProps<TSchema>, 'getRowId' | 'expandedRow' | 'defaultExpanded'>;
1430
1436
  /** 表单配置 */
1431
1437
  form?: {
1432
1438
  /** 表单覆盖配置(兼容旧 API,优先级低于 fieldOverrides) */
package/dist/index.d.ts CHANGED
@@ -658,8 +658,8 @@ interface DataTableProps<TData> extends ComponentProps<'div'> {
658
658
  pagination?: TablePaginationOptions;
659
659
  classNames?: DataTableClassNames;
660
660
  actionBar?: ReactNode;
661
- /** Content mounted below an expanded row. */
662
- expandedRow?: (record: TData) => ReactNode;
661
+ /** Content mounted below an expanded row; row provides resolved cell contexts. */
662
+ expandedRow?: (record: TData, row: Row<TData>) => ReactNode;
663
663
  }
664
664
  declare function DataTable<TData>({
665
665
  table,
@@ -873,6 +873,8 @@ interface AutoTableProps<T extends z.ZodObject<z.ZodRawShape>> extends Pick<Data
873
873
  data: z.infer<T>[];
874
874
  /** Stable row identity; required when expandedRow is provided. */
875
875
  getRowId?: TableOptions<z.infer<T>>['getRowId'];
876
+ /** Initially expand all rows. Defaults to false; only applies with expandedRow. */
877
+ defaultExpanded?: boolean;
876
878
  pageCount?: number;
877
879
  pagination?: TablePaginationOptions;
878
880
  /** Total rows matching the current search and filters. */
@@ -900,6 +902,8 @@ interface AutoTableProps<T extends z.ZodObject<z.ZodRawShape>> extends Pick<Data
900
902
  search?: boolean | {
901
903
  placeholder?: string;
902
904
  };
905
+ /** Extra table controls rendered between sorting and column visibility. */
906
+ toolbarExtra?: React.ReactNode;
903
907
  /** 批量删除回调 */
904
908
  onDeleteSelected?: (rows: z.infer<T>[]) => void;
905
909
  /** 批量更新回调 */
@@ -930,6 +934,7 @@ declare function AutoTable<T extends z.ZodObject<z.ZodRawShape>>({
930
934
  data,
931
935
  getRowId,
932
936
  expandedRow,
937
+ defaultExpanded,
933
938
  pageCount,
934
939
  pagination,
935
940
  total,
@@ -942,6 +947,7 @@ declare function AutoTable<T extends z.ZodObject<z.ZodRawShape>>({
942
947
  pinnedColumns,
943
948
  filterMode,
944
949
  search,
950
+ toolbarExtra,
945
951
  onDeleteSelected,
946
952
  onUpdateSelected,
947
953
  batchUpdateFields,
@@ -1426,7 +1432,7 @@ interface AutoCrudTableProps<TSchema extends z.ZodObject<z.ZodRawShape>> {
1426
1432
  batchActions?: BatchActionConfig<z.output<TSchema>>;
1427
1433
  /** 默认排序 */
1428
1434
  defaultSort?: any[];
1429
- } & Pick<AutoTableProps<TSchema>, 'getRowId' | 'expandedRow'>;
1435
+ } & Pick<AutoTableProps<TSchema>, 'getRowId' | 'expandedRow' | 'defaultExpanded'>;
1430
1436
  /** 表单配置 */
1431
1437
  form?: {
1432
1438
  /** 表单覆盖配置(兼容旧 API,优先级低于 fieldOverrides) */
package/dist/index.js CHANGED
@@ -566,6 +566,7 @@ function DataTable({ table, actionBar, pagination, expandedRow, children, classN
566
566
  children,
567
567
  /* @__PURE__ */ jsx("div", {
568
568
  className: "overflow-hidden rounded-md border",
569
+ style: expandedRow ? { containerType: "inline-size" } : void 0,
569
570
  children: /* @__PURE__ */ jsxs(Table$1, {
570
571
  className: classNames?.table,
571
572
  style: hasWidthCap ? {
@@ -607,7 +608,7 @@ function DataTable({ table, actionBar, pagination, expandedRow, children, classN
607
608
  children: /* @__PURE__ */ jsx(TableCell, {
608
609
  colSpan: row.getVisibleCells().length,
609
610
  className: classNames?.td,
610
- children: expandedRow(row.original)
611
+ children: expandedRow(row.original, row)
611
612
  })
612
613
  })] }, row.id)) : /* @__PURE__ */ jsx(TableRow, {
613
614
  className: classNames?.tr,
@@ -4414,7 +4415,7 @@ const filterModeConfig = {
4414
4415
  tooltip: "Linear-style command filters"
4415
4416
  }
4416
4417
  };
4417
- 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 }) {
4418
+ function AutoTable({ schema, data, getRowId, expandedRow, defaultExpanded = false, pageCount = 1, pagination, total, overrides, classNames, enableRowSelection = true, exclude, filterOnly, actions, pinnedColumns, filterMode, search = false, toolbarExtra, onDeleteSelected, onUpdateSelected, batchUpdateFields, actionBarExtra, actionBarActions, deleteConfirmation, initialSorting, enableExport = true, showDefaultExport = true, onSelectedCountChange, onSelectedRowsChange, getSelectedRows }) {
4418
4419
  if (expandedRow && !getRowId) throw new Error("AutoTable requires getRowId when expandedRow is provided.");
4419
4420
  const enableExpansion = !!expandedRow;
4420
4421
  const modes = filterMode ? Array.isArray(filterMode) ? filterMode : [filterMode] : DEFAULT_MODES;
@@ -4490,6 +4491,7 @@ function AutoTable({ schema, data, getRowId, expandedRow, pageCount = 1, paginat
4490
4491
  ]);
4491
4492
  const stableInitialState = useMemo(() => ({
4492
4493
  sorting: initialSorting,
4494
+ expanded: enableExpansion && defaultExpanded ? true : {},
4493
4495
  columnPinning: pinnedColumns ?? {
4494
4496
  left: [...enableExpansion ? [expandColumnId] : [], ...enableRowSelection ? ["select"] : []],
4495
4497
  right: actions ? ["actions"] : []
@@ -4499,6 +4501,7 @@ function AutoTable({ schema, data, getRowId, expandedRow, pageCount = 1, paginat
4499
4501
  pinnedColumns,
4500
4502
  enableRowSelection,
4501
4503
  enableExpansion,
4504
+ defaultExpanded,
4502
4505
  expandColumnId,
4503
4506
  actions
4504
4507
  ]);
@@ -4509,6 +4512,7 @@ function AutoTable({ schema, data, getRowId, expandedRow, pageCount = 1, paginat
4509
4512
  getRowId,
4510
4513
  getRowCanExpand: () => enableExpansion,
4511
4514
  getExpandedRowModel: enableExpansion ? expandedRowModel : void 0,
4515
+ autoResetExpanded: defaultExpanded ? false : void 0,
4512
4516
  pageCount,
4513
4517
  ...total !== void 0 ? { rowCount: total } : {},
4514
4518
  enableAdvancedFilter,
@@ -4630,6 +4634,7 @@ function AutoTable({ schema, data, getRowId, expandedRow, pageCount = 1, paginat
4630
4634
  table,
4631
4635
  align: "end"
4632
4636
  }),
4637
+ toolbarExtra,
4633
4638
  /* @__PURE__ */ jsx(DataTableViewOptions, {
4634
4639
  table,
4635
4640
  align: "end"
@@ -7747,6 +7752,60 @@ function AutoCrudTable({ id, title, description, schema, resource, fields, table
7747
7752
  rowActionDefaults,
7748
7753
  openComponent
7749
7754
  ]);
7755
+ const normalizedToolbarActions = resolvedToolbarActions.length > 0 ? resolveOwnerToolbarActions(resolvedToolbarActions) : [];
7756
+ const renderBuiltinButton = (type, overrides) => {
7757
+ if (type === "refresh") {
7758
+ const onRefresh = overrides?.onClick ?? toolbarRefresh;
7759
+ const label = overrides?.label ?? locale.toolbar.refresh;
7760
+ if (!onRefresh) return null;
7761
+ return /* @__PURE__ */ jsx(Button, {
7762
+ variant: "outline",
7763
+ size: "icon-sm",
7764
+ "aria-label": label,
7765
+ title: label,
7766
+ onClick: onRefresh,
7767
+ disabled: resource.tableData.isFetching,
7768
+ children: /* @__PURE__ */ jsx(RefreshCw, { className: resource.tableData.isFetching ? "h-4 w-4 animate-spin" : "h-4 w-4" })
7769
+ }, "refresh");
7770
+ }
7771
+ if (type === "import" && canImport) return /* @__PURE__ */ jsxs(Button, {
7772
+ variant: "outline",
7773
+ size: "sm",
7774
+ onClick: overrides?.onClick ?? toolbarOpenImport,
7775
+ children: [/* @__PURE__ */ jsx(Download, { className: "mr-2 h-4 w-4" }), overrides?.label ?? locale.toolbar.import]
7776
+ }, "import");
7777
+ if (type === "export" && canExport) return /* @__PURE__ */ jsxs(Button, {
7778
+ variant: "outline",
7779
+ size: "sm",
7780
+ onClick: overrides?.onClick ?? toolbarExportData,
7781
+ disabled: exporting || selectedCount === 0 && !resource.handlers.export,
7782
+ children: [exporting ? /* @__PURE__ */ jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" }) : /* @__PURE__ */ jsx(Upload, { className: "mr-2 h-4 w-4" }), overrides?.label ?? (selectedCount > 0 ? locale.toolbar.exportSelected(selectedCount) : locale.toolbar.export)]
7783
+ }, "export");
7784
+ if (type === "create" && can.create) return /* @__PURE__ */ jsx(Button, {
7785
+ onClick: overrides?.onClick ?? toolbarOpenCreate,
7786
+ children: overrides?.label ?? locale.toolbar.create
7787
+ }, "create");
7788
+ return null;
7789
+ };
7790
+ const renderToolbarComponent = (component) => typeof component === "function" ? component(toolbarContext) : component;
7791
+ const renderToolbarAction = (action$1, index$1) => {
7792
+ if (action$1.type === "custom") return /* @__PURE__ */ jsx(React.Fragment, { children: renderToolbarComponent(action$1.component) }, `custom-${index$1}`);
7793
+ if (!(() => {
7794
+ switch (action$1.type) {
7795
+ case "refresh": return !!(resource.handlers.refresh || action$1.onClick || action$1.component);
7796
+ case "import": return canImport;
7797
+ case "export": return canExport;
7798
+ case "create": return can.create;
7799
+ }
7800
+ })()) return null;
7801
+ if (action$1.component) return /* @__PURE__ */ jsx(React.Fragment, { children: renderToolbarComponent(action$1.component) }, action$1.type);
7802
+ return renderBuiltinButton(action$1.type, {
7803
+ onClick: action$1.onClick,
7804
+ label: action$1.label
7805
+ });
7806
+ };
7807
+ const toolbarNodes = normalizedToolbarActions.filter((action$1) => action$1.type !== "refresh").map(renderToolbarAction).filter((node) => node !== null);
7808
+ const refreshNodes = normalizedToolbarActions.filter((action$1) => action$1.type === "refresh").map(renderToolbarAction);
7750
7809
  return /* @__PURE__ */ jsxs("div", {
7751
7810
  className: "space-y-4",
7752
7811
  children: [
@@ -7760,80 +7819,12 @@ function AutoCrudTable({ id, title, description, schema, resource, fields, table
7760
7819
  children: description
7761
7820
  })]
7762
7821
  }),
7763
- /* @__PURE__ */ jsx("div", {
7822
+ toolbarNodes.length > 0 && /* @__PURE__ */ jsx("div", {
7764
7823
  className: "flex items-center justify-end gap-4",
7765
- children: (() => {
7766
- const renderBuiltinButton = (type, overrides) => {
7767
- if (type === "refresh") {
7768
- const onRefresh = overrides?.onClick ?? toolbarRefresh;
7769
- const label = overrides?.label ?? locale.toolbar.refresh;
7770
- if (!onRefresh) return null;
7771
- return /* @__PURE__ */ jsx(Button, {
7772
- variant: "outline",
7773
- size: "icon-sm",
7774
- "aria-label": label,
7775
- title: label,
7776
- onClick: onRefresh,
7777
- disabled: resource.tableData.isFetching,
7778
- children: /* @__PURE__ */ jsx(RefreshCw, { className: resource.tableData.isFetching ? "h-4 w-4 animate-spin" : "h-4 w-4" })
7779
- }, "refresh");
7780
- }
7781
- if (type === "import" && canImport) return /* @__PURE__ */ jsxs(Button, {
7782
- variant: "outline",
7783
- size: "sm",
7784
- onClick: overrides?.onClick ?? toolbarOpenImport,
7785
- children: [/* @__PURE__ */ jsx(Download, { className: "mr-2 h-4 w-4" }), overrides?.label ?? locale.toolbar.import]
7786
- }, "import");
7787
- if (type === "export" && canExport) return /* @__PURE__ */ jsxs(Button, {
7788
- variant: "outline",
7789
- size: "sm",
7790
- onClick: overrides?.onClick ?? toolbarExportData,
7791
- disabled: exporting || selectedCount === 0 && !resource.handlers.export,
7792
- children: [exporting ? /* @__PURE__ */ jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" }) : /* @__PURE__ */ jsx(Upload, { className: "mr-2 h-4 w-4" }), overrides?.label ?? (selectedCount > 0 ? locale.toolbar.exportSelected(selectedCount) : locale.toolbar.export)]
7793
- }, "export");
7794
- if (type === "create" && can.create) return /* @__PURE__ */ jsx(Button, {
7795
- onClick: overrides?.onClick ?? toolbarOpenCreate,
7796
- children: overrides?.label ?? locale.toolbar.create
7797
- }, "create");
7798
- return null;
7799
- };
7800
- const renderToolbarComponent = (component) => typeof component === "function" ? component(toolbarContext) : component;
7801
- if (!resolvedToolbarActions || resolvedToolbarActions.length === 0) return /* @__PURE__ */ jsx("div", { className: "flex items-center gap-2" });
7802
- if (!resolvedToolbarActions.some((i) => i.type !== "custom")) {
7803
- const startNodes = resolvedToolbarActions.filter((i) => i.type === "custom" && i.position === "start").map((a, i) => /* @__PURE__ */ jsx(React.Fragment, { children: renderToolbarComponent(a.component) }, `start-${i}`));
7804
- const endNodes = resolvedToolbarActions.filter((i) => i.type === "custom" && i.position !== "start").map((a, i) => /* @__PURE__ */ jsx(React.Fragment, { children: renderToolbarComponent(a.component) }, `end-${i}`));
7805
- return /* @__PURE__ */ jsxs("div", {
7806
- className: "flex items-center gap-2",
7807
- children: [
7808
- startNodes,
7809
- renderBuiltinButton("refresh"),
7810
- renderBuiltinButton("import"),
7811
- renderBuiltinButton("export"),
7812
- renderBuiltinButton("create"),
7813
- endNodes
7814
- ]
7815
- });
7816
- }
7817
- return /* @__PURE__ */ jsx("div", {
7818
- className: "flex items-center gap-2",
7819
- children: resolvedToolbarActions.map((action$1, index$1) => {
7820
- if (action$1.type === "custom") return /* @__PURE__ */ jsx(React.Fragment, { children: renderToolbarComponent(action$1.component) }, `custom-${index$1}`);
7821
- if (!(() => {
7822
- switch (action$1.type) {
7823
- case "refresh": return !!(resource.handlers.refresh || action$1.onClick || action$1.component);
7824
- case "import": return canImport;
7825
- case "export": return canExport;
7826
- case "create": return can.create;
7827
- }
7828
- })()) return null;
7829
- if (action$1.component) return /* @__PURE__ */ jsx(React.Fragment, { children: renderToolbarComponent(action$1.component) }, action$1.type);
7830
- return renderBuiltinButton(action$1.type, {
7831
- onClick: action$1.onClick,
7832
- label: action$1.label
7833
- });
7834
- })
7835
- });
7836
- })()
7824
+ children: /* @__PURE__ */ jsx("div", {
7825
+ className: "flex items-center gap-2",
7826
+ children: toolbarNodes
7827
+ })
7837
7828
  }),
7838
7829
  /* @__PURE__ */ jsx(AutoTable, {
7839
7830
  data: resource.tableData.data,
@@ -7846,12 +7837,14 @@ function AutoCrudTable({ id, title, description, schema, resource, fields, table
7846
7837
  ...resource.tableData.total !== void 0 ? { total: resource.tableData.total } : {},
7847
7838
  getRowId: tableConfig?.getRowId,
7848
7839
  expandedRow: tableConfig?.expandedRow,
7840
+ defaultExpanded: tableConfig?.defaultExpanded,
7849
7841
  overrides: tableOverrides,
7850
7842
  classNames: tableConfig?.classNames,
7851
7843
  exclude: hiddenColumns.filter((key) => !filterOnly.includes(key)),
7852
7844
  filterOnly,
7853
7845
  filterMode: tableConfig?.filterModes,
7854
7846
  search: searchConfig,
7847
+ toolbarExtra: refreshNodes,
7855
7848
  actions: tableRowActions,
7856
7849
  onDeleteSelected: can.delete ? resource.handlers.deleteMany : void 0,
7857
7850
  onUpdateSelected: can.update ? resource.handlers.updateMany : void 0,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@wordrhyme/auto-crud",
3
3
  "type": "module",
4
- "version": "1.10.0",
4
+ "version": "1.11.1",
5
5
  "description": "Schema-first CRUD components with auto-generated tables and forms",
6
6
  "author": "wordrhyme",
7
7
  "license": "MIT",
@@ -70,9 +70,9 @@
70
70
  "nanoid": "^5.1.6",
71
71
  "tailwind-merge": "^3.5.0",
72
72
  "vaul": "^1.1.2",
73
+ "@wordrhyme/shadcn": "2.0.1",
73
74
  "@wordrhyme/formily-shadcn": "2.1.1",
74
- "@wordrhyme/shadcn-ui": "2.1.1",
75
- "@wordrhyme/shadcn": "2.0.1"
75
+ "@wordrhyme/shadcn-ui": "2.1.1"
76
76
  },
77
77
  "devDependencies": {
78
78
  "@storybook/react": "^8.6.18",
@@ -95,9 +95,9 @@
95
95
  "vitest": "^3.2.4",
96
96
  "@internal/eslint-config": "0.3.0",
97
97
  "@internal/prettier-config": "0.0.1",
98
- "@internal/vitest-config": "0.1.0",
99
98
  "@internal/tsdown-config": "0.1.0",
100
- "@internal/tsconfig": "0.1.0"
99
+ "@internal/tsconfig": "0.1.0",
100
+ "@internal/vitest-config": "0.1.0"
101
101
  },
102
102
  "prettier": "@internal/prettier-config",
103
103
  "scripts": {