@wordrhyme/auto-crud 1.3.8 → 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/README.md CHANGED
@@ -546,11 +546,11 @@ table={{
546
546
 
547
547
  `toolbar` 数组控制页面顶部右侧工具栏的按钮,设计思路完全对齐行操作 `actions`。旧 prop `toolbarActions` 仍兼容读取,但新代码请使用 `toolbar`。
548
548
 
549
- 内置操作类型:`create`(新建)、`import`(导入)、`export`(导出)。
549
+ 内置操作类型:`refresh`(刷新)、`create`(新建)、`import`(导入)、`export`(导出)。
550
550
 
551
551
  ### 只添加自定义按钮(内置保持默认)
552
552
 
553
- 只传 `type: "custom"` 项时,所有内置按钮(导入、导出、新建)保持原样,custom 项按 `position` 插入首部或尾部。
553
+ 只传 `type: "custom"` 项时,所有内置按钮(刷新、导入、导出、新建)保持原样,custom 项按 `position` 插入首部或尾部。
554
554
 
555
555
  ```tsx
556
556
  <AutoCrudTable
@@ -563,7 +563,7 @@ table={{
563
563
  { type: 'custom', component: <Button variant="outline">批量标签</Button> }, // 默认 position: "end"
564
564
  ]}
565
565
  />
566
- // 渲染顺序: 分类管理 · [导入] · [导出] · [新建] · 批量标签
566
+ // 渲染顺序: 分类管理 · [刷新] · [导入] · [导出] · [新建] · 批量标签
567
567
  ```
568
568
 
569
569
  ### 调整内置按钮顺序
@@ -669,7 +669,7 @@ table={{
669
669
 
670
670
  ```tsx
671
671
  <AutoCrudTable
672
- // defaults 即内置三个按钮的信息,在此数组里你可以随意 map、过滤或插值
672
+ // defaults 即内置按钮的信息,在此数组里你可以随意 map、过滤或插值
673
673
  toolbar={(defaults) =>
674
674
  defaults.map((btn) =>
675
675
  btn.type === 'create'
@@ -697,7 +697,7 @@ type ToolbarActionConfig =
697
697
  | ((defaults: ToolbarBuiltinActionItem[]) => ToolbarActionItem[]);
698
698
 
699
699
  type ToolbarBuiltinActionItem = ActionMeta & {
700
- type: 'create' | 'import' | 'export';
700
+ type: 'refresh' | 'create' | 'import' | 'export';
701
701
  onClick?: () => void; // 覆盖默认行为
702
702
  label?: string; // 覆盖默认文案
703
703
  component?: React.ReactNode | ((context: AutoCrudToolbarContext) => React.ReactNode);
@@ -715,10 +715,34 @@ interface AutoCrudToolbarContext {
715
715
  rowIds: string[];
716
716
  selectedRowIds: string[];
717
717
  selectedCount: number;
718
+ refresh?: () => Promise<unknown>;
719
+ openImport?: () => void;
720
+ exportData?: () => Promise<void>;
718
721
  openCreate?: () => void;
722
+ isRefreshing: boolean;
723
+ isExporting: boolean;
719
724
  }
720
725
  ```
721
726
 
727
+ `AutoCrudToolbarContext` 是工具栏自定义组件的正式 command contract。自定义按钮需要复用 AutoCrud 内置能力时,直接调用 context 中的命令,不要绕过内部状态重新实现。
728
+
729
+ 例如业务侧希望把默认“新建”按钮替换成“手动创建”,可以替换 `type: "create"` 的组件并调用 `openCreate`:
730
+
731
+ ```tsx
732
+ <AutoCrudTable
733
+ toolbar={[
734
+ {
735
+ type: 'create',
736
+ component: ({ openCreate }) => (
737
+ <Button onClick={() => openCreate?.()}>手动创建</Button>
738
+ ),
739
+ },
740
+ ]}
741
+ />
742
+ ```
743
+
744
+ 这里“手动创建”只是业务按钮文案,不是新的 AutoCrud 流程;它仍然打开 AutoCrud 原生的新建弹窗。`openCreate`、`openImport`、`exportData` 会按对应权限和能力可选暴露,扩展方应使用 `?.()` 或自行控制 disabled 状态。
745
+
722
746
  ### 工具栏扩展 Resolver
723
747
 
724
748
  宿主应用可以通过 `setToolbarResolver` 为指定 CRUD 注入额外工具栏动作。resolver 是普通纯函数,不是 React Hook;不要在 resolver 内调用 `useRouter`、`useMemo` 等 React Hooks。
package/dist/index.cjs CHANGED
@@ -4365,6 +4365,7 @@ function normalizeDataSourceRegistration(registration) {
4365
4365
  debounceMs: typeof registration.debounceMs === "number" && registration.debounceMs >= 0 ? registration.debounceMs : 300,
4366
4366
  loadMore: registration.loadMore === true,
4367
4367
  pageSize: typeof registration.pageSize === "number" && registration.pageSize > 0 ? registration.pageSize : 20,
4368
+ resolveFields: registration.resolveFields,
4368
4369
  load: registration.load
4369
4370
  };
4370
4371
  }
@@ -4599,8 +4600,10 @@ function loadAutoCrudDataSource({ entry, field, registryVersion, search, sourceK
4599
4600
  }
4600
4601
  Promise.resolve(entry.load({
4601
4602
  field: field.path.toString(),
4603
+ type: "form",
4602
4604
  page,
4603
4605
  pageSize: entry.loadMore ? entry.pageSize : void 0,
4606
+ query: searchValue,
4604
4607
  search: searchValue,
4605
4608
  values,
4606
4609
  signal: controller?.signal
@@ -5889,8 +5892,8 @@ function haveSameRowSelection(left, right, idKey) {
5889
5892
  if (leftIds.length === left.length && rightIds.length === right.length) return areStringArraysEqual(leftIds, rightIds);
5890
5893
  return left.every((row, index$1) => row === right[index$1]);
5891
5894
  }
5892
- function readCreateAction(actions) {
5893
- return actions.find((action$1) => action$1.type === "create");
5895
+ function readToolbarAction(actions, type) {
5896
+ return actions.find((action$1) => action$1.type === type);
5894
5897
  }
5895
5898
  function resolveToolbarActionsWithResolver(targetId, ownerActions, context) {
5896
5899
  const resolver = autoCrudToolbarResolver;
@@ -5964,6 +5967,47 @@ function shouldLoadDynamicFilterOptions(config) {
5964
5967
  }
5965
5968
  return normalizeDataSourceConfig(getFilterDataSource(config)) !== void 0;
5966
5969
  }
5970
+ function isResolveValuePresent(value) {
5971
+ return value !== null && value !== void 0 && String(value).length > 0;
5972
+ }
5973
+ function getResolveValues(rows, field) {
5974
+ const values = /* @__PURE__ */ new Set();
5975
+ for (const row of rows) {
5976
+ const value = row[field];
5977
+ const items = Array.isArray(value) ? value : [value];
5978
+ for (const item of items) if (isResolveValuePresent(item)) values.add(String(item));
5979
+ }
5980
+ return Array.from(values);
5981
+ }
5982
+ function normalizeResolveCacheValue(value) {
5983
+ if (value instanceof Date) return value.toISOString();
5984
+ if (Array.isArray(value)) return value.map(normalizeResolveCacheValue);
5985
+ if (value && typeof value === "object") return Object.fromEntries(Object.keys(value).sort().map((key) => [key, normalizeResolveCacheValue(value[key])]));
5986
+ return value === void 0 ? { __autoCrudUndefined: true } : value;
5987
+ }
5988
+ function resolveRecordSignature(record) {
5989
+ return JSON.stringify(Object.keys(record).sort().map((key) => [key, normalizeResolveCacheValue(record[key])]));
5990
+ }
5991
+ function resolveRecordCacheKey(sourceKey, field, record) {
5992
+ return `${sourceKey}\u0000${field}\u0000${String(record[field])}\u0000${resolveRecordSignature(record)}`;
5993
+ }
5994
+ function mergeFieldOptions(current, incoming) {
5995
+ if ((!current || current.length === 0) && (!incoming || incoming.length === 0)) return;
5996
+ return mergeFilterOptions(current, incoming ?? []);
5997
+ }
5998
+ function mergeOptionsByField(left, right) {
5999
+ const keys = new Set([...Object.keys(left ?? {}), ...Object.keys(right ?? {})]);
6000
+ return Object.fromEntries(Array.from(keys).flatMap((key) => {
6001
+ const options = mergeFieldOptions(left?.[key], right?.[key]);
6002
+ return options ? [[key, options]] : [];
6003
+ }));
6004
+ }
6005
+ function shouldResolveOptions(field, config, hiddenColumns) {
6006
+ if (!config) return false;
6007
+ if (hiddenColumns.has(field)) return false;
6008
+ if (normalizeFieldOptions(config.enum)) return false;
6009
+ return normalizeDataSourceConfig(config.dataSource) !== void 0;
6010
+ }
5967
6011
  function useRegistryVersion(subscribe) {
5968
6012
  const [version$1, setVersion] = react.useState(0);
5969
6013
  react.useEffect(() => subscribe(() => setVersion((current) => current + 1)), [subscribe]);
@@ -6085,8 +6129,10 @@ function useDynamicFilterOptions(fields) {
6085
6129
  const timer = setTimeout(() => {
6086
6130
  Promise.resolve(entry.load({
6087
6131
  field,
6132
+ type: "filter",
6088
6133
  page,
6089
6134
  pageSize: entry.loadMore ? entry.pageSize : void 0,
6135
+ query: search,
6090
6136
  search,
6091
6137
  values: {},
6092
6138
  signal: controller?.signal
@@ -6179,6 +6225,117 @@ function useDynamicFilterOptions(fields) {
6179
6225
  setSearchValue
6180
6226
  };
6181
6227
  }
6228
+ function rowHasResolveValue(row, field, value) {
6229
+ const rowValue = row[field];
6230
+ return (Array.isArray(rowValue) ? rowValue : [rowValue]).some((item) => item !== null && item !== void 0 && String(item) === value);
6231
+ }
6232
+ function resolveExtraFields(entry, field) {
6233
+ const configured = typeof entry.resolveFields === "function" ? entry.resolveFields({ field }) : entry.resolveFields;
6234
+ return Array.from(new Set((configured ?? []).filter((key) => typeof key === "string" && key.length > 0)));
6235
+ }
6236
+ function buildResolveRecords(rows, field, values, extraFields) {
6237
+ return values.map((value) => {
6238
+ const row = rows.find((candidate) => rowHasResolveValue(candidate, field, value));
6239
+ const record = { [field]: value };
6240
+ for (const extraField of extraFields) if (row && Object.prototype.hasOwnProperty.call(row, extraField)) record[extraField] = row[extraField];
6241
+ return record;
6242
+ });
6243
+ }
6244
+ function useDynamicResolveOptions(fields, rows, hiddenColumns) {
6245
+ const registryVersion = useRegistryVersion(dataSources.subscribe);
6246
+ const [optionsByField, setOptionsByField] = react.useState({});
6247
+ const cacheRef = react.useRef(/* @__PURE__ */ new Map());
6248
+ const requestVersionRef = react.useRef(0);
6249
+ const sourceEntries = react.useMemo(() => {
6250
+ if (!fields || rows.length === 0) return [];
6251
+ const hiddenSet = new Set(hiddenColumns);
6252
+ return Object.entries(fields).flatMap(([field, config]) => {
6253
+ if (!shouldResolveOptions(field, config, hiddenSet)) return [];
6254
+ const source = normalizeDataSourceConfig(config.dataSource);
6255
+ const values = getResolveValues(rows, field);
6256
+ return source && values.length > 0 ? [{
6257
+ field,
6258
+ values,
6259
+ source
6260
+ }] : [];
6261
+ });
6262
+ }, [
6263
+ fields,
6264
+ hiddenColumns,
6265
+ rows
6266
+ ]);
6267
+ react.useEffect(() => {
6268
+ const requestVersion = requestVersionRef.current + 1;
6269
+ requestVersionRef.current = requestVersion;
6270
+ if (sourceEntries.length === 0) {
6271
+ setOptionsByField({});
6272
+ return;
6273
+ }
6274
+ let cancelled = false;
6275
+ const activeFields = new Set(sourceEntries.map(({ field }) => field));
6276
+ const readCachedOptions = (entry, records) => mergeFilterOptions(void 0, records.flatMap((record) => {
6277
+ const option = cacheRef.current.get(resolveRecordCacheKey(entry.source.key, entry.field, record));
6278
+ return option ? [option] : [];
6279
+ }));
6280
+ const recordsByField = new Map(sourceEntries.flatMap((sourceEntry) => {
6281
+ const entry = dataSources.get(sourceEntry.source.key);
6282
+ if (!entry) return [];
6283
+ return [[sourceEntry.field, buildResolveRecords(rows, sourceEntry.field, sourceEntry.values, resolveExtraFields(entry, sourceEntry.field))]];
6284
+ }));
6285
+ const readSourceEntryCachedOptions = (entry) => {
6286
+ return readCachedOptions(entry, recordsByField.get(entry.field) ?? []);
6287
+ };
6288
+ const findOptionForRecord = (options, field, record) => {
6289
+ const value = String(record[field]);
6290
+ return options.find((option) => option.value === value);
6291
+ };
6292
+ const missingRecordsFor = (entry, records) => records.filter((record) => !cacheRef.current.has(resolveRecordCacheKey(entry.source.key, entry.field, record)));
6293
+ setOptionsByField((current) => {
6294
+ const next = {};
6295
+ for (const entry of sourceEntries) {
6296
+ const cached = readSourceEntryCachedOptions(entry);
6297
+ if (cached.length > 0) next[entry.field] = cached;
6298
+ }
6299
+ for (const [field, options] of Object.entries(current)) if (activeFields.has(field) && !next[field]) next[field] = options;
6300
+ return next;
6301
+ });
6302
+ for (const sourceEntry of sourceEntries) {
6303
+ const entry = dataSources.get(sourceEntry.source.key);
6304
+ if (!entry) continue;
6305
+ const missingRecords = missingRecordsFor(sourceEntry, recordsByField.get(sourceEntry.field) ?? []);
6306
+ if (missingRecords.length === 0) continue;
6307
+ Promise.resolve(entry.load({
6308
+ field: sourceEntry.field,
6309
+ type: "resolve",
6310
+ values: missingRecords
6311
+ })).then((result) => {
6312
+ if (cancelled || requestVersionRef.current !== requestVersion) return;
6313
+ const options = normalizeOptions(result);
6314
+ for (const record of missingRecords) {
6315
+ const option = findOptionForRecord(options, sourceEntry.field, record);
6316
+ if (!option) continue;
6317
+ cacheRef.current.set(resolveRecordCacheKey(sourceEntry.source.key, sourceEntry.field, record), option);
6318
+ }
6319
+ const cached = readSourceEntryCachedOptions(sourceEntry);
6320
+ setOptionsByField((current) => ({
6321
+ ...current,
6322
+ [sourceEntry.field]: cached
6323
+ }));
6324
+ }, (error) => {
6325
+ if (cancelled || requestVersionRef.current !== requestVersion) return;
6326
+ console.warn(`[AutoCrud] Failed to resolve data source "${sourceEntry.source.key}".`, error);
6327
+ });
6328
+ }
6329
+ return () => {
6330
+ cancelled = true;
6331
+ };
6332
+ }, [
6333
+ registryVersion,
6334
+ rows,
6335
+ sourceEntries
6336
+ ]);
6337
+ return { optionsByField };
6338
+ }
6182
6339
  function getOptionLabel(value, options) {
6183
6340
  const stringValue = String(value);
6184
6341
  return options?.find((option) => option.value === stringValue)?.label ?? stringValue;
@@ -6187,7 +6344,7 @@ function getOptionLabel(value, options) {
6187
6344
  * 从统一配置生成表格 overrides
6188
6345
  * 当 filter 配置存在时,合并到 meta 中(不影响列隐藏)
6189
6346
  */
6190
- function buildTableOverrides(fields, legacyOverrides, dynamicFilterState) {
6347
+ function buildTableOverrides(fields, legacyOverrides, dynamicFilterState, dynamicResolveState) {
6191
6348
  const result = { ...legacyOverrides };
6192
6349
  if (fields) for (const [key, config] of Object.entries(fields)) {
6193
6350
  const fieldOptions = normalizeFieldOptions(config.enum);
@@ -6195,7 +6352,8 @@ function buildTableOverrides(fields, legacyOverrides, dynamicFilterState) {
6195
6352
  const filterDataSourceRegistered = dynamicFilterState?.registeredByField[key];
6196
6353
  const filterDataSourceSearchable = dynamicFilterState?.searchableByField[key];
6197
6354
  const currentDynamicOptions = !fieldOptions && filterDataSourceRegistered ? toTableOptions(dynamicFilterState?.optionsByField[key] ?? []) : void 0;
6198
- const dynamicOptions = !fieldOptions && filterDataSourceRegistered ? toTableOptions(dynamicFilterState?.labelOptionsByField[key] ?? dynamicFilterState?.optionsByField[key] ?? []) : void 0;
6355
+ const mergedDynamicOptions = mergeFieldOptions(!fieldOptions && filterDataSourceRegistered ? dynamicFilterState?.labelOptionsByField[key] ?? dynamicFilterState?.optionsByField[key] ?? [] : void 0, !fieldOptions ? dynamicResolveState?.optionsByField[key] : void 0);
6356
+ const dynamicOptions = !fieldOptions && mergedDynamicOptions ? toTableOptions(mergedDynamicOptions) : void 0;
6199
6357
  const tableMeta = config.table !== false && typeof config.table === "object" ? config.table.meta : void 0;
6200
6358
  const fieldEnumMeta = tableOptions ? {
6201
6359
  options: tableOptions,
@@ -6203,18 +6361,20 @@ function buildTableOverrides(fields, legacyOverrides, dynamicFilterState) {
6203
6361
  } : void 0;
6204
6362
  const fieldDataSourceMeta = dynamicOptions ? {
6205
6363
  options: dynamicOptions,
6206
- autoCrudFilterOptions: currentDynamicOptions ?? dynamicOptions,
6207
- autoCrudFilterHasMore: dynamicFilterState?.hasMoreByField[key] ?? false,
6208
- autoCrudFilterLoading: dynamicFilterState?.loadingByField[key] ?? false,
6209
- autoCrudFilterOnPopupScroll: (event) => {
6210
- if (!isNearPopupScrollBottom(event.currentTarget)) return;
6211
- dynamicFilterState?.loadMore(key);
6212
- },
6213
- variant: "multiSelect",
6214
- ...filterDataSourceSearchable ? {
6215
- autoCrudFilterSearchValue: dynamicFilterState?.searchValues[key] ?? "",
6216
- autoCrudFilterOnSearch: (value) => dynamicFilterState?.setSearchValue(key, value),
6217
- autoCrudFilterShouldFilter: false
6364
+ ...filterDataSourceRegistered ? {
6365
+ autoCrudFilterOptions: currentDynamicOptions ?? dynamicOptions,
6366
+ autoCrudFilterHasMore: dynamicFilterState?.hasMoreByField[key] ?? false,
6367
+ autoCrudFilterLoading: dynamicFilterState?.loadingByField[key] ?? false,
6368
+ autoCrudFilterOnPopupScroll: (event) => {
6369
+ if (!isNearPopupScrollBottom(event.currentTarget)) return;
6370
+ dynamicFilterState?.loadMore(key);
6371
+ },
6372
+ variant: "multiSelect",
6373
+ ...filterDataSourceSearchable ? {
6374
+ autoCrudFilterSearchValue: dynamicFilterState?.searchValues[key] ?? "",
6375
+ autoCrudFilterOnSearch: (value) => dynamicFilterState?.setSearchValue(key, value),
6376
+ autoCrudFilterShouldFilter: false
6377
+ } : void 0
6218
6378
  } : void 0
6219
6379
  } : void 0;
6220
6380
  let filterMeta;
@@ -6543,6 +6703,14 @@ function AutoCrudTable({ id, title, description, schema, resource, fields, table
6543
6703
  const [exporting, setExporting] = react.useState(false);
6544
6704
  const getSelectedRowsRef = react.useRef(null);
6545
6705
  const dynamicFilterOptions = useDynamicFilterOptions(resolvedFields);
6706
+ const hiddenColumns = react.useMemo(() => buildHiddenColumns(resolvedFields, tableConfig?.hidden, denyFields, tableConfig?.overrides), [
6707
+ resolvedFields,
6708
+ tableConfig?.hidden,
6709
+ denyFields,
6710
+ tableConfig?.overrides
6711
+ ]);
6712
+ const dynamicResolveOptions = useDynamicResolveOptions(resolvedFields, resource.tableData.data, hiddenColumns);
6713
+ const dynamicOptionsByField = react.useMemo(() => mergeOptionsByField(dynamicFilterOptions.labelOptionsByField, dynamicResolveOptions.optionsByField), [dynamicFilterOptions.labelOptionsByField, dynamicResolveOptions.optionsByField]);
6546
6714
  const resourceIdKey = resource.idKey ?? "id";
6547
6715
  const actionRegistryVersion = useCrudActionsVersion();
6548
6716
  const unifiedActions = react.useMemo(() => {
@@ -6576,28 +6744,10 @@ function AutoCrudTable({ id, title, description, schema, resource, fields, table
6576
6744
  const handleSelectedRowsChange = react.useCallback((rows) => {
6577
6745
  setSelectedRows((currentRows) => haveSameRowSelection(currentRows, rows, resourceIdKey) ? currentRows : rows);
6578
6746
  }, [resourceIdKey]);
6579
- const ownerCreateAction = react.useMemo(() => readCreateAction(ownerToolbarActions), [ownerToolbarActions]);
6580
- const toolbarOpenCreate = can.create ? ownerCreateAction?.onClick ?? onCreate ?? resource.handlers.openCreate : void 0;
6581
- const toolbarContext = react.useMemo(() => ({
6582
- crudId: id ?? "",
6583
- idKey: resourceIdKey,
6584
- rowIds,
6585
- selectedRowIds,
6586
- selectedCount,
6587
- isRefreshing: resource.tableData.isFetching,
6588
- ...resource.handlers.refresh ? { refresh: resource.handlers.refresh } : {},
6589
- ...toolbarOpenCreate ? { openCreate: toolbarOpenCreate } : {}
6590
- }), [
6591
- id,
6592
- resourceIdKey,
6593
- rowIds,
6594
- selectedRowIds,
6595
- selectedCount,
6596
- resource.handlers.refresh,
6597
- resource.tableData.isFetching,
6598
- toolbarOpenCreate
6599
- ]);
6600
- const resolvedToolbarActions = resolveToolbarActionsWithResolver(id, registryToolbarActions, toolbarContext);
6747
+ const ownerRefreshAction = react.useMemo(() => readToolbarAction(ownerToolbarActions, "refresh"), [ownerToolbarActions]);
6748
+ const ownerImportAction = react.useMemo(() => readToolbarAction(ownerToolbarActions, "import"), [ownerToolbarActions]);
6749
+ const ownerExportAction = react.useMemo(() => readToolbarAction(ownerToolbarActions, "export"), [ownerToolbarActions]);
6750
+ const ownerCreateAction = react.useMemo(() => readToolbarAction(ownerToolbarActions, "create"), [ownerToolbarActions]);
6601
6751
  const importColumns = react.useMemo(() => {
6602
6752
  const shape = resolvedSchema.shape;
6603
6753
  const denySet = new Set([...DEFAULT_IMPORT_MANAGED_FIELDS, ...denyFields ?? []]);
@@ -6634,10 +6784,64 @@ function AutoCrudTable({ id, title, description, schema, resource, fields, table
6634
6784
  setExporting(false);
6635
6785
  }
6636
6786
  }, [selectedCount, handleExport]);
6637
- const tableOverrides = react.useMemo(() => buildTableOverrides(resolvedFields, tableConfig?.overrides, dynamicFilterOptions), [
6787
+ const toolbarRefresh = react.useMemo(() => {
6788
+ const refresh = ownerRefreshAction?.onClick ?? resource.handlers.refresh;
6789
+ if (!refresh) return void 0;
6790
+ return async () => refresh();
6791
+ }, [ownerRefreshAction?.onClick, resource.handlers.refresh]);
6792
+ const toolbarOpenImport = react.useMemo(() => {
6793
+ if (!canImport) return void 0;
6794
+ return ownerImportAction?.onClick ?? (() => setImportOpen(true));
6795
+ }, [canImport, ownerImportAction?.onClick]);
6796
+ const toolbarExportData = react.useMemo(() => {
6797
+ if (!canExport) return void 0;
6798
+ const exportData = ownerExportAction?.onClick ?? handleExportClick;
6799
+ return async () => exportData();
6800
+ }, [
6801
+ canExport,
6802
+ handleExportClick,
6803
+ ownerExportAction?.onClick
6804
+ ]);
6805
+ const toolbarOpenCreate = react.useMemo(() => {
6806
+ if (!can.create) return void 0;
6807
+ return ownerCreateAction?.onClick ?? onCreate ?? resource.handlers.openCreate;
6808
+ }, [
6809
+ can.create,
6810
+ onCreate,
6811
+ ownerCreateAction?.onClick,
6812
+ resource.handlers.openCreate
6813
+ ]);
6814
+ const toolbarContext = react.useMemo(() => ({
6815
+ crudId: id ?? "",
6816
+ idKey: resourceIdKey,
6817
+ rowIds,
6818
+ selectedRowIds,
6819
+ selectedCount,
6820
+ isRefreshing: resource.tableData.isFetching,
6821
+ isExporting: exporting,
6822
+ ...toolbarRefresh ? { refresh: toolbarRefresh } : {},
6823
+ ...toolbarOpenImport ? { openImport: toolbarOpenImport } : {},
6824
+ ...toolbarExportData ? { exportData: toolbarExportData } : {},
6825
+ ...toolbarOpenCreate ? { openCreate: toolbarOpenCreate } : {}
6826
+ }), [
6827
+ id,
6828
+ resourceIdKey,
6829
+ rowIds,
6830
+ selectedRowIds,
6831
+ selectedCount,
6832
+ resource.tableData.isFetching,
6833
+ exporting,
6834
+ toolbarRefresh,
6835
+ toolbarOpenImport,
6836
+ toolbarExportData,
6837
+ toolbarOpenCreate
6838
+ ]);
6839
+ const resolvedToolbarActions = resolveToolbarActionsWithResolver(id, registryToolbarActions, toolbarContext);
6840
+ const tableOverrides = react.useMemo(() => buildTableOverrides(resolvedFields, tableConfig?.overrides, dynamicFilterOptions, dynamicResolveOptions), [
6638
6841
  resolvedFields,
6639
6842
  tableConfig?.overrides,
6640
- dynamicFilterOptions
6843
+ dynamicFilterOptions,
6844
+ dynamicResolveOptions
6641
6845
  ]);
6642
6846
  const defaultSort = react.useMemo(() => resource.defaultSort ?? tableConfig?.defaultSort ?? getDefaultSortingForSchema(resolvedSchema), [
6643
6847
  resource.defaultSort,
@@ -6649,12 +6853,6 @@ function AutoCrudTable({ id, title, description, schema, resource, fields, table
6649
6853
  formConfig?.overrides,
6650
6854
  denyFields
6651
6855
  ]);
6652
- const hiddenColumns = react.useMemo(() => buildHiddenColumns(resolvedFields, tableConfig?.hidden, denyFields, tableConfig?.overrides), [
6653
- resolvedFields,
6654
- tableConfig?.hidden,
6655
- denyFields,
6656
- tableConfig?.overrides
6657
- ]);
6658
6856
  const searchConfig = react.useMemo(() => {
6659
6857
  const tableSearch = tableConfig?.search;
6660
6858
  if (tableSearch === false) return false;
@@ -6715,7 +6913,7 @@ function AutoCrudTable({ id, title, description, schema, resource, fields, table
6715
6913
  children: (() => {
6716
6914
  const renderBuiltinButton = (type, overrides) => {
6717
6915
  if (type === "refresh") {
6718
- const onRefresh = overrides?.onClick ?? resource.handlers.refresh;
6916
+ const onRefresh = overrides?.onClick ?? toolbarRefresh;
6719
6917
  const label = overrides?.label ?? locale.toolbar.refresh;
6720
6918
  if (!onRefresh) return null;
6721
6919
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__wordrhyme_shadcn.Button, {
@@ -6731,18 +6929,18 @@ function AutoCrudTable({ id, title, description, schema, resource, fields, table
6731
6929
  if (type === "import" && canImport) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(__wordrhyme_shadcn.Button, {
6732
6930
  variant: "outline",
6733
6931
  size: "sm",
6734
- onClick: overrides?.onClick ?? (() => setImportOpen(true)),
6932
+ onClick: overrides?.onClick ?? toolbarOpenImport,
6735
6933
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Download, { className: "mr-2 h-4 w-4" }), overrides?.label ?? locale.toolbar.import]
6736
6934
  }, "import");
6737
6935
  if (type === "export" && canExport) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(__wordrhyme_shadcn.Button, {
6738
6936
  variant: "outline",
6739
6937
  size: "sm",
6740
- onClick: overrides?.onClick ?? handleExportClick,
6938
+ onClick: overrides?.onClick ?? toolbarExportData,
6741
6939
  disabled: exporting || selectedCount === 0 && !resource.handlers.export,
6742
6940
  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)]
6743
6941
  }, "export");
6744
6942
  if (type === "create" && can.create) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__wordrhyme_shadcn.Button, {
6745
- onClick: overrides?.onClick ?? onCreate ?? resource.handlers.openCreate,
6943
+ onClick: overrides?.onClick ?? toolbarOpenCreate,
6746
6944
  children: overrides?.label ?? locale.toolbar.create
6747
6945
  }, "create");
6748
6946
  return null;
@@ -6830,7 +7028,7 @@ function AutoCrudTable({ id, title, description, schema, resource, fields, table
6830
7028
  data: resource.modal.selected,
6831
7029
  schema: resolvedSchema,
6832
7030
  fields: resolvedFields,
6833
- dynamicOptions: dynamicFilterOptions.optionsByField,
7031
+ dynamicOptions: dynamicOptionsByField,
6834
7032
  denyFields,
6835
7033
  locale
6836
7034
  }),
package/dist/index.d.cts CHANGED
@@ -1,4 +1,4 @@
1
- import * as react_jsx_runtime5 from "react/jsx-runtime";
1
+ import * as react_jsx_runtime3 from "react/jsx-runtime";
2
2
  import { z } from "zod";
3
3
  import { JsonSchemaFormScope } from "@wordrhyme/formily-shadcn";
4
4
  import * as _tanstack_react_table0 from "@tanstack/react-table";
@@ -395,7 +395,7 @@ declare function AutoTableActionBar<TData>({
395
395
  extraActions,
396
396
  actions,
397
397
  deleteConfirmation
398
- }: AutoTableActionBarProps<TData>): react_jsx_runtime5.JSX.Element;
398
+ }: AutoTableActionBarProps<TData>): react_jsx_runtime3.JSX.Element;
399
399
  //#endregion
400
400
  //#region src/lib/schema-bridge/types.d.ts
401
401
  /**
@@ -571,7 +571,7 @@ declare function AutoTable<T extends z.ZodObject<z.ZodRawShape>>({
571
571
  onSelectedCountChange,
572
572
  onSelectedRowsChange,
573
573
  getSelectedRows
574
- }: AutoTableProps<T>): react_jsx_runtime5.JSX.Element;
574
+ }: AutoTableProps<T>): react_jsx_runtime3.JSX.Element;
575
575
  //#endregion
576
576
  //#region src/i18n/locale.d.ts
577
577
  /**
@@ -684,12 +684,19 @@ type AutoCrudOption = {
684
684
  count?: number;
685
685
  disabled?: boolean;
686
686
  };
687
+ type AutoCrudDataSourceContextType = 'form' | 'filter' | 'resolve';
688
+ type AutoCrudDataSourceResolveFields = readonly string[] | ((context: {
689
+ field: string;
690
+ }) => readonly string[]);
687
691
  type AutoCrudDataSourceContext = {
688
692
  field: string;
693
+ type: AutoCrudDataSourceContextType;
689
694
  page?: number;
690
695
  pageSize?: number;
696
+ query?: string;
697
+ /** @deprecated Use `query` instead. */
691
698
  search?: string;
692
- values?: Record<string, unknown>;
699
+ values?: Record<string, unknown> | Record<string, unknown>[];
693
700
  signal?: AbortSignal;
694
701
  };
695
702
  type AutoCrudDataSourceResult = AutoCrudOption[] | {
@@ -705,6 +712,7 @@ type AutoCrudDataSourceRegistration = AutoCrudDataSourceLoader | {
705
712
  debounceMs?: number;
706
713
  loadMore?: boolean;
707
714
  pageSize?: number;
715
+ resolveFields?: AutoCrudDataSourceResolveFields;
708
716
  load: AutoCrudDataSourceLoader;
709
717
  };
710
718
  type AutoCrudDataSourceEntry = {
@@ -714,6 +722,7 @@ type AutoCrudDataSourceEntry = {
714
722
  debounceMs: number;
715
723
  loadMore: boolean;
716
724
  pageSize: number;
725
+ resolveFields?: AutoCrudDataSourceResolveFields;
717
726
  load: AutoCrudDataSourceLoader;
718
727
  };
719
728
  type RegistryListener$1 = () => void;
@@ -915,8 +924,11 @@ interface AutoCrudToolbarContext {
915
924
  selectedRowIds: string[];
916
925
  selectedCount: number;
917
926
  refresh?: () => Promise<unknown>;
918
- isRefreshing: boolean;
927
+ openImport?: () => void;
928
+ exportData?: () => Promise<void>;
919
929
  openCreate?: () => void;
930
+ isRefreshing: boolean;
931
+ isExporting: boolean;
920
932
  }
921
933
  type AutoCrudToolbarResolver = (targetId: string, ownerActions: readonly ToolbarActionItem[], context: AutoCrudToolbarContext) => ToolbarActionItem[];
922
934
  declare function setToolbarResolver(resolver: AutoCrudToolbarResolver | null): void;
@@ -1051,7 +1063,7 @@ declare function AutoCrudTable<TSchema extends z.ZodObject<z.ZodRawShape>>({
1051
1063
  toolbarActions,
1052
1064
  locale: localeProp,
1053
1065
  onCreate
1054
- }: AutoCrudTableProps<TSchema>): react_jsx_runtime5.JSX.Element;
1066
+ }: AutoCrudTableProps<TSchema>): react_jsx_runtime3.JSX.Element;
1055
1067
  //#endregion
1056
1068
  //#region src/components/auto-crud/auto-form.d.ts
1057
1069
  interface AutoFormProps<T extends z.ZodObject<z.ZodRawShape>> {
@@ -1090,7 +1102,7 @@ declare function AutoFormInner<T extends z.ZodObject<z.ZodRawShape>>({
1090
1102
  labelAlign,
1091
1103
  labelWidth,
1092
1104
  showSubmitButton
1093
- }: AutoFormProps<T>, ref: React.Ref<AutoFormRef>): react_jsx_runtime5.JSX.Element;
1105
+ }: AutoFormProps<T>, ref: React.Ref<AutoFormRef>): react_jsx_runtime3.JSX.Element;
1094
1106
  declare const AutoForm: <T extends z.ZodObject<z.ZodRawShape>>(props: AutoFormProps<T> & {
1095
1107
  ref?: React.Ref<AutoFormRef>;
1096
1108
  }) => ReturnType<typeof AutoFormInner>;
@@ -1406,7 +1418,7 @@ declare function AutoTableSimpleFilters<TData>({
1406
1418
  filters: externalFilters,
1407
1419
  onFiltersChange,
1408
1420
  leading
1409
- }: AutoTableSimpleFiltersProps<TData>): react_jsx_runtime5.JSX.Element | null;
1421
+ }: AutoTableSimpleFiltersProps<TData>): react_jsx_runtime3.JSX.Element | null;
1410
1422
  //#endregion
1411
1423
  //#region src/components/auto-crud/form-modal.d.ts
1412
1424
  type ModalVariant = "dialog" | "sheet";
@@ -1450,7 +1462,7 @@ declare function CrudFormModal<T extends z.ZodObject<z.ZodRawShape>>({
1450
1462
  labelAlign,
1451
1463
  labelWidth,
1452
1464
  className
1453
- }: CrudFormModalProps<T>): react_jsx_runtime5.JSX.Element;
1465
+ }: CrudFormModalProps<T>): react_jsx_runtime3.JSX.Element;
1454
1466
  //#endregion
1455
1467
  //#region src/components/auto-crud/import-dialog.d.ts
1456
1468
  interface ImportDialogProps {
@@ -1472,7 +1484,7 @@ declare function ImportDialog({
1472
1484
  columns,
1473
1485
  title,
1474
1486
  locale
1475
- }: ImportDialogProps): react_jsx_runtime5.JSX.Element;
1487
+ }: ImportDialogProps): react_jsx_runtime3.JSX.Element;
1476
1488
  //#endregion
1477
1489
  //#region src/components/auto-crud/export-dialog.d.ts
1478
1490
  type ExportMode = "selected" | "filtered";
@@ -1492,7 +1504,7 @@ declare function ExportDialog({
1492
1504
  selectedCount,
1493
1505
  onExport,
1494
1506
  canExportFiltered
1495
- }: ExportDialogProps): react_jsx_runtime5.JSX.Element;
1507
+ }: ExportDialogProps): react_jsx_runtime3.JSX.Element;
1496
1508
  //#endregion
1497
1509
  //#region src/components/data-table/data-table.d.ts
1498
1510
  interface DataTableProps<TData> extends React$1.ComponentProps<"div"> {
@@ -1505,7 +1517,7 @@ declare function DataTable<TData>({
1505
1517
  children,
1506
1518
  className,
1507
1519
  ...props
1508
- }: DataTableProps<TData>): react_jsx_runtime5.JSX.Element;
1520
+ }: DataTableProps<TData>): react_jsx_runtime3.JSX.Element;
1509
1521
  //#endregion
1510
1522
  //#region src/components/data-table/data-table-advanced-toolbar.d.ts
1511
1523
  interface DataTableAdvancedToolbarProps<TData> extends React$1.ComponentProps<"div"> {
@@ -1516,7 +1528,7 @@ declare function DataTableAdvancedToolbar<TData>({
1516
1528
  children,
1517
1529
  className,
1518
1530
  ...props
1519
- }: DataTableAdvancedToolbarProps<TData>): react_jsx_runtime5.JSX.Element;
1531
+ }: DataTableAdvancedToolbarProps<TData>): react_jsx_runtime3.JSX.Element;
1520
1532
  //#endregion
1521
1533
  //#region src/components/data-table/data-table-column-header.d.ts
1522
1534
  interface DataTableColumnHeaderProps<TData, TValue> extends React.ComponentProps<typeof DropdownMenuTrigger> {
@@ -1528,7 +1540,7 @@ declare function DataTableColumnHeader<TData, TValue>({
1528
1540
  label,
1529
1541
  className,
1530
1542
  ...props
1531
- }: DataTableColumnHeaderProps<TData, TValue>): react_jsx_runtime5.JSX.Element;
1543
+ }: DataTableColumnHeaderProps<TData, TValue>): react_jsx_runtime3.JSX.Element;
1532
1544
  //#endregion
1533
1545
  //#region src/components/data-table/data-table-faceted-filter.d.ts
1534
1546
  interface DataTableFacetedFilterProps<TData, TValue> {
@@ -1542,7 +1554,7 @@ declare function DataTableFacetedFilter<TData, TValue>({
1542
1554
  title,
1543
1555
  options,
1544
1556
  multiple
1545
- }: DataTableFacetedFilterProps<TData, TValue>): react_jsx_runtime5.JSX.Element;
1557
+ }: DataTableFacetedFilterProps<TData, TValue>): react_jsx_runtime3.JSX.Element;
1546
1558
  //#endregion
1547
1559
  //#region src/components/data-table/data-table-pagination.d.ts
1548
1560
  interface DataTablePaginationProps<TData> extends React.ComponentProps<"div"> {
@@ -1554,7 +1566,7 @@ declare function DataTablePagination<TData>({
1554
1566
  pageSizeOptions,
1555
1567
  className,
1556
1568
  ...props
1557
- }: DataTablePaginationProps<TData>): react_jsx_runtime5.JSX.Element;
1569
+ }: DataTablePaginationProps<TData>): react_jsx_runtime3.JSX.Element;
1558
1570
  //#endregion
1559
1571
  //#region src/components/data-table/data-table-toolbar.d.ts
1560
1572
  interface DataTableToolbarProps<TData> extends React$1.ComponentProps<"div"> {
@@ -1565,7 +1577,7 @@ declare function DataTableToolbar<TData>({
1565
1577
  children,
1566
1578
  className,
1567
1579
  ...props
1568
- }: DataTableToolbarProps<TData>): react_jsx_runtime5.JSX.Element;
1580
+ }: DataTableToolbarProps<TData>): react_jsx_runtime3.JSX.Element;
1569
1581
  //#endregion
1570
1582
  //#region src/components/data-table/data-table-view-options.d.ts
1571
1583
  interface DataTableViewOptionsProps<TData> extends React$1.ComponentProps<typeof PopoverContent> {
@@ -1576,7 +1588,7 @@ declare function DataTableViewOptions<TData>({
1576
1588
  table,
1577
1589
  disabled,
1578
1590
  ...props
1579
- }: DataTableViewOptionsProps<TData>): react_jsx_runtime5.JSX.Element;
1591
+ }: DataTableViewOptionsProps<TData>): react_jsx_runtime3.JSX.Element;
1580
1592
  //#endregion
1581
1593
  //#region src/lib/crud-actions.d.ts
1582
1594
  type CrudActionZone = 'toolbar' | 'row' | 'batch';
package/dist/index.d.ts CHANGED
@@ -3,7 +3,7 @@ import * as _tanstack_react_table0 from "@tanstack/react-table";
3
3
  import { Column, ColumnDef, ColumnSort, Row, RowData, Table, TableOptions, TableState } from "@tanstack/react-table";
4
4
  import { DropdownMenuTrigger, PopoverContent } from "@wordrhyme/shadcn";
5
5
  import { ClassValue } from "clsx";
6
- import * as react_jsx_runtime2 from "react/jsx-runtime";
6
+ import * as react_jsx_runtime5 from "react/jsx-runtime";
7
7
  import { MultiCombobox, MultiComboboxOption, MultiComboboxProps, MultiComboboxTriggerRenderProps, Select, SelectMode, SelectOption, SelectProps, SelectSearchableDynamicProps, SelectSearchableMultipleProps, SelectSearchableProps, SelectSearchableSingleProps, SelectSimpleProps, SelectTriggerRenderProps } from "@wordrhyme/shadcn-ui";
8
8
  import { z } from "zod";
9
9
  import { JsonSchemaFormScope } from "@wordrhyme/formily-shadcn";
@@ -395,7 +395,7 @@ declare function AutoTableActionBar<TData>({
395
395
  extraActions,
396
396
  actions,
397
397
  deleteConfirmation
398
- }: AutoTableActionBarProps<TData>): react_jsx_runtime2.JSX.Element;
398
+ }: AutoTableActionBarProps<TData>): react_jsx_runtime5.JSX.Element;
399
399
  //#endregion
400
400
  //#region src/lib/schema-bridge/types.d.ts
401
401
  /**
@@ -571,7 +571,7 @@ declare function AutoTable<T extends z.ZodObject<z.ZodRawShape>>({
571
571
  onSelectedCountChange,
572
572
  onSelectedRowsChange,
573
573
  getSelectedRows
574
- }: AutoTableProps<T>): react_jsx_runtime2.JSX.Element;
574
+ }: AutoTableProps<T>): react_jsx_runtime5.JSX.Element;
575
575
  //#endregion
576
576
  //#region src/i18n/locale.d.ts
577
577
  /**
@@ -684,12 +684,19 @@ type AutoCrudOption = {
684
684
  count?: number;
685
685
  disabled?: boolean;
686
686
  };
687
+ type AutoCrudDataSourceContextType = 'form' | 'filter' | 'resolve';
688
+ type AutoCrudDataSourceResolveFields = readonly string[] | ((context: {
689
+ field: string;
690
+ }) => readonly string[]);
687
691
  type AutoCrudDataSourceContext = {
688
692
  field: string;
693
+ type: AutoCrudDataSourceContextType;
689
694
  page?: number;
690
695
  pageSize?: number;
696
+ query?: string;
697
+ /** @deprecated Use `query` instead. */
691
698
  search?: string;
692
- values?: Record<string, unknown>;
699
+ values?: Record<string, unknown> | Record<string, unknown>[];
693
700
  signal?: AbortSignal;
694
701
  };
695
702
  type AutoCrudDataSourceResult = AutoCrudOption[] | {
@@ -705,6 +712,7 @@ type AutoCrudDataSourceRegistration = AutoCrudDataSourceLoader | {
705
712
  debounceMs?: number;
706
713
  loadMore?: boolean;
707
714
  pageSize?: number;
715
+ resolveFields?: AutoCrudDataSourceResolveFields;
708
716
  load: AutoCrudDataSourceLoader;
709
717
  };
710
718
  type AutoCrudDataSourceEntry = {
@@ -714,6 +722,7 @@ type AutoCrudDataSourceEntry = {
714
722
  debounceMs: number;
715
723
  loadMore: boolean;
716
724
  pageSize: number;
725
+ resolveFields?: AutoCrudDataSourceResolveFields;
717
726
  load: AutoCrudDataSourceLoader;
718
727
  };
719
728
  type RegistryListener$1 = () => void;
@@ -915,8 +924,11 @@ interface AutoCrudToolbarContext {
915
924
  selectedRowIds: string[];
916
925
  selectedCount: number;
917
926
  refresh?: () => Promise<unknown>;
918
- isRefreshing: boolean;
927
+ openImport?: () => void;
928
+ exportData?: () => Promise<void>;
919
929
  openCreate?: () => void;
930
+ isRefreshing: boolean;
931
+ isExporting: boolean;
920
932
  }
921
933
  type AutoCrudToolbarResolver = (targetId: string, ownerActions: readonly ToolbarActionItem[], context: AutoCrudToolbarContext) => ToolbarActionItem[];
922
934
  declare function setToolbarResolver(resolver: AutoCrudToolbarResolver | null): void;
@@ -1051,7 +1063,7 @@ declare function AutoCrudTable<TSchema extends z.ZodObject<z.ZodRawShape>>({
1051
1063
  toolbarActions,
1052
1064
  locale: localeProp,
1053
1065
  onCreate
1054
- }: AutoCrudTableProps<TSchema>): react_jsx_runtime2.JSX.Element;
1066
+ }: AutoCrudTableProps<TSchema>): react_jsx_runtime5.JSX.Element;
1055
1067
  //#endregion
1056
1068
  //#region src/components/auto-crud/auto-form.d.ts
1057
1069
  interface AutoFormProps<T extends z.ZodObject<z.ZodRawShape>> {
@@ -1090,7 +1102,7 @@ declare function AutoFormInner<T extends z.ZodObject<z.ZodRawShape>>({
1090
1102
  labelAlign,
1091
1103
  labelWidth,
1092
1104
  showSubmitButton
1093
- }: AutoFormProps<T>, ref: React.Ref<AutoFormRef>): react_jsx_runtime2.JSX.Element;
1105
+ }: AutoFormProps<T>, ref: React.Ref<AutoFormRef>): react_jsx_runtime5.JSX.Element;
1094
1106
  declare const AutoForm: <T extends z.ZodObject<z.ZodRawShape>>(props: AutoFormProps<T> & {
1095
1107
  ref?: React.Ref<AutoFormRef>;
1096
1108
  }) => ReturnType<typeof AutoFormInner>;
@@ -1406,7 +1418,7 @@ declare function AutoTableSimpleFilters<TData>({
1406
1418
  filters: externalFilters,
1407
1419
  onFiltersChange,
1408
1420
  leading
1409
- }: AutoTableSimpleFiltersProps<TData>): react_jsx_runtime2.JSX.Element | null;
1421
+ }: AutoTableSimpleFiltersProps<TData>): react_jsx_runtime5.JSX.Element | null;
1410
1422
  //#endregion
1411
1423
  //#region src/components/auto-crud/form-modal.d.ts
1412
1424
  type ModalVariant = "dialog" | "sheet";
@@ -1450,7 +1462,7 @@ declare function CrudFormModal<T extends z.ZodObject<z.ZodRawShape>>({
1450
1462
  labelAlign,
1451
1463
  labelWidth,
1452
1464
  className
1453
- }: CrudFormModalProps<T>): react_jsx_runtime2.JSX.Element;
1465
+ }: CrudFormModalProps<T>): react_jsx_runtime5.JSX.Element;
1454
1466
  //#endregion
1455
1467
  //#region src/components/auto-crud/import-dialog.d.ts
1456
1468
  interface ImportDialogProps {
@@ -1472,7 +1484,7 @@ declare function ImportDialog({
1472
1484
  columns,
1473
1485
  title,
1474
1486
  locale
1475
- }: ImportDialogProps): react_jsx_runtime2.JSX.Element;
1487
+ }: ImportDialogProps): react_jsx_runtime5.JSX.Element;
1476
1488
  //#endregion
1477
1489
  //#region src/components/auto-crud/export-dialog.d.ts
1478
1490
  type ExportMode = "selected" | "filtered";
@@ -1492,7 +1504,7 @@ declare function ExportDialog({
1492
1504
  selectedCount,
1493
1505
  onExport,
1494
1506
  canExportFiltered
1495
- }: ExportDialogProps): react_jsx_runtime2.JSX.Element;
1507
+ }: ExportDialogProps): react_jsx_runtime5.JSX.Element;
1496
1508
  //#endregion
1497
1509
  //#region src/components/data-table/data-table.d.ts
1498
1510
  interface DataTableProps<TData> extends React$1.ComponentProps<"div"> {
@@ -1505,7 +1517,7 @@ declare function DataTable<TData>({
1505
1517
  children,
1506
1518
  className,
1507
1519
  ...props
1508
- }: DataTableProps<TData>): react_jsx_runtime2.JSX.Element;
1520
+ }: DataTableProps<TData>): react_jsx_runtime5.JSX.Element;
1509
1521
  //#endregion
1510
1522
  //#region src/components/data-table/data-table-advanced-toolbar.d.ts
1511
1523
  interface DataTableAdvancedToolbarProps<TData> extends React$1.ComponentProps<"div"> {
@@ -1516,7 +1528,7 @@ declare function DataTableAdvancedToolbar<TData>({
1516
1528
  children,
1517
1529
  className,
1518
1530
  ...props
1519
- }: DataTableAdvancedToolbarProps<TData>): react_jsx_runtime2.JSX.Element;
1531
+ }: DataTableAdvancedToolbarProps<TData>): react_jsx_runtime5.JSX.Element;
1520
1532
  //#endregion
1521
1533
  //#region src/components/data-table/data-table-column-header.d.ts
1522
1534
  interface DataTableColumnHeaderProps<TData, TValue> extends React.ComponentProps<typeof DropdownMenuTrigger> {
@@ -1528,7 +1540,7 @@ declare function DataTableColumnHeader<TData, TValue>({
1528
1540
  label,
1529
1541
  className,
1530
1542
  ...props
1531
- }: DataTableColumnHeaderProps<TData, TValue>): react_jsx_runtime2.JSX.Element;
1543
+ }: DataTableColumnHeaderProps<TData, TValue>): react_jsx_runtime5.JSX.Element;
1532
1544
  //#endregion
1533
1545
  //#region src/components/data-table/data-table-faceted-filter.d.ts
1534
1546
  interface DataTableFacetedFilterProps<TData, TValue> {
@@ -1542,7 +1554,7 @@ declare function DataTableFacetedFilter<TData, TValue>({
1542
1554
  title,
1543
1555
  options,
1544
1556
  multiple
1545
- }: DataTableFacetedFilterProps<TData, TValue>): react_jsx_runtime2.JSX.Element;
1557
+ }: DataTableFacetedFilterProps<TData, TValue>): react_jsx_runtime5.JSX.Element;
1546
1558
  //#endregion
1547
1559
  //#region src/components/data-table/data-table-pagination.d.ts
1548
1560
  interface DataTablePaginationProps<TData> extends React.ComponentProps<"div"> {
@@ -1554,7 +1566,7 @@ declare function DataTablePagination<TData>({
1554
1566
  pageSizeOptions,
1555
1567
  className,
1556
1568
  ...props
1557
- }: DataTablePaginationProps<TData>): react_jsx_runtime2.JSX.Element;
1569
+ }: DataTablePaginationProps<TData>): react_jsx_runtime5.JSX.Element;
1558
1570
  //#endregion
1559
1571
  //#region src/components/data-table/data-table-toolbar.d.ts
1560
1572
  interface DataTableToolbarProps<TData> extends React$1.ComponentProps<"div"> {
@@ -1565,7 +1577,7 @@ declare function DataTableToolbar<TData>({
1565
1577
  children,
1566
1578
  className,
1567
1579
  ...props
1568
- }: DataTableToolbarProps<TData>): react_jsx_runtime2.JSX.Element;
1580
+ }: DataTableToolbarProps<TData>): react_jsx_runtime5.JSX.Element;
1569
1581
  //#endregion
1570
1582
  //#region src/components/data-table/data-table-view-options.d.ts
1571
1583
  interface DataTableViewOptionsProps<TData> extends React$1.ComponentProps<typeof PopoverContent> {
@@ -1576,7 +1588,7 @@ declare function DataTableViewOptions<TData>({
1576
1588
  table,
1577
1589
  disabled,
1578
1590
  ...props
1579
- }: DataTableViewOptionsProps<TData>): react_jsx_runtime2.JSX.Element;
1591
+ }: DataTableViewOptionsProps<TData>): react_jsx_runtime5.JSX.Element;
1580
1592
  //#endregion
1581
1593
  //#region src/lib/crud-actions.d.ts
1582
1594
  type CrudActionZone = 'toolbar' | 'row' | 'batch';
package/dist/index.js CHANGED
@@ -4328,6 +4328,7 @@ function normalizeDataSourceRegistration(registration) {
4328
4328
  debounceMs: typeof registration.debounceMs === "number" && registration.debounceMs >= 0 ? registration.debounceMs : 300,
4329
4329
  loadMore: registration.loadMore === true,
4330
4330
  pageSize: typeof registration.pageSize === "number" && registration.pageSize > 0 ? registration.pageSize : 20,
4331
+ resolveFields: registration.resolveFields,
4331
4332
  load: registration.load
4332
4333
  };
4333
4334
  }
@@ -4562,8 +4563,10 @@ function loadAutoCrudDataSource({ entry, field, registryVersion, search, sourceK
4562
4563
  }
4563
4564
  Promise.resolve(entry.load({
4564
4565
  field: field.path.toString(),
4566
+ type: "form",
4565
4567
  page,
4566
4568
  pageSize: entry.loadMore ? entry.pageSize : void 0,
4569
+ query: searchValue,
4567
4570
  search: searchValue,
4568
4571
  values,
4569
4572
  signal: controller?.signal
@@ -5852,8 +5855,8 @@ function haveSameRowSelection(left, right, idKey) {
5852
5855
  if (leftIds.length === left.length && rightIds.length === right.length) return areStringArraysEqual(leftIds, rightIds);
5853
5856
  return left.every((row, index$1) => row === right[index$1]);
5854
5857
  }
5855
- function readCreateAction(actions) {
5856
- return actions.find((action$1) => action$1.type === "create");
5858
+ function readToolbarAction(actions, type) {
5859
+ return actions.find((action$1) => action$1.type === type);
5857
5860
  }
5858
5861
  function resolveToolbarActionsWithResolver(targetId, ownerActions, context) {
5859
5862
  const resolver = autoCrudToolbarResolver;
@@ -5927,6 +5930,47 @@ function shouldLoadDynamicFilterOptions(config) {
5927
5930
  }
5928
5931
  return normalizeDataSourceConfig(getFilterDataSource(config)) !== void 0;
5929
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
+ }
5930
5974
  function useRegistryVersion(subscribe) {
5931
5975
  const [version$1, setVersion] = React.useState(0);
5932
5976
  React.useEffect(() => subscribe(() => setVersion((current) => current + 1)), [subscribe]);
@@ -6048,8 +6092,10 @@ function useDynamicFilterOptions(fields) {
6048
6092
  const timer = setTimeout(() => {
6049
6093
  Promise.resolve(entry.load({
6050
6094
  field,
6095
+ type: "filter",
6051
6096
  page,
6052
6097
  pageSize: entry.loadMore ? entry.pageSize : void 0,
6098
+ query: search,
6053
6099
  search,
6054
6100
  values: {},
6055
6101
  signal: controller?.signal
@@ -6142,6 +6188,117 @@ function useDynamicFilterOptions(fields) {
6142
6188
  setSearchValue
6143
6189
  };
6144
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
+ }
6145
6302
  function getOptionLabel(value, options) {
6146
6303
  const stringValue = String(value);
6147
6304
  return options?.find((option) => option.value === stringValue)?.label ?? stringValue;
@@ -6150,7 +6307,7 @@ function getOptionLabel(value, options) {
6150
6307
  * 从统一配置生成表格 overrides
6151
6308
  * 当 filter 配置存在时,合并到 meta 中(不影响列隐藏)
6152
6309
  */
6153
- function buildTableOverrides(fields, legacyOverrides, dynamicFilterState) {
6310
+ function buildTableOverrides(fields, legacyOverrides, dynamicFilterState, dynamicResolveState) {
6154
6311
  const result = { ...legacyOverrides };
6155
6312
  if (fields) for (const [key, config] of Object.entries(fields)) {
6156
6313
  const fieldOptions = normalizeFieldOptions(config.enum);
@@ -6158,7 +6315,8 @@ function buildTableOverrides(fields, legacyOverrides, dynamicFilterState) {
6158
6315
  const filterDataSourceRegistered = dynamicFilterState?.registeredByField[key];
6159
6316
  const filterDataSourceSearchable = dynamicFilterState?.searchableByField[key];
6160
6317
  const currentDynamicOptions = !fieldOptions && filterDataSourceRegistered ? toTableOptions(dynamicFilterState?.optionsByField[key] ?? []) : void 0;
6161
- 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;
6162
6320
  const tableMeta = config.table !== false && typeof config.table === "object" ? config.table.meta : void 0;
6163
6321
  const fieldEnumMeta = tableOptions ? {
6164
6322
  options: tableOptions,
@@ -6166,18 +6324,20 @@ function buildTableOverrides(fields, legacyOverrides, dynamicFilterState) {
6166
6324
  } : void 0;
6167
6325
  const fieldDataSourceMeta = dynamicOptions ? {
6168
6326
  options: dynamicOptions,
6169
- autoCrudFilterOptions: currentDynamicOptions ?? dynamicOptions,
6170
- autoCrudFilterHasMore: dynamicFilterState?.hasMoreByField[key] ?? false,
6171
- autoCrudFilterLoading: dynamicFilterState?.loadingByField[key] ?? false,
6172
- autoCrudFilterOnPopupScroll: (event) => {
6173
- if (!isNearPopupScrollBottom(event.currentTarget)) return;
6174
- dynamicFilterState?.loadMore(key);
6175
- },
6176
- variant: "multiSelect",
6177
- ...filterDataSourceSearchable ? {
6178
- autoCrudFilterSearchValue: dynamicFilterState?.searchValues[key] ?? "",
6179
- autoCrudFilterOnSearch: (value) => dynamicFilterState?.setSearchValue(key, value),
6180
- 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
6181
6341
  } : void 0
6182
6342
  } : void 0;
6183
6343
  let filterMeta;
@@ -6506,6 +6666,14 @@ function AutoCrudTable({ id, title, description, schema, resource, fields, table
6506
6666
  const [exporting, setExporting] = React.useState(false);
6507
6667
  const getSelectedRowsRef = React.useRef(null);
6508
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]);
6509
6677
  const resourceIdKey = resource.idKey ?? "id";
6510
6678
  const actionRegistryVersion = useCrudActionsVersion();
6511
6679
  const unifiedActions = React.useMemo(() => {
@@ -6539,28 +6707,10 @@ function AutoCrudTable({ id, title, description, schema, resource, fields, table
6539
6707
  const handleSelectedRowsChange = React.useCallback((rows) => {
6540
6708
  setSelectedRows((currentRows) => haveSameRowSelection(currentRows, rows, resourceIdKey) ? currentRows : rows);
6541
6709
  }, [resourceIdKey]);
6542
- const ownerCreateAction = React.useMemo(() => readCreateAction(ownerToolbarActions), [ownerToolbarActions]);
6543
- const toolbarOpenCreate = can.create ? ownerCreateAction?.onClick ?? onCreate ?? resource.handlers.openCreate : void 0;
6544
- const toolbarContext = React.useMemo(() => ({
6545
- crudId: id ?? "",
6546
- idKey: resourceIdKey,
6547
- rowIds,
6548
- selectedRowIds,
6549
- selectedCount,
6550
- isRefreshing: resource.tableData.isFetching,
6551
- ...resource.handlers.refresh ? { refresh: resource.handlers.refresh } : {},
6552
- ...toolbarOpenCreate ? { openCreate: toolbarOpenCreate } : {}
6553
- }), [
6554
- id,
6555
- resourceIdKey,
6556
- rowIds,
6557
- selectedRowIds,
6558
- selectedCount,
6559
- resource.handlers.refresh,
6560
- resource.tableData.isFetching,
6561
- toolbarOpenCreate
6562
- ]);
6563
- 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]);
6564
6714
  const importColumns = React.useMemo(() => {
6565
6715
  const shape = resolvedSchema.shape;
6566
6716
  const denySet = new Set([...DEFAULT_IMPORT_MANAGED_FIELDS, ...denyFields ?? []]);
@@ -6597,10 +6747,64 @@ function AutoCrudTable({ id, title, description, schema, resource, fields, table
6597
6747
  setExporting(false);
6598
6748
  }
6599
6749
  }, [selectedCount, handleExport]);
6600
- 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), [
6601
6804
  resolvedFields,
6602
6805
  tableConfig?.overrides,
6603
- dynamicFilterOptions
6806
+ dynamicFilterOptions,
6807
+ dynamicResolveOptions
6604
6808
  ]);
6605
6809
  const defaultSort = React.useMemo(() => resource.defaultSort ?? tableConfig?.defaultSort ?? getDefaultSortingForSchema(resolvedSchema), [
6606
6810
  resource.defaultSort,
@@ -6612,12 +6816,6 @@ function AutoCrudTable({ id, title, description, schema, resource, fields, table
6612
6816
  formConfig?.overrides,
6613
6817
  denyFields
6614
6818
  ]);
6615
- const hiddenColumns = React.useMemo(() => buildHiddenColumns(resolvedFields, tableConfig?.hidden, denyFields, tableConfig?.overrides), [
6616
- resolvedFields,
6617
- tableConfig?.hidden,
6618
- denyFields,
6619
- tableConfig?.overrides
6620
- ]);
6621
6819
  const searchConfig = React.useMemo(() => {
6622
6820
  const tableSearch = tableConfig?.search;
6623
6821
  if (tableSearch === false) return false;
@@ -6678,7 +6876,7 @@ function AutoCrudTable({ id, title, description, schema, resource, fields, table
6678
6876
  children: (() => {
6679
6877
  const renderBuiltinButton = (type, overrides) => {
6680
6878
  if (type === "refresh") {
6681
- const onRefresh = overrides?.onClick ?? resource.handlers.refresh;
6879
+ const onRefresh = overrides?.onClick ?? toolbarRefresh;
6682
6880
  const label = overrides?.label ?? locale.toolbar.refresh;
6683
6881
  if (!onRefresh) return null;
6684
6882
  return /* @__PURE__ */ jsx(Button, {
@@ -6694,18 +6892,18 @@ function AutoCrudTable({ id, title, description, schema, resource, fields, table
6694
6892
  if (type === "import" && canImport) return /* @__PURE__ */ jsxs(Button, {
6695
6893
  variant: "outline",
6696
6894
  size: "sm",
6697
- onClick: overrides?.onClick ?? (() => setImportOpen(true)),
6895
+ onClick: overrides?.onClick ?? toolbarOpenImport,
6698
6896
  children: [/* @__PURE__ */ jsx(Download, { className: "mr-2 h-4 w-4" }), overrides?.label ?? locale.toolbar.import]
6699
6897
  }, "import");
6700
6898
  if (type === "export" && canExport) return /* @__PURE__ */ jsxs(Button, {
6701
6899
  variant: "outline",
6702
6900
  size: "sm",
6703
- onClick: overrides?.onClick ?? handleExportClick,
6901
+ onClick: overrides?.onClick ?? toolbarExportData,
6704
6902
  disabled: exporting || selectedCount === 0 && !resource.handlers.export,
6705
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)]
6706
6904
  }, "export");
6707
6905
  if (type === "create" && can.create) return /* @__PURE__ */ jsx(Button, {
6708
- onClick: overrides?.onClick ?? onCreate ?? resource.handlers.openCreate,
6906
+ onClick: overrides?.onClick ?? toolbarOpenCreate,
6709
6907
  children: overrides?.label ?? locale.toolbar.create
6710
6908
  }, "create");
6711
6909
  return null;
@@ -6793,7 +6991,7 @@ function AutoCrudTable({ id, title, description, schema, resource, fields, table
6793
6991
  data: resource.modal.selected,
6794
6992
  schema: resolvedSchema,
6795
6993
  fields: resolvedFields,
6796
- dynamicOptions: dynamicFilterOptions.optionsByField,
6994
+ dynamicOptions: dynamicOptionsByField,
6797
6995
  denyFields,
6798
6996
  locale
6799
6997
  }),
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@wordrhyme/auto-crud",
3
3
  "type": "module",
4
- "version": "1.3.8",
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",
@@ -67,6 +67,8 @@
67
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"