@wordrhyme/auto-crud 1.11.2 → 1.12.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
@@ -4641,6 +4641,27 @@ function coerceRowValues(rows, schema) {
4641
4641
 
4642
4642
  //#endregion
4643
4643
  //#region src/lib/export.ts
4644
+ /** Snapshot rendered business columns without changing the legacy CSV column policy. */
4645
+ function getVisibleExportColumns(table, deny = []) {
4646
+ const excluded = new Set([
4647
+ "select",
4648
+ "actions",
4649
+ ...deny
4650
+ ]);
4651
+ return [
4652
+ ...table.getLeftVisibleLeafColumns(),
4653
+ ...table.getCenterVisibleLeafColumns(),
4654
+ ...table.getRightVisibleLeafColumns()
4655
+ ].filter((column) => {
4656
+ if (excluded.has(column.id)) return false;
4657
+ const meta = column.columnDef.meta;
4658
+ if (meta?.autoCrudExpandColumn) return false;
4659
+ return ![meta?.classNames?.th, meta?.classNames?.td].some((value) => typeof value === "string" && /(?:^|\s)!?hidden!?(?:\s|$)/.test(value));
4660
+ }).map((column) => ({
4661
+ id: column.id,
4662
+ label: column.columnDef.meta?.label ?? (typeof column.columnDef.header === "string" ? column.columnDef.header : column.id)
4663
+ }));
4664
+ }
4644
4665
  function getExportColumnIds(table) {
4645
4666
  return table.getAllLeafColumns().filter((column) => column.accessorFn).map((column) => column.id);
4646
4667
  }
@@ -5157,7 +5178,7 @@ const filterModeConfig = {
5157
5178
  tooltip: "Linear-style command filters"
5158
5179
  }
5159
5180
  };
5160
- function AutoTable({ schema, data, getRowId, expandedRow, defaultExpanded = false, pageCount = 1, pagination, total, overrides, classNames, enableRowSelection = true, exclude, filterOnly, actions, pinnedColumns, filterMode, search = false, toolbarExtra, locale = enUSTableControls, onDeleteSelected, onUpdateSelected, batchUpdateFields, actionBarExtra, actionBarActions, deleteConfirmation, initialSorting, enableExport = true, showDefaultExport = true, onSelectedCountChange, onSelectedRowsChange, getSelectedRows }) {
5181
+ function AutoTable({ schema, data, getRowId, expandedRow, defaultExpanded = false, pageCount = 1, pagination, total, overrides, classNames, enableRowSelection = true, exclude, filterOnly, actions, pinnedColumns, filterMode, search = false, toolbarExtra, locale = enUSTableControls, onDeleteSelected, onUpdateSelected, batchUpdateFields, actionBarExtra, actionBarActions, deleteConfirmation, initialSorting, enableExport = true, showDefaultExport = true, onSelectedCountChange, onSelectedRowsChange, getSelectedRows, exportTableRef }) {
5161
5182
  if (expandedRow && !getRowId) throw new Error("AutoTable requires getRowId when expandedRow is provided.");
5162
5183
  const enableExpansion = !!expandedRow;
5163
5184
  const modes = filterMode ? Array.isArray(filterMode) ? filterMode : [filterMode] : DEFAULT_MODES;
@@ -5199,6 +5220,7 @@ function AutoTable({ schema, data, getRowId, expandedRow, defaultExpanded = fals
5199
5220
  while (expandColumnId$1 in schema.shape || result.some((column) => column.id === expandColumnId$1)) expandColumnId$1 = `_${expandColumnId$1}`;
5200
5221
  result.unshift({
5201
5222
  id: expandColumnId$1,
5223
+ meta: { autoCrudExpandColumn: true },
5202
5224
  header: () => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5203
5225
  className: "sr-only",
5204
5226
  children: "Row details"
@@ -5272,6 +5294,13 @@ function AutoTable({ schema, data, getRowId, expandedRow, defaultExpanded = fals
5272
5294
  };
5273
5295
  });
5274
5296
  }, [table, filterOnly]);
5297
+ (0, react.useEffect)(() => {
5298
+ if (!exportTableRef) return;
5299
+ exportTableRef.current = table;
5300
+ return () => {
5301
+ exportTableRef.current = null;
5302
+ };
5303
+ }, [table, exportTableRef]);
5275
5304
  const rowSelection = table.getState().rowSelection;
5276
5305
  const columnFilters = table.getState().columnFilters;
5277
5306
  const selectedRows = (0, react.useMemo)(() => table.getFilteredSelectedRowModel().rows.map((row) => row.original), [
@@ -6830,6 +6859,9 @@ function resolveToolbarActionsWithResolver(targetId, ownerActions, context) {
6830
6859
  const resolver = autoCrudToolbarResolver;
6831
6860
  return targetId !== void 0 && targetId.length > 0 && resolver !== null ? resolver(targetId, ownerActions, context) : ownerActions;
6832
6861
  }
6862
+ function reportExportError(error) {
6863
+ sonner.toast.error(error instanceof Error ? error.message : String(error));
6864
+ }
6833
6865
  function applyResourceTablePresentation(fields, resourceFields) {
6834
6866
  if (!fields || !resourceFields) return fields;
6835
6867
  return Object.fromEntries(Object.entries(fields).map(([key, config]) => {
@@ -7709,7 +7741,7 @@ function resolveActions(actions, defaults, rowActionsLocale, context) {
7709
7741
  *
7710
7742
  * 高级 CRUD 表格组件,封装了完整的增删改查流程
7711
7743
  */
7712
- function AutoCrudTable({ id, title, description, schema, resource, fields, table: tableConfig, form: formConfig, view: viewConfig, permissions, actions: actionItems, toolbar, toolbarActions, locale: localeProp, onCreate }) {
7744
+ function AutoCrudTable({ id, title, description, schema, resource, fields, table: tableConfig, form: formConfig, view: viewConfig, permissions, actions: actionItems, toolbar, toolbarActions, locale: localeProp, onCreate, onExport }) {
7713
7745
  const locale = resolveLocale(localeProp);
7714
7746
  const getPagination = react.useCallback(() => id ? tablePagination.get(id) : void 0, [id]);
7715
7747
  const registeredPagination = react.useSyncExternalStore(tablePagination.subscribe, getPagination, getPagination);
@@ -7729,6 +7761,8 @@ function AutoCrudTable({ id, title, description, schema, resource, fields, table
7729
7761
  const [importOpen, setImportOpen] = react.useState(false);
7730
7762
  const [selectedRows, setSelectedRows] = react.useState([]);
7731
7763
  const [exporting, setExporting] = react.useState(false);
7764
+ const exportBusyRef = react.useRef(false);
7765
+ const exportTableRef = react.useRef(null);
7732
7766
  const getSelectedRowsRef = react.useRef(null);
7733
7767
  const dynamicFilterOptions = useDynamicFilterOptions(resolvedFields);
7734
7768
  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), [
@@ -7787,6 +7821,26 @@ function AutoCrudTable({ id, title, description, schema, resource, fields, table
7787
7821
  return Object.keys(shape).filter((key) => !denySet.has(key));
7788
7822
  }, [resolvedSchema, denyFields]);
7789
7823
  const handleExport = react.useCallback(async (mode) => {
7824
+ if (onExport) {
7825
+ if (exportBusyRef.current || !exportTableRef.current) return;
7826
+ exportBusyRef.current = true;
7827
+ setExporting(true);
7828
+ try {
7829
+ const denied = new Set(denyFields);
7830
+ const columns = getVisibleExportColumns(exportTableRef.current, denyFields);
7831
+ const rows = mode === "selected" ? getSelectedRowsRef.current?.() : await resource.handlers.export?.();
7832
+ if (!rows || mode === "selected" && rows.length === 0) return;
7833
+ await onExport({
7834
+ mode,
7835
+ rows: denied.size ? rows.map((row) => Object.fromEntries(Object.entries(row).filter(([key]) => !denied.has(key)))) : rows,
7836
+ columns
7837
+ });
7838
+ } finally {
7839
+ exportBusyRef.current = false;
7840
+ setExporting(false);
7841
+ }
7842
+ return;
7843
+ }
7790
7844
  const filename = title ?? "export";
7791
7845
  const excludeColumns = denyFields;
7792
7846
  if (mode === "selected") {
@@ -7807,18 +7861,30 @@ function AutoCrudTable({ id, title, description, schema, resource, fields, table
7807
7861
  resource.handlers.export,
7808
7862
  title,
7809
7863
  denyFields,
7810
- resolvedFields
7864
+ resolvedFields,
7865
+ onExport
7811
7866
  ]);
7812
7867
  const handleExportClick = react.useCallback(async () => {
7813
7868
  const mode = selectedCount > 0 ? "selected" : "filtered";
7869
+ if (onExport) return handleExport(mode);
7814
7870
  setExporting(true);
7815
7871
  try {
7816
7872
  await handleExport(mode);
7817
7873
  } finally {
7818
7874
  setExporting(false);
7819
7875
  }
7820
- }, [selectedCount, handleExport]);
7876
+ }, [
7877
+ selectedCount,
7878
+ handleExport,
7879
+ onExport
7880
+ ]);
7821
7881
  const exportBatchActions = react.useMemo(() => {
7882
+ if (onExport) return registryBatchActions.map((action$1) => action$1.type === "export" && action$1.onClick === void 0 && !action$1.component ? {
7883
+ ...action$1,
7884
+ onClick: () => {
7885
+ handleExport("selected").catch(reportExportError);
7886
+ }
7887
+ } : action$1);
7822
7888
  if (!Object.values(resolvedFields).some((field) => field.format)) return registryBatchActions;
7823
7889
  return registryBatchActions.map((action$1) => {
7824
7890
  if (action$1.type !== "export" || action$1.onClick !== void 0 || Boolean(action$1.component)) return action$1;
@@ -7836,7 +7902,9 @@ function AutoCrudTable({ id, title, description, schema, resource, fields, table
7836
7902
  }, [
7837
7903
  registryBatchActions,
7838
7904
  resolvedFields,
7839
- denyFields
7905
+ denyFields,
7906
+ onExport,
7907
+ handleExport
7840
7908
  ]);
7841
7909
  const toolbarRefresh = react.useMemo(() => {
7842
7910
  const refresh = ownerRefreshAction?.onClick ?? resource.handlers.refresh;
@@ -8013,7 +8081,9 @@ function AutoCrudTable({ id, title, description, schema, resource, fields, table
8013
8081
  if (type === "export" && canExport) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(__wordrhyme_shadcn.Button, {
8014
8082
  variant: "outline",
8015
8083
  size: "sm",
8016
- onClick: overrides?.onClick ?? toolbarExportData,
8084
+ onClick: overrides?.onClick ?? (!ownerExportAction?.onClick ? () => {
8085
+ toolbarExportData?.().catch(reportExportError);
8086
+ } : toolbarExportData),
8017
8087
  disabled: exporting || selectedCount === 0 && !resource.handlers.export,
8018
8088
  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)]
8019
8089
  }, "export");
@@ -8092,7 +8162,8 @@ function AutoCrudTable({ id, title, description, schema, resource, fields, table
8092
8162
  showDefaultExport: false,
8093
8163
  initialSorting: defaultSort,
8094
8164
  onSelectedRowsChange: handleSelectedRowsChange,
8095
- getSelectedRows: getSelectedRowsRef
8165
+ getSelectedRows: getSelectedRowsRef,
8166
+ ...onExport ? { exportTableRef } : {}
8096
8167
  }),
8097
8168
  rowActionDialog,
8098
8169
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CrudFormModal, {
@@ -8914,12 +8985,13 @@ function modalReducer(state, action$1) {
8914
8985
  */
8915
8986
  function useAutoCrudResource({ router, schema, query: queryTransform, fields, options = {} }) {
8916
8987
  const { idKey = "id", defaultVariant = "dialog", hooks, toast: toastAdapter, exportFetcher, defaultSort: defaultSortOption } = options;
8917
- const toast = (0, react.useMemo)(() => toastAdapter === false ? noopToastAdapter : toastAdapter ?? defaultToastAdapter, [toastAdapter]);
8988
+ const toast$1 = (0, react.useMemo)(() => toastAdapter === false ? noopToastAdapter : toastAdapter ?? defaultToastAdapter, [toastAdapter]);
8918
8989
  const hooksRef = (0, react.useRef)(hooks);
8919
8990
  (0, react.useEffect)(() => {
8920
8991
  hooksRef.current = hooks;
8921
8992
  }, [hooks]);
8922
- const metadata = (router.meta?.useQuery(void 0, { staleTime: 3e4 }))?.data;
8993
+ const metadataQuery = router.meta?.useQuery(void 0, { staleTime: 3e4 });
8994
+ const metadata = metadataQuery?.data;
8923
8995
  const resolvedSchema = (0, react.useMemo)(() => mergeMetadataSchema(schema, metadata?.schema), [schema, metadata?.schema]);
8924
8996
  const defaultSorting = (0, react.useMemo)(() => defaultSortOption === false ? [] : defaultSortOption ?? getDefaultSortingForSchema(resolvedSchema), [defaultSortOption, resolvedSchema]);
8925
8997
  const metadataFields = (0, react.useMemo)(() => readMetadataFields(metadata?.fields), [metadata?.fields]);
@@ -8940,6 +9012,10 @@ function useAutoCrudResource({ router, schema, query: queryTransform, fields, op
8940
9012
  search: parseAsString.withDefault("")
8941
9013
  }, { shallow: false });
8942
9014
  const [filters] = useReadableFilters(columns, { shallow: false });
9015
+ const metadataPending = Boolean(metadataQuery?.isPending);
9016
+ const [filtersReady, setFiltersReady] = (0, react.useState)(!metadataPending);
9017
+ (0, react.useEffect)(() => setFiltersReady(!metadataPending), [metadataPending]);
9018
+ const listReady = !metadataPending && filtersReady;
8943
9019
  const queryInput = (0, react.useMemo)(() => {
8944
9020
  const autoParams = {
8945
9021
  page: urlParams.page,
@@ -8955,11 +9031,17 @@ function useAutoCrudResource({ router, schema, query: queryTransform, fields, op
8955
9031
  filters,
8956
9032
  queryTransform
8957
9033
  ]);
9034
+ const hasReadyListData = (0, react.useRef)(false);
8958
9035
  const listQuery = router.list.useQuery(queryInput, {
8959
- placeholderData: __tanstack_react_query.keepPreviousData,
9036
+ enabled: listReady,
9037
+ placeholderData: hasReadyListData.current ? __tanstack_react_query.keepPreviousData : void 0,
8960
9038
  staleTime: 0
8961
9039
  });
8962
- const tableRows = listQuery.data?.data ?? [];
9040
+ (0, react.useEffect)(() => {
9041
+ if (listReady && listQuery.data !== void 0) hasReadyListData.current = true;
9042
+ }, [listReady, listQuery.data]);
9043
+ const listData = listReady ? listQuery.data : void 0;
9044
+ const tableRows = listData?.data ?? [];
8963
9045
  const createMutation = router.create.useMutation();
8964
9046
  const updateMutation = router.update.useMutation();
8965
9047
  const deleteMutation = router.delete.useMutation();
@@ -9006,7 +9088,7 @@ function useAutoCrudResource({ router, schema, query: queryTransform, fields, op
9006
9088
  const result = await currentHooks.beforeCreate(values);
9007
9089
  if (typeof result === "boolean") {
9008
9090
  if (result) {
9009
- toast.success("创建成功");
9091
+ toast$1.success("创建成功");
9010
9092
  closeModals();
9011
9093
  listQuery.refetch();
9012
9094
  currentHooks?.onSuccess?.("create", values);
@@ -9017,20 +9099,20 @@ function useAutoCrudResource({ router, schema, query: queryTransform, fields, op
9017
9099
  }
9018
9100
  createMutation.mutate(values, {
9019
9101
  onSuccess: (data) => {
9020
- toast.success("创建成功");
9102
+ toast$1.success("创建成功");
9021
9103
  closeModals();
9022
9104
  listQuery.refetch();
9023
9105
  currentHooks?.onSuccess?.("create", data);
9024
9106
  },
9025
9107
  onError: (error) => {
9026
9108
  const err = error;
9027
- toast.error(`创建失败: ${err.message}`);
9109
+ toast$1.error(`创建失败: ${err.message}`);
9028
9110
  currentHooks?.onError?.("create", err);
9029
9111
  }
9030
9112
  });
9031
9113
  } catch (error) {
9032
9114
  const err = error;
9033
- toast.error(`创建失败: ${err.message}`);
9115
+ toast$1.error(`创建失败: ${err.message}`);
9034
9116
  currentHooks?.onError?.("create", err);
9035
9117
  }
9036
9118
  }, [
@@ -9046,7 +9128,7 @@ function useAutoCrudResource({ router, schema, query: queryTransform, fields, op
9046
9128
  const result = await currentHooks.beforeUpdate(values, modal.selected);
9047
9129
  if (typeof result === "boolean") {
9048
9130
  if (result) {
9049
- toast.success("更新成功");
9131
+ toast$1.success("更新成功");
9050
9132
  closeModals();
9051
9133
  listQuery.refetch();
9052
9134
  currentHooks?.onSuccess?.("update", values);
@@ -9061,20 +9143,20 @@ function useAutoCrudResource({ router, schema, query: queryTransform, fields, op
9061
9143
  data: values
9062
9144
  }, {
9063
9145
  onSuccess: (data) => {
9064
- toast.success("更新成功");
9146
+ toast$1.success("更新成功");
9065
9147
  closeModals();
9066
9148
  listQuery.refetch();
9067
9149
  currentHooks?.onSuccess?.("update", data);
9068
9150
  },
9069
9151
  onError: (error) => {
9070
9152
  const err = error;
9071
- toast.error(`更新失败: ${err.message}`);
9153
+ toast$1.error(`更新失败: ${err.message}`);
9072
9154
  currentHooks?.onError?.("update", err);
9073
9155
  }
9074
9156
  });
9075
9157
  } catch (error) {
9076
9158
  const err = error;
9077
- toast.error(`更新失败: ${err.message}`);
9159
+ toast$1.error(`更新失败: ${err.message}`);
9078
9160
  currentHooks?.onError?.("update", err);
9079
9161
  }
9080
9162
  }, [
@@ -9092,7 +9174,7 @@ function useAutoCrudResource({ router, schema, query: queryTransform, fields, op
9092
9174
  const result = await currentHooks.beforeDelete(modal.selected);
9093
9175
  if (typeof result === "boolean") {
9094
9176
  if (result) {
9095
- toast.success("删除成功");
9177
+ toast$1.success("删除成功");
9096
9178
  closeModals();
9097
9179
  listQuery.refetch();
9098
9180
  currentHooks?.onSuccess?.("delete", modal.selected);
@@ -9103,20 +9185,20 @@ function useAutoCrudResource({ router, schema, query: queryTransform, fields, op
9103
9185
  const id = modal.selected[idKey];
9104
9186
  deleteMutation.mutate(id, {
9105
9187
  onSuccess: (data) => {
9106
- toast.success("删除成功");
9188
+ toast$1.success("删除成功");
9107
9189
  closeModals();
9108
9190
  listQuery.refetch();
9109
9191
  currentHooks?.onSuccess?.("delete", data);
9110
9192
  },
9111
9193
  onError: (error) => {
9112
9194
  const err = error;
9113
- toast.error(`删除失败: ${err.message}`);
9195
+ toast$1.error(`删除失败: ${err.message}`);
9114
9196
  currentHooks?.onError?.("delete", err);
9115
9197
  }
9116
9198
  });
9117
9199
  } catch (error) {
9118
9200
  const err = error;
9119
- toast.error(`删除失败: ${err.message}`);
9201
+ toast$1.error(`删除失败: ${err.message}`);
9120
9202
  currentHooks?.onError?.("delete", err);
9121
9203
  }
9122
9204
  }, [
@@ -9129,19 +9211,19 @@ function useAutoCrudResource({ router, schema, query: queryTransform, fields, op
9129
9211
  const deleteMany = (0, react.useCallback)((rows) => {
9130
9212
  const currentHooks = hooksRef.current;
9131
9213
  if (!deleteManyMutation) {
9132
- toast.error("批量删除功能未启用");
9214
+ toast$1.error("批量删除功能未启用");
9133
9215
  return;
9134
9216
  }
9135
9217
  const ids = rows.map((row) => row[idKey]);
9136
9218
  deleteManyMutation.mutate(ids, {
9137
9219
  onSuccess: (data) => {
9138
- toast.success(`成功删除 ${ids.length} 条记录`);
9220
+ toast$1.success(`成功删除 ${ids.length} 条记录`);
9139
9221
  listQuery.refetch();
9140
9222
  currentHooks?.onSuccess?.("delete", data);
9141
9223
  },
9142
9224
  onError: (error) => {
9143
9225
  const err = error;
9144
- toast.error(`批量删除失败: ${err.message}`);
9226
+ toast$1.error(`批量删除失败: ${err.message}`);
9145
9227
  currentHooks?.onError?.("delete", err);
9146
9228
  }
9147
9229
  });
@@ -9153,7 +9235,7 @@ function useAutoCrudResource({ router, schema, query: queryTransform, fields, op
9153
9235
  const updateMany = (0, react.useCallback)((rows, data) => {
9154
9236
  const currentHooks = hooksRef.current;
9155
9237
  if (!updateManyMutation) {
9156
- toast.error("批量更新功能未启用");
9238
+ toast$1.error("批量更新功能未启用");
9157
9239
  return;
9158
9240
  }
9159
9241
  const ids = rows.map((row) => row[idKey]);
@@ -9162,13 +9244,13 @@ function useAutoCrudResource({ router, schema, query: queryTransform, fields, op
9162
9244
  data
9163
9245
  }, {
9164
9246
  onSuccess: (result) => {
9165
- toast.success(`成功更新 ${ids.length} 条记录`);
9247
+ toast$1.success(`成功更新 ${ids.length} 条记录`);
9166
9248
  listQuery.refetch();
9167
9249
  currentHooks?.onSuccess?.("update", result);
9168
9250
  },
9169
9251
  onError: (error) => {
9170
9252
  const err = error;
9171
- toast.error(`批量更新失败: ${err.message}`);
9253
+ toast$1.error(`批量更新失败: ${err.message}`);
9172
9254
  currentHooks?.onError?.("update", err);
9173
9255
  }
9174
9256
  });
@@ -9198,12 +9280,18 @@ function useAutoCrudResource({ router, schema, query: queryTransform, fields, op
9198
9280
  schema
9199
9281
  ]);
9200
9282
  const handleExport = (0, react.useCallback)(async () => {
9283
+ let result;
9201
9284
  if (exportFetcher) {
9202
9285
  const { page: _p, perPage: _pp,...exportParams } = queryInput ?? {};
9203
- return (await exportFetcher(exportParams))?.data ?? [];
9204
- }
9205
- if (exportQuery) return (await exportQuery.refetch())?.data?.data ?? [];
9206
- throw new Error("Export not supported");
9286
+ result = await exportFetcher(exportParams);
9287
+ } else if (exportQuery) {
9288
+ const response = await exportQuery.refetch();
9289
+ if (response.error) throw response.error;
9290
+ result = response.data;
9291
+ } else throw new Error("Export not supported");
9292
+ if (!result || !Array.isArray(result.data)) throw new Error("Export returned no data. Please retry.");
9293
+ if (result.hasMore !== false || result.total > result.data.length) throw new Error("Export results are incomplete. Narrow your filters and retry.");
9294
+ return result.data;
9207
9295
  }, [
9208
9296
  exportFetcher,
9209
9297
  exportQuery,
@@ -9211,16 +9299,17 @@ function useAutoCrudResource({ router, schema, query: queryTransform, fields, op
9211
9299
  ]);
9212
9300
  const canExport = !!(exportFetcher || exportQuery);
9213
9301
  const refresh = (0, react.useCallback)(async () => {
9302
+ if (!listReady) return;
9214
9303
  return listQuery.refetch();
9215
- }, [listQuery]);
9304
+ }, [listQuery, listReady]);
9216
9305
  return {
9217
9306
  idKey,
9218
9307
  tableData: {
9219
9308
  data: tableRows,
9220
- pageCount: listQuery.data?.pageCount ?? 0,
9221
- ...typeof listQuery.data?.total === "number" ? { total: listQuery.data.total } : {},
9222
- isLoading: listQuery.isLoading,
9223
- isFetching: listQuery.isFetching
9309
+ pageCount: listData?.pageCount ?? 0,
9310
+ ...typeof listData?.total === "number" ? { total: listData.total } : {},
9311
+ isLoading: !listReady || listQuery.isLoading,
9312
+ isFetching: !listReady || listQuery.isFetching
9224
9313
  },
9225
9314
  schema: resolvedSchema,
9226
9315
  fields: metadataFields,
@@ -9557,6 +9646,72 @@ function createMemoryDataSource(initialData = []) {
9557
9646
  };
9558
9647
  }
9559
9648
 
9649
+ //#endregion
9650
+ //#region src/lib/workbook.ts
9651
+ const MAX_ROWS = 1048576;
9652
+ const MAX_COLUMNS = 16384;
9653
+ const MAX_CELL_LENGTH = 32767;
9654
+ const MAX_LINE_FEEDS = 253;
9655
+ function validateCell(value) {
9656
+ if (typeof value === "string") {
9657
+ if (value.length > MAX_CELL_LENGTH) throw new RangeError(`Excel cells cannot exceed ${MAX_CELL_LENGTH} characters.`);
9658
+ if ((value.match(/\n/g)?.length ?? 0) > MAX_LINE_FEEDS) throw new RangeError(`Excel cells cannot exceed ${MAX_LINE_FEEDS} line feeds.`);
9659
+ } else if (value != null && (typeof value !== "number" || !Number.isFinite(value))) throw new TypeError("Workbook cells must contain text, finite numbers, or null.");
9660
+ }
9661
+ function validateSheets(sheets) {
9662
+ if (sheets.length === 0) throw new Error("A workbook needs at least one sheet.");
9663
+ const names = /* @__PURE__ */ new Set();
9664
+ for (const sheet of sheets) {
9665
+ if (!sheet.name || sheet.name.length > 31 || /[*?:\\/\[\]]/.test(sheet.name) || sheet.name.startsWith("'") || sheet.name.endsWith("'")) throw new Error("Worksheet names must be valid Excel names of 1–31 characters.");
9666
+ const name = sheet.name.toLowerCase();
9667
+ if (names.has(name)) throw new Error("Worksheet names must be unique.");
9668
+ names.add(name);
9669
+ if (sheet.rows.length + 1 > MAX_ROWS) throw new RangeError(`Excel worksheets cannot exceed ${MAX_ROWS} rows including headers.`);
9670
+ if (sheet.headers.length > MAX_COLUMNS) throw new RangeError(`Excel worksheets cannot exceed ${MAX_COLUMNS} columns.`);
9671
+ sheet.headers.forEach(validateCell);
9672
+ for (const row of sheet.rows) {
9673
+ if (row.length > MAX_COLUMNS) throw new RangeError(`Excel worksheets cannot exceed ${MAX_COLUMNS} columns.`);
9674
+ row.forEach(validateCell);
9675
+ }
9676
+ }
9677
+ }
9678
+ /** Internal byte builder, exported from this module for real workbook roundtrip tests. */
9679
+ async function buildWorkbookBytes(sheets) {
9680
+ validateSheets(sheets);
9681
+ const { default: ExcelJS } = await import("exceljs");
9682
+ const workbook = new ExcelJS.Workbook();
9683
+ for (const sheet of sheets) {
9684
+ const worksheet = workbook.addWorksheet(sheet.name);
9685
+ const append = (values) => {
9686
+ worksheet.addRow(values.map((value) => value ?? null)).eachCell((cell, column) => {
9687
+ const numberFormat = sheet.numberFormats?.[column - 1];
9688
+ if (typeof cell.value === "number" && numberFormat !== void 0) cell.numFmt = numberFormat;
9689
+ if (typeof cell.value === "string" && /[\r\n]/.test(cell.value)) cell.alignment = { wrapText: true };
9690
+ });
9691
+ };
9692
+ append(sheet.headers);
9693
+ sheet.rows.forEach(append);
9694
+ }
9695
+ return new Uint8Array(await workbook.xlsx.writeBuffer()).buffer;
9696
+ }
9697
+ /** Download a real XLSX workbook. Errors reject; no CSV or raw-value fallback. */
9698
+ async function exportWorkbook(sheets, { filename }) {
9699
+ const bytes = await buildWorkbookBytes(sheets);
9700
+ const blob = new Blob([bytes], { type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" });
9701
+ const url = URL.createObjectURL(blob);
9702
+ const link = document.createElement("a");
9703
+ try {
9704
+ link.href = url;
9705
+ link.download = /\.xlsx$/i.test(filename) ? filename : `${filename}.xlsx`;
9706
+ link.hidden = true;
9707
+ document.body.appendChild(link);
9708
+ link.click();
9709
+ } finally {
9710
+ link.remove();
9711
+ setTimeout(() => URL.revokeObjectURL(url), 0);
9712
+ }
9713
+ }
9714
+
9560
9715
  //#endregion
9561
9716
  exports.AutoCrudTable = AutoCrudTable;
9562
9717
  exports.AutoForm = AutoForm;
@@ -9607,6 +9762,7 @@ exports.enUS = enUS;
9607
9762
  exports.esES = esES;
9608
9763
  exports.exportAllToCSV = exportAllToCSV;
9609
9764
  exports.exportTableToCSV = exportTableToCSV;
9765
+ exports.exportWorkbook = exportWorkbook;
9610
9766
  exports.formComponents = formComponents;
9611
9767
  exports.formatDate = formatDate;
9612
9768
  exports.frFR = frFR;
package/dist/index.d.cts CHANGED
@@ -276,6 +276,8 @@ declare module '@tanstack/react-table' {
276
276
  interface ColumnMeta<TData extends RowData, TValue> {
277
277
  label?: string;
278
278
  index?: number;
279
+ /** @internal Identifies the generated row expansion control, regardless of its id. */
280
+ autoCrudExpandColumn?: boolean;
279
281
  /** 按元素分组的列类名,优先于全局单元格类名 */
280
282
  classNames?: Pick<DataTableClassNames, 'th' | 'td'>;
281
283
  placeholder?: string;
@@ -1059,6 +1061,8 @@ interface AutoTableProps<T extends z.ZodObject<z.ZodRawShape>> extends Pick<Data
1059
1061
  onSelectedCountChange?: (count: number) => void;
1060
1062
  /** 选中行数据变化回调(用于外层组件获知选中行身份变化) */
1061
1063
  onSelectedRowsChange?: (rows: z.infer<T>[]) => void;
1064
+ /** @internal Supplies the current table for opt-in export snapshots. */
1065
+ exportTableRef?: React.MutableRefObject<Table<z.infer<T>> | null>;
1062
1066
  /** 获取选中行数据的回调(由外层组件调用) */
1063
1067
  getSelectedRows?: React.MutableRefObject<(() => z.infer<T>[]) | null>;
1064
1068
  }
@@ -1093,7 +1097,8 @@ declare function AutoTable<T extends z.ZodObject<z.ZodRawShape>>({
1093
1097
  showDefaultExport,
1094
1098
  onSelectedCountChange,
1095
1099
  onSelectedRowsChange,
1096
- getSelectedRows
1100
+ getSelectedRows,
1101
+ exportTableRef
1097
1102
  }: AutoTableProps<T>): react_jsx_runtime9.JSX.Element;
1098
1103
  //#endregion
1099
1104
  //#region src/lib/row-action-dialog.d.ts
@@ -1405,9 +1410,15 @@ interface AutoCrudToolbarContext {
1405
1410
  }
1406
1411
  type AutoCrudToolbarResolver = (targetId: string, ownerActions: readonly ToolbarActionItem[], context: AutoCrudToolbarContext) => ToolbarActionItem[];
1407
1412
  declare function setToolbarResolver(resolver: AutoCrudToolbarResolver | null): void;
1408
- /**
1409
- * AutoCrudTable Props 接口
1410
- */
1413
+ /** Unformatted rows without denied fields, and visible localized business columns. */
1414
+ interface AutoCrudExportContext {
1415
+ mode: 'selected' | 'filtered';
1416
+ rows: Record<string, unknown>[];
1417
+ columns: Array<{
1418
+ id: string;
1419
+ label: string;
1420
+ }>;
1421
+ }
1411
1422
  interface AutoCrudTableProps<TSchema extends z.ZodObject<z.ZodRawShape>> {
1412
1423
  /** Public CRUD id. Used by hosts to document the extension target. */
1413
1424
  id?: string;
@@ -1526,6 +1537,8 @@ interface AutoCrudTableProps<TSchema extends z.ZodObject<z.ZodRawShape>> {
1526
1537
  * 如果提供,将覆盖默认的打开新建弹窗行为
1527
1538
  */
1528
1539
  onCreate?: () => void;
1540
+ /** Owns export delivery when supplied. Rejections propagate without CSV fallback. */
1541
+ onExport?: (context: AutoCrudExportContext) => Promise<void>;
1529
1542
  }
1530
1543
  /**
1531
1544
  * AutoCrudTable 组件
@@ -1547,7 +1560,8 @@ declare function AutoCrudTable<TSchema extends z.ZodObject<z.ZodRawShape>>({
1547
1560
  toolbar,
1548
1561
  toolbarActions,
1549
1562
  locale: localeProp,
1550
- onCreate
1563
+ onCreate,
1564
+ onExport
1551
1565
  }: AutoCrudTableProps<TSchema>): react_jsx_runtime9.JSX.Element;
1552
1566
  //#endregion
1553
1567
  //#region src/components/auto-crud/auto-form.d.ts
@@ -2163,4 +2177,19 @@ declare function exportAllToCSV<T extends Record<string, unknown>>(data: T[], op
2163
2177
  */
2164
2178
  declare function downloadCSVTemplate(headers: string[], filename?: string): void;
2165
2179
  //#endregion
2166
- export { type ActionConfig, type ActionItem, type ActionPosition, type ActionsColumnConfig, type AutoCrudActionConfig, type AutoCrudActionsConfig, type AutoCrudDataSourceConfig, type AutoCrudDataSourceContext, type AutoCrudDataSourceEntry, type AutoCrudDataSourceLoader, type AutoCrudDataSourceRegistration, type AutoCrudFormComponentConfig, type AutoCrudLocale, type AutoCrudOption, type AutoCrudQueryCapabilities, type AutoCrudRowActionContext, type AutoCrudRowOpenOptions, AutoCrudTable, type AutoCrudTableProps, type AutoCrudToolbarContext, type AutoCrudToolbarResolver, AutoForm, type AutoFormProps, type AutoQueryParams, AutoTable, AutoTableActionBar, type AutoTableProps, AutoTableSimpleFilters, type BatchActionConfig, type BatchActionContext, type BatchActionItem, type BatchBuiltinActionItem, type BatchBuiltinActionType, type BatchCustomActionItem, type BatchUpdateField, type ColumnOverrides, type CreateFormSchemaOptions, type CreateTableSchemaOptions, type CrudActionBase, type CrudActionEntry, type CrudActionRegistration, type CrudActionZone, CrudFormModal, type CrudHooks, type CrudOperationPermissions, type CrudPermissions, type DataSource, DataTable, DataTableAdvancedToolbar, DataTableClassNames, DataTableColumnHeader, DataTableFacetedFilter, DataTablePagination, type DataTableProps, DataTableRowAction, DataTableToolbar, DataTableViewOptions, type DataTableViewOptionsProps, type DateFormatPreset, type DateFormatter, type DateLocaleOptions, type EnumOption, ExportDialog, type ExportDialogProps, type ExportMode, ExtendedColumnFilter, ExtendedColumnSort, type Field, type FieldOption, type FieldTableDisplay, type FieldType, type Fields, type FilterConfig, FilterOperator, type FilterVariant, type FormSchemaOverrides, ImportDialog, type ImportDialogProps, type ImportResult, type JSONSchema, type JSONSchemaProperty, JoinOperator, type ListParams, type ListResult, type LocaleProp, MultiCombobox, type MultiComboboxOption, type MultiComboboxProps, type MultiComboboxTriggerRenderProps, Option, type ParsedImportData, type ParsedZodField, type Parser, QueryKeys, type RowActionConfig, type RowActionDialogProps, type RowActionItem, type RowBuiltinActionItem, type RowBuiltinActionType, type RowCustomActionItem, SchemaAdapter, Select, type SelectMode, type SelectOption, type SelectProps, type SelectSearchableDynamicProps, type SelectSearchableMultipleProps, type SelectSearchableProps, type SelectSearchableSingleProps, type SelectSimpleProps, type SelectTriggerRenderProps, type SimpleFieldConfig, type SimpleFieldsConfig, type TableControlsLocale, type TablePaginationOptions, type ToastAdapter, type ToolbarActionConfig, type ToolbarActionItem, type ToolbarBuiltinActionItem, type ToolbarBuiltinActionType, type ToolbarCustomActionItem, type UnifiedField, type UnifiedSchema, type UrlStateOptions, type UseAutoCrudResourceOptions, type UseAutoCrudResourceParams, type UseAutoCrudResourceReturn, actionPositionSchema, cn, coerceRowValues, compareOrder, components, createActionsColumn, createEditFormSchema, createFormSchema, createMemoryDataSource, createSelectColumn, createTRPCDataSource, createTableSchema, crudActions, dataSources, dataToCSV, deDE, downloadCSVTemplate, enUS, esES, exportAllToCSV, exportTableToCSV, formComponents, formatDate, frFR, generateCSVTemplate, getUrlParams, humanize, jaJP, koKR, matchesPosition, noopToastAdapter, normalizeDataSourceConfig, normalizeOptions, parseAsArrayOf, parseAsInteger, parseAsString, parseAsStringEnum, parseCSV, parseImportFile, parseJSON, parseZodField, positionActions, resolveLocale, setDateFormatter, setSearchParams, setToolbarResolver, tablePagination, useAutoCrudResource, useDataTable, useQueryState, useQueryStates, useReadableFilters, useUrlState, useUrlStates, zhCN };
2180
+ //#region src/lib/workbook.d.ts
2181
+ interface WorkbookSheet {
2182
+ name: string;
2183
+ headers: string[];
2184
+ /** Excel formats by header index; applied only to numeric cells. */
2185
+ numberFormats?: (string | undefined)[];
2186
+ rows: (string | number | null | undefined)[][];
2187
+ }
2188
+ /** Download a real XLSX workbook. Errors reject; no CSV or raw-value fallback. */
2189
+ declare function exportWorkbook(sheets: WorkbookSheet[], {
2190
+ filename
2191
+ }: {
2192
+ filename: string;
2193
+ }): Promise<void>;
2194
+ //#endregion
2195
+ export { type ActionConfig, type ActionItem, type ActionPosition, type ActionsColumnConfig, type AutoCrudActionConfig, type AutoCrudActionsConfig, type AutoCrudDataSourceConfig, type AutoCrudDataSourceContext, type AutoCrudDataSourceEntry, type AutoCrudDataSourceLoader, type AutoCrudDataSourceRegistration, type AutoCrudExportContext, type AutoCrudFormComponentConfig, type AutoCrudLocale, type AutoCrudOption, type AutoCrudQueryCapabilities, type AutoCrudRowActionContext, type AutoCrudRowOpenOptions, AutoCrudTable, type AutoCrudTableProps, type AutoCrudToolbarContext, type AutoCrudToolbarResolver, AutoForm, type AutoFormProps, type AutoQueryParams, AutoTable, AutoTableActionBar, type AutoTableProps, AutoTableSimpleFilters, type BatchActionConfig, type BatchActionContext, type BatchActionItem, type BatchBuiltinActionItem, type BatchBuiltinActionType, type BatchCustomActionItem, type BatchUpdateField, type ColumnOverrides, type CreateFormSchemaOptions, type CreateTableSchemaOptions, type CrudActionBase, type CrudActionEntry, type CrudActionRegistration, type CrudActionZone, CrudFormModal, type CrudHooks, type CrudOperationPermissions, type CrudPermissions, type DataSource, DataTable, DataTableAdvancedToolbar, DataTableClassNames, DataTableColumnHeader, DataTableFacetedFilter, DataTablePagination, type DataTableProps, DataTableRowAction, DataTableToolbar, DataTableViewOptions, type DataTableViewOptionsProps, type DateFormatPreset, type DateFormatter, type DateLocaleOptions, type EnumOption, ExportDialog, type ExportDialogProps, type ExportMode, ExtendedColumnFilter, ExtendedColumnSort, type Field, type FieldOption, type FieldTableDisplay, type FieldType, type Fields, type FilterConfig, FilterOperator, type FilterVariant, type FormSchemaOverrides, ImportDialog, type ImportDialogProps, type ImportResult, type JSONSchema, type JSONSchemaProperty, JoinOperator, type ListParams, type ListResult, type LocaleProp, MultiCombobox, type MultiComboboxOption, type MultiComboboxProps, type MultiComboboxTriggerRenderProps, Option, type ParsedImportData, type ParsedZodField, type Parser, QueryKeys, type RowActionConfig, type RowActionDialogProps, type RowActionItem, type RowBuiltinActionItem, type RowBuiltinActionType, type RowCustomActionItem, SchemaAdapter, Select, type SelectMode, type SelectOption, type SelectProps, type SelectSearchableDynamicProps, type SelectSearchableMultipleProps, type SelectSearchableProps, type SelectSearchableSingleProps, type SelectSimpleProps, type SelectTriggerRenderProps, type SimpleFieldConfig, type SimpleFieldsConfig, type TableControlsLocale, type TablePaginationOptions, type ToastAdapter, type ToolbarActionConfig, type ToolbarActionItem, type ToolbarBuiltinActionItem, type ToolbarBuiltinActionType, type ToolbarCustomActionItem, type UnifiedField, type UnifiedSchema, type UrlStateOptions, type UseAutoCrudResourceOptions, type UseAutoCrudResourceParams, type UseAutoCrudResourceReturn, type WorkbookSheet, actionPositionSchema, cn, coerceRowValues, compareOrder, components, createActionsColumn, createEditFormSchema, createFormSchema, createMemoryDataSource, createSelectColumn, createTRPCDataSource, createTableSchema, crudActions, dataSources, dataToCSV, deDE, downloadCSVTemplate, enUS, esES, exportAllToCSV, exportTableToCSV, exportWorkbook, formComponents, formatDate, frFR, generateCSVTemplate, getUrlParams, humanize, jaJP, koKR, matchesPosition, noopToastAdapter, normalizeDataSourceConfig, normalizeOptions, parseAsArrayOf, parseAsInteger, parseAsString, parseAsStringEnum, parseCSV, parseImportFile, parseJSON, parseZodField, positionActions, resolveLocale, setDateFormatter, setSearchParams, setToolbarResolver, tablePagination, useAutoCrudResource, useDataTable, useQueryState, useQueryStates, useReadableFilters, useUrlState, useUrlStates, zhCN };
package/dist/index.d.ts CHANGED
@@ -276,6 +276,8 @@ declare module '@tanstack/react-table' {
276
276
  interface ColumnMeta<TData extends RowData, TValue> {
277
277
  label?: string;
278
278
  index?: number;
279
+ /** @internal Identifies the generated row expansion control, regardless of its id. */
280
+ autoCrudExpandColumn?: boolean;
279
281
  /** 按元素分组的列类名,优先于全局单元格类名 */
280
282
  classNames?: Pick<DataTableClassNames, 'th' | 'td'>;
281
283
  placeholder?: string;
@@ -1059,6 +1061,8 @@ interface AutoTableProps<T extends z.ZodObject<z.ZodRawShape>> extends Pick<Data
1059
1061
  onSelectedCountChange?: (count: number) => void;
1060
1062
  /** 选中行数据变化回调(用于外层组件获知选中行身份变化) */
1061
1063
  onSelectedRowsChange?: (rows: z.infer<T>[]) => void;
1064
+ /** @internal Supplies the current table for opt-in export snapshots. */
1065
+ exportTableRef?: React.MutableRefObject<Table<z.infer<T>> | null>;
1062
1066
  /** 获取选中行数据的回调(由外层组件调用) */
1063
1067
  getSelectedRows?: React.MutableRefObject<(() => z.infer<T>[]) | null>;
1064
1068
  }
@@ -1093,7 +1097,8 @@ declare function AutoTable<T extends z.ZodObject<z.ZodRawShape>>({
1093
1097
  showDefaultExport,
1094
1098
  onSelectedCountChange,
1095
1099
  onSelectedRowsChange,
1096
- getSelectedRows
1100
+ getSelectedRows,
1101
+ exportTableRef
1097
1102
  }: AutoTableProps<T>): react_jsx_runtime8.JSX.Element;
1098
1103
  //#endregion
1099
1104
  //#region src/lib/row-action-dialog.d.ts
@@ -1405,9 +1410,15 @@ interface AutoCrudToolbarContext {
1405
1410
  }
1406
1411
  type AutoCrudToolbarResolver = (targetId: string, ownerActions: readonly ToolbarActionItem[], context: AutoCrudToolbarContext) => ToolbarActionItem[];
1407
1412
  declare function setToolbarResolver(resolver: AutoCrudToolbarResolver | null): void;
1408
- /**
1409
- * AutoCrudTable Props 接口
1410
- */
1413
+ /** Unformatted rows without denied fields, and visible localized business columns. */
1414
+ interface AutoCrudExportContext {
1415
+ mode: 'selected' | 'filtered';
1416
+ rows: Record<string, unknown>[];
1417
+ columns: Array<{
1418
+ id: string;
1419
+ label: string;
1420
+ }>;
1421
+ }
1411
1422
  interface AutoCrudTableProps<TSchema extends z.ZodObject<z.ZodRawShape>> {
1412
1423
  /** Public CRUD id. Used by hosts to document the extension target. */
1413
1424
  id?: string;
@@ -1526,6 +1537,8 @@ interface AutoCrudTableProps<TSchema extends z.ZodObject<z.ZodRawShape>> {
1526
1537
  * 如果提供,将覆盖默认的打开新建弹窗行为
1527
1538
  */
1528
1539
  onCreate?: () => void;
1540
+ /** Owns export delivery when supplied. Rejections propagate without CSV fallback. */
1541
+ onExport?: (context: AutoCrudExportContext) => Promise<void>;
1529
1542
  }
1530
1543
  /**
1531
1544
  * AutoCrudTable 组件
@@ -1547,7 +1560,8 @@ declare function AutoCrudTable<TSchema extends z.ZodObject<z.ZodRawShape>>({
1547
1560
  toolbar,
1548
1561
  toolbarActions,
1549
1562
  locale: localeProp,
1550
- onCreate
1563
+ onCreate,
1564
+ onExport
1551
1565
  }: AutoCrudTableProps<TSchema>): react_jsx_runtime8.JSX.Element;
1552
1566
  //#endregion
1553
1567
  //#region src/components/auto-crud/auto-form.d.ts
@@ -2163,4 +2177,19 @@ declare function exportAllToCSV<T extends Record<string, unknown>>(data: T[], op
2163
2177
  */
2164
2178
  declare function downloadCSVTemplate(headers: string[], filename?: string): void;
2165
2179
  //#endregion
2166
- export { type ActionConfig, type ActionItem, type ActionPosition, type ActionsColumnConfig, type AutoCrudActionConfig, type AutoCrudActionsConfig, type AutoCrudDataSourceConfig, type AutoCrudDataSourceContext, type AutoCrudDataSourceEntry, type AutoCrudDataSourceLoader, type AutoCrudDataSourceRegistration, type AutoCrudFormComponentConfig, type AutoCrudLocale, type AutoCrudOption, type AutoCrudQueryCapabilities, type AutoCrudRowActionContext, type AutoCrudRowOpenOptions, AutoCrudTable, type AutoCrudTableProps, type AutoCrudToolbarContext, type AutoCrudToolbarResolver, AutoForm, type AutoFormProps, type AutoQueryParams, AutoTable, AutoTableActionBar, type AutoTableProps, AutoTableSimpleFilters, type BatchActionConfig, type BatchActionContext, type BatchActionItem, type BatchBuiltinActionItem, type BatchBuiltinActionType, type BatchCustomActionItem, type BatchUpdateField, type ColumnOverrides, type CreateFormSchemaOptions, type CreateTableSchemaOptions, type CrudActionBase, type CrudActionEntry, type CrudActionRegistration, type CrudActionZone, CrudFormModal, type CrudHooks, type CrudOperationPermissions, type CrudPermissions, type DataSource, DataTable, DataTableAdvancedToolbar, DataTableClassNames, DataTableColumnHeader, DataTableFacetedFilter, DataTablePagination, type DataTableProps, DataTableRowAction, DataTableToolbar, DataTableViewOptions, type DataTableViewOptionsProps, type DateFormatPreset, type DateFormatter, type DateLocaleOptions, type EnumOption, ExportDialog, type ExportDialogProps, type ExportMode, ExtendedColumnFilter, ExtendedColumnSort, type Field, type FieldOption, type FieldTableDisplay, type FieldType, type Fields, type FilterConfig, FilterOperator, type FilterVariant, type FormSchemaOverrides, ImportDialog, type ImportDialogProps, type ImportResult, type JSONSchema, type JSONSchemaProperty, JoinOperator, type ListParams, type ListResult, type LocaleProp, MultiCombobox, type MultiComboboxOption, type MultiComboboxProps, type MultiComboboxTriggerRenderProps, Option, type ParsedImportData, type ParsedZodField, type Parser, QueryKeys, type RowActionConfig, type RowActionDialogProps, type RowActionItem, type RowBuiltinActionItem, type RowBuiltinActionType, type RowCustomActionItem, SchemaAdapter, Select, type SelectMode, type SelectOption, type SelectProps, type SelectSearchableDynamicProps, type SelectSearchableMultipleProps, type SelectSearchableProps, type SelectSearchableSingleProps, type SelectSimpleProps, type SelectTriggerRenderProps, type SimpleFieldConfig, type SimpleFieldsConfig, type TableControlsLocale, type TablePaginationOptions, type ToastAdapter, type ToolbarActionConfig, type ToolbarActionItem, type ToolbarBuiltinActionItem, type ToolbarBuiltinActionType, type ToolbarCustomActionItem, type UnifiedField, type UnifiedSchema, type UrlStateOptions, type UseAutoCrudResourceOptions, type UseAutoCrudResourceParams, type UseAutoCrudResourceReturn, actionPositionSchema, cn, coerceRowValues, compareOrder, components, createActionsColumn, createEditFormSchema, createFormSchema, createMemoryDataSource, createSelectColumn, createTRPCDataSource, createTableSchema, crudActions, dataSources, dataToCSV, deDE, downloadCSVTemplate, enUS, esES, exportAllToCSV, exportTableToCSV, formComponents, formatDate, frFR, generateCSVTemplate, getUrlParams, humanize, jaJP, koKR, matchesPosition, noopToastAdapter, normalizeDataSourceConfig, normalizeOptions, parseAsArrayOf, parseAsInteger, parseAsString, parseAsStringEnum, parseCSV, parseImportFile, parseJSON, parseZodField, positionActions, resolveLocale, setDateFormatter, setSearchParams, setToolbarResolver, tablePagination, useAutoCrudResource, useDataTable, useQueryState, useQueryStates, useReadableFilters, useUrlState, useUrlStates, zhCN };
2180
+ //#region src/lib/workbook.d.ts
2181
+ interface WorkbookSheet {
2182
+ name: string;
2183
+ headers: string[];
2184
+ /** Excel formats by header index; applied only to numeric cells. */
2185
+ numberFormats?: (string | undefined)[];
2186
+ rows: (string | number | null | undefined)[][];
2187
+ }
2188
+ /** Download a real XLSX workbook. Errors reject; no CSV or raw-value fallback. */
2189
+ declare function exportWorkbook(sheets: WorkbookSheet[], {
2190
+ filename
2191
+ }: {
2192
+ filename: string;
2193
+ }): Promise<void>;
2194
+ //#endregion
2195
+ export { type ActionConfig, type ActionItem, type ActionPosition, type ActionsColumnConfig, type AutoCrudActionConfig, type AutoCrudActionsConfig, type AutoCrudDataSourceConfig, type AutoCrudDataSourceContext, type AutoCrudDataSourceEntry, type AutoCrudDataSourceLoader, type AutoCrudDataSourceRegistration, type AutoCrudExportContext, type AutoCrudFormComponentConfig, type AutoCrudLocale, type AutoCrudOption, type AutoCrudQueryCapabilities, type AutoCrudRowActionContext, type AutoCrudRowOpenOptions, AutoCrudTable, type AutoCrudTableProps, type AutoCrudToolbarContext, type AutoCrudToolbarResolver, AutoForm, type AutoFormProps, type AutoQueryParams, AutoTable, AutoTableActionBar, type AutoTableProps, AutoTableSimpleFilters, type BatchActionConfig, type BatchActionContext, type BatchActionItem, type BatchBuiltinActionItem, type BatchBuiltinActionType, type BatchCustomActionItem, type BatchUpdateField, type ColumnOverrides, type CreateFormSchemaOptions, type CreateTableSchemaOptions, type CrudActionBase, type CrudActionEntry, type CrudActionRegistration, type CrudActionZone, CrudFormModal, type CrudHooks, type CrudOperationPermissions, type CrudPermissions, type DataSource, DataTable, DataTableAdvancedToolbar, DataTableClassNames, DataTableColumnHeader, DataTableFacetedFilter, DataTablePagination, type DataTableProps, DataTableRowAction, DataTableToolbar, DataTableViewOptions, type DataTableViewOptionsProps, type DateFormatPreset, type DateFormatter, type DateLocaleOptions, type EnumOption, ExportDialog, type ExportDialogProps, type ExportMode, ExtendedColumnFilter, ExtendedColumnSort, type Field, type FieldOption, type FieldTableDisplay, type FieldType, type Fields, type FilterConfig, FilterOperator, type FilterVariant, type FormSchemaOverrides, ImportDialog, type ImportDialogProps, type ImportResult, type JSONSchema, type JSONSchemaProperty, JoinOperator, type ListParams, type ListResult, type LocaleProp, MultiCombobox, type MultiComboboxOption, type MultiComboboxProps, type MultiComboboxTriggerRenderProps, Option, type ParsedImportData, type ParsedZodField, type Parser, QueryKeys, type RowActionConfig, type RowActionDialogProps, type RowActionItem, type RowBuiltinActionItem, type RowBuiltinActionType, type RowCustomActionItem, SchemaAdapter, Select, type SelectMode, type SelectOption, type SelectProps, type SelectSearchableDynamicProps, type SelectSearchableMultipleProps, type SelectSearchableProps, type SelectSearchableSingleProps, type SelectSimpleProps, type SelectTriggerRenderProps, type SimpleFieldConfig, type SimpleFieldsConfig, type TableControlsLocale, type TablePaginationOptions, type ToastAdapter, type ToolbarActionConfig, type ToolbarActionItem, type ToolbarBuiltinActionItem, type ToolbarBuiltinActionType, type ToolbarCustomActionItem, type UnifiedField, type UnifiedSchema, type UrlStateOptions, type UseAutoCrudResourceOptions, type UseAutoCrudResourceParams, type UseAutoCrudResourceReturn, type WorkbookSheet, actionPositionSchema, cn, coerceRowValues, compareOrder, components, createActionsColumn, createEditFormSchema, createFormSchema, createMemoryDataSource, createSelectColumn, createTRPCDataSource, createTableSchema, crudActions, dataSources, dataToCSV, deDE, downloadCSVTemplate, enUS, esES, exportAllToCSV, exportTableToCSV, exportWorkbook, formComponents, formatDate, frFR, generateCSVTemplate, getUrlParams, humanize, jaJP, koKR, matchesPosition, noopToastAdapter, normalizeDataSourceConfig, normalizeOptions, parseAsArrayOf, parseAsInteger, parseAsString, parseAsStringEnum, parseCSV, parseImportFile, parseJSON, parseZodField, positionActions, resolveLocale, setDateFormatter, setSearchParams, setToolbarResolver, tablePagination, useAutoCrudResource, useDataTable, useQueryState, useQueryStates, useReadableFilters, useUrlState, useUrlStates, zhCN };
package/dist/index.js CHANGED
@@ -4626,6 +4626,27 @@ function coerceRowValues(rows, schema) {
4626
4626
 
4627
4627
  //#endregion
4628
4628
  //#region src/lib/export.ts
4629
+ /** Snapshot rendered business columns without changing the legacy CSV column policy. */
4630
+ function getVisibleExportColumns(table, deny = []) {
4631
+ const excluded = new Set([
4632
+ "select",
4633
+ "actions",
4634
+ ...deny
4635
+ ]);
4636
+ return [
4637
+ ...table.getLeftVisibleLeafColumns(),
4638
+ ...table.getCenterVisibleLeafColumns(),
4639
+ ...table.getRightVisibleLeafColumns()
4640
+ ].filter((column) => {
4641
+ if (excluded.has(column.id)) return false;
4642
+ const meta = column.columnDef.meta;
4643
+ if (meta?.autoCrudExpandColumn) return false;
4644
+ return ![meta?.classNames?.th, meta?.classNames?.td].some((value) => typeof value === "string" && /(?:^|\s)!?hidden!?(?:\s|$)/.test(value));
4645
+ }).map((column) => ({
4646
+ id: column.id,
4647
+ label: column.columnDef.meta?.label ?? (typeof column.columnDef.header === "string" ? column.columnDef.header : column.id)
4648
+ }));
4649
+ }
4629
4650
  function getExportColumnIds(table) {
4630
4651
  return table.getAllLeafColumns().filter((column) => column.accessorFn).map((column) => column.id);
4631
4652
  }
@@ -5142,7 +5163,7 @@ const filterModeConfig = {
5142
5163
  tooltip: "Linear-style command filters"
5143
5164
  }
5144
5165
  };
5145
- function AutoTable({ schema, data, getRowId, expandedRow, defaultExpanded = false, pageCount = 1, pagination, total, overrides, classNames, enableRowSelection = true, exclude, filterOnly, actions, pinnedColumns, filterMode, search = false, toolbarExtra, locale = enUSTableControls, onDeleteSelected, onUpdateSelected, batchUpdateFields, actionBarExtra, actionBarActions, deleteConfirmation, initialSorting, enableExport = true, showDefaultExport = true, onSelectedCountChange, onSelectedRowsChange, getSelectedRows }) {
5166
+ function AutoTable({ schema, data, getRowId, expandedRow, defaultExpanded = false, pageCount = 1, pagination, total, overrides, classNames, enableRowSelection = true, exclude, filterOnly, actions, pinnedColumns, filterMode, search = false, toolbarExtra, locale = enUSTableControls, onDeleteSelected, onUpdateSelected, batchUpdateFields, actionBarExtra, actionBarActions, deleteConfirmation, initialSorting, enableExport = true, showDefaultExport = true, onSelectedCountChange, onSelectedRowsChange, getSelectedRows, exportTableRef }) {
5146
5167
  if (expandedRow && !getRowId) throw new Error("AutoTable requires getRowId when expandedRow is provided.");
5147
5168
  const enableExpansion = !!expandedRow;
5148
5169
  const modes = filterMode ? Array.isArray(filterMode) ? filterMode : [filterMode] : DEFAULT_MODES;
@@ -5184,6 +5205,7 @@ function AutoTable({ schema, data, getRowId, expandedRow, defaultExpanded = fals
5184
5205
  while (expandColumnId$1 in schema.shape || result.some((column) => column.id === expandColumnId$1)) expandColumnId$1 = `_${expandColumnId$1}`;
5185
5206
  result.unshift({
5186
5207
  id: expandColumnId$1,
5208
+ meta: { autoCrudExpandColumn: true },
5187
5209
  header: () => /* @__PURE__ */ jsx("span", {
5188
5210
  className: "sr-only",
5189
5211
  children: "Row details"
@@ -5257,6 +5279,13 @@ function AutoTable({ schema, data, getRowId, expandedRow, defaultExpanded = fals
5257
5279
  };
5258
5280
  });
5259
5281
  }, [table, filterOnly]);
5282
+ useEffect(() => {
5283
+ if (!exportTableRef) return;
5284
+ exportTableRef.current = table;
5285
+ return () => {
5286
+ exportTableRef.current = null;
5287
+ };
5288
+ }, [table, exportTableRef]);
5260
5289
  const rowSelection = table.getState().rowSelection;
5261
5290
  const columnFilters = table.getState().columnFilters;
5262
5291
  const selectedRows = useMemo(() => table.getFilteredSelectedRowModel().rows.map((row) => row.original), [
@@ -6815,6 +6844,9 @@ function resolveToolbarActionsWithResolver(targetId, ownerActions, context) {
6815
6844
  const resolver = autoCrudToolbarResolver;
6816
6845
  return targetId !== void 0 && targetId.length > 0 && resolver !== null ? resolver(targetId, ownerActions, context) : ownerActions;
6817
6846
  }
6847
+ function reportExportError(error) {
6848
+ toast.error(error instanceof Error ? error.message : String(error));
6849
+ }
6818
6850
  function applyResourceTablePresentation(fields, resourceFields) {
6819
6851
  if (!fields || !resourceFields) return fields;
6820
6852
  return Object.fromEntries(Object.entries(fields).map(([key, config]) => {
@@ -7694,7 +7726,7 @@ function resolveActions(actions, defaults, rowActionsLocale, context) {
7694
7726
  *
7695
7727
  * 高级 CRUD 表格组件,封装了完整的增删改查流程
7696
7728
  */
7697
- function AutoCrudTable({ id, title, description, schema, resource, fields, table: tableConfig, form: formConfig, view: viewConfig, permissions, actions: actionItems, toolbar, toolbarActions, locale: localeProp, onCreate }) {
7729
+ function AutoCrudTable({ id, title, description, schema, resource, fields, table: tableConfig, form: formConfig, view: viewConfig, permissions, actions: actionItems, toolbar, toolbarActions, locale: localeProp, onCreate, onExport }) {
7698
7730
  const locale = resolveLocale(localeProp);
7699
7731
  const getPagination = React.useCallback(() => id ? tablePagination.get(id) : void 0, [id]);
7700
7732
  const registeredPagination = React.useSyncExternalStore(tablePagination.subscribe, getPagination, getPagination);
@@ -7714,6 +7746,8 @@ function AutoCrudTable({ id, title, description, schema, resource, fields, table
7714
7746
  const [importOpen, setImportOpen] = React.useState(false);
7715
7747
  const [selectedRows, setSelectedRows] = React.useState([]);
7716
7748
  const [exporting, setExporting] = React.useState(false);
7749
+ const exportBusyRef = React.useRef(false);
7750
+ const exportTableRef = React.useRef(null);
7717
7751
  const getSelectedRowsRef = React.useRef(null);
7718
7752
  const dynamicFilterOptions = useDynamicFilterOptions(resolvedFields);
7719
7753
  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), [
@@ -7772,6 +7806,26 @@ function AutoCrudTable({ id, title, description, schema, resource, fields, table
7772
7806
  return Object.keys(shape).filter((key) => !denySet.has(key));
7773
7807
  }, [resolvedSchema, denyFields]);
7774
7808
  const handleExport = React.useCallback(async (mode) => {
7809
+ if (onExport) {
7810
+ if (exportBusyRef.current || !exportTableRef.current) return;
7811
+ exportBusyRef.current = true;
7812
+ setExporting(true);
7813
+ try {
7814
+ const denied = new Set(denyFields);
7815
+ const columns = getVisibleExportColumns(exportTableRef.current, denyFields);
7816
+ const rows = mode === "selected" ? getSelectedRowsRef.current?.() : await resource.handlers.export?.();
7817
+ if (!rows || mode === "selected" && rows.length === 0) return;
7818
+ await onExport({
7819
+ mode,
7820
+ rows: denied.size ? rows.map((row) => Object.fromEntries(Object.entries(row).filter(([key]) => !denied.has(key)))) : rows,
7821
+ columns
7822
+ });
7823
+ } finally {
7824
+ exportBusyRef.current = false;
7825
+ setExporting(false);
7826
+ }
7827
+ return;
7828
+ }
7775
7829
  const filename = title ?? "export";
7776
7830
  const excludeColumns = denyFields;
7777
7831
  if (mode === "selected") {
@@ -7792,18 +7846,30 @@ function AutoCrudTable({ id, title, description, schema, resource, fields, table
7792
7846
  resource.handlers.export,
7793
7847
  title,
7794
7848
  denyFields,
7795
- resolvedFields
7849
+ resolvedFields,
7850
+ onExport
7796
7851
  ]);
7797
7852
  const handleExportClick = React.useCallback(async () => {
7798
7853
  const mode = selectedCount > 0 ? "selected" : "filtered";
7854
+ if (onExport) return handleExport(mode);
7799
7855
  setExporting(true);
7800
7856
  try {
7801
7857
  await handleExport(mode);
7802
7858
  } finally {
7803
7859
  setExporting(false);
7804
7860
  }
7805
- }, [selectedCount, handleExport]);
7861
+ }, [
7862
+ selectedCount,
7863
+ handleExport,
7864
+ onExport
7865
+ ]);
7806
7866
  const exportBatchActions = React.useMemo(() => {
7867
+ if (onExport) return registryBatchActions.map((action$1) => action$1.type === "export" && action$1.onClick === void 0 && !action$1.component ? {
7868
+ ...action$1,
7869
+ onClick: () => {
7870
+ handleExport("selected").catch(reportExportError);
7871
+ }
7872
+ } : action$1);
7807
7873
  if (!Object.values(resolvedFields).some((field) => field.format)) return registryBatchActions;
7808
7874
  return registryBatchActions.map((action$1) => {
7809
7875
  if (action$1.type !== "export" || action$1.onClick !== void 0 || Boolean(action$1.component)) return action$1;
@@ -7821,7 +7887,9 @@ function AutoCrudTable({ id, title, description, schema, resource, fields, table
7821
7887
  }, [
7822
7888
  registryBatchActions,
7823
7889
  resolvedFields,
7824
- denyFields
7890
+ denyFields,
7891
+ onExport,
7892
+ handleExport
7825
7893
  ]);
7826
7894
  const toolbarRefresh = React.useMemo(() => {
7827
7895
  const refresh = ownerRefreshAction?.onClick ?? resource.handlers.refresh;
@@ -7998,7 +8066,9 @@ function AutoCrudTable({ id, title, description, schema, resource, fields, table
7998
8066
  if (type === "export" && canExport) return /* @__PURE__ */ jsxs(Button, {
7999
8067
  variant: "outline",
8000
8068
  size: "sm",
8001
- onClick: overrides?.onClick ?? toolbarExportData,
8069
+ onClick: overrides?.onClick ?? (!ownerExportAction?.onClick ? () => {
8070
+ toolbarExportData?.().catch(reportExportError);
8071
+ } : toolbarExportData),
8002
8072
  disabled: exporting || selectedCount === 0 && !resource.handlers.export,
8003
8073
  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)]
8004
8074
  }, "export");
@@ -8077,7 +8147,8 @@ function AutoCrudTable({ id, title, description, schema, resource, fields, table
8077
8147
  showDefaultExport: false,
8078
8148
  initialSorting: defaultSort,
8079
8149
  onSelectedRowsChange: handleSelectedRowsChange,
8080
- getSelectedRows: getSelectedRowsRef
8150
+ getSelectedRows: getSelectedRowsRef,
8151
+ ...onExport ? { exportTableRef } : {}
8081
8152
  }),
8082
8153
  rowActionDialog,
8083
8154
  /* @__PURE__ */ jsx(CrudFormModal, {
@@ -8904,7 +8975,8 @@ function useAutoCrudResource({ router, schema, query: queryTransform, fields, op
8904
8975
  useEffect(() => {
8905
8976
  hooksRef.current = hooks;
8906
8977
  }, [hooks]);
8907
- const metadata = (router.meta?.useQuery(void 0, { staleTime: 3e4 }))?.data;
8978
+ const metadataQuery = router.meta?.useQuery(void 0, { staleTime: 3e4 });
8979
+ const metadata = metadataQuery?.data;
8908
8980
  const resolvedSchema = useMemo(() => mergeMetadataSchema(schema, metadata?.schema), [schema, metadata?.schema]);
8909
8981
  const defaultSorting = useMemo(() => defaultSortOption === false ? [] : defaultSortOption ?? getDefaultSortingForSchema(resolvedSchema), [defaultSortOption, resolvedSchema]);
8910
8982
  const metadataFields = useMemo(() => readMetadataFields(metadata?.fields), [metadata?.fields]);
@@ -8925,6 +8997,10 @@ function useAutoCrudResource({ router, schema, query: queryTransform, fields, op
8925
8997
  search: parseAsString.withDefault("")
8926
8998
  }, { shallow: false });
8927
8999
  const [filters] = useReadableFilters(columns, { shallow: false });
9000
+ const metadataPending = Boolean(metadataQuery?.isPending);
9001
+ const [filtersReady, setFiltersReady] = useState(!metadataPending);
9002
+ useEffect(() => setFiltersReady(!metadataPending), [metadataPending]);
9003
+ const listReady = !metadataPending && filtersReady;
8928
9004
  const queryInput = useMemo(() => {
8929
9005
  const autoParams = {
8930
9006
  page: urlParams.page,
@@ -8940,11 +9016,17 @@ function useAutoCrudResource({ router, schema, query: queryTransform, fields, op
8940
9016
  filters,
8941
9017
  queryTransform
8942
9018
  ]);
9019
+ const hasReadyListData = useRef(false);
8943
9020
  const listQuery = router.list.useQuery(queryInput, {
8944
- placeholderData: keepPreviousData,
9021
+ enabled: listReady,
9022
+ placeholderData: hasReadyListData.current ? keepPreviousData : void 0,
8945
9023
  staleTime: 0
8946
9024
  });
8947
- const tableRows = listQuery.data?.data ?? [];
9025
+ useEffect(() => {
9026
+ if (listReady && listQuery.data !== void 0) hasReadyListData.current = true;
9027
+ }, [listReady, listQuery.data]);
9028
+ const listData = listReady ? listQuery.data : void 0;
9029
+ const tableRows = listData?.data ?? [];
8948
9030
  const createMutation = router.create.useMutation();
8949
9031
  const updateMutation = router.update.useMutation();
8950
9032
  const deleteMutation = router.delete.useMutation();
@@ -9183,12 +9265,18 @@ function useAutoCrudResource({ router, schema, query: queryTransform, fields, op
9183
9265
  schema
9184
9266
  ]);
9185
9267
  const handleExport = useCallback(async () => {
9268
+ let result;
9186
9269
  if (exportFetcher) {
9187
9270
  const { page: _p, perPage: _pp,...exportParams } = queryInput ?? {};
9188
- return (await exportFetcher(exportParams))?.data ?? [];
9189
- }
9190
- if (exportQuery) return (await exportQuery.refetch())?.data?.data ?? [];
9191
- throw new Error("Export not supported");
9271
+ result = await exportFetcher(exportParams);
9272
+ } else if (exportQuery) {
9273
+ const response = await exportQuery.refetch();
9274
+ if (response.error) throw response.error;
9275
+ result = response.data;
9276
+ } else throw new Error("Export not supported");
9277
+ if (!result || !Array.isArray(result.data)) throw new Error("Export returned no data. Please retry.");
9278
+ if (result.hasMore !== false || result.total > result.data.length) throw new Error("Export results are incomplete. Narrow your filters and retry.");
9279
+ return result.data;
9192
9280
  }, [
9193
9281
  exportFetcher,
9194
9282
  exportQuery,
@@ -9196,16 +9284,17 @@ function useAutoCrudResource({ router, schema, query: queryTransform, fields, op
9196
9284
  ]);
9197
9285
  const canExport = !!(exportFetcher || exportQuery);
9198
9286
  const refresh = useCallback(async () => {
9287
+ if (!listReady) return;
9199
9288
  return listQuery.refetch();
9200
- }, [listQuery]);
9289
+ }, [listQuery, listReady]);
9201
9290
  return {
9202
9291
  idKey,
9203
9292
  tableData: {
9204
9293
  data: tableRows,
9205
- pageCount: listQuery.data?.pageCount ?? 0,
9206
- ...typeof listQuery.data?.total === "number" ? { total: listQuery.data.total } : {},
9207
- isLoading: listQuery.isLoading,
9208
- isFetching: listQuery.isFetching
9294
+ pageCount: listData?.pageCount ?? 0,
9295
+ ...typeof listData?.total === "number" ? { total: listData.total } : {},
9296
+ isLoading: !listReady || listQuery.isLoading,
9297
+ isFetching: !listReady || listQuery.isFetching
9209
9298
  },
9210
9299
  schema: resolvedSchema,
9211
9300
  fields: metadataFields,
@@ -9543,4 +9632,70 @@ function createMemoryDataSource(initialData = []) {
9543
9632
  }
9544
9633
 
9545
9634
  //#endregion
9546
- export { AutoCrudTable, AutoForm, AutoTable, AutoTableActionBar, AutoTableSimpleFilters, CrudFormModal, DataTable, DataTableAdvancedToolbar, DataTableColumnHeader, DataTableFacetedFilter, DataTablePagination, DataTableToolbar, DataTableViewOptions, ExportDialog, ImportDialog, MultiCombobox, SchemaAdapter, Select, actionPositionSchema, cn, coerceRowValues, compareOrder, components, createActionsColumn, createEditFormSchema, createFormSchema, createMemoryDataSource, createSelectColumn, createTRPCDataSource, createTableSchema, crudActions, dataSources, dataToCSV, deDE, downloadCSVTemplate, enUS, esES, exportAllToCSV, exportTableToCSV, formComponents, formatDate, frFR, generateCSVTemplate, getUrlParams, humanize, jaJP, koKR, matchesPosition, noopToastAdapter, normalizeDataSourceConfig, normalizeOptions, parseAsArrayOf, parseAsInteger, parseAsString, parseAsStringEnum, parseCSV, parseImportFile, parseJSON, parseZodField, positionActions, resolveLocale, setDateFormatter, setSearchParams, setToolbarResolver, tablePagination, useAutoCrudResource, useDataTable, useQueryState, useQueryStates, useReadableFilters, useUrlState, useUrlStates, zhCN };
9635
+ //#region src/lib/workbook.ts
9636
+ const MAX_ROWS = 1048576;
9637
+ const MAX_COLUMNS = 16384;
9638
+ const MAX_CELL_LENGTH = 32767;
9639
+ const MAX_LINE_FEEDS = 253;
9640
+ function validateCell(value) {
9641
+ if (typeof value === "string") {
9642
+ if (value.length > MAX_CELL_LENGTH) throw new RangeError(`Excel cells cannot exceed ${MAX_CELL_LENGTH} characters.`);
9643
+ if ((value.match(/\n/g)?.length ?? 0) > MAX_LINE_FEEDS) throw new RangeError(`Excel cells cannot exceed ${MAX_LINE_FEEDS} line feeds.`);
9644
+ } else if (value != null && (typeof value !== "number" || !Number.isFinite(value))) throw new TypeError("Workbook cells must contain text, finite numbers, or null.");
9645
+ }
9646
+ function validateSheets(sheets) {
9647
+ if (sheets.length === 0) throw new Error("A workbook needs at least one sheet.");
9648
+ const names = /* @__PURE__ */ new Set();
9649
+ for (const sheet of sheets) {
9650
+ if (!sheet.name || sheet.name.length > 31 || /[*?:\\/\[\]]/.test(sheet.name) || sheet.name.startsWith("'") || sheet.name.endsWith("'")) throw new Error("Worksheet names must be valid Excel names of 1–31 characters.");
9651
+ const name = sheet.name.toLowerCase();
9652
+ if (names.has(name)) throw new Error("Worksheet names must be unique.");
9653
+ names.add(name);
9654
+ if (sheet.rows.length + 1 > MAX_ROWS) throw new RangeError(`Excel worksheets cannot exceed ${MAX_ROWS} rows including headers.`);
9655
+ if (sheet.headers.length > MAX_COLUMNS) throw new RangeError(`Excel worksheets cannot exceed ${MAX_COLUMNS} columns.`);
9656
+ sheet.headers.forEach(validateCell);
9657
+ for (const row of sheet.rows) {
9658
+ if (row.length > MAX_COLUMNS) throw new RangeError(`Excel worksheets cannot exceed ${MAX_COLUMNS} columns.`);
9659
+ row.forEach(validateCell);
9660
+ }
9661
+ }
9662
+ }
9663
+ /** Internal byte builder, exported from this module for real workbook roundtrip tests. */
9664
+ async function buildWorkbookBytes(sheets) {
9665
+ validateSheets(sheets);
9666
+ const { default: ExcelJS } = await import("exceljs");
9667
+ const workbook = new ExcelJS.Workbook();
9668
+ for (const sheet of sheets) {
9669
+ const worksheet = workbook.addWorksheet(sheet.name);
9670
+ const append = (values) => {
9671
+ worksheet.addRow(values.map((value) => value ?? null)).eachCell((cell, column) => {
9672
+ const numberFormat = sheet.numberFormats?.[column - 1];
9673
+ if (typeof cell.value === "number" && numberFormat !== void 0) cell.numFmt = numberFormat;
9674
+ if (typeof cell.value === "string" && /[\r\n]/.test(cell.value)) cell.alignment = { wrapText: true };
9675
+ });
9676
+ };
9677
+ append(sheet.headers);
9678
+ sheet.rows.forEach(append);
9679
+ }
9680
+ return new Uint8Array(await workbook.xlsx.writeBuffer()).buffer;
9681
+ }
9682
+ /** Download a real XLSX workbook. Errors reject; no CSV or raw-value fallback. */
9683
+ async function exportWorkbook(sheets, { filename }) {
9684
+ const bytes = await buildWorkbookBytes(sheets);
9685
+ const blob = new Blob([bytes], { type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" });
9686
+ const url = URL.createObjectURL(blob);
9687
+ const link = document.createElement("a");
9688
+ try {
9689
+ link.href = url;
9690
+ link.download = /\.xlsx$/i.test(filename) ? filename : `${filename}.xlsx`;
9691
+ link.hidden = true;
9692
+ document.body.appendChild(link);
9693
+ link.click();
9694
+ } finally {
9695
+ link.remove();
9696
+ setTimeout(() => URL.revokeObjectURL(url), 0);
9697
+ }
9698
+ }
9699
+
9700
+ //#endregion
9701
+ export { AutoCrudTable, AutoForm, AutoTable, AutoTableActionBar, AutoTableSimpleFilters, CrudFormModal, DataTable, DataTableAdvancedToolbar, DataTableColumnHeader, DataTableFacetedFilter, DataTablePagination, DataTableToolbar, DataTableViewOptions, ExportDialog, ImportDialog, MultiCombobox, SchemaAdapter, Select, actionPositionSchema, cn, coerceRowValues, compareOrder, components, createActionsColumn, createEditFormSchema, createFormSchema, createMemoryDataSource, createSelectColumn, createTRPCDataSource, createTableSchema, crudActions, dataSources, dataToCSV, deDE, downloadCSVTemplate, enUS, esES, exportAllToCSV, exportTableToCSV, exportWorkbook, formComponents, formatDate, frFR, generateCSVTemplate, getUrlParams, humanize, jaJP, koKR, matchesPosition, noopToastAdapter, normalizeDataSourceConfig, normalizeOptions, parseAsArrayOf, parseAsInteger, parseAsString, parseAsStringEnum, parseCSV, parseImportFile, parseJSON, parseZodField, positionActions, resolveLocale, setDateFormatter, setSearchParams, setToolbarResolver, tablePagination, useAutoCrudResource, useDataTable, useQueryState, useQueryStates, useReadableFilters, useUrlState, useUrlStates, zhCN };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@wordrhyme/auto-crud",
3
3
  "type": "module",
4
- "version": "1.11.2",
4
+ "version": "1.12.1",
5
5
  "description": "Schema-first CRUD components with auto-generated tables and forms",
6
6
  "author": "wordrhyme",
7
7
  "license": "MIT",
@@ -66,6 +66,7 @@
66
66
  "class-variance-authority": "^0.7.1",
67
67
  "clsx": "^2.1.1",
68
68
  "cmdk": "^1.1.1",
69
+ "exceljs": "4.4.0",
69
70
  "lucide-react": "0.553.0",
70
71
  "nanoid": "^5.1.6",
71
72
  "tailwind-merge": "^3.5.0",
@@ -93,11 +94,11 @@
93
94
  "tsdown": "^0.15.12",
94
95
  "typescript": "^5.9.3",
95
96
  "vitest": "^3.2.4",
97
+ "@internal/eslint-config": "0.3.0",
98
+ "@internal/tsconfig": "0.1.0",
96
99
  "@internal/tsdown-config": "0.1.0",
97
100
  "@internal/vitest-config": "0.1.0",
98
- "@internal/tsconfig": "0.1.0",
99
- "@internal/prettier-config": "0.0.1",
100
- "@internal/eslint-config": "0.3.0"
101
+ "@internal/prettier-config": "0.0.1"
101
102
  },
102
103
  "prettier": "@internal/prettier-config",
103
104
  "scripts": {