@wordrhyme/auto-crud 1.3.8 → 1.4.0
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 +294 -52
- package/dist/index.d.cts +47 -19
- package/dist/index.d.ts +47 -19
- package/dist/index.js +294 -52
- package/package.json +5 -3
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;
|
|
@@ -6229,6 +6389,35 @@ function buildTableOverrides(fields, legacyOverrides, dynamicFilterState) {
|
|
|
6229
6389
|
}
|
|
6230
6390
|
return result;
|
|
6231
6391
|
}
|
|
6392
|
+
function buildCapabilityTableOverrides(schema, capabilities) {
|
|
6393
|
+
if (!capabilities) return {};
|
|
6394
|
+
const result = {};
|
|
6395
|
+
const filterFields = capabilities.filters?.fields;
|
|
6396
|
+
const sortFields = capabilities.sort?.fields;
|
|
6397
|
+
const filterSet = Array.isArray(filterFields) ? new Set(filterFields) : null;
|
|
6398
|
+
const sortSet = Array.isArray(sortFields) ? new Set(sortFields) : null;
|
|
6399
|
+
for (const key of Object.keys(schema.shape)) {
|
|
6400
|
+
const override = {};
|
|
6401
|
+
if (capabilities.filters) {
|
|
6402
|
+
if (!capabilities.filters.enabled) override.enableColumnFilter = false;
|
|
6403
|
+
else if (filterSet) override.enableColumnFilter = filterSet.has(key);
|
|
6404
|
+
}
|
|
6405
|
+
if (capabilities.sort) {
|
|
6406
|
+
if (!capabilities.sort.enabled) override.enableSorting = false;
|
|
6407
|
+
else if (sortSet) override.enableSorting = sortSet.has(key);
|
|
6408
|
+
}
|
|
6409
|
+
if (Object.keys(override).length > 0) result[key] = override;
|
|
6410
|
+
}
|
|
6411
|
+
return result;
|
|
6412
|
+
}
|
|
6413
|
+
function mergeTableOverrides(base, enforced) {
|
|
6414
|
+
const result = { ...base ?? {} };
|
|
6415
|
+
for (const [key, override] of Object.entries(enforced)) result[key] = {
|
|
6416
|
+
...result[key] ?? {},
|
|
6417
|
+
...override
|
|
6418
|
+
};
|
|
6419
|
+
return result;
|
|
6420
|
+
}
|
|
6232
6421
|
/**
|
|
6233
6422
|
* 从统一配置生成隐藏列列表
|
|
6234
6423
|
*/
|
|
@@ -6506,6 +6695,14 @@ function AutoCrudTable({ id, title, description, schema, resource, fields, table
|
|
|
6506
6695
|
const [exporting, setExporting] = React.useState(false);
|
|
6507
6696
|
const getSelectedRowsRef = React.useRef(null);
|
|
6508
6697
|
const dynamicFilterOptions = useDynamicFilterOptions(resolvedFields);
|
|
6698
|
+
const hiddenColumns = React.useMemo(() => buildHiddenColumns(resolvedFields, tableConfig?.hidden, denyFields, tableConfig?.overrides), [
|
|
6699
|
+
resolvedFields,
|
|
6700
|
+
tableConfig?.hidden,
|
|
6701
|
+
denyFields,
|
|
6702
|
+
tableConfig?.overrides
|
|
6703
|
+
]);
|
|
6704
|
+
const dynamicResolveOptions = useDynamicResolveOptions(resolvedFields, resource.tableData.data, hiddenColumns);
|
|
6705
|
+
const dynamicOptionsByField = React.useMemo(() => mergeOptionsByField(dynamicFilterOptions.labelOptionsByField, dynamicResolveOptions.optionsByField), [dynamicFilterOptions.labelOptionsByField, dynamicResolveOptions.optionsByField]);
|
|
6509
6706
|
const resourceIdKey = resource.idKey ?? "id";
|
|
6510
6707
|
const actionRegistryVersion = useCrudActionsVersion();
|
|
6511
6708
|
const unifiedActions = React.useMemo(() => {
|
|
@@ -6539,28 +6736,10 @@ function AutoCrudTable({ id, title, description, schema, resource, fields, table
|
|
|
6539
6736
|
const handleSelectedRowsChange = React.useCallback((rows) => {
|
|
6540
6737
|
setSelectedRows((currentRows) => haveSameRowSelection(currentRows, rows, resourceIdKey) ? currentRows : rows);
|
|
6541
6738
|
}, [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);
|
|
6739
|
+
const ownerRefreshAction = React.useMemo(() => readToolbarAction(ownerToolbarActions, "refresh"), [ownerToolbarActions]);
|
|
6740
|
+
const ownerImportAction = React.useMemo(() => readToolbarAction(ownerToolbarActions, "import"), [ownerToolbarActions]);
|
|
6741
|
+
const ownerExportAction = React.useMemo(() => readToolbarAction(ownerToolbarActions, "export"), [ownerToolbarActions]);
|
|
6742
|
+
const ownerCreateAction = React.useMemo(() => readToolbarAction(ownerToolbarActions, "create"), [ownerToolbarActions]);
|
|
6564
6743
|
const importColumns = React.useMemo(() => {
|
|
6565
6744
|
const shape = resolvedSchema.shape;
|
|
6566
6745
|
const denySet = new Set([...DEFAULT_IMPORT_MANAGED_FIELDS, ...denyFields ?? []]);
|
|
@@ -6597,10 +6776,66 @@ function AutoCrudTable({ id, title, description, schema, resource, fields, table
|
|
|
6597
6776
|
setExporting(false);
|
|
6598
6777
|
}
|
|
6599
6778
|
}, [selectedCount, handleExport]);
|
|
6600
|
-
const
|
|
6779
|
+
const toolbarRefresh = React.useMemo(() => {
|
|
6780
|
+
const refresh = ownerRefreshAction?.onClick ?? resource.handlers.refresh;
|
|
6781
|
+
if (!refresh) return void 0;
|
|
6782
|
+
return async () => refresh();
|
|
6783
|
+
}, [ownerRefreshAction?.onClick, resource.handlers.refresh]);
|
|
6784
|
+
const toolbarOpenImport = React.useMemo(() => {
|
|
6785
|
+
if (!canImport) return void 0;
|
|
6786
|
+
return ownerImportAction?.onClick ?? (() => setImportOpen(true));
|
|
6787
|
+
}, [canImport, ownerImportAction?.onClick]);
|
|
6788
|
+
const toolbarExportData = React.useMemo(() => {
|
|
6789
|
+
if (!canExport) return void 0;
|
|
6790
|
+
const exportData = ownerExportAction?.onClick ?? handleExportClick;
|
|
6791
|
+
return async () => exportData();
|
|
6792
|
+
}, [
|
|
6793
|
+
canExport,
|
|
6794
|
+
handleExportClick,
|
|
6795
|
+
ownerExportAction?.onClick
|
|
6796
|
+
]);
|
|
6797
|
+
const toolbarOpenCreate = React.useMemo(() => {
|
|
6798
|
+
if (!can.create) return void 0;
|
|
6799
|
+
return ownerCreateAction?.onClick ?? onCreate ?? resource.handlers.openCreate;
|
|
6800
|
+
}, [
|
|
6801
|
+
can.create,
|
|
6802
|
+
onCreate,
|
|
6803
|
+
ownerCreateAction?.onClick,
|
|
6804
|
+
resource.handlers.openCreate
|
|
6805
|
+
]);
|
|
6806
|
+
const toolbarContext = React.useMemo(() => ({
|
|
6807
|
+
crudId: id ?? "",
|
|
6808
|
+
idKey: resourceIdKey,
|
|
6809
|
+
rowIds,
|
|
6810
|
+
selectedRowIds,
|
|
6811
|
+
selectedCount,
|
|
6812
|
+
isRefreshing: resource.tableData.isFetching,
|
|
6813
|
+
isExporting: exporting,
|
|
6814
|
+
...toolbarRefresh ? { refresh: toolbarRefresh } : {},
|
|
6815
|
+
...toolbarOpenImport ? { openImport: toolbarOpenImport } : {},
|
|
6816
|
+
...toolbarExportData ? { exportData: toolbarExportData } : {},
|
|
6817
|
+
...toolbarOpenCreate ? { openCreate: toolbarOpenCreate } : {}
|
|
6818
|
+
}), [
|
|
6819
|
+
id,
|
|
6820
|
+
resourceIdKey,
|
|
6821
|
+
rowIds,
|
|
6822
|
+
selectedRowIds,
|
|
6823
|
+
selectedCount,
|
|
6824
|
+
resource.tableData.isFetching,
|
|
6825
|
+
exporting,
|
|
6826
|
+
toolbarRefresh,
|
|
6827
|
+
toolbarOpenImport,
|
|
6828
|
+
toolbarExportData,
|
|
6829
|
+
toolbarOpenCreate
|
|
6830
|
+
]);
|
|
6831
|
+
const resolvedToolbarActions = resolveToolbarActionsWithResolver(id, registryToolbarActions, toolbarContext);
|
|
6832
|
+
const tableOverrides = React.useMemo(() => mergeTableOverrides(buildTableOverrides(resolvedFields, tableConfig?.overrides, dynamicFilterOptions, dynamicResolveOptions), buildCapabilityTableOverrides(resolvedSchema, resource.capabilities)), [
|
|
6601
6833
|
resolvedFields,
|
|
6602
6834
|
tableConfig?.overrides,
|
|
6603
|
-
dynamicFilterOptions
|
|
6835
|
+
dynamicFilterOptions,
|
|
6836
|
+
dynamicResolveOptions,
|
|
6837
|
+
resolvedSchema,
|
|
6838
|
+
resource.capabilities
|
|
6604
6839
|
]);
|
|
6605
6840
|
const defaultSort = React.useMemo(() => resource.defaultSort ?? tableConfig?.defaultSort ?? getDefaultSortingForSchema(resolvedSchema), [
|
|
6606
6841
|
resource.defaultSort,
|
|
@@ -6612,18 +6847,23 @@ function AutoCrudTable({ id, title, description, schema, resource, fields, table
|
|
|
6612
6847
|
formConfig?.overrides,
|
|
6613
6848
|
denyFields
|
|
6614
6849
|
]);
|
|
6615
|
-
const hiddenColumns = React.useMemo(() => buildHiddenColumns(resolvedFields, tableConfig?.hidden, denyFields, tableConfig?.overrides), [
|
|
6616
|
-
resolvedFields,
|
|
6617
|
-
tableConfig?.hidden,
|
|
6618
|
-
denyFields,
|
|
6619
|
-
tableConfig?.overrides
|
|
6620
|
-
]);
|
|
6621
6850
|
const searchConfig = React.useMemo(() => {
|
|
6622
6851
|
const tableSearch = tableConfig?.search;
|
|
6852
|
+
const searchCapability = resource.capabilities?.search;
|
|
6623
6853
|
if (tableSearch === false) return false;
|
|
6854
|
+
if (searchCapability) {
|
|
6855
|
+
if (!searchCapability.enabled) return false;
|
|
6856
|
+
if (tableSearch && typeof tableSearch === "object") return tableSearch;
|
|
6857
|
+
return true;
|
|
6858
|
+
}
|
|
6624
6859
|
if (tableSearch && typeof tableSearch === "object") return tableSearch;
|
|
6860
|
+
if (tableSearch === true) return true;
|
|
6625
6861
|
return Object.values(resolvedFields).some((field) => field?.search === true) ? true : false;
|
|
6626
|
-
}, [
|
|
6862
|
+
}, [
|
|
6863
|
+
resolvedFields,
|
|
6864
|
+
resource.capabilities?.search,
|
|
6865
|
+
tableConfig?.search
|
|
6866
|
+
]);
|
|
6627
6867
|
const batchFields = React.useMemo(() => buildBatchUpdateFields(resolvedSchema, tableConfig?.batchFields, resolvedFields), [
|
|
6628
6868
|
resolvedSchema,
|
|
6629
6869
|
tableConfig?.batchFields,
|
|
@@ -6678,7 +6918,7 @@ function AutoCrudTable({ id, title, description, schema, resource, fields, table
|
|
|
6678
6918
|
children: (() => {
|
|
6679
6919
|
const renderBuiltinButton = (type, overrides) => {
|
|
6680
6920
|
if (type === "refresh") {
|
|
6681
|
-
const onRefresh = overrides?.onClick ??
|
|
6921
|
+
const onRefresh = overrides?.onClick ?? toolbarRefresh;
|
|
6682
6922
|
const label = overrides?.label ?? locale.toolbar.refresh;
|
|
6683
6923
|
if (!onRefresh) return null;
|
|
6684
6924
|
return /* @__PURE__ */ jsx(Button, {
|
|
@@ -6694,18 +6934,18 @@ function AutoCrudTable({ id, title, description, schema, resource, fields, table
|
|
|
6694
6934
|
if (type === "import" && canImport) return /* @__PURE__ */ jsxs(Button, {
|
|
6695
6935
|
variant: "outline",
|
|
6696
6936
|
size: "sm",
|
|
6697
|
-
onClick: overrides?.onClick ??
|
|
6937
|
+
onClick: overrides?.onClick ?? toolbarOpenImport,
|
|
6698
6938
|
children: [/* @__PURE__ */ jsx(Download, { className: "mr-2 h-4 w-4" }), overrides?.label ?? locale.toolbar.import]
|
|
6699
6939
|
}, "import");
|
|
6700
6940
|
if (type === "export" && canExport) return /* @__PURE__ */ jsxs(Button, {
|
|
6701
6941
|
variant: "outline",
|
|
6702
6942
|
size: "sm",
|
|
6703
|
-
onClick: overrides?.onClick ??
|
|
6943
|
+
onClick: overrides?.onClick ?? toolbarExportData,
|
|
6704
6944
|
disabled: exporting || selectedCount === 0 && !resource.handlers.export,
|
|
6705
6945
|
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
6946
|
}, "export");
|
|
6707
6947
|
if (type === "create" && can.create) return /* @__PURE__ */ jsx(Button, {
|
|
6708
|
-
onClick: overrides?.onClick ??
|
|
6948
|
+
onClick: overrides?.onClick ?? toolbarOpenCreate,
|
|
6709
6949
|
children: overrides?.label ?? locale.toolbar.create
|
|
6710
6950
|
}, "create");
|
|
6711
6951
|
return null;
|
|
@@ -6793,7 +7033,7 @@ function AutoCrudTable({ id, title, description, schema, resource, fields, table
|
|
|
6793
7033
|
data: resource.modal.selected,
|
|
6794
7034
|
schema: resolvedSchema,
|
|
6795
7035
|
fields: resolvedFields,
|
|
6796
|
-
dynamicOptions:
|
|
7036
|
+
dynamicOptions: dynamicOptionsByField,
|
|
6797
7037
|
denyFields,
|
|
6798
7038
|
locale
|
|
6799
7039
|
}),
|
|
@@ -7576,6 +7816,7 @@ function useAutoCrudResource({ router, schema, query: queryTransform, options =
|
|
|
7576
7816
|
const resolvedSchema = useMemo(() => mergeMetadataSchema(schema, metadata?.schema), [schema, metadata?.schema]);
|
|
7577
7817
|
const defaultSorting = useMemo(() => defaultSortOption === false ? [] : defaultSortOption ?? getDefaultSortingForSchema(resolvedSchema), [defaultSortOption, resolvedSchema]);
|
|
7578
7818
|
const metadataFields = useMemo(() => readMetadataFields(metadata?.fields), [metadata?.fields]);
|
|
7819
|
+
const capabilities = metadata?.capabilities;
|
|
7579
7820
|
const columns = useMemo(() => createTableSchema(resolvedSchema), [resolvedSchema]);
|
|
7580
7821
|
const [urlParams] = useQueryStates({
|
|
7581
7822
|
page: parseAsInteger.withDefault(1),
|
|
@@ -7868,6 +8109,7 @@ function useAutoCrudResource({ router, schema, query: queryTransform, options =
|
|
|
7868
8109
|
},
|
|
7869
8110
|
schema: resolvedSchema,
|
|
7870
8111
|
fields: metadataFields,
|
|
8112
|
+
capabilities,
|
|
7871
8113
|
defaultSort: defaultSorting,
|
|
7872
8114
|
modal,
|
|
7873
8115
|
mutations: {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wordrhyme/auto-crud",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.4.0",
|
|
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",
|
|
@@ -84,9 +86,9 @@
|
|
|
84
86
|
"vitest": "^3.2.4",
|
|
85
87
|
"@internal/eslint-config": "0.3.0",
|
|
86
88
|
"@internal/prettier-config": "0.0.1",
|
|
89
|
+
"@internal/vitest-config": "0.1.0",
|
|
87
90
|
"@internal/tsconfig": "0.1.0",
|
|
88
|
-
"@internal/tsdown-config": "0.1.0"
|
|
89
|
-
"@internal/vitest-config": "0.1.0"
|
|
91
|
+
"@internal/tsdown-config": "0.1.0"
|
|
90
92
|
},
|
|
91
93
|
"prettier": "@internal/prettier-config",
|
|
92
94
|
"scripts": {
|