@wordrhyme/auto-crud 1.8.2 → 1.10.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.js CHANGED
@@ -1,12 +1,12 @@
1
1
  import { i as positionActions, n as compareOrder, r as matchesPosition, t as actionPositionSchema } from "./position--p6kJnYA.js";
2
2
  import * as React from "react";
3
- import { forwardRef, useCallback, useEffect, useImperativeHandle, useMemo, useReducer, useRef, useState, useSyncExternalStore } from "react";
4
- import { ArrowDownUp, BadgeCheck, CalendarIcon, Check, CheckCircle2, ChevronDown, ChevronLeft, ChevronRight, ChevronUp, ChevronsLeft, ChevronsRight, ChevronsUpDown, CommandIcon, Download, Ellipsis, EyeOff, FileDown, FileSpreadsheetIcon, GripVertical, ListFilter, ListFilterIcon, Loader2, PlusCircle, RefreshCw, Settings2, Text, Trash2, Upload, X, XCircle } from "lucide-react";
5
- import { flexRender, getCoreRowModel, getFacetedMinMaxValues, getFacetedRowModel, getFacetedUniqueValues, getFilteredRowModel, getPaginationRowModel, getSortedRowModel, useReactTable } from "@tanstack/react-table";
3
+ import { Fragment, forwardRef, useCallback, useEffect, useImperativeHandle, useMemo, useReducer, useRef, useState, useSyncExternalStore } from "react";
4
+ import { ArrowDownUp, BadgeCheck, CalendarIcon, Check, CheckCircle2, ChevronDown, ChevronLeft, ChevronRight, ChevronRightIcon, ChevronUp, ChevronsLeft, ChevronsRight, ChevronsUpDown, CommandIcon, Download, Ellipsis, EyeOff, FileDown, FileSpreadsheetIcon, GripVertical, ListFilter, ListFilterIcon, Loader2, PlusCircle, RefreshCw, Settings2, Text, Trash2, Upload, X, XCircle } from "lucide-react";
5
+ import { flexRender, getCoreRowModel, getExpandedRowModel, getFacetedMinMaxValues, getFacetedRowModel, getFacetedUniqueValues, getFilteredRowModel, getPaginationRowModel, getSortedRowModel, useReactTable } from "@tanstack/react-table";
6
6
  import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, Badge, Button, Calendar, Checkbox, Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuItem, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuTrigger, Input, Label, Popover, PopoverContent, PopoverTrigger, Select as Select$1, SelectContent, SelectItem, SelectTrigger, SelectValue, Separator, Slider, Switch, Table as Table$1, TableBody, TableCell, TableHead, TableHeader, TableRow, cn as cn$1 } from "@wordrhyme/shadcn";
7
7
  import { clsx } from "clsx";
8
8
  import { twMerge } from "tailwind-merge";
9
- import { Fragment, jsx, jsxs } from "react/jsx-runtime";
9
+ import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
10
10
  import { customAlphabet } from "nanoid";
11
11
  import { ActionBar, ActionBarClose, ActionBarGroup, ActionBarItem, ActionBarSelection, ActionBarSeparator, Faceted, FacetedBadgeList, FacetedContent, FacetedEmpty, FacetedGroup, FacetedInput, FacetedItem, FacetedList, FacetedTrigger, MultiCombobox, Select, Select as Select$2, Sortable, SortableContent, SortableItem, SortableItemHandle, SortableOverlay } from "@wordrhyme/shadcn-ui";
12
12
  import { AlertDialog as AlertDialog$1, AlertDialogAction as AlertDialogAction$1, AlertDialogCancel as AlertDialogCancel$1, AlertDialogContent as AlertDialogContent$1, AlertDialogDescription as AlertDialogDescription$1, AlertDialogFooter as AlertDialogFooter$1, AlertDialogHeader as AlertDialogHeader$1, AlertDialogTitle as AlertDialogTitle$1, Button as Button$1, Calendar as Calendar$1, Dialog as Dialog$1, DialogContent as DialogContent$1, DialogDescription as DialogDescription$1, DialogHeader as DialogHeader$1, DialogTitle as DialogTitle$1, DropdownMenu as DropdownMenu$1, DropdownMenuContent as DropdownMenuContent$1, DropdownMenuItem as DropdownMenuItem$1, DropdownMenuSeparator, DropdownMenuTrigger as DropdownMenuTrigger$1, Popover as Popover$1, PopoverContent as PopoverContent$1, PopoverTrigger as PopoverTrigger$1, Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle } from "@wordrhyme/ui";
@@ -551,7 +551,7 @@ function formatDate(date, opts = {}, preset) {
551
551
 
552
552
  //#endregion
553
553
  //#region src/components/data-table/data-table.tsx
554
- function DataTable({ table, actionBar, pagination, children, className, classNames,...props }) {
554
+ function DataTable({ table, actionBar, pagination, expandedRow, children, className, classNames,...props }) {
555
555
  useDateFormatterVersion();
556
556
  const columns = [
557
557
  ...table.getLeftVisibleLeafColumns(),
@@ -591,7 +591,7 @@ function DataTable({ table, actionBar, pagination, children, className, classNam
591
591
  }),
592
592
  /* @__PURE__ */ jsx(TableBody, {
593
593
  className: classNames?.tbody,
594
- children: table.getRowModel().rows?.length ? table.getRowModel().rows.map((row) => /* @__PURE__ */ jsx(TableRow, {
594
+ children: table.getRowModel().rows?.length ? table.getRowModel().rows.map((row) => /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(TableRow, {
595
595
  className: classNames?.tr,
596
596
  "data-state": row.getIsSelected() && "selected",
597
597
  children: row.getVisibleCells().map((cell) => {
@@ -602,7 +602,14 @@ function DataTable({ table, actionBar, pagination, children, className, classNam
602
602
  children: flexRender(cell.column.columnDef.cell, cell.getContext())
603
603
  }, cell.id);
604
604
  })
605
- }, row.id)) : /* @__PURE__ */ jsx(TableRow, {
605
+ }), expandedRow && row.getCanExpand() && row.getIsExpanded() && /* @__PURE__ */ jsx(TableRow, {
606
+ className: classNames?.tr,
607
+ children: /* @__PURE__ */ jsx(TableCell, {
608
+ colSpan: row.getVisibleCells().length,
609
+ className: classNames?.td,
610
+ children: expandedRow(row.original)
611
+ })
612
+ })] }, row.id)) : /* @__PURE__ */ jsx(TableRow, {
606
613
  className: classNames?.tr,
607
614
  children: /* @__PURE__ */ jsx(TableCell, {
608
615
  colSpan: table.getAllColumns().length,
@@ -997,6 +1004,11 @@ function useReadableFilters(columns, options = {}) {
997
1004
  filtersRef.current = initial;
998
1005
  return initial;
999
1006
  });
1007
+ React.useEffect(() => {
1008
+ const next = parseReadableFilters(getUrlParams(), columnSnapshot);
1009
+ filtersRef.current = next;
1010
+ setFilters((previous) => JSON.stringify(previous) === JSON.stringify(next) ? previous : next);
1011
+ }, [columnSnapshot]);
1000
1012
  React.useEffect(() => {
1001
1013
  const handleUrlChange = () => {
1002
1014
  if (lastWrittenUrlRef.current !== null && window.location.search === lastWrittenUrlRef.current) {
@@ -1954,7 +1966,7 @@ function DataTableFilterMenu({ table, debounceMs = DEBOUNCE_MS$1, throttleMs = T
1954
1966
  onKeyDown: onInputKeyDown
1955
1967
  }), /* @__PURE__ */ jsx(CommandList, {
1956
1968
  onScroll: selectedColumnMeta?.autoCrudFilterOnPopupScroll,
1957
- children: selectedColumn ? /* @__PURE__ */ jsxs(Fragment, { children: [
1969
+ children: selectedColumn ? /* @__PURE__ */ jsxs(Fragment$1, { children: [
1958
1970
  selectedColumn.columnDef.meta?.options && /* @__PURE__ */ jsx(CommandEmpty, { children: "No options found." }),
1959
1971
  /* @__PURE__ */ jsx(FilterValueSelector, {
1960
1972
  column: selectedColumn,
@@ -1973,7 +1985,7 @@ function DataTableFilterMenu({ table, debounceMs = DEBOUNCE_MS$1, throttleMs = T
1973
1985
  className: "sr-only",
1974
1986
  children: "Load more"
1975
1987
  })
1976
- ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(CommandEmpty, { children: "No fields found." }), /* @__PURE__ */ jsx(CommandGroup, { children: columns.map((column) => /* @__PURE__ */ jsxs(CommandItem, {
1988
+ ] }) : /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(CommandEmpty, { children: "No fields found." }), /* @__PURE__ */ jsx(CommandGroup, { children: columns.map((column) => /* @__PURE__ */ jsxs(CommandItem, {
1977
1989
  value: column.id,
1978
1990
  onSelect: () => {
1979
1991
  setSelectedColumn(column);
@@ -2157,7 +2169,7 @@ function FilterValueSelector({ column, value, onSelect }) {
2157
2169
  value,
2158
2170
  onSelect: () => onSelect(value),
2159
2171
  disabled: isEmpty,
2160
- children: isEmpty ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Text, {}), /* @__PURE__ */ jsx("span", { children: "Type to add filter..." })] }) : /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(BadgeCheck, {}), /* @__PURE__ */ jsxs("span", {
2172
+ children: isEmpty ? /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(Text, {}), /* @__PURE__ */ jsx("span", { children: "Type to add filter..." })] }) : /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(BadgeCheck, {}), /* @__PURE__ */ jsxs("span", {
2161
2173
  className: "truncate",
2162
2174
  children: [
2163
2175
  "Filter by \"",
@@ -2243,7 +2255,7 @@ function onFilterInputRender({ today, filter, column, inputId, onFilterUpdate, s
2243
2255
  variant: "ghost",
2244
2256
  size: "sm",
2245
2257
  className: "h-full min-w-16 rounded-none border px-1.5 font-normal dark:bg-input/30",
2246
- children: selectedOptions.length === 0 ? filter.variant === "multiSelect" ? "Select options..." : "Select option..." : /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("div", {
2258
+ children: selectedOptions.length === 0 ? filter.variant === "multiSelect" ? "Select options..." : "Select option..." : /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("div", {
2247
2259
  className: "flex items-center -space-x-2 rtl:space-x-reverse",
2248
2260
  children: selectedOptions.map((selectedOption) => selectedOption.icon ? /* @__PURE__ */ jsx("div", {
2249
2261
  className: "rounded-full border bg-background p-0.5",
@@ -2956,7 +2968,7 @@ function SimpleFacetedFilter({ title, options, multiple, value, hasMore, loading
2956
2968
  children: /* @__PURE__ */ jsx(XCircle, {})
2957
2969
  }) : /* @__PURE__ */ jsx(PlusCircle, {}),
2958
2970
  title,
2959
- selectedValues.length > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
2971
+ selectedValues.length > 0 && /* @__PURE__ */ jsxs(Fragment$1, { children: [
2960
2972
  /* @__PURE__ */ jsx(Separator, {
2961
2973
  orientation: "vertical",
2962
2974
  className: "mx-0.5 h-4"
@@ -3030,7 +3042,7 @@ function SimpleSliderFilter({ title, range, unit, value, onChange }) {
3030
3042
  children: /* @__PURE__ */ jsx(XCircle, {})
3031
3043
  }) : /* @__PURE__ */ jsx(PlusCircle, {}),
3032
3044
  /* @__PURE__ */ jsx("span", { children: title }),
3033
- value && /* @__PURE__ */ jsxs(Fragment, { children: [
3045
+ value && /* @__PURE__ */ jsxs(Fragment$1, { children: [
3034
3046
  /* @__PURE__ */ jsx(Separator, {
3035
3047
  orientation: "vertical",
3036
3048
  className: "mx-0.5 h-4"
@@ -3170,7 +3182,7 @@ function SimpleDateFilter({ title, maxDate, multiple, value, onChange }) {
3170
3182
  children: /* @__PURE__ */ jsx(XCircle, {})
3171
3183
  }) : /* @__PURE__ */ jsx(CalendarIcon, {}),
3172
3184
  /* @__PURE__ */ jsx("span", { children: title }),
3173
- label && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Separator, {
3185
+ label && /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(Separator, {
3174
3186
  orientation: "vertical",
3175
3187
  className: "mx-0.5 h-4"
3176
3188
  }), /* @__PURE__ */ jsx("span", { children: label })] })
@@ -3272,7 +3284,7 @@ function DataTableColumnHeader({ column, label, className,...props }) {
3272
3284
  }), /* @__PURE__ */ jsxs(DropdownMenuContent, {
3273
3285
  align: "start",
3274
3286
  className: "w-28",
3275
- children: [column.getCanSort() && /* @__PURE__ */ jsxs(Fragment, { children: [
3287
+ children: [column.getCanSort() && /* @__PURE__ */ jsxs(Fragment$1, { children: [
3276
3288
  /* @__PURE__ */ jsxs(DropdownMenuCheckboxItem, {
3277
3289
  className: "relative pr-8 pl-2 [&>span:first-child]:right-2 [&>span:first-child]:left-auto [&_svg]:text-muted-foreground",
3278
3290
  checked: column.getIsSorted() === "asc",
@@ -3886,9 +3898,12 @@ function coerceRowValues(rows, schema) {
3886
3898
 
3887
3899
  //#endregion
3888
3900
  //#region src/lib/export.ts
3901
+ function getExportColumnIds(table) {
3902
+ return table.getAllLeafColumns().filter((column) => column.accessorFn).map((column) => column.id);
3903
+ }
3889
3904
  function exportTableToCSV(table, opts = {}) {
3890
3905
  const { filename = "table", excludeColumns = [], onlySelected = false } = opts;
3891
- const headers = table.getAllLeafColumns().map((column) => column.id).filter((id) => !excludeColumns.includes(id));
3906
+ const headers = getExportColumnIds(table).filter((id) => !excludeColumns.includes(id));
3892
3907
  downloadCSV([headers.join(","), ...(onlySelected ? table.getFilteredSelectedRowModel().rows : table.getRowModel().rows).map((row) => headers.map((header) => {
3893
3908
  const cellValue = row.getValue(header);
3894
3909
  return typeof cellValue === "string" ? `"${cellValue.replace(/"/g, "\"\"")}"` : cellValue;
@@ -4076,7 +4091,7 @@ function AutoTableActionBar({ table, onDeleteSelected, onUpdateSelected, batchUp
4076
4091
  { type: "delete" }
4077
4092
  ]) : actions;
4078
4093
  let resolvedActions = configuredActions?.filter((action$1) => !action$1.hidden);
4079
- if (configuredActions === void 0) return /* @__PURE__ */ jsxs(Fragment, { children: [
4094
+ if (configuredActions === void 0) return /* @__PURE__ */ jsxs(Fragment$1, { children: [
4080
4095
  renderBuiltinAction({ type: "batchUpdate" }),
4081
4096
  extraActions,
4082
4097
  showDefaultExport && renderBuiltinAction({ type: "export" }),
@@ -4099,7 +4114,7 @@ function AutoTableActionBar({ table, onDeleteSelected, onUpdateSelected, batchUp
4099
4114
  if (!hasBuiltin) {
4100
4115
  const startNodes = resolvedActions.filter(isBatchCustomAction).filter((action$1) => action$1.position === "start").map((action$1, index$1) => renderCustomAction(action$1, index$1));
4101
4116
  const endNodes = resolvedActions.filter(isBatchCustomAction).filter((action$1) => action$1.position !== "start").map((action$1, index$1) => renderCustomAction(action$1, index$1));
4102
- return /* @__PURE__ */ jsxs(Fragment, { children: [
4117
+ return /* @__PURE__ */ jsxs(Fragment$1, { children: [
4103
4118
  startNodes,
4104
4119
  renderBuiltinAction({ type: "batchUpdate" }),
4105
4120
  extraActions,
@@ -4399,7 +4414,9 @@ const filterModeConfig = {
4399
4414
  tooltip: "Linear-style command filters"
4400
4415
  }
4401
4416
  };
4402
- function AutoTable({ schema, data, pageCount = 1, pagination, total, overrides, classNames, enableRowSelection = true, exclude, actions, pinnedColumns, filterMode, search = false, onDeleteSelected, onUpdateSelected, batchUpdateFields, actionBarExtra, actionBarActions, deleteConfirmation, initialSorting, enableExport = true, showDefaultExport = true, onSelectedCountChange, onSelectedRowsChange, getSelectedRows }) {
4417
+ function AutoTable({ schema, data, getRowId, expandedRow, pageCount = 1, pagination, total, overrides, classNames, enableRowSelection = true, exclude, filterOnly, actions, pinnedColumns, filterMode, search = false, onDeleteSelected, onUpdateSelected, batchUpdateFields, actionBarExtra, actionBarActions, deleteConfirmation, initialSorting, enableExport = true, showDefaultExport = true, onSelectedCountChange, onSelectedRowsChange, getSelectedRows }) {
4418
+ if (expandedRow && !getRowId) throw new Error("AutoTable requires getRowId when expandedRow is provided.");
4419
+ const enableExpansion = !!expandedRow;
4403
4420
  const modes = filterMode ? Array.isArray(filterMode) ? filterMode : [filterMode] : DEFAULT_MODES;
4404
4421
  const [currentMode, setCurrentMode] = useQueryState("filterMode", parseAsStringEnum(modes).withDefault(modes[0] ?? "simple"));
4405
4422
  const showToggle = modes.length > 1;
@@ -4413,36 +4430,85 @@ function AutoTable({ schema, data, pageCount = 1, pagination, total, overrides,
4413
4430
  });
4414
4431
  const searchConfig = search && typeof search === "object" ? search : {};
4415
4432
  const enableAdvancedFilter = true;
4416
- const columns = useMemo(() => {
4433
+ const { columns, expandColumnId } = useMemo(() => {
4434
+ const columnOverrides = Object.assign({}, overrides);
4435
+ for (const key of filterOnly ?? []) {
4436
+ const field = key;
4437
+ Object.assign(columnOverrides, { [field]: {
4438
+ ...columnOverrides[field],
4439
+ hidden: false
4440
+ } });
4441
+ }
4417
4442
  const dataColumns = createTableSchema(schema, {
4418
- overrides,
4443
+ overrides: columnOverrides,
4419
4444
  exclude
4445
+ }).map((column) => {
4446
+ const key = column.id ?? ("accessorKey" in column ? String(column.accessorKey) : "");
4447
+ return filterOnly?.includes(key) ? {
4448
+ ...column,
4449
+ enableHiding: false,
4450
+ enableSorting: false
4451
+ } : column;
4420
4452
  });
4421
4453
  const result = enableRowSelection ? [createSelectColumn(), ...dataColumns] : dataColumns;
4454
+ let expandColumnId$1 = "expand";
4455
+ if (enableExpansion) {
4456
+ while (expandColumnId$1 in schema.shape || result.some((column) => column.id === expandColumnId$1)) expandColumnId$1 = `_${expandColumnId$1}`;
4457
+ result.unshift({
4458
+ id: expandColumnId$1,
4459
+ header: () => /* @__PURE__ */ jsx("span", {
4460
+ className: "sr-only",
4461
+ children: "Row details"
4462
+ }),
4463
+ cell: ({ row }) => /* @__PURE__ */ jsx(Button, {
4464
+ type: "button",
4465
+ variant: "ghost",
4466
+ size: "icon-sm",
4467
+ "aria-label": row.getIsExpanded() ? "Collapse row" : "Expand row",
4468
+ "aria-expanded": row.getIsExpanded(),
4469
+ onClick: row.getToggleExpandedHandler(),
4470
+ children: /* @__PURE__ */ jsx(ChevronRightIcon, { className: row.getIsExpanded() ? "size-4 rotate-90" : "size-4" })
4471
+ }),
4472
+ enableHiding: false,
4473
+ enableSorting: false,
4474
+ size: 40
4475
+ });
4476
+ }
4422
4477
  if (actions) result.push(createActionsColumn(actions));
4423
- return result;
4478
+ return {
4479
+ columns: result,
4480
+ expandColumnId: expandColumnId$1
4481
+ };
4424
4482
  }, [
4425
4483
  schema,
4426
4484
  overrides,
4427
4485
  enableRowSelection,
4486
+ enableExpansion,
4428
4487
  exclude,
4429
- actions
4488
+ actions,
4489
+ filterOnly
4430
4490
  ]);
4431
4491
  const stableInitialState = useMemo(() => ({
4432
4492
  sorting: initialSorting,
4433
4493
  columnPinning: pinnedColumns ?? {
4434
- left: enableRowSelection ? ["select"] : [],
4494
+ left: [...enableExpansion ? [expandColumnId] : [], ...enableRowSelection ? ["select"] : []],
4435
4495
  right: actions ? ["actions"] : []
4436
4496
  }
4437
4497
  }), [
4438
4498
  initialSorting,
4439
4499
  pinnedColumns,
4440
4500
  enableRowSelection,
4501
+ enableExpansion,
4502
+ expandColumnId,
4441
4503
  actions
4442
4504
  ]);
4505
+ const expandedRowModel = useMemo(() => getExpandedRowModel(), []);
4443
4506
  const { table, shallow, debounceMs, throttleMs } = useDataTable({
4444
4507
  data,
4445
4508
  columns,
4509
+ getRowId,
4510
+ getRowCanExpand: () => enableExpansion,
4511
+ getExpandedRowModel: enableExpansion ? expandedRowModel : void 0,
4446
4512
  pageCount,
4447
4513
  ...total !== void 0 ? { rowCount: total } : {},
4448
4514
  enableAdvancedFilter,
@@ -4450,6 +4516,16 @@ function AutoTable({ schema, data, pageCount = 1, pagination, total, overrides,
4450
4516
  shallow: false,
4451
4517
  clearOnDefault: true
4452
4518
  });
4519
+ useEffect(() => {
4520
+ if (!filterOnly?.length) return;
4521
+ table.setColumnVisibility((previous) => {
4522
+ if (filterOnly.every((key) => previous[key] === false)) return previous;
4523
+ return {
4524
+ ...previous,
4525
+ ...Object.fromEntries(filterOnly.map((key) => [key, false]))
4526
+ };
4527
+ });
4528
+ }, [table, filterOnly]);
4453
4529
  const rowSelection = table.getState().rowSelection;
4454
4530
  const columnFilters = table.getState().columnFilters;
4455
4531
  const selectedRows = useMemo(() => table.getFilteredSelectedRowModel().rows.map((row) => row.original), [
@@ -4564,7 +4640,8 @@ function AutoTable({ schema, data, pageCount = 1, pagination, total, overrides,
4564
4640
  /* @__PURE__ */ jsx(DataTable, {
4565
4641
  pagination,
4566
4642
  table,
4567
- classNames
4643
+ classNames,
4644
+ expandedRow
4568
4645
  }),
4569
4646
  enableRowSelection && /* @__PURE__ */ jsx(AutoTableActionBar, {
4570
4647
  table,
@@ -4890,6 +4967,30 @@ function normalizeHasMore(result) {
4890
4967
 
4891
4968
  //#endregion
4892
4969
  //#region src/lib/field-config.ts
4970
+ function mergeFieldPart(base, override) {
4971
+ if (override === void 0) return base;
4972
+ if (override === false || base === false || typeof base !== "object" || typeof override !== "object" || base === null || override === null || Array.isArray(base) || Array.isArray(override)) return override;
4973
+ return {
4974
+ ...base,
4975
+ ...override
4976
+ };
4977
+ }
4978
+ function mergeFieldConfig(base, override) {
4979
+ return {
4980
+ ...base,
4981
+ ...override,
4982
+ enum: override?.enum ?? base?.enum,
4983
+ dataSource: override?.dataSource ?? base?.dataSource,
4984
+ table: mergeFieldPart(base?.table, override?.table),
4985
+ filter: mergeFieldPart(base?.filter, override?.filter),
4986
+ form: mergeFieldPart(base?.form, override?.form)
4987
+ };
4988
+ }
4989
+ function mergeFields(base, override) {
4990
+ if (!base && !override) return void 0;
4991
+ const keys = new Set([...Object.keys(base ?? {}), ...Object.keys(override ?? {})]);
4992
+ return Object.fromEntries(Array.from(keys).map((key) => [key, mergeFieldConfig(base?.[key], override?.[key])]));
4993
+ }
4893
4994
  function normalizeFieldOptions$1(options) {
4894
4995
  if (!options || options.length === 0) return void 0;
4895
4996
  return options.map((option) => ({
@@ -5986,7 +6087,7 @@ function ImportDialog({ open, onOpenChange, onImport, columns = [], title, local
5986
6087
  ] })] }), /* @__PURE__ */ jsxs("div", {
5987
6088
  className: "space-y-4",
5988
6089
  children: [
5989
- step === "upload" && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs("div", {
6090
+ step === "upload" && /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsxs("div", {
5990
6091
  className: `border-2 border-dashed rounded-lg p-8 text-center cursor-pointer transition-colors ${isDragging ? "border-primary bg-primary/5" : "border-muted-foreground/25 hover:border-muted-foreground/50"}`,
5991
6092
  onDragOver: (e) => {
5992
6093
  e.preventDefault();
@@ -6034,7 +6135,7 @@ function ImportDialog({ open, onOpenChange, onImport, columns = [], title, local
6034
6135
  onClick: handleDownloadTemplate,
6035
6136
  children: locale.downloadTemplate
6036
6137
  })] }),
6037
- step === "preview" && parsedData && /* @__PURE__ */ jsxs(Fragment, { children: [
6138
+ step === "preview" && parsedData && /* @__PURE__ */ jsxs(Fragment$1, { children: [
6038
6139
  /* @__PURE__ */ jsx("div", {
6039
6140
  className: "rounded-md border",
6040
6141
  children: /* @__PURE__ */ jsx("div", {
@@ -6089,7 +6190,7 @@ function ImportDialog({ open, onOpenChange, onImport, columns = [], title, local
6089
6190
  children: locale.importingRows(parsedData?.rows.length ?? 0)
6090
6191
  })]
6091
6192
  }),
6092
- step === "result" && result && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs("div", {
6193
+ step === "result" && result && /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsxs("div", {
6093
6194
  className: "space-y-3",
6094
6195
  children: [/* @__PURE__ */ jsxs("div", {
6095
6196
  className: "flex gap-4",
@@ -6277,7 +6378,6 @@ function resolveActions$1(targetId, zone, ownerActions) {
6277
6378
  const ownerCustomIds = new Set(ownerActions.flatMap((action$1) => isCustomAction$1(action$1) && action$1.id ? [action$1.id] : []));
6278
6379
  const baseActions = ownerActions.filter((action$1) => !action$1.hidden && !isMaskedCustom(action$1)).map((action$1) => ({ ...isCustomAction$1(action$1) && action$1.id ? customOverrides.get(action$1.id)?.action ?? action$1 : action$1 }));
6279
6380
  const isReplacedCustom = ({ action: action$1, seq: seq$1 }) => isCustomAction$1(action$1) && Boolean(action$1.id && (ownerCustomIds.has(action$1.id) || customOverrides.get(action$1.id)?.seq !== seq$1));
6280
- if (registered.length === 0) return positionActions(baseActions).map(withoutRegistryMeta);
6281
6381
  const nextActions = [...baseActions];
6282
6382
  const extraActions = [];
6283
6383
  const groups = /* @__PURE__ */ new Map();
@@ -6304,7 +6404,13 @@ function resolveActions$1(targetId, zone, ownerActions) {
6304
6404
  const items = group.sort((left, right) => left.seq - right.seq).flatMap((entry) => {
6305
6405
  const { action: action$1 } = entry;
6306
6406
  if (action$1.hidden || isReplacedCustom(entry)) return [];
6307
- if (isCustomAction$1(action$1)) return [{ ...action$1 }];
6407
+ if (isCustomAction$1(action$1)) {
6408
+ if (action$1.before) {
6409
+ extraActions.push({ ...action$1 });
6410
+ return [];
6411
+ }
6412
+ return [{ ...action$1 }];
6413
+ }
6308
6414
  const builtin = nextActions.find((item) => item.type === action$1.type);
6309
6415
  return builtin ? [builtin] : [];
6310
6416
  });
@@ -6325,6 +6431,15 @@ function resolveActions$1(targetId, zone, ownerActions) {
6325
6431
  }
6326
6432
  } else extraActions.push(...items);
6327
6433
  }
6434
+ for (const action$1 of [...nextActions, ...extraActions]) {
6435
+ if (!isCustomAction$1(action$1) || !action$1.before) continue;
6436
+ const anchor = nextActions.find((item) => !isCustomAction$1(item) && item.type === action$1.before);
6437
+ if (anchor) action$1.position = {
6438
+ anchor: anchor.id ?? anchor.type,
6439
+ side: "before"
6440
+ };
6441
+ else if (typeof action$1.position === "object") action$1.position = "end";
6442
+ }
6328
6443
  return positionActions(nextActions, extraActions).map(withoutRegistryMeta);
6329
6444
  }
6330
6445
  const crudActions = {
@@ -6474,30 +6589,6 @@ function resolveToolbarActionsWithResolver(targetId, ownerActions, context) {
6474
6589
  const resolver = autoCrudToolbarResolver;
6475
6590
  return targetId !== void 0 && targetId.length > 0 && resolver !== null ? resolver(targetId, ownerActions, context) : ownerActions;
6476
6591
  }
6477
- function mergeFieldPart(base, override) {
6478
- if (override === void 0) return base;
6479
- if (override === false || base === false || typeof base !== "object" || typeof override !== "object" || base === null || override === null || Array.isArray(base) || Array.isArray(override)) return override;
6480
- return {
6481
- ...base,
6482
- ...override
6483
- };
6484
- }
6485
- function mergeFieldConfig(base, override) {
6486
- return {
6487
- ...base,
6488
- ...override,
6489
- enum: override?.enum ?? base?.enum,
6490
- dataSource: override?.dataSource ?? base?.dataSource,
6491
- table: mergeFieldPart(base?.table, override?.table),
6492
- filter: mergeFieldPart(base?.filter, override?.filter),
6493
- form: mergeFieldPart(base?.form, override?.form)
6494
- };
6495
- }
6496
- function mergeFields(base, override) {
6497
- if (!base && !override) return void 0;
6498
- const keys = new Set([...Object.keys(base ?? {}), ...Object.keys(override ?? {})]);
6499
- return Object.fromEntries(Array.from(keys).map((key) => [key, mergeFieldConfig(base?.[key], override?.[key])]));
6500
- }
6501
6592
  function applyResourceTablePresentation(fields, resourceFields) {
6502
6593
  if (!fields || !resourceFields) return fields;
6503
6594
  return Object.fromEntries(Object.entries(fields).map(([key, config]) => {
@@ -7399,6 +7490,12 @@ function AutoCrudTable({ id, title, description, schema, resource, fields, table
7399
7490
  const [exporting, setExporting] = React.useState(false);
7400
7491
  const getSelectedRowsRef = React.useRef(null);
7401
7492
  const dynamicFilterOptions = useDynamicFilterOptions(resolvedFields);
7493
+ const filterOnly = React.useMemo(() => Object.entries(resolvedFields).filter(([key, config]) => config.table === false && config.filter && config.hidden !== true && !denyFields?.includes(key) && !tableConfig?.hidden?.includes(key) && tableConfig?.overrides?.[key]?.hidden !== true).map(([key]) => key), [
7494
+ resolvedFields,
7495
+ denyFields,
7496
+ tableConfig?.hidden,
7497
+ tableConfig?.overrides
7498
+ ]);
7402
7499
  const hiddenColumns = React.useMemo(() => buildHiddenColumns(resolvedFields, tableConfig?.hidden, denyFields, tableConfig?.overrides), [
7403
7500
  resolvedFields,
7404
7501
  tableConfig?.hidden,
@@ -7487,7 +7584,7 @@ function AutoCrudTable({ id, title, description, schema, resource, fields, table
7487
7584
  return {
7488
7585
  ...action$1,
7489
7586
  onClick: (_rows, { table }) => {
7490
- const headers = table.getAllLeafColumns().map((column) => column.id).filter((key) => key !== "select" && key !== "actions" && !denyFields?.includes(key));
7587
+ const headers = getExportColumnIds(table).filter((key) => key !== "select" && key !== "actions" && !denyFields?.includes(key));
7491
7588
  exportAllToCSV(table.getFilteredSelectedRowModel().rows.map((row) => formatExportRow(row.original, resolvedFields, Object.fromEntries(headers.map((key) => [key, row.getValue(key)])))), {
7492
7589
  filename: "table",
7493
7590
  headers
@@ -7747,9 +7844,12 @@ function AutoCrudTable({ id, title, description, schema, resource, fields, table
7747
7844
  ...pagination
7748
7845
  } : void 0,
7749
7846
  ...resource.tableData.total !== void 0 ? { total: resource.tableData.total } : {},
7847
+ getRowId: tableConfig?.getRowId,
7848
+ expandedRow: tableConfig?.expandedRow,
7750
7849
  overrides: tableOverrides,
7751
7850
  classNames: tableConfig?.classNames,
7752
- exclude: hiddenColumns,
7851
+ exclude: hiddenColumns.filter((key) => !filterOnly.includes(key)),
7852
+ filterOnly,
7753
7853
  filterMode: tableConfig?.filterModes,
7754
7854
  search: searchConfig,
7755
7855
  actions: tableRowActions,
@@ -7957,7 +8057,7 @@ function DataTableFacetedFilter({ column, title, options, multiple }) {
7957
8057
  children: /* @__PURE__ */ jsx(XCircle, {})
7958
8058
  }) : /* @__PURE__ */ jsx(PlusCircle, {}),
7959
8059
  title,
7960
- selectedValues$1.length > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
8060
+ selectedValues$1.length > 0 && /* @__PURE__ */ jsxs(Fragment$1, { children: [
7961
8061
  /* @__PURE__ */ jsx(Separator, {
7962
8062
  orientation: "vertical",
7963
8063
  className: "mx-0.5 data-[orientation=vertical]:h-4"
@@ -8116,7 +8216,7 @@ function DataTableDateFilter({ column, title, multiple }) {
8116
8216
  const dateText$1 = hasSelectedDates ? formatDateRange(selectedDates) : "Select date range";
8117
8217
  return /* @__PURE__ */ jsxs("span", {
8118
8218
  className: "flex items-center gap-2",
8119
- children: [/* @__PURE__ */ jsx("span", { children: title }), hasSelectedDates && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Separator, {
8219
+ children: [/* @__PURE__ */ jsx("span", { children: title }), hasSelectedDates && /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(Separator, {
8120
8220
  orientation: "vertical",
8121
8221
  className: "mx-0.5 data-[orientation=vertical]:h-4"
8122
8222
  }), /* @__PURE__ */ jsx("span", { children: dateText$1 })] })]
@@ -8127,7 +8227,7 @@ function DataTableDateFilter({ column, title, multiple }) {
8127
8227
  const dateText = hasSelectedDate ? formatDate$1(selectedDates[0]) : "Select date";
8128
8228
  return /* @__PURE__ */ jsxs("span", {
8129
8229
  className: "flex items-center gap-2",
8130
- children: [/* @__PURE__ */ jsx("span", { children: title }), hasSelectedDate && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Separator, {
8230
+ children: [/* @__PURE__ */ jsx("span", { children: title }), hasSelectedDate && /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(Separator, {
8131
8231
  orientation: "vertical",
8132
8232
  className: "mx-0.5 data-[orientation=vertical]:h-4"
8133
8233
  }), /* @__PURE__ */ jsx("span", { children: dateText })] })]
@@ -8283,7 +8383,7 @@ function DataTableSliderFilter({ column, title }) {
8283
8383
  children: /* @__PURE__ */ jsx(XCircle, {})
8284
8384
  }) : /* @__PURE__ */ jsx(PlusCircle, {}),
8285
8385
  /* @__PURE__ */ jsx("span", { children: title }),
8286
- columnFilterValue ? /* @__PURE__ */ jsxs(Fragment, { children: [
8386
+ columnFilterValue ? /* @__PURE__ */ jsxs(Fragment$1, { children: [
8287
8387
  /* @__PURE__ */ jsx(Separator, {
8288
8388
  orientation: "vertical",
8289
8389
  className: "mx-0.5 data-[orientation=vertical]:h-4"
@@ -8582,7 +8682,7 @@ function modalReducer(state, action$1) {
8582
8682
  /**
8583
8683
  * Hook 主函数
8584
8684
  */
8585
- function useAutoCrudResource({ router, schema, query: queryTransform, options = {} }) {
8685
+ function useAutoCrudResource({ router, schema, query: queryTransform, fields, options = {} }) {
8586
8686
  const { idKey = "id", defaultVariant = "dialog", hooks, toast: toastAdapter, exportFetcher, defaultSort: defaultSortOption } = options;
8587
8687
  const toast$1 = useMemo(() => toastAdapter === false ? noopToastAdapter : toastAdapter ?? defaultToastAdapter, [toastAdapter]);
8588
8688
  const hooksRef = useRef(hooks);
@@ -8594,7 +8694,14 @@ function useAutoCrudResource({ router, schema, query: queryTransform, options =
8594
8694
  const defaultSorting = useMemo(() => defaultSortOption === false ? [] : defaultSortOption ?? getDefaultSortingForSchema(resolvedSchema), [defaultSortOption, resolvedSchema]);
8595
8695
  const metadataFields = useMemo(() => readMetadataFields(metadata?.fields), [metadata?.fields]);
8596
8696
  const capabilities = metadata?.capabilities;
8597
- const columns = useMemo(() => createTableSchema(resolvedSchema), [resolvedSchema]);
8697
+ const columns = useMemo(() => createTableSchema(resolvedSchema, { overrides: Object.fromEntries(Object.entries(mergeFields(metadataFields, fields) ?? {}).flatMap(([key, field]) => {
8698
+ const filter = field.filter;
8699
+ return filter && typeof filter === "object" && filter.variant ? [[key, { meta: { variant: filter.variant } }]] : [];
8700
+ })) }), [
8701
+ resolvedSchema,
8702
+ fields,
8703
+ metadataFields
8704
+ ]);
8598
8705
  const [urlParams] = useQueryStates({
8599
8706
  page: parseAsInteger.withDefault(1),
8600
8707
  perPage: parseAsInteger.withDefault(10),
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@wordrhyme/auto-crud",
3
3
  "type": "module",
4
- "version": "1.8.2",
4
+ "version": "1.10.0",
5
5
  "description": "Schema-first CRUD components with auto-generated tables and forms",
6
6
  "author": "wordrhyme",
7
7
  "license": "MIT",
@@ -70,9 +70,9 @@
70
70
  "nanoid": "^5.1.6",
71
71
  "tailwind-merge": "^3.5.0",
72
72
  "vaul": "^1.1.2",
73
- "@wordrhyme/shadcn": "2.0.1",
74
73
  "@wordrhyme/formily-shadcn": "2.1.1",
75
- "@wordrhyme/shadcn-ui": "2.1.1"
74
+ "@wordrhyme/shadcn-ui": "2.1.1",
75
+ "@wordrhyme/shadcn": "2.0.1"
76
76
  },
77
77
  "devDependencies": {
78
78
  "@storybook/react": "^8.6.18",
@@ -93,11 +93,11 @@
93
93
  "tsdown": "^0.15.12",
94
94
  "typescript": "^5.9.3",
95
95
  "vitest": "^3.2.4",
96
- "@internal/tsconfig": "0.1.0",
97
- "@internal/prettier-config": "0.0.1",
98
96
  "@internal/eslint-config": "0.3.0",
97
+ "@internal/prettier-config": "0.0.1",
98
+ "@internal/vitest-config": "0.1.0",
99
99
  "@internal/tsdown-config": "0.1.0",
100
- "@internal/vitest-config": "0.1.0"
100
+ "@internal/tsconfig": "0.1.0"
101
101
  },
102
102
  "prettier": "@internal/prettier-config",
103
103
  "scripts": {