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