@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 +194 -38
- package/dist/index.d.cts +35 -6
- package/dist/index.d.ts +35 -6
- package/dist/index.js +175 -20
- package/package.json +5 -4
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
|
-
}, [
|
|
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 ??
|
|
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
|
|
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
|
-
|
|
9036
|
+
enabled: listReady,
|
|
9037
|
+
placeholderData: hasReadyListData.current ? __tanstack_react_query.keepPreviousData : void 0,
|
|
8960
9038
|
staleTime: 0
|
|
8961
9039
|
});
|
|
8962
|
-
|
|
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
|
-
|
|
9204
|
-
}
|
|
9205
|
-
|
|
9206
|
-
|
|
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:
|
|
9221
|
-
...typeof
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
}, [
|
|
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 ??
|
|
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
|
|
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
|
-
|
|
9021
|
+
enabled: listReady,
|
|
9022
|
+
placeholderData: hasReadyListData.current ? keepPreviousData : void 0,
|
|
8945
9023
|
staleTime: 0
|
|
8946
9024
|
});
|
|
8947
|
-
|
|
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
|
-
|
|
9189
|
-
}
|
|
9190
|
-
|
|
9191
|
-
|
|
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:
|
|
9206
|
-
...typeof
|
|
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
|
-
|
|
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.
|
|
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/
|
|
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": {
|