@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 +29 -5
- package/dist/index.cjs +249 -51
- package/dist/index.d.cts +30 -18
- package/dist/index.d.ts +30 -18
- package/dist/index.js +249 -51
- package/package.json +4 -2
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"`
|
|
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
|
|
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
|
|
5893
|
-
return actions.find((action$1) => action$1.type ===
|
|
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
|
|
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
|
-
|
|
6207
|
-
|
|
6208
|
-
|
|
6209
|
-
|
|
6210
|
-
|
|
6211
|
-
|
|
6212
|
-
|
|
6213
|
-
|
|
6214
|
-
|
|
6215
|
-
|
|
6216
|
-
|
|
6217
|
-
|
|
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
|
|
6580
|
-
const
|
|
6581
|
-
const
|
|
6582
|
-
|
|
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
|
|
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 ??
|
|
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 ??
|
|
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 ??
|
|
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 ??
|
|
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:
|
|
7031
|
+
dynamicOptions: dynamicOptionsByField,
|
|
6834
7032
|
denyFields,
|
|
6835
7033
|
locale
|
|
6836
7034
|
}),
|
package/dist/index.d.cts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import * as
|
|
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>):
|
|
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>):
|
|
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
|
-
|
|
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>):
|
|
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>):
|
|
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>):
|
|
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>):
|
|
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):
|
|
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):
|
|
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>):
|
|
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>):
|
|
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>):
|
|
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>):
|
|
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>):
|
|
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>):
|
|
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>):
|
|
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
|
|
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>):
|
|
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>):
|
|
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
|
-
|
|
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>):
|
|
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>):
|
|
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>):
|
|
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>):
|
|
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):
|
|
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):
|
|
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>):
|
|
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>):
|
|
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>):
|
|
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>):
|
|
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>):
|
|
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>):
|
|
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>):
|
|
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
|
|
5856
|
-
return actions.find((action$1) => action$1.type ===
|
|
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
|
|
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
|
-
|
|
6170
|
-
|
|
6171
|
-
|
|
6172
|
-
|
|
6173
|
-
|
|
6174
|
-
|
|
6175
|
-
|
|
6176
|
-
|
|
6177
|
-
|
|
6178
|
-
|
|
6179
|
-
|
|
6180
|
-
|
|
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
|
|
6543
|
-
const
|
|
6544
|
-
const
|
|
6545
|
-
|
|
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
|
|
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 ??
|
|
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 ??
|
|
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 ??
|
|
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 ??
|
|
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:
|
|
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.
|
|
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"
|