@wordrhyme/auto-crud 1.1.7 → 1.2.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/dist/index.cjs CHANGED
@@ -5232,6 +5232,67 @@ function createEditFormSchema(schema, options) {
5232
5232
  });
5233
5233
  }
5234
5234
 
5235
+ //#endregion
5236
+ //#region src/lib/registries.ts
5237
+ function createRegistry(label) {
5238
+ const entries = /* @__PURE__ */ new Map();
5239
+ const listeners = /* @__PURE__ */ new Set();
5240
+ const notify = () => {
5241
+ queueMicrotask(() => {
5242
+ for (const listener of listeners) listener();
5243
+ });
5244
+ };
5245
+ return {
5246
+ register(name, value) {
5247
+ if (entries.has(name)) {
5248
+ console.warn(`[AutoCrud] ${label} "${name}" is already registered.`);
5249
+ return;
5250
+ }
5251
+ entries.set(name, value);
5252
+ notify();
5253
+ },
5254
+ unregister(name) {
5255
+ if (!entries.delete(name)) return;
5256
+ notify();
5257
+ },
5258
+ get(name) {
5259
+ return entries.get(name);
5260
+ },
5261
+ all() {
5262
+ return Object.fromEntries(entries);
5263
+ },
5264
+ subscribe(listener) {
5265
+ listeners.add(listener);
5266
+ return () => listeners.delete(listener);
5267
+ }
5268
+ };
5269
+ }
5270
+ const formComponents = createRegistry("form component");
5271
+ const dataSources = createRegistry("data source");
5272
+ function normalizeDataSourceConfig(config) {
5273
+ if (typeof config === "string") return config.length > 0 ? {
5274
+ key: config,
5275
+ dependsOn: [],
5276
+ resetOnChange: false
5277
+ } : void 0;
5278
+ if (!config?.key) return void 0;
5279
+ return {
5280
+ key: config.key,
5281
+ dependsOn: Array.isArray(config.dependsOn) ? config.dependsOn : [],
5282
+ resetOnChange: config.resetOnChange === true
5283
+ };
5284
+ }
5285
+ function normalizeOptions(result) {
5286
+ const options = Array.isArray(result) ? result : result?.options;
5287
+ if (!Array.isArray(options)) return [];
5288
+ return options.filter((option) => {
5289
+ return typeof option === "object" && option !== null && typeof option.value === "string" && typeof option.label === "string";
5290
+ }).map((option) => ({
5291
+ ...option,
5292
+ value: String(option.value)
5293
+ }));
5294
+ }
5295
+
5235
5296
  //#endregion
5236
5297
  //#region src/components/auto-crud/auto-form.tsx
5237
5298
  const COMBOBOX_LIST_CLASS = "[&_[cmdk-list]]:max-h-[360px] [&_[cmdk-list]]:overflow-x-hidden [&_[cmdk-list]]:overflow-y-auto";
@@ -5266,6 +5327,104 @@ function AutoCrudMultiCombobox({ options = [],...props }) {
5266
5327
  options
5267
5328
  });
5268
5329
  }
5330
+ const defaultFieldComponents = {
5331
+ Combobox: {
5332
+ component: FormilyCombobox,
5333
+ decorator: "FormItem"
5334
+ },
5335
+ MultiCombobox: {
5336
+ component: FormilyMultiCombobox,
5337
+ decorator: "FormItem"
5338
+ },
5339
+ Switch: {
5340
+ component: FormilySwitch,
5341
+ decorator: "FormItem"
5342
+ }
5343
+ };
5344
+ function useRegistryVersion$1(subscribe) {
5345
+ const [version, setVersion] = (0, react.useState)(0);
5346
+ (0, react.useEffect)(() => subscribe(() => setVersion((current) => current + 1)), [subscribe]);
5347
+ return version;
5348
+ }
5349
+ function isDataSourceConfig(value) {
5350
+ if (typeof value === "string") return value.length > 0;
5351
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
5352
+ return typeof value.key === "string";
5353
+ }
5354
+ function collectDataSourceConfigs(schema, result = {}) {
5355
+ const properties = schema.properties;
5356
+ if (!properties || typeof properties !== "object") return result;
5357
+ for (const [key, property] of Object.entries(properties)) {
5358
+ if (!property || typeof property !== "object" || Array.isArray(property)) continue;
5359
+ const record = property;
5360
+ const customDataSource = record["x-data-source"];
5361
+ const schemaDataSource = record.dataSource;
5362
+ const dataSourceConfig = isDataSourceConfig(customDataSource) ? customDataSource : isDataSourceConfig(schemaDataSource) ? schemaDataSource : void 0;
5363
+ if (dataSourceConfig) result[key] = dataSourceConfig;
5364
+ collectDataSourceConfigs(record, result);
5365
+ }
5366
+ return result;
5367
+ }
5368
+ function useFormDataSources(form, schema) {
5369
+ (0, react.useEffect)(() => {
5370
+ const sourceEntries = Object.entries(collectDataSourceConfigs(schema)).map(([fieldName, config]) => {
5371
+ const normalized = normalizeDataSourceConfig(config);
5372
+ return normalized ? [fieldName, normalized] : void 0;
5373
+ }).filter((entry) => entry !== void 0);
5374
+ if (sourceEntries.length === 0) return;
5375
+ let active = true;
5376
+ const effectId = Symbol("autoCrudDataSources");
5377
+ const loadVersions = /* @__PURE__ */ new Map();
5378
+ const controller = typeof AbortController === "undefined" ? void 0 : new AbortController();
5379
+ const loadFieldOptions = async (fieldName, source) => {
5380
+ const version = (loadVersions.get(fieldName) ?? 0) + 1;
5381
+ loadVersions.set(fieldName, version);
5382
+ const loader = dataSources.get(source.key);
5383
+ if (!loader) {
5384
+ form.setFieldState(fieldName, (state) => {
5385
+ state.dataSource = [];
5386
+ });
5387
+ return;
5388
+ }
5389
+ try {
5390
+ const options = normalizeOptions(await loader({
5391
+ field: fieldName,
5392
+ values: Object.fromEntries(source.dependsOn.map((dependency) => [dependency, form.getValuesIn(dependency)])),
5393
+ signal: controller?.signal
5394
+ }));
5395
+ if (!active || loadVersions.get(fieldName) !== version) return;
5396
+ form.setFieldState(fieldName, (state) => {
5397
+ state.dataSource = options;
5398
+ });
5399
+ } catch (error) {
5400
+ if (!active || controller?.signal.aborted) return;
5401
+ console.warn(`[AutoCrud] Failed to load data source "${source.key}".`, error);
5402
+ form.setFieldState(fieldName, (state) => {
5403
+ state.dataSource = [];
5404
+ });
5405
+ }
5406
+ };
5407
+ for (const [fieldName, source] of sourceEntries) loadFieldOptions(fieldName, source);
5408
+ form.addEffects(effectId, () => {
5409
+ for (const dependency of new Set(sourceEntries.flatMap(([, source]) => source.dependsOn))) (0, __formily_core.onFieldValueChange)(dependency, () => {
5410
+ for (const [fieldName, source] of sourceEntries) {
5411
+ if (!source.dependsOn.includes(dependency)) continue;
5412
+ if (source.resetOnChange) form.setValuesIn(fieldName, void 0);
5413
+ loadFieldOptions(fieldName, source);
5414
+ }
5415
+ });
5416
+ });
5417
+ return () => {
5418
+ active = false;
5419
+ controller?.abort();
5420
+ form.removeEffects(effectId);
5421
+ };
5422
+ }, [
5423
+ form,
5424
+ schema,
5425
+ useRegistryVersion$1(dataSources.subscribe)
5426
+ ]);
5427
+ }
5269
5428
  function AutoFormInner({ schema: zodSchema, initialValues, onSubmit, overrides, mode = "create", loading = false, gridColumns = 1, labelAlign = "top", labelWidth, showSubmitButton = true }, ref) {
5270
5429
  const form = (0, react.useMemo)(() => (0, __formily_core.createForm)({ initialValues }), [JSON.stringify(initialValues)]);
5271
5430
  const formSchema = (0, react.useMemo)(() => createEditFormSchema(zodSchema, {
@@ -5281,6 +5440,11 @@ function AutoFormInner({ schema: zodSchema, initialValues, onSubmit, overrides,
5281
5440
  labelAlign,
5282
5441
  labelWidth
5283
5442
  ]);
5443
+ const fieldComponents = (0, react.useMemo)(() => ({ fields: {
5444
+ ...defaultFieldComponents,
5445
+ ...formComponents.all()
5446
+ } }), [useRegistryVersion$1(formComponents.subscribe)]);
5447
+ useFormDataSources(form, formSchema);
5284
5448
  const handleSubmit = async () => {
5285
5449
  await form.validate();
5286
5450
  if (form.valid) await onSubmit(form.values);
@@ -5290,20 +5454,7 @@ function AutoFormInner({ schema: zodSchema, initialValues, onSubmit, overrides,
5290
5454
  form,
5291
5455
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(__pixpilot_formily_shadcn.JsonSchemaField, {
5292
5456
  schema: formSchema,
5293
- components: { fields: {
5294
- Combobox: {
5295
- component: FormilyCombobox,
5296
- decorator: "FormItem"
5297
- },
5298
- MultiCombobox: {
5299
- component: FormilyMultiCombobox,
5300
- decorator: "FormItem"
5301
- },
5302
- Switch: {
5303
- component: FormilySwitch,
5304
- decorator: "FormItem"
5305
- }
5306
- } }
5457
+ components: fieldComponents
5307
5458
  }), showSubmitButton && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
5308
5459
  className: "flex justify-end gap-2 mt-6",
5309
5460
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__pixpilot_shadcn.Button, {
@@ -6117,6 +6268,7 @@ function mergeFieldConfig(base, override) {
6117
6268
  ...base,
6118
6269
  ...override,
6119
6270
  enum: override?.enum ?? base?.enum,
6271
+ dataSource: override?.dataSource ?? base?.dataSource,
6120
6272
  table: mergeFieldPart(base?.table, override?.table),
6121
6273
  filter: mergeFieldPart(base?.filter, override?.filter),
6122
6274
  form: mergeFieldPart(base?.form, override?.form)
@@ -6149,6 +6301,69 @@ function toBatchOptions(options) {
6149
6301
  value
6150
6302
  }));
6151
6303
  }
6304
+ function getFilterDataSource(config) {
6305
+ if (config.filter && typeof config.filter === "object") return config.filter.dataSource ?? config.dataSource;
6306
+ return config.dataSource;
6307
+ }
6308
+ function shouldLoadDynamicFilterOptions(config) {
6309
+ if (config.filter === false) return false;
6310
+ if (normalizeFieldOptions(config.enum)) return false;
6311
+ if (config.filter && typeof config.filter === "object") {
6312
+ if (config.filter.enabled === false || config.filter.hidden === true) return false;
6313
+ if (normalizeFieldOptions(config.filter.options)) return false;
6314
+ }
6315
+ return normalizeDataSourceConfig(getFilterDataSource(config)) !== void 0;
6316
+ }
6317
+ function useRegistryVersion(subscribe) {
6318
+ const [version, setVersion] = react.useState(0);
6319
+ react.useEffect(() => subscribe(() => setVersion((current) => current + 1)), [subscribe]);
6320
+ return version;
6321
+ }
6322
+ function useDynamicFilterOptions(fields) {
6323
+ const registryVersion = useRegistryVersion(dataSources.subscribe);
6324
+ const [optionsByField, setOptionsByField] = react.useState({});
6325
+ const sourceEntries = react.useMemo(() => {
6326
+ if (!fields) return [];
6327
+ return Object.entries(fields).flatMap(([field, config]) => {
6328
+ if (!shouldLoadDynamicFilterOptions(config)) return [];
6329
+ const source = normalizeDataSourceConfig(getFilterDataSource(config));
6330
+ return source ? [{
6331
+ field,
6332
+ source
6333
+ }] : [];
6334
+ });
6335
+ }, [fields]);
6336
+ react.useEffect(() => {
6337
+ if (sourceEntries.length === 0) {
6338
+ setOptionsByField({});
6339
+ return;
6340
+ }
6341
+ let active = true;
6342
+ const controller = typeof AbortController === "undefined" ? void 0 : new AbortController();
6343
+ Promise.all(sourceEntries.map(async ({ field, source }) => {
6344
+ const loader = dataSources.get(source.key);
6345
+ if (!loader) return [field, []];
6346
+ try {
6347
+ return [field, normalizeOptions(await loader({
6348
+ field,
6349
+ values: {},
6350
+ signal: controller?.signal
6351
+ }))];
6352
+ } catch (error) {
6353
+ if (!controller?.signal.aborted) console.warn(`[AutoCrud] Failed to load filter data source "${source.key}".`, error);
6354
+ return [field, []];
6355
+ }
6356
+ })).then((entries) => {
6357
+ if (!active) return;
6358
+ setOptionsByField(Object.fromEntries(entries));
6359
+ });
6360
+ return () => {
6361
+ active = false;
6362
+ controller?.abort();
6363
+ };
6364
+ }, [sourceEntries, registryVersion]);
6365
+ return optionsByField;
6366
+ }
6152
6367
  function getOptionLabel(value, options) {
6153
6368
  const stringValue = String(value);
6154
6369
  return options?.find((option) => option.value === stringValue)?.label ?? stringValue;
@@ -6157,15 +6372,21 @@ function getOptionLabel(value, options) {
6157
6372
  * 从统一配置生成表格 overrides
6158
6373
  * 当 filter 配置存在时,合并到 meta 中(不影响列隐藏)
6159
6374
  */
6160
- function buildTableOverrides(fields, legacyOverrides) {
6375
+ function buildTableOverrides(fields, legacyOverrides, dynamicFilterOptions) {
6161
6376
  const result = { ...legacyOverrides };
6162
6377
  if (fields) for (const [key, config] of Object.entries(fields)) {
6163
- const tableOptions = toTableOptions(normalizeFieldOptions(config.enum));
6378
+ const fieldOptions = normalizeFieldOptions(config.enum);
6379
+ const tableOptions = toTableOptions(fieldOptions);
6380
+ const dynamicOptions = !fieldOptions ? toTableOptions(normalizeFieldOptions(dynamicFilterOptions?.[key])) : void 0;
6164
6381
  const tableMeta = config.table !== false && typeof config.table === "object" ? config.table.meta : void 0;
6165
6382
  const fieldEnumMeta = tableOptions ? {
6166
6383
  options: tableOptions,
6167
6384
  variant: "multiSelect"
6168
6385
  } : void 0;
6386
+ const fieldDataSourceMeta = dynamicOptions ? {
6387
+ options: dynamicOptions,
6388
+ variant: "multiSelect"
6389
+ } : void 0;
6169
6390
  let filterMeta;
6170
6391
  if (config.filter && typeof config.filter === "object") {
6171
6392
  if (config.filter.enabled !== false && config.filter.hidden !== true) {
@@ -6183,11 +6404,12 @@ function buildTableOverrides(fields, legacyOverrides) {
6183
6404
  enableColumnFilter: false
6184
6405
  };
6185
6406
  }
6186
- if (fieldEnumMeta || tableMeta || filterMeta) result[key] = {
6407
+ if (fieldEnumMeta || fieldDataSourceMeta || tableMeta || filterMeta) result[key] = {
6187
6408
  ...result[key],
6188
6409
  meta: {
6189
6410
  ...result[key]?.meta ?? {},
6190
6411
  ...fieldEnumMeta ?? {},
6412
+ ...fieldDataSourceMeta ?? {},
6191
6413
  ...tableMeta ?? {},
6192
6414
  ...filterMeta ?? {}
6193
6415
  }
@@ -6225,7 +6447,8 @@ function buildFormOverrides(fields, legacyOverrides, denyFields) {
6225
6447
  "x-hidden": true
6226
6448
  };
6227
6449
  if (fields) for (const [key, config] of Object.entries(fields)) {
6228
- const fieldOptions = normalizeFieldOptions(config.enum);
6450
+ const fieldOptions = normalizeFieldOptions((config.form && typeof config.form === "object" ? config.form : void 0)?.enum) ?? normalizeFieldOptions(config.enum);
6451
+ const fieldDataSource = fieldOptions || !config.dataSource ? void 0 : normalizeDataSourceConfig(config.dataSource);
6229
6452
  if (config.label) result[key] = {
6230
6453
  ...result[key],
6231
6454
  title: config.label
@@ -6238,6 +6461,10 @@ function buildFormOverrides(fields, legacyOverrides, denyFields) {
6238
6461
  ...result[key],
6239
6462
  enum: fieldOptions
6240
6463
  };
6464
+ if (fieldDataSource) result[key] = {
6465
+ ...result[key],
6466
+ "x-data-source": config.dataSource
6467
+ };
6241
6468
  if (config.form === false) result[key] = {
6242
6469
  ...result[key],
6243
6470
  "x-hidden": true
@@ -6495,6 +6722,7 @@ function AutoCrudTable({ title, description, schema, resource, fields, table: ta
6495
6722
  const [selectedCount, setSelectedCount] = react.useState(0);
6496
6723
  const [exporting, setExporting] = react.useState(false);
6497
6724
  const getSelectedRowsRef = react.useRef(null);
6725
+ const dynamicFilterOptions = useDynamicFilterOptions(resolvedFields);
6498
6726
  const importColumns = react.useMemo(() => {
6499
6727
  const shape = resolvedSchema.shape;
6500
6728
  const denySet = new Set(denyFields ?? []);
@@ -6531,7 +6759,11 @@ function AutoCrudTable({ title, description, schema, resource, fields, table: ta
6531
6759
  setExporting(false);
6532
6760
  }
6533
6761
  }, [selectedCount, handleExport]);
6534
- const tableOverrides = react.useMemo(() => buildTableOverrides(resolvedFields, tableConfig?.overrides), [resolvedFields, tableConfig?.overrides]);
6762
+ const tableOverrides = react.useMemo(() => buildTableOverrides(resolvedFields, tableConfig?.overrides, dynamicFilterOptions), [
6763
+ resolvedFields,
6764
+ tableConfig?.overrides,
6765
+ dynamicFilterOptions
6766
+ ]);
6535
6767
  const formOverrides = react.useMemo(() => buildFormOverrides(resolvedFields, formConfig?.overrides, denyFields), [
6536
6768
  resolvedFields,
6537
6769
  formConfig?.overrides,
@@ -8104,6 +8336,7 @@ exports.createMemoryDataSource = createMemoryDataSource;
8104
8336
  exports.createSelectColumn = createSelectColumn;
8105
8337
  exports.createTRPCDataSource = createTRPCDataSource;
8106
8338
  exports.createTableSchema = createTableSchema;
8339
+ exports.dataSources = dataSources;
8107
8340
  exports.dataToCSV = dataToCSV;
8108
8341
  exports.deDE = deDE;
8109
8342
  exports.downloadCSVTemplate = downloadCSVTemplate;
@@ -8111,6 +8344,7 @@ exports.enUS = enUS;
8111
8344
  exports.esES = esES;
8112
8345
  exports.exportAllToCSV = exportAllToCSV;
8113
8346
  exports.exportTableToCSV = exportTableToCSV;
8347
+ exports.formComponents = formComponents;
8114
8348
  exports.formatDate = formatDate;
8115
8349
  exports.frFR = frFR;
8116
8350
  exports.generateCSVTemplate = generateCSVTemplate;
@@ -8119,6 +8353,8 @@ exports.humanize = humanize;
8119
8353
  exports.jaJP = jaJP;
8120
8354
  exports.koKR = koKR;
8121
8355
  exports.noopToastAdapter = noopToastAdapter;
8356
+ exports.normalizeDataSourceConfig = normalizeDataSourceConfig;
8357
+ exports.normalizeOptions = normalizeOptions;
8122
8358
  exports.parseAsArrayOf = parseAsArrayOf;
8123
8359
  exports.parseAsInteger = parseAsInteger;
8124
8360
  exports.parseAsString = parseAsString;
package/dist/index.d.cts CHANGED
@@ -622,6 +622,57 @@ type LocaleProp = string | DeepPartial<AutoCrudLocale>;
622
622
  */
623
623
  declare function resolveLocale(localeProp?: LocaleProp): AutoCrudLocale;
624
624
  //#endregion
625
+ //#region src/lib/registries.d.ts
626
+ type AutoCrudFormComponentConfig = {
627
+ component: React$1.ComponentType<any>;
628
+ decorator?: string | React$1.ComponentType<any>;
629
+ [key: string]: unknown;
630
+ };
631
+ type AutoCrudOption = {
632
+ value: string;
633
+ label: string;
634
+ searchText?: string | string[];
635
+ keywords?: string[];
636
+ count?: number;
637
+ disabled?: boolean;
638
+ };
639
+ type AutoCrudDataSourceContext = {
640
+ field: string;
641
+ search?: string;
642
+ values?: Record<string, unknown>;
643
+ signal?: AbortSignal;
644
+ };
645
+ type AutoCrudDataSourceResult = AutoCrudOption[] | {
646
+ options?: AutoCrudOption[];
647
+ };
648
+ type AutoCrudDataSourceLoader = (context: AutoCrudDataSourceContext) => AutoCrudDataSourceResult | Promise<AutoCrudDataSourceResult>;
649
+ type AutoCrudDataSourceConfig = string | {
650
+ key: string;
651
+ dependsOn?: string[];
652
+ resetOnChange?: boolean;
653
+ };
654
+ type RegistryListener = () => void;
655
+ declare const formComponents: {
656
+ register(name: string, value: AutoCrudFormComponentConfig): void;
657
+ unregister(name: string): void;
658
+ get(name: string): AutoCrudFormComponentConfig | undefined;
659
+ all(): Record<string, AutoCrudFormComponentConfig>;
660
+ subscribe(listener: RegistryListener): () => void;
661
+ };
662
+ declare const dataSources: {
663
+ register(name: string, value: AutoCrudDataSourceLoader): void;
664
+ unregister(name: string): void;
665
+ get(name: string): AutoCrudDataSourceLoader | undefined;
666
+ all(): Record<string, AutoCrudDataSourceLoader>;
667
+ subscribe(listener: RegistryListener): () => void;
668
+ };
669
+ declare function normalizeDataSourceConfig(config: AutoCrudDataSourceConfig | undefined): {
670
+ key: string;
671
+ dependsOn: string[];
672
+ resetOnChange: boolean;
673
+ } | undefined;
674
+ declare function normalizeOptions(result: AutoCrudDataSourceResult | undefined): AutoCrudOption[];
675
+ //#endregion
625
676
  //#region src/components/auto-crud/auto-crud-table.d.ts
626
677
  /**
627
678
  * 筛选器独立配置
@@ -634,6 +685,8 @@ interface FilterConfig {
634
685
  variant?: 'text' | 'number' | 'range' | 'date' | 'dateRange' | 'boolean' | 'select' | 'multiSelect';
635
686
  /** select/multiSelect 的选项列表 */
636
687
  options?: FieldOption[];
688
+ /** select/multiSelect 的动态选项源 */
689
+ dataSource?: AutoCrudDataSourceConfig;
637
690
  /** range 的最小/最大值 */
638
691
  range?: [number, number];
639
692
  /** number 的单位 */
@@ -664,6 +717,8 @@ interface Field {
664
717
  label?: string;
665
718
  /** 字段级静态选项(表单、筛选、展示共用) */
666
719
  enum?: FieldOption[];
720
+ /** 字段级动态选项源(表单、筛选、展示共用) */
721
+ dataSource?: AutoCrudDataSourceConfig;
667
722
  /** 是否隐藏(表格和表单都隐藏) */
668
723
  hidden?: boolean;
669
724
  /** 是否参与 AutoCrud 全局搜索 */
@@ -1404,7 +1459,7 @@ interface MultiComboboxTriggerRenderProps {
1404
1459
  disabled?: boolean;
1405
1460
  readOnly?: boolean;
1406
1461
  }
1407
- interface MultiComboboxProps extends Omit<ComponentProps<typeof Command>, 'value' | 'onChange' | 'onValueChange'> {
1462
+ interface MultiComboboxProps extends Omit<ComponentProps<typeof Command>, 'value' | 'defaultValue' | 'onChange' | 'onValueChange'> {
1408
1463
  value?: string[];
1409
1464
  defaultValue?: string[];
1410
1465
  onChange?: (value: string[]) => void;
@@ -1759,4 +1814,4 @@ declare function exportAllToCSV<T extends Record<string, unknown>>(data: T[], op
1759
1814
  */
1760
1815
  declare function downloadCSVTemplate(headers: string[], filename?: string): void;
1761
1816
  //#endregion
1762
- export { type ActionItem, type ActionsColumnConfig, type AutoCrudLocale, AutoCrudTable, type AutoCrudTableProps, AutoForm, type AutoQueryParams, AutoTable, AutoTableActionBar, AutoTableSimpleFilters, type BatchActionConfig, type BatchActionContext, type BatchActionItem, type BatchBuiltinActionItem, type BatchBuiltinActionType, type BatchCustomActionItem, type BatchUpdateField, type ColumnOverrides, type CreateFormSchemaOptions, type CreateTableSchemaOptions, CrudFormModal, type CrudHooks, type CrudOperationPermissions, type CrudPermissions, type DataSource, DataTable, DataTableAdvancedToolbar, DataTableColumnHeader, DataTableFacetedFilter, DataTablePagination, DataTableRowAction, DataTableToolbar, DataTableViewOptions, type EnumOption, ExportDialog, type ExportDialogProps, type ExportMode, ExtendedColumnFilter, ExtendedColumnSort, type Field, type FieldOption, type FieldType, type Fields, type FilterConfig, FilterOperator, type FilterVariant, type FormSchemaOverrides, ImportDialog, type ImportDialogProps, type ImportResult, type JSONSchema, type JSONSchemaProperty, JoinOperator, type ListParams, type ListResult, type LocaleProp, MultiCombobox, type MultiComboboxOption, type MultiComboboxProps, type MultiComboboxTriggerRenderProps, Option, type ParsedImportData, type ParsedZodField, type Parser, QueryKeys, SchemaAdapter, type SimpleFieldConfig, type SimpleFieldsConfig, type ToastAdapter, type ToolbarActionItem, type ToolbarBuiltinActionItem, type ToolbarCustomActionItem, type UnifiedField, type UnifiedSchema, type UrlStateOptions, type UseAutoCrudResourceOptions, type UseAutoCrudResourceParams, type UseAutoCrudResourceReturn, cn, coerceRowValues, createActionsColumn, createEditFormSchema, createFormSchema, createMemoryDataSource, createSelectColumn, createTRPCDataSource, createTableSchema, dataToCSV, deDE, downloadCSVTemplate, enUS, esES, exportAllToCSV, exportTableToCSV, formatDate, frFR, generateCSVTemplate, getUrlParams, humanize, jaJP, koKR, noopToastAdapter, parseAsArrayOf, parseAsInteger, parseAsString, parseAsStringEnum, parseCSV, parseImportFile, parseJSON, parseZodField, resolveLocale, setSearchParams, useAutoCrudResource, useDataTable, useQueryState, useQueryStates, useReadableFilters, useUrlState, useUrlStates, zhCN };
1817
+ export { type ActionItem, type ActionsColumnConfig, type AutoCrudDataSourceConfig, type AutoCrudDataSourceContext, type AutoCrudDataSourceLoader, type AutoCrudFormComponentConfig, type AutoCrudLocale, type AutoCrudOption, AutoCrudTable, type AutoCrudTableProps, AutoForm, type AutoQueryParams, AutoTable, AutoTableActionBar, AutoTableSimpleFilters, type BatchActionConfig, type BatchActionContext, type BatchActionItem, type BatchBuiltinActionItem, type BatchBuiltinActionType, type BatchCustomActionItem, type BatchUpdateField, type ColumnOverrides, type CreateFormSchemaOptions, type CreateTableSchemaOptions, CrudFormModal, type CrudHooks, type CrudOperationPermissions, type CrudPermissions, type DataSource, DataTable, DataTableAdvancedToolbar, DataTableColumnHeader, DataTableFacetedFilter, DataTablePagination, DataTableRowAction, DataTableToolbar, DataTableViewOptions, type EnumOption, ExportDialog, type ExportDialogProps, type ExportMode, ExtendedColumnFilter, ExtendedColumnSort, type Field, type FieldOption, type FieldType, type Fields, type FilterConfig, FilterOperator, type FilterVariant, type FormSchemaOverrides, ImportDialog, type ImportDialogProps, type ImportResult, type JSONSchema, type JSONSchemaProperty, JoinOperator, type ListParams, type ListResult, type LocaleProp, MultiCombobox, type MultiComboboxOption, type MultiComboboxProps, type MultiComboboxTriggerRenderProps, Option, type ParsedImportData, type ParsedZodField, type Parser, QueryKeys, SchemaAdapter, type SimpleFieldConfig, type SimpleFieldsConfig, type ToastAdapter, type ToolbarActionItem, type ToolbarBuiltinActionItem, type ToolbarCustomActionItem, type UnifiedField, type UnifiedSchema, type UrlStateOptions, type UseAutoCrudResourceOptions, type UseAutoCrudResourceParams, type UseAutoCrudResourceReturn, cn, coerceRowValues, createActionsColumn, createEditFormSchema, createFormSchema, createMemoryDataSource, createSelectColumn, createTRPCDataSource, createTableSchema, dataSources, dataToCSV, deDE, downloadCSVTemplate, enUS, esES, exportAllToCSV, exportTableToCSV, formComponents, formatDate, frFR, generateCSVTemplate, getUrlParams, humanize, jaJP, koKR, noopToastAdapter, normalizeDataSourceConfig, normalizeOptions, parseAsArrayOf, parseAsInteger, parseAsString, parseAsStringEnum, parseCSV, parseImportFile, parseJSON, parseZodField, resolveLocale, setSearchParams, useAutoCrudResource, useDataTable, useQueryState, useQueryStates, useReadableFilters, useUrlState, useUrlStates, zhCN };
package/dist/index.d.ts CHANGED
@@ -4,7 +4,7 @@ import * as _tanstack_react_table0 from "@tanstack/react-table";
4
4
  import { Column, ColumnDef, ColumnSort, Row, RowData, Table, TableOptions, TableState } from "@tanstack/react-table";
5
5
  import { Command, DropdownMenuTrigger, PopoverContent } from "@pixpilot/shadcn";
6
6
  import { ClassValue } from "clsx";
7
- import * as react_jsx_runtime2 from "react/jsx-runtime";
7
+ import * as react_jsx_runtime3 from "react/jsx-runtime";
8
8
  import { z } from "zod";
9
9
  import { ISchema } from "@formily/json-schema";
10
10
 
@@ -362,7 +362,7 @@ declare function AutoTableActionBar<TData>({
362
362
  enableDelete,
363
363
  extraActions,
364
364
  actions
365
- }: AutoTableActionBarProps<TData>): react_jsx_runtime2.JSX.Element;
365
+ }: AutoTableActionBarProps<TData>): react_jsx_runtime3.JSX.Element;
366
366
  //#endregion
367
367
  //#region src/lib/schema-bridge/types.d.ts
368
368
  /**
@@ -530,7 +530,7 @@ declare function AutoTable<T extends z.ZodObject<z.ZodRawShape>>({
530
530
  showDefaultExport,
531
531
  onSelectedCountChange,
532
532
  getSelectedRows
533
- }: AutoTableProps<T>): react_jsx_runtime2.JSX.Element;
533
+ }: AutoTableProps<T>): react_jsx_runtime3.JSX.Element;
534
534
  //#endregion
535
535
  //#region src/i18n/locale.d.ts
536
536
  /**
@@ -622,6 +622,57 @@ type LocaleProp = string | DeepPartial<AutoCrudLocale>;
622
622
  */
623
623
  declare function resolveLocale(localeProp?: LocaleProp): AutoCrudLocale;
624
624
  //#endregion
625
+ //#region src/lib/registries.d.ts
626
+ type AutoCrudFormComponentConfig = {
627
+ component: React$1.ComponentType<any>;
628
+ decorator?: string | React$1.ComponentType<any>;
629
+ [key: string]: unknown;
630
+ };
631
+ type AutoCrudOption = {
632
+ value: string;
633
+ label: string;
634
+ searchText?: string | string[];
635
+ keywords?: string[];
636
+ count?: number;
637
+ disabled?: boolean;
638
+ };
639
+ type AutoCrudDataSourceContext = {
640
+ field: string;
641
+ search?: string;
642
+ values?: Record<string, unknown>;
643
+ signal?: AbortSignal;
644
+ };
645
+ type AutoCrudDataSourceResult = AutoCrudOption[] | {
646
+ options?: AutoCrudOption[];
647
+ };
648
+ type AutoCrudDataSourceLoader = (context: AutoCrudDataSourceContext) => AutoCrudDataSourceResult | Promise<AutoCrudDataSourceResult>;
649
+ type AutoCrudDataSourceConfig = string | {
650
+ key: string;
651
+ dependsOn?: string[];
652
+ resetOnChange?: boolean;
653
+ };
654
+ type RegistryListener = () => void;
655
+ declare const formComponents: {
656
+ register(name: string, value: AutoCrudFormComponentConfig): void;
657
+ unregister(name: string): void;
658
+ get(name: string): AutoCrudFormComponentConfig | undefined;
659
+ all(): Record<string, AutoCrudFormComponentConfig>;
660
+ subscribe(listener: RegistryListener): () => void;
661
+ };
662
+ declare const dataSources: {
663
+ register(name: string, value: AutoCrudDataSourceLoader): void;
664
+ unregister(name: string): void;
665
+ get(name: string): AutoCrudDataSourceLoader | undefined;
666
+ all(): Record<string, AutoCrudDataSourceLoader>;
667
+ subscribe(listener: RegistryListener): () => void;
668
+ };
669
+ declare function normalizeDataSourceConfig(config: AutoCrudDataSourceConfig | undefined): {
670
+ key: string;
671
+ dependsOn: string[];
672
+ resetOnChange: boolean;
673
+ } | undefined;
674
+ declare function normalizeOptions(result: AutoCrudDataSourceResult | undefined): AutoCrudOption[];
675
+ //#endregion
625
676
  //#region src/components/auto-crud/auto-crud-table.d.ts
626
677
  /**
627
678
  * 筛选器独立配置
@@ -634,6 +685,8 @@ interface FilterConfig {
634
685
  variant?: 'text' | 'number' | 'range' | 'date' | 'dateRange' | 'boolean' | 'select' | 'multiSelect';
635
686
  /** select/multiSelect 的选项列表 */
636
687
  options?: FieldOption[];
688
+ /** select/multiSelect 的动态选项源 */
689
+ dataSource?: AutoCrudDataSourceConfig;
637
690
  /** range 的最小/最大值 */
638
691
  range?: [number, number];
639
692
  /** number 的单位 */
@@ -664,6 +717,8 @@ interface Field {
664
717
  label?: string;
665
718
  /** 字段级静态选项(表单、筛选、展示共用) */
666
719
  enum?: FieldOption[];
720
+ /** 字段级动态选项源(表单、筛选、展示共用) */
721
+ dataSource?: AutoCrudDataSourceConfig;
667
722
  /** 是否隐藏(表格和表单都隐藏) */
668
723
  hidden?: boolean;
669
724
  /** 是否参与 AutoCrud 全局搜索 */
@@ -880,7 +935,7 @@ declare function AutoCrudTable<TSchema extends z.ZodObject<z.ZodRawShape>>({
880
935
  toolbarActions,
881
936
  locale: localeProp,
882
937
  onCreate
883
- }: AutoCrudTableProps<TSchema>): react_jsx_runtime2.JSX.Element;
938
+ }: AutoCrudTableProps<TSchema>): react_jsx_runtime3.JSX.Element;
884
939
  //#endregion
885
940
  //#region src/components/auto-crud/auto-form.d.ts
886
941
  interface AutoFormProps<T extends z.ZodObject<z.ZodRawShape>> {
@@ -912,7 +967,7 @@ declare function AutoFormInner<T extends z.ZodObject<z.ZodRawShape>>({
912
967
  labelAlign,
913
968
  labelWidth,
914
969
  showSubmitButton
915
- }: AutoFormProps<T>, ref: React.Ref<AutoFormRef>): react_jsx_runtime2.JSX.Element;
970
+ }: AutoFormProps<T>, ref: React.Ref<AutoFormRef>): react_jsx_runtime3.JSX.Element;
916
971
  declare const AutoForm: <T extends z.ZodObject<z.ZodRawShape>>(props: AutoFormProps<T> & {
917
972
  ref?: React.Ref<AutoFormRef>;
918
973
  }) => ReturnType<typeof AutoFormInner>;
@@ -1214,7 +1269,7 @@ declare function AutoTableSimpleFilters<TData>({
1214
1269
  filters: externalFilters,
1215
1270
  onFiltersChange,
1216
1271
  leading
1217
- }: AutoTableSimpleFiltersProps<TData>): react_jsx_runtime2.JSX.Element | null;
1272
+ }: AutoTableSimpleFiltersProps<TData>): react_jsx_runtime3.JSX.Element | null;
1218
1273
  //#endregion
1219
1274
  //#region src/components/auto-crud/form-modal.d.ts
1220
1275
  type ModalVariant = "dialog" | "sheet";
@@ -1256,7 +1311,7 @@ declare function CrudFormModal<T extends z.ZodObject<z.ZodRawShape>>({
1256
1311
  labelAlign,
1257
1312
  labelWidth,
1258
1313
  className
1259
- }: CrudFormModalProps<T>): react_jsx_runtime2.JSX.Element;
1314
+ }: CrudFormModalProps<T>): react_jsx_runtime3.JSX.Element;
1260
1315
  //#endregion
1261
1316
  //#region src/components/auto-crud/import-dialog.d.ts
1262
1317
  interface ImportDialogProps {
@@ -1278,7 +1333,7 @@ declare function ImportDialog({
1278
1333
  columns,
1279
1334
  title,
1280
1335
  locale
1281
- }: ImportDialogProps): react_jsx_runtime2.JSX.Element;
1336
+ }: ImportDialogProps): react_jsx_runtime3.JSX.Element;
1282
1337
  //#endregion
1283
1338
  //#region src/components/auto-crud/export-dialog.d.ts
1284
1339
  type ExportMode = "selected" | "filtered";
@@ -1298,7 +1353,7 @@ declare function ExportDialog({
1298
1353
  selectedCount,
1299
1354
  onExport,
1300
1355
  canExportFiltered
1301
- }: ExportDialogProps): react_jsx_runtime2.JSX.Element;
1356
+ }: ExportDialogProps): react_jsx_runtime3.JSX.Element;
1302
1357
  //#endregion
1303
1358
  //#region src/components/data-table/data-table.d.ts
1304
1359
  interface DataTableProps<TData> extends React$1.ComponentProps<"div"> {
@@ -1311,7 +1366,7 @@ declare function DataTable<TData>({
1311
1366
  children,
1312
1367
  className,
1313
1368
  ...props
1314
- }: DataTableProps<TData>): react_jsx_runtime2.JSX.Element;
1369
+ }: DataTableProps<TData>): react_jsx_runtime3.JSX.Element;
1315
1370
  //#endregion
1316
1371
  //#region src/components/data-table/data-table-advanced-toolbar.d.ts
1317
1372
  interface DataTableAdvancedToolbarProps<TData> extends React$1.ComponentProps<"div"> {
@@ -1322,7 +1377,7 @@ declare function DataTableAdvancedToolbar<TData>({
1322
1377
  children,
1323
1378
  className,
1324
1379
  ...props
1325
- }: DataTableAdvancedToolbarProps<TData>): react_jsx_runtime2.JSX.Element;
1380
+ }: DataTableAdvancedToolbarProps<TData>): react_jsx_runtime3.JSX.Element;
1326
1381
  //#endregion
1327
1382
  //#region src/components/data-table/data-table-column-header.d.ts
1328
1383
  interface DataTableColumnHeaderProps<TData, TValue> extends React.ComponentProps<typeof DropdownMenuTrigger> {
@@ -1334,7 +1389,7 @@ declare function DataTableColumnHeader<TData, TValue>({
1334
1389
  label,
1335
1390
  className,
1336
1391
  ...props
1337
- }: DataTableColumnHeaderProps<TData, TValue>): react_jsx_runtime2.JSX.Element;
1392
+ }: DataTableColumnHeaderProps<TData, TValue>): react_jsx_runtime3.JSX.Element;
1338
1393
  //#endregion
1339
1394
  //#region src/components/data-table/data-table-faceted-filter.d.ts
1340
1395
  interface DataTableFacetedFilterProps<TData, TValue> {
@@ -1348,7 +1403,7 @@ declare function DataTableFacetedFilter<TData, TValue>({
1348
1403
  title,
1349
1404
  options,
1350
1405
  multiple
1351
- }: DataTableFacetedFilterProps<TData, TValue>): react_jsx_runtime2.JSX.Element;
1406
+ }: DataTableFacetedFilterProps<TData, TValue>): react_jsx_runtime3.JSX.Element;
1352
1407
  //#endregion
1353
1408
  //#region src/components/data-table/data-table-pagination.d.ts
1354
1409
  interface DataTablePaginationProps<TData> extends React.ComponentProps<"div"> {
@@ -1360,7 +1415,7 @@ declare function DataTablePagination<TData>({
1360
1415
  pageSizeOptions,
1361
1416
  className,
1362
1417
  ...props
1363
- }: DataTablePaginationProps<TData>): react_jsx_runtime2.JSX.Element;
1418
+ }: DataTablePaginationProps<TData>): react_jsx_runtime3.JSX.Element;
1364
1419
  //#endregion
1365
1420
  //#region src/components/data-table/data-table-toolbar.d.ts
1366
1421
  interface DataTableToolbarProps<TData> extends React$1.ComponentProps<"div"> {
@@ -1371,7 +1426,7 @@ declare function DataTableToolbar<TData>({
1371
1426
  children,
1372
1427
  className,
1373
1428
  ...props
1374
- }: DataTableToolbarProps<TData>): react_jsx_runtime2.JSX.Element;
1429
+ }: DataTableToolbarProps<TData>): react_jsx_runtime3.JSX.Element;
1375
1430
  //#endregion
1376
1431
  //#region src/components/data-table/data-table-view-options.d.ts
1377
1432
  interface DataTableViewOptionsProps<TData> extends React$1.ComponentProps<typeof PopoverContent> {
@@ -1382,7 +1437,7 @@ declare function DataTableViewOptions<TData>({
1382
1437
  table,
1383
1438
  disabled,
1384
1439
  ...props
1385
- }: DataTableViewOptionsProps<TData>): react_jsx_runtime2.JSX.Element;
1440
+ }: DataTableViewOptionsProps<TData>): react_jsx_runtime3.JSX.Element;
1386
1441
  //#endregion
1387
1442
  //#region src/components/ui/multi-combobox.d.ts
1388
1443
  interface MultiComboboxOption {
@@ -1404,7 +1459,7 @@ interface MultiComboboxTriggerRenderProps {
1404
1459
  disabled?: boolean;
1405
1460
  readOnly?: boolean;
1406
1461
  }
1407
- interface MultiComboboxProps extends Omit<ComponentProps<typeof Command>, 'value' | 'onChange' | 'onValueChange'> {
1462
+ interface MultiComboboxProps extends Omit<ComponentProps<typeof Command>, 'value' | 'defaultValue' | 'onChange' | 'onValueChange'> {
1408
1463
  value?: string[];
1409
1464
  defaultValue?: string[];
1410
1465
  onChange?: (value: string[]) => void;
@@ -1759,4 +1814,4 @@ declare function exportAllToCSV<T extends Record<string, unknown>>(data: T[], op
1759
1814
  */
1760
1815
  declare function downloadCSVTemplate(headers: string[], filename?: string): void;
1761
1816
  //#endregion
1762
- export { type ActionItem, type ActionsColumnConfig, type AutoCrudLocale, AutoCrudTable, type AutoCrudTableProps, AutoForm, type AutoQueryParams, AutoTable, AutoTableActionBar, AutoTableSimpleFilters, type BatchActionConfig, type BatchActionContext, type BatchActionItem, type BatchBuiltinActionItem, type BatchBuiltinActionType, type BatchCustomActionItem, type BatchUpdateField, type ColumnOverrides, type CreateFormSchemaOptions, type CreateTableSchemaOptions, CrudFormModal, type CrudHooks, type CrudOperationPermissions, type CrudPermissions, type DataSource, DataTable, DataTableAdvancedToolbar, DataTableColumnHeader, DataTableFacetedFilter, DataTablePagination, DataTableRowAction, DataTableToolbar, DataTableViewOptions, type EnumOption, ExportDialog, type ExportDialogProps, type ExportMode, ExtendedColumnFilter, ExtendedColumnSort, type Field, type FieldOption, type FieldType, type Fields, type FilterConfig, FilterOperator, type FilterVariant, type FormSchemaOverrides, ImportDialog, type ImportDialogProps, type ImportResult, type JSONSchema, type JSONSchemaProperty, JoinOperator, type ListParams, type ListResult, type LocaleProp, MultiCombobox, type MultiComboboxOption, type MultiComboboxProps, type MultiComboboxTriggerRenderProps, Option, type ParsedImportData, type ParsedZodField, type Parser, QueryKeys, SchemaAdapter, type SimpleFieldConfig, type SimpleFieldsConfig, type ToastAdapter, type ToolbarActionItem, type ToolbarBuiltinActionItem, type ToolbarCustomActionItem, type UnifiedField, type UnifiedSchema, type UrlStateOptions, type UseAutoCrudResourceOptions, type UseAutoCrudResourceParams, type UseAutoCrudResourceReturn, cn, coerceRowValues, createActionsColumn, createEditFormSchema, createFormSchema, createMemoryDataSource, createSelectColumn, createTRPCDataSource, createTableSchema, dataToCSV, deDE, downloadCSVTemplate, enUS, esES, exportAllToCSV, exportTableToCSV, formatDate, frFR, generateCSVTemplate, getUrlParams, humanize, jaJP, koKR, noopToastAdapter, parseAsArrayOf, parseAsInteger, parseAsString, parseAsStringEnum, parseCSV, parseImportFile, parseJSON, parseZodField, resolveLocale, setSearchParams, useAutoCrudResource, useDataTable, useQueryState, useQueryStates, useReadableFilters, useUrlState, useUrlStates, zhCN };
1817
+ export { type ActionItem, type ActionsColumnConfig, type AutoCrudDataSourceConfig, type AutoCrudDataSourceContext, type AutoCrudDataSourceLoader, type AutoCrudFormComponentConfig, type AutoCrudLocale, type AutoCrudOption, AutoCrudTable, type AutoCrudTableProps, AutoForm, type AutoQueryParams, AutoTable, AutoTableActionBar, AutoTableSimpleFilters, type BatchActionConfig, type BatchActionContext, type BatchActionItem, type BatchBuiltinActionItem, type BatchBuiltinActionType, type BatchCustomActionItem, type BatchUpdateField, type ColumnOverrides, type CreateFormSchemaOptions, type CreateTableSchemaOptions, CrudFormModal, type CrudHooks, type CrudOperationPermissions, type CrudPermissions, type DataSource, DataTable, DataTableAdvancedToolbar, DataTableColumnHeader, DataTableFacetedFilter, DataTablePagination, DataTableRowAction, DataTableToolbar, DataTableViewOptions, type EnumOption, ExportDialog, type ExportDialogProps, type ExportMode, ExtendedColumnFilter, ExtendedColumnSort, type Field, type FieldOption, type FieldType, type Fields, type FilterConfig, FilterOperator, type FilterVariant, type FormSchemaOverrides, ImportDialog, type ImportDialogProps, type ImportResult, type JSONSchema, type JSONSchemaProperty, JoinOperator, type ListParams, type ListResult, type LocaleProp, MultiCombobox, type MultiComboboxOption, type MultiComboboxProps, type MultiComboboxTriggerRenderProps, Option, type ParsedImportData, type ParsedZodField, type Parser, QueryKeys, SchemaAdapter, type SimpleFieldConfig, type SimpleFieldsConfig, type ToastAdapter, type ToolbarActionItem, type ToolbarBuiltinActionItem, type ToolbarCustomActionItem, type UnifiedField, type UnifiedSchema, type UrlStateOptions, type UseAutoCrudResourceOptions, type UseAutoCrudResourceParams, type UseAutoCrudResourceReturn, cn, coerceRowValues, createActionsColumn, createEditFormSchema, createFormSchema, createMemoryDataSource, createSelectColumn, createTRPCDataSource, createTableSchema, dataSources, dataToCSV, deDE, downloadCSVTemplate, enUS, esES, exportAllToCSV, exportTableToCSV, formComponents, formatDate, frFR, generateCSVTemplate, getUrlParams, humanize, jaJP, koKR, noopToastAdapter, normalizeDataSourceConfig, normalizeOptions, parseAsArrayOf, parseAsInteger, parseAsString, parseAsStringEnum, parseCSV, parseImportFile, parseJSON, parseZodField, resolveLocale, setSearchParams, useAutoCrudResource, useDataTable, useQueryState, useQueryStates, useReadableFilters, useUrlState, useUrlStates, zhCN };
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import * as React from "react";
2
- import { forwardRef, useCallback, useEffect, useImperativeHandle, useMemo, useReducer, useRef } from "react";
2
+ import { forwardRef, useCallback, useEffect, useImperativeHandle, useMemo, useReducer, useRef, useState } from "react";
3
3
  import { ArrowDownUp, BadgeCheck, CalendarIcon, Check, CheckCircle2, ChevronDown, ChevronLeft, ChevronRight, ChevronUp, ChevronsLeft, ChevronsRight, ChevronsUpDown, CommandIcon, Download, Ellipsis, EyeOff, FileDown, FileSpreadsheetIcon, GripVertical, ListFilter, ListFilterIcon, Loader2, PlusCircle, Settings2, Text, Trash2, Upload, X, XCircle } from "lucide-react";
4
4
  import { flexRender, getCoreRowModel, getFacetedMinMaxValues, getFacetedRowModel, getFacetedUniqueValues, getFilteredRowModel, getPaginationRowModel, getSortedRowModel, useReactTable } from "@tanstack/react-table";
5
5
  import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, Badge, Button, Calendar, Checkbox, Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, CommandSeparator, Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuItem, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuTrigger, Input, Label, Popover, PopoverContent, PopoverTrigger, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, Separator, Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle, Slider, Switch, cn as cn$1 } from "@pixpilot/shadcn";
@@ -15,7 +15,7 @@ import { Slot } from "@radix-ui/react-slot";
15
15
  import * as ReactDOM from "react-dom";
16
16
  import { useDirection } from "@radix-ui/react-direction";
17
17
  import { z } from "zod";
18
- import { createForm } from "@formily/core";
18
+ import { createForm, onFieldValueChange } from "@formily/core";
19
19
  import { Form, JsonSchemaField } from "@pixpilot/formily-shadcn";
20
20
  import { connect, mapProps } from "@formily/react";
21
21
  import { toast } from "sonner";
@@ -5190,6 +5190,67 @@ function createEditFormSchema(schema, options) {
5190
5190
  });
5191
5191
  }
5192
5192
 
5193
+ //#endregion
5194
+ //#region src/lib/registries.ts
5195
+ function createRegistry(label) {
5196
+ const entries = /* @__PURE__ */ new Map();
5197
+ const listeners = /* @__PURE__ */ new Set();
5198
+ const notify = () => {
5199
+ queueMicrotask(() => {
5200
+ for (const listener of listeners) listener();
5201
+ });
5202
+ };
5203
+ return {
5204
+ register(name, value) {
5205
+ if (entries.has(name)) {
5206
+ console.warn(`[AutoCrud] ${label} "${name}" is already registered.`);
5207
+ return;
5208
+ }
5209
+ entries.set(name, value);
5210
+ notify();
5211
+ },
5212
+ unregister(name) {
5213
+ if (!entries.delete(name)) return;
5214
+ notify();
5215
+ },
5216
+ get(name) {
5217
+ return entries.get(name);
5218
+ },
5219
+ all() {
5220
+ return Object.fromEntries(entries);
5221
+ },
5222
+ subscribe(listener) {
5223
+ listeners.add(listener);
5224
+ return () => listeners.delete(listener);
5225
+ }
5226
+ };
5227
+ }
5228
+ const formComponents = createRegistry("form component");
5229
+ const dataSources = createRegistry("data source");
5230
+ function normalizeDataSourceConfig(config) {
5231
+ if (typeof config === "string") return config.length > 0 ? {
5232
+ key: config,
5233
+ dependsOn: [],
5234
+ resetOnChange: false
5235
+ } : void 0;
5236
+ if (!config?.key) return void 0;
5237
+ return {
5238
+ key: config.key,
5239
+ dependsOn: Array.isArray(config.dependsOn) ? config.dependsOn : [],
5240
+ resetOnChange: config.resetOnChange === true
5241
+ };
5242
+ }
5243
+ function normalizeOptions(result) {
5244
+ const options = Array.isArray(result) ? result : result?.options;
5245
+ if (!Array.isArray(options)) return [];
5246
+ return options.filter((option) => {
5247
+ return typeof option === "object" && option !== null && typeof option.value === "string" && typeof option.label === "string";
5248
+ }).map((option) => ({
5249
+ ...option,
5250
+ value: String(option.value)
5251
+ }));
5252
+ }
5253
+
5193
5254
  //#endregion
5194
5255
  //#region src/components/auto-crud/auto-form.tsx
5195
5256
  const COMBOBOX_LIST_CLASS = "[&_[cmdk-list]]:max-h-[360px] [&_[cmdk-list]]:overflow-x-hidden [&_[cmdk-list]]:overflow-y-auto";
@@ -5224,6 +5285,104 @@ function AutoCrudMultiCombobox({ options = [],...props }) {
5224
5285
  options
5225
5286
  });
5226
5287
  }
5288
+ const defaultFieldComponents = {
5289
+ Combobox: {
5290
+ component: FormilyCombobox,
5291
+ decorator: "FormItem"
5292
+ },
5293
+ MultiCombobox: {
5294
+ component: FormilyMultiCombobox,
5295
+ decorator: "FormItem"
5296
+ },
5297
+ Switch: {
5298
+ component: FormilySwitch,
5299
+ decorator: "FormItem"
5300
+ }
5301
+ };
5302
+ function useRegistryVersion$1(subscribe) {
5303
+ const [version, setVersion] = useState(0);
5304
+ useEffect(() => subscribe(() => setVersion((current) => current + 1)), [subscribe]);
5305
+ return version;
5306
+ }
5307
+ function isDataSourceConfig(value) {
5308
+ if (typeof value === "string") return value.length > 0;
5309
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
5310
+ return typeof value.key === "string";
5311
+ }
5312
+ function collectDataSourceConfigs(schema, result = {}) {
5313
+ const properties = schema.properties;
5314
+ if (!properties || typeof properties !== "object") return result;
5315
+ for (const [key, property] of Object.entries(properties)) {
5316
+ if (!property || typeof property !== "object" || Array.isArray(property)) continue;
5317
+ const record = property;
5318
+ const customDataSource = record["x-data-source"];
5319
+ const schemaDataSource = record.dataSource;
5320
+ const dataSourceConfig = isDataSourceConfig(customDataSource) ? customDataSource : isDataSourceConfig(schemaDataSource) ? schemaDataSource : void 0;
5321
+ if (dataSourceConfig) result[key] = dataSourceConfig;
5322
+ collectDataSourceConfigs(record, result);
5323
+ }
5324
+ return result;
5325
+ }
5326
+ function useFormDataSources(form, schema) {
5327
+ useEffect(() => {
5328
+ const sourceEntries = Object.entries(collectDataSourceConfigs(schema)).map(([fieldName, config]) => {
5329
+ const normalized = normalizeDataSourceConfig(config);
5330
+ return normalized ? [fieldName, normalized] : void 0;
5331
+ }).filter((entry) => entry !== void 0);
5332
+ if (sourceEntries.length === 0) return;
5333
+ let active = true;
5334
+ const effectId = Symbol("autoCrudDataSources");
5335
+ const loadVersions = /* @__PURE__ */ new Map();
5336
+ const controller = typeof AbortController === "undefined" ? void 0 : new AbortController();
5337
+ const loadFieldOptions = async (fieldName, source) => {
5338
+ const version = (loadVersions.get(fieldName) ?? 0) + 1;
5339
+ loadVersions.set(fieldName, version);
5340
+ const loader = dataSources.get(source.key);
5341
+ if (!loader) {
5342
+ form.setFieldState(fieldName, (state) => {
5343
+ state.dataSource = [];
5344
+ });
5345
+ return;
5346
+ }
5347
+ try {
5348
+ const options = normalizeOptions(await loader({
5349
+ field: fieldName,
5350
+ values: Object.fromEntries(source.dependsOn.map((dependency) => [dependency, form.getValuesIn(dependency)])),
5351
+ signal: controller?.signal
5352
+ }));
5353
+ if (!active || loadVersions.get(fieldName) !== version) return;
5354
+ form.setFieldState(fieldName, (state) => {
5355
+ state.dataSource = options;
5356
+ });
5357
+ } catch (error) {
5358
+ if (!active || controller?.signal.aborted) return;
5359
+ console.warn(`[AutoCrud] Failed to load data source "${source.key}".`, error);
5360
+ form.setFieldState(fieldName, (state) => {
5361
+ state.dataSource = [];
5362
+ });
5363
+ }
5364
+ };
5365
+ for (const [fieldName, source] of sourceEntries) loadFieldOptions(fieldName, source);
5366
+ form.addEffects(effectId, () => {
5367
+ for (const dependency of new Set(sourceEntries.flatMap(([, source]) => source.dependsOn))) onFieldValueChange(dependency, () => {
5368
+ for (const [fieldName, source] of sourceEntries) {
5369
+ if (!source.dependsOn.includes(dependency)) continue;
5370
+ if (source.resetOnChange) form.setValuesIn(fieldName, void 0);
5371
+ loadFieldOptions(fieldName, source);
5372
+ }
5373
+ });
5374
+ });
5375
+ return () => {
5376
+ active = false;
5377
+ controller?.abort();
5378
+ form.removeEffects(effectId);
5379
+ };
5380
+ }, [
5381
+ form,
5382
+ schema,
5383
+ useRegistryVersion$1(dataSources.subscribe)
5384
+ ]);
5385
+ }
5227
5386
  function AutoFormInner({ schema: zodSchema, initialValues, onSubmit, overrides, mode = "create", loading = false, gridColumns = 1, labelAlign = "top", labelWidth, showSubmitButton = true }, ref) {
5228
5387
  const form = useMemo(() => createForm({ initialValues }), [JSON.stringify(initialValues)]);
5229
5388
  const formSchema = useMemo(() => createEditFormSchema(zodSchema, {
@@ -5239,6 +5398,11 @@ function AutoFormInner({ schema: zodSchema, initialValues, onSubmit, overrides,
5239
5398
  labelAlign,
5240
5399
  labelWidth
5241
5400
  ]);
5401
+ const fieldComponents = useMemo(() => ({ fields: {
5402
+ ...defaultFieldComponents,
5403
+ ...formComponents.all()
5404
+ } }), [useRegistryVersion$1(formComponents.subscribe)]);
5405
+ useFormDataSources(form, formSchema);
5242
5406
  const handleSubmit = async () => {
5243
5407
  await form.validate();
5244
5408
  if (form.valid) await onSubmit(form.values);
@@ -5248,20 +5412,7 @@ function AutoFormInner({ schema: zodSchema, initialValues, onSubmit, overrides,
5248
5412
  form,
5249
5413
  children: [/* @__PURE__ */ jsx(JsonSchemaField, {
5250
5414
  schema: formSchema,
5251
- components: { fields: {
5252
- Combobox: {
5253
- component: FormilyCombobox,
5254
- decorator: "FormItem"
5255
- },
5256
- MultiCombobox: {
5257
- component: FormilyMultiCombobox,
5258
- decorator: "FormItem"
5259
- },
5260
- Switch: {
5261
- component: FormilySwitch,
5262
- decorator: "FormItem"
5263
- }
5264
- } }
5415
+ components: fieldComponents
5265
5416
  }), showSubmitButton && /* @__PURE__ */ jsx("div", {
5266
5417
  className: "flex justify-end gap-2 mt-6",
5267
5418
  children: /* @__PURE__ */ jsx(Button, {
@@ -6075,6 +6226,7 @@ function mergeFieldConfig(base, override) {
6075
6226
  ...base,
6076
6227
  ...override,
6077
6228
  enum: override?.enum ?? base?.enum,
6229
+ dataSource: override?.dataSource ?? base?.dataSource,
6078
6230
  table: mergeFieldPart(base?.table, override?.table),
6079
6231
  filter: mergeFieldPart(base?.filter, override?.filter),
6080
6232
  form: mergeFieldPart(base?.form, override?.form)
@@ -6107,6 +6259,69 @@ function toBatchOptions(options) {
6107
6259
  value
6108
6260
  }));
6109
6261
  }
6262
+ function getFilterDataSource(config) {
6263
+ if (config.filter && typeof config.filter === "object") return config.filter.dataSource ?? config.dataSource;
6264
+ return config.dataSource;
6265
+ }
6266
+ function shouldLoadDynamicFilterOptions(config) {
6267
+ if (config.filter === false) return false;
6268
+ if (normalizeFieldOptions(config.enum)) return false;
6269
+ if (config.filter && typeof config.filter === "object") {
6270
+ if (config.filter.enabled === false || config.filter.hidden === true) return false;
6271
+ if (normalizeFieldOptions(config.filter.options)) return false;
6272
+ }
6273
+ return normalizeDataSourceConfig(getFilterDataSource(config)) !== void 0;
6274
+ }
6275
+ function useRegistryVersion(subscribe) {
6276
+ const [version, setVersion] = React.useState(0);
6277
+ React.useEffect(() => subscribe(() => setVersion((current) => current + 1)), [subscribe]);
6278
+ return version;
6279
+ }
6280
+ function useDynamicFilterOptions(fields) {
6281
+ const registryVersion = useRegistryVersion(dataSources.subscribe);
6282
+ const [optionsByField, setOptionsByField] = React.useState({});
6283
+ const sourceEntries = React.useMemo(() => {
6284
+ if (!fields) return [];
6285
+ return Object.entries(fields).flatMap(([field, config]) => {
6286
+ if (!shouldLoadDynamicFilterOptions(config)) return [];
6287
+ const source = normalizeDataSourceConfig(getFilterDataSource(config));
6288
+ return source ? [{
6289
+ field,
6290
+ source
6291
+ }] : [];
6292
+ });
6293
+ }, [fields]);
6294
+ React.useEffect(() => {
6295
+ if (sourceEntries.length === 0) {
6296
+ setOptionsByField({});
6297
+ return;
6298
+ }
6299
+ let active = true;
6300
+ const controller = typeof AbortController === "undefined" ? void 0 : new AbortController();
6301
+ Promise.all(sourceEntries.map(async ({ field, source }) => {
6302
+ const loader = dataSources.get(source.key);
6303
+ if (!loader) return [field, []];
6304
+ try {
6305
+ return [field, normalizeOptions(await loader({
6306
+ field,
6307
+ values: {},
6308
+ signal: controller?.signal
6309
+ }))];
6310
+ } catch (error) {
6311
+ if (!controller?.signal.aborted) console.warn(`[AutoCrud] Failed to load filter data source "${source.key}".`, error);
6312
+ return [field, []];
6313
+ }
6314
+ })).then((entries) => {
6315
+ if (!active) return;
6316
+ setOptionsByField(Object.fromEntries(entries));
6317
+ });
6318
+ return () => {
6319
+ active = false;
6320
+ controller?.abort();
6321
+ };
6322
+ }, [sourceEntries, registryVersion]);
6323
+ return optionsByField;
6324
+ }
6110
6325
  function getOptionLabel(value, options) {
6111
6326
  const stringValue = String(value);
6112
6327
  return options?.find((option) => option.value === stringValue)?.label ?? stringValue;
@@ -6115,15 +6330,21 @@ function getOptionLabel(value, options) {
6115
6330
  * 从统一配置生成表格 overrides
6116
6331
  * 当 filter 配置存在时,合并到 meta 中(不影响列隐藏)
6117
6332
  */
6118
- function buildTableOverrides(fields, legacyOverrides) {
6333
+ function buildTableOverrides(fields, legacyOverrides, dynamicFilterOptions) {
6119
6334
  const result = { ...legacyOverrides };
6120
6335
  if (fields) for (const [key, config] of Object.entries(fields)) {
6121
- const tableOptions = toTableOptions(normalizeFieldOptions(config.enum));
6336
+ const fieldOptions = normalizeFieldOptions(config.enum);
6337
+ const tableOptions = toTableOptions(fieldOptions);
6338
+ const dynamicOptions = !fieldOptions ? toTableOptions(normalizeFieldOptions(dynamicFilterOptions?.[key])) : void 0;
6122
6339
  const tableMeta = config.table !== false && typeof config.table === "object" ? config.table.meta : void 0;
6123
6340
  const fieldEnumMeta = tableOptions ? {
6124
6341
  options: tableOptions,
6125
6342
  variant: "multiSelect"
6126
6343
  } : void 0;
6344
+ const fieldDataSourceMeta = dynamicOptions ? {
6345
+ options: dynamicOptions,
6346
+ variant: "multiSelect"
6347
+ } : void 0;
6127
6348
  let filterMeta;
6128
6349
  if (config.filter && typeof config.filter === "object") {
6129
6350
  if (config.filter.enabled !== false && config.filter.hidden !== true) {
@@ -6141,11 +6362,12 @@ function buildTableOverrides(fields, legacyOverrides) {
6141
6362
  enableColumnFilter: false
6142
6363
  };
6143
6364
  }
6144
- if (fieldEnumMeta || tableMeta || filterMeta) result[key] = {
6365
+ if (fieldEnumMeta || fieldDataSourceMeta || tableMeta || filterMeta) result[key] = {
6145
6366
  ...result[key],
6146
6367
  meta: {
6147
6368
  ...result[key]?.meta ?? {},
6148
6369
  ...fieldEnumMeta ?? {},
6370
+ ...fieldDataSourceMeta ?? {},
6149
6371
  ...tableMeta ?? {},
6150
6372
  ...filterMeta ?? {}
6151
6373
  }
@@ -6183,7 +6405,8 @@ function buildFormOverrides(fields, legacyOverrides, denyFields) {
6183
6405
  "x-hidden": true
6184
6406
  };
6185
6407
  if (fields) for (const [key, config] of Object.entries(fields)) {
6186
- const fieldOptions = normalizeFieldOptions(config.enum);
6408
+ const fieldOptions = normalizeFieldOptions((config.form && typeof config.form === "object" ? config.form : void 0)?.enum) ?? normalizeFieldOptions(config.enum);
6409
+ const fieldDataSource = fieldOptions || !config.dataSource ? void 0 : normalizeDataSourceConfig(config.dataSource);
6187
6410
  if (config.label) result[key] = {
6188
6411
  ...result[key],
6189
6412
  title: config.label
@@ -6196,6 +6419,10 @@ function buildFormOverrides(fields, legacyOverrides, denyFields) {
6196
6419
  ...result[key],
6197
6420
  enum: fieldOptions
6198
6421
  };
6422
+ if (fieldDataSource) result[key] = {
6423
+ ...result[key],
6424
+ "x-data-source": config.dataSource
6425
+ };
6199
6426
  if (config.form === false) result[key] = {
6200
6427
  ...result[key],
6201
6428
  "x-hidden": true
@@ -6453,6 +6680,7 @@ function AutoCrudTable({ title, description, schema, resource, fields, table: ta
6453
6680
  const [selectedCount, setSelectedCount] = React.useState(0);
6454
6681
  const [exporting, setExporting] = React.useState(false);
6455
6682
  const getSelectedRowsRef = React.useRef(null);
6683
+ const dynamicFilterOptions = useDynamicFilterOptions(resolvedFields);
6456
6684
  const importColumns = React.useMemo(() => {
6457
6685
  const shape = resolvedSchema.shape;
6458
6686
  const denySet = new Set(denyFields ?? []);
@@ -6489,7 +6717,11 @@ function AutoCrudTable({ title, description, schema, resource, fields, table: ta
6489
6717
  setExporting(false);
6490
6718
  }
6491
6719
  }, [selectedCount, handleExport]);
6492
- const tableOverrides = React.useMemo(() => buildTableOverrides(resolvedFields, tableConfig?.overrides), [resolvedFields, tableConfig?.overrides]);
6720
+ const tableOverrides = React.useMemo(() => buildTableOverrides(resolvedFields, tableConfig?.overrides, dynamicFilterOptions), [
6721
+ resolvedFields,
6722
+ tableConfig?.overrides,
6723
+ dynamicFilterOptions
6724
+ ]);
6493
6725
  const formOverrides = React.useMemo(() => buildFormOverrides(resolvedFields, formConfig?.overrides, denyFields), [
6494
6726
  resolvedFields,
6495
6727
  formConfig?.overrides,
@@ -8036,4 +8268,4 @@ function createMemoryDataSource(initialData = []) {
8036
8268
  }
8037
8269
 
8038
8270
  //#endregion
8039
- export { AutoCrudTable, AutoForm, AutoTable, AutoTableActionBar, AutoTableSimpleFilters, CrudFormModal, DataTable, DataTableAdvancedToolbar, DataTableColumnHeader, DataTableFacetedFilter, DataTablePagination, DataTableToolbar, DataTableViewOptions, ExportDialog, ImportDialog, MultiCombobox, SchemaAdapter, cn, coerceRowValues, createActionsColumn, createEditFormSchema, createFormSchema, createMemoryDataSource, createSelectColumn, createTRPCDataSource, createTableSchema, dataToCSV, deDE, downloadCSVTemplate, enUS, esES, exportAllToCSV, exportTableToCSV, formatDate, frFR, generateCSVTemplate, getUrlParams, humanize, jaJP, koKR, noopToastAdapter, parseAsArrayOf, parseAsInteger, parseAsString, parseAsStringEnum, parseCSV, parseImportFile, parseJSON, parseZodField, resolveLocale, setSearchParams, useAutoCrudResource, useDataTable, useQueryState, useQueryStates, useReadableFilters, useUrlState, useUrlStates, zhCN };
8271
+ export { AutoCrudTable, AutoForm, AutoTable, AutoTableActionBar, AutoTableSimpleFilters, CrudFormModal, DataTable, DataTableAdvancedToolbar, DataTableColumnHeader, DataTableFacetedFilter, DataTablePagination, DataTableToolbar, DataTableViewOptions, ExportDialog, ImportDialog, MultiCombobox, SchemaAdapter, cn, coerceRowValues, createActionsColumn, createEditFormSchema, createFormSchema, createMemoryDataSource, createSelectColumn, createTRPCDataSource, createTableSchema, dataSources, dataToCSV, deDE, downloadCSVTemplate, enUS, esES, exportAllToCSV, exportTableToCSV, formComponents, formatDate, frFR, generateCSVTemplate, getUrlParams, humanize, jaJP, koKR, noopToastAdapter, normalizeDataSourceConfig, normalizeOptions, parseAsArrayOf, parseAsInteger, parseAsString, parseAsStringEnum, parseCSV, parseImportFile, parseJSON, parseZodField, resolveLocale, setSearchParams, useAutoCrudResource, useDataTable, useQueryState, useQueryStates, useReadableFilters, useUrlState, useUrlStates, zhCN };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@wordrhyme/auto-crud",
3
3
  "type": "module",
4
- "version": "1.1.7",
4
+ "version": "1.2.0",
5
5
  "description": "Schema-first CRUD components with auto-generated tables and forms",
6
6
  "author": "wordrhyme",
7
7
  "license": "MIT",
@@ -65,9 +65,9 @@
65
65
  "nanoid": "^5.1.6",
66
66
  "tailwind-merge": "^3.5.0",
67
67
  "vaul": "^1.1.2",
68
- "@pixpilot/shadcn": "1.2.7",
69
68
  "@pixpilot/formily-shadcn": "1.11.20",
70
- "@pixpilot/shadcn-ui": "1.21.1"
69
+ "@pixpilot/shadcn-ui": "1.21.1",
70
+ "@pixpilot/shadcn": "1.2.7"
71
71
  },
72
72
  "devDependencies": {
73
73
  "@tanstack/react-query": "^5.90.15",
@@ -82,11 +82,11 @@
82
82
  "react-dom": "19.2.4",
83
83
  "tsdown": "^0.15.12",
84
84
  "typescript": "^5.9.3",
85
- "@internal/prettier-config": "0.0.1",
86
85
  "@internal/eslint-config": "0.3.0",
87
- "@internal/tsconfig": "0.1.0",
86
+ "@internal/prettier-config": "0.0.1",
87
+ "@internal/tsdown-config": "0.1.0",
88
88
  "@internal/vitest-config": "0.1.0",
89
- "@internal/tsdown-config": "0.1.0"
89
+ "@internal/tsconfig": "0.1.0"
90
90
  },
91
91
  "prettier": "@internal/prettier-config",
92
92
  "scripts": {