@wordrhyme/auto-crud 1.12.2 → 1.14.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
@@ -25,6 +25,8 @@ let __wordrhyme_formily_shadcn = require("@wordrhyme/formily-shadcn");
25
25
  __wordrhyme_formily_shadcn = require_position.__toESM(__wordrhyme_formily_shadcn);
26
26
  let __formily_core = require("@formily/core");
27
27
  __formily_core = require_position.__toESM(__formily_core);
28
+ let __formily_react = require("@formily/react");
29
+ __formily_react = require_position.__toESM(__formily_react);
28
30
  let __formily_reactive = require("@formily/reactive");
29
31
  __formily_reactive = require_position.__toESM(__formily_reactive);
30
32
  let sonner = require("sonner");
@@ -5120,7 +5122,7 @@ function useDataTable(props) {
5120
5122
  ...tableProps.defaultColumn,
5121
5123
  enableColumnFilter: false
5122
5124
  },
5123
- enableRowSelection: true,
5125
+ enableRowSelection: tableProps.enableRowSelection ?? true,
5124
5126
  onRowSelectionChange: setRowSelection,
5125
5127
  onPaginationChange,
5126
5128
  onSortingChange,
@@ -5186,7 +5188,7 @@ const filterModeConfig = {
5186
5188
  tooltip: "Linear-style command filters"
5187
5189
  }
5188
5190
  };
5189
- function AutoTable({ schema, data, getRowId, expandedRow, defaultExpanded = false, pageCount = 1, pagination, total, overrides, classNames, enableRowSelection = true, exclude, filterOnly, actions, pinnedColumns, filterMode, search = false, toolbarExtra, locale = enUSTableControls, onDeleteSelected, onUpdateSelected, batchUpdateFields, actionBarExtra, actionBarActions, deleteConfirmation, initialSorting, enableExport = true, showDefaultExport = true, onSelectedCountChange, onSelectedRowsChange, getSelectedRows, exportTableRef }) {
5191
+ function AutoTable({ schema, data, getRowId, expandedRow, defaultExpanded = false, pageCount = 1, pagination, total, overrides, classNames, enableRowSelection = true, queryKeys, initialColumnVisibility, exclude, filterOnly, actions, pinnedColumns, filterMode, search = false, toolbarExtra, locale = enUSTableControls, onDeleteSelected, onUpdateSelected, batchUpdateFields, actionBarExtra, actionBarActions, deleteConfirmation, initialSorting, enableExport = true, showDefaultExport = true, onSelectedCountChange, onSelectedRowsChange, getSelectedRows, exportTableRef }) {
5190
5192
  if (expandedRow && !getRowId) throw new Error("AutoTable requires getRowId when expandedRow is provided.");
5191
5193
  const enableExpansion = !!expandedRow;
5192
5194
  const modes = filterMode ? Array.isArray(filterMode) ? filterMode : [filterMode] : DEFAULT_MODES;
@@ -5196,7 +5198,7 @@ function AutoTable({ schema, data, getRowId, expandedRow, defaultExpanded = fals
5196
5198
  shallow: false,
5197
5199
  clearOnDefault: true
5198
5200
  });
5199
- const [, setSearchPage] = useUrlState("page", parseAsInteger.withDefault(1), {
5201
+ const [, setSearchPage] = useUrlState(queryKeys?.page ?? "page", parseAsInteger.withDefault(1), {
5200
5202
  shallow: false,
5201
5203
  clearOnDefault: true
5202
5204
  });
@@ -5263,6 +5265,7 @@ function AutoTable({ schema, data, getRowId, expandedRow, defaultExpanded = fals
5263
5265
  ]);
5264
5266
  const stableInitialState = (0, react.useMemo)(() => ({
5265
5267
  sorting: initialSorting,
5268
+ columnVisibility: initialColumnVisibility,
5266
5269
  expanded: enableExpansion && defaultExpanded ? true : {},
5267
5270
  columnPinning: pinnedColumns ?? {
5268
5271
  left: [...enableExpansion ? [expandColumnId] : [], ...enableRowSelection ? ["select"] : []],
@@ -5270,6 +5273,7 @@ function AutoTable({ schema, data, getRowId, expandedRow, defaultExpanded = fals
5270
5273
  }
5271
5274
  }), [
5272
5275
  initialSorting,
5276
+ initialColumnVisibility,
5273
5277
  pinnedColumns,
5274
5278
  enableRowSelection,
5275
5279
  enableExpansion,
@@ -5281,6 +5285,8 @@ function AutoTable({ schema, data, getRowId, expandedRow, defaultExpanded = fals
5281
5285
  const { table, shallow, debounceMs, throttleMs } = useDataTable({
5282
5286
  data,
5283
5287
  columns,
5288
+ queryKeys,
5289
+ enableRowSelection,
5284
5290
  getRowId,
5285
5291
  getRowCanExpand: () => enableExpansion,
5286
5292
  getExpandedRowModel: enableExpansion ? expandedRowModel : void 0,
@@ -5478,6 +5484,7 @@ function FormModal({ open, onOpenChange, title, description, children, variant =
5478
5484
  open,
5479
5485
  onOpenChange,
5480
5486
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(__wordrhyme_ui.SheetContent, {
5487
+ ...!description ? { "aria-describedby": void 0 } : {},
5481
5488
  side,
5482
5489
  className: cn("sm:max-w-lg flex flex-col p-0", className),
5483
5490
  children: [
@@ -5503,6 +5510,7 @@ function FormModal({ open, onOpenChange, title, description, children, variant =
5503
5510
  open,
5504
5511
  onOpenChange,
5505
5512
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(__wordrhyme_ui.DialogContent, {
5513
+ ...!description ? { "aria-describedby": void 0 } : {},
5506
5514
  className: cn("max-w-2xl sm:max-w-2xl flex flex-col max-h-[85vh] overflow-hidden p-0", className),
5507
5515
  children: [
5508
5516
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(__wordrhyme_ui.DialogHeader, {
@@ -5624,6 +5632,8 @@ function createFormSchema(schema, options) {
5624
5632
  const inferredByName = inferComponentByFieldName(key);
5625
5633
  const component = inferredByName?.component ?? fieldTypeToFormilyComponent[parsed.type];
5626
5634
  const inferredProps = inferredByName?.props ?? {};
5635
+ const componentProps = getComponentProps(parsed.type, parsed);
5636
+ if (parsed.type === "enum" && override?.enum) componentProps.options = override.enum;
5627
5637
  properties[key] = {
5628
5638
  type: mapToJsonSchemaType(parsed.type),
5629
5639
  title: humanize$1(key),
@@ -5632,7 +5642,7 @@ function createFormSchema(schema, options) {
5632
5642
  "x-decorator-props": decoratorProps,
5633
5643
  "x-component": component,
5634
5644
  "x-component-props": {
5635
- ...getComponentProps(parsed.type, parsed),
5645
+ ...componentProps,
5636
5646
  ...inferredProps
5637
5647
  },
5638
5648
  ...parsed.enumValues && { enum: parsed.enumValues.map((v) => ({
@@ -5666,6 +5676,51 @@ function createEditFormSchema(schema, options) {
5666
5676
  });
5667
5677
  }
5668
5678
 
5679
+ //#endregion
5680
+ //#region src/components/auto-crud/read-pretty-field.tsx
5681
+ const ReadPrettyBooleanLocale = (0, react.createContext)(zhCN.boolean);
5682
+ const ReadPrettyRenderers = (0, react.createContext)({});
5683
+ function ReadPrettyField({ options }) {
5684
+ const field = (0, __formily_react.useField)();
5685
+ const schema = (0, __formily_react.useFieldSchema)();
5686
+ const renderCustomValue = (0, react.useContext)(ReadPrettyRenderers)[field.path.toString()];
5687
+ return renderCustomValue ? renderCustomValue() : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ReadPrettyValue, {
5688
+ value: field.value,
5689
+ options: options?.length ? options : field.dataSource,
5690
+ date: schema["x-component"] === "DatePicker"
5691
+ });
5692
+ }
5693
+ function ReadPrettyValue({ value, options: choices, date }) {
5694
+ const booleanLocale = (0, react.useContext)(ReadPrettyBooleanLocale);
5695
+ useDateFormatterVersion();
5696
+ const renderValue = (item) => {
5697
+ if (item == null || item === "") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5698
+ className: "text-muted-foreground",
5699
+ children: "-"
5700
+ });
5701
+ const option = choices?.find((choice) => String(choice.value) === String(item));
5702
+ if (option) return Array.isArray(value) ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__wordrhyme_shadcn.Badge, {
5703
+ variant: "secondary",
5704
+ className: "font-normal",
5705
+ children: option.label
5706
+ }) : option.label;
5707
+ if (typeof item === "boolean") return item ? booleanLocale.true : booleanLocale.false;
5708
+ if (item instanceof Date || date) return formatDate(item);
5709
+ if (typeof item === "object") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("pre", {
5710
+ className: "rounded-md bg-muted/50 p-3 text-sm whitespace-pre-wrap break-words",
5711
+ children: JSON.stringify(item, null, 2)
5712
+ });
5713
+ return String(item);
5714
+ };
5715
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
5716
+ className: "min-w-0 whitespace-pre-wrap break-words text-base leading-6 text-foreground",
5717
+ children: Array.isArray(value) ? value.length ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
5718
+ className: "flex flex-wrap gap-2",
5719
+ children: value.map((item, index$1) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: renderValue(item) }, index$1))
5720
+ }) : renderValue(null) : renderValue(value)
5721
+ });
5722
+ }
5723
+
5669
5724
  //#endregion
5670
5725
  //#region src/lib/crud-reference-projection.ts
5671
5726
  function isRecord$1(value) {
@@ -5814,12 +5869,8 @@ function normalizeFieldOptions$1(options) {
5814
5869
  * 从统一字段配置生成 Formily overrides。
5815
5870
  * `legacyOverrides` 保持兼容旧 API,字段级 `form` 配置优先级最高。
5816
5871
  */
5817
- function buildFormOverrides(fields, legacyOverrides, denyFields) {
5872
+ function buildFormOverrides(fields, legacyOverrides, denyFields, mode = "edit") {
5818
5873
  const result = { ...legacyOverrides };
5819
- if (denyFields) for (const field of denyFields) result[field] = {
5820
- ...result[field],
5821
- "x-hidden": true
5822
- };
5823
5874
  if (fields) for (const [key, config] of Object.entries(fields)) {
5824
5875
  const fieldOptions = normalizeFieldOptions$1((config.form && typeof config.form === "object" ? config.form : void 0)?.enum) ?? normalizeFieldOptions$1(config.enum);
5825
5876
  const fieldDataSource = fieldOptions || !config.dataSource ? void 0 : normalizeDataSourceConfig(config.dataSource);
@@ -5827,10 +5878,6 @@ function buildFormOverrides(fields, legacyOverrides, denyFields) {
5827
5878
  ...result[key],
5828
5879
  title: config.label
5829
5880
  };
5830
- if (config.hidden) result[key] = {
5831
- ...result[key],
5832
- "x-hidden": true
5833
- };
5834
5881
  if (fieldOptions) result[key] = {
5835
5882
  ...result[key],
5836
5883
  enum: fieldOptions
@@ -5839,15 +5886,27 @@ function buildFormOverrides(fields, legacyOverrides, denyFields) {
5839
5886
  ...result[key],
5840
5887
  "x-data-source": config.dataSource
5841
5888
  };
5842
- if (config.form === false) result[key] = {
5889
+ if (config.form === false && mode !== "view") result[key] = {
5843
5890
  ...result[key],
5844
5891
  "x-hidden": true
5845
5892
  };
5846
- else if (config.form && typeof config.form === "object") result[key] = {
5893
+ else if (config.form && typeof config.form === "object") {
5894
+ const { "x-hidden": formHidden,...sharedFormConfig } = config.form;
5895
+ result[key] = {
5896
+ ...result[key],
5897
+ ...sharedFormConfig,
5898
+ ...mode !== "view" && formHidden !== void 0 ? { "x-hidden": formHidden } : {}
5899
+ };
5900
+ }
5901
+ if (config.hidden) result[key] = {
5847
5902
  ...result[key],
5848
- ...config.form
5903
+ "x-hidden": true
5849
5904
  };
5850
5905
  }
5906
+ for (const field of denyFields ?? []) result[field] = {
5907
+ ...result[field],
5908
+ "x-hidden": true
5909
+ };
5851
5910
  return result;
5852
5911
  }
5853
5912
 
@@ -5910,6 +5969,13 @@ const defaultFieldComponents = {
5910
5969
  decorator: "FormItem"
5911
5970
  }
5912
5971
  };
5972
+ const readPrettyFieldComponents = Object.fromEntries(Object.entries({
5973
+ ...__wordrhyme_formily_shadcn.defaultComponentRegistry,
5974
+ ...defaultFieldComponents
5975
+ }).filter(([, config]) => "decorator" in config).map(([name, config]) => [name, {
5976
+ ...config,
5977
+ component: (0, __wordrhyme_formily_shadcn.connect)(config.component, (0, __formily_react.mapReadPretty)(ReadPrettyField))
5978
+ }]));
5913
5979
  const AUTO_CRUD_DATA_SOURCE_SCOPE_KEY = "$autoCrudDataSource";
5914
5980
  const POPUP_SCROLL_LOAD_THRESHOLD$1 = 24;
5915
5981
  const dataSourceReactionStates = /* @__PURE__ */ new WeakMap();
@@ -5938,7 +6004,8 @@ function mergeReactions(existing, reaction) {
5938
6004
  if (Array.isArray(existing)) return existing.includes(reaction) ? existing : [...existing, reaction];
5939
6005
  return [existing, reaction];
5940
6006
  }
5941
- function applyDataSourceReactions(schema) {
6007
+ function applyDataSourceReactions(schema, readPretty = false) {
6008
+ if (readPretty) schema["x-pattern"] = "readPretty";
5942
6009
  const dataSourceConfig = getSchemaDataSourceConfig(schema);
5943
6010
  if (dataSourceConfig) {
5944
6011
  const reaction = `{{${AUTO_CRUD_DATA_SOURCE_SCOPE_KEY}(${JSON.stringify(dataSourceConfig)})}}`;
@@ -5946,12 +6013,12 @@ function applyDataSourceReactions(schema) {
5946
6013
  }
5947
6014
  const properties = schema.properties;
5948
6015
  if (properties && typeof properties === "object" && !Array.isArray(properties)) {
5949
- for (const property of Object.values(properties)) if (property && typeof property === "object" && !Array.isArray(property)) applyDataSourceReactions(property);
6016
+ for (const property of Object.values(properties)) if (property && typeof property === "object" && !Array.isArray(property)) applyDataSourceReactions(property, readPretty);
5950
6017
  }
5951
6018
  const items = schema.items;
5952
6019
  if (Array.isArray(items)) {
5953
- for (const item of items) if (item && typeof item === "object") applyDataSourceReactions(item);
5954
- } else if (items && typeof items === "object") applyDataSourceReactions(items);
6020
+ for (const item of items) if (item && typeof item === "object") applyDataSourceReactions(item, readPretty);
6021
+ } else if (items && typeof items === "object") applyDataSourceReactions(items, readPretty);
5955
6022
  }
5956
6023
  function getDataSourceReactionState(field) {
5957
6024
  const existing = dataSourceReactionStates.get(field);
@@ -5996,7 +6063,7 @@ function loadAutoCrudDataSource({ entry, field, registryVersion, search, sourceK
5996
6063
  });
5997
6064
  setAutoCrudDataSourceOptions(field, state.options);
5998
6065
  if (!append && state.loadKey === loadKey && state.registryVersion === registryVersion) return;
5999
- if (entry.reset && state.dependencyKey !== void 0 && state.dependencyKey !== dependencyKey) field.setValue(void 0);
6066
+ if (entry.reset && !field.readPretty && state.dependencyKey !== void 0 && state.dependencyKey !== dependencyKey) field.setValue(void 0);
6000
6067
  state.dependencyKey = dependencyKey;
6001
6068
  if (!append) {
6002
6069
  state.loadKey = loadKey;
@@ -6172,7 +6239,10 @@ function createAutoCrudDataSourceReaction(config, registryState) {
6172
6239
  };
6173
6240
  }
6174
6241
  function AutoFormInner({ schema: zodSchema, initialValues, onSubmit, fields, overrides, scope: scopeProp, mode = "create", loading = false, gridColumns = 1, labelAlign = "top", labelWidth, showSubmitButton = true }, ref) {
6175
- const form = (0, react.useMemo)(() => (0, __formily_core.createForm)({ initialValues }), [JSON.stringify(initialValues)]);
6242
+ const form = (0, react.useMemo)(() => (0, __formily_core.createForm)({
6243
+ initialValues,
6244
+ pattern: mode === "view" ? "readPretty" : "editable"
6245
+ }), [JSON.stringify(initialValues), mode]);
6176
6246
  const dataSourceRegistryState = (0, react.useMemo)(() => (0, __formily_reactive.observable)({ version: 0 }), []);
6177
6247
  const updateDataSourceRegistryState = (0, react.useCallback)(() => {
6178
6248
  (0, __formily_reactive.action)(() => {
@@ -6180,40 +6250,75 @@ function AutoFormInner({ schema: zodSchema, initialValues, onSubmit, fields, ove
6180
6250
  });
6181
6251
  }, [dataSourceRegistryState]);
6182
6252
  const dataSourceRegistryVersion = useRegistryVersion$1(dataSources.subscribe, updateDataSourceRegistryState);
6183
- const formOverrides = (0, react.useMemo)(() => buildFormOverrides(fields, overrides), [fields, overrides]);
6253
+ const formOverrides = (0, react.useMemo)(() => buildFormOverrides(fields, overrides, void 0, mode), [
6254
+ fields,
6255
+ overrides,
6256
+ mode
6257
+ ]);
6184
6258
  const formSchema = (0, react.useMemo)(() => {
6185
- const nextSchema = createEditFormSchema(zodSchema, {
6259
+ const nextSchema = (mode === "view" ? createFormSchema : createEditFormSchema)(zodSchema, {
6186
6260
  overrides: formOverrides,
6187
6261
  layout: "grid",
6188
6262
  gridColumns
6189
6263
  });
6190
- applyDataSourceReactions(nextSchema);
6264
+ const grid = typeof nextSchema.properties === "object" ? nextSchema.properties.layout : void 0;
6265
+ if (mode === "view" && grid && typeof grid === "object") {
6266
+ grid["x-component-props"] = {
6267
+ ...grid["x-component-props"],
6268
+ className: (0, __wordrhyme_shadcn.cn)(grid["x-component-props"]?.className, "gap-x-10 gap-y-7 py-2")
6269
+ };
6270
+ const properties = typeof grid.properties === "object" ? grid.properties : {};
6271
+ for (const property of Object.values(properties)) {
6272
+ const decoratorProps = property["x-decorator-props"];
6273
+ const fullWidth = property.type === "object" || property.type === "array" || ["Textarea", "RichTextEditor"].includes(property["x-component"]);
6274
+ property["x-decorator-props"] = {
6275
+ colon: false,
6276
+ ...decoratorProps,
6277
+ className: (0, __wordrhyme_shadcn.cn)(fullWidth && decoratorProps?.gridSpan == null && "col-span-full", decoratorProps?.className)
6278
+ };
6279
+ }
6280
+ }
6281
+ applyDataSourceReactions(nextSchema, mode === "view");
6191
6282
  return nextSchema;
6192
6283
  }, [
6193
6284
  zodSchema,
6194
6285
  formOverrides,
6195
6286
  gridColumns,
6196
- dataSourceRegistryVersion
6287
+ dataSourceRegistryVersion,
6288
+ mode
6197
6289
  ]);
6198
6290
  const formLayout = (0, react.useMemo)(() => {
6199
6291
  const inlineLabel = labelAlign === "left" || labelAlign === "right";
6200
- return {
6201
- labelPlacement: inlineLabel ? "start" : "top",
6202
- itemProps: inlineLabel ? { label: {
6203
- className: labelAlign === "right" ? "text-right" : void 0,
6204
- style: labelWidth != null ? { width: labelWidth } : void 0
6205
- } } : void 0
6206
- };
6207
- }, [labelAlign, labelWidth]);
6292
+ return { itemProps: {
6293
+ container: mode === "view" ? { className: "min-w-0 space-y-0 gap-2" } : void 0,
6294
+ label: {
6295
+ placement: inlineLabel ? "start" : "top",
6296
+ className: (0, __wordrhyme_shadcn.cn)(mode === "view" && "mb-0 text-sm font-normal text-muted-foreground leading-5", labelAlign === "right" && "text-right"),
6297
+ style: inlineLabel && labelWidth != null ? { width: labelWidth } : void 0
6298
+ }
6299
+ } };
6300
+ }, [
6301
+ labelAlign,
6302
+ labelWidth,
6303
+ mode
6304
+ ]);
6208
6305
  const fieldComponents = (0, react.useMemo)(() => ({ fields: {
6209
6306
  ...defaultFieldComponents,
6307
+ ...mode === "view" ? {
6308
+ ...readPrettyFieldComponents,
6309
+ ObjectField: {
6310
+ component: ReadPrettyField,
6311
+ decorator: "FormItem"
6312
+ }
6313
+ } : {},
6210
6314
  ...components.all()
6211
- } }), [useRegistryVersion$1(components.subscribe)]);
6315
+ } }), [useRegistryVersion$1(components.subscribe), mode]);
6212
6316
  const scope = (0, react.useMemo)(() => ({
6213
6317
  ...scopeProp,
6214
6318
  [AUTO_CRUD_DATA_SOURCE_SCOPE_KEY]: (config) => createAutoCrudDataSourceReaction(config, dataSourceRegistryState)
6215
6319
  }), [dataSourceRegistryState, scopeProp]);
6216
6320
  const handleSubmit = async () => {
6321
+ if (mode === "view" || !onSubmit) return;
6217
6322
  await form.validate();
6218
6323
  if (form.valid) await onSubmit(form.values);
6219
6324
  };
@@ -6225,7 +6330,7 @@ function AutoFormInner({ schema: zodSchema, initialValues, onSubmit, fields, ove
6225
6330
  schema: formSchema,
6226
6331
  components: fieldComponents,
6227
6332
  scope
6228
- }), showSubmitButton && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6333
+ }), mode !== "view" && showSubmitButton && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6229
6334
  className: "flex justify-end gap-2 mt-6",
6230
6335
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__wordrhyme_shadcn.Button, {
6231
6336
  type: "button",
@@ -6241,7 +6346,7 @@ const AutoForm = (0, react.forwardRef)(AutoFormInner);
6241
6346
  //#endregion
6242
6347
  //#region src/components/auto-crud/crud-form-modal.tsx
6243
6348
  function CrudFormModal({ open, onOpenChange, mode, schema, initialValues, onSubmit, loading = false, variant = "dialog", overrides, scope, title, locale = zhCN.formModal, gridColumns, labelAlign, labelWidth, className }) {
6244
- const defaultTitle = mode === "create" ? locale.createTitle : locale.editTitle;
6349
+ const defaultTitle = mode === "create" ? locale.createTitle : mode === "edit" ? locale.editTitle : zhCN.viewModal.title;
6245
6350
  const formRef = (0, react.useRef)(null);
6246
6351
  const handleSubmit = async () => {
6247
6352
  await formRef.current?.submit();
@@ -6252,7 +6357,7 @@ function CrudFormModal({ open, onOpenChange, mode, schema, initialValues, onSubm
6252
6357
  title: title ?? defaultTitle,
6253
6358
  variant,
6254
6359
  className,
6255
- footer: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6360
+ footer: mode !== "view" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6256
6361
  className: "flex justify-end gap-2",
6257
6362
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(__wordrhyme_shadcn.Button, {
6258
6363
  type: "button",
@@ -6265,14 +6370,16 @@ function CrudFormModal({ open, onOpenChange, mode, schema, initialValues, onSubm
6265
6370
  disabled: loading,
6266
6371
  children: loading ? locale.submitting : mode === "create" ? locale.create : locale.save
6267
6372
  })]
6268
- }),
6373
+ }) : void 0,
6269
6374
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AutoForm, {
6270
6375
  ref: formRef,
6271
6376
  schema,
6272
- mode,
6377
+ ...mode === "view" ? { mode } : {
6378
+ mode,
6379
+ onSubmit
6380
+ },
6273
6381
  initialValues,
6274
6382
  loading,
6275
- onSubmit,
6276
6383
  overrides,
6277
6384
  scope,
6278
6385
  gridColumns,
@@ -7652,7 +7759,7 @@ function ViewModal(props) {
7652
7759
  if (!props.open || !props.data) return null;
7653
7760
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ViewModalContent, { ...props });
7654
7761
  }
7655
- function ViewModalContent({ open, onOpenChange, variant, data, schema, fields: fieldConfig, dynamicOptions, denyFields, locale, tableOverrides, view }) {
7762
+ function ViewModalContent({ open, onOpenChange, variant, data, schema, fields: fieldConfig, dynamicOptions, denyFields, locale, tableOverrides, view, form }) {
7656
7763
  const excluded = react.useMemo(() => {
7657
7764
  const denied = new Set(denyFields ?? []);
7658
7765
  return Object.keys(schema.shape).filter((key) => {
@@ -7667,70 +7774,82 @@ function ViewModalContent({ open, onOpenChange, variant, data, schema, fields: f
7667
7774
  ]);
7668
7775
  const rows = react.useMemo(() => data ? [data] : [], [data]);
7669
7776
  const resolvedOptions = useDynamicResolveOptions(fieldConfig, rows, excluded);
7670
- const content = /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dl", {
7671
- className: "grid gap-4 py-4",
7672
- children: (0, __tanstack_react_table.useReactTable)({
7673
- data: rows,
7674
- columns: react.useMemo(() => {
7675
- const overrides = {};
7676
- for (const [key, fieldSchema] of Object.entries(schema.shape).filter(([fieldKey]) => !excluded.includes(fieldKey))) {
7677
- const config = fieldConfig?.[key] ?? {};
7678
- const tableConfig = getTableConfig(config);
7679
- const listOverride = tableOverrides?.[key];
7680
- const options = getTableOptions(config) ?? resolvedOptions.optionsByField[key] ?? (tableConfig?.dataSource !== void 0 ? void 0 : dynamicOptions?.[key]);
7681
- overrides[key] = {
7682
- label: tableConfig?.label ?? config.label ?? listOverride?.label ?? humanize(key),
7683
- index: tableConfig?.index ?? listOverride?.index,
7684
- cell: ({ getValue, row }) => {
7685
- const value = getValue();
7686
- return config.format?.(value, {
7687
- row: row.original,
7688
- target: "display"
7689
- }) ?? renderFieldValue(value, parseZodField(fieldSchema).type, locale.boolean, options, tableConfig?.display ?? "auto", Infinity);
7690
- },
7691
- ...view?.presentation === "table" ? listOverride : void 0,
7692
- hidden: false,
7693
- ...view?.overrides?.[key]
7694
- };
7695
- }
7696
- return createTableSchema(schema, {
7697
- overrides,
7698
- exclude: excluded
7699
- });
7700
- }, [
7701
- schema,
7702
- excluded,
7703
- fieldConfig,
7704
- tableOverrides,
7705
- view,
7706
- resolvedOptions.optionsByField,
7707
- dynamicOptions,
7708
- locale.boolean
7709
- ]),
7710
- getCoreRowModel: (0, __tanstack_react_table.getCoreRowModel)()
7711
- }).getRowModel().rows[0]?.getAllCells().map((cell) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7712
- className: "grid grid-cols-3 items-start gap-4",
7713
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("dt", {
7714
- className: "text-sm font-medium text-muted-foreground",
7715
- children: cell.column.columnDef.meta?.label ?? humanize(cell.column.id)
7716
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dd", {
7717
- className: "col-span-2 min-w-0 whitespace-pre-wrap break-words text-sm",
7718
- children: (0, __tanstack_react_table.flexRender)(cell.column.columnDef.cell, cell.getContext())
7719
- })]
7720
- }, cell.id))
7721
- });
7722
- if (variant === "sheet") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__wordrhyme_ui.Sheet, {
7723
- open,
7724
- onOpenChange,
7725
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(__wordrhyme_ui.SheetContent, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(__wordrhyme_ui.SheetHeader, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__wordrhyme_ui.SheetTitle, { children: locale.viewModal.title }) }), content] })
7777
+ const table = (0, __tanstack_react_table.useReactTable)({
7778
+ data: rows,
7779
+ columns: react.useMemo(() => {
7780
+ const overrides$1 = {};
7781
+ for (const [key, fieldSchema] of Object.entries(schema.shape).filter(([fieldKey]) => !excluded.includes(fieldKey))) {
7782
+ const config = fieldConfig?.[key] ?? {};
7783
+ const tableConfig = getTableConfig(config);
7784
+ const listOverride = tableOverrides?.[key];
7785
+ const options = getTableOptions(config) ?? resolvedOptions.optionsByField[key] ?? (tableConfig?.dataSource !== void 0 ? void 0 : dynamicOptions?.[key]);
7786
+ overrides$1[key] = {
7787
+ label: tableConfig?.label ?? config.label ?? listOverride?.label ?? humanize(key),
7788
+ index: tableConfig?.index ?? listOverride?.index,
7789
+ cell: ({ getValue, row }) => {
7790
+ const value = getValue();
7791
+ return config.format?.(value, {
7792
+ row: row.original,
7793
+ target: "display"
7794
+ }) ?? (tableConfig?.display && tableConfig.display !== "auto" || options?.some((option) => option.badgeTone) ? renderFieldValue(value, parseZodField(fieldSchema).type, locale.boolean, options, tableConfig?.display ?? "auto", Infinity) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ReadPrettyValue, {
7795
+ value,
7796
+ options,
7797
+ date: parseZodField(fieldSchema).type === "date"
7798
+ }));
7799
+ },
7800
+ ...view?.presentation === "table" ? listOverride : void 0,
7801
+ hidden: false,
7802
+ ...view?.overrides?.[key]
7803
+ };
7804
+ }
7805
+ return createTableSchema(schema, {
7806
+ overrides: overrides$1,
7807
+ exclude: excluded
7808
+ });
7809
+ }, [
7810
+ schema,
7811
+ excluded,
7812
+ fieldConfig,
7813
+ tableOverrides,
7814
+ view,
7815
+ resolvedOptions.optionsByField,
7816
+ dynamicOptions,
7817
+ locale.boolean
7818
+ ]),
7819
+ getCoreRowModel: (0, __tanstack_react_table.getCoreRowModel)()
7726
7820
  });
7727
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__wordrhyme_ui.Dialog, {
7728
- open,
7729
- onOpenChange,
7730
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(__wordrhyme_ui.DialogContent, {
7731
- "aria-describedby": void 0,
7732
- className: "max-w-2xl max-h-[80vh] overflow-y-auto",
7733
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(__wordrhyme_ui.DialogHeader, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__wordrhyme_ui.DialogTitle, { children: locale.viewModal.title }) }), content]
7821
+ const detailSchema = react.useMemo(() => schema.omit(Object.fromEntries(excluded.map((key) => [key, true]))), [schema, excluded]);
7822
+ const overrides = buildFormOverrides(fieldConfig, form?.overrides, denyFields, "view");
7823
+ const renderers = {};
7824
+ for (const [index$1, cell] of (table.getRowModel().rows[0]?.getAllCells() ?? []).entries()) {
7825
+ const key = cell.column.id;
7826
+ overrides[key] = {
7827
+ ...overrides[key],
7828
+ title: cell.column.columnDef.meta?.label ?? humanize(key),
7829
+ "x-index": index$1
7830
+ };
7831
+ renderers[key] = () => (0, __tanstack_react_table.flexRender)(cell.column.columnDef.cell, cell.getContext());
7832
+ delete overrides[key]["x-data-source"];
7833
+ }
7834
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ReadPrettyBooleanLocale.Provider, {
7835
+ value: locale.boolean,
7836
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ReadPrettyRenderers.Provider, {
7837
+ value: renderers,
7838
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CrudFormModal, {
7839
+ open,
7840
+ onOpenChange,
7841
+ mode: "view",
7842
+ variant,
7843
+ schema: detailSchema,
7844
+ initialValues: data ?? void 0,
7845
+ overrides,
7846
+ scope: form?.scope,
7847
+ title: locale.viewModal.title,
7848
+ gridColumns: form?.columns,
7849
+ labelAlign: form?.labelAlign,
7850
+ labelWidth: form?.labelWidth,
7851
+ className: form?.className
7852
+ })
7734
7853
  })
7735
7854
  });
7736
7855
  }
@@ -8194,6 +8313,9 @@ function AutoCrudTable({ id, title, description, schema, resource, fields, table
8194
8313
  pageCount: resource.tableData.pageCount,
8195
8314
  pagination: paginationOptions,
8196
8315
  ...resource.tableData.total !== void 0 ? { total: resource.tableData.total } : {},
8316
+ queryKeys: tableConfig?.queryKeys ?? resource.queryKeys,
8317
+ enableRowSelection: tableConfig?.enableRowSelection,
8318
+ initialColumnVisibility: tableConfig?.initialColumnVisibility,
8197
8319
  getRowId: tableConfig?.getRowId,
8198
8320
  expandedRow: tableConfig?.expandedRow,
8199
8321
  defaultExpanded: tableConfig?.defaultExpanded,
@@ -8235,6 +8357,8 @@ function AutoCrudTable({ id, title, description, schema, resource, fields, table
8235
8357
  scope: formConfig?.scope,
8236
8358
  locale: locale.formModal,
8237
8359
  gridColumns: formConfig?.columns,
8360
+ labelAlign: formConfig?.labelAlign,
8361
+ labelWidth: formConfig?.labelWidth,
8238
8362
  className: formConfig?.className
8239
8363
  }),
8240
8364
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ViewModal, {
@@ -8248,7 +8372,8 @@ function AutoCrudTable({ id, title, description, schema, resource, fields, table
8248
8372
  denyFields,
8249
8373
  locale,
8250
8374
  tableOverrides: detailTableOverrides,
8251
- view: viewConfig
8375
+ view: viewConfig,
8376
+ form: formConfig
8252
8377
  }),
8253
8378
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__wordrhyme_ui.AlertDialog, {
8254
8379
  open: resource.modal.deleteOpen,
@@ -9036,7 +9161,7 @@ function modalReducer(state, action$1) {
9036
9161
  /**
9037
9162
  * Hook 主函数
9038
9163
  */
9039
- function useAutoCrudResource({ router, schema, query: queryTransform, fields, options = {} }) {
9164
+ function useAutoCrudResource({ router, queryKeys, schema, query: queryTransform, fields, options = {} }) {
9040
9165
  const { idKey = "id", defaultVariant = "dialog", hooks, toast: toastAdapter, exportFetcher, defaultSort: defaultSortOption } = options;
9041
9166
  const toast$1 = (0, react.useMemo)(() => toastAdapter === false ? noopToastAdapter : toastAdapter ?? defaultToastAdapter, [toastAdapter]);
9042
9167
  const hooksRef = (0, react.useRef)(hooks);
@@ -9057,31 +9182,36 @@ function useAutoCrudResource({ router, schema, query: queryTransform, fields, op
9057
9182
  fields,
9058
9183
  metadataFields
9059
9184
  ]);
9060
- const [urlParams] = useQueryStates({
9061
- page: parseAsInteger.withDefault(1),
9062
- perPage: parseAsInteger.withDefault(10),
9063
- sort: getSortingStateParser().withDefault(defaultSorting),
9064
- joinOperator: parseAsStringEnum(["and", "or"]).withDefault("and"),
9065
- search: parseAsString.withDefault("")
9066
- }, { shallow: false });
9067
- const [filters] = useReadableFilters(columns, { shallow: false });
9185
+ const [page] = useQueryState(queryKeys?.page ?? "page", parseAsInteger.withDefault(1));
9186
+ const [perPage] = useQueryState(queryKeys?.perPage ?? "perPage", parseAsInteger.withDefault(10));
9187
+ const [sort] = useQueryState(queryKeys?.sort ?? "sort", getSortingStateParser().withDefault(defaultSorting));
9188
+ const [joinOperator] = useQueryState(queryKeys?.joinOperator ?? "joinOperator", parseAsStringEnum(["and", "or"]).withDefault("and"));
9189
+ const [search] = useQueryState("search", parseAsString.withDefault(""), { shallow: false });
9190
+ const [filters] = useReadableFilters(columns, {
9191
+ shallow: false,
9192
+ resetPageKey: queryKeys?.page ?? "page"
9193
+ });
9068
9194
  const metadataPending = Boolean(metadataQuery?.isPending);
9069
9195
  const [filtersReady, setFiltersReady] = (0, react.useState)(!metadataPending);
9070
9196
  (0, react.useEffect)(() => setFiltersReady(!metadataPending), [metadataPending]);
9071
9197
  const listReady = !metadataPending && filtersReady;
9072
9198
  const queryInput = (0, react.useMemo)(() => {
9073
9199
  const autoParams = {
9074
- page: urlParams.page,
9075
- perPage: urlParams.perPage,
9076
- sort: urlParams.sort,
9200
+ page,
9201
+ perPage,
9202
+ sort,
9077
9203
  filters,
9078
- joinOperator: urlParams.joinOperator,
9079
- ...urlParams.search.trim() ? { search: urlParams.search.trim() } : {}
9204
+ joinOperator,
9205
+ ...search.trim() ? { search: search.trim() } : {}
9080
9206
  };
9081
9207
  return queryTransform ? queryTransform(autoParams) : autoParams;
9082
9208
  }, [
9083
- urlParams,
9209
+ page,
9210
+ perPage,
9211
+ sort,
9084
9212
  filters,
9213
+ joinOperator,
9214
+ search,
9085
9215
  queryTransform
9086
9216
  ]);
9087
9217
  const hasReadyListData = (0, react.useRef)(false);
@@ -9357,6 +9487,7 @@ function useAutoCrudResource({ router, schema, query: queryTransform, fields, op
9357
9487
  }, [listQuery, listReady]);
9358
9488
  return {
9359
9489
  idKey,
9490
+ queryKeys,
9360
9491
  tableData: {
9361
9492
  data: tableRows,
9362
9493
  pageCount: listData?.pageCount ?? 0,
@@ -9644,18 +9775,21 @@ function createMemoryDataSource(initialData = []) {
9644
9775
  let filtered = [...data];
9645
9776
  if (params.filters) filtered = filtered.filter((item) => {
9646
9777
  return params.filters.every((filter) => {
9647
- return item[filter.id] === filter.value;
9778
+ const value = item[filter.id];
9779
+ if (filter.operator === "inArray") return Array.isArray(filter.value) && filter.value.includes(value);
9780
+ if (!filter.operator || filter.operator === "eq") return value === filter.value;
9781
+ throw new Error(`Unsupported memory filter operator: ${filter.operator}`);
9648
9782
  });
9649
9783
  });
9650
- if (params.sort && params.sort.length > 0) {
9651
- const sortItem = params.sort[0];
9652
- filtered.sort((a, b) => {
9784
+ if (params.sort && params.sort.length > 0) filtered.sort((a, b) => {
9785
+ for (const sortItem of params.sort) {
9653
9786
  const aVal = a[sortItem.id];
9654
9787
  const bVal = b[sortItem.id];
9655
9788
  const result = aVal > bVal ? 1 : aVal < bVal ? -1 : 0;
9656
- return sortItem.desc ? -result : result;
9657
- });
9658
- }
9789
+ if (result) return sortItem.desc ? -result : result;
9790
+ }
9791
+ return 0;
9792
+ });
9659
9793
  const start = (params.page - 1) * params.perPage;
9660
9794
  const end = start + params.perPage;
9661
9795
  return {