@wordrhyme/auto-crud 1.3.0 → 1.3.2

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,23 +1,17 @@
1
1
  import * as React from "react";
2
2
  import { forwardRef, useCallback, useEffect, useImperativeHandle, useMemo, useReducer, useRef, useState } from "react";
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";
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, RefreshCw, 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, cn as cn$1 } from "@pixpilot/shadcn";
5
+ 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, DropdownMenuSeparator, DropdownMenuTrigger, Input, Label, Popover, PopoverContent, PopoverTrigger, Select as Select$1, SelectContent, SelectItem, SelectTrigger, SelectValue, Separator, Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle, Slider, Switch, Table as Table$1, TableBody, TableCell, TableHead, TableHeader, TableRow, cn as cn$1 } from "@wordrhyme/shadcn";
6
6
  import { clsx } from "clsx";
7
7
  import { twMerge } from "tailwind-merge";
8
8
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
9
9
  import { customAlphabet } from "nanoid";
10
- import { DndContext, DragOverlay, KeyboardSensor, MouseSensor, TouchSensor, closestCenter, closestCorners, defaultDropAnimationSideEffects, useSensor, useSensors } from "@dnd-kit/core";
11
- import { restrictToHorizontalAxis, restrictToParentElement, restrictToVerticalAxis } from "@dnd-kit/modifiers";
12
- import { SortableContext, arrayMove, horizontalListSortingStrategy, sortableKeyboardCoordinates, useSortable, verticalListSortingStrategy } from "@dnd-kit/sortable";
13
- import { CSS } from "@dnd-kit/utilities";
14
- import { Slot } from "@radix-ui/react-slot";
15
- import * as ReactDOM from "react-dom";
16
- import { useDirection } from "@radix-ui/react-direction";
10
+ 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";
17
11
  import { z } from "zod";
18
- import { createForm, onFieldValueChange } from "@formily/core";
19
- import { Form, JsonSchemaField } from "@pixpilot/formily-shadcn";
20
- import { connect, mapProps } from "@formily/react";
12
+ import { createForm } from "@formily/core";
13
+ import { action, observable } from "@formily/reactive";
14
+ import { Form, JsonSchemaField, connect, mapProps } from "@wordrhyme/formily-shadcn";
21
15
  import { toast } from "sonner";
22
16
  import { keepPreviousData } from "@tanstack/react-query";
23
17
 
@@ -55,7 +49,7 @@ function DataTablePagination({ table, pageSizeOptions = [
55
49
  children: [/* @__PURE__ */ jsx("p", {
56
50
  className: "whitespace-nowrap font-medium text-sm",
57
51
  children: "Rows per page"
58
- }), /* @__PURE__ */ jsxs(Select, {
52
+ }), /* @__PURE__ */ jsxs(Select$1, {
59
53
  value: `${table.getState().pagination.pageSize}`,
60
54
  onValueChange: (value) => {
61
55
  table.setPageSize(Number(value));
@@ -128,55 +122,6 @@ function DataTablePagination({ table, pageSizeOptions = [
128
122
  });
129
123
  }
130
124
 
131
- //#endregion
132
- //#region src/components/ui/table.tsx
133
- function Table$1({ className,...props }) {
134
- return /* @__PURE__ */ jsx("div", {
135
- "data-slot": "table-container",
136
- className: "relative w-full overflow-x-auto",
137
- children: /* @__PURE__ */ jsx("table", {
138
- "data-slot": "table",
139
- className: cn("w-full caption-bottom text-sm", className),
140
- ...props
141
- })
142
- });
143
- }
144
- function TableHeader({ className,...props }) {
145
- return /* @__PURE__ */ jsx("thead", {
146
- "data-slot": "table-header",
147
- className: cn("[&_tr]:border-b", className),
148
- ...props
149
- });
150
- }
151
- function TableBody({ className,...props }) {
152
- return /* @__PURE__ */ jsx("tbody", {
153
- "data-slot": "table-body",
154
- className: cn("[&_tr:last-child]:border-0", className),
155
- ...props
156
- });
157
- }
158
- function TableRow({ className,...props }) {
159
- return /* @__PURE__ */ jsx("tr", {
160
- "data-slot": "table-row",
161
- className: cn("border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted", className),
162
- ...props
163
- });
164
- }
165
- function TableHead({ className,...props }) {
166
- return /* @__PURE__ */ jsx("th", {
167
- "data-slot": "table-head",
168
- className: cn("h-10 whitespace-nowrap px-2 text-left align-middle font-medium text-foreground [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]", className),
169
- ...props
170
- });
171
- }
172
- function TableCell({ className,...props }) {
173
- return /* @__PURE__ */ jsx("td", {
174
- "data-slot": "table-cell",
175
- className: cn("whitespace-nowrap p-2 align-middle [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]", className),
176
- ...props
177
- });
178
- }
179
-
180
125
  //#endregion
181
126
  //#region src/config/data-table.ts
182
127
  const dataTableConfig = {
@@ -911,454 +856,6 @@ function DataTableRangeFilter({ filter, column, inputId, onFilterUpdate, classNa
911
856
  });
912
857
  }
913
858
 
914
- //#endregion
915
- //#region src/components/ui/faceted.tsx
916
- const FacetedContext = React.createContext(null);
917
- function useFacetedContext(name) {
918
- const context = React.useContext(FacetedContext);
919
- if (!context) throw new Error(`\`${name}\` must be within Faceted`);
920
- return context;
921
- }
922
- function Faceted(props) {
923
- const { open: openProp, onOpenChange: onOpenChangeProp, value, onValueChange, children, multiple = false,...facetedProps } = props;
924
- const [uncontrolledOpen, setUncontrolledOpen] = React.useState(false);
925
- const isControlled = openProp !== void 0;
926
- const open = isControlled ? openProp : uncontrolledOpen;
927
- const onOpenChange = React.useCallback((newOpen) => {
928
- if (!isControlled) setUncontrolledOpen(newOpen);
929
- onOpenChangeProp?.(newOpen);
930
- }, [isControlled, onOpenChangeProp]);
931
- const onItemSelect = React.useCallback((selectedValue) => {
932
- if (!onValueChange) return;
933
- if (multiple) {
934
- const currentValue = Array.isArray(value) ? value : [];
935
- onValueChange(currentValue.includes(selectedValue) ? currentValue.filter((v) => v !== selectedValue) : [...currentValue, selectedValue]);
936
- } else {
937
- if (value === selectedValue) onValueChange(void 0);
938
- else onValueChange(selectedValue);
939
- requestAnimationFrame(() => onOpenChange(false));
940
- }
941
- }, [
942
- multiple,
943
- value,
944
- onValueChange,
945
- onOpenChange
946
- ]);
947
- const contextValue = React.useMemo(() => ({
948
- value,
949
- onItemSelect,
950
- multiple
951
- }), [
952
- value,
953
- onItemSelect,
954
- multiple
955
- ]);
956
- return /* @__PURE__ */ jsx(FacetedContext.Provider, {
957
- value: contextValue,
958
- children: /* @__PURE__ */ jsx(Popover, {
959
- open,
960
- onOpenChange,
961
- ...facetedProps,
962
- children
963
- })
964
- });
965
- }
966
- function FacetedTrigger(props) {
967
- const { className, children,...triggerProps } = props;
968
- return /* @__PURE__ */ jsx(PopoverTrigger, {
969
- ...triggerProps,
970
- className: cn("justify-between text-left", className),
971
- children
972
- });
973
- }
974
- function FacetedBadgeList(props) {
975
- const { options = [], max = 2, placeholder = "Select options...", className, badgeClassName,...badgeListProps } = props;
976
- const context = useFacetedContext("FacetedBadgeList");
977
- const values = Array.isArray(context.value) ? context.value : [context.value].filter(Boolean);
978
- const getLabel = React.useCallback((value) => {
979
- return options.find((opt) => opt.value === value)?.label ?? value;
980
- }, [options]);
981
- if (!values || values.length === 0) return /* @__PURE__ */ jsxs("div", {
982
- ...badgeListProps,
983
- className: "flex w-full items-center gap-1 text-muted-foreground",
984
- children: [placeholder, /* @__PURE__ */ jsx(ChevronsUpDown, { className: "ml-auto size-4 shrink-0 opacity-50" })]
985
- });
986
- return /* @__PURE__ */ jsx("div", {
987
- ...badgeListProps,
988
- className: cn("flex flex-wrap items-center gap-1", className),
989
- children: values.length > max ? /* @__PURE__ */ jsxs(Badge, {
990
- variant: "secondary",
991
- className: cn("rounded-sm px-1 font-normal", badgeClassName),
992
- children: [values.length, " selected"]
993
- }) : values.map((value) => /* @__PURE__ */ jsx(Badge, {
994
- variant: "secondary",
995
- className: cn("rounded-sm px-1 font-normal", badgeClassName),
996
- children: /* @__PURE__ */ jsx("span", {
997
- className: "truncate",
998
- children: getLabel(value)
999
- })
1000
- }, value))
1001
- });
1002
- }
1003
- function FacetedContent(props) {
1004
- const { className, children,...contentProps } = props;
1005
- return /* @__PURE__ */ jsx(PopoverContent, {
1006
- ...contentProps,
1007
- align: "start",
1008
- className: cn("w-[200px] origin-(--radix-popover-content-transform-origin) p-0", className),
1009
- children: /* @__PURE__ */ jsx(Command, { children })
1010
- });
1011
- }
1012
- const FacetedInput = CommandInput;
1013
- const FacetedList = CommandList;
1014
- const FacetedEmpty = CommandEmpty;
1015
- const FacetedGroup = CommandGroup;
1016
- function FacetedItem(props) {
1017
- const { value, onSelect, className, children,...itemProps } = props;
1018
- const context = useFacetedContext("FacetedItem");
1019
- const isSelected = context.multiple ? Array.isArray(context.value) && context.value.includes(value) : context.value === value;
1020
- const onItemSelect = React.useCallback((currentValue) => {
1021
- if (onSelect) onSelect(currentValue);
1022
- else if (context.onItemSelect) context.onItemSelect(currentValue);
1023
- }, [onSelect, context]);
1024
- return /* @__PURE__ */ jsxs(CommandItem, {
1025
- value,
1026
- "aria-selected": isSelected,
1027
- "data-selected": isSelected,
1028
- className: cn("gap-2", className),
1029
- onSelect: () => onItemSelect(value),
1030
- ...itemProps,
1031
- children: [/* @__PURE__ */ jsx("span", {
1032
- className: cn("flex size-4 items-center justify-center rounded-sm border border-primary", isSelected ? "bg-primary text-primary-foreground" : "opacity-50 [&_svg]:invisible"),
1033
- children: /* @__PURE__ */ jsx(Check, { className: "size-4" })
1034
- }), children]
1035
- });
1036
- }
1037
-
1038
- //#endregion
1039
- //#region src/lib/compose-refs.ts
1040
- /**
1041
- * Set a given ref to a given value
1042
- * This utility takes care of different types of refs: callback refs and RefObject(s)
1043
- */
1044
- function setRef(ref, value) {
1045
- if (typeof ref === "function") return ref(value);
1046
- if (ref !== null && ref !== void 0) ref.current = value;
1047
- }
1048
- /**
1049
- * A utility to compose multiple refs together
1050
- * Accepts callback refs and RefObject(s)
1051
- */
1052
- function composeRefs(...refs) {
1053
- return (node) => {
1054
- let hasCleanup = false;
1055
- const cleanups = refs.map((ref) => {
1056
- const cleanup = setRef(ref, node);
1057
- if (!hasCleanup && typeof cleanup === "function") hasCleanup = true;
1058
- return cleanup;
1059
- });
1060
- if (hasCleanup) return () => {
1061
- for (let i = 0; i < cleanups.length; i++) {
1062
- const cleanup = cleanups[i];
1063
- if (typeof cleanup === "function") cleanup();
1064
- else setRef(refs[i], null);
1065
- }
1066
- };
1067
- };
1068
- }
1069
- /**
1070
- * A custom hook that composes multiple refs
1071
- * Accepts callback refs and RefObject(s)
1072
- */
1073
- function useComposedRefs(...refs) {
1074
- return React.useCallback(composeRefs(...refs), refs);
1075
- }
1076
-
1077
- //#endregion
1078
- //#region src/components/ui/sortable.tsx
1079
- const orientationConfig = {
1080
- vertical: {
1081
- modifiers: [restrictToVerticalAxis, restrictToParentElement],
1082
- strategy: verticalListSortingStrategy,
1083
- collisionDetection: closestCenter
1084
- },
1085
- horizontal: {
1086
- modifiers: [restrictToHorizontalAxis, restrictToParentElement],
1087
- strategy: horizontalListSortingStrategy,
1088
- collisionDetection: closestCenter
1089
- },
1090
- mixed: {
1091
- modifiers: [restrictToParentElement],
1092
- strategy: void 0,
1093
- collisionDetection: closestCorners
1094
- }
1095
- };
1096
- const ROOT_NAME$1 = "Sortable";
1097
- const CONTENT_NAME = "SortableContent";
1098
- const ITEM_NAME$1 = "SortableItem";
1099
- const ITEM_HANDLE_NAME = "SortableItemHandle";
1100
- const OVERLAY_NAME = "SortableOverlay";
1101
- const SortableRootContext = React.createContext(null);
1102
- function useSortableContext(consumerName) {
1103
- const context = React.useContext(SortableRootContext);
1104
- if (!context) throw new Error(`\`${consumerName}\` must be used within \`${ROOT_NAME$1}\``);
1105
- return context;
1106
- }
1107
- function SortableRoot(props) {
1108
- const { value, onValueChange, collisionDetection, modifiers, strategy, onMove, orientation = "vertical", flatCursor = false, getItemValue: getItemValueProp, accessibility, onDragStart: onDragStartProp, onDragEnd: onDragEndProp, onDragCancel: onDragCancelProp,...sortableProps } = props;
1109
- const id = React.useId();
1110
- const [activeId, setActiveId] = React.useState(null);
1111
- const sensors = useSensors(useSensor(MouseSensor), useSensor(TouchSensor), useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates }));
1112
- const config = React.useMemo(() => orientationConfig[orientation], [orientation]);
1113
- const getItemValue = React.useCallback((item) => {
1114
- if (typeof item === "object" && !getItemValueProp) throw new Error("getItemValue is required when using array of objects");
1115
- return getItemValueProp ? getItemValueProp(item) : item;
1116
- }, [getItemValueProp]);
1117
- const items = React.useMemo(() => {
1118
- return value.map((item) => getItemValue(item));
1119
- }, [value, getItemValue]);
1120
- const onDragStart = React.useCallback((event) => {
1121
- onDragStartProp?.(event);
1122
- if (event.activatorEvent.defaultPrevented) return;
1123
- setActiveId(event.active.id);
1124
- }, [onDragStartProp]);
1125
- const onDragEnd = React.useCallback((event) => {
1126
- onDragEndProp?.(event);
1127
- if (event.activatorEvent.defaultPrevented) return;
1128
- const { active, over } = event;
1129
- if (over && active.id !== over?.id) {
1130
- const activeIndex = value.findIndex((item) => getItemValue(item) === active.id);
1131
- const overIndex = value.findIndex((item) => getItemValue(item) === over.id);
1132
- if (onMove) onMove({
1133
- ...event,
1134
- activeIndex,
1135
- overIndex
1136
- });
1137
- else onValueChange?.(arrayMove(value, activeIndex, overIndex));
1138
- }
1139
- setActiveId(null);
1140
- }, [
1141
- value,
1142
- onValueChange,
1143
- onMove,
1144
- getItemValue,
1145
- onDragEndProp
1146
- ]);
1147
- const onDragCancel = React.useCallback((event) => {
1148
- onDragCancelProp?.(event);
1149
- if (event.activatorEvent.defaultPrevented) return;
1150
- setActiveId(null);
1151
- }, [onDragCancelProp]);
1152
- const announcements = React.useMemo(() => ({
1153
- onDragStart({ active }) {
1154
- return `Grabbed sortable item "${active.id.toString()}". Current position is ${active.data.current?.sortable.index + 1} of ${value.length}. Use arrow keys to move, space to drop.`;
1155
- },
1156
- onDragOver({ active, over }) {
1157
- if (over) {
1158
- const overIndex = over.data.current?.sortable.index ?? 0;
1159
- const moveDirection = overIndex > (active.data.current?.sortable.index ?? 0) ? "down" : "up";
1160
- return `Sortable item "${active.id.toString()}" moved ${moveDirection} to position ${overIndex + 1} of ${value.length}.`;
1161
- }
1162
- return "Sortable item is no longer over a droppable area. Press escape to cancel.";
1163
- },
1164
- onDragEnd({ active, over }) {
1165
- const activeValue = active.id.toString();
1166
- if (over) return `Sortable item "${activeValue}" dropped at position ${(over.data.current?.sortable.index ?? 0) + 1} of ${value.length}.`;
1167
- return `Sortable item "${activeValue}" dropped. No changes were made.`;
1168
- },
1169
- onDragCancel({ active }) {
1170
- const activeIndex = active.data.current?.sortable.index ?? 0;
1171
- return `Sorting cancelled. Sortable item "${active.id.toString()}" returned to position ${activeIndex + 1} of ${value.length}.`;
1172
- },
1173
- onDragMove({ active, over }) {
1174
- if (over) {
1175
- const overIndex = over.data.current?.sortable.index ?? 0;
1176
- const moveDirection = overIndex > (active.data.current?.sortable.index ?? 0) ? "down" : "up";
1177
- return `Sortable item "${active.id.toString()}" is moving ${moveDirection} to position ${overIndex + 1} of ${value.length}.`;
1178
- }
1179
- return "Sortable item is no longer over a droppable area. Press escape to cancel.";
1180
- }
1181
- }), [value]);
1182
- const screenReaderInstructions = React.useMemo(() => ({ draggable: `
1183
- To pick up a sortable item, press space or enter.
1184
- While dragging, use the ${orientation === "vertical" ? "up and down" : orientation === "horizontal" ? "left and right" : "arrow"} keys to move the item.
1185
- Press space or enter again to drop the item in its new position, or press escape to cancel.
1186
- ` }), [orientation]);
1187
- const contextValue = React.useMemo(() => ({
1188
- id,
1189
- items,
1190
- modifiers: modifiers ?? config.modifiers,
1191
- strategy: strategy ?? config.strategy,
1192
- activeId,
1193
- setActiveId,
1194
- getItemValue,
1195
- flatCursor
1196
- }), [
1197
- id,
1198
- items,
1199
- modifiers,
1200
- strategy,
1201
- config.modifiers,
1202
- config.strategy,
1203
- activeId,
1204
- getItemValue,
1205
- flatCursor
1206
- ]);
1207
- return /* @__PURE__ */ jsx(SortableRootContext.Provider, {
1208
- value: contextValue,
1209
- children: /* @__PURE__ */ jsx(DndContext, {
1210
- collisionDetection: collisionDetection ?? config.collisionDetection,
1211
- modifiers: modifiers ?? config.modifiers,
1212
- sensors,
1213
- ...sortableProps,
1214
- id,
1215
- onDragStart,
1216
- onDragEnd,
1217
- onDragCancel,
1218
- accessibility: {
1219
- announcements,
1220
- screenReaderInstructions,
1221
- ...accessibility
1222
- }
1223
- })
1224
- });
1225
- }
1226
- const SortableContentContext = React.createContext(false);
1227
- function SortableContent(props) {
1228
- const { strategy: strategyProp, asChild, withoutSlot, children, ref,...contentProps } = props;
1229
- const context = useSortableContext(CONTENT_NAME);
1230
- const ContentPrimitive = asChild ? Slot : "div";
1231
- return /* @__PURE__ */ jsx(SortableContentContext.Provider, {
1232
- value: true,
1233
- children: /* @__PURE__ */ jsx(SortableContext, {
1234
- items: context.items,
1235
- strategy: strategyProp ?? context.strategy,
1236
- children: withoutSlot ? children : /* @__PURE__ */ jsx(ContentPrimitive, {
1237
- "data-slot": "sortable-content",
1238
- ...contentProps,
1239
- ref,
1240
- children
1241
- })
1242
- })
1243
- });
1244
- }
1245
- const SortableItemContext = React.createContext(null);
1246
- function useSortableItemContext(consumerName) {
1247
- const context = React.useContext(SortableItemContext);
1248
- if (!context) throw new Error(`\`${consumerName}\` must be used within \`${ITEM_NAME$1}\``);
1249
- return context;
1250
- }
1251
- function SortableItem(props) {
1252
- const { value, style, asHandle, asChild, disabled, className, ref,...itemProps } = props;
1253
- const inSortableContent = React.useContext(SortableContentContext);
1254
- const inSortableOverlay = React.useContext(SortableOverlayContext);
1255
- if (!inSortableContent && !inSortableOverlay) throw new Error(`\`${ITEM_NAME$1}\` must be used within \`${CONTENT_NAME}\` or \`${OVERLAY_NAME}\``);
1256
- if (value === "") throw new Error(`\`${ITEM_NAME$1}\` value cannot be an empty string`);
1257
- const context = useSortableContext(ITEM_NAME$1);
1258
- const id = React.useId();
1259
- const { attributes, listeners: listeners$1, setNodeRef, setActivatorNodeRef, transform, transition, isDragging } = useSortable({
1260
- id: value,
1261
- disabled
1262
- });
1263
- const composedRef = useComposedRefs(ref, (node) => {
1264
- if (disabled) return;
1265
- setNodeRef(node);
1266
- if (asHandle) setActivatorNodeRef(node);
1267
- });
1268
- const composedStyle = React.useMemo(() => {
1269
- return {
1270
- transform: CSS.Translate.toString(transform),
1271
- transition,
1272
- ...style
1273
- };
1274
- }, [
1275
- transform,
1276
- transition,
1277
- style
1278
- ]);
1279
- const itemContext = React.useMemo(() => ({
1280
- id,
1281
- attributes,
1282
- listeners: listeners$1,
1283
- setActivatorNodeRef,
1284
- isDragging,
1285
- disabled
1286
- }), [
1287
- id,
1288
- attributes,
1289
- listeners$1,
1290
- setActivatorNodeRef,
1291
- isDragging,
1292
- disabled
1293
- ]);
1294
- const ItemPrimitive = asChild ? Slot : "div";
1295
- return /* @__PURE__ */ jsx(SortableItemContext.Provider, {
1296
- value: itemContext,
1297
- children: /* @__PURE__ */ jsx(ItemPrimitive, {
1298
- id,
1299
- "data-disabled": disabled,
1300
- "data-dragging": isDragging ? "" : void 0,
1301
- "data-slot": "sortable-item",
1302
- ...itemProps,
1303
- ...asHandle && !disabled ? attributes : {},
1304
- ...asHandle && !disabled ? listeners$1 : {},
1305
- ref: composedRef,
1306
- style: composedStyle,
1307
- className: cn("focus-visible:outline-hidden focus-visible:ring-1 focus-visible:ring-ring focus-visible:ring-offset-1", {
1308
- "touch-none select-none": asHandle,
1309
- "cursor-default": context.flatCursor,
1310
- "data-dragging:cursor-grabbing": !context.flatCursor,
1311
- "cursor-grab": !isDragging && asHandle && !context.flatCursor,
1312
- "opacity-50": isDragging,
1313
- "pointer-events-none opacity-50": disabled
1314
- }, className)
1315
- })
1316
- });
1317
- }
1318
- function SortableItemHandle(props) {
1319
- const { asChild, disabled, className, ref,...itemHandleProps } = props;
1320
- const context = useSortableContext(ITEM_HANDLE_NAME);
1321
- const itemContext = useSortableItemContext(ITEM_HANDLE_NAME);
1322
- const isDisabled = disabled ?? itemContext.disabled;
1323
- const composedRef = useComposedRefs(ref, (node) => {
1324
- if (!isDisabled) return;
1325
- itemContext.setActivatorNodeRef(node);
1326
- });
1327
- return /* @__PURE__ */ jsx(asChild ? Slot : "button", {
1328
- type: "button",
1329
- "aria-controls": itemContext.id,
1330
- "data-disabled": isDisabled,
1331
- "data-dragging": itemContext.isDragging ? "" : void 0,
1332
- "data-slot": "sortable-item-handle",
1333
- ...itemHandleProps,
1334
- ...isDisabled ? {} : itemContext.attributes,
1335
- ...isDisabled ? {} : itemContext.listeners,
1336
- ref: composedRef,
1337
- className: cn("select-none disabled:pointer-events-none disabled:opacity-50", context.flatCursor ? "cursor-default" : "cursor-grab data-dragging:cursor-grabbing", className),
1338
- disabled: isDisabled
1339
- });
1340
- }
1341
- const SortableOverlayContext = React.createContext(false);
1342
- const dropAnimation = { sideEffects: defaultDropAnimationSideEffects({ styles: { active: { opacity: "0.4" } } }) };
1343
- function SortableOverlay(props) {
1344
- const { container: containerProp, children,...overlayProps } = props;
1345
- const context = useSortableContext(OVERLAY_NAME);
1346
- const [mounted, setMounted] = React.useState(false);
1347
- React.useLayoutEffect(() => setMounted(true), []);
1348
- const container = containerProp ?? (mounted ? globalThis.document?.body : null);
1349
- if (!container) return null;
1350
- return ReactDOM.createPortal(/* @__PURE__ */ jsx(DragOverlay, {
1351
- dropAnimation,
1352
- modifiers: context.modifiers,
1353
- className: cn(!context.flatCursor && "cursor-grabbing"),
1354
- ...overlayProps,
1355
- children: /* @__PURE__ */ jsx(SortableOverlayContext.Provider, {
1356
- value: true,
1357
- children: context.activeId ? typeof children === "function" ? children({ value: context.activeId }) : children : null
1358
- })
1359
- }), container);
1360
- }
1361
-
1362
859
  //#endregion
1363
860
  //#region src/hooks/use-callback-ref.ts
1364
861
  /**
@@ -1475,7 +972,7 @@ function DataTableFilterList({ table, debounceMs = DEBOUNCE_MS$2, throttleMs = T
1475
972
  window.addEventListener("keydown", onKeyDown);
1476
973
  return () => window.removeEventListener("keydown", onKeyDown);
1477
974
  }, []);
1478
- return /* @__PURE__ */ jsxs(SortableRoot, {
975
+ return /* @__PURE__ */ jsxs(Sortable, {
1479
976
  value: filters,
1480
977
  onValueChange: setFilters,
1481
978
  getItemValue: (item) => item.filterId,
@@ -1612,7 +1109,7 @@ function DataTableFilterItem$1({ filter, index: index$1, filterItemId, joinOpera
1612
1109
  children: index$1 === 0 ? /* @__PURE__ */ jsx("span", {
1613
1110
  className: "text-muted-foreground text-sm",
1614
1111
  children: "Where"
1615
- }) : index$1 === 1 ? /* @__PURE__ */ jsxs(Select, {
1112
+ }) : index$1 === 1 ? /* @__PURE__ */ jsxs(Select$1, {
1616
1113
  value: joinOperator,
1617
1114
  onValueChange: (value) => setJoinOperator(value),
1618
1115
  children: [/* @__PURE__ */ jsx(SelectTrigger, {
@@ -1672,7 +1169,7 @@ function DataTableFilterItem$1({ filter, index: index$1, filterItemId, joinOpera
1672
1169
  }, column$1.id)) })] })] })
1673
1170
  })]
1674
1171
  }),
1675
- /* @__PURE__ */ jsxs(Select, {
1172
+ /* @__PURE__ */ jsxs(Select$1, {
1676
1173
  open: showOperatorSelector,
1677
1174
  onOpenChange: setShowOperatorSelector,
1678
1175
  value: filter.operator,
@@ -1764,7 +1261,7 @@ function onFilterInputRender$1({ filter, inputId, column, columnMeta, onFilterUp
1764
1261
  case "boolean": {
1765
1262
  if (Array.isArray(filter.value)) return null;
1766
1263
  const inputListboxId = `${inputId}-listbox`;
1767
- return /* @__PURE__ */ jsxs(Select, {
1264
+ return /* @__PURE__ */ jsxs(Select$1, {
1768
1265
  open: showValueSelector,
1769
1266
  onOpenChange: setShowValueSelector,
1770
1267
  value: filter.value,
@@ -1791,6 +1288,7 @@ function onFilterInputRender$1({ filter, inputId, column, columnMeta, onFilterUp
1791
1288
  case "select":
1792
1289
  case "multiSelect": {
1793
1290
  const inputListboxId = `${inputId}-listbox`;
1291
+ const optionList = columnMeta?.autoCrudFilterOptions ?? columnMeta?.options;
1794
1292
  const multiple = filter.variant === "multiSelect";
1795
1293
  return /* @__PURE__ */ jsxs(Faceted, {
1796
1294
  open: showValueSelector,
@@ -1817,20 +1315,41 @@ function onFilterInputRender$1({ filter, inputId, column, columnMeta, onFilterUp
1817
1315
  }), /* @__PURE__ */ jsxs(FacetedContent, {
1818
1316
  id: inputListboxId,
1819
1317
  className: "w-[200px]",
1318
+ commandProps: { shouldFilter: columnMeta?.autoCrudFilterShouldFilter },
1820
1319
  children: [/* @__PURE__ */ jsx(FacetedInput, {
1821
1320
  "aria-label": `Search ${columnMeta?.label} options`,
1822
- placeholder: columnMeta?.placeholder ?? "Search options..."
1823
- }), /* @__PURE__ */ jsxs(FacetedList, { children: [/* @__PURE__ */ jsx(FacetedEmpty, { children: "No options found." }), /* @__PURE__ */ jsx(FacetedGroup, { children: columnMeta?.options?.map((option) => /* @__PURE__ */ jsxs(FacetedItem, {
1824
- value: option.value,
1321
+ placeholder: columnMeta?.placeholder ?? "Search options...",
1322
+ value: columnMeta?.autoCrudFilterSearchValue,
1323
+ onValueChange: columnMeta?.autoCrudFilterOnSearch
1324
+ }), /* @__PURE__ */ jsxs(FacetedList, {
1325
+ onScroll: columnMeta?.autoCrudFilterOnPopupScroll,
1825
1326
  children: [
1826
- option.icon && /* @__PURE__ */ jsx(option.icon, {}),
1827
- /* @__PURE__ */ jsx("span", { children: option.label }),
1828
- option.count && /* @__PURE__ */ jsx("span", {
1829
- className: "ml-auto font-mono text-xs",
1830
- children: option.count
1327
+ /* @__PURE__ */ jsx(FacetedEmpty, { children: "No options found." }),
1328
+ /* @__PURE__ */ jsx(FacetedGroup, { children: optionList?.map((option) => /* @__PURE__ */ jsxs(FacetedItem, {
1329
+ value: option.value,
1330
+ children: [
1331
+ option.icon && /* @__PURE__ */ jsx(option.icon, {}),
1332
+ /* @__PURE__ */ jsx("span", { children: option.label }),
1333
+ option.count && /* @__PURE__ */ jsx("span", {
1334
+ className: "ml-auto font-mono text-xs",
1335
+ children: option.count
1336
+ })
1337
+ ]
1338
+ }, option.value)) }),
1339
+ columnMeta?.autoCrudFilterLoading && /* @__PURE__ */ jsx(FacetedGroup, { children: /* @__PURE__ */ jsx("div", {
1340
+ role: "status",
1341
+ "aria-live": "polite",
1342
+ className: "py-2 text-center text-sm text-muted-foreground",
1343
+ children: "Loading..."
1344
+ }) }),
1345
+ !columnMeta?.autoCrudFilterLoading && columnMeta?.autoCrudFilterHasMore && /* @__PURE__ */ jsx("div", {
1346
+ role: "status",
1347
+ "aria-live": "polite",
1348
+ className: "sr-only",
1349
+ children: "Load more"
1831
1350
  })
1832
1351
  ]
1833
- }, option.value)) })] })]
1352
+ })]
1834
1353
  })]
1835
1354
  });
1836
1355
  }
@@ -1909,6 +1428,9 @@ function getOptionCommandValue(option) {
1909
1428
  option.searchText
1910
1429
  ].filter(Boolean).join(" ");
1911
1430
  }
1431
+ function getColumnFilterOptions(column) {
1432
+ return column.columnDef.meta?.autoCrudFilterOptions ?? column.columnDef.meta?.options;
1433
+ }
1912
1434
  function DataTableFilterMenu({ table, debounceMs = DEBOUNCE_MS$1, throttleMs = THROTTLE_MS$1, shallow = true, disabled,...props }) {
1913
1435
  const id = React.useId();
1914
1436
  const columnDefs = table.options.columns;
@@ -1994,6 +1516,12 @@ function DataTableFilterMenu({ table, debounceMs = DEBOUNCE_MS$1, throttleMs = T
1994
1516
  onFilterRemove(filters[filters.length - 1]?.filterId ?? "");
1995
1517
  }
1996
1518
  }, [filters, onFilterRemove]);
1519
+ const selectedColumnMeta = selectedColumn?.columnDef.meta;
1520
+ const commandInputValue = selectedColumnMeta?.autoCrudFilterOnSearch !== void 0 ? selectedColumnMeta.autoCrudFilterSearchValue ?? "" : inputValue;
1521
+ const onCommandInputValueChange = React.useCallback((value) => {
1522
+ setInputValue(value);
1523
+ selectedColumn?.columnDef.meta?.autoCrudFilterOnSearch?.(value);
1524
+ }, [selectedColumn]);
1997
1525
  return /* @__PURE__ */ jsxs("div", {
1998
1526
  role: "list",
1999
1527
  className: "flex flex-wrap items-center gap-2",
@@ -2033,31 +1561,50 @@ function DataTableFilterMenu({ table, debounceMs = DEBOUNCE_MS$1, throttleMs = T
2033
1561
  ...props,
2034
1562
  children: /* @__PURE__ */ jsxs(Command, {
2035
1563
  loop: true,
1564
+ shouldFilter: selectedColumnMeta?.autoCrudFilterShouldFilter,
2036
1565
  className: "[&_[cmdk-input-wrapper]_svg]:hidden",
2037
1566
  children: [/* @__PURE__ */ jsx(CommandInput, {
2038
1567
  ref: inputRef,
2039
1568
  placeholder: selectedColumn ? selectedColumn.columnDef.meta?.label ?? selectedColumn.id : "Search fields...",
2040
- value: inputValue,
2041
- onValueChange: setInputValue,
1569
+ value: commandInputValue,
1570
+ onValueChange: onCommandInputValueChange,
2042
1571
  onKeyDown: onInputKeyDown
2043
- }), /* @__PURE__ */ jsx(CommandList, { children: selectedColumn ? /* @__PURE__ */ jsxs(Fragment, { children: [selectedColumn.columnDef.meta?.options && /* @__PURE__ */ jsx(CommandEmpty, { children: "No options found." }), /* @__PURE__ */ jsx(FilterValueSelector, {
2044
- column: selectedColumn,
2045
- value: inputValue,
2046
- onSelect: (value) => onFilterAdd(selectedColumn, value)
2047
- })] }) : /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(CommandEmpty, { children: "No fields found." }), /* @__PURE__ */ jsx(CommandGroup, { children: columns.map((column) => /* @__PURE__ */ jsxs(CommandItem, {
2048
- value: column.id,
2049
- onSelect: () => {
2050
- setSelectedColumn(column);
2051
- setInputValue("");
2052
- requestAnimationFrame(() => {
2053
- inputRef.current?.focus();
2054
- });
2055
- },
2056
- children: [column.columnDef.meta?.icon && /* @__PURE__ */ jsx(column.columnDef.meta.icon, {}), /* @__PURE__ */ jsx("span", {
2057
- className: "truncate",
2058
- children: column.columnDef.meta?.label ?? column.id
2059
- })]
2060
- }, column.id)) })] }) })]
1572
+ }), /* @__PURE__ */ jsx(CommandList, {
1573
+ onScroll: selectedColumnMeta?.autoCrudFilterOnPopupScroll,
1574
+ children: selectedColumn ? /* @__PURE__ */ jsxs(Fragment, { children: [
1575
+ selectedColumn.columnDef.meta?.options && /* @__PURE__ */ jsx(CommandEmpty, { children: "No options found." }),
1576
+ /* @__PURE__ */ jsx(FilterValueSelector, {
1577
+ column: selectedColumn,
1578
+ value: inputValue,
1579
+ onSelect: (value) => onFilterAdd(selectedColumn, value)
1580
+ }),
1581
+ selectedColumnMeta?.autoCrudFilterLoading && /* @__PURE__ */ jsx(CommandGroup, { children: /* @__PURE__ */ jsx("div", {
1582
+ role: "status",
1583
+ "aria-live": "polite",
1584
+ className: "py-2 text-center text-sm text-muted-foreground",
1585
+ children: "Loading..."
1586
+ }) }),
1587
+ !selectedColumnMeta?.autoCrudFilterLoading && selectedColumnMeta?.autoCrudFilterHasMore && /* @__PURE__ */ jsx("div", {
1588
+ role: "status",
1589
+ "aria-live": "polite",
1590
+ className: "sr-only",
1591
+ children: "Load more"
1592
+ })
1593
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(CommandEmpty, { children: "No fields found." }), /* @__PURE__ */ jsx(CommandGroup, { children: columns.map((column) => /* @__PURE__ */ jsxs(CommandItem, {
1594
+ value: column.id,
1595
+ onSelect: () => {
1596
+ setSelectedColumn(column);
1597
+ setInputValue("");
1598
+ requestAnimationFrame(() => {
1599
+ inputRef.current?.focus();
1600
+ });
1601
+ },
1602
+ children: [column.columnDef.meta?.icon && /* @__PURE__ */ jsx(column.columnDef.meta.icon, {}), /* @__PURE__ */ jsx("span", {
1603
+ className: "truncate",
1604
+ children: column.columnDef.meta?.label ?? column.id
1605
+ })]
1606
+ }, column.id)) })] })
1607
+ })]
2061
1608
  })
2062
1609
  })]
2063
1610
  })
@@ -2134,7 +1681,7 @@ function DataTableFilterItem({ filter, filterItemId, columns, onFilterUpdate, on
2134
1681
  })
2135
1682
  })]
2136
1683
  }),
2137
- /* @__PURE__ */ jsxs(Select, {
1684
+ /* @__PURE__ */ jsxs(Select$1, {
2138
1685
  open: showOperatorSelector,
2139
1686
  onOpenChange: setShowOperatorSelector,
2140
1687
  value: filter.operator,
@@ -2187,7 +1734,7 @@ function FilterValueSelector({ column, value, onSelect }) {
2187
1734
  children: "False"
2188
1735
  })] });
2189
1736
  case "select":
2190
- case "multiSelect": return /* @__PURE__ */ jsx(CommandGroup, { children: column.columnDef.meta?.options?.map((option) => /* @__PURE__ */ jsxs(CommandItem, {
1737
+ case "multiSelect": return /* @__PURE__ */ jsx(CommandGroup, { children: getColumnFilterOptions(column)?.map((option) => /* @__PURE__ */ jsxs(CommandItem, {
2191
1738
  value: getOptionCommandValue(option),
2192
1739
  onSelect: () => onSelect(option.value),
2193
1740
  children: [
@@ -2260,7 +1807,7 @@ function onFilterInputRender({ filter, column, inputId, onFilterUpdate, showValu
2260
1807
  }
2261
1808
  case "boolean": {
2262
1809
  const inputListboxId = `${inputId}-listbox`;
2263
- return /* @__PURE__ */ jsxs(Select, {
1810
+ return /* @__PURE__ */ jsxs(Select$1, {
2264
1811
  open: showValueSelector,
2265
1812
  onOpenChange: setShowValueSelector,
2266
1813
  value: typeof filter.value === "string" ? filter.value : "true",
@@ -2286,6 +1833,7 @@ function onFilterInputRender({ filter, column, inputId, onFilterUpdate, showValu
2286
1833
  case "multiSelect": {
2287
1834
  const inputListboxId = `${inputId}-listbox`;
2288
1835
  const options = column.columnDef.meta?.options ?? [];
1836
+ const optionList = getColumnFilterOptions(column) ?? [];
2289
1837
  const selectedValues = Array.isArray(filter.value) ? filter.value : [filter.value];
2290
1838
  const selectedOptions = options.filter((option) => selectedValues.includes(option.value));
2291
1839
  return /* @__PURE__ */ jsxs(Popover, {
@@ -2314,21 +1862,46 @@ function onFilterInputRender({ filter, column, inputId, onFilterUpdate, showValu
2314
1862
  id: inputListboxId,
2315
1863
  align: "start",
2316
1864
  className: "w-48 p-0",
2317
- children: /* @__PURE__ */ jsxs(Command, { children: [/* @__PURE__ */ jsx(CommandInput, { placeholder: "Search options..." }), /* @__PURE__ */ jsxs(CommandList, { children: [/* @__PURE__ */ jsx(CommandEmpty, { children: "No options found." }), /* @__PURE__ */ jsx(CommandGroup, { children: options.map((option) => /* @__PURE__ */ jsxs(CommandItem, {
2318
- value: getOptionCommandValue(option),
2319
- onSelect: () => {
2320
- const value = filter.variant === "multiSelect" ? selectedValues.includes(option.value) ? selectedValues.filter((v) => v !== option.value) : [...selectedValues, option.value] : option.value;
2321
- onFilterUpdate(filter.filterId, { value });
2322
- },
2323
- children: [
2324
- option.icon && /* @__PURE__ */ jsx(option.icon, {}),
2325
- /* @__PURE__ */ jsx("span", {
2326
- className: "truncate",
2327
- children: option.label
2328
- }),
2329
- filter.variant === "multiSelect" && /* @__PURE__ */ jsx(Check, { className: cn("ml-auto", selectedValues.includes(option.value) ? "opacity-100" : "opacity-0") })
2330
- ]
2331
- }, option.value)) })] })] })
1865
+ children: /* @__PURE__ */ jsxs(Command, {
1866
+ shouldFilter: column.columnDef.meta?.autoCrudFilterShouldFilter,
1867
+ children: [/* @__PURE__ */ jsx(CommandInput, {
1868
+ placeholder: "Search options...",
1869
+ value: column.columnDef.meta?.autoCrudFilterSearchValue,
1870
+ onValueChange: column.columnDef.meta?.autoCrudFilterOnSearch
1871
+ }), /* @__PURE__ */ jsxs(CommandList, {
1872
+ onScroll: column.columnDef.meta?.autoCrudFilterOnPopupScroll,
1873
+ children: [
1874
+ /* @__PURE__ */ jsx(CommandEmpty, { children: "No options found." }),
1875
+ /* @__PURE__ */ jsx(CommandGroup, { children: optionList.map((option) => /* @__PURE__ */ jsxs(CommandItem, {
1876
+ value: getOptionCommandValue(option),
1877
+ onSelect: () => {
1878
+ const value = filter.variant === "multiSelect" ? selectedValues.includes(option.value) ? selectedValues.filter((v) => v !== option.value) : [...selectedValues, option.value] : option.value;
1879
+ onFilterUpdate(filter.filterId, { value });
1880
+ },
1881
+ children: [
1882
+ option.icon && /* @__PURE__ */ jsx(option.icon, {}),
1883
+ /* @__PURE__ */ jsx("span", {
1884
+ className: "truncate",
1885
+ children: option.label
1886
+ }),
1887
+ filter.variant === "multiSelect" && /* @__PURE__ */ jsx(Check, { className: cn("ml-auto", selectedValues.includes(option.value) ? "opacity-100" : "opacity-0") })
1888
+ ]
1889
+ }, option.value)) }),
1890
+ column.columnDef.meta?.autoCrudFilterLoading && /* @__PURE__ */ jsx(CommandGroup, { children: /* @__PURE__ */ jsx("div", {
1891
+ role: "status",
1892
+ "aria-live": "polite",
1893
+ className: "py-2 text-center text-sm text-muted-foreground",
1894
+ children: "Loading..."
1895
+ }) }),
1896
+ !column.columnDef.meta?.autoCrudFilterLoading && column.columnDef.meta?.autoCrudFilterHasMore && /* @__PURE__ */ jsx("div", {
1897
+ role: "status",
1898
+ "aria-live": "polite",
1899
+ className: "sr-only",
1900
+ children: "Load more"
1901
+ })
1902
+ ]
1903
+ })]
1904
+ })
2332
1905
  })]
2333
1906
  });
2334
1907
  }
@@ -2452,7 +2025,7 @@ function DataTableSortList({ table, disabled,...props }) {
2452
2025
  window.addEventListener("keydown", onKeyDown);
2453
2026
  return () => window.removeEventListener("keydown", onKeyDown);
2454
2027
  }, []);
2455
- return /* @__PURE__ */ jsxs(SortableRoot, {
2028
+ return /* @__PURE__ */ jsxs(Sortable, {
2456
2029
  value: sorting,
2457
2030
  onValueChange: onSortingChange,
2458
2031
  getItemValue: (item) => item.id,
@@ -2603,7 +2176,7 @@ function DataTableSortItem({ sort, sortItemId, columns, columnLabels, onSortUpda
2603
2176
  }, column.id)) })] })] })
2604
2177
  })]
2605
2178
  }),
2606
- /* @__PURE__ */ jsxs(Select, {
2179
+ /* @__PURE__ */ jsxs(Select$1, {
2607
2180
  open: showDirectionSelector,
2608
2181
  onOpenChange: setShowDirectionSelector,
2609
2182
  value: sort.desc ? "desc" : "asc",
@@ -2672,207 +2245,12 @@ function DataTableViewOptions({ table, disabled,...props }) {
2672
2245
  })] });
2673
2246
  }
2674
2247
 
2675
- //#endregion
2676
- //#region src/components/ui/multi-combobox.tsx
2677
- const DEFAULT_OPTIONS = [];
2678
- const SCROLLABLE_COMMAND_GROUP_STYLE = {
2679
- maxHeight: "300px",
2680
- overscrollBehavior: "contain",
2681
- overflowX: "hidden",
2682
- overflowY: "auto"
2683
- };
2684
- function getWheelDeltaY(event) {
2685
- if (event.deltaMode === 1) return event.deltaY * 16;
2686
- if (event.deltaMode === 2) return event.deltaY * event.currentTarget.clientHeight;
2687
- return event.deltaY;
2688
- }
2689
- function handleScrollableCommandGroupWheel(event) {
2690
- const target = event.currentTarget;
2691
- if (target.scrollHeight <= target.clientHeight) return;
2692
- const maxScrollTop = target.scrollHeight - target.clientHeight;
2693
- const nextScrollTop = Math.max(0, Math.min(target.scrollTop + getWheelDeltaY(event), maxScrollTop));
2694
- if (nextScrollTop === target.scrollTop) return;
2695
- event.preventDefault();
2696
- event.stopPropagation();
2697
- target.scrollTop = nextScrollTop;
2698
- }
2699
- function collectSearchText(...parts) {
2700
- const texts = [];
2701
- for (const part of parts) {
2702
- if (!part) continue;
2703
- if (Array.isArray(part)) texts.push(...part.map(String));
2704
- else if (typeof part === "object") texts.push(...Object.values(part).filter(Boolean).map(String));
2705
- else texts.push(String(part));
2706
- }
2707
- return texts.join(" ");
2708
- }
2709
- function getOptionKeywords(option) {
2710
- return collectSearchText(typeof option.label === "string" || typeof option.label === "number" ? option.label : void 0, option.searchText, option.keywords).split(/\s+/).filter(Boolean);
2711
- }
2712
- function getOptionText(option) {
2713
- if (typeof option.label === "string" || typeof option.label === "number") return String(option.label);
2714
- return option.value;
2715
- }
2716
- function createNextValue(currentValue, optionValue, isSelected, selectionMode) {
2717
- if (selectionMode === "single") return isSelected ? [] : [optionValue];
2718
- return isSelected ? currentValue.filter((value) => value !== optionValue) : [...currentValue, optionValue];
2719
- }
2720
- function DefaultMultiComboboxTrigger({ open, selectedOptions, selectedValues, clearSelection, disabled, readOnly, placeholder, selectedText, className,...buttonProps }) {
2721
- return /* @__PURE__ */ jsxs(Button, {
2722
- ...buttonProps,
2723
- type: "button",
2724
- variant: "outline",
2725
- role: "combobox",
2726
- "aria-expanded": open,
2727
- disabled,
2728
- className: cn$1("w-full justify-between", className),
2729
- children: [/* @__PURE__ */ jsx("span", {
2730
- className: "flex min-w-0 flex-1 items-center gap-1 overflow-hidden",
2731
- children: selectedValues.length === 0 ? /* @__PURE__ */ jsx("span", {
2732
- className: "truncate text-muted-foreground",
2733
- children: placeholder
2734
- }) : selectedOptions.length <= 2 ? selectedOptions.map((option) => /* @__PURE__ */ jsx(Badge, {
2735
- variant: "secondary",
2736
- className: "max-w-[8rem] rounded-sm px-1 font-normal",
2737
- children: /* @__PURE__ */ jsx("span", {
2738
- className: "truncate",
2739
- children: option.label
2740
- })
2741
- }, option.value)) : /* @__PURE__ */ jsxs(Badge, {
2742
- variant: "secondary",
2743
- className: "rounded-sm px-1 font-normal",
2744
- children: [
2745
- selectedValues.length,
2746
- " ",
2747
- selectedText
2748
- ]
2749
- })
2750
- }), /* @__PURE__ */ jsxs("span", {
2751
- className: "ml-2 flex shrink-0 items-center gap-1",
2752
- children: [selectedValues.length > 0 && !readOnly && /* @__PURE__ */ jsx("span", {
2753
- role: "button",
2754
- "aria-label": "Clear selection",
2755
- tabIndex: 0,
2756
- className: "rounded-sm px-1 text-muted-foreground opacity-70 transition-opacity hover:opacity-100 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring",
2757
- onClick: clearSelection,
2758
- onKeyDown: (event) => {
2759
- if (event.key === "Enter" || event.key === " ") {
2760
- event.preventDefault();
2761
- clearSelection(event);
2762
- }
2763
- },
2764
- children: /* @__PURE__ */ jsx(X, { className: "size-3" })
2765
- }), /* @__PURE__ */ jsx(ChevronsUpDown, { className: "h-4 w-4 opacity-50" })]
2766
- })]
2767
- });
2768
- }
2769
- const MultiCombobox = ({ value, defaultValue, onChange, options = DEFAULT_OPTIONS, placeholder = "Select options...", searchPlaceholder = "Search...", emptyText = "No options found.", clearText = "Clear selection", selectedText = "selected", selectionMode = "multiple", disabled, readOnly, className, contentClassName, triggerClassName, matchTriggerWidth = true, renderTrigger, filter,...commandProps }) => {
2770
- const [open, setOpen] = React.useState(false);
2771
- const [internalValue, setInternalValue] = React.useState(defaultValue ?? []);
2772
- const selectedValues = value ?? internalValue;
2773
- const selectedValueSet = React.useMemo(() => new Set(selectedValues), [selectedValues]);
2774
- const selectedOptions = React.useMemo(() => options.filter((option) => selectedValueSet.has(option.value)), [options, selectedValueSet]);
2775
- const commitChange = React.useCallback((nextValue) => {
2776
- if (value === void 0) setInternalValue(nextValue);
2777
- onChange?.(nextValue);
2778
- }, [onChange, value]);
2779
- const clearSelection = React.useCallback((event) => {
2780
- event?.preventDefault();
2781
- event?.stopPropagation();
2782
- if (readOnly) return;
2783
- commitChange([]);
2784
- }, [commitChange, readOnly]);
2785
- const onItemSelect = React.useCallback((option) => {
2786
- if (option.disabled || readOnly) return;
2787
- const isSelected = selectedValueSet.has(option.value);
2788
- commitChange(createNextValue(selectedValues, option.value, isSelected, selectionMode));
2789
- if (selectionMode === "single") setOpen(false);
2790
- }, [
2791
- commitChange,
2792
- readOnly,
2793
- selectedValues,
2794
- selectedValueSet,
2795
- selectionMode
2796
- ]);
2797
- return /* @__PURE__ */ jsxs(Popover, {
2798
- open,
2799
- onOpenChange: setOpen,
2800
- children: [/* @__PURE__ */ jsx(PopoverTrigger, {
2801
- asChild: true,
2802
- children: renderTrigger ? renderTrigger({
2803
- open,
2804
- selectedOptions,
2805
- selectedValues,
2806
- clearSelection,
2807
- disabled,
2808
- readOnly
2809
- }) : /* @__PURE__ */ jsx(DefaultMultiComboboxTrigger, {
2810
- open,
2811
- selectedOptions,
2812
- selectedValues,
2813
- clearSelection,
2814
- disabled,
2815
- readOnly,
2816
- placeholder,
2817
- selectedText,
2818
- className: triggerClassName
2819
- })
2820
- }), /* @__PURE__ */ jsx(PopoverContent, {
2821
- className: cn$1("w-full p-0", contentClassName),
2822
- style: matchTriggerWidth ? { width: "var(--radix-popover-trigger-width)" } : void 0,
2823
- children: /* @__PURE__ */ jsxs(Command, {
2824
- className,
2825
- filter,
2826
- ...commandProps,
2827
- children: [/* @__PURE__ */ jsx(CommandInput, { placeholder: searchPlaceholder }), /* @__PURE__ */ jsxs(CommandList, {
2828
- className: "max-h-full",
2829
- children: [
2830
- /* @__PURE__ */ jsx(CommandEmpty, { children: emptyText }),
2831
- /* @__PURE__ */ jsx(CommandGroup, {
2832
- className: "max-h-[300px] scroll-py-1 overflow-y-auto overflow-x-hidden",
2833
- onWheelCapture: handleScrollableCommandGroupWheel,
2834
- style: SCROLLABLE_COMMAND_GROUP_STYLE,
2835
- children: options.map((option) => {
2836
- const isSelected = selectedValueSet.has(option.value);
2837
- const Icon = option.icon;
2838
- return /* @__PURE__ */ jsxs(CommandItem, {
2839
- value: option.value,
2840
- keywords: getOptionKeywords(option),
2841
- disabled: option.disabled,
2842
- onSelect: () => onItemSelect(option),
2843
- children: [
2844
- /* @__PURE__ */ jsx("div", {
2845
- className: cn$1("flex size-4 items-center justify-center rounded-sm border border-primary", isSelected ? "bg-primary text-primary-foreground" : "opacity-50 [&_svg]:invisible"),
2846
- children: /* @__PURE__ */ jsx(Check, {})
2847
- }),
2848
- Icon && /* @__PURE__ */ jsx(Icon, { className: "size-4" }),
2849
- /* @__PURE__ */ jsx("span", {
2850
- className: "truncate",
2851
- children: getOptionText(option)
2852
- }),
2853
- option.count !== void 0 && /* @__PURE__ */ jsx("span", {
2854
- className: "ml-auto font-mono text-xs",
2855
- children: option.count
2856
- })
2857
- ]
2858
- }, option.value);
2859
- })
2860
- }),
2861
- selectedValues.length > 0 && !readOnly && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(CommandSeparator, {}), /* @__PURE__ */ jsx(CommandGroup, { children: /* @__PURE__ */ jsx(CommandItem, {
2862
- onSelect: () => commitChange([]),
2863
- className: "justify-center text-center",
2864
- children: clearText
2865
- }) })] })
2866
- ]
2867
- })]
2868
- })
2869
- })]
2870
- });
2871
- };
2872
- MultiCombobox.displayName = "MultiCombobox";
2873
-
2874
2248
  //#endregion
2875
2249
  //#region src/components/auto-crud/auto-table-simple-filters.tsx
2250
+ function mergeSelectedFilterOptions(currentOptions, labelOptions, selectedValues) {
2251
+ const currentValues = new Set(currentOptions.map((option) => option.value));
2252
+ return [...labelOptions.filter((option) => selectedValues.includes(option.value) && !currentValues.has(option.value)), ...currentOptions];
2253
+ }
2876
2254
  function AutoTableSimpleFilters({ table, shallow = false, filters: externalFilters, onFiltersChange, leading }) {
2877
2255
  const columnDefs = table.options.columns;
2878
2256
  const columns = React.useMemo(() => table.getAllColumns().filter((col) => col.getCanFilter()).sort((a, b) => {
@@ -3061,17 +2439,27 @@ function AutoTableSimpleFilters({ table, shallow = false, filters: externalFilte
3061
2439
  })
3062
2440
  }, column.id);
3063
2441
  case "select":
3064
- case "multiSelect": return /* @__PURE__ */ jsx("div", {
3065
- className: isHidden ? "hidden" : "",
3066
- "data-filter-item": true,
3067
- children: /* @__PURE__ */ jsx(SimpleFacetedFilter, {
3068
- title: meta.label ?? column.id,
3069
- options: meta.options ?? [],
3070
- multiple: meta.variant === "multiSelect",
3071
- value: Array.isArray(value) ? value : value ? [value] : [],
3072
- onChange: (v) => updateFilter(column.id, v.length ? v : void 0)
3073
- })
3074
- }, column.id);
2442
+ case "multiSelect": {
2443
+ const selectedValues = Array.isArray(value) ? value : value ? [value] : [];
2444
+ const filterOptions = mergeSelectedFilterOptions(meta.autoCrudFilterOptions ?? meta.options ?? [], meta.options ?? [], selectedValues);
2445
+ return /* @__PURE__ */ jsx("div", {
2446
+ className: isHidden ? "hidden" : "",
2447
+ "data-filter-item": true,
2448
+ children: /* @__PURE__ */ jsx(SimpleFacetedFilter, {
2449
+ title: meta.label ?? column.id,
2450
+ options: filterOptions,
2451
+ multiple: meta.variant === "multiSelect",
2452
+ value: selectedValues,
2453
+ hasMore: meta.autoCrudFilterHasMore,
2454
+ loading: meta.autoCrudFilterLoading,
2455
+ searchValue: meta.autoCrudFilterSearchValue,
2456
+ shouldFilter: meta.autoCrudFilterShouldFilter,
2457
+ onChange: (v) => updateFilter(column.id, v.length ? v : void 0),
2458
+ onPopupScroll: meta.autoCrudFilterOnPopupScroll,
2459
+ onSearch: meta.autoCrudFilterOnSearch
2460
+ })
2461
+ }, column.id);
2462
+ }
3075
2463
  case "range": return /* @__PURE__ */ jsx("div", {
3076
2464
  className: isHidden ? "hidden" : "",
3077
2465
  "data-filter-item": true,
@@ -3118,13 +2506,23 @@ function AutoTableSimpleFilters({ table, shallow = false, filters: externalFilte
3118
2506
  ]
3119
2507
  });
3120
2508
  }
3121
- function SimpleFacetedFilter({ title, options, multiple, value, onChange }) {
3122
- return /* @__PURE__ */ jsx(MultiCombobox, {
3123
- value,
3124
- onChange,
2509
+ function SimpleFacetedFilter({ title, options, multiple, value, hasMore, loading, searchValue, shouldFilter, onChange, onPopupScroll, onSearch }) {
2510
+ const onSelectChange = (nextValue) => {
2511
+ onChange(Array.isArray(nextValue) ? nextValue : nextValue ? [nextValue] : []);
2512
+ };
2513
+ return /* @__PURE__ */ jsx(Select$2, {
2514
+ mode: "searchable",
2515
+ multiple: Boolean(multiple),
2516
+ value: multiple ? value : value[0] ?? "",
2517
+ onChange: onSelectChange,
3125
2518
  options,
3126
- selectionMode: multiple ? "multiple" : "single",
3127
2519
  searchPlaceholder: title,
2520
+ hasMore,
2521
+ loading,
2522
+ searchValue,
2523
+ shouldFilter,
2524
+ onPopupScroll,
2525
+ onSearch,
3128
2526
  contentClassName: "w-50",
3129
2527
  matchTriggerWidth: false,
3130
2528
  clearText: "Clear filters",
@@ -3422,386 +2820,6 @@ function SimpleDateFilter({ title, multiple, value, onChange }) {
3422
2820
  })] });
3423
2821
  }
3424
2822
 
3425
- //#endregion
3426
- //#region src/hooks/use-isomorphic-layout-effect.ts
3427
- const useIsomorphicLayoutEffect = typeof window !== "undefined" ? React.useLayoutEffect : React.useEffect;
3428
-
3429
- //#endregion
3430
- //#region src/hooks/use-as-ref.ts
3431
- function useAsRef(props) {
3432
- const ref = React.useRef(props);
3433
- useIsomorphicLayoutEffect(() => {
3434
- ref.current = props;
3435
- });
3436
- return ref;
3437
- }
3438
-
3439
- //#endregion
3440
- //#region src/components/ui/action-bar.tsx
3441
- const ROOT_NAME = "ActionBar";
3442
- const GROUP_NAME = "ActionBarGroup";
3443
- const ITEM_NAME = "ActionBarItem";
3444
- const CLOSE_NAME = "ActionBarClose";
3445
- const SEPARATOR_NAME = "ActionBarSeparator";
3446
- const ITEM_SELECT = "actionbar.itemSelect";
3447
- const ENTRY_FOCUS = "actionbarFocusGroup.onEntryFocus";
3448
- const EVENT_OPTIONS = {
3449
- bubbles: false,
3450
- cancelable: true
3451
- };
3452
- function focusFirst(candidates, preventScroll = false) {
3453
- const PREVIOUSLY_FOCUSED_ELEMENT = document.activeElement;
3454
- for (const candidateRef of candidates) {
3455
- const candidate = candidateRef.current;
3456
- if (!candidate) continue;
3457
- if (candidate === PREVIOUSLY_FOCUSED_ELEMENT) return;
3458
- candidate.focus({ preventScroll });
3459
- if (document.activeElement !== PREVIOUSLY_FOCUSED_ELEMENT) return;
3460
- }
3461
- }
3462
- function wrapArray(array, startIndex) {
3463
- return array.map((_, index$1) => array[(startIndex + index$1) % array.length]);
3464
- }
3465
- function getDirectionAwareKey(key, dir) {
3466
- if (dir !== "rtl") return key;
3467
- return key === "ArrowLeft" ? "ArrowRight" : key === "ArrowRight" ? "ArrowLeft" : key;
3468
- }
3469
- const ActionBarContext = React.createContext(null);
3470
- function useActionBarContext(consumerName) {
3471
- const context = React.useContext(ActionBarContext);
3472
- if (!context) throw new Error(`\`${consumerName}\` must be used within \`${ROOT_NAME}\``);
3473
- return context;
3474
- }
3475
- const FocusContext = React.createContext(null);
3476
- function useFocusContext(consumerName) {
3477
- const context = React.useContext(FocusContext);
3478
- if (!context) throw new Error(`\`${consumerName}\` must be used within \`FocusProvider\``);
3479
- return context;
3480
- }
3481
- function ActionBar(props) {
3482
- const { open = false, onOpenChange, onEscapeKeyDown, side = "bottom", alignOffset = 0, align = "center", sideOffset = 16, portalContainer: portalContainerProp, dir: dirProp, orientation = "horizontal", loop = true, className, style, ref, asChild,...rootProps } = props;
3483
- const [mounted, setMounted] = React.useState(false);
3484
- const rootRef = React.useRef(null);
3485
- const composedRef = useComposedRefs(ref, rootRef);
3486
- const propsRef = useAsRef({
3487
- onEscapeKeyDown,
3488
- onOpenChange
3489
- });
3490
- const dir = useDirection(dirProp);
3491
- React.useLayoutEffect(() => {
3492
- setMounted(true);
3493
- }, []);
3494
- React.useEffect(() => {
3495
- if (!open) return;
3496
- const ownerDocument = rootRef.current?.ownerDocument ?? document;
3497
- function onKeyDown(event) {
3498
- if (event.key === "Escape") {
3499
- propsRef.current.onEscapeKeyDown?.(event);
3500
- if (!event.defaultPrevented) propsRef.current.onOpenChange?.(false);
3501
- }
3502
- }
3503
- ownerDocument.addEventListener("keydown", onKeyDown);
3504
- return () => ownerDocument.removeEventListener("keydown", onKeyDown);
3505
- }, [open, propsRef]);
3506
- const contextValue = React.useMemo(() => ({
3507
- onOpenChange,
3508
- dir,
3509
- orientation,
3510
- loop
3511
- }), [
3512
- onOpenChange,
3513
- dir,
3514
- orientation,
3515
- loop
3516
- ]);
3517
- const portalContainer = portalContainerProp ?? (mounted ? globalThis.document?.body : null);
3518
- if (!portalContainer || !open) return null;
3519
- const RootPrimitive = asChild ? Slot : "div";
3520
- return /* @__PURE__ */ jsx(ActionBarContext.Provider, {
3521
- value: contextValue,
3522
- children: ReactDOM.createPortal(/* @__PURE__ */ jsx(RootPrimitive, {
3523
- role: "toolbar",
3524
- "aria-orientation": orientation,
3525
- "data-slot": "action-bar",
3526
- "data-side": side,
3527
- "data-align": align,
3528
- "data-orientation": orientation,
3529
- dir,
3530
- ...rootProps,
3531
- ref: composedRef,
3532
- className: cn("fixed z-50 rounded-lg border bg-card shadow-lg outline-none", "fade-in-0 zoom-in-95 animate-in duration-250 [animation-timing-function:cubic-bezier(0.16,1,0.3,1)]", "data-[side=bottom]:slide-in-from-bottom-4 data-[side=top]:slide-in-from-top-4", "motion-reduce:animate-none motion-reduce:transition-none", orientation === "horizontal" ? "flex flex-row items-center gap-2 px-2 py-1.5" : "flex flex-col items-start gap-2 px-1.5 py-2", className),
3533
- style: {
3534
- [side]: `${sideOffset}px`,
3535
- ...align === "center" && {
3536
- left: "50%",
3537
- translate: "-50% 0"
3538
- },
3539
- ...align === "start" && { left: `${alignOffset}px` },
3540
- ...align === "end" && { right: `${alignOffset}px` },
3541
- ...style
3542
- }
3543
- }), portalContainer)
3544
- });
3545
- }
3546
- function ActionBarSelection(props) {
3547
- const { className, asChild,...selectionProps } = props;
3548
- return /* @__PURE__ */ jsx(asChild ? Slot : "div", {
3549
- "data-slot": "action-bar-selection",
3550
- ...selectionProps,
3551
- className: cn("flex items-center gap-1 rounded-sm border px-2 py-1 font-medium text-sm tabular-nums", className)
3552
- });
3553
- }
3554
- function ActionBarGroup(props) {
3555
- const { onBlur: onBlurProp, onFocus: onFocusProp, onMouseDown: onMouseDownProp, className, asChild, ref,...groupProps } = props;
3556
- const [tabStopId, setTabStopId] = React.useState(null);
3557
- const [isTabbingBackOut, setIsTabbingBackOut] = React.useState(false);
3558
- const [focusableItemCount, setFocusableItemCount] = React.useState(0);
3559
- const composedRef = useComposedRefs(ref, React.useRef(null));
3560
- const isClickFocusRef = React.useRef(false);
3561
- const itemsRef = React.useRef(/* @__PURE__ */ new Map());
3562
- const { dir, orientation } = useActionBarContext(GROUP_NAME);
3563
- const onItemFocus = React.useCallback((tabStopId$1) => {
3564
- setTabStopId(tabStopId$1);
3565
- }, []);
3566
- const onItemShiftTab = React.useCallback(() => {
3567
- setIsTabbingBackOut(true);
3568
- }, []);
3569
- const onFocusableItemAdd = React.useCallback(() => {
3570
- setFocusableItemCount((prevCount) => prevCount + 1);
3571
- }, []);
3572
- const onFocusableItemRemove = React.useCallback(() => {
3573
- setFocusableItemCount((prevCount) => prevCount - 1);
3574
- }, []);
3575
- const onItemRegister = React.useCallback((item) => {
3576
- itemsRef.current.set(item.id, item);
3577
- }, []);
3578
- const onItemUnregister = React.useCallback((id) => {
3579
- itemsRef.current.delete(id);
3580
- }, []);
3581
- const getItems = React.useCallback(() => {
3582
- return Array.from(itemsRef.current.values()).filter((item) => item.ref.current).sort((a, b) => {
3583
- const elementA = a.ref.current;
3584
- const elementB = b.ref.current;
3585
- if (!elementA || !elementB) return 0;
3586
- const position = elementA.compareDocumentPosition(elementB);
3587
- if (position & Node.DOCUMENT_POSITION_FOLLOWING) return -1;
3588
- if (position & Node.DOCUMENT_POSITION_PRECEDING) return 1;
3589
- return 0;
3590
- });
3591
- }, []);
3592
- const onBlur = React.useCallback((event) => {
3593
- onBlurProp?.(event);
3594
- if (event.defaultPrevented) return;
3595
- setIsTabbingBackOut(false);
3596
- }, [onBlurProp]);
3597
- const onFocus = React.useCallback((event) => {
3598
- onFocusProp?.(event);
3599
- if (event.defaultPrevented) return;
3600
- const isKeyboardFocus = !isClickFocusRef.current;
3601
- if (event.target === event.currentTarget && isKeyboardFocus && !isTabbingBackOut) {
3602
- const entryFocusEvent = new CustomEvent(ENTRY_FOCUS, EVENT_OPTIONS);
3603
- event.currentTarget.dispatchEvent(entryFocusEvent);
3604
- if (!entryFocusEvent.defaultPrevented) {
3605
- const items = Array.from(itemsRef.current.values()).filter((item) => !item.disabled);
3606
- focusFirst([items.find((item) => item.id === tabStopId), ...items].filter(Boolean).map((item) => item.ref), false);
3607
- }
3608
- }
3609
- isClickFocusRef.current = false;
3610
- }, [
3611
- onFocusProp,
3612
- isTabbingBackOut,
3613
- tabStopId
3614
- ]);
3615
- const onMouseDown = React.useCallback((event) => {
3616
- onMouseDownProp?.(event);
3617
- if (event.defaultPrevented) return;
3618
- isClickFocusRef.current = true;
3619
- }, [onMouseDownProp]);
3620
- const focusContextValue = React.useMemo(() => ({
3621
- tabStopId,
3622
- onItemFocus,
3623
- onItemShiftTab,
3624
- onFocusableItemAdd,
3625
- onFocusableItemRemove,
3626
- onItemRegister,
3627
- onItemUnregister,
3628
- getItems
3629
- }), [
3630
- tabStopId,
3631
- onItemFocus,
3632
- onItemShiftTab,
3633
- onFocusableItemAdd,
3634
- onFocusableItemRemove,
3635
- onItemRegister,
3636
- onItemUnregister,
3637
- getItems
3638
- ]);
3639
- const GroupPrimitive = asChild ? Slot : "div";
3640
- return /* @__PURE__ */ jsx(FocusContext.Provider, {
3641
- value: focusContextValue,
3642
- children: /* @__PURE__ */ jsx(GroupPrimitive, {
3643
- role: "group",
3644
- "data-slot": "action-bar-group",
3645
- "data-orientation": orientation,
3646
- dir,
3647
- tabIndex: isTabbingBackOut || focusableItemCount === 0 ? -1 : 0,
3648
- ...groupProps,
3649
- ref: composedRef,
3650
- className: cn("flex gap-2 outline-none", orientation === "horizontal" ? "items-center" : "w-full flex-col items-start", className),
3651
- onBlur,
3652
- onFocus,
3653
- onMouseDown
3654
- })
3655
- });
3656
- }
3657
- function ActionBarItem(props) {
3658
- const { onSelect, onClick: onClickProp, onFocus: onFocusProp, onKeyDown: onKeyDownProp, onMouseDown: onMouseDownProp, className, disabled, ref,...itemProps } = props;
3659
- const itemRef = React.useRef(null);
3660
- const composedRef = useComposedRefs(ref, itemRef);
3661
- const isMouseClickRef = React.useRef(false);
3662
- const { onOpenChange, dir, orientation, loop } = useActionBarContext(ITEM_NAME);
3663
- const focusContext = useFocusContext(ITEM_NAME);
3664
- const itemId = React.useId();
3665
- const isTabStop = focusContext.tabStopId === itemId;
3666
- useIsomorphicLayoutEffect(() => {
3667
- focusContext.onItemRegister({
3668
- id: itemId,
3669
- ref: itemRef,
3670
- disabled: !!disabled
3671
- });
3672
- if (!disabled) focusContext.onFocusableItemAdd();
3673
- return () => {
3674
- focusContext.onItemUnregister(itemId);
3675
- if (!disabled) focusContext.onFocusableItemRemove();
3676
- };
3677
- }, [
3678
- focusContext,
3679
- itemId,
3680
- disabled
3681
- ]);
3682
- const onClick = React.useCallback((event) => {
3683
- onClickProp?.(event);
3684
- if (event.defaultPrevented) return;
3685
- const item = itemRef.current;
3686
- if (!item) return;
3687
- const itemSelectEvent = new CustomEvent(ITEM_SELECT, {
3688
- bubbles: true,
3689
- cancelable: true
3690
- });
3691
- item.addEventListener(ITEM_SELECT, (event$1) => onSelect?.(event$1), { once: true });
3692
- item.dispatchEvent(itemSelectEvent);
3693
- if (!itemSelectEvent.defaultPrevented) onOpenChange?.(false);
3694
- }, [
3695
- onClickProp,
3696
- onOpenChange,
3697
- onSelect
3698
- ]);
3699
- const onFocus = React.useCallback((event) => {
3700
- onFocusProp?.(event);
3701
- if (event.defaultPrevented) return;
3702
- focusContext.onItemFocus(itemId);
3703
- isMouseClickRef.current = false;
3704
- }, [
3705
- onFocusProp,
3706
- focusContext,
3707
- itemId
3708
- ]);
3709
- const onKeyDown = React.useCallback((event) => {
3710
- onKeyDownProp?.(event);
3711
- if (event.defaultPrevented) return;
3712
- if (event.key === "Tab" && event.shiftKey) {
3713
- focusContext.onItemShiftTab();
3714
- return;
3715
- }
3716
- if (event.target !== event.currentTarget) return;
3717
- const key = getDirectionAwareKey(event.key, dir);
3718
- let focusIntent;
3719
- if (orientation === "horizontal") {
3720
- if (key === "ArrowLeft") focusIntent = "prev";
3721
- else if (key === "ArrowRight") focusIntent = "next";
3722
- else if (key === "Home") focusIntent = "first";
3723
- else if (key === "End") focusIntent = "last";
3724
- } else if (key === "ArrowUp") focusIntent = "prev";
3725
- else if (key === "ArrowDown") focusIntent = "next";
3726
- else if (key === "Home") focusIntent = "first";
3727
- else if (key === "End") focusIntent = "last";
3728
- if (focusIntent !== void 0) {
3729
- if (event.metaKey || event.ctrlKey || event.altKey || event.shiftKey) return;
3730
- event.preventDefault();
3731
- let candidateRefs = focusContext.getItems().filter((item) => !item.disabled).map((item) => item.ref);
3732
- if (focusIntent === "last") candidateRefs.reverse();
3733
- else if (focusIntent === "prev" || focusIntent === "next") {
3734
- if (focusIntent === "prev") candidateRefs.reverse();
3735
- const currentIndex = candidateRefs.findIndex((ref$1) => ref$1.current === event.currentTarget);
3736
- candidateRefs = loop ? wrapArray(candidateRefs, currentIndex + 1) : candidateRefs.slice(currentIndex + 1);
3737
- }
3738
- queueMicrotask(() => focusFirst(candidateRefs));
3739
- }
3740
- }, [
3741
- onKeyDownProp,
3742
- focusContext,
3743
- dir,
3744
- orientation,
3745
- loop
3746
- ]);
3747
- const onMouseDown = React.useCallback((event) => {
3748
- onMouseDownProp?.(event);
3749
- if (event.defaultPrevented) return;
3750
- isMouseClickRef.current = true;
3751
- if (disabled) event.preventDefault();
3752
- else focusContext.onItemFocus(itemId);
3753
- }, [
3754
- onMouseDownProp,
3755
- focusContext,
3756
- itemId,
3757
- disabled
3758
- ]);
3759
- return /* @__PURE__ */ jsx(Button, {
3760
- type: "button",
3761
- "data-slot": "action-bar-item",
3762
- variant: "secondary",
3763
- size: "sm",
3764
- disabled,
3765
- tabIndex: isTabStop ? 0 : -1,
3766
- ...itemProps,
3767
- className: cn(orientation === "vertical" && "w-full", className),
3768
- ref: composedRef,
3769
- onClick,
3770
- onFocus,
3771
- onKeyDown,
3772
- onMouseDown
3773
- });
3774
- }
3775
- function ActionBarClose(props) {
3776
- const { asChild, className, onClick,...closeProps } = props;
3777
- const { onOpenChange } = useActionBarContext(CLOSE_NAME);
3778
- const onCloseClick = React.useCallback((event) => {
3779
- onClick?.(event);
3780
- if (event.defaultPrevented) return;
3781
- onOpenChange?.(false);
3782
- }, [onOpenChange, onClick]);
3783
- return /* @__PURE__ */ jsx(asChild ? Slot : "button", {
3784
- type: "button",
3785
- "data-slot": "action-bar-close",
3786
- ...closeProps,
3787
- className: cn("rounded-xs opacity-70 outline-none hover:opacity-100 focus-visible:border-ring focus-visible:ring-2 focus-visible:ring-ring/50 disabled:pointer-events-none [&_svg:not([class*='size-'])]:size-3.5 [&_svg]:pointer-events-none [&_svg]:shrink-0", className),
3788
- onClick: onCloseClick
3789
- });
3790
- }
3791
- function ActionBarSeparator(props) {
3792
- const { orientation: orientationProp, asChild, className,...separatorProps } = props;
3793
- const context = useActionBarContext(SEPARATOR_NAME);
3794
- const orientation = orientationProp ?? context.orientation;
3795
- return /* @__PURE__ */ jsx(asChild ? Slot : "div", {
3796
- role: "separator",
3797
- "aria-orientation": orientation,
3798
- "aria-hidden": "true",
3799
- "data-slot": "action-bar-separator",
3800
- ...separatorProps,
3801
- className: cn("in-data-[slot=action-bar-selection]:ml-0.5 in-data-[slot=action-bar-selection]:h-4 in-data-[slot=action-bar-selection]:w-px bg-border", orientation === "horizontal" ? "h-6 w-px" : "h-px w-full", className)
3802
- });
3803
- }
3804
-
3805
2823
  //#endregion
3806
2824
  //#region src/components/data-table/data-table-column-header.tsx
3807
2825
  function DataTableColumnHeader({ column, label, className,...props }) {
@@ -4462,12 +3480,22 @@ function downloadCSV(csvContent, filename) {
4462
3480
 
4463
3481
  //#endregion
4464
3482
  //#region src/components/auto-crud/auto-table-action-bar.tsx
4465
- function isBatchCustomAction(action) {
4466
- return action.type === "custom";
3483
+ const DEFAULT_DELETE_CONFIRMATION = {
3484
+ title: "确认批量删除",
3485
+ description: (count) => `此操作无法撤销。确定要删除选中的 ${count} 条记录吗?`,
3486
+ cancel: "取消",
3487
+ confirm: "确认删除"
3488
+ };
3489
+ function isBatchCustomAction(action$1) {
3490
+ return action$1.type === "custom";
4467
3491
  }
4468
- function AutoTableActionBar({ table, onDeleteSelected, onUpdateSelected, batchUpdateFields, enableExport = true, showDefaultExport = true, enableDelete = true, extraActions, actions }) {
3492
+ function AutoTableActionBar({ table, onDeleteSelected, onUpdateSelected, batchUpdateFields, enableExport = true, showDefaultExport = true, enableDelete = true, extraActions, actions, deleteConfirmation = DEFAULT_DELETE_CONFIRMATION }) {
4469
3493
  const rows = table.getFilteredSelectedRowModel().rows;
4470
3494
  const selectedRows = React.useMemo(() => rows.map((row) => row.original), [rows]);
3495
+ const [deleteDialogOpen, setDeleteDialogOpen] = React.useState(false);
3496
+ React.useEffect(() => {
3497
+ if (selectedRows.length === 0) setDeleteDialogOpen(false);
3498
+ }, [selectedRows.length]);
4471
3499
  const clearSelection = React.useCallback(() => {
4472
3500
  table.toggleAllRowsSelected(false);
4473
3501
  }, [table]);
@@ -4490,8 +3518,34 @@ function AutoTableActionBar({ table, onDeleteSelected, onUpdateSelected, batchUp
4490
3518
  });
4491
3519
  }, [table]);
4492
3520
  const onDelete = React.useCallback(() => {
4493
- if (onDeleteSelected) onDeleteSelected(selectedRows);
4494
- }, [selectedRows, onDeleteSelected]);
3521
+ if (onDeleteSelected && selectedRows.length > 0) {
3522
+ onDeleteSelected(selectedRows);
3523
+ clearSelection();
3524
+ }
3525
+ setDeleteDialogOpen(false);
3526
+ }, [
3527
+ selectedRows,
3528
+ onDeleteSelected,
3529
+ clearSelection
3530
+ ]);
3531
+ const runDeleteAction = React.useCallback((action$1, event) => {
3532
+ if (selectedRows.length === 0) {
3533
+ setDeleteDialogOpen(false);
3534
+ return;
3535
+ }
3536
+ if (action$1.onClick) {
3537
+ action$1.onClick(selectedRows, actionContext, event);
3538
+ clearSelection();
3539
+ setDeleteDialogOpen(false);
3540
+ return;
3541
+ }
3542
+ onDelete();
3543
+ }, [
3544
+ actionContext,
3545
+ clearSelection,
3546
+ onDelete,
3547
+ selectedRows
3548
+ ]);
4495
3549
  const handleBatchUpdate = React.useCallback((field, value) => {
4496
3550
  if (onUpdateSelected) {
4497
3551
  onUpdateSelected(selectedRows, { [field]: value });
@@ -4518,18 +3572,28 @@ function AutoTableActionBar({ table, onDeleteSelected, onUpdateSelected, batchUp
4518
3572
  children: option.label
4519
3573
  }, option.value))
4520
3574
  })] }, `${keyPrefix}-${fieldConfig.field}`)), [batchUpdateFields, handleBatchUpdate]);
4521
- const renderBuiltinAction = React.useCallback((action) => {
4522
- if (!(action.type === "batchUpdate" ? !!onUpdateSelected && !!batchUpdateFields?.length : action.type === "export" ? enableExport : enableDelete && !!onDeleteSelected)) return null;
4523
- if (action.component) return renderComponent(action.component);
4524
- if (action.type === "batchUpdate") return renderBatchUpdate(action.type);
4525
- if (action.type === "export") return /* @__PURE__ */ jsxs(ActionBarItem, {
4526
- onClick: (event) => action.onClick ? action.onClick(selectedRows, actionContext, event) : onExport(),
4527
- children: [/* @__PURE__ */ jsx(Download, {}), action.label ?? "Export"]
3575
+ const renderBuiltinAction = React.useCallback((action$1) => {
3576
+ if (!(action$1.type === "batchUpdate" ? !!onUpdateSelected && !!batchUpdateFields?.length : action$1.type === "export" ? enableExport : enableDelete && !!onDeleteSelected)) return null;
3577
+ if (action$1.component) return renderComponent(action$1.component);
3578
+ if (action$1.type === "batchUpdate") return renderBatchUpdate(action$1.type);
3579
+ if (action$1.type === "export") return /* @__PURE__ */ jsxs(ActionBarItem, {
3580
+ onClick: (event) => action$1.onClick ? action$1.onClick(selectedRows, actionContext, event) : onExport(),
3581
+ children: [/* @__PURE__ */ jsx(Download, {}), action$1.label ?? "Export"]
4528
3582
  }, "export");
4529
- if (action.type === "delete") return /* @__PURE__ */ jsxs(ActionBarItem, {
4530
- variant: "destructive",
4531
- onClick: (event) => action.onClick ? action.onClick(selectedRows, actionContext, event) : onDelete(),
4532
- children: [/* @__PURE__ */ jsx(Trash2, {}), action.label ?? "Delete"]
3583
+ if (action$1.type === "delete") return /* @__PURE__ */ jsxs(AlertDialog, {
3584
+ open: deleteDialogOpen,
3585
+ onOpenChange: setDeleteDialogOpen,
3586
+ children: [/* @__PURE__ */ jsxs(ActionBarItem, {
3587
+ variant: "destructive",
3588
+ onClick: () => setDeleteDialogOpen(true),
3589
+ onSelect: (event) => event.preventDefault(),
3590
+ children: [/* @__PURE__ */ jsx(Trash2, {}), action$1.label ?? "Delete"]
3591
+ }), /* @__PURE__ */ jsxs(AlertDialogContent, { children: [/* @__PURE__ */ jsxs(AlertDialogHeader, { children: [/* @__PURE__ */ jsx(AlertDialogTitle, { children: deleteConfirmation.title }), /* @__PURE__ */ jsx(AlertDialogDescription, { children: deleteConfirmation.description(selectedRows.length) })] }), /* @__PURE__ */ jsxs(AlertDialogFooter, { children: [/* @__PURE__ */ jsx(AlertDialogCancel, { children: deleteConfirmation.cancel }), /* @__PURE__ */ jsx(AlertDialogAction, {
3592
+ className: "bg-destructive/10 text-destructive hover:bg-destructive/20 focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:hover:bg-destructive/30",
3593
+ disabled: selectedRows.length === 0,
3594
+ onClick: (event) => runDeleteAction(action$1, event),
3595
+ children: deleteConfirmation.confirm
3596
+ })] })] })]
4533
3597
  }, "delete");
4534
3598
  return null;
4535
3599
  }, [
@@ -4537,21 +3601,24 @@ function AutoTableActionBar({ table, onDeleteSelected, onUpdateSelected, batchUp
4537
3601
  batchUpdateFields?.length,
4538
3602
  enableDelete,
4539
3603
  enableExport,
3604
+ deleteConfirmation,
3605
+ deleteDialogOpen,
4540
3606
  onDelete,
4541
3607
  onDeleteSelected,
4542
3608
  onUpdateSelected,
4543
3609
  onExport,
4544
3610
  renderBatchUpdate,
4545
3611
  renderComponent,
3612
+ runDeleteAction,
4546
3613
  selectedRows
4547
3614
  ]);
4548
- const renderCustomAction = React.useCallback((action, index$1) => {
4549
- if (action.component) return /* @__PURE__ */ jsx(React.Fragment, { children: renderComponent(action.component) }, `custom-${index$1}`);
4550
- if (!action.label || !action.onClick) return null;
3615
+ const renderCustomAction = React.useCallback((action$1, index$1) => {
3616
+ if (action$1.component) return /* @__PURE__ */ jsx(React.Fragment, { children: renderComponent(action$1.component) }, `custom-${index$1}`);
3617
+ if (!action$1.label || !action$1.onClick) return null;
4551
3618
  return /* @__PURE__ */ jsx(ActionBarItem, {
4552
- variant: action.variant === "destructive" ? "destructive" : "secondary",
4553
- onClick: (event) => action.onClick?.(selectedRows, actionContext, event),
4554
- children: action.label
3619
+ variant: action$1.variant === "destructive" ? "destructive" : "secondary",
3620
+ onClick: (event) => action$1.onClick?.(selectedRows, actionContext, event),
3621
+ children: action$1.label
4555
3622
  }, `custom-${index$1}`);
4556
3623
  }, [
4557
3624
  actionContext,
@@ -4564,7 +3631,7 @@ function AutoTableActionBar({ table, onDeleteSelected, onUpdateSelected, batchUp
4564
3631
  { type: "export" },
4565
3632
  { type: "delete" }
4566
3633
  ]) : actions;
4567
- const resolvedActions = configuredActions?.filter((action) => !action.hidden);
3634
+ const resolvedActions = configuredActions?.filter((action$1) => !action$1.hidden);
4568
3635
  if (configuredActions === void 0) return /* @__PURE__ */ jsxs(Fragment, { children: [
4569
3636
  renderBuiltinAction({ type: "batchUpdate" }),
4570
3637
  extraActions,
@@ -4572,9 +3639,9 @@ function AutoTableActionBar({ table, onDeleteSelected, onUpdateSelected, batchUp
4572
3639
  renderBuiltinAction({ type: "delete" })
4573
3640
  ] });
4574
3641
  if (!resolvedActions || resolvedActions.length === 0) return null;
4575
- if (!resolvedActions.some((action) => action.type !== "custom")) {
4576
- const startNodes = resolvedActions.filter(isBatchCustomAction).filter((action) => action.position === "start").map((action, index$1) => renderCustomAction(action, index$1));
4577
- const endNodes = resolvedActions.filter(isBatchCustomAction).filter((action) => action.position !== "start").map((action, index$1) => renderCustomAction(action, index$1));
3642
+ if (!resolvedActions.some((action$1) => action$1.type !== "custom")) {
3643
+ const startNodes = resolvedActions.filter(isBatchCustomAction).filter((action$1) => action$1.position === "start").map((action$1, index$1) => renderCustomAction(action$1, index$1));
3644
+ const endNodes = resolvedActions.filter(isBatchCustomAction).filter((action$1) => action$1.position !== "start").map((action$1, index$1) => renderCustomAction(action$1, index$1));
4578
3645
  return /* @__PURE__ */ jsxs(Fragment, { children: [
4579
3646
  startNodes,
4580
3647
  renderBuiltinAction({ type: "batchUpdate" }),
@@ -4584,9 +3651,9 @@ function AutoTableActionBar({ table, onDeleteSelected, onUpdateSelected, batchUp
4584
3651
  endNodes
4585
3652
  ] });
4586
3653
  }
4587
- return resolvedActions.map((action, index$1) => {
4588
- if (action.type === "custom") return renderCustomAction(action, index$1);
4589
- return /* @__PURE__ */ jsx(React.Fragment, { children: renderBuiltinAction(action) }, action.type);
3654
+ return resolvedActions.map((action$1, index$1) => {
3655
+ if (action$1.type === "custom") return renderCustomAction(action$1, index$1);
3656
+ return /* @__PURE__ */ jsx(React.Fragment, { children: renderBuiltinAction(action$1) }, action$1.type);
4590
3657
  });
4591
3658
  }, [
4592
3659
  actions,
@@ -4870,7 +3937,7 @@ const filterModeConfig = {
4870
3937
  tooltip: "Linear-style command filters"
4871
3938
  }
4872
3939
  };
4873
- 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, onSelectedRowsChange, getSelectedRows }) {
3940
+ function AutoTable({ schema, data, pageCount = 1, overrides, enableRowSelection = true, exclude, actions, pinnedColumns, filterMode = "simple", search = false, onDeleteSelected, onUpdateSelected, batchUpdateFields, actionBarExtra, actionBarActions, deleteConfirmation, initialSorting, enableExport = true, showDefaultExport = true, onSelectedCountChange, onSelectedRowsChange, getSelectedRows }) {
4874
3941
  const modes = filterMode ? Array.isArray(filterMode) ? filterMode : [filterMode] : DEFAULT_MODES;
4875
3942
  const [currentMode, setCurrentMode] = useQueryState("filterMode", parseAsStringEnum(modes).withDefault(modes[0] ?? "simple"));
4876
3943
  const showToggle = modes.length > 1;
@@ -5047,7 +4114,8 @@ function AutoTable({ schema, data, pageCount = 1, overrides, enableRowSelection
5047
4114
  enableExport,
5048
4115
  showDefaultExport,
5049
4116
  extraActions: actionBarExtra,
5050
- actions: actionBarActions
4117
+ actions: actionBarActions,
4118
+ deleteConfirmation
5051
4119
  })
5052
4120
  ]
5053
4121
  });
@@ -5249,11 +4317,19 @@ function normalizeDataSourceRegistration(registration) {
5249
4317
  if (typeof registration === "function") return {
5250
4318
  dependencies: [],
5251
4319
  reset: false,
4320
+ search: false,
4321
+ debounceMs: 300,
4322
+ loadMore: false,
4323
+ pageSize: 20,
5252
4324
  load: registration
5253
4325
  };
5254
4326
  return {
5255
4327
  dependencies: Array.isArray(registration.dependencies) ? registration.dependencies : [],
5256
4328
  reset: registration.reset === true,
4329
+ search: registration.search === true,
4330
+ debounceMs: typeof registration.debounceMs === "number" && registration.debounceMs >= 0 ? registration.debounceMs : 300,
4331
+ loadMore: registration.loadMore === true,
4332
+ pageSize: typeof registration.pageSize === "number" && registration.pageSize > 0 ? registration.pageSize : 20,
5257
4333
  load: registration.load
5258
4334
  };
5259
4335
  }
@@ -5280,10 +4356,63 @@ function normalizeOptions(result) {
5280
4356
  value: String(option.value)
5281
4357
  }));
5282
4358
  }
4359
+ function normalizeHasMore(result) {
4360
+ return !Array.isArray(result) && result?.hasMore === true;
4361
+ }
4362
+
4363
+ //#endregion
4364
+ //#region src/lib/field-config.ts
4365
+ function normalizeFieldOptions$1(options) {
4366
+ if (!options || options.length === 0) return void 0;
4367
+ return options.map((option) => ({
4368
+ ...option,
4369
+ value: String(option.value)
4370
+ }));
4371
+ }
4372
+ /**
4373
+ * 从统一字段配置生成 Formily overrides。
4374
+ * `legacyOverrides` 保持兼容旧 API,字段级 `form` 配置优先级最高。
4375
+ */
4376
+ function buildFormOverrides(fields, legacyOverrides, denyFields) {
4377
+ const result = { ...legacyOverrides };
4378
+ if (denyFields) for (const field of denyFields) result[field] = {
4379
+ ...result[field],
4380
+ "x-hidden": true
4381
+ };
4382
+ if (fields) for (const [key, config] of Object.entries(fields)) {
4383
+ const fieldOptions = normalizeFieldOptions$1((config.form && typeof config.form === "object" ? config.form : void 0)?.enum) ?? normalizeFieldOptions$1(config.enum);
4384
+ const fieldDataSource = fieldOptions || !config.dataSource ? void 0 : normalizeDataSourceConfig(config.dataSource);
4385
+ if (config.label) result[key] = {
4386
+ ...result[key],
4387
+ title: config.label
4388
+ };
4389
+ if (config.hidden) result[key] = {
4390
+ ...result[key],
4391
+ "x-hidden": true
4392
+ };
4393
+ if (fieldOptions) result[key] = {
4394
+ ...result[key],
4395
+ enum: fieldOptions
4396
+ };
4397
+ if (fieldDataSource) result[key] = {
4398
+ ...result[key],
4399
+ "x-data-source": config.dataSource
4400
+ };
4401
+ if (config.form === false) result[key] = {
4402
+ ...result[key],
4403
+ "x-hidden": true
4404
+ };
4405
+ else if (config.form && typeof config.form === "object") result[key] = {
4406
+ ...result[key],
4407
+ ...config.form
4408
+ };
4409
+ }
4410
+ return result;
4411
+ }
5283
4412
 
5284
4413
  //#endregion
5285
4414
  //#region src/components/auto-crud/auto-form.tsx
5286
- const COMBOBOX_LIST_CLASS = "[&_[cmdk-list]]:max-h-[360px] [&_[cmdk-list]]:overflow-x-hidden [&_[cmdk-list]]:overflow-y-auto";
4415
+ const COMBOBOX_LIST_CLASS = "[&_[data-multi-combobox-viewport]]:max-h-[360px] [&_[data-multi-combobox-viewport]]:overflow-x-hidden [&_[data-multi-combobox-viewport]]:overflow-y-auto";
5287
4416
  const FormilySwitch = connect(Switch, mapProps({
5288
4417
  value: "checked",
5289
4418
  onInput: "onCheckedChange"
@@ -5293,25 +4422,27 @@ const FormilyMultiCombobox = connect(AutoCrudMultiCombobox, mapProps({
5293
4422
  dataSource: "options",
5294
4423
  onInput: "onChange"
5295
4424
  }, (props, field) => {
5296
- const fieldValue = field.value;
4425
+ const fieldValue = field && typeof field === "object" && "value" in field ? field.value : props.value;
5297
4426
  return {
5298
4427
  ...props,
5299
4428
  value: Array.isArray(fieldValue) ? fieldValue.map(String) : []
5300
4429
  };
5301
4430
  }));
5302
4431
  function AutoCrudCombobox({ options = [], className, value, onChange,...props }) {
5303
- return /* @__PURE__ */ jsx(MultiCombobox, {
4432
+ return /* @__PURE__ */ jsx(Select$2, {
5304
4433
  ...props,
5305
- value: value ? [value] : [],
4434
+ mode: "searchable",
4435
+ value: value ?? "",
5306
4436
  options,
5307
- selectionMode: "single",
5308
- onChange: (nextValues) => onChange?.(nextValues[0] ?? ""),
4437
+ onChange: (nextValue) => onChange?.(nextValue),
5309
4438
  className: cn$1(COMBOBOX_LIST_CLASS, className)
5310
4439
  });
5311
4440
  }
5312
4441
  function AutoCrudMultiCombobox({ options = [],...props }) {
5313
- return /* @__PURE__ */ jsx(MultiCombobox, {
4442
+ return /* @__PURE__ */ jsx(Select$2, {
5314
4443
  ...props,
4444
+ mode: "searchable",
4445
+ multiple: true,
5315
4446
  options
5316
4447
  });
5317
4448
  }
@@ -5329,99 +4460,280 @@ const defaultFieldComponents = {
5329
4460
  decorator: "FormItem"
5330
4461
  }
5331
4462
  };
5332
- function useRegistryVersion$1(subscribe) {
4463
+ const AUTO_CRUD_DATA_SOURCE_SCOPE_KEY = "$autoCrudDataSource";
4464
+ const POPUP_SCROLL_LOAD_THRESHOLD$1 = 24;
4465
+ const dataSourceReactionStates = /* @__PURE__ */ new WeakMap();
4466
+ function createBoundAction(callback) {
4467
+ return action.bound?.(callback) ?? ((...args) => action(() => callback(...args)));
4468
+ }
4469
+ function useRegistryVersion$1(subscribe, onChange) {
5333
4470
  const [version$1, setVersion] = useState(0);
5334
- useEffect(() => subscribe(() => setVersion((current) => current + 1)), [subscribe]);
4471
+ useEffect(() => subscribe(() => {
4472
+ onChange?.();
4473
+ setVersion((current) => current + 1);
4474
+ }), [onChange, subscribe]);
5335
4475
  return version$1;
5336
4476
  }
5337
4477
  function isDataSourceConfig(value) {
5338
4478
  return typeof value === "string" && value.length > 0;
5339
4479
  }
5340
- function collectDataSourceConfigs(schema, result = {}) {
4480
+ function getSchemaDataSourceConfig(schema) {
4481
+ const customDataSource = schema["x-data-source"];
4482
+ const schemaDataSource = schema.dataSource;
4483
+ return isDataSourceConfig(customDataSource) ? customDataSource : isDataSourceConfig(schemaDataSource) ? schemaDataSource : void 0;
4484
+ }
4485
+ function mergeReactions(existing, reaction) {
4486
+ if (existing == null) return reaction;
4487
+ if (existing === reaction) return existing;
4488
+ if (Array.isArray(existing)) return existing.includes(reaction) ? existing : [...existing, reaction];
4489
+ return [existing, reaction];
4490
+ }
4491
+ function applyDataSourceReactions(schema) {
4492
+ const dataSourceConfig = getSchemaDataSourceConfig(schema);
4493
+ if (dataSourceConfig) {
4494
+ const reaction = `{{${AUTO_CRUD_DATA_SOURCE_SCOPE_KEY}(${JSON.stringify(dataSourceConfig)})}}`;
4495
+ schema["x-reactions"] = mergeReactions(schema["x-reactions"], reaction);
4496
+ }
5341
4497
  const properties = schema.properties;
5342
- if (!properties || typeof properties !== "object") return result;
5343
- for (const [key, property] of Object.entries(properties)) {
5344
- if (!property || typeof property !== "object" || Array.isArray(property)) continue;
5345
- const record = property;
5346
- const customDataSource = record["x-data-source"];
5347
- const schemaDataSource = record.dataSource;
5348
- const dataSourceConfig = isDataSourceConfig(customDataSource) ? customDataSource : isDataSourceConfig(schemaDataSource) ? schemaDataSource : void 0;
5349
- if (dataSourceConfig) result[key] = dataSourceConfig;
5350
- collectDataSourceConfigs(record, result);
4498
+ if (properties && typeof properties === "object" && !Array.isArray(properties)) {
4499
+ for (const property of Object.values(properties)) if (property && typeof property === "object" && !Array.isArray(property)) applyDataSourceReactions(property);
5351
4500
  }
5352
- return result;
4501
+ const items = schema.items;
4502
+ if (Array.isArray(items)) {
4503
+ for (const item of items) if (item && typeof item === "object") applyDataSourceReactions(item);
4504
+ } else if (items && typeof items === "object") applyDataSourceReactions(items);
4505
+ }
4506
+ function getDataSourceReactionState(field) {
4507
+ const existing = dataSourceReactionStates.get(field);
4508
+ if (existing) return existing;
4509
+ const next = {
4510
+ hasMore: false,
4511
+ initialized: false,
4512
+ appendLoading: false,
4513
+ options: [],
4514
+ page: 0,
4515
+ requestVersion: 0
4516
+ };
4517
+ dataSourceReactionStates.set(field, next);
4518
+ return next;
4519
+ }
4520
+ function mergeDataSourceOptions(current, incoming) {
4521
+ const optionsByValue = new Map(current.map((option) => [option.value, option]));
4522
+ for (const option of incoming) optionsByValue.set(option.value, option);
4523
+ return Array.from(optionsByValue.values());
4524
+ }
4525
+ function isNearPopupScrollBottom$1(target) {
4526
+ return target.scrollHeight - target.scrollTop - target.clientHeight <= POPUP_SCROLL_LOAD_THRESHOLD$1;
4527
+ }
4528
+ function getDataSourceDependencyValues(entry, field) {
4529
+ return Object.fromEntries(entry.dependencies.map((dependency) => [dependency, field.form.getValuesIn(dependency)]));
4530
+ }
4531
+ function loadAutoCrudDataSource({ entry, field, registryVersion, search, sourceKey, state, append = false }) {
4532
+ const values = getDataSourceDependencyValues(entry, field);
4533
+ const dependencyKey = JSON.stringify(values);
4534
+ const searchValue = entry.search ? search ?? "" : void 0;
4535
+ const page = entry.loadMore ? append ? state.page + 1 : 1 : void 0;
4536
+ const loadKey = JSON.stringify({
4537
+ values,
4538
+ search: searchValue
4539
+ });
4540
+ if (!append && state.loadKey === loadKey && state.registryVersion === registryVersion) return;
4541
+ if (entry.reset && state.dependencyKey !== void 0 && state.dependencyKey !== dependencyKey) field.setValue(void 0);
4542
+ state.dependencyKey = dependencyKey;
4543
+ if (!append) {
4544
+ state.loadKey = loadKey;
4545
+ state.options = [];
4546
+ state.page = 0;
4547
+ state.hasMore = false;
4548
+ }
4549
+ state.registryVersion = registryVersion;
4550
+ state.controller?.abort();
4551
+ const requestVersion = state.requestVersion + 1;
4552
+ state.requestVersion = requestVersion;
4553
+ const controller = typeof AbortController === "undefined" ? void 0 : new AbortController();
4554
+ state.controller = controller;
4555
+ if (append) {
4556
+ state.appendLoading = true;
4557
+ field.setComponentProps({ loading: true });
4558
+ } else {
4559
+ field.setLoading(true);
4560
+ field.setComponentProps({
4561
+ loading: true,
4562
+ ...entry.loadMore ? { hasMore: false } : {}
4563
+ });
4564
+ }
4565
+ Promise.resolve(entry.load({
4566
+ field: field.path.toString(),
4567
+ page,
4568
+ pageSize: entry.loadMore ? entry.pageSize : void 0,
4569
+ search: searchValue,
4570
+ values,
4571
+ signal: controller?.signal
4572
+ })).then(createBoundAction((result) => {
4573
+ if (dataSourceReactionStates.get(field)?.requestVersion !== requestVersion || controller?.signal.aborted) return;
4574
+ const options = normalizeOptions(result);
4575
+ const nextOptions = append ? mergeDataSourceOptions(state.options, options) : options;
4576
+ state.options = nextOptions;
4577
+ state.page = page ?? 0;
4578
+ state.hasMore = entry.loadMore ? normalizeHasMore(result) : false;
4579
+ state.appendLoading = false;
4580
+ field.setDataSource(nextOptions);
4581
+ field.setLoading(false);
4582
+ field.setComponentProps({
4583
+ loading: false,
4584
+ ...entry.loadMore ? { hasMore: state.hasMore } : {}
4585
+ });
4586
+ }), createBoundAction((error) => {
4587
+ if (dataSourceReactionStates.get(field)?.requestVersion !== requestVersion || controller?.signal.aborted) return;
4588
+ console.warn(`[AutoCrud] Failed to load data source "${sourceKey}".`, error);
4589
+ if (!append) {
4590
+ state.options = [];
4591
+ state.hasMore = false;
4592
+ field.setDataSource([]);
4593
+ }
4594
+ state.appendLoading = false;
4595
+ field.setLoading(false);
4596
+ field.setComponentProps({
4597
+ loading: false,
4598
+ ...entry.loadMore ? { hasMore: state.hasMore } : {}
4599
+ });
4600
+ }));
5353
4601
  }
5354
- function useFormDataSources(form, schema) {
5355
- useEffect(() => {
5356
- const sourceEntries = Object.entries(collectDataSourceConfigs(schema)).map(([fieldName, config]) => {
5357
- const normalized = normalizeDataSourceConfig(config);
5358
- return normalized ? [fieldName, normalized] : void 0;
5359
- }).filter((entry) => entry !== void 0);
5360
- if (sourceEntries.length === 0) return;
5361
- let active = true;
5362
- const effectId = Symbol("autoCrudDataSources");
5363
- const loadVersions = /* @__PURE__ */ new Map();
5364
- const controller = typeof AbortController === "undefined" ? void 0 : new AbortController();
5365
- const loadFieldOptions = async (fieldName, source) => {
5366
- const version$1 = (loadVersions.get(fieldName) ?? 0) + 1;
5367
- loadVersions.set(fieldName, version$1);
5368
- const entry = dataSources.get(source.key);
5369
- if (!entry) {
5370
- form.setFieldState(fieldName, (state) => {
5371
- state.dataSource = [];
5372
- });
5373
- return;
5374
- }
5375
- try {
5376
- const options = normalizeOptions(await entry.load({
5377
- field: fieldName,
5378
- values: Object.fromEntries(entry.dependencies.map((dependency) => [dependency, form.getValuesIn(dependency)])),
5379
- signal: controller?.signal
5380
- }));
5381
- if (!active || loadVersions.get(fieldName) !== version$1) return;
5382
- form.setFieldState(fieldName, (state) => {
5383
- state.dataSource = options;
4602
+ function applySearchComponentProps({ entry, field, registryState, sourceKey, state }) {
4603
+ if (!entry.search) return;
4604
+ field.setComponentProps({
4605
+ searchValue: state.search ?? "",
4606
+ shouldFilter: false,
4607
+ onSearch: (search) => {
4608
+ if (state.search === search) return;
4609
+ state.search = search;
4610
+ field.setComponentProps({ searchValue: search });
4611
+ if (state.searchTimer) clearTimeout(state.searchTimer);
4612
+ state.searchTimer = setTimeout(() => {
4613
+ const currentEntry = dataSources.get(sourceKey);
4614
+ if (!currentEntry) {
4615
+ state.controller?.abort();
4616
+ field.setDataSource([]);
4617
+ field.setLoading(false);
4618
+ field.setComponentProps({ loading: false });
4619
+ return;
4620
+ }
4621
+ loadAutoCrudDataSource({
4622
+ entry: currentEntry,
4623
+ field,
4624
+ registryVersion: registryState.version,
4625
+ search,
4626
+ sourceKey,
4627
+ state
5384
4628
  });
5385
- } catch (error) {
5386
- if (!active || controller?.signal.aborted) return;
5387
- console.warn(`[AutoCrud] Failed to load data source "${source.key}".`, error);
5388
- form.setFieldState(fieldName, (state) => {
5389
- state.dataSource = [];
4629
+ }, entry.debounceMs);
4630
+ }
4631
+ });
4632
+ }
4633
+ function applyLoadMoreComponentProps({ entry, field, registryState, sourceKey, state }) {
4634
+ if (!entry.loadMore) return;
4635
+ field.setComponentProps({
4636
+ hasMore: state.hasMore,
4637
+ loading: state.appendLoading,
4638
+ onPopupScroll: (event) => {
4639
+ if (!isNearPopupScrollBottom$1(event.currentTarget)) return;
4640
+ if (!state.hasMore || state.appendLoading) return;
4641
+ const currentEntry = dataSources.get(sourceKey);
4642
+ if (!currentEntry) {
4643
+ state.controller?.abort();
4644
+ state.hasMore = false;
4645
+ state.appendLoading = false;
4646
+ field.setDataSource([]);
4647
+ field.setLoading(false);
4648
+ field.setComponentProps({
4649
+ hasMore: false,
4650
+ loading: false
5390
4651
  });
4652
+ return;
5391
4653
  }
5392
- };
5393
- for (const [fieldName, source] of sourceEntries) loadFieldOptions(fieldName, source);
5394
- form.addEffects(effectId, () => {
5395
- for (const dependency of new Set(sourceEntries.flatMap(([, source]) => dataSources.get(source.key)?.dependencies ?? []))) onFieldValueChange(dependency, () => {
5396
- for (const [fieldName, source] of sourceEntries) {
5397
- const entry = dataSources.get(source.key);
5398
- if (!entry?.dependencies.includes(dependency)) continue;
5399
- if (entry.reset) form.setValuesIn(fieldName, void 0);
5400
- loadFieldOptions(fieldName, source);
5401
- }
4654
+ loadAutoCrudDataSource({
4655
+ append: true,
4656
+ entry: currentEntry,
4657
+ field,
4658
+ registryVersion: registryState.version,
4659
+ search: state.search,
4660
+ sourceKey,
4661
+ state
4662
+ });
4663
+ }
4664
+ });
4665
+ }
4666
+ function createAutoCrudDataSourceReaction(config, registryState) {
4667
+ const source = normalizeDataSourceConfig(config);
4668
+ return (field) => {
4669
+ const registryVersion = registryState.version;
4670
+ const state = getDataSourceReactionState(field);
4671
+ if (!state.initialized) {
4672
+ field.disposers.push(() => {
4673
+ state.controller?.abort();
4674
+ if (state.searchTimer) clearTimeout(state.searchTimer);
5402
4675
  });
4676
+ state.initialized = true;
4677
+ }
4678
+ if (!source) {
4679
+ field.setDataSource([]);
4680
+ field.setLoading(false);
4681
+ return;
4682
+ }
4683
+ const entry = dataSources.get(source.key);
4684
+ if (!entry) {
4685
+ state.controller?.abort();
4686
+ field.setDataSource([]);
4687
+ field.setLoading(false);
4688
+ return;
4689
+ }
4690
+ applySearchComponentProps({
4691
+ entry,
4692
+ field,
4693
+ registryState,
4694
+ sourceKey: source.key,
4695
+ state
5403
4696
  });
5404
- return () => {
5405
- active = false;
5406
- controller?.abort();
5407
- form.removeEffects(effectId);
5408
- };
5409
- }, [
5410
- form,
5411
- schema,
5412
- useRegistryVersion$1(dataSources.subscribe)
5413
- ]);
4697
+ applyLoadMoreComponentProps({
4698
+ entry,
4699
+ field,
4700
+ registryState,
4701
+ sourceKey: source.key,
4702
+ state
4703
+ });
4704
+ loadAutoCrudDataSource({
4705
+ entry,
4706
+ field,
4707
+ registryVersion,
4708
+ search: state.search,
4709
+ sourceKey: source.key,
4710
+ state
4711
+ });
4712
+ };
5414
4713
  }
5415
- function AutoFormInner({ schema: zodSchema, initialValues, onSubmit, overrides, mode = "create", loading = false, gridColumns = 1, labelAlign = "top", labelWidth, showSubmitButton = true }, ref) {
4714
+ function AutoFormInner({ schema: zodSchema, initialValues, onSubmit, fields, overrides, scope: scopeProp, mode = "create", loading = false, gridColumns = 1, labelAlign = "top", labelWidth, showSubmitButton = true }, ref) {
5416
4715
  const form = useMemo(() => createForm({ initialValues }), [JSON.stringify(initialValues)]);
5417
- const formSchema = useMemo(() => createEditFormSchema(zodSchema, {
5418
- overrides,
5419
- layout: "grid",
5420
- gridColumns
5421
- }), [
4716
+ const dataSourceRegistryState = useMemo(() => observable({ version: 0 }), []);
4717
+ const updateDataSourceRegistryState = useCallback(() => {
4718
+ action(() => {
4719
+ dataSourceRegistryState.version += 1;
4720
+ });
4721
+ }, [dataSourceRegistryState]);
4722
+ const dataSourceRegistryVersion = useRegistryVersion$1(dataSources.subscribe, updateDataSourceRegistryState);
4723
+ const formOverrides = useMemo(() => buildFormOverrides(fields, overrides), [fields, overrides]);
4724
+ const formSchema = useMemo(() => {
4725
+ const nextSchema = createEditFormSchema(zodSchema, {
4726
+ overrides: formOverrides,
4727
+ layout: "grid",
4728
+ gridColumns
4729
+ });
4730
+ applyDataSourceReactions(nextSchema);
4731
+ return nextSchema;
4732
+ }, [
5422
4733
  zodSchema,
5423
- overrides,
5424
- gridColumns
4734
+ formOverrides,
4735
+ gridColumns,
4736
+ dataSourceRegistryVersion
5425
4737
  ]);
5426
4738
  const formLayout = useMemo(() => {
5427
4739
  const inlineLabel = labelAlign === "left" || labelAlign === "right";
@@ -5437,7 +4749,10 @@ function AutoFormInner({ schema: zodSchema, initialValues, onSubmit, overrides,
5437
4749
  ...defaultFieldComponents,
5438
4750
  ...components.all()
5439
4751
  } }), [useRegistryVersion$1(components.subscribe)]);
5440
- useFormDataSources(form, formSchema);
4752
+ const scope = useMemo(() => ({
4753
+ ...scopeProp,
4754
+ [AUTO_CRUD_DATA_SOURCE_SCOPE_KEY]: (config) => createAutoCrudDataSourceReaction(config, dataSourceRegistryState)
4755
+ }), [dataSourceRegistryState, scopeProp]);
5441
4756
  const handleSubmit = async () => {
5442
4757
  await form.validate();
5443
4758
  if (form.valid) await onSubmit(form.values);
@@ -5448,7 +4763,8 @@ function AutoFormInner({ schema: zodSchema, initialValues, onSubmit, overrides,
5448
4763
  layout: formLayout,
5449
4764
  children: [/* @__PURE__ */ jsx(JsonSchemaField, {
5450
4765
  schema: formSchema,
5451
- components: fieldComponents
4766
+ components: fieldComponents,
4767
+ scope
5452
4768
  }), showSubmitButton && /* @__PURE__ */ jsx("div", {
5453
4769
  className: "flex justify-end gap-2 mt-6",
5454
4770
  children: /* @__PURE__ */ jsx(Button, {
@@ -5466,6 +4782,7 @@ const AutoForm = forwardRef(AutoFormInner);
5466
4782
  //#region src/i18n/locale.ts
5467
4783
  const zhCN = {
5468
4784
  toolbar: {
4785
+ refresh: "刷新",
5469
4786
  create: "新建",
5470
4787
  import: "导入",
5471
4788
  export: "导出",
@@ -5497,6 +4814,12 @@ const zhCN = {
5497
4814
  confirm: "确认删除",
5498
4815
  confirming: "删除中..."
5499
4816
  },
4817
+ bulkDeleteModal: {
4818
+ title: "确认批量删除",
4819
+ description: (count) => `此操作无法撤销。确定要删除选中的 ${count} 条记录吗?`,
4820
+ cancel: "取消",
4821
+ confirm: "确认删除"
4822
+ },
5500
4823
  importDialog: {
5501
4824
  title: "导入数据",
5502
4825
  uploadDescription: "上传 CSV 或 JSON 文件以批量导入数据",
@@ -5526,6 +4849,7 @@ const zhCN = {
5526
4849
  };
5527
4850
  const enUS = {
5528
4851
  toolbar: {
4852
+ refresh: "Refresh",
5529
4853
  create: "New",
5530
4854
  import: "Import",
5531
4855
  export: "Export",
@@ -5557,6 +4881,12 @@ const enUS = {
5557
4881
  confirm: "Delete",
5558
4882
  confirming: "Deleting..."
5559
4883
  },
4884
+ bulkDeleteModal: {
4885
+ title: "Confirm Bulk Delete",
4886
+ description: (count) => `This action cannot be undone. Are you sure you want to delete ${count} selected ${count === 1 ? "record" : "records"}?`,
4887
+ cancel: "Cancel",
4888
+ confirm: "Delete"
4889
+ },
5560
4890
  importDialog: {
5561
4891
  title: "Import Data",
5562
4892
  uploadDescription: "Upload a CSV or JSON file to bulk import data",
@@ -5586,6 +4916,7 @@ const enUS = {
5586
4916
  };
5587
4917
  const jaJP = {
5588
4918
  toolbar: {
4919
+ refresh: "更新",
5589
4920
  create: "新規作成",
5590
4921
  import: "インポート",
5591
4922
  export: "エクスポート",
@@ -5617,6 +4948,12 @@ const jaJP = {
5617
4948
  confirm: "削除する",
5618
4949
  confirming: "削除中..."
5619
4950
  },
4951
+ bulkDeleteModal: {
4952
+ title: "一括削除の確認",
4953
+ description: (count) => `この操作は元に戻せません。選択した${count}件のレコードを削除してもよろしいですか?`,
4954
+ cancel: "キャンセル",
4955
+ confirm: "削除する"
4956
+ },
5620
4957
  importDialog: {
5621
4958
  title: "データのインポート",
5622
4959
  uploadDescription: "CSVまたはJSONファイルをアップロードして一括インポート",
@@ -5646,6 +4983,7 @@ const jaJP = {
5646
4983
  };
5647
4984
  const koKR = {
5648
4985
  toolbar: {
4986
+ refresh: "새로고침",
5649
4987
  create: "새로 만들기",
5650
4988
  import: "가져오기",
5651
4989
  export: "내보내기",
@@ -5677,6 +5015,12 @@ const koKR = {
5677
5015
  confirm: "삭제",
5678
5016
  confirming: "삭제 중..."
5679
5017
  },
5018
+ bulkDeleteModal: {
5019
+ title: "일괄 삭제 확인",
5020
+ description: (count) => `이 작업은 취소할 수 없습니다. 선택한 ${count}개 레코드를 삭제하시겠습니까?`,
5021
+ cancel: "취소",
5022
+ confirm: "삭제"
5023
+ },
5680
5024
  importDialog: {
5681
5025
  title: "데이터 가져오기",
5682
5026
  uploadDescription: "CSV 또는 JSON 파일을 업로드하여 일괄 가져오기",
@@ -5706,6 +5050,7 @@ const koKR = {
5706
5050
  };
5707
5051
  const frFR = {
5708
5052
  toolbar: {
5053
+ refresh: "Actualiser",
5709
5054
  create: "Nouveau",
5710
5055
  import: "Importer",
5711
5056
  export: "Exporter",
@@ -5737,6 +5082,12 @@ const frFR = {
5737
5082
  confirm: "Supprimer",
5738
5083
  confirming: "Suppression..."
5739
5084
  },
5085
+ bulkDeleteModal: {
5086
+ title: "Confirmer la suppression en masse",
5087
+ description: (count) => `Cette action est irréversible. Voulez-vous vraiment supprimer les ${count} enregistrements sélectionnés ?`,
5088
+ cancel: "Annuler",
5089
+ confirm: "Supprimer"
5090
+ },
5740
5091
  importDialog: {
5741
5092
  title: "Importer des données",
5742
5093
  uploadDescription: "Téléchargez un fichier CSV ou JSON pour importer en masse",
@@ -5766,6 +5117,7 @@ const frFR = {
5766
5117
  };
5767
5118
  const deDE = {
5768
5119
  toolbar: {
5120
+ refresh: "Aktualisieren",
5769
5121
  create: "Neu",
5770
5122
  import: "Importieren",
5771
5123
  export: "Exportieren",
@@ -5797,6 +5149,12 @@ const deDE = {
5797
5149
  confirm: "Löschen",
5798
5150
  confirming: "Wird gelöscht..."
5799
5151
  },
5152
+ bulkDeleteModal: {
5153
+ title: "Mehrfaches Löschen bestätigen",
5154
+ description: (count) => `Diese Aktion kann nicht rückgängig gemacht werden. Möchten Sie die ${count} ausgewählten Datensätze wirklich löschen?`,
5155
+ cancel: "Abbrechen",
5156
+ confirm: "Löschen"
5157
+ },
5800
5158
  importDialog: {
5801
5159
  title: "Daten importieren",
5802
5160
  uploadDescription: "CSV- oder JSON-Datei hochladen, um Daten massenweise zu importieren",
@@ -5826,6 +5184,7 @@ const deDE = {
5826
5184
  };
5827
5185
  const esES = {
5828
5186
  toolbar: {
5187
+ refresh: "Actualizar",
5829
5188
  create: "Nuevo",
5830
5189
  import: "Importar",
5831
5190
  export: "Exportar",
@@ -5857,6 +5216,12 @@ const esES = {
5857
5216
  confirm: "Eliminar",
5858
5217
  confirming: "Eliminando..."
5859
5218
  },
5219
+ bulkDeleteModal: {
5220
+ title: "Confirmar eliminación masiva",
5221
+ description: (count) => `Esta acción no se puede deshacer. ¿Está seguro de que desea eliminar los ${count} registros seleccionados?`,
5222
+ cancel: "Cancelar",
5223
+ confirm: "Eliminar"
5224
+ },
5860
5225
  importDialog: {
5861
5226
  title: "Importar datos",
5862
5227
  uploadDescription: "Suba un archivo CSV o JSON para importar datos de forma masiva",
@@ -5916,7 +5281,7 @@ function resolveLocale(localeProp) {
5916
5281
 
5917
5282
  //#endregion
5918
5283
  //#region src/components/auto-crud/crud-form-modal.tsx
5919
- function CrudFormModal({ open, onOpenChange, mode, schema, initialValues, onSubmit, loading = false, variant = "dialog", overrides, title, locale = zhCN.formModal, gridColumns, labelAlign, labelWidth, className }) {
5284
+ function CrudFormModal({ open, onOpenChange, mode, schema, initialValues, onSubmit, loading = false, variant = "dialog", overrides, scope, title, locale = zhCN.formModal, gridColumns, labelAlign, labelWidth, className }) {
5920
5285
  const defaultTitle = mode === "create" ? locale.createTitle : locale.editTitle;
5921
5286
  const formRef = useRef(null);
5922
5287
  const handleSubmit = async () => {
@@ -5950,6 +5315,7 @@ function CrudFormModal({ open, onOpenChange, mode, schema, initialValues, onSubm
5950
5315
  loading,
5951
5316
  onSubmit,
5952
5317
  overrides,
5318
+ scope,
5953
5319
  gridColumns,
5954
5320
  labelAlign,
5955
5321
  labelWidth,
@@ -6028,7 +5394,7 @@ function ImportDialog({ open, onOpenChange, onImport, columns = [], title, local
6028
5394
  open,
6029
5395
  onOpenChange: handleOpenChange,
6030
5396
  children: /* @__PURE__ */ jsxs(DialogContent, {
6031
- className: "sm:max-w-[600px]",
5397
+ className: "w-[calc(100vw-2rem)] sm:max-w-5xl",
6032
5398
  children: [/* @__PURE__ */ jsxs(DialogHeader, { children: [/* @__PURE__ */ jsx(DialogTitle, { children: title ?? locale.title }), /* @__PURE__ */ jsxs(DialogDescription, { children: [
6033
5399
  step === "upload" && locale.uploadDescription,
6034
5400
  step === "preview" && locale.previewDescription(parsedData?.rows.length ?? 0),
@@ -6089,41 +5455,30 @@ function ImportDialog({ open, onOpenChange, onImport, columns = [], title, local
6089
5455
  /* @__PURE__ */ jsx("div", {
6090
5456
  className: "rounded-md border",
6091
5457
  children: /* @__PURE__ */ jsx("div", {
6092
- className: "max-h-[300px] overflow-auto",
5458
+ className: "max-h-[45vh] overflow-auto",
6093
5459
  children: /* @__PURE__ */ jsxs("table", {
6094
- className: "w-full text-sm",
5460
+ className: "min-w-max text-sm",
6095
5461
  children: [/* @__PURE__ */ jsx("thead", {
6096
5462
  className: "sticky top-0 bg-muted",
6097
- children: /* @__PURE__ */ jsxs("tr", { children: [
6098
- /* @__PURE__ */ jsx("th", {
6099
- className: "px-3 py-2 text-left font-medium text-muted-foreground",
6100
- children: "#"
6101
- }),
6102
- parsedData.headers.slice(0, 6).map((h) => /* @__PURE__ */ jsx("th", {
6103
- className: "px-3 py-2 text-left font-medium text-muted-foreground",
6104
- children: h
6105
- }, h)),
6106
- parsedData.headers.length > 6 && /* @__PURE__ */ jsx("th", {
6107
- className: "px-3 py-2 text-left font-medium text-muted-foreground",
6108
- children: locale.moreColumns(parsedData.headers.length - 6)
6109
- })
6110
- ] })
5463
+ children: /* @__PURE__ */ jsxs("tr", { children: [/* @__PURE__ */ jsx("th", {
5464
+ className: "sticky left-0 z-10 bg-muted px-3 py-2 text-left font-medium text-muted-foreground",
5465
+ children: "#"
5466
+ }), parsedData.headers.map((h) => /* @__PURE__ */ jsx("th", {
5467
+ className: "max-w-[180px] whitespace-nowrap px-3 py-2 text-left font-medium text-muted-foreground",
5468
+ children: h
5469
+ }, h))] })
6111
5470
  }), /* @__PURE__ */ jsx("tbody", { children: parsedData.rows.slice(0, 10).map((row, i) => /* @__PURE__ */ jsxs("tr", {
6112
5471
  className: "border-t",
6113
- children: [
6114
- /* @__PURE__ */ jsx("td", {
6115
- className: "px-3 py-2 text-muted-foreground",
6116
- children: i + 1
6117
- }),
6118
- parsedData.headers.slice(0, 6).map((h) => /* @__PURE__ */ jsx("td", {
6119
- className: "px-3 py-2 max-w-[150px] truncate",
5472
+ children: [/* @__PURE__ */ jsx("td", {
5473
+ className: "sticky left-0 bg-background px-3 py-2 text-muted-foreground",
5474
+ children: i + 1
5475
+ }), parsedData.headers.map((h) => /* @__PURE__ */ jsx("td", {
5476
+ className: "max-w-[180px] truncate px-3 py-2",
5477
+ children: /* @__PURE__ */ jsx("span", {
5478
+ title: String(row[h] ?? ""),
6120
5479
  children: String(row[h] ?? "")
6121
- }, h)),
6122
- parsedData.headers.length > 6 && /* @__PURE__ */ jsx("td", {
6123
- className: "px-3 py-2 text-muted-foreground",
6124
- children: "..."
6125
5480
  })
6126
- ]
5481
+ }, h))]
6127
5482
  }, i)) })]
6128
5483
  })
6129
5484
  })
@@ -6255,12 +5610,12 @@ const listeners = /* @__PURE__ */ new Set();
6255
5610
  let seq = 0;
6256
5611
  let version = 0;
6257
5612
  let notifyScheduled = false;
6258
- function actionKey(registration, action, index$1) {
5613
+ function actionKey(registration, action$1, index$1) {
6259
5614
  return [
6260
5615
  registration.ownerId,
6261
5616
  registration.targetId,
6262
5617
  registration.zone,
6263
- action.id ?? action.type,
5618
+ action$1.id ?? action$1.type,
6264
5619
  index$1
6265
5620
  ].join(":");
6266
5621
  }
@@ -6308,15 +5663,15 @@ function sortEntries(left, right) {
6308
5663
  if (ownerDiff !== 0) return ownerDiff;
6309
5664
  return left.seq - right.seq;
6310
5665
  }
6311
- function withoutRegistryMeta(action) {
6312
- const { id, order,...rest } = action;
5666
+ function withoutRegistryMeta(action$1) {
5667
+ const { id, order,...rest } = action$1;
6313
5668
  return rest;
6314
5669
  }
6315
- function isCustomAction$1(action) {
6316
- return action.type === "custom";
5670
+ function isCustomAction$1(action$1) {
5671
+ return action$1.type === "custom";
6317
5672
  }
6318
5673
  function resolveActions$1(targetId, zone, ownerActions) {
6319
- const baseActions = ownerActions.filter((action) => !action.hidden).map((action) => withoutRegistryMeta(action));
5674
+ const baseActions = ownerActions.filter((action$1) => !action$1.hidden).map((action$1) => withoutRegistryMeta(action$1));
6320
5675
  if (!targetId) return baseActions;
6321
5676
  const registered = crudActions.get(targetId, zone);
6322
5677
  if (registered.length === 0) return baseActions;
@@ -6324,20 +5679,20 @@ function resolveActions$1(targetId, zone, ownerActions) {
6324
5679
  const startCustomActions = [];
6325
5680
  const endCustomActions = [];
6326
5681
  for (const entry of registered) {
6327
- const action = entry.action;
6328
- if (action.hidden && isCustomAction$1(action)) continue;
6329
- if (isCustomAction$1(action)) {
6330
- const custom = withoutRegistryMeta(action);
5682
+ const action$1 = entry.action;
5683
+ if (action$1.hidden && isCustomAction$1(action$1)) continue;
5684
+ if (isCustomAction$1(action$1)) {
5685
+ const custom = withoutRegistryMeta(action$1);
6331
5686
  if (custom.position === "start") startCustomActions.push(custom);
6332
5687
  else endCustomActions.push(custom);
6333
5688
  continue;
6334
5689
  }
6335
- const existingIndex = nextActions.findIndex((item) => item.type === action.type);
6336
- if (action.hidden) {
5690
+ const existingIndex = nextActions.findIndex((item) => item.type === action$1.type);
5691
+ if (action$1.hidden) {
6337
5692
  if (existingIndex >= 0) nextActions.splice(existingIndex, 1);
6338
5693
  continue;
6339
5694
  }
6340
- const builtin = withoutRegistryMeta(action);
5695
+ const builtin = withoutRegistryMeta(action$1);
6341
5696
  if (existingIndex >= 0) nextActions[existingIndex] = {
6342
5697
  ...nextActions[existingIndex],
6343
5698
  ...builtin
@@ -6356,14 +5711,14 @@ const crudActions = {
6356
5711
  targetId: registration.targetId,
6357
5712
  zone: registration.zone
6358
5713
  }, false);
6359
- registration.actions.forEach((action, index$1) => {
6360
- const key = actionKey(registration, action, index$1);
5714
+ registration.actions.forEach((action$1, index$1) => {
5715
+ const key = actionKey(registration, action$1, index$1);
6361
5716
  const entry = {
6362
5717
  targetId: registration.targetId,
6363
5718
  zone: registration.zone,
6364
5719
  ownerId: registration.ownerId,
6365
- action,
6366
- order: action.order ?? 100,
5720
+ action: action$1,
5721
+ order: action$1.order ?? 100,
6367
5722
  seq: seq++
6368
5723
  };
6369
5724
  entries.set(key, entry);
@@ -6402,6 +5757,7 @@ function setToolbarResolver(resolver) {
6402
5757
  }
6403
5758
  function getDefaultToolbarActions() {
6404
5759
  return [
5760
+ { type: "refresh" },
6405
5761
  { type: "import" },
6406
5762
  { type: "export" },
6407
5763
  { type: "create" }
@@ -6418,8 +5774,8 @@ function getDefaultRowActions() {
6418
5774
  function getDefaultBatchActions() {
6419
5775
  return [{ type: "batchUpdate" }, { type: "delete" }];
6420
5776
  }
6421
- function isCustomAction(action) {
6422
- return action.type === "custom";
5777
+ function isCustomAction(action$1) {
5778
+ return action$1.type === "custom";
6423
5779
  }
6424
5780
  function resolveOwnerActions(config, defaults) {
6425
5781
  const resolved = typeof config === "function" ? config([...defaults]) : config;
@@ -6467,7 +5823,7 @@ function haveSameRowSelection(left, right, idKey) {
6467
5823
  return left.every((row, index$1) => row === right[index$1]);
6468
5824
  }
6469
5825
  function readCreateAction(actions) {
6470
- return actions.find((action) => action.type === "create");
5826
+ return actions.find((action$1) => action$1.type === "create");
6471
5827
  }
6472
5828
  function resolveToolbarActionsWithResolver(targetId, ownerActions, context) {
6473
5829
  const resolver = autoCrudToolbarResolver;
@@ -6519,6 +5875,15 @@ function toBatchOptions(options) {
6519
5875
  value
6520
5876
  }));
6521
5877
  }
5878
+ const POPUP_SCROLL_LOAD_THRESHOLD = 24;
5879
+ function isNearPopupScrollBottom(target) {
5880
+ return target.scrollHeight - target.scrollTop - target.clientHeight <= POPUP_SCROLL_LOAD_THRESHOLD;
5881
+ }
5882
+ function mergeFilterOptions(current, incoming) {
5883
+ const optionsByValue = new Map((current ?? []).map((option) => [option.value, option]));
5884
+ for (const option of incoming) optionsByValue.set(option.value, option);
5885
+ return Array.from(optionsByValue.values());
5886
+ }
6522
5887
  function getFilterDataSource(config) {
6523
5888
  if (config.filter && typeof config.filter === "object") return config.filter.dataSource ?? config.dataSource;
6524
5889
  return config.dataSource;
@@ -6542,7 +5907,20 @@ function useCrudActionsVersion() {
6542
5907
  }
6543
5908
  function useDynamicFilterOptions(fields) {
6544
5909
  const registryVersion = useRegistryVersion(dataSources.subscribe);
5910
+ const [searchValues, setSearchValues] = React.useState({});
5911
+ const [hasMoreByField, setHasMoreByField] = React.useState({});
5912
+ const [labelOptionsByField, setLabelOptionsByField] = React.useState({});
5913
+ const [loadingByField, setLoadingByField] = React.useState({});
6545
5914
  const [optionsByField, setOptionsByField] = React.useState({});
5915
+ const controllersRef = React.useRef({});
5916
+ const hasMoreByFieldRef = React.useRef({});
5917
+ const loadingByFieldRef = React.useRef({});
5918
+ const optionsByFieldRef = React.useRef({});
5919
+ const pageByFieldRef = React.useRef({});
5920
+ const requestVersionsRef = React.useRef({});
5921
+ const searchValuesRef = React.useRef({});
5922
+ const sourceEntriesByFieldRef = React.useRef({});
5923
+ const timersRef = React.useRef({});
6546
5924
  const sourceEntries = React.useMemo(() => {
6547
5925
  if (!fields) return [];
6548
5926
  return Object.entries(fields).flatMap(([field, config]) => {
@@ -6554,36 +5932,185 @@ function useDynamicFilterOptions(fields) {
6554
5932
  }] : [];
6555
5933
  });
6556
5934
  }, [fields]);
5935
+ const sourceEntriesByField = React.useMemo(() => Object.fromEntries(sourceEntries.map(({ field, source }) => [field, source])), [sourceEntries]);
6557
5936
  React.useEffect(() => {
6558
- if (sourceEntries.length === 0) {
6559
- setOptionsByField({});
5937
+ searchValuesRef.current = searchValues;
5938
+ }, [searchValues]);
5939
+ React.useEffect(() => {
5940
+ sourceEntriesByFieldRef.current = sourceEntriesByField;
5941
+ }, [sourceEntriesByField]);
5942
+ const registeredByField = React.useMemo(() => Object.fromEntries(sourceEntries.map(({ field, source }) => [field, dataSources.get(source.key) !== void 0])), [sourceEntries, registryVersion]);
5943
+ const searchableByField = React.useMemo(() => Object.fromEntries(sourceEntries.map(({ field, source }) => [field, dataSources.get(source.key)?.search === true])), [sourceEntries, registryVersion]);
5944
+ const setFieldState = React.useCallback((setter, ref, field, value) => {
5945
+ ref.current = {
5946
+ ...ref.current,
5947
+ [field]: value
5948
+ };
5949
+ setter(ref.current);
5950
+ }, []);
5951
+ const removeInactiveFieldState = React.useCallback((setter, ref, activeFields) => {
5952
+ const nextEntries = Object.entries(ref.current).filter(([field]) => activeFields.has(field));
5953
+ if (nextEntries.length === Object.keys(ref.current).length) return;
5954
+ ref.current = Object.fromEntries(nextEntries);
5955
+ setter(ref.current);
5956
+ }, []);
5957
+ const setFieldOptions = React.useCallback((field, options) => {
5958
+ optionsByFieldRef.current = {
5959
+ ...optionsByFieldRef.current,
5960
+ [field]: options
5961
+ };
5962
+ setOptionsByField(optionsByFieldRef.current);
5963
+ }, []);
5964
+ const setFieldLoading = React.useCallback((field, loading) => {
5965
+ setFieldState(setLoadingByField, loadingByFieldRef, field, loading);
5966
+ }, [setFieldState]);
5967
+ const setFieldHasMore = React.useCallback((field, hasMore) => {
5968
+ setFieldState(setHasMoreByField, hasMoreByFieldRef, field, hasMore);
5969
+ }, [setFieldState]);
5970
+ const clearFieldRequest = React.useCallback((field) => {
5971
+ const timer = timersRef.current[field];
5972
+ if (timer) clearTimeout(timer);
5973
+ timersRef.current[field] = void 0;
5974
+ controllersRef.current[field]?.abort();
5975
+ controllersRef.current[field] = void 0;
5976
+ }, []);
5977
+ const resetField = React.useCallback((field, { clearLabels = false } = {}) => {
5978
+ clearFieldRequest(field);
5979
+ setFieldOptions(field, []);
5980
+ setFieldLoading(field, false);
5981
+ setFieldHasMore(field, false);
5982
+ pageByFieldRef.current = {
5983
+ ...pageByFieldRef.current,
5984
+ [field]: 0
5985
+ };
5986
+ if (clearLabels) setLabelOptionsByField((current) => {
5987
+ const { [field]: _,...rest } = current;
5988
+ return rest;
5989
+ });
5990
+ }, [
5991
+ clearFieldRequest,
5992
+ setFieldHasMore,
5993
+ setFieldLoading,
5994
+ setFieldOptions
5995
+ ]);
5996
+ const loadFieldOptions = React.useCallback((field, append = false) => {
5997
+ if (append) {
5998
+ if (loadingByFieldRef.current[field] || !hasMoreByFieldRef.current[field]) return;
5999
+ }
6000
+ const source = sourceEntriesByFieldRef.current[field];
6001
+ const entry = source ? dataSources.get(source.key) : void 0;
6002
+ if (!source || !entry) {
6003
+ resetField(field);
6560
6004
  return;
6561
6005
  }
6562
- let active = true;
6006
+ const requestVersion = (requestVersionsRef.current[field] ?? 0) + 1;
6007
+ requestVersionsRef.current = {
6008
+ ...requestVersionsRef.current,
6009
+ [field]: requestVersion
6010
+ };
6011
+ clearFieldRequest(field);
6563
6012
  const controller = typeof AbortController === "undefined" ? void 0 : new AbortController();
6564
- Promise.all(sourceEntries.map(async ({ field, source }) => {
6565
- const entry = dataSources.get(source.key);
6566
- if (!entry) return [field, []];
6567
- try {
6568
- return [field, normalizeOptions(await entry.load({
6569
- field,
6570
- values: {},
6571
- signal: controller?.signal
6572
- }))];
6573
- } catch (error) {
6574
- if (!controller?.signal.aborted) console.warn(`[AutoCrud] Failed to load filter data source "${source.key}".`, error);
6575
- return [field, []];
6576
- }
6577
- })).then((entries$1) => {
6578
- if (!active) return;
6579
- setOptionsByField(Object.fromEntries(entries$1));
6580
- });
6581
- return () => {
6582
- active = false;
6583
- controller?.abort();
6013
+ controllersRef.current[field] = controller;
6014
+ const search = entry.search ? searchValuesRef.current[field] ?? "" : void 0;
6015
+ const page = entry.loadMore ? append ? (pageByFieldRef.current[field] ?? 0) + 1 : 1 : void 0;
6016
+ setFieldLoading(field, true);
6017
+ if (!append) setFieldHasMore(field, false);
6018
+ const timer = setTimeout(() => {
6019
+ Promise.resolve(entry.load({
6020
+ field,
6021
+ page,
6022
+ pageSize: entry.loadMore ? entry.pageSize : void 0,
6023
+ search,
6024
+ values: {},
6025
+ signal: controller?.signal
6026
+ })).then((result) => {
6027
+ if (requestVersionsRef.current[field] !== requestVersion || controller?.signal.aborted) return;
6028
+ const options = normalizeOptions(result);
6029
+ const nextOptions = append ? mergeFilterOptions(optionsByFieldRef.current[field], options) : options;
6030
+ const hasMore = normalizeHasMore(result);
6031
+ setFieldOptions(field, nextOptions);
6032
+ setLabelOptionsByField((current) => ({
6033
+ ...current,
6034
+ [field]: mergeFilterOptions(current[field], nextOptions)
6035
+ }));
6036
+ pageByFieldRef.current = {
6037
+ ...pageByFieldRef.current,
6038
+ [field]: page ?? 0
6039
+ };
6040
+ setFieldHasMore(field, hasMore);
6041
+ setFieldLoading(field, false);
6042
+ }, (error) => {
6043
+ if (controller?.signal.aborted) return;
6044
+ console.warn(`[AutoCrud] Failed to load filter data source "${source.key}".`, error);
6045
+ if (!append) {
6046
+ setFieldOptions(field, []);
6047
+ setFieldHasMore(field, false);
6048
+ pageByFieldRef.current = {
6049
+ ...pageByFieldRef.current,
6050
+ [field]: 0
6051
+ };
6052
+ }
6053
+ setFieldLoading(field, false);
6054
+ });
6055
+ }, !append && entry.search ? entry.debounceMs : 0);
6056
+ timersRef.current[field] = timer;
6057
+ }, [
6058
+ clearFieldRequest,
6059
+ resetField,
6060
+ setFieldHasMore,
6061
+ setFieldLoading,
6062
+ setFieldOptions
6063
+ ]);
6064
+ const setSearchValue = React.useCallback((field, value) => {
6065
+ searchValuesRef.current = {
6066
+ ...searchValuesRef.current,
6067
+ [field]: value
6584
6068
  };
6585
- }, [sourceEntries, registryVersion]);
6586
- return optionsByField;
6069
+ setSearchValues((current) => current[field] === value ? current : {
6070
+ ...current,
6071
+ [field]: value
6072
+ });
6073
+ }, []);
6074
+ React.useEffect(() => {
6075
+ if (sourceEntries.length === 0) {
6076
+ hasMoreByFieldRef.current = {};
6077
+ loadingByFieldRef.current = {};
6078
+ optionsByFieldRef.current = {};
6079
+ pageByFieldRef.current = {};
6080
+ Object.keys(controllersRef.current).forEach(clearFieldRequest);
6081
+ setHasMoreByField({});
6082
+ setLoadingByField({});
6083
+ setOptionsByField({});
6084
+ return;
6085
+ }
6086
+ const activeFields = new Set(sourceEntries.map(({ field }) => field));
6087
+ removeInactiveFieldState(setHasMoreByField, hasMoreByFieldRef, activeFields);
6088
+ removeInactiveFieldState(setLoadingByField, loadingByFieldRef, activeFields);
6089
+ removeInactiveFieldState(setOptionsByField, optionsByFieldRef, activeFields);
6090
+ for (const field of Object.keys(controllersRef.current)) if (!activeFields.has(field)) clearFieldRequest(field);
6091
+ for (const { field } of sourceEntries) loadFieldOptions(field);
6092
+ }, [
6093
+ clearFieldRequest,
6094
+ loadFieldOptions,
6095
+ removeInactiveFieldState,
6096
+ sourceEntries,
6097
+ registryVersion,
6098
+ searchValues
6099
+ ]);
6100
+ React.useEffect(() => () => {
6101
+ for (const field of Object.keys(controllersRef.current)) clearFieldRequest(field);
6102
+ }, [clearFieldRequest]);
6103
+ return {
6104
+ hasMoreByField,
6105
+ labelOptionsByField,
6106
+ loadingByField,
6107
+ optionsByField,
6108
+ registeredByField,
6109
+ searchValues,
6110
+ searchableByField,
6111
+ loadMore: (field) => loadFieldOptions(field, true),
6112
+ setSearchValue
6113
+ };
6587
6114
  }
6588
6115
  function getOptionLabel(value, options) {
6589
6116
  const stringValue = String(value);
@@ -6593,12 +6120,15 @@ function getOptionLabel(value, options) {
6593
6120
  * 从统一配置生成表格 overrides
6594
6121
  * 当 filter 配置存在时,合并到 meta 中(不影响列隐藏)
6595
6122
  */
6596
- function buildTableOverrides(fields, legacyOverrides, dynamicFilterOptions) {
6123
+ function buildTableOverrides(fields, legacyOverrides, dynamicFilterState) {
6597
6124
  const result = { ...legacyOverrides };
6598
6125
  if (fields) for (const [key, config] of Object.entries(fields)) {
6599
6126
  const fieldOptions = normalizeFieldOptions(config.enum);
6600
6127
  const tableOptions = toTableOptions(fieldOptions);
6601
- const dynamicOptions = !fieldOptions ? toTableOptions(normalizeFieldOptions(dynamicFilterOptions?.[key])) : void 0;
6128
+ const filterDataSourceRegistered = dynamicFilterState?.registeredByField[key];
6129
+ const filterDataSourceSearchable = dynamicFilterState?.searchableByField[key];
6130
+ const currentDynamicOptions = !fieldOptions && filterDataSourceRegistered ? toTableOptions(dynamicFilterState?.optionsByField[key] ?? []) : void 0;
6131
+ const dynamicOptions = !fieldOptions && filterDataSourceRegistered ? toTableOptions(dynamicFilterState?.labelOptionsByField[key] ?? dynamicFilterState?.optionsByField[key] ?? []) : void 0;
6602
6132
  const tableMeta = config.table !== false && typeof config.table === "object" ? config.table.meta : void 0;
6603
6133
  const fieldEnumMeta = tableOptions ? {
6604
6134
  options: tableOptions,
@@ -6606,7 +6136,19 @@ function buildTableOverrides(fields, legacyOverrides, dynamicFilterOptions) {
6606
6136
  } : void 0;
6607
6137
  const fieldDataSourceMeta = dynamicOptions ? {
6608
6138
  options: dynamicOptions,
6609
- variant: "multiSelect"
6139
+ autoCrudFilterOptions: currentDynamicOptions ?? dynamicOptions,
6140
+ autoCrudFilterHasMore: dynamicFilterState?.hasMoreByField[key] ?? false,
6141
+ autoCrudFilterLoading: dynamicFilterState?.loadingByField[key] ?? false,
6142
+ autoCrudFilterOnPopupScroll: (event) => {
6143
+ if (!isNearPopupScrollBottom(event.currentTarget)) return;
6144
+ dynamicFilterState?.loadMore(key);
6145
+ },
6146
+ variant: "multiSelect",
6147
+ ...filterDataSourceSearchable ? {
6148
+ autoCrudFilterSearchValue: dynamicFilterState?.searchValues[key] ?? "",
6149
+ autoCrudFilterOnSearch: (value) => dynamicFilterState?.setSearchValue(key, value),
6150
+ autoCrudFilterShouldFilter: false
6151
+ } : void 0
6610
6152
  } : void 0;
6611
6153
  let filterMeta;
6612
6154
  if (config.filter && typeof config.filter === "object") {
@@ -6658,46 +6200,6 @@ function buildTableOverrides(fields, legacyOverrides, dynamicFilterOptions) {
6658
6200
  return result;
6659
6201
  }
6660
6202
  /**
6661
- * 从统一配置生成表单 overrides
6662
- * Critical #2: 支持 denyFields 隐藏敏感字段
6663
- */
6664
- function buildFormOverrides(fields, legacyOverrides, denyFields) {
6665
- const result = { ...legacyOverrides };
6666
- if (denyFields) for (const field of denyFields) result[field] = {
6667
- ...result[field],
6668
- "x-hidden": true
6669
- };
6670
- if (fields) for (const [key, config] of Object.entries(fields)) {
6671
- const fieldOptions = normalizeFieldOptions((config.form && typeof config.form === "object" ? config.form : void 0)?.enum) ?? normalizeFieldOptions(config.enum);
6672
- const fieldDataSource = fieldOptions || !config.dataSource ? void 0 : normalizeDataSourceConfig(config.dataSource);
6673
- if (config.label) result[key] = {
6674
- ...result[key],
6675
- title: config.label
6676
- };
6677
- if (config.hidden) result[key] = {
6678
- ...result[key],
6679
- "x-hidden": true
6680
- };
6681
- if (fieldOptions) result[key] = {
6682
- ...result[key],
6683
- enum: fieldOptions
6684
- };
6685
- if (fieldDataSource) result[key] = {
6686
- ...result[key],
6687
- "x-data-source": config.dataSource
6688
- };
6689
- if (config.form === false) result[key] = {
6690
- ...result[key],
6691
- "x-hidden": true
6692
- };
6693
- else if (config.form && typeof config.form === "object") result[key] = {
6694
- ...result[key],
6695
- ...config.form
6696
- };
6697
- }
6698
- return result;
6699
- }
6700
- /**
6701
6203
  * 从统一配置生成隐藏列列表
6702
6204
  */
6703
6205
  function buildHiddenColumns(fields, legacyHidden, denyFields) {
@@ -7004,6 +6506,8 @@ function AutoCrudTable({ id, title, description, schema, resource, fields, table
7004
6506
  rowIds,
7005
6507
  selectedRowIds,
7006
6508
  selectedCount,
6509
+ isRefreshing: resource.tableData.isFetching,
6510
+ ...resource.handlers.refresh ? { refresh: resource.handlers.refresh } : {},
7007
6511
  ...toolbarOpenCreate ? { openCreate: toolbarOpenCreate } : {}
7008
6512
  }), [
7009
6513
  id,
@@ -7011,6 +6515,8 @@ function AutoCrudTable({ id, title, description, schema, resource, fields, table
7011
6515
  rowIds,
7012
6516
  selectedRowIds,
7013
6517
  selectedCount,
6518
+ resource.handlers.refresh,
6519
+ resource.tableData.isFetching,
7014
6520
  toolbarOpenCreate
7015
6521
  ]);
7016
6522
  const resolvedToolbarActions = resolveToolbarActionsWithResolver(id, registryToolbarActions, toolbarContext);
@@ -7124,6 +6630,17 @@ function AutoCrudTable({ id, title, description, schema, resource, fields, table
7124
6630
  className: "flex items-center justify-end gap-4",
7125
6631
  children: (() => {
7126
6632
  const renderBuiltinButton = (type, overrides) => {
6633
+ if (type === "refresh") {
6634
+ const onRefresh = overrides?.onClick ?? resource.handlers.refresh;
6635
+ if (!onRefresh) return null;
6636
+ return /* @__PURE__ */ jsxs(Button, {
6637
+ variant: "outline",
6638
+ size: "sm",
6639
+ onClick: onRefresh,
6640
+ disabled: resource.tableData.isFetching,
6641
+ children: [/* @__PURE__ */ jsx(RefreshCw, { className: resource.tableData.isFetching ? "mr-2 h-4 w-4 animate-spin" : "mr-2 h-4 w-4" }), overrides?.label ?? locale.toolbar.refresh]
6642
+ }, "refresh");
6643
+ }
7127
6644
  if (type === "import" && canImport) return /* @__PURE__ */ jsxs(Button, {
7128
6645
  variant: "outline",
7129
6646
  size: "sm",
@@ -7152,6 +6669,7 @@ function AutoCrudTable({ id, title, description, schema, resource, fields, table
7152
6669
  className: "flex items-center gap-2",
7153
6670
  children: [
7154
6671
  startNodes,
6672
+ renderBuiltinButton("refresh"),
7155
6673
  renderBuiltinButton("import"),
7156
6674
  renderBuiltinButton("export"),
7157
6675
  renderBuiltinButton("create"),
@@ -7161,13 +6679,20 @@ function AutoCrudTable({ id, title, description, schema, resource, fields, table
7161
6679
  }
7162
6680
  return /* @__PURE__ */ jsx("div", {
7163
6681
  className: "flex items-center gap-2",
7164
- children: resolvedToolbarActions.map((action, index$1) => {
7165
- if (action.type === "custom") return /* @__PURE__ */ jsx(React.Fragment, { children: renderToolbarComponent(action.component) }, `custom-${index$1}`);
7166
- if (!(action.type === "import" ? canImport : action.type === "export" ? canExport : can.create)) return null;
7167
- if (action.component) return /* @__PURE__ */ jsx(React.Fragment, { children: renderToolbarComponent(action.component) }, action.type);
7168
- return renderBuiltinButton(action.type, {
7169
- onClick: action.onClick,
7170
- label: action.label
6682
+ children: resolvedToolbarActions.map((action$1, index$1) => {
6683
+ if (action$1.type === "custom") return /* @__PURE__ */ jsx(React.Fragment, { children: renderToolbarComponent(action$1.component) }, `custom-${index$1}`);
6684
+ if (!(() => {
6685
+ switch (action$1.type) {
6686
+ case "refresh": return !!(resource.handlers.refresh || action$1.onClick || action$1.component);
6687
+ case "import": return canImport;
6688
+ case "export": return canExport;
6689
+ case "create": return can.create;
6690
+ }
6691
+ })()) return null;
6692
+ if (action$1.component) return /* @__PURE__ */ jsx(React.Fragment, { children: renderToolbarComponent(action$1.component) }, action$1.type);
6693
+ return renderBuiltinButton(action$1.type, {
6694
+ onClick: action$1.onClick,
6695
+ label: action$1.label
7171
6696
  });
7172
6697
  })
7173
6698
  });
@@ -7186,6 +6711,7 @@ function AutoCrudTable({ id, title, description, schema, resource, fields, table
7186
6711
  onUpdateSelected: can.update ? resource.handlers.updateMany : void 0,
7187
6712
  batchUpdateFields: can.update ? batchFields : void 0,
7188
6713
  actionBarActions: registryBatchActions,
6714
+ deleteConfirmation: locale.bulkDeleteModal,
7189
6715
  enableExport: canExport,
7190
6716
  showDefaultExport: false,
7191
6717
  initialSorting: tableConfig?.defaultSort,
@@ -7205,6 +6731,7 @@ function AutoCrudTable({ id, title, description, schema, resource, fields, table
7205
6731
  loading: resource.mutations.isCreating || resource.mutations.isUpdating,
7206
6732
  variant: resource.modal.variant,
7207
6733
  overrides: formOverrides,
6734
+ scope: formConfig?.scope,
7208
6735
  locale: locale.formModal,
7209
6736
  gridColumns: formConfig?.columns,
7210
6737
  className: formConfig?.className
@@ -7216,7 +6743,7 @@ function AutoCrudTable({ id, title, description, schema, resource, fields, table
7216
6743
  data: resource.modal.selected,
7217
6744
  schema: resolvedSchema,
7218
6745
  fields: resolvedFields,
7219
- dynamicOptions: dynamicFilterOptions,
6746
+ dynamicOptions: dynamicFilterOptions.optionsByField,
7220
6747
  denyFields,
7221
6748
  locale
7222
6749
  }),
@@ -7339,15 +6866,29 @@ function DataTableAdvancedToolbar({ table, children, className,...props }) {
7339
6866
  function DataTableFacetedFilter({ column, title, options, multiple }) {
7340
6867
  const columnFilterValue = column?.getFilterValue();
7341
6868
  const selectedValues = Array.isArray(columnFilterValue) ? columnFilterValue.filter((value) => typeof value === "string") : [];
6869
+ const currentOptions = column?.columnDef.meta?.autoCrudFilterOptions ?? options;
6870
+ const currentOptionValues = new Set(currentOptions.map((option) => option.value));
6871
+ const selectedOptionValues = new Set(selectedValues);
6872
+ const comboboxOptions = currentOptions === options ? currentOptions : [...options.filter((option) => selectedOptionValues.has(option.value) && !currentOptionValues.has(option.value)), ...currentOptions];
7342
6873
  const onChange = (nextValues) => {
7343
6874
  column?.setFilterValue(nextValues.length ? nextValues : void 0);
7344
6875
  };
7345
- return /* @__PURE__ */ jsx(MultiCombobox, {
7346
- value: selectedValues,
7347
- onChange,
7348
- options,
7349
- selectionMode: multiple ? "multiple" : "single",
6876
+ const onSelectChange = (nextValue) => {
6877
+ onChange(Array.isArray(nextValue) ? nextValue : nextValue ? [nextValue] : []);
6878
+ };
6879
+ return /* @__PURE__ */ jsx(Select$2, {
6880
+ mode: "searchable",
6881
+ multiple: Boolean(multiple),
6882
+ value: multiple ? selectedValues : selectedValues[0] ?? "",
6883
+ onChange: onSelectChange,
6884
+ options: comboboxOptions,
7350
6885
  searchPlaceholder: title,
6886
+ hasMore: column?.columnDef.meta?.autoCrudFilterHasMore,
6887
+ loading: column?.columnDef.meta?.autoCrudFilterLoading,
6888
+ searchValue: column?.columnDef.meta?.autoCrudFilterSearchValue,
6889
+ shouldFilter: column?.columnDef.meta?.autoCrudFilterShouldFilter,
6890
+ onPopupScroll: column?.columnDef.meta?.autoCrudFilterOnPopupScroll,
6891
+ onSearch: column?.columnDef.meta?.autoCrudFilterOnSearch,
7351
6892
  contentClassName: "w-50",
7352
6893
  matchTriggerWidth: false,
7353
6894
  clearText: "Clear filters",
@@ -7926,8 +7467,8 @@ function readMetadataFields(fields) {
7926
7467
  /**
7927
7468
  * Modal 状态 Reducer
7928
7469
  */
7929
- function modalReducer(state, action) {
7930
- switch (action.type) {
7470
+ function modalReducer(state, action$1) {
7471
+ switch (action$1.type) {
7931
7472
  case "OPEN_CREATE": return {
7932
7473
  ...state,
7933
7474
  createOpen: true,
@@ -7938,22 +7479,22 @@ function modalReducer(state, action) {
7938
7479
  ...state,
7939
7480
  createOpen: true,
7940
7481
  selected: null,
7941
- copySource: action.payload
7482
+ copySource: action$1.payload
7942
7483
  };
7943
7484
  case "OPEN_EDIT": return {
7944
7485
  ...state,
7945
7486
  editOpen: true,
7946
- selected: action.payload
7487
+ selected: action$1.payload
7947
7488
  };
7948
7489
  case "OPEN_DELETE": return {
7949
7490
  ...state,
7950
7491
  deleteOpen: true,
7951
- selected: action.payload
7492
+ selected: action$1.payload
7952
7493
  };
7953
7494
  case "OPEN_VIEW": return {
7954
7495
  ...state,
7955
7496
  viewOpen: true,
7956
- selected: action.payload
7497
+ selected: action$1.payload
7957
7498
  };
7958
7499
  case "CLOSE_ALL": return {
7959
7500
  ...state,
@@ -7966,7 +7507,7 @@ function modalReducer(state, action) {
7966
7507
  };
7967
7508
  case "SET_VARIANT": return {
7968
7509
  ...state,
7969
- variant: action.payload
7510
+ variant: action$1.payload
7970
7511
  };
7971
7512
  default: return state;
7972
7513
  }
@@ -8263,6 +7804,9 @@ function useAutoCrudResource({ router, schema, query: queryTransform, options =
8263
7804
  queryInput
8264
7805
  ]);
8265
7806
  const canExport = !!(exportFetcher || exportQuery);
7807
+ const refresh = useCallback(async () => {
7808
+ return listQuery.refetch();
7809
+ }, [listQuery]);
8266
7810
  return {
8267
7811
  idKey,
8268
7812
  tableData: {
@@ -8293,6 +7837,7 @@ function useAutoCrudResource({ router, schema, query: queryTransform, options =
8293
7837
  deleteMany,
8294
7838
  updateMany,
8295
7839
  setVariant,
7840
+ refresh,
8296
7841
  import: importMutation ? handleImport : null,
8297
7842
  export: canExport ? handleExport : null
8298
7843
  }
@@ -8604,4 +8149,4 @@ function createMemoryDataSource(initialData = []) {
8604
8149
  }
8605
8150
 
8606
8151
  //#endregion
8607
- export { AutoCrudTable, AutoForm, AutoTable, AutoTableActionBar, AutoTableSimpleFilters, CrudFormModal, DataTable, DataTableAdvancedToolbar, DataTableColumnHeader, DataTableFacetedFilter, DataTablePagination, DataTableToolbar, DataTableViewOptions, ExportDialog, ImportDialog, MultiCombobox, SchemaAdapter, cn, coerceRowValues, components, createActionsColumn, createEditFormSchema, createFormSchema, createMemoryDataSource, createSelectColumn, createTRPCDataSource, createTableSchema, crudActions, dataSources, dataToCSV, deDE, downloadCSVTemplate, enUS, esES, exportAllToCSV, exportTableToCSV, formComponents, formatDate, frFR, generateCSVTemplate, getUrlParams, humanize, jaJP, koKR, noopToastAdapter, normalizeDataSourceConfig, normalizeOptions, parseAsArrayOf, parseAsInteger, parseAsString, parseAsStringEnum, parseCSV, parseImportFile, parseJSON, parseZodField, resolveLocale, setSearchParams, setToolbarResolver, useAutoCrudResource, useDataTable, useQueryState, useQueryStates, useReadableFilters, useUrlState, useUrlStates, zhCN };
8152
+ export { AutoCrudTable, AutoForm, AutoTable, AutoTableActionBar, AutoTableSimpleFilters, CrudFormModal, DataTable, DataTableAdvancedToolbar, DataTableColumnHeader, DataTableFacetedFilter, DataTablePagination, DataTableToolbar, DataTableViewOptions, ExportDialog, ImportDialog, MultiCombobox, SchemaAdapter, Select, cn, coerceRowValues, components, createActionsColumn, createEditFormSchema, createFormSchema, createMemoryDataSource, createSelectColumn, createTRPCDataSource, createTableSchema, crudActions, dataSources, dataToCSV, deDE, downloadCSVTemplate, enUS, esES, exportAllToCSV, exportTableToCSV, formComponents, formatDate, frFR, generateCSVTemplate, getUrlParams, humanize, jaJP, koKR, noopToastAdapter, normalizeDataSourceConfig, normalizeOptions, parseAsArrayOf, parseAsInteger, parseAsString, parseAsStringEnum, parseCSV, parseImportFile, parseJSON, parseZodField, resolveLocale, setSearchParams, setToolbarResolver, useAutoCrudResource, useDataTable, useQueryState, useQueryStates, useReadableFilters, useUrlState, useUrlStates, zhCN };