@wordrhyme/auto-crud 1.1.2 → 1.1.4
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 +266 -63
- package/dist/index.d.cts +51 -20
- package/dist/index.d.ts +51 -20
- package/dist/index.js +266 -64
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -2,7 +2,7 @@ import * as React from "react";
|
|
|
2
2
|
import { forwardRef, useCallback, useEffect, useImperativeHandle, useMemo, useReducer, useRef } 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
|
-
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 } from "@pixpilot/shadcn";
|
|
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";
|
|
6
6
|
import { clsx } from "clsx";
|
|
7
7
|
import { twMerge } from "tailwind-merge";
|
|
8
8
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
@@ -18,6 +18,7 @@ import { z } from "zod";
|
|
|
18
18
|
import { createForm } from "@formily/core";
|
|
19
19
|
import { Form, JsonSchemaField } from "@pixpilot/formily-shadcn";
|
|
20
20
|
import { connect, mapProps } from "@formily/react";
|
|
21
|
+
import { Combobox } from "@pixpilot/shadcn-ui";
|
|
21
22
|
import { toast } from "sonner";
|
|
22
23
|
|
|
23
24
|
//#region src/lib/utils.ts
|
|
@@ -2663,8 +2664,17 @@ function DataTableViewOptions({ table, disabled,...props }) {
|
|
|
2663
2664
|
|
|
2664
2665
|
//#endregion
|
|
2665
2666
|
//#region src/components/auto-crud/auto-table-simple-filters.tsx
|
|
2666
|
-
function AutoTableSimpleFilters({ table, shallow = false, filters: externalFilters, onFiltersChange }) {
|
|
2667
|
-
const columns = React.useMemo(() => table.getAllColumns().filter((col) => col.getCanFilter()),
|
|
2667
|
+
function AutoTableSimpleFilters({ table, shallow = false, filters: externalFilters, onFiltersChange, leading }) {
|
|
2668
|
+
const columns = React.useMemo(() => table.getAllColumns().filter((col) => col.getCanFilter()).sort((a, b) => {
|
|
2669
|
+
const readOrder = (column) => {
|
|
2670
|
+
const metaIndex = column.columnDef.meta?.index;
|
|
2671
|
+
const columnIndex = column.columnDef.index;
|
|
2672
|
+
return typeof metaIndex === "number" ? metaIndex : typeof columnIndex === "number" ? columnIndex : 1e4;
|
|
2673
|
+
};
|
|
2674
|
+
const orderDiff = readOrder(a) - readOrder(b);
|
|
2675
|
+
if (orderDiff !== 0) return orderDiff;
|
|
2676
|
+
return a.getIndex() - b.getIndex();
|
|
2677
|
+
}), [table]);
|
|
2668
2678
|
const queryStateOptions = table.options.meta?.queryStateOptions;
|
|
2669
2679
|
const [queryFilters, setQueryFilters] = useReadableFilters(columns, {
|
|
2670
2680
|
...queryStateOptions,
|
|
@@ -2721,32 +2731,65 @@ function AutoTableSimpleFilters({ table, shallow = false, filters: externalFilte
|
|
|
2721
2731
|
const [expanded, setExpanded] = React.useState(false);
|
|
2722
2732
|
const [visibleCount, setVisibleCount] = React.useState(null);
|
|
2723
2733
|
const containerRef = React.useRef(null);
|
|
2734
|
+
const hasLeading = leading != null;
|
|
2735
|
+
const filterMeasureKey = React.useMemo(() => filters.map((filter) => [
|
|
2736
|
+
filter.id,
|
|
2737
|
+
filter.operator,
|
|
2738
|
+
Array.isArray(filter.value) ? filter.value.join(",") : String(filter.value ?? "")
|
|
2739
|
+
].join(":")).join("|"), [filters]);
|
|
2724
2740
|
const calcVisibleCount = React.useCallback(() => {
|
|
2725
2741
|
const el = containerRef.current;
|
|
2726
2742
|
if (!el) return;
|
|
2727
|
-
const items = el.querySelectorAll("[data-filter-item]");
|
|
2728
|
-
if (items.length === 0)
|
|
2743
|
+
const items = Array.from(el.querySelectorAll("[data-filter-item]"));
|
|
2744
|
+
if (items.length === 0) {
|
|
2745
|
+
setVisibleCount(null);
|
|
2746
|
+
return;
|
|
2747
|
+
}
|
|
2729
2748
|
const restoreList = [];
|
|
2730
|
-
el.querySelectorAll(".hidden").forEach((node) => {
|
|
2731
|
-
|
|
2732
|
-
|
|
2733
|
-
restoreList.push(htmlNode);
|
|
2749
|
+
el.querySelectorAll("[data-filter-item].hidden, [data-filter-toggle].hidden").forEach((node) => {
|
|
2750
|
+
node.classList.remove("hidden");
|
|
2751
|
+
restoreList.push(node);
|
|
2734
2752
|
});
|
|
2735
|
-
el.
|
|
2736
|
-
el.
|
|
2737
|
-
const
|
|
2753
|
+
const availableWidth = el.getBoundingClientRect().width;
|
|
2754
|
+
const previousFlex = el.style.flex;
|
|
2755
|
+
const previousWidth = el.style.width;
|
|
2756
|
+
el.style.flex = `0 0 ${availableWidth}px`;
|
|
2757
|
+
el.style.width = `${availableWidth}px`;
|
|
2758
|
+
el.getBoundingClientRect();
|
|
2759
|
+
const computedStyle = window.getComputedStyle(el);
|
|
2760
|
+
const gap = Number.parseFloat(computedStyle.columnGap) || Number.parseFloat(computedStyle.gap) || 0;
|
|
2761
|
+
const leadingEl = el.querySelector("[data-filter-leading]");
|
|
2762
|
+
const toggle = el.querySelector("[data-filter-toggle]");
|
|
2763
|
+
const reset = el.querySelector("[data-filter-reset]");
|
|
2764
|
+
const addWidth = (current, next) => {
|
|
2765
|
+
if (next <= 0) return current;
|
|
2766
|
+
if (current === 0) return next;
|
|
2767
|
+
return current + gap + next;
|
|
2768
|
+
};
|
|
2769
|
+
const leadingWidth = leadingEl?.offsetWidth ?? 0;
|
|
2770
|
+
const itemWidths = items.map((item) => item.offsetWidth);
|
|
2771
|
+
const toggleWidth = toggle?.offsetWidth ?? 0;
|
|
2772
|
+
const resetWidth = reset?.offsetWidth ?? 0;
|
|
2773
|
+
if (itemWidths.reduce((width, itemWidth) => addWidth(width, itemWidth), addWidth(leadingWidth, resetWidth)) <= availableWidth) {
|
|
2774
|
+
el.style.flex = previousFlex;
|
|
2775
|
+
el.style.width = previousWidth;
|
|
2776
|
+
restoreList.forEach((node) => node.classList.add("hidden"));
|
|
2777
|
+
setVisibleCount(null);
|
|
2778
|
+
return;
|
|
2779
|
+
}
|
|
2780
|
+
const trailingWidth = addWidth(addWidth(0, toggleWidth), resetWidth);
|
|
2781
|
+
let usedWidth = leadingWidth;
|
|
2738
2782
|
let count = 0;
|
|
2739
|
-
for (const
|
|
2740
|
-
|
|
2783
|
+
for (const itemWidth of itemWidths) {
|
|
2784
|
+
const nextWidth = addWidth(usedWidth, itemWidth);
|
|
2785
|
+
if (addWidth(nextWidth, trailingWidth) > availableWidth) break;
|
|
2786
|
+
usedWidth = nextWidth;
|
|
2741
2787
|
count++;
|
|
2742
2788
|
}
|
|
2743
|
-
|
|
2744
|
-
|
|
2745
|
-
if (btn.offsetTop > firstTop) count--;
|
|
2746
|
-
}
|
|
2747
|
-
el.style.flex = "";
|
|
2789
|
+
el.style.flex = previousFlex;
|
|
2790
|
+
el.style.width = previousWidth;
|
|
2748
2791
|
restoreList.forEach((node) => node.classList.add("hidden"));
|
|
2749
|
-
setVisibleCount(count
|
|
2792
|
+
setVisibleCount(count);
|
|
2750
2793
|
}, []);
|
|
2751
2794
|
React.useLayoutEffect(() => {
|
|
2752
2795
|
if (!mounted) return;
|
|
@@ -2754,6 +2797,9 @@ function AutoTableSimpleFilters({ table, shallow = false, filters: externalFilte
|
|
|
2754
2797
|
}, [
|
|
2755
2798
|
mounted,
|
|
2756
2799
|
filterColumns.length,
|
|
2800
|
+
hasLeading,
|
|
2801
|
+
hasFilters,
|
|
2802
|
+
filterMeasureKey,
|
|
2757
2803
|
calcVisibleCount
|
|
2758
2804
|
]);
|
|
2759
2805
|
React.useEffect(() => {
|
|
@@ -2776,13 +2822,19 @@ function AutoTableSimpleFilters({ table, shallow = false, filters: externalFilte
|
|
|
2776
2822
|
}, [
|
|
2777
2823
|
mounted,
|
|
2778
2824
|
filterColumns.length,
|
|
2825
|
+
hasLeading,
|
|
2779
2826
|
calcVisibleCount
|
|
2780
2827
|
]);
|
|
2781
2828
|
if (!mounted) return null;
|
|
2782
2829
|
return /* @__PURE__ */ jsxs("div", {
|
|
2783
2830
|
ref: containerRef,
|
|
2784
|
-
className: "flex flex-wrap items-center gap-2 min-w-0",
|
|
2831
|
+
className: "flex flex-1 flex-wrap items-center gap-2 min-w-0",
|
|
2785
2832
|
children: [
|
|
2833
|
+
leading != null ? /* @__PURE__ */ jsx("div", {
|
|
2834
|
+
className: "shrink-0",
|
|
2835
|
+
"data-filter-leading": true,
|
|
2836
|
+
children: leading
|
|
2837
|
+
}) : null,
|
|
2786
2838
|
filterColumns.map((column, index) => {
|
|
2787
2839
|
const meta = column.columnDef.meta;
|
|
2788
2840
|
const value = getFilterValue(column.id);
|
|
@@ -2846,6 +2898,7 @@ function AutoTableSimpleFilters({ table, shallow = false, filters: externalFilte
|
|
|
2846
2898
|
children: expanded ? /* @__PURE__ */ jsx(ChevronUp, { className: "size-4" }) : /* @__PURE__ */ jsx(ChevronDown, { className: "size-4" })
|
|
2847
2899
|
}),
|
|
2848
2900
|
hasFilters && /* @__PURE__ */ jsxs(Button, {
|
|
2901
|
+
"data-filter-reset": true,
|
|
2849
2902
|
variant: "outline",
|
|
2850
2903
|
size: "sm",
|
|
2851
2904
|
className: "h-8 border-dashed shrink-0",
|
|
@@ -3915,7 +3968,7 @@ function createTableSchema(schema, options) {
|
|
|
3915
3968
|
if (cached) return [...cached];
|
|
3916
3969
|
}
|
|
3917
3970
|
const shape = schema.shape;
|
|
3918
|
-
const
|
|
3971
|
+
const columnEntries = [];
|
|
3919
3972
|
for (const [key, fieldSchema] of Object.entries(shape)) {
|
|
3920
3973
|
if (exclude.includes(key)) continue;
|
|
3921
3974
|
const override = overrides?.[key];
|
|
@@ -3936,20 +3989,31 @@ function createTableSchema(schema, options) {
|
|
|
3936
3989
|
options: override?.meta?.options ?? autoOptions
|
|
3937
3990
|
};
|
|
3938
3991
|
const { meta: _ignoredMeta,...restOverride } = override ?? {};
|
|
3939
|
-
|
|
3940
|
-
|
|
3941
|
-
|
|
3942
|
-
|
|
3943
|
-
|
|
3944
|
-
|
|
3945
|
-
|
|
3946
|
-
|
|
3947
|
-
|
|
3948
|
-
|
|
3949
|
-
|
|
3950
|
-
|
|
3992
|
+
const sourceIndex = columnEntries.length;
|
|
3993
|
+
const order = typeof restOverride.index === "number" ? restOverride.index : typeof meta.index === "number" ? meta.index : 1e4 + sourceIndex;
|
|
3994
|
+
columnEntries.push({
|
|
3995
|
+
order,
|
|
3996
|
+
sourceIndex,
|
|
3997
|
+
column: {
|
|
3998
|
+
id: key,
|
|
3999
|
+
accessorKey: key,
|
|
4000
|
+
header: ({ column }) => /* @__PURE__ */ jsx(DataTableColumnHeader, {
|
|
4001
|
+
column,
|
|
4002
|
+
label
|
|
4003
|
+
}),
|
|
4004
|
+
cell: ({ row }) => renderCell(row.getValue(key), parsed.type),
|
|
4005
|
+
enableColumnFilter: true,
|
|
4006
|
+
enableSorting: true,
|
|
4007
|
+
meta,
|
|
4008
|
+
...restOverride
|
|
4009
|
+
}
|
|
3951
4010
|
});
|
|
3952
4011
|
}
|
|
4012
|
+
const columns = columnEntries.sort((a, b) => {
|
|
4013
|
+
const orderDiff = a.order - b.order;
|
|
4014
|
+
if (orderDiff !== 0) return orderDiff;
|
|
4015
|
+
return a.sourceIndex - b.sourceIndex;
|
|
4016
|
+
}).map((entry) => entry.column);
|
|
3953
4017
|
if (canCache) baseTableSchemaCache.set(schema, columns);
|
|
3954
4018
|
return columns;
|
|
3955
4019
|
}
|
|
@@ -4621,10 +4685,19 @@ const filterModeConfig = {
|
|
|
4621
4685
|
tooltip: "Linear-style command filters"
|
|
4622
4686
|
}
|
|
4623
4687
|
};
|
|
4624
|
-
function AutoTable({ schema, data, pageCount = 1, overrides, enableRowSelection = true, exclude, actions, pinnedColumns, filterMode = "simple", onDeleteSelected, onUpdateSelected, batchUpdateFields, actionBarExtra, actionBarActions, initialSorting, enableExport = true, showDefaultExport = true, onSelectedCountChange, getSelectedRows }) {
|
|
4688
|
+
function AutoTable({ schema, data, pageCount = 1, overrides, enableRowSelection = true, exclude, actions, pinnedColumns, filterMode = "simple", search = false, onDeleteSelected, onUpdateSelected, batchUpdateFields, actionBarExtra, actionBarActions, initialSorting, enableExport = true, showDefaultExport = true, onSelectedCountChange, getSelectedRows }) {
|
|
4625
4689
|
const modes = filterMode ? Array.isArray(filterMode) ? filterMode : [filterMode] : DEFAULT_MODES;
|
|
4626
4690
|
const [currentMode, setCurrentMode] = useQueryState("filterMode", parseAsStringEnum(modes).withDefault(modes[0] ?? "simple"));
|
|
4627
4691
|
const showToggle = modes.length > 1;
|
|
4692
|
+
const [searchValue, setSearchValue] = useUrlState("search", parseAsString.withDefault(""), {
|
|
4693
|
+
shallow: false,
|
|
4694
|
+
clearOnDefault: true
|
|
4695
|
+
});
|
|
4696
|
+
const [, setSearchPage] = useUrlState("page", parseAsInteger.withDefault(1), {
|
|
4697
|
+
shallow: false,
|
|
4698
|
+
clearOnDefault: true
|
|
4699
|
+
});
|
|
4700
|
+
const searchConfig = search && typeof search === "object" ? search : {};
|
|
4628
4701
|
const { table, shallow, debounceMs, throttleMs } = useDataTable({
|
|
4629
4702
|
data,
|
|
4630
4703
|
columns: useMemo(() => {
|
|
@@ -4696,11 +4769,21 @@ function AutoTable({ schema, data, pageCount = 1, overrides, enableRowSelection
|
|
|
4696
4769
|
})
|
|
4697
4770
|
})
|
|
4698
4771
|
})] }) : null;
|
|
4772
|
+
const searchInput = search ? /* @__PURE__ */ jsx(Input, {
|
|
4773
|
+
value: searchValue,
|
|
4774
|
+
onChange: (event) => {
|
|
4775
|
+
setSearchValue(event.target.value);
|
|
4776
|
+
setSearchPage(1);
|
|
4777
|
+
},
|
|
4778
|
+
placeholder: searchConfig.placeholder ?? "搜索",
|
|
4779
|
+
className: "h-8 w-48 shrink-0"
|
|
4780
|
+
}) : null;
|
|
4699
4781
|
const filtersContent = useMemo(() => {
|
|
4700
4782
|
switch (currentMode) {
|
|
4701
4783
|
case "simple": return /* @__PURE__ */ jsx(AutoTableSimpleFilters, {
|
|
4702
4784
|
table,
|
|
4703
|
-
shallow
|
|
4785
|
+
shallow,
|
|
4786
|
+
leading: searchInput
|
|
4704
4787
|
});
|
|
4705
4788
|
case "command": return /* @__PURE__ */ jsx(DataTableFilterMenu, {
|
|
4706
4789
|
table,
|
|
@@ -4720,6 +4803,7 @@ function AutoTable({ schema, data, pageCount = 1, overrides, enableRowSelection
|
|
|
4720
4803
|
currentMode,
|
|
4721
4804
|
table,
|
|
4722
4805
|
shallow,
|
|
4806
|
+
searchInput,
|
|
4723
4807
|
debounceMs,
|
|
4724
4808
|
throttleMs
|
|
4725
4809
|
]);
|
|
@@ -4729,12 +4813,16 @@ function AutoTable({ schema, data, pageCount = 1, overrides, enableRowSelection
|
|
|
4729
4813
|
/* @__PURE__ */ jsxs("div", {
|
|
4730
4814
|
className: "flex w-full items-start justify-between gap-2 p-1",
|
|
4731
4815
|
children: [/* @__PURE__ */ jsxs("div", {
|
|
4732
|
-
className: "flex flex-1 items-start gap-2
|
|
4816
|
+
className: "flex min-h-[40px] min-w-0 flex-1 items-start gap-2",
|
|
4733
4817
|
"data-filter-parent": true,
|
|
4734
|
-
children: [
|
|
4735
|
-
|
|
4736
|
-
|
|
4737
|
-
|
|
4818
|
+
children: [
|
|
4819
|
+
currentMode !== "simple" && searchInput,
|
|
4820
|
+
currentMode !== "simple" && /* @__PURE__ */ jsx(DataTableSortList, {
|
|
4821
|
+
table,
|
|
4822
|
+
align: "start"
|
|
4823
|
+
}),
|
|
4824
|
+
filtersContent
|
|
4825
|
+
]
|
|
4738
4826
|
}), /* @__PURE__ */ jsxs("div", {
|
|
4739
4827
|
className: "flex items-center gap-2",
|
|
4740
4828
|
children: [
|
|
@@ -4923,10 +5011,44 @@ function createEditFormSchema(schema, options) {
|
|
|
4923
5011
|
|
|
4924
5012
|
//#endregion
|
|
4925
5013
|
//#region src/components/auto-crud/auto-form.tsx
|
|
5014
|
+
const COMBOBOX_LIST_CLASS = "[&_[cmdk-list]]:max-h-[360px]";
|
|
4926
5015
|
const FormilySwitch = connect(Switch, mapProps({
|
|
4927
5016
|
value: "checked",
|
|
4928
5017
|
onInput: "onCheckedChange"
|
|
4929
5018
|
}));
|
|
5019
|
+
const FormilyCombobox = connect(AutoCrudCombobox, mapProps({ dataSource: "options" }));
|
|
5020
|
+
function AutoCrudCombobox({ options = [], filter, className,...props }) {
|
|
5021
|
+
const resolvedFilter = useMemo(() => {
|
|
5022
|
+
if (typeof filter === "function") return filter;
|
|
5023
|
+
return createComboboxFilter(options);
|
|
5024
|
+
}, [filter, options]);
|
|
5025
|
+
return /* @__PURE__ */ jsx(Combobox, {
|
|
5026
|
+
...props,
|
|
5027
|
+
options,
|
|
5028
|
+
filter: resolvedFilter,
|
|
5029
|
+
className: cn$1(COMBOBOX_LIST_CLASS, className)
|
|
5030
|
+
});
|
|
5031
|
+
}
|
|
5032
|
+
function createComboboxFilter(options) {
|
|
5033
|
+
const searchTextByValue = new Map(options.map((option) => [option.value, collectSearchText(option.value, option.label, option.searchText, option.keywords).toLocaleLowerCase()]));
|
|
5034
|
+
return (value, search, keywords) => {
|
|
5035
|
+
const query = search.trim().toLocaleLowerCase();
|
|
5036
|
+
if (!query) return 1;
|
|
5037
|
+
const searchText = collectSearchText(searchTextByValue.get(value), keywords).toLocaleLowerCase();
|
|
5038
|
+
if (searchText === query) return 2;
|
|
5039
|
+
return searchText.includes(query) ? 1 : 0;
|
|
5040
|
+
};
|
|
5041
|
+
}
|
|
5042
|
+
function collectSearchText(...parts) {
|
|
5043
|
+
const texts = [];
|
|
5044
|
+
for (const part of parts) {
|
|
5045
|
+
if (!part) continue;
|
|
5046
|
+
if (Array.isArray(part)) texts.push(...part.map(String));
|
|
5047
|
+
else if (typeof part === "object") texts.push(...Object.values(part).filter(Boolean).map(String));
|
|
5048
|
+
else texts.push(String(part));
|
|
5049
|
+
}
|
|
5050
|
+
return texts.join(" ");
|
|
5051
|
+
}
|
|
4930
5052
|
function AutoFormInner({ schema: zodSchema, initialValues, onSubmit, overrides, mode = "create", loading = false, gridColumns = 1, labelAlign = "top", labelWidth, showSubmitButton = true }, ref) {
|
|
4931
5053
|
const form = useMemo(() => createForm({ initialValues }), [JSON.stringify(initialValues)]);
|
|
4932
5054
|
const formSchema = useMemo(() => createEditFormSchema(zodSchema, {
|
|
@@ -4951,10 +5073,16 @@ function AutoFormInner({ schema: zodSchema, initialValues, onSubmit, overrides,
|
|
|
4951
5073
|
form,
|
|
4952
5074
|
children: [/* @__PURE__ */ jsx(JsonSchemaField, {
|
|
4953
5075
|
schema: formSchema,
|
|
4954
|
-
components: { fields: {
|
|
4955
|
-
|
|
4956
|
-
|
|
4957
|
-
|
|
5076
|
+
components: { fields: {
|
|
5077
|
+
Combobox: {
|
|
5078
|
+
component: FormilyCombobox,
|
|
5079
|
+
decorator: "FormItem"
|
|
5080
|
+
},
|
|
5081
|
+
Switch: {
|
|
5082
|
+
component: FormilySwitch,
|
|
5083
|
+
decorator: "FormItem"
|
|
5084
|
+
}
|
|
5085
|
+
} }
|
|
4958
5086
|
}), showSubmitButton && /* @__PURE__ */ jsx("div", {
|
|
4959
5087
|
className: "flex justify-end gap-2 mt-6",
|
|
4960
5088
|
children: /* @__PURE__ */ jsx(Button, {
|
|
@@ -6046,6 +6174,11 @@ function resolveActions(actionsOrFn, defaults, rowActionsLocale) {
|
|
|
6046
6174
|
*/
|
|
6047
6175
|
function AutoCrudTable({ title, description, schema, resource, fields, table: tableConfig, form: formConfig, permissions, actions: actionItems, toolbarActions, locale: localeProp, onCreate }) {
|
|
6048
6176
|
const locale = resolveLocale(localeProp);
|
|
6177
|
+
const resolvedSchema = resource.schema ?? schema;
|
|
6178
|
+
const resolvedFields = React.useMemo(() => ({
|
|
6179
|
+
...fields,
|
|
6180
|
+
...resource.fields
|
|
6181
|
+
}), [fields, resource.fields]);
|
|
6049
6182
|
const can = {
|
|
6050
6183
|
create: permissions?.can?.create ?? true,
|
|
6051
6184
|
update: permissions?.can?.update ?? true,
|
|
@@ -6061,10 +6194,10 @@ function AutoCrudTable({ title, description, schema, resource, fields, table: ta
|
|
|
6061
6194
|
const [exporting, setExporting] = React.useState(false);
|
|
6062
6195
|
const getSelectedRowsRef = React.useRef(null);
|
|
6063
6196
|
const importColumns = React.useMemo(() => {
|
|
6064
|
-
const shape =
|
|
6197
|
+
const shape = resolvedSchema.shape;
|
|
6065
6198
|
const denySet = new Set(denyFields ?? []);
|
|
6066
6199
|
return Object.keys(shape).filter((key) => !denySet.has(key));
|
|
6067
|
-
}, [
|
|
6200
|
+
}, [resolvedSchema, denyFields]);
|
|
6068
6201
|
const handleExport = React.useCallback(async (mode) => {
|
|
6069
6202
|
const filename = title ?? "export";
|
|
6070
6203
|
const excludeColumns = denyFields;
|
|
@@ -6096,21 +6229,34 @@ function AutoCrudTable({ title, description, schema, resource, fields, table: ta
|
|
|
6096
6229
|
setExporting(false);
|
|
6097
6230
|
}
|
|
6098
6231
|
}, [selectedCount, handleExport]);
|
|
6099
|
-
const tableOverrides = React.useMemo(() => buildTableOverrides(
|
|
6100
|
-
const formOverrides = React.useMemo(() => buildFormOverrides(
|
|
6101
|
-
|
|
6232
|
+
const tableOverrides = React.useMemo(() => buildTableOverrides(resolvedFields, tableConfig?.overrides), [resolvedFields, tableConfig?.overrides]);
|
|
6233
|
+
const formOverrides = React.useMemo(() => buildFormOverrides(resolvedFields, formConfig?.overrides, denyFields), [
|
|
6234
|
+
resolvedFields,
|
|
6102
6235
|
formConfig?.overrides,
|
|
6103
6236
|
denyFields
|
|
6104
6237
|
]);
|
|
6105
|
-
const hiddenColumns = React.useMemo(() => buildHiddenColumns(
|
|
6106
|
-
|
|
6238
|
+
const hiddenColumns = React.useMemo(() => buildHiddenColumns(resolvedFields, tableConfig?.hidden, denyFields), [
|
|
6239
|
+
resolvedFields,
|
|
6107
6240
|
tableConfig?.hidden,
|
|
6108
6241
|
denyFields
|
|
6109
6242
|
]);
|
|
6110
|
-
const
|
|
6111
|
-
|
|
6243
|
+
const searchConfig = React.useMemo(() => {
|
|
6244
|
+
const tableSearch = tableConfig?.search;
|
|
6245
|
+
if (tableSearch === false) return false;
|
|
6246
|
+
if (tableSearch && typeof tableSearch === "object") return tableSearch;
|
|
6247
|
+
return Object.values(resolvedFields).some((field) => field?.search === true) ? true : false;
|
|
6248
|
+
}, [resolvedFields, tableConfig?.search]);
|
|
6249
|
+
const batchFields = React.useMemo(() => buildBatchUpdateFields(resolvedSchema, tableConfig?.batchFields, resolvedFields), [
|
|
6250
|
+
resolvedSchema,
|
|
6112
6251
|
tableConfig?.batchFields,
|
|
6113
|
-
|
|
6252
|
+
resolvedFields
|
|
6253
|
+
]);
|
|
6254
|
+
const formInitialValues = React.useMemo(() => {
|
|
6255
|
+
return resource.modal.createOpen ? resource.modal.copySource ?? void 0 : resource.modal.selected ?? void 0;
|
|
6256
|
+
}, [
|
|
6257
|
+
resource.modal.createOpen,
|
|
6258
|
+
resource.modal.copySource,
|
|
6259
|
+
resource.modal.selected
|
|
6114
6260
|
]);
|
|
6115
6261
|
return /* @__PURE__ */ jsxs("div", {
|
|
6116
6262
|
className: "space-y-4",
|
|
@@ -6191,11 +6337,12 @@ function AutoCrudTable({ title, description, schema, resource, fields, table: ta
|
|
|
6191
6337
|
}),
|
|
6192
6338
|
/* @__PURE__ */ jsx(AutoTable, {
|
|
6193
6339
|
data: resource.tableData.data,
|
|
6194
|
-
schema,
|
|
6340
|
+
schema: resolvedSchema,
|
|
6195
6341
|
pageCount: resource.tableData.pageCount,
|
|
6196
6342
|
overrides: tableOverrides,
|
|
6197
6343
|
exclude: hiddenColumns,
|
|
6198
6344
|
filterMode: tableConfig?.filterModes,
|
|
6345
|
+
search: searchConfig,
|
|
6199
6346
|
actions: resolveActions(actionItems, {
|
|
6200
6347
|
openView: resource.handlers.openView,
|
|
6201
6348
|
openEdit: can.update ? resource.handlers.openEdit : void 0,
|
|
@@ -6216,8 +6363,8 @@ function AutoCrudTable({ title, description, schema, resource, fields, table: ta
|
|
|
6216
6363
|
open: resource.modal.createOpen || resource.modal.editOpen,
|
|
6217
6364
|
onOpenChange: (open) => !open && resource.handlers.closeModals(),
|
|
6218
6365
|
mode: resource.modal.createOpen ? "create" : "edit",
|
|
6219
|
-
schema,
|
|
6220
|
-
initialValues:
|
|
6366
|
+
schema: resolvedSchema,
|
|
6367
|
+
initialValues: formInitialValues,
|
|
6221
6368
|
onSubmit: (values) => {
|
|
6222
6369
|
if (resource.modal.createOpen) resource.handlers.submitCreate(values);
|
|
6223
6370
|
else resource.handlers.submitUpdate(values);
|
|
@@ -6234,8 +6381,8 @@ function AutoCrudTable({ title, description, schema, resource, fields, table: ta
|
|
|
6234
6381
|
onOpenChange: (open) => !open && resource.handlers.closeModals(),
|
|
6235
6382
|
variant: resource.modal.variant,
|
|
6236
6383
|
data: resource.modal.selected,
|
|
6237
|
-
schema,
|
|
6238
|
-
fields,
|
|
6384
|
+
schema: resolvedSchema,
|
|
6385
|
+
fields: resolvedFields,
|
|
6239
6386
|
denyFields,
|
|
6240
6387
|
locale
|
|
6241
6388
|
}),
|
|
@@ -6954,6 +7101,53 @@ const noopToastAdapter = {
|
|
|
6954
7101
|
info: () => {},
|
|
6955
7102
|
warning: () => {}
|
|
6956
7103
|
};
|
|
7104
|
+
function isRecord(value) {
|
|
7105
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
7106
|
+
}
|
|
7107
|
+
function isJsonSchemaObject(value) {
|
|
7108
|
+
return isRecord(value) && isRecord(value.properties);
|
|
7109
|
+
}
|
|
7110
|
+
function readJsonSchemaType(property) {
|
|
7111
|
+
return Array.isArray(property.type) ? property.type.find((item) => item !== "null") : property.type;
|
|
7112
|
+
}
|
|
7113
|
+
function jsonPropertyToZod(property, required) {
|
|
7114
|
+
const enumValues = property.enum?.filter((value) => typeof value === "string");
|
|
7115
|
+
let schema;
|
|
7116
|
+
if (enumValues && enumValues.length > 0) schema = z.enum(enumValues);
|
|
7117
|
+
else switch (readJsonSchemaType(property)) {
|
|
7118
|
+
case "number":
|
|
7119
|
+
case "integer":
|
|
7120
|
+
schema = z.number();
|
|
7121
|
+
break;
|
|
7122
|
+
case "boolean":
|
|
7123
|
+
schema = z.boolean();
|
|
7124
|
+
break;
|
|
7125
|
+
case "array":
|
|
7126
|
+
schema = z.array(z.unknown());
|
|
7127
|
+
break;
|
|
7128
|
+
case "object":
|
|
7129
|
+
schema = z.record(z.string(), z.unknown());
|
|
7130
|
+
break;
|
|
7131
|
+
case "string":
|
|
7132
|
+
schema = property.format === "date" || property.format === "date-time" ? z.coerce.date() : z.string();
|
|
7133
|
+
break;
|
|
7134
|
+
default: schema = z.unknown();
|
|
7135
|
+
}
|
|
7136
|
+
return required ? schema : schema.optional();
|
|
7137
|
+
}
|
|
7138
|
+
function mergeMetadataSchema(baseSchema, metadataSchema) {
|
|
7139
|
+
if (!isJsonSchemaObject(metadataSchema)) return baseSchema;
|
|
7140
|
+
const required = new Set(Array.isArray(metadataSchema.required) ? metadataSchema.required.filter((field) => typeof field === "string") : []);
|
|
7141
|
+
const extensionShape = {};
|
|
7142
|
+
for (const [field, property] of Object.entries(metadataSchema.properties ?? {})) {
|
|
7143
|
+
if (field in baseSchema.shape) continue;
|
|
7144
|
+
extensionShape[field] = jsonPropertyToZod(property, required.has(field));
|
|
7145
|
+
}
|
|
7146
|
+
return Object.keys(extensionShape).length > 0 ? baseSchema.extend(extensionShape) : baseSchema;
|
|
7147
|
+
}
|
|
7148
|
+
function readMetadataFields(fields) {
|
|
7149
|
+
return isRecord(fields) ? fields : void 0;
|
|
7150
|
+
}
|
|
6957
7151
|
/**
|
|
6958
7152
|
* Modal 状态 Reducer
|
|
6959
7153
|
*/
|
|
@@ -7012,12 +7206,16 @@ function useAutoCrudResource({ router, schema, query: queryTransform, options =
|
|
|
7012
7206
|
useEffect(() => {
|
|
7013
7207
|
hooksRef.current = hooks;
|
|
7014
7208
|
}, [hooks]);
|
|
7015
|
-
const
|
|
7209
|
+
const metadata = (router.meta?.useQuery(void 0, { staleTime: 3e4 }))?.data;
|
|
7210
|
+
const resolvedSchema = useMemo(() => mergeMetadataSchema(schema, metadata?.schema), [schema, metadata?.schema]);
|
|
7211
|
+
const metadataFields = useMemo(() => readMetadataFields(metadata?.fields), [metadata?.fields]);
|
|
7212
|
+
const columns = useMemo(() => createTableSchema(resolvedSchema), [resolvedSchema]);
|
|
7016
7213
|
const [urlParams] = useQueryStates({
|
|
7017
7214
|
page: parseAsInteger.withDefault(1),
|
|
7018
7215
|
perPage: parseAsInteger.withDefault(10),
|
|
7019
7216
|
sort: getSortingStateParser().withDefault([]),
|
|
7020
|
-
joinOperator: parseAsStringEnum(["and", "or"]).withDefault("and")
|
|
7217
|
+
joinOperator: parseAsStringEnum(["and", "or"]).withDefault("and"),
|
|
7218
|
+
search: parseAsString.withDefault("")
|
|
7021
7219
|
}, { shallow: false });
|
|
7022
7220
|
const [filters] = useReadableFilters(columns, { shallow: false });
|
|
7023
7221
|
const queryInput = useMemo(() => {
|
|
@@ -7026,7 +7224,8 @@ function useAutoCrudResource({ router, schema, query: queryTransform, options =
|
|
|
7026
7224
|
perPage: urlParams.perPage,
|
|
7027
7225
|
sort: urlParams.sort,
|
|
7028
7226
|
filters,
|
|
7029
|
-
joinOperator: urlParams.joinOperator
|
|
7227
|
+
joinOperator: urlParams.joinOperator,
|
|
7228
|
+
...urlParams.search.trim() ? { search: urlParams.search.trim() } : {}
|
|
7030
7229
|
};
|
|
7031
7230
|
return queryTransform ? queryTransform(autoParams) : autoParams;
|
|
7032
7231
|
}, [
|
|
@@ -7038,6 +7237,7 @@ function useAutoCrudResource({ router, schema, query: queryTransform, options =
|
|
|
7038
7237
|
placeholderData: keepPreviousData,
|
|
7039
7238
|
staleTime: 0
|
|
7040
7239
|
});
|
|
7240
|
+
const tableRows = listQuery.data?.data ?? [];
|
|
7041
7241
|
const createMutation = router.create.useMutation();
|
|
7042
7242
|
const updateMutation = router.update.useMutation();
|
|
7043
7243
|
const deleteMutation = router.delete.useMutation();
|
|
@@ -7290,11 +7490,13 @@ function useAutoCrudResource({ router, schema, query: queryTransform, options =
|
|
|
7290
7490
|
const canExport = !!(exportFetcher || exportQuery);
|
|
7291
7491
|
return {
|
|
7292
7492
|
tableData: {
|
|
7293
|
-
data:
|
|
7493
|
+
data: tableRows,
|
|
7294
7494
|
pageCount: listQuery.data?.pageCount ?? 0,
|
|
7295
7495
|
isLoading: listQuery.isLoading,
|
|
7296
7496
|
isFetching: listQuery.isFetching
|
|
7297
7497
|
},
|
|
7498
|
+
schema: resolvedSchema,
|
|
7499
|
+
fields: metadataFields,
|
|
7298
7500
|
modal,
|
|
7299
7501
|
mutations: {
|
|
7300
7502
|
isCreating: createMutation.isPending,
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wordrhyme/auto-crud",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "1.1.
|
|
4
|
+
"version": "1.1.4",
|
|
5
5
|
"description": "Schema-first CRUD components with auto-generated tables and forms",
|
|
6
6
|
"author": "wordrhyme",
|
|
7
7
|
"license": "MIT",
|
|
@@ -83,10 +83,10 @@
|
|
|
83
83
|
"tsdown": "^0.15.12",
|
|
84
84
|
"typescript": "^5.9.3",
|
|
85
85
|
"@internal/prettier-config": "0.0.1",
|
|
86
|
-
"@internal/tsconfig": "0.1.0",
|
|
87
86
|
"@internal/eslint-config": "0.3.0",
|
|
88
|
-
"@internal/
|
|
89
|
-
"@internal/tsdown-config": "0.1.0"
|
|
87
|
+
"@internal/tsconfig": "0.1.0",
|
|
88
|
+
"@internal/tsdown-config": "0.1.0",
|
|
89
|
+
"@internal/vitest-config": "0.1.0"
|
|
90
90
|
},
|
|
91
91
|
"prettier": "@internal/prettier-config",
|
|
92
92
|
"scripts": {
|