@wordrhyme/auto-crud 1.3.6 → 1.3.9

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.js CHANGED
@@ -4170,6 +4170,15 @@ function FormModal({ open, onOpenChange, title, description, children, variant =
4170
4170
 
4171
4171
  //#endregion
4172
4172
  //#region src/lib/schema-bridge/zod-to-formily.ts
4173
+ const DEFAULT_FORM_MANAGED_FIELDS = [
4174
+ "id",
4175
+ "createdAt",
4176
+ "updatedAt",
4177
+ "createdBy",
4178
+ "createdByType",
4179
+ "updatedBy",
4180
+ "updatedByType"
4181
+ ];
4173
4182
  /**
4174
4183
  * 将字符串转为人类可读格式
4175
4184
  */
@@ -4252,16 +4261,12 @@ function createFormSchema(schema, options) {
4252
4261
  };
4253
4262
  }
4254
4263
  /**
4255
- * 为编辑模式创建 Form Schema(排除 id, createdAt, updatedAt)
4264
+ * 为编辑模式创建 Form Schema(排除平台托管字段)
4256
4265
  */
4257
4266
  function createEditFormSchema(schema, options) {
4258
4267
  return createFormSchema(schema, {
4259
4268
  ...options,
4260
- exclude: [
4261
- "id",
4262
- "createdAt",
4263
- "updatedAt"
4264
- ]
4269
+ exclude: [...DEFAULT_FORM_MANAGED_FIELDS]
4265
4270
  });
4266
4271
  }
4267
4272
 
@@ -4323,6 +4328,7 @@ function normalizeDataSourceRegistration(registration) {
4323
4328
  debounceMs: typeof registration.debounceMs === "number" && registration.debounceMs >= 0 ? registration.debounceMs : 300,
4324
4329
  loadMore: registration.loadMore === true,
4325
4330
  pageSize: typeof registration.pageSize === "number" && registration.pageSize > 0 ? registration.pageSize : 20,
4331
+ resolveFields: registration.resolveFields,
4326
4332
  load: registration.load
4327
4333
  };
4328
4334
  }
@@ -4557,8 +4563,10 @@ function loadAutoCrudDataSource({ entry, field, registryVersion, search, sourceK
4557
4563
  }
4558
4564
  Promise.resolve(entry.load({
4559
4565
  field: field.path.toString(),
4566
+ type: "form",
4560
4567
  page,
4561
4568
  pageSize: entry.loadMore ? entry.pageSize : void 0,
4569
+ query: searchValue,
4562
4570
  search: searchValue,
4563
4571
  values,
4564
4572
  signal: controller?.signal
@@ -5595,6 +5603,15 @@ function ImportDialog({ open, onOpenChange, onImport, columns = [], title, local
5595
5603
  });
5596
5604
  }
5597
5605
 
5606
+ //#endregion
5607
+ //#region src/lib/default-sorting.ts
5608
+ function getDefaultSortingForSchema(schema) {
5609
+ return Object.prototype.hasOwnProperty.call(schema.shape, "createdAt") ? [{
5610
+ id: "createdAt",
5611
+ desc: true
5612
+ }] : [];
5613
+ }
5614
+
5598
5615
  //#endregion
5599
5616
  //#region src/lib/crud-actions.ts
5600
5617
  const entries = /* @__PURE__ */ new Map();
@@ -5744,6 +5761,29 @@ const crudActions = {
5744
5761
 
5745
5762
  //#endregion
5746
5763
  //#region src/components/auto-crud/auto-crud-table.tsx
5764
+ const DEFAULT_IMPORT_MANAGED_FIELDS = [
5765
+ "createdAt",
5766
+ "updatedAt",
5767
+ "createdBy",
5768
+ "createdByType",
5769
+ "updatedBy",
5770
+ "updatedByType"
5771
+ ];
5772
+ const DEFAULT_HIDDEN_AUDIT_FIELDS = [
5773
+ "createdBy",
5774
+ "createdByType",
5775
+ "updatedBy",
5776
+ "updatedByType"
5777
+ ];
5778
+ function isDefaultHiddenAuditField(key) {
5779
+ return DEFAULT_HIDDEN_AUDIT_FIELDS.includes(key);
5780
+ }
5781
+ function isFieldTableExplicitlyVisible(config) {
5782
+ return config?.hidden === false || config?.table !== false && typeof config?.table === "object" && config.table.hidden === false;
5783
+ }
5784
+ function isTableOverrideExplicitlyVisible(config) {
5785
+ return config?.hidden === false;
5786
+ }
5747
5787
  let autoCrudToolbarResolver = null;
5748
5788
  function setToolbarResolver(resolver) {
5749
5789
  autoCrudToolbarResolver = resolver;
@@ -5815,8 +5855,8 @@ function haveSameRowSelection(left, right, idKey) {
5815
5855
  if (leftIds.length === left.length && rightIds.length === right.length) return areStringArraysEqual(leftIds, rightIds);
5816
5856
  return left.every((row, index$1) => row === right[index$1]);
5817
5857
  }
5818
- function readCreateAction(actions) {
5819
- return actions.find((action$1) => action$1.type === "create");
5858
+ function readToolbarAction(actions, type) {
5859
+ return actions.find((action$1) => action$1.type === type);
5820
5860
  }
5821
5861
  function resolveToolbarActionsWithResolver(targetId, ownerActions, context) {
5822
5862
  const resolver = autoCrudToolbarResolver;
@@ -5890,6 +5930,47 @@ function shouldLoadDynamicFilterOptions(config) {
5890
5930
  }
5891
5931
  return normalizeDataSourceConfig(getFilterDataSource(config)) !== void 0;
5892
5932
  }
5933
+ function isResolveValuePresent(value) {
5934
+ return value !== null && value !== void 0 && String(value).length > 0;
5935
+ }
5936
+ function getResolveValues(rows, field) {
5937
+ const values = /* @__PURE__ */ new Set();
5938
+ for (const row of rows) {
5939
+ const value = row[field];
5940
+ const items = Array.isArray(value) ? value : [value];
5941
+ for (const item of items) if (isResolveValuePresent(item)) values.add(String(item));
5942
+ }
5943
+ return Array.from(values);
5944
+ }
5945
+ function normalizeResolveCacheValue(value) {
5946
+ if (value instanceof Date) return value.toISOString();
5947
+ if (Array.isArray(value)) return value.map(normalizeResolveCacheValue);
5948
+ if (value && typeof value === "object") return Object.fromEntries(Object.keys(value).sort().map((key) => [key, normalizeResolveCacheValue(value[key])]));
5949
+ return value === void 0 ? { __autoCrudUndefined: true } : value;
5950
+ }
5951
+ function resolveRecordSignature(record) {
5952
+ return JSON.stringify(Object.keys(record).sort().map((key) => [key, normalizeResolveCacheValue(record[key])]));
5953
+ }
5954
+ function resolveRecordCacheKey(sourceKey, field, record) {
5955
+ return `${sourceKey}\u0000${field}\u0000${String(record[field])}\u0000${resolveRecordSignature(record)}`;
5956
+ }
5957
+ function mergeFieldOptions(current, incoming) {
5958
+ if ((!current || current.length === 0) && (!incoming || incoming.length === 0)) return;
5959
+ return mergeFilterOptions(current, incoming ?? []);
5960
+ }
5961
+ function mergeOptionsByField(left, right) {
5962
+ const keys = new Set([...Object.keys(left ?? {}), ...Object.keys(right ?? {})]);
5963
+ return Object.fromEntries(Array.from(keys).flatMap((key) => {
5964
+ const options = mergeFieldOptions(left?.[key], right?.[key]);
5965
+ return options ? [[key, options]] : [];
5966
+ }));
5967
+ }
5968
+ function shouldResolveOptions(field, config, hiddenColumns) {
5969
+ if (!config) return false;
5970
+ if (hiddenColumns.has(field)) return false;
5971
+ if (normalizeFieldOptions(config.enum)) return false;
5972
+ return normalizeDataSourceConfig(config.dataSource) !== void 0;
5973
+ }
5893
5974
  function useRegistryVersion(subscribe) {
5894
5975
  const [version$1, setVersion] = React.useState(0);
5895
5976
  React.useEffect(() => subscribe(() => setVersion((current) => current + 1)), [subscribe]);
@@ -6011,8 +6092,10 @@ function useDynamicFilterOptions(fields) {
6011
6092
  const timer = setTimeout(() => {
6012
6093
  Promise.resolve(entry.load({
6013
6094
  field,
6095
+ type: "filter",
6014
6096
  page,
6015
6097
  pageSize: entry.loadMore ? entry.pageSize : void 0,
6098
+ query: search,
6016
6099
  search,
6017
6100
  values: {},
6018
6101
  signal: controller?.signal
@@ -6105,6 +6188,117 @@ function useDynamicFilterOptions(fields) {
6105
6188
  setSearchValue
6106
6189
  };
6107
6190
  }
6191
+ function rowHasResolveValue(row, field, value) {
6192
+ const rowValue = row[field];
6193
+ return (Array.isArray(rowValue) ? rowValue : [rowValue]).some((item) => item !== null && item !== void 0 && String(item) === value);
6194
+ }
6195
+ function resolveExtraFields(entry, field) {
6196
+ const configured = typeof entry.resolveFields === "function" ? entry.resolveFields({ field }) : entry.resolveFields;
6197
+ return Array.from(new Set((configured ?? []).filter((key) => typeof key === "string" && key.length > 0)));
6198
+ }
6199
+ function buildResolveRecords(rows, field, values, extraFields) {
6200
+ return values.map((value) => {
6201
+ const row = rows.find((candidate) => rowHasResolveValue(candidate, field, value));
6202
+ const record = { [field]: value };
6203
+ for (const extraField of extraFields) if (row && Object.prototype.hasOwnProperty.call(row, extraField)) record[extraField] = row[extraField];
6204
+ return record;
6205
+ });
6206
+ }
6207
+ function useDynamicResolveOptions(fields, rows, hiddenColumns) {
6208
+ const registryVersion = useRegistryVersion(dataSources.subscribe);
6209
+ const [optionsByField, setOptionsByField] = React.useState({});
6210
+ const cacheRef = React.useRef(/* @__PURE__ */ new Map());
6211
+ const requestVersionRef = React.useRef(0);
6212
+ const sourceEntries = React.useMemo(() => {
6213
+ if (!fields || rows.length === 0) return [];
6214
+ const hiddenSet = new Set(hiddenColumns);
6215
+ return Object.entries(fields).flatMap(([field, config]) => {
6216
+ if (!shouldResolveOptions(field, config, hiddenSet)) return [];
6217
+ const source = normalizeDataSourceConfig(config.dataSource);
6218
+ const values = getResolveValues(rows, field);
6219
+ return source && values.length > 0 ? [{
6220
+ field,
6221
+ values,
6222
+ source
6223
+ }] : [];
6224
+ });
6225
+ }, [
6226
+ fields,
6227
+ hiddenColumns,
6228
+ rows
6229
+ ]);
6230
+ React.useEffect(() => {
6231
+ const requestVersion = requestVersionRef.current + 1;
6232
+ requestVersionRef.current = requestVersion;
6233
+ if (sourceEntries.length === 0) {
6234
+ setOptionsByField({});
6235
+ return;
6236
+ }
6237
+ let cancelled = false;
6238
+ const activeFields = new Set(sourceEntries.map(({ field }) => field));
6239
+ const readCachedOptions = (entry, records) => mergeFilterOptions(void 0, records.flatMap((record) => {
6240
+ const option = cacheRef.current.get(resolveRecordCacheKey(entry.source.key, entry.field, record));
6241
+ return option ? [option] : [];
6242
+ }));
6243
+ const recordsByField = new Map(sourceEntries.flatMap((sourceEntry) => {
6244
+ const entry = dataSources.get(sourceEntry.source.key);
6245
+ if (!entry) return [];
6246
+ return [[sourceEntry.field, buildResolveRecords(rows, sourceEntry.field, sourceEntry.values, resolveExtraFields(entry, sourceEntry.field))]];
6247
+ }));
6248
+ const readSourceEntryCachedOptions = (entry) => {
6249
+ return readCachedOptions(entry, recordsByField.get(entry.field) ?? []);
6250
+ };
6251
+ const findOptionForRecord = (options, field, record) => {
6252
+ const value = String(record[field]);
6253
+ return options.find((option) => option.value === value);
6254
+ };
6255
+ const missingRecordsFor = (entry, records) => records.filter((record) => !cacheRef.current.has(resolveRecordCacheKey(entry.source.key, entry.field, record)));
6256
+ setOptionsByField((current) => {
6257
+ const next = {};
6258
+ for (const entry of sourceEntries) {
6259
+ const cached = readSourceEntryCachedOptions(entry);
6260
+ if (cached.length > 0) next[entry.field] = cached;
6261
+ }
6262
+ for (const [field, options] of Object.entries(current)) if (activeFields.has(field) && !next[field]) next[field] = options;
6263
+ return next;
6264
+ });
6265
+ for (const sourceEntry of sourceEntries) {
6266
+ const entry = dataSources.get(sourceEntry.source.key);
6267
+ if (!entry) continue;
6268
+ const missingRecords = missingRecordsFor(sourceEntry, recordsByField.get(sourceEntry.field) ?? []);
6269
+ if (missingRecords.length === 0) continue;
6270
+ Promise.resolve(entry.load({
6271
+ field: sourceEntry.field,
6272
+ type: "resolve",
6273
+ values: missingRecords
6274
+ })).then((result) => {
6275
+ if (cancelled || requestVersionRef.current !== requestVersion) return;
6276
+ const options = normalizeOptions(result);
6277
+ for (const record of missingRecords) {
6278
+ const option = findOptionForRecord(options, sourceEntry.field, record);
6279
+ if (!option) continue;
6280
+ cacheRef.current.set(resolveRecordCacheKey(sourceEntry.source.key, sourceEntry.field, record), option);
6281
+ }
6282
+ const cached = readSourceEntryCachedOptions(sourceEntry);
6283
+ setOptionsByField((current) => ({
6284
+ ...current,
6285
+ [sourceEntry.field]: cached
6286
+ }));
6287
+ }, (error) => {
6288
+ if (cancelled || requestVersionRef.current !== requestVersion) return;
6289
+ console.warn(`[AutoCrud] Failed to resolve data source "${sourceEntry.source.key}".`, error);
6290
+ });
6291
+ }
6292
+ return () => {
6293
+ cancelled = true;
6294
+ };
6295
+ }, [
6296
+ registryVersion,
6297
+ rows,
6298
+ sourceEntries
6299
+ ]);
6300
+ return { optionsByField };
6301
+ }
6108
6302
  function getOptionLabel(value, options) {
6109
6303
  const stringValue = String(value);
6110
6304
  return options?.find((option) => option.value === stringValue)?.label ?? stringValue;
@@ -6113,7 +6307,7 @@ function getOptionLabel(value, options) {
6113
6307
  * 从统一配置生成表格 overrides
6114
6308
  * 当 filter 配置存在时,合并到 meta 中(不影响列隐藏)
6115
6309
  */
6116
- function buildTableOverrides(fields, legacyOverrides, dynamicFilterState) {
6310
+ function buildTableOverrides(fields, legacyOverrides, dynamicFilterState, dynamicResolveState) {
6117
6311
  const result = { ...legacyOverrides };
6118
6312
  if (fields) for (const [key, config] of Object.entries(fields)) {
6119
6313
  const fieldOptions = normalizeFieldOptions(config.enum);
@@ -6121,7 +6315,8 @@ function buildTableOverrides(fields, legacyOverrides, dynamicFilterState) {
6121
6315
  const filterDataSourceRegistered = dynamicFilterState?.registeredByField[key];
6122
6316
  const filterDataSourceSearchable = dynamicFilterState?.searchableByField[key];
6123
6317
  const currentDynamicOptions = !fieldOptions && filterDataSourceRegistered ? toTableOptions(dynamicFilterState?.optionsByField[key] ?? []) : void 0;
6124
- const dynamicOptions = !fieldOptions && filterDataSourceRegistered ? toTableOptions(dynamicFilterState?.labelOptionsByField[key] ?? dynamicFilterState?.optionsByField[key] ?? []) : void 0;
6318
+ const mergedDynamicOptions = mergeFieldOptions(!fieldOptions && filterDataSourceRegistered ? dynamicFilterState?.labelOptionsByField[key] ?? dynamicFilterState?.optionsByField[key] ?? [] : void 0, !fieldOptions ? dynamicResolveState?.optionsByField[key] : void 0);
6319
+ const dynamicOptions = !fieldOptions && mergedDynamicOptions ? toTableOptions(mergedDynamicOptions) : void 0;
6125
6320
  const tableMeta = config.table !== false && typeof config.table === "object" ? config.table.meta : void 0;
6126
6321
  const fieldEnumMeta = tableOptions ? {
6127
6322
  options: tableOptions,
@@ -6129,18 +6324,20 @@ function buildTableOverrides(fields, legacyOverrides, dynamicFilterState) {
6129
6324
  } : void 0;
6130
6325
  const fieldDataSourceMeta = dynamicOptions ? {
6131
6326
  options: dynamicOptions,
6132
- autoCrudFilterOptions: currentDynamicOptions ?? dynamicOptions,
6133
- autoCrudFilterHasMore: dynamicFilterState?.hasMoreByField[key] ?? false,
6134
- autoCrudFilterLoading: dynamicFilterState?.loadingByField[key] ?? false,
6135
- autoCrudFilterOnPopupScroll: (event) => {
6136
- if (!isNearPopupScrollBottom(event.currentTarget)) return;
6137
- dynamicFilterState?.loadMore(key);
6138
- },
6139
- variant: "multiSelect",
6140
- ...filterDataSourceSearchable ? {
6141
- autoCrudFilterSearchValue: dynamicFilterState?.searchValues[key] ?? "",
6142
- autoCrudFilterOnSearch: (value) => dynamicFilterState?.setSearchValue(key, value),
6143
- autoCrudFilterShouldFilter: false
6327
+ ...filterDataSourceRegistered ? {
6328
+ autoCrudFilterOptions: currentDynamicOptions ?? dynamicOptions,
6329
+ autoCrudFilterHasMore: dynamicFilterState?.hasMoreByField[key] ?? false,
6330
+ autoCrudFilterLoading: dynamicFilterState?.loadingByField[key] ?? false,
6331
+ autoCrudFilterOnPopupScroll: (event) => {
6332
+ if (!isNearPopupScrollBottom(event.currentTarget)) return;
6333
+ dynamicFilterState?.loadMore(key);
6334
+ },
6335
+ variant: "multiSelect",
6336
+ ...filterDataSourceSearchable ? {
6337
+ autoCrudFilterSearchValue: dynamicFilterState?.searchValues[key] ?? "",
6338
+ autoCrudFilterOnSearch: (value) => dynamicFilterState?.setSearchValue(key, value),
6339
+ autoCrudFilterShouldFilter: false
6340
+ } : void 0
6144
6341
  } : void 0
6145
6342
  } : void 0;
6146
6343
  let filterMeta;
@@ -6195,10 +6392,18 @@ function buildTableOverrides(fields, legacyOverrides, dynamicFilterState) {
6195
6392
  /**
6196
6393
  * 从统一配置生成隐藏列列表
6197
6394
  */
6198
- function buildHiddenColumns(fields, legacyHidden, denyFields) {
6199
- const hidden = new Set([...legacyHidden ?? [], ...denyFields ?? []]);
6200
- if (fields) {
6201
- for (const [key, config] of Object.entries(fields)) if (config.hidden) hidden.add(key);
6395
+ function buildHiddenColumns(fields, legacyHidden, denyFields, legacyOverrides) {
6396
+ const legacyHiddenSet = new Set(legacyHidden ?? []);
6397
+ const denySet = new Set(denyFields ?? []);
6398
+ const hidden = new Set([
6399
+ ...DEFAULT_HIDDEN_AUDIT_FIELDS,
6400
+ ...legacyHiddenSet,
6401
+ ...denySet
6402
+ ]);
6403
+ for (const key of DEFAULT_HIDDEN_AUDIT_FIELDS) if (!legacyHiddenSet.has(key) && !denySet.has(key) && isTableOverrideExplicitlyVisible(legacyOverrides?.[key])) hidden.delete(key);
6404
+ if (fields) for (const [key, config] of Object.entries(fields)) {
6405
+ if (isDefaultHiddenAuditField(key) && !legacyHiddenSet.has(key) && !denySet.has(key) && isFieldTableExplicitlyVisible(config)) hidden.delete(key);
6406
+ if (config.hidden) hidden.add(key);
6202
6407
  else if (config.table === false) hidden.add(key);
6203
6408
  else if (config.table && typeof config.table === "object" && config.table.hidden) hidden.add(key);
6204
6409
  }
@@ -6302,7 +6507,10 @@ function ViewModal({ open, onOpenChange, variant, data, schema, fields: fieldCon
6302
6507
  className: "grid gap-4 py-4",
6303
6508
  children: Object.entries(shape).filter(([key]) => {
6304
6509
  if (denySet.has(key)) return false;
6305
- return !(fieldConfig?.[key])?.hidden;
6510
+ const config = fieldConfig?.[key];
6511
+ if (config?.hidden) return false;
6512
+ if (isDefaultHiddenAuditField(key) && !isFieldTableExplicitlyVisible(config)) return false;
6513
+ return true;
6306
6514
  }).map(([key, fieldSchema]) => {
6307
6515
  const parsed = parseZodField(fieldSchema);
6308
6516
  const label = fieldConfig?.[key]?.label ?? humanize(key);
@@ -6458,6 +6666,14 @@ function AutoCrudTable({ id, title, description, schema, resource, fields, table
6458
6666
  const [exporting, setExporting] = React.useState(false);
6459
6667
  const getSelectedRowsRef = React.useRef(null);
6460
6668
  const dynamicFilterOptions = useDynamicFilterOptions(resolvedFields);
6669
+ const hiddenColumns = React.useMemo(() => buildHiddenColumns(resolvedFields, tableConfig?.hidden, denyFields, tableConfig?.overrides), [
6670
+ resolvedFields,
6671
+ tableConfig?.hidden,
6672
+ denyFields,
6673
+ tableConfig?.overrides
6674
+ ]);
6675
+ const dynamicResolveOptions = useDynamicResolveOptions(resolvedFields, resource.tableData.data, hiddenColumns);
6676
+ const dynamicOptionsByField = React.useMemo(() => mergeOptionsByField(dynamicFilterOptions.labelOptionsByField, dynamicResolveOptions.optionsByField), [dynamicFilterOptions.labelOptionsByField, dynamicResolveOptions.optionsByField]);
6461
6677
  const resourceIdKey = resource.idKey ?? "id";
6462
6678
  const actionRegistryVersion = useCrudActionsVersion();
6463
6679
  const unifiedActions = React.useMemo(() => {
@@ -6491,31 +6707,13 @@ function AutoCrudTable({ id, title, description, schema, resource, fields, table
6491
6707
  const handleSelectedRowsChange = React.useCallback((rows) => {
6492
6708
  setSelectedRows((currentRows) => haveSameRowSelection(currentRows, rows, resourceIdKey) ? currentRows : rows);
6493
6709
  }, [resourceIdKey]);
6494
- const ownerCreateAction = React.useMemo(() => readCreateAction(ownerToolbarActions), [ownerToolbarActions]);
6495
- const toolbarOpenCreate = can.create ? ownerCreateAction?.onClick ?? onCreate ?? resource.handlers.openCreate : void 0;
6496
- const toolbarContext = React.useMemo(() => ({
6497
- crudId: id ?? "",
6498
- idKey: resourceIdKey,
6499
- rowIds,
6500
- selectedRowIds,
6501
- selectedCount,
6502
- isRefreshing: resource.tableData.isFetching,
6503
- ...resource.handlers.refresh ? { refresh: resource.handlers.refresh } : {},
6504
- ...toolbarOpenCreate ? { openCreate: toolbarOpenCreate } : {}
6505
- }), [
6506
- id,
6507
- resourceIdKey,
6508
- rowIds,
6509
- selectedRowIds,
6510
- selectedCount,
6511
- resource.handlers.refresh,
6512
- resource.tableData.isFetching,
6513
- toolbarOpenCreate
6514
- ]);
6515
- const resolvedToolbarActions = resolveToolbarActionsWithResolver(id, registryToolbarActions, toolbarContext);
6710
+ const ownerRefreshAction = React.useMemo(() => readToolbarAction(ownerToolbarActions, "refresh"), [ownerToolbarActions]);
6711
+ const ownerImportAction = React.useMemo(() => readToolbarAction(ownerToolbarActions, "import"), [ownerToolbarActions]);
6712
+ const ownerExportAction = React.useMemo(() => readToolbarAction(ownerToolbarActions, "export"), [ownerToolbarActions]);
6713
+ const ownerCreateAction = React.useMemo(() => readToolbarAction(ownerToolbarActions, "create"), [ownerToolbarActions]);
6516
6714
  const importColumns = React.useMemo(() => {
6517
6715
  const shape = resolvedSchema.shape;
6518
- const denySet = new Set(denyFields ?? []);
6716
+ const denySet = new Set([...DEFAULT_IMPORT_MANAGED_FIELDS, ...denyFields ?? []]);
6519
6717
  return Object.keys(shape).filter((key) => !denySet.has(key));
6520
6718
  }, [resolvedSchema, denyFields]);
6521
6719
  const handleExport = React.useCallback(async (mode) => {
@@ -6549,21 +6747,75 @@ function AutoCrudTable({ id, title, description, schema, resource, fields, table
6549
6747
  setExporting(false);
6550
6748
  }
6551
6749
  }, [selectedCount, handleExport]);
6552
- const tableOverrides = React.useMemo(() => buildTableOverrides(resolvedFields, tableConfig?.overrides, dynamicFilterOptions), [
6750
+ const toolbarRefresh = React.useMemo(() => {
6751
+ const refresh = ownerRefreshAction?.onClick ?? resource.handlers.refresh;
6752
+ if (!refresh) return void 0;
6753
+ return async () => refresh();
6754
+ }, [ownerRefreshAction?.onClick, resource.handlers.refresh]);
6755
+ const toolbarOpenImport = React.useMemo(() => {
6756
+ if (!canImport) return void 0;
6757
+ return ownerImportAction?.onClick ?? (() => setImportOpen(true));
6758
+ }, [canImport, ownerImportAction?.onClick]);
6759
+ const toolbarExportData = React.useMemo(() => {
6760
+ if (!canExport) return void 0;
6761
+ const exportData = ownerExportAction?.onClick ?? handleExportClick;
6762
+ return async () => exportData();
6763
+ }, [
6764
+ canExport,
6765
+ handleExportClick,
6766
+ ownerExportAction?.onClick
6767
+ ]);
6768
+ const toolbarOpenCreate = React.useMemo(() => {
6769
+ if (!can.create) return void 0;
6770
+ return ownerCreateAction?.onClick ?? onCreate ?? resource.handlers.openCreate;
6771
+ }, [
6772
+ can.create,
6773
+ onCreate,
6774
+ ownerCreateAction?.onClick,
6775
+ resource.handlers.openCreate
6776
+ ]);
6777
+ const toolbarContext = React.useMemo(() => ({
6778
+ crudId: id ?? "",
6779
+ idKey: resourceIdKey,
6780
+ rowIds,
6781
+ selectedRowIds,
6782
+ selectedCount,
6783
+ isRefreshing: resource.tableData.isFetching,
6784
+ isExporting: exporting,
6785
+ ...toolbarRefresh ? { refresh: toolbarRefresh } : {},
6786
+ ...toolbarOpenImport ? { openImport: toolbarOpenImport } : {},
6787
+ ...toolbarExportData ? { exportData: toolbarExportData } : {},
6788
+ ...toolbarOpenCreate ? { openCreate: toolbarOpenCreate } : {}
6789
+ }), [
6790
+ id,
6791
+ resourceIdKey,
6792
+ rowIds,
6793
+ selectedRowIds,
6794
+ selectedCount,
6795
+ resource.tableData.isFetching,
6796
+ exporting,
6797
+ toolbarRefresh,
6798
+ toolbarOpenImport,
6799
+ toolbarExportData,
6800
+ toolbarOpenCreate
6801
+ ]);
6802
+ const resolvedToolbarActions = resolveToolbarActionsWithResolver(id, registryToolbarActions, toolbarContext);
6803
+ const tableOverrides = React.useMemo(() => buildTableOverrides(resolvedFields, tableConfig?.overrides, dynamicFilterOptions, dynamicResolveOptions), [
6553
6804
  resolvedFields,
6554
6805
  tableConfig?.overrides,
6555
- dynamicFilterOptions
6806
+ dynamicFilterOptions,
6807
+ dynamicResolveOptions
6808
+ ]);
6809
+ const defaultSort = React.useMemo(() => resource.defaultSort ?? tableConfig?.defaultSort ?? getDefaultSortingForSchema(resolvedSchema), [
6810
+ resource.defaultSort,
6811
+ resolvedSchema,
6812
+ tableConfig?.defaultSort
6556
6813
  ]);
6557
6814
  const formOverrides = React.useMemo(() => buildFormOverrides(resolvedFields, formConfig?.overrides, denyFields), [
6558
6815
  resolvedFields,
6559
6816
  formConfig?.overrides,
6560
6817
  denyFields
6561
6818
  ]);
6562
- const hiddenColumns = React.useMemo(() => buildHiddenColumns(resolvedFields, tableConfig?.hidden, denyFields), [
6563
- resolvedFields,
6564
- tableConfig?.hidden,
6565
- denyFields
6566
- ]);
6567
6819
  const searchConfig = React.useMemo(() => {
6568
6820
  const tableSearch = tableConfig?.search;
6569
6821
  if (tableSearch === false) return false;
@@ -6624,7 +6876,7 @@ function AutoCrudTable({ id, title, description, schema, resource, fields, table
6624
6876
  children: (() => {
6625
6877
  const renderBuiltinButton = (type, overrides) => {
6626
6878
  if (type === "refresh") {
6627
- const onRefresh = overrides?.onClick ?? resource.handlers.refresh;
6879
+ const onRefresh = overrides?.onClick ?? toolbarRefresh;
6628
6880
  const label = overrides?.label ?? locale.toolbar.refresh;
6629
6881
  if (!onRefresh) return null;
6630
6882
  return /* @__PURE__ */ jsx(Button, {
@@ -6640,18 +6892,18 @@ function AutoCrudTable({ id, title, description, schema, resource, fields, table
6640
6892
  if (type === "import" && canImport) return /* @__PURE__ */ jsxs(Button, {
6641
6893
  variant: "outline",
6642
6894
  size: "sm",
6643
- onClick: overrides?.onClick ?? (() => setImportOpen(true)),
6895
+ onClick: overrides?.onClick ?? toolbarOpenImport,
6644
6896
  children: [/* @__PURE__ */ jsx(Download, { className: "mr-2 h-4 w-4" }), overrides?.label ?? locale.toolbar.import]
6645
6897
  }, "import");
6646
6898
  if (type === "export" && canExport) return /* @__PURE__ */ jsxs(Button, {
6647
6899
  variant: "outline",
6648
6900
  size: "sm",
6649
- onClick: overrides?.onClick ?? handleExportClick,
6901
+ onClick: overrides?.onClick ?? toolbarExportData,
6650
6902
  disabled: exporting || selectedCount === 0 && !resource.handlers.export,
6651
6903
  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)]
6652
6904
  }, "export");
6653
6905
  if (type === "create" && can.create) return /* @__PURE__ */ jsx(Button, {
6654
- onClick: overrides?.onClick ?? onCreate ?? resource.handlers.openCreate,
6906
+ onClick: overrides?.onClick ?? toolbarOpenCreate,
6655
6907
  children: overrides?.label ?? locale.toolbar.create
6656
6908
  }, "create");
6657
6909
  return null;
@@ -6710,7 +6962,7 @@ function AutoCrudTable({ id, title, description, schema, resource, fields, table
6710
6962
  deleteConfirmation: locale.bulkDeleteModal,
6711
6963
  enableExport: canExport,
6712
6964
  showDefaultExport: false,
6713
- initialSorting: tableConfig?.defaultSort,
6965
+ initialSorting: defaultSort,
6714
6966
  onSelectedRowsChange: handleSelectedRowsChange,
6715
6967
  getSelectedRows: getSelectedRowsRef
6716
6968
  }),
@@ -6739,7 +6991,7 @@ function AutoCrudTable({ id, title, description, schema, resource, fields, table
6739
6991
  data: resource.modal.selected,
6740
6992
  schema: resolvedSchema,
6741
6993
  fields: resolvedFields,
6742
- dynamicOptions: dynamicFilterOptions.optionsByField,
6994
+ dynamicOptions: dynamicOptionsByField,
6743
6995
  denyFields,
6744
6996
  locale
6745
6997
  }),
@@ -7512,7 +7764,7 @@ function modalReducer(state, action$1) {
7512
7764
  * Hook 主函数
7513
7765
  */
7514
7766
  function useAutoCrudResource({ router, schema, query: queryTransform, options = {} }) {
7515
- const { idKey = "id", defaultVariant = "dialog", hooks, toast: toastAdapter, exportFetcher } = options;
7767
+ const { idKey = "id", defaultVariant = "dialog", hooks, toast: toastAdapter, exportFetcher, defaultSort: defaultSortOption } = options;
7516
7768
  const toast$1 = useMemo(() => toastAdapter === false ? noopToastAdapter : toastAdapter ?? defaultToastAdapter, [toastAdapter]);
7517
7769
  const hooksRef = useRef(hooks);
7518
7770
  useEffect(() => {
@@ -7520,12 +7772,13 @@ function useAutoCrudResource({ router, schema, query: queryTransform, options =
7520
7772
  }, [hooks]);
7521
7773
  const metadata = (router.meta?.useQuery(void 0, { staleTime: 3e4 }))?.data;
7522
7774
  const resolvedSchema = useMemo(() => mergeMetadataSchema(schema, metadata?.schema), [schema, metadata?.schema]);
7775
+ const defaultSorting = useMemo(() => defaultSortOption === false ? [] : defaultSortOption ?? getDefaultSortingForSchema(resolvedSchema), [defaultSortOption, resolvedSchema]);
7523
7776
  const metadataFields = useMemo(() => readMetadataFields(metadata?.fields), [metadata?.fields]);
7524
7777
  const columns = useMemo(() => createTableSchema(resolvedSchema), [resolvedSchema]);
7525
7778
  const [urlParams] = useQueryStates({
7526
7779
  page: parseAsInteger.withDefault(1),
7527
7780
  perPage: parseAsInteger.withDefault(10),
7528
- sort: getSortingStateParser().withDefault([]),
7781
+ sort: getSortingStateParser().withDefault(defaultSorting),
7529
7782
  joinOperator: parseAsStringEnum(["and", "or"]).withDefault("and"),
7530
7783
  search: parseAsString.withDefault("")
7531
7784
  }, { shallow: false });
@@ -7813,6 +8066,7 @@ function useAutoCrudResource({ router, schema, query: queryTransform, options =
7813
8066
  },
7814
8067
  schema: resolvedSchema,
7815
8068
  fields: metadataFields,
8069
+ defaultSort: defaultSorting,
7816
8070
  modal,
7817
8071
  mutations: {
7818
8072
  isCreating: createMutation.isPending,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@wordrhyme/auto-crud",
3
3
  "type": "module",
4
- "version": "1.3.6",
4
+ "version": "1.3.9",
5
5
  "description": "Schema-first CRUD components with auto-generated tables and forms",
6
6
  "author": "wordrhyme",
7
7
  "license": "MIT",
@@ -63,10 +63,12 @@
63
63
  "tailwind-merge": "^3.5.0",
64
64
  "vaul": "^1.1.2",
65
65
  "@wordrhyme/formily-shadcn": "1.12.9",
66
- "@wordrhyme/shadcn-ui": "1.32.4",
67
- "@wordrhyme/shadcn": "1.3.2"
66
+ "@wordrhyme/shadcn": "1.3.2",
67
+ "@wordrhyme/shadcn-ui": "1.32.4"
68
68
  },
69
69
  "devDependencies": {
70
+ "@storybook/react": "^8.6.18",
71
+ "@storybook/test": "^8.6.18",
70
72
  "@tanstack/react-query": "^5.90.15",
71
73
  "@testing-library/jest-dom": "^6.9.1",
72
74
  "@testing-library/react": "^16.3.2",
@@ -82,8 +84,8 @@
82
84
  "tsdown": "^0.15.12",
83
85
  "typescript": "^5.9.3",
84
86
  "vitest": "^3.2.4",
85
- "@internal/eslint-config": "0.3.0",
86
87
  "@internal/prettier-config": "0.0.1",
88
+ "@internal/eslint-config": "0.3.0",
87
89
  "@internal/tsconfig": "0.1.0",
88
90
  "@internal/tsdown-config": "0.1.0",
89
91
  "@internal/vitest-config": "0.1.0"