@wordrhyme/auto-crud 1.2.5 → 1.3.1

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
@@ -2,22 +2,16 @@ import * as React from "react";
2
2
  import { forwardRef, useCallback, useEffect, useImperativeHandle, useMemo, useReducer, useRef, useState } from "react";
3
3
  import { ArrowDownUp, BadgeCheck, CalendarIcon, Check, CheckCircle2, ChevronDown, ChevronLeft, ChevronRight, ChevronUp, ChevronsLeft, ChevronsRight, ChevronsUpDown, CommandIcon, Download, Ellipsis, EyeOff, FileDown, FileSpreadsheetIcon, GripVertical, ListFilter, ListFilterIcon, Loader2, PlusCircle, Settings2, Text, Trash2, Upload, X, XCircle } from "lucide-react";
4
4
  import { flexRender, getCoreRowModel, getFacetedMinMaxValues, getFacetedRowModel, getFacetedUniqueValues, getFilteredRowModel, getPaginationRowModel, getSortedRowModel, useReactTable } from "@tanstack/react-table";
5
- 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, 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, MultiCombobox as MultiCombobox$1, 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
 
@@ -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 = {
@@ -461,10 +406,10 @@ function getColumnVariant(column) {
461
406
  function splitKey(key) {
462
407
  const match = key.match(/^(.*?)(?:__(\d+))?$/);
463
408
  if (!match) return { base: key };
464
- const index = match[2] ? Number(match[2]) : void 0;
409
+ const index$1 = match[2] ? Number(match[2]) : void 0;
465
410
  return {
466
411
  base: match[1] ?? key,
467
- index
412
+ index: index$1
468
413
  };
469
414
  }
470
415
  function isFilterKey(key, columnIds) {
@@ -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, 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,
1283
- setActivatorNodeRef,
1284
- isDragging,
1285
- disabled
1286
- }), [
1287
- id,
1288
- attributes,
1289
- listeners,
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 : {},
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,
@@ -1528,9 +1025,9 @@ function DataTableFilterList({ table, debounceMs = DEBOUNCE_MS$2, throttleMs = T
1528
1025
  children: /* @__PURE__ */ jsx("div", {
1529
1026
  role: "list",
1530
1027
  className: "flex max-h-[300px] flex-col gap-2 overflow-y-auto p-1",
1531
- children: filters.map((filter, index) => /* @__PURE__ */ jsx(DataTableFilterItem$1, {
1028
+ children: filters.map((filter, index$1) => /* @__PURE__ */ jsx(DataTableFilterItem$1, {
1532
1029
  filter,
1533
- index,
1030
+ index: index$1,
1534
1031
  filterItemId: `${id}-filter-${filter.filterId}`,
1535
1032
  joinOperator,
1536
1033
  setJoinOperator,
@@ -1571,7 +1068,7 @@ function DataTableFilterList({ table, debounceMs = DEBOUNCE_MS$2, throttleMs = T
1571
1068
  }) })]
1572
1069
  });
1573
1070
  }
1574
- function DataTableFilterItem$1({ filter, index, filterItemId, joinOperator, setJoinOperator, columns, onFilterUpdate, onFilterRemove }) {
1071
+ function DataTableFilterItem$1({ filter, index: index$1, filterItemId, joinOperator, setJoinOperator, columns, onFilterUpdate, onFilterRemove }) {
1575
1072
  const [showFieldSelector, setShowFieldSelector] = React.useState(false);
1576
1073
  const [showOperatorSelector, setShowOperatorSelector] = React.useState(false);
1577
1074
  const [showValueSelector, setShowValueSelector] = React.useState(false);
@@ -1609,10 +1106,10 @@ function DataTableFilterItem$1({ filter, index, filterItemId, joinOperator, setJ
1609
1106
  children: [
1610
1107
  /* @__PURE__ */ jsx("div", {
1611
1108
  className: "min-w-[72px] text-center",
1612
- children: index === 0 ? /* @__PURE__ */ jsx("span", {
1109
+ children: index$1 === 0 ? /* @__PURE__ */ jsx("span", {
1613
1110
  className: "text-muted-foreground text-sm",
1614
1111
  children: "Where"
1615
- }) : index === 1 ? /* @__PURE__ */ jsxs(Select, {
1112
+ }) : index$1 === 1 ? /* @__PURE__ */ jsxs(Select, {
1616
1113
  value: joinOperator,
1617
1114
  onValueChange: (value) => setJoinOperator(value),
1618
1115
  children: [/* @__PURE__ */ jsx(SelectTrigger, {
@@ -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
  })
@@ -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: [
@@ -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,
@@ -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) => {
@@ -3045,10 +2423,10 @@ function AutoTableSimpleFilters({ table, shallow = false, filters: externalFilte
3045
2423
  "data-filter-leading": true,
3046
2424
  children: leading
3047
2425
  }) : null,
3048
- filterColumns.map((column, index) => {
2426
+ filterColumns.map((column, index$1) => {
3049
2427
  const meta = column.columnDef.meta;
3050
2428
  const value = getFilterValue(column.id);
3051
- const isHidden = !expanded && visibleCount !== null && index >= visibleCount;
2429
+ const isHidden = !expanded && visibleCount !== null && index$1 >= visibleCount;
3052
2430
  switch (meta.variant) {
3053
2431
  case "text": return /* @__PURE__ */ jsx("div", {
3054
2432
  className: isHidden ? "hidden" : "",
@@ -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,19 @@ 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, {
2509
+ function SimpleFacetedFilter({ title, options, multiple, value, hasMore, loading, searchValue, shouldFilter, onChange, onPopupScroll, onSearch }) {
2510
+ return /* @__PURE__ */ jsx(MultiCombobox$1, {
3123
2511
  value,
3124
2512
  onChange,
3125
2513
  options,
3126
2514
  selectionMode: multiple ? "multiple" : "single",
3127
2515
  searchPlaceholder: title,
2516
+ hasMore,
2517
+ loading,
2518
+ searchValue,
2519
+ shouldFilter,
2520
+ onPopupScroll,
2521
+ onSearch,
3128
2522
  contentClassName: "w-50",
3129
2523
  matchTriggerWidth: false,
3130
2524
  clearText: "Clear filters",
@@ -3423,438 +2817,58 @@ function SimpleDateFilter({ title, multiple, value, onChange }) {
3423
2817
  }
3424
2818
 
3425
2819
  //#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;
2820
+ //#region src/components/data-table/data-table-column-header.tsx
2821
+ function DataTableColumnHeader({ column, label, className,...props }) {
2822
+ if (!column.getCanSort() && !column.getCanHide()) return /* @__PURE__ */ jsx("div", {
2823
+ className: cn(className),
2824
+ children: label
3435
2825
  });
3436
- return ref;
2826
+ return /* @__PURE__ */ jsxs(DropdownMenu, { children: [/* @__PURE__ */ jsxs(DropdownMenuTrigger, {
2827
+ className: cn("-ml-1.5 flex h-8 items-center gap-1.5 rounded-md px-2 py-1.5 hover:bg-accent focus:outline-none focus:ring-1 focus:ring-ring data-[state=open]:bg-accent [&_svg]:size-4 [&_svg]:shrink-0 [&_svg]:text-muted-foreground", className),
2828
+ ...props,
2829
+ children: [label, column.getCanSort() && (column.getIsSorted() === "desc" ? /* @__PURE__ */ jsx(ChevronDown, {}) : column.getIsSorted() === "asc" ? /* @__PURE__ */ jsx(ChevronUp, {}) : /* @__PURE__ */ jsx(ChevronsUpDown, {}))]
2830
+ }), /* @__PURE__ */ jsxs(DropdownMenuContent, {
2831
+ align: "start",
2832
+ className: "w-28",
2833
+ children: [column.getCanSort() && /* @__PURE__ */ jsxs(Fragment, { children: [
2834
+ /* @__PURE__ */ jsxs(DropdownMenuCheckboxItem, {
2835
+ className: "relative pr-8 pl-2 [&>span:first-child]:right-2 [&>span:first-child]:left-auto [&_svg]:text-muted-foreground",
2836
+ checked: column.getIsSorted() === "asc",
2837
+ onClick: () => column.toggleSorting(false),
2838
+ children: [/* @__PURE__ */ jsx(ChevronUp, {}), "Asc"]
2839
+ }),
2840
+ /* @__PURE__ */ jsxs(DropdownMenuCheckboxItem, {
2841
+ className: "relative pr-8 pl-2 [&>span:first-child]:right-2 [&>span:first-child]:left-auto [&_svg]:text-muted-foreground",
2842
+ checked: column.getIsSorted() === "desc",
2843
+ onClick: () => column.toggleSorting(true),
2844
+ children: [/* @__PURE__ */ jsx(ChevronDown, {}), "Desc"]
2845
+ }),
2846
+ column.getIsSorted() && /* @__PURE__ */ jsxs(DropdownMenuItem, {
2847
+ className: "pl-2 [&_svg]:text-muted-foreground",
2848
+ onClick: () => column.clearSorting(),
2849
+ children: [/* @__PURE__ */ jsx(X, {}), "Reset"]
2850
+ })
2851
+ ] }), column.getCanHide() && /* @__PURE__ */ jsxs(DropdownMenuCheckboxItem, {
2852
+ className: "relative pr-8 pl-2 [&>span:first-child]:right-2 [&>span:first-child]:left-auto [&_svg]:text-muted-foreground",
2853
+ checked: !column.getIsVisible(),
2854
+ onClick: () => column.toggleVisibility(false),
2855
+ children: [/* @__PURE__ */ jsx(EyeOff, {}), "Hide"]
2856
+ })]
2857
+ })] });
3437
2858
  }
3438
2859
 
3439
2860
  //#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) => array[(startIndex + index) % 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
- //#endregion
3806
- //#region src/components/data-table/data-table-column-header.tsx
3807
- function DataTableColumnHeader({ column, label, className,...props }) {
3808
- if (!column.getCanSort() && !column.getCanHide()) return /* @__PURE__ */ jsx("div", {
3809
- className: cn(className),
3810
- children: label
3811
- });
3812
- return /* @__PURE__ */ jsxs(DropdownMenu, { children: [/* @__PURE__ */ jsxs(DropdownMenuTrigger, {
3813
- className: cn("-ml-1.5 flex h-8 items-center gap-1.5 rounded-md px-2 py-1.5 hover:bg-accent focus:outline-none focus:ring-1 focus:ring-ring data-[state=open]:bg-accent [&_svg]:size-4 [&_svg]:shrink-0 [&_svg]:text-muted-foreground", className),
3814
- ...props,
3815
- children: [label, column.getCanSort() && (column.getIsSorted() === "desc" ? /* @__PURE__ */ jsx(ChevronDown, {}) : column.getIsSorted() === "asc" ? /* @__PURE__ */ jsx(ChevronUp, {}) : /* @__PURE__ */ jsx(ChevronsUpDown, {}))]
3816
- }), /* @__PURE__ */ jsxs(DropdownMenuContent, {
3817
- align: "start",
3818
- className: "w-28",
3819
- children: [column.getCanSort() && /* @__PURE__ */ jsxs(Fragment, { children: [
3820
- /* @__PURE__ */ jsxs(DropdownMenuCheckboxItem, {
3821
- className: "relative pr-8 pl-2 [&>span:first-child]:right-2 [&>span:first-child]:left-auto [&_svg]:text-muted-foreground",
3822
- checked: column.getIsSorted() === "asc",
3823
- onClick: () => column.toggleSorting(false),
3824
- children: [/* @__PURE__ */ jsx(ChevronUp, {}), "Asc"]
3825
- }),
3826
- /* @__PURE__ */ jsxs(DropdownMenuCheckboxItem, {
3827
- className: "relative pr-8 pl-2 [&>span:first-child]:right-2 [&>span:first-child]:left-auto [&_svg]:text-muted-foreground",
3828
- checked: column.getIsSorted() === "desc",
3829
- onClick: () => column.toggleSorting(true),
3830
- children: [/* @__PURE__ */ jsx(ChevronDown, {}), "Desc"]
3831
- }),
3832
- column.getIsSorted() && /* @__PURE__ */ jsxs(DropdownMenuItem, {
3833
- className: "pl-2 [&_svg]:text-muted-foreground",
3834
- onClick: () => column.clearSorting(),
3835
- children: [/* @__PURE__ */ jsx(X, {}), "Reset"]
3836
- })
3837
- ] }), column.getCanHide() && /* @__PURE__ */ jsxs(DropdownMenuCheckboxItem, {
3838
- className: "relative pr-8 pl-2 [&>span:first-child]:right-2 [&>span:first-child]:left-auto [&_svg]:text-muted-foreground",
3839
- checked: !column.getIsVisible(),
3840
- onClick: () => column.toggleVisibility(false),
3841
- children: [/* @__PURE__ */ jsx(EyeOff, {}), "Hide"]
3842
- })]
3843
- })] });
3844
- }
3845
-
3846
- //#endregion
3847
- //#region src/lib/humanize.ts
3848
- /**
3849
- * 将字符串转为人类可读格式
3850
- *
3851
- * @example
3852
- * humanize("createdAt") // "Created At"
3853
- * humanize("user_name") // "User Name"
3854
- * humanize("firstName") // "First Name"
3855
- */
3856
- function humanize(str) {
3857
- return str.replace(/([A-Z])/g, " $1").replace(/[_-]/g, " ").replace(/^./, (s) => s.toUpperCase()).trim();
2861
+ //#region src/lib/humanize.ts
2862
+ /**
2863
+ * 将字符串转为人类可读格式
2864
+ *
2865
+ * @example
2866
+ * humanize("createdAt") // "Created At"
2867
+ * humanize("user_name") // "User Name"
2868
+ * humanize("firstName") // "First Name"
2869
+ */
2870
+ function humanize(str) {
2871
+ return str.replace(/([A-Z])/g, " $1").replace(/[_-]/g, " ").replace(/^./, (s) => s.toUpperCase()).trim();
3858
2872
  }
3859
2873
 
3860
2874
  //#endregion
@@ -4003,9 +3017,9 @@ function parseZodField(schema) {
4003
3017
  if (def?.type === "enum") {
4004
3018
  let enumValues$1;
4005
3019
  if (def?.entries) {
4006
- const entries = def.entries;
4007
- if (Array.isArray(entries)) enumValues$1 = entries.map(String);
4008
- else if (typeof entries === "object") enumValues$1 = Object.keys(entries);
3020
+ const entries$1 = def.entries;
3021
+ if (Array.isArray(entries$1)) enumValues$1 = entries$1.map(String);
3022
+ else if (typeof entries$1 === "object") enumValues$1 = Object.keys(entries$1);
4009
3023
  } else if (def?.values) {
4010
3024
  const values = def.values;
4011
3025
  if (Array.isArray(values)) enumValues$1 = values.map(String);
@@ -4026,9 +3040,9 @@ function parseZodField(schema) {
4026
3040
  });
4027
3041
  let enumValues;
4028
3042
  if (def?.entries) {
4029
- const entries = def.entries;
4030
- if (Array.isArray(entries)) enumValues = entries.map(String);
4031
- else if (typeof entries === "object") enumValues = Object.keys(entries);
3043
+ const entries$1 = def.entries;
3044
+ if (Array.isArray(entries$1)) enumValues = entries$1.map(String);
3045
+ else if (typeof entries$1 === "object") enumValues = Object.keys(entries$1);
4032
3046
  } else if (def?.values) {
4033
3047
  const values = def.values;
4034
3048
  if (Array.isArray(values)) enumValues = values.map(String);
@@ -4223,6 +3237,10 @@ function createSelectColumn() {
4223
3237
  * 创建操作列
4224
3238
  */
4225
3239
  function createActionsColumn(items) {
3240
+ const renderActionComponent = (item, row) => {
3241
+ if (!item.component) return null;
3242
+ return typeof item.component === "function" ? item.component(item.getContext?.(row.original)) : item.component;
3243
+ };
4226
3244
  return {
4227
3245
  id: "actions",
4228
3246
  cell: ({ row }) => /* @__PURE__ */ jsxs(DropdownMenu, { children: [/* @__PURE__ */ jsx(DropdownMenuTrigger, {
@@ -4236,11 +3254,11 @@ function createActionsColumn(items) {
4236
3254
  }), /* @__PURE__ */ jsx(DropdownMenuContent, {
4237
3255
  align: "end",
4238
3256
  className: "w-40",
4239
- children: items.map((item, i) => /* @__PURE__ */ jsxs(React.Fragment, { children: [item.separator && /* @__PURE__ */ jsx(DropdownMenuSeparator, {}), /* @__PURE__ */ jsx(DropdownMenuItem, {
3257
+ children: items.map((item, i) => /* @__PURE__ */ jsxs(React.Fragment, { children: [item.separator && /* @__PURE__ */ jsx(DropdownMenuSeparator, {}), item.component ? renderActionComponent(item, row) : item.label && item.onClick ? /* @__PURE__ */ jsx(DropdownMenuItem, {
4240
3258
  className: item.variant === "destructive" ? "text-destructive" : "",
4241
- onClick: () => item.onClick(row.original),
3259
+ onClick: () => item.onClick?.(row.original),
4242
3260
  children: item.label
4243
- })] }, i))
3261
+ }) : null] }, i))
4244
3262
  })] }),
4245
3263
  size: 40
4246
3264
  };
@@ -4458,12 +3476,22 @@ function downloadCSV(csvContent, filename) {
4458
3476
 
4459
3477
  //#endregion
4460
3478
  //#region src/components/auto-crud/auto-table-action-bar.tsx
4461
- function isBatchCustomAction(action) {
4462
- return action.type === "custom";
3479
+ const DEFAULT_DELETE_CONFIRMATION = {
3480
+ title: "确认批量删除",
3481
+ description: (count) => `此操作无法撤销。确定要删除选中的 ${count} 条记录吗?`,
3482
+ cancel: "取消",
3483
+ confirm: "确认删除"
3484
+ };
3485
+ function isBatchCustomAction(action$1) {
3486
+ return action$1.type === "custom";
4463
3487
  }
4464
- function AutoTableActionBar({ table, onDeleteSelected, onUpdateSelected, batchUpdateFields, enableExport = true, showDefaultExport = true, enableDelete = true, extraActions, actions }) {
3488
+ function AutoTableActionBar({ table, onDeleteSelected, onUpdateSelected, batchUpdateFields, enableExport = true, showDefaultExport = true, enableDelete = true, extraActions, actions, deleteConfirmation = DEFAULT_DELETE_CONFIRMATION }) {
4465
3489
  const rows = table.getFilteredSelectedRowModel().rows;
4466
3490
  const selectedRows = React.useMemo(() => rows.map((row) => row.original), [rows]);
3491
+ const [deleteDialogOpen, setDeleteDialogOpen] = React.useState(false);
3492
+ React.useEffect(() => {
3493
+ if (selectedRows.length === 0) setDeleteDialogOpen(false);
3494
+ }, [selectedRows.length]);
4467
3495
  const clearSelection = React.useCallback(() => {
4468
3496
  table.toggleAllRowsSelected(false);
4469
3497
  }, [table]);
@@ -4486,8 +3514,34 @@ function AutoTableActionBar({ table, onDeleteSelected, onUpdateSelected, batchUp
4486
3514
  });
4487
3515
  }, [table]);
4488
3516
  const onDelete = React.useCallback(() => {
4489
- if (onDeleteSelected) onDeleteSelected(selectedRows);
4490
- }, [selectedRows, onDeleteSelected]);
3517
+ if (onDeleteSelected && selectedRows.length > 0) {
3518
+ onDeleteSelected(selectedRows);
3519
+ clearSelection();
3520
+ }
3521
+ setDeleteDialogOpen(false);
3522
+ }, [
3523
+ selectedRows,
3524
+ onDeleteSelected,
3525
+ clearSelection
3526
+ ]);
3527
+ const runDeleteAction = React.useCallback((action$1, event) => {
3528
+ if (selectedRows.length === 0) {
3529
+ setDeleteDialogOpen(false);
3530
+ return;
3531
+ }
3532
+ if (action$1.onClick) {
3533
+ action$1.onClick(selectedRows, actionContext, event);
3534
+ clearSelection();
3535
+ setDeleteDialogOpen(false);
3536
+ return;
3537
+ }
3538
+ onDelete();
3539
+ }, [
3540
+ actionContext,
3541
+ clearSelection,
3542
+ onDelete,
3543
+ selectedRows
3544
+ ]);
4491
3545
  const handleBatchUpdate = React.useCallback((field, value) => {
4492
3546
  if (onUpdateSelected) {
4493
3547
  onUpdateSelected(selectedRows, { [field]: value });
@@ -4514,18 +3568,28 @@ function AutoTableActionBar({ table, onDeleteSelected, onUpdateSelected, batchUp
4514
3568
  children: option.label
4515
3569
  }, option.value))
4516
3570
  })] }, `${keyPrefix}-${fieldConfig.field}`)), [batchUpdateFields, handleBatchUpdate]);
4517
- const renderBuiltinAction = React.useCallback((action) => {
4518
- if (!(action.type === "batchUpdate" ? !!onUpdateSelected && !!batchUpdateFields?.length : action.type === "export" ? enableExport : enableDelete && !!onDeleteSelected)) return null;
4519
- if (action.component) return renderComponent(action.component);
4520
- if (action.type === "batchUpdate") return renderBatchUpdate(action.type);
4521
- if (action.type === "export") return /* @__PURE__ */ jsxs(ActionBarItem, {
4522
- onClick: (event) => action.onClick ? action.onClick(selectedRows, actionContext, event) : onExport(),
4523
- children: [/* @__PURE__ */ jsx(Download, {}), action.label ?? "Export"]
3571
+ const renderBuiltinAction = React.useCallback((action$1) => {
3572
+ if (!(action$1.type === "batchUpdate" ? !!onUpdateSelected && !!batchUpdateFields?.length : action$1.type === "export" ? enableExport : enableDelete && !!onDeleteSelected)) return null;
3573
+ if (action$1.component) return renderComponent(action$1.component);
3574
+ if (action$1.type === "batchUpdate") return renderBatchUpdate(action$1.type);
3575
+ if (action$1.type === "export") return /* @__PURE__ */ jsxs(ActionBarItem, {
3576
+ onClick: (event) => action$1.onClick ? action$1.onClick(selectedRows, actionContext, event) : onExport(),
3577
+ children: [/* @__PURE__ */ jsx(Download, {}), action$1.label ?? "Export"]
4524
3578
  }, "export");
4525
- if (action.type === "delete") return /* @__PURE__ */ jsxs(ActionBarItem, {
4526
- variant: "destructive",
4527
- onClick: (event) => action.onClick ? action.onClick(selectedRows, actionContext, event) : onDelete(),
4528
- children: [/* @__PURE__ */ jsx(Trash2, {}), action.label ?? "Delete"]
3579
+ if (action$1.type === "delete") return /* @__PURE__ */ jsxs(AlertDialog, {
3580
+ open: deleteDialogOpen,
3581
+ onOpenChange: setDeleteDialogOpen,
3582
+ children: [/* @__PURE__ */ jsxs(ActionBarItem, {
3583
+ variant: "destructive",
3584
+ onClick: () => setDeleteDialogOpen(true),
3585
+ onSelect: (event) => event.preventDefault(),
3586
+ children: [/* @__PURE__ */ jsx(Trash2, {}), action$1.label ?? "Delete"]
3587
+ }), /* @__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, {
3588
+ 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",
3589
+ disabled: selectedRows.length === 0,
3590
+ onClick: (event) => runDeleteAction(action$1, event),
3591
+ children: deleteConfirmation.confirm
3592
+ })] })] })]
4529
3593
  }, "delete");
4530
3594
  return null;
4531
3595
  }, [
@@ -4533,42 +3597,47 @@ function AutoTableActionBar({ table, onDeleteSelected, onUpdateSelected, batchUp
4533
3597
  batchUpdateFields?.length,
4534
3598
  enableDelete,
4535
3599
  enableExport,
3600
+ deleteConfirmation,
3601
+ deleteDialogOpen,
4536
3602
  onDelete,
4537
3603
  onDeleteSelected,
4538
3604
  onUpdateSelected,
4539
3605
  onExport,
4540
3606
  renderBatchUpdate,
4541
3607
  renderComponent,
3608
+ runDeleteAction,
4542
3609
  selectedRows
4543
3610
  ]);
4544
- const renderCustomAction = React.useCallback((action, index) => {
4545
- if (action.component) return /* @__PURE__ */ jsx(React.Fragment, { children: renderComponent(action.component) }, `custom-${index}`);
4546
- if (!action.label || !action.onClick) return null;
3611
+ const renderCustomAction = React.useCallback((action$1, index$1) => {
3612
+ if (action$1.component) return /* @__PURE__ */ jsx(React.Fragment, { children: renderComponent(action$1.component) }, `custom-${index$1}`);
3613
+ if (!action$1.label || !action$1.onClick) return null;
4547
3614
  return /* @__PURE__ */ jsx(ActionBarItem, {
4548
- variant: action.variant === "destructive" ? "destructive" : "secondary",
4549
- onClick: (event) => action.onClick?.(selectedRows, actionContext, event),
4550
- children: action.label
4551
- }, `custom-${index}`);
3615
+ variant: action$1.variant === "destructive" ? "destructive" : "secondary",
3616
+ onClick: (event) => action$1.onClick?.(selectedRows, actionContext, event),
3617
+ children: action$1.label
3618
+ }, `custom-${index$1}`);
4552
3619
  }, [
4553
3620
  actionContext,
4554
3621
  renderComponent,
4555
3622
  selectedRows
4556
3623
  ]);
4557
3624
  const renderedActions = React.useMemo(() => {
4558
- const resolvedActions = typeof actions === "function" ? actions([
3625
+ const configuredActions = typeof actions === "function" ? actions([
4559
3626
  { type: "batchUpdate" },
4560
3627
  { type: "export" },
4561
3628
  { type: "delete" }
4562
3629
  ]) : actions;
4563
- if (!resolvedActions || resolvedActions.length === 0) return /* @__PURE__ */ jsxs(Fragment, { children: [
3630
+ const resolvedActions = configuredActions?.filter((action$1) => !action$1.hidden);
3631
+ if (configuredActions === void 0) return /* @__PURE__ */ jsxs(Fragment, { children: [
4564
3632
  renderBuiltinAction({ type: "batchUpdate" }),
4565
3633
  extraActions,
4566
3634
  showDefaultExport && renderBuiltinAction({ type: "export" }),
4567
3635
  renderBuiltinAction({ type: "delete" })
4568
3636
  ] });
4569
- if (!resolvedActions.some((action) => action.type !== "custom")) {
4570
- const startNodes = resolvedActions.filter(isBatchCustomAction).filter((action) => action.position === "start").map((action, index) => renderCustomAction(action, index));
4571
- const endNodes = resolvedActions.filter(isBatchCustomAction).filter((action) => action.position !== "start").map((action, index) => renderCustomAction(action, index));
3637
+ if (!resolvedActions || resolvedActions.length === 0) return null;
3638
+ if (!resolvedActions.some((action$1) => action$1.type !== "custom")) {
3639
+ const startNodes = resolvedActions.filter(isBatchCustomAction).filter((action$1) => action$1.position === "start").map((action$1, index$1) => renderCustomAction(action$1, index$1));
3640
+ const endNodes = resolvedActions.filter(isBatchCustomAction).filter((action$1) => action$1.position !== "start").map((action$1, index$1) => renderCustomAction(action$1, index$1));
4572
3641
  return /* @__PURE__ */ jsxs(Fragment, { children: [
4573
3642
  startNodes,
4574
3643
  renderBuiltinAction({ type: "batchUpdate" }),
@@ -4578,9 +3647,9 @@ function AutoTableActionBar({ table, onDeleteSelected, onUpdateSelected, batchUp
4578
3647
  endNodes
4579
3648
  ] });
4580
3649
  }
4581
- return resolvedActions.map((action, index) => {
4582
- if (action.type === "custom") return renderCustomAction(action, index);
4583
- return /* @__PURE__ */ jsx(React.Fragment, { children: renderBuiltinAction(action) }, action.type);
3650
+ return resolvedActions.map((action$1, index$1) => {
3651
+ if (action$1.type === "custom") return renderCustomAction(action$1, index$1);
3652
+ return /* @__PURE__ */ jsx(React.Fragment, { children: renderBuiltinAction(action$1) }, action$1.type);
4584
3653
  });
4585
3654
  }, [
4586
3655
  actions,
@@ -4864,7 +3933,7 @@ const filterModeConfig = {
4864
3933
  tooltip: "Linear-style command filters"
4865
3934
  }
4866
3935
  };
4867
- 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 }) {
3936
+ 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 }) {
4868
3937
  const modes = filterMode ? Array.isArray(filterMode) ? filterMode : [filterMode] : DEFAULT_MODES;
4869
3938
  const [currentMode, setCurrentMode] = useQueryState("filterMode", parseAsStringEnum(modes).withDefault(modes[0] ?? "simple"));
4870
3939
  const showToggle = modes.length > 1;
@@ -5041,7 +4110,8 @@ function AutoTable({ schema, data, pageCount = 1, overrides, enableRowSelection
5041
4110
  enableExport,
5042
4111
  showDefaultExport,
5043
4112
  extraActions: actionBarExtra,
5044
- actions: actionBarActions
4113
+ actions: actionBarActions,
4114
+ deleteConfirmation
5045
4115
  })
5046
4116
  ]
5047
4117
  });
@@ -5201,39 +4271,39 @@ function createEditFormSchema(schema, options) {
5201
4271
  //#endregion
5202
4272
  //#region src/lib/registries.ts
5203
4273
  function createRegistry(label) {
5204
- const entries = /* @__PURE__ */ new Map();
5205
- const listeners = /* @__PURE__ */ new Set();
5206
- let notifyScheduled = false;
5207
- const notify = () => {
5208
- if (notifyScheduled) return;
5209
- notifyScheduled = true;
4274
+ const entries$1 = /* @__PURE__ */ new Map();
4275
+ const listeners$1 = /* @__PURE__ */ new Set();
4276
+ let notifyScheduled$1 = false;
4277
+ const notify$1 = () => {
4278
+ if (notifyScheduled$1) return;
4279
+ notifyScheduled$1 = true;
5210
4280
  queueMicrotask(() => {
5211
- notifyScheduled = false;
5212
- for (const listener of listeners) listener();
4281
+ notifyScheduled$1 = false;
4282
+ for (const listener of listeners$1) listener();
5213
4283
  });
5214
4284
  };
5215
4285
  return {
5216
4286
  register(name, value) {
5217
- if (entries.has(name)) {
4287
+ if (entries$1.has(name)) {
5218
4288
  console.warn(`[AutoCrud] ${label} "${name}" is already registered.`);
5219
4289
  return;
5220
4290
  }
5221
- entries.set(name, value);
5222
- notify();
4291
+ entries$1.set(name, value);
4292
+ notify$1();
5223
4293
  },
5224
4294
  unregister(name) {
5225
- if (!entries.delete(name)) return;
5226
- notify();
4295
+ if (!entries$1.delete(name)) return;
4296
+ notify$1();
5227
4297
  },
5228
4298
  get(name) {
5229
- return entries.get(name);
4299
+ return entries$1.get(name);
5230
4300
  },
5231
4301
  all() {
5232
- return Object.fromEntries(entries);
4302
+ return Object.fromEntries(entries$1);
5233
4303
  },
5234
4304
  subscribe(listener) {
5235
- listeners.add(listener);
5236
- return () => listeners.delete(listener);
4305
+ listeners$1.add(listener);
4306
+ return () => listeners$1.delete(listener);
5237
4307
  }
5238
4308
  };
5239
4309
  }
@@ -5243,11 +4313,19 @@ function normalizeDataSourceRegistration(registration) {
5243
4313
  if (typeof registration === "function") return {
5244
4314
  dependencies: [],
5245
4315
  reset: false,
4316
+ search: false,
4317
+ debounceMs: 300,
4318
+ loadMore: false,
4319
+ pageSize: 20,
5246
4320
  load: registration
5247
4321
  };
5248
4322
  return {
5249
4323
  dependencies: Array.isArray(registration.dependencies) ? registration.dependencies : [],
5250
4324
  reset: registration.reset === true,
4325
+ search: registration.search === true,
4326
+ debounceMs: typeof registration.debounceMs === "number" && registration.debounceMs >= 0 ? registration.debounceMs : 300,
4327
+ loadMore: registration.loadMore === true,
4328
+ pageSize: typeof registration.pageSize === "number" && registration.pageSize > 0 ? registration.pageSize : 20,
5251
4329
  load: registration.load
5252
4330
  };
5253
4331
  }
@@ -5274,10 +4352,63 @@ function normalizeOptions(result) {
5274
4352
  value: String(option.value)
5275
4353
  }));
5276
4354
  }
4355
+ function normalizeHasMore(result) {
4356
+ return !Array.isArray(result) && result?.hasMore === true;
4357
+ }
4358
+
4359
+ //#endregion
4360
+ //#region src/lib/field-config.ts
4361
+ function normalizeFieldOptions$1(options) {
4362
+ if (!options || options.length === 0) return void 0;
4363
+ return options.map((option) => ({
4364
+ ...option,
4365
+ value: String(option.value)
4366
+ }));
4367
+ }
4368
+ /**
4369
+ * 从统一字段配置生成 Formily overrides。
4370
+ * `legacyOverrides` 保持兼容旧 API,字段级 `form` 配置优先级最高。
4371
+ */
4372
+ function buildFormOverrides(fields, legacyOverrides, denyFields) {
4373
+ const result = { ...legacyOverrides };
4374
+ if (denyFields) for (const field of denyFields) result[field] = {
4375
+ ...result[field],
4376
+ "x-hidden": true
4377
+ };
4378
+ if (fields) for (const [key, config] of Object.entries(fields)) {
4379
+ const fieldOptions = normalizeFieldOptions$1((config.form && typeof config.form === "object" ? config.form : void 0)?.enum) ?? normalizeFieldOptions$1(config.enum);
4380
+ const fieldDataSource = fieldOptions || !config.dataSource ? void 0 : normalizeDataSourceConfig(config.dataSource);
4381
+ if (config.label) result[key] = {
4382
+ ...result[key],
4383
+ title: config.label
4384
+ };
4385
+ if (config.hidden) result[key] = {
4386
+ ...result[key],
4387
+ "x-hidden": true
4388
+ };
4389
+ if (fieldOptions) result[key] = {
4390
+ ...result[key],
4391
+ enum: fieldOptions
4392
+ };
4393
+ if (fieldDataSource) result[key] = {
4394
+ ...result[key],
4395
+ "x-data-source": config.dataSource
4396
+ };
4397
+ if (config.form === false) result[key] = {
4398
+ ...result[key],
4399
+ "x-hidden": true
4400
+ };
4401
+ else if (config.form && typeof config.form === "object") result[key] = {
4402
+ ...result[key],
4403
+ ...config.form
4404
+ };
4405
+ }
4406
+ return result;
4407
+ }
5277
4408
 
5278
4409
  //#endregion
5279
4410
  //#region src/components/auto-crud/auto-form.tsx
5280
- const COMBOBOX_LIST_CLASS = "[&_[cmdk-list]]:max-h-[360px] [&_[cmdk-list]]:overflow-x-hidden [&_[cmdk-list]]:overflow-y-auto";
4411
+ 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";
5281
4412
  const FormilySwitch = connect(Switch, mapProps({
5282
4413
  value: "checked",
5283
4414
  onInput: "onCheckedChange"
@@ -5287,14 +4418,14 @@ const FormilyMultiCombobox = connect(AutoCrudMultiCombobox, mapProps({
5287
4418
  dataSource: "options",
5288
4419
  onInput: "onChange"
5289
4420
  }, (props, field) => {
5290
- const fieldValue = field.value;
4421
+ const fieldValue = field && typeof field === "object" && "value" in field ? field.value : props.value;
5291
4422
  return {
5292
4423
  ...props,
5293
4424
  value: Array.isArray(fieldValue) ? fieldValue.map(String) : []
5294
4425
  };
5295
4426
  }));
5296
4427
  function AutoCrudCombobox({ options = [], className, value, onChange,...props }) {
5297
- return /* @__PURE__ */ jsx(MultiCombobox, {
4428
+ return /* @__PURE__ */ jsx(MultiCombobox$1, {
5298
4429
  ...props,
5299
4430
  value: value ? [value] : [],
5300
4431
  options,
@@ -5304,7 +4435,7 @@ function AutoCrudCombobox({ options = [], className, value, onChange,...props })
5304
4435
  });
5305
4436
  }
5306
4437
  function AutoCrudMultiCombobox({ options = [],...props }) {
5307
- return /* @__PURE__ */ jsx(MultiCombobox, {
4438
+ return /* @__PURE__ */ jsx(MultiCombobox$1, {
5308
4439
  ...props,
5309
4440
  options
5310
4441
  });
@@ -5323,99 +4454,280 @@ const defaultFieldComponents = {
5323
4454
  decorator: "FormItem"
5324
4455
  }
5325
4456
  };
5326
- function useRegistryVersion$1(subscribe) {
5327
- const [version, setVersion] = useState(0);
5328
- useEffect(() => subscribe(() => setVersion((current) => current + 1)), [subscribe]);
5329
- return version;
4457
+ const AUTO_CRUD_DATA_SOURCE_SCOPE_KEY = "$autoCrudDataSource";
4458
+ const POPUP_SCROLL_LOAD_THRESHOLD$1 = 24;
4459
+ const dataSourceReactionStates = /* @__PURE__ */ new WeakMap();
4460
+ function createBoundAction(callback) {
4461
+ return action.bound?.(callback) ?? ((...args) => action(() => callback(...args)));
4462
+ }
4463
+ function useRegistryVersion$1(subscribe, onChange) {
4464
+ const [version$1, setVersion] = useState(0);
4465
+ useEffect(() => subscribe(() => {
4466
+ onChange?.();
4467
+ setVersion((current) => current + 1);
4468
+ }), [onChange, subscribe]);
4469
+ return version$1;
5330
4470
  }
5331
4471
  function isDataSourceConfig(value) {
5332
4472
  return typeof value === "string" && value.length > 0;
5333
4473
  }
5334
- function collectDataSourceConfigs(schema, result = {}) {
4474
+ function getSchemaDataSourceConfig(schema) {
4475
+ const customDataSource = schema["x-data-source"];
4476
+ const schemaDataSource = schema.dataSource;
4477
+ return isDataSourceConfig(customDataSource) ? customDataSource : isDataSourceConfig(schemaDataSource) ? schemaDataSource : void 0;
4478
+ }
4479
+ function mergeReactions(existing, reaction) {
4480
+ if (existing == null) return reaction;
4481
+ if (existing === reaction) return existing;
4482
+ if (Array.isArray(existing)) return existing.includes(reaction) ? existing : [...existing, reaction];
4483
+ return [existing, reaction];
4484
+ }
4485
+ function applyDataSourceReactions(schema) {
4486
+ const dataSourceConfig = getSchemaDataSourceConfig(schema);
4487
+ if (dataSourceConfig) {
4488
+ const reaction = `{{${AUTO_CRUD_DATA_SOURCE_SCOPE_KEY}(${JSON.stringify(dataSourceConfig)})}}`;
4489
+ schema["x-reactions"] = mergeReactions(schema["x-reactions"], reaction);
4490
+ }
5335
4491
  const properties = schema.properties;
5336
- if (!properties || typeof properties !== "object") return result;
5337
- for (const [key, property] of Object.entries(properties)) {
5338
- if (!property || typeof property !== "object" || Array.isArray(property)) continue;
5339
- const record = property;
5340
- const customDataSource = record["x-data-source"];
5341
- const schemaDataSource = record.dataSource;
5342
- const dataSourceConfig = isDataSourceConfig(customDataSource) ? customDataSource : isDataSourceConfig(schemaDataSource) ? schemaDataSource : void 0;
5343
- if (dataSourceConfig) result[key] = dataSourceConfig;
5344
- collectDataSourceConfigs(record, result);
4492
+ if (properties && typeof properties === "object" && !Array.isArray(properties)) {
4493
+ for (const property of Object.values(properties)) if (property && typeof property === "object" && !Array.isArray(property)) applyDataSourceReactions(property);
5345
4494
  }
5346
- return result;
4495
+ const items = schema.items;
4496
+ if (Array.isArray(items)) {
4497
+ for (const item of items) if (item && typeof item === "object") applyDataSourceReactions(item);
4498
+ } else if (items && typeof items === "object") applyDataSourceReactions(items);
4499
+ }
4500
+ function getDataSourceReactionState(field) {
4501
+ const existing = dataSourceReactionStates.get(field);
4502
+ if (existing) return existing;
4503
+ const next = {
4504
+ hasMore: false,
4505
+ initialized: false,
4506
+ appendLoading: false,
4507
+ options: [],
4508
+ page: 0,
4509
+ requestVersion: 0
4510
+ };
4511
+ dataSourceReactionStates.set(field, next);
4512
+ return next;
4513
+ }
4514
+ function mergeDataSourceOptions(current, incoming) {
4515
+ const optionsByValue = new Map(current.map((option) => [option.value, option]));
4516
+ for (const option of incoming) optionsByValue.set(option.value, option);
4517
+ return Array.from(optionsByValue.values());
4518
+ }
4519
+ function isNearPopupScrollBottom$1(target) {
4520
+ return target.scrollHeight - target.scrollTop - target.clientHeight <= POPUP_SCROLL_LOAD_THRESHOLD$1;
4521
+ }
4522
+ function getDataSourceDependencyValues(entry, field) {
4523
+ return Object.fromEntries(entry.dependencies.map((dependency) => [dependency, field.form.getValuesIn(dependency)]));
4524
+ }
4525
+ function loadAutoCrudDataSource({ entry, field, registryVersion, search, sourceKey, state, append = false }) {
4526
+ const values = getDataSourceDependencyValues(entry, field);
4527
+ const dependencyKey = JSON.stringify(values);
4528
+ const searchValue = entry.search ? search ?? "" : void 0;
4529
+ const page = entry.loadMore ? append ? state.page + 1 : 1 : void 0;
4530
+ const loadKey = JSON.stringify({
4531
+ values,
4532
+ search: searchValue
4533
+ });
4534
+ if (!append && state.loadKey === loadKey && state.registryVersion === registryVersion) return;
4535
+ if (entry.reset && state.dependencyKey !== void 0 && state.dependencyKey !== dependencyKey) field.setValue(void 0);
4536
+ state.dependencyKey = dependencyKey;
4537
+ if (!append) {
4538
+ state.loadKey = loadKey;
4539
+ state.options = [];
4540
+ state.page = 0;
4541
+ state.hasMore = false;
4542
+ }
4543
+ state.registryVersion = registryVersion;
4544
+ state.controller?.abort();
4545
+ const requestVersion = state.requestVersion + 1;
4546
+ state.requestVersion = requestVersion;
4547
+ const controller = typeof AbortController === "undefined" ? void 0 : new AbortController();
4548
+ state.controller = controller;
4549
+ if (append) {
4550
+ state.appendLoading = true;
4551
+ field.setComponentProps({ loading: true });
4552
+ } else {
4553
+ field.setLoading(true);
4554
+ field.setComponentProps({
4555
+ loading: true,
4556
+ ...entry.loadMore ? { hasMore: false } : {}
4557
+ });
4558
+ }
4559
+ Promise.resolve(entry.load({
4560
+ field: field.path.toString(),
4561
+ page,
4562
+ pageSize: entry.loadMore ? entry.pageSize : void 0,
4563
+ search: searchValue,
4564
+ values,
4565
+ signal: controller?.signal
4566
+ })).then(createBoundAction((result) => {
4567
+ if (dataSourceReactionStates.get(field)?.requestVersion !== requestVersion || controller?.signal.aborted) return;
4568
+ const options = normalizeOptions(result);
4569
+ const nextOptions = append ? mergeDataSourceOptions(state.options, options) : options;
4570
+ state.options = nextOptions;
4571
+ state.page = page ?? 0;
4572
+ state.hasMore = entry.loadMore ? normalizeHasMore(result) : false;
4573
+ state.appendLoading = false;
4574
+ field.setDataSource(nextOptions);
4575
+ field.setLoading(false);
4576
+ field.setComponentProps({
4577
+ loading: false,
4578
+ ...entry.loadMore ? { hasMore: state.hasMore } : {}
4579
+ });
4580
+ }), createBoundAction((error) => {
4581
+ if (dataSourceReactionStates.get(field)?.requestVersion !== requestVersion || controller?.signal.aborted) return;
4582
+ console.warn(`[AutoCrud] Failed to load data source "${sourceKey}".`, error);
4583
+ if (!append) {
4584
+ state.options = [];
4585
+ state.hasMore = false;
4586
+ field.setDataSource([]);
4587
+ }
4588
+ state.appendLoading = false;
4589
+ field.setLoading(false);
4590
+ field.setComponentProps({
4591
+ loading: false,
4592
+ ...entry.loadMore ? { hasMore: state.hasMore } : {}
4593
+ });
4594
+ }));
5347
4595
  }
5348
- function useFormDataSources(form, schema) {
5349
- useEffect(() => {
5350
- const sourceEntries = Object.entries(collectDataSourceConfigs(schema)).map(([fieldName, config]) => {
5351
- const normalized = normalizeDataSourceConfig(config);
5352
- return normalized ? [fieldName, normalized] : void 0;
5353
- }).filter((entry) => entry !== void 0);
5354
- if (sourceEntries.length === 0) return;
5355
- let active = true;
5356
- const effectId = Symbol("autoCrudDataSources");
5357
- const loadVersions = /* @__PURE__ */ new Map();
5358
- const controller = typeof AbortController === "undefined" ? void 0 : new AbortController();
5359
- const loadFieldOptions = async (fieldName, source) => {
5360
- const version = (loadVersions.get(fieldName) ?? 0) + 1;
5361
- loadVersions.set(fieldName, version);
5362
- const entry = dataSources.get(source.key);
5363
- if (!entry) {
5364
- form.setFieldState(fieldName, (state) => {
5365
- state.dataSource = [];
5366
- });
5367
- return;
5368
- }
5369
- try {
5370
- const options = normalizeOptions(await entry.load({
5371
- field: fieldName,
5372
- values: Object.fromEntries(entry.dependencies.map((dependency) => [dependency, form.getValuesIn(dependency)])),
5373
- signal: controller?.signal
5374
- }));
5375
- if (!active || loadVersions.get(fieldName) !== version) return;
5376
- form.setFieldState(fieldName, (state) => {
5377
- state.dataSource = options;
4596
+ function applySearchComponentProps({ entry, field, registryState, sourceKey, state }) {
4597
+ if (!entry.search) return;
4598
+ field.setComponentProps({
4599
+ searchValue: state.search ?? "",
4600
+ shouldFilter: false,
4601
+ onSearch: (search) => {
4602
+ if (state.search === search) return;
4603
+ state.search = search;
4604
+ field.setComponentProps({ searchValue: search });
4605
+ if (state.searchTimer) clearTimeout(state.searchTimer);
4606
+ state.searchTimer = setTimeout(() => {
4607
+ const currentEntry = dataSources.get(sourceKey);
4608
+ if (!currentEntry) {
4609
+ state.controller?.abort();
4610
+ field.setDataSource([]);
4611
+ field.setLoading(false);
4612
+ field.setComponentProps({ loading: false });
4613
+ return;
4614
+ }
4615
+ loadAutoCrudDataSource({
4616
+ entry: currentEntry,
4617
+ field,
4618
+ registryVersion: registryState.version,
4619
+ search,
4620
+ sourceKey,
4621
+ state
5378
4622
  });
5379
- } catch (error) {
5380
- if (!active || controller?.signal.aborted) return;
5381
- console.warn(`[AutoCrud] Failed to load data source "${source.key}".`, error);
5382
- form.setFieldState(fieldName, (state) => {
5383
- state.dataSource = [];
4623
+ }, entry.debounceMs);
4624
+ }
4625
+ });
4626
+ }
4627
+ function applyLoadMoreComponentProps({ entry, field, registryState, sourceKey, state }) {
4628
+ if (!entry.loadMore) return;
4629
+ field.setComponentProps({
4630
+ hasMore: state.hasMore,
4631
+ loading: state.appendLoading,
4632
+ onPopupScroll: (event) => {
4633
+ if (!isNearPopupScrollBottom$1(event.currentTarget)) return;
4634
+ if (!state.hasMore || state.appendLoading) return;
4635
+ const currentEntry = dataSources.get(sourceKey);
4636
+ if (!currentEntry) {
4637
+ state.controller?.abort();
4638
+ state.hasMore = false;
4639
+ state.appendLoading = false;
4640
+ field.setDataSource([]);
4641
+ field.setLoading(false);
4642
+ field.setComponentProps({
4643
+ hasMore: false,
4644
+ loading: false
5384
4645
  });
4646
+ return;
5385
4647
  }
5386
- };
5387
- for (const [fieldName, source] of sourceEntries) loadFieldOptions(fieldName, source);
5388
- form.addEffects(effectId, () => {
5389
- for (const dependency of new Set(sourceEntries.flatMap(([, source]) => dataSources.get(source.key)?.dependencies ?? []))) onFieldValueChange(dependency, () => {
5390
- for (const [fieldName, source] of sourceEntries) {
5391
- const entry = dataSources.get(source.key);
5392
- if (!entry?.dependencies.includes(dependency)) continue;
5393
- if (entry.reset) form.setValuesIn(fieldName, void 0);
5394
- loadFieldOptions(fieldName, source);
5395
- }
4648
+ loadAutoCrudDataSource({
4649
+ append: true,
4650
+ entry: currentEntry,
4651
+ field,
4652
+ registryVersion: registryState.version,
4653
+ search: state.search,
4654
+ sourceKey,
4655
+ state
5396
4656
  });
4657
+ }
4658
+ });
4659
+ }
4660
+ function createAutoCrudDataSourceReaction(config, registryState) {
4661
+ const source = normalizeDataSourceConfig(config);
4662
+ return (field) => {
4663
+ const registryVersion = registryState.version;
4664
+ const state = getDataSourceReactionState(field);
4665
+ if (!state.initialized) {
4666
+ field.disposers.push(() => {
4667
+ state.controller?.abort();
4668
+ if (state.searchTimer) clearTimeout(state.searchTimer);
4669
+ });
4670
+ state.initialized = true;
4671
+ }
4672
+ if (!source) {
4673
+ field.setDataSource([]);
4674
+ field.setLoading(false);
4675
+ return;
4676
+ }
4677
+ const entry = dataSources.get(source.key);
4678
+ if (!entry) {
4679
+ state.controller?.abort();
4680
+ field.setDataSource([]);
4681
+ field.setLoading(false);
4682
+ return;
4683
+ }
4684
+ applySearchComponentProps({
4685
+ entry,
4686
+ field,
4687
+ registryState,
4688
+ sourceKey: source.key,
4689
+ state
5397
4690
  });
5398
- return () => {
5399
- active = false;
5400
- controller?.abort();
5401
- form.removeEffects(effectId);
5402
- };
5403
- }, [
5404
- form,
5405
- schema,
5406
- useRegistryVersion$1(dataSources.subscribe)
5407
- ]);
4691
+ applyLoadMoreComponentProps({
4692
+ entry,
4693
+ field,
4694
+ registryState,
4695
+ sourceKey: source.key,
4696
+ state
4697
+ });
4698
+ loadAutoCrudDataSource({
4699
+ entry,
4700
+ field,
4701
+ registryVersion,
4702
+ search: state.search,
4703
+ sourceKey: source.key,
4704
+ state
4705
+ });
4706
+ };
5408
4707
  }
5409
- function AutoFormInner({ schema: zodSchema, initialValues, onSubmit, overrides, mode = "create", loading = false, gridColumns = 1, labelAlign = "top", labelWidth, showSubmitButton = true }, ref) {
4708
+ function AutoFormInner({ schema: zodSchema, initialValues, onSubmit, fields, overrides, scope: scopeProp, mode = "create", loading = false, gridColumns = 1, labelAlign = "top", labelWidth, showSubmitButton = true }, ref) {
5410
4709
  const form = useMemo(() => createForm({ initialValues }), [JSON.stringify(initialValues)]);
5411
- const formSchema = useMemo(() => createEditFormSchema(zodSchema, {
5412
- overrides,
5413
- layout: "grid",
5414
- gridColumns
5415
- }), [
4710
+ const dataSourceRegistryState = useMemo(() => observable({ version: 0 }), []);
4711
+ const updateDataSourceRegistryState = useCallback(() => {
4712
+ action(() => {
4713
+ dataSourceRegistryState.version += 1;
4714
+ });
4715
+ }, [dataSourceRegistryState]);
4716
+ const dataSourceRegistryVersion = useRegistryVersion$1(dataSources.subscribe, updateDataSourceRegistryState);
4717
+ const formOverrides = useMemo(() => buildFormOverrides(fields, overrides), [fields, overrides]);
4718
+ const formSchema = useMemo(() => {
4719
+ const nextSchema = createEditFormSchema(zodSchema, {
4720
+ overrides: formOverrides,
4721
+ layout: "grid",
4722
+ gridColumns
4723
+ });
4724
+ applyDataSourceReactions(nextSchema);
4725
+ return nextSchema;
4726
+ }, [
5416
4727
  zodSchema,
5417
- overrides,
5418
- gridColumns
4728
+ formOverrides,
4729
+ gridColumns,
4730
+ dataSourceRegistryVersion
5419
4731
  ]);
5420
4732
  const formLayout = useMemo(() => {
5421
4733
  const inlineLabel = labelAlign === "left" || labelAlign === "right";
@@ -5431,7 +4743,10 @@ function AutoFormInner({ schema: zodSchema, initialValues, onSubmit, overrides,
5431
4743
  ...defaultFieldComponents,
5432
4744
  ...components.all()
5433
4745
  } }), [useRegistryVersion$1(components.subscribe)]);
5434
- useFormDataSources(form, formSchema);
4746
+ const scope = useMemo(() => ({
4747
+ ...scopeProp,
4748
+ [AUTO_CRUD_DATA_SOURCE_SCOPE_KEY]: (config) => createAutoCrudDataSourceReaction(config, dataSourceRegistryState)
4749
+ }), [dataSourceRegistryState, scopeProp]);
5435
4750
  const handleSubmit = async () => {
5436
4751
  await form.validate();
5437
4752
  if (form.valid) await onSubmit(form.values);
@@ -5442,7 +4757,8 @@ function AutoFormInner({ schema: zodSchema, initialValues, onSubmit, overrides,
5442
4757
  layout: formLayout,
5443
4758
  children: [/* @__PURE__ */ jsx(JsonSchemaField, {
5444
4759
  schema: formSchema,
5445
- components: fieldComponents
4760
+ components: fieldComponents,
4761
+ scope
5446
4762
  }), showSubmitButton && /* @__PURE__ */ jsx("div", {
5447
4763
  className: "flex justify-end gap-2 mt-6",
5448
4764
  children: /* @__PURE__ */ jsx(Button, {
@@ -5491,6 +4807,12 @@ const zhCN = {
5491
4807
  confirm: "确认删除",
5492
4808
  confirming: "删除中..."
5493
4809
  },
4810
+ bulkDeleteModal: {
4811
+ title: "确认批量删除",
4812
+ description: (count) => `此操作无法撤销。确定要删除选中的 ${count} 条记录吗?`,
4813
+ cancel: "取消",
4814
+ confirm: "确认删除"
4815
+ },
5494
4816
  importDialog: {
5495
4817
  title: "导入数据",
5496
4818
  uploadDescription: "上传 CSV 或 JSON 文件以批量导入数据",
@@ -5551,6 +4873,12 @@ const enUS = {
5551
4873
  confirm: "Delete",
5552
4874
  confirming: "Deleting..."
5553
4875
  },
4876
+ bulkDeleteModal: {
4877
+ title: "Confirm Bulk Delete",
4878
+ description: (count) => `This action cannot be undone. Are you sure you want to delete ${count} selected ${count === 1 ? "record" : "records"}?`,
4879
+ cancel: "Cancel",
4880
+ confirm: "Delete"
4881
+ },
5554
4882
  importDialog: {
5555
4883
  title: "Import Data",
5556
4884
  uploadDescription: "Upload a CSV or JSON file to bulk import data",
@@ -5611,6 +4939,12 @@ const jaJP = {
5611
4939
  confirm: "削除する",
5612
4940
  confirming: "削除中..."
5613
4941
  },
4942
+ bulkDeleteModal: {
4943
+ title: "一括削除の確認",
4944
+ description: (count) => `この操作は元に戻せません。選択した${count}件のレコードを削除してもよろしいですか?`,
4945
+ cancel: "キャンセル",
4946
+ confirm: "削除する"
4947
+ },
5614
4948
  importDialog: {
5615
4949
  title: "データのインポート",
5616
4950
  uploadDescription: "CSVまたはJSONファイルをアップロードして一括インポート",
@@ -5671,6 +5005,12 @@ const koKR = {
5671
5005
  confirm: "삭제",
5672
5006
  confirming: "삭제 중..."
5673
5007
  },
5008
+ bulkDeleteModal: {
5009
+ title: "일괄 삭제 확인",
5010
+ description: (count) => `이 작업은 취소할 수 없습니다. 선택한 ${count}개 레코드를 삭제하시겠습니까?`,
5011
+ cancel: "취소",
5012
+ confirm: "삭제"
5013
+ },
5674
5014
  importDialog: {
5675
5015
  title: "데이터 가져오기",
5676
5016
  uploadDescription: "CSV 또는 JSON 파일을 업로드하여 일괄 가져오기",
@@ -5731,6 +5071,12 @@ const frFR = {
5731
5071
  confirm: "Supprimer",
5732
5072
  confirming: "Suppression..."
5733
5073
  },
5074
+ bulkDeleteModal: {
5075
+ title: "Confirmer la suppression en masse",
5076
+ description: (count) => `Cette action est irréversible. Voulez-vous vraiment supprimer les ${count} enregistrements sélectionnés ?`,
5077
+ cancel: "Annuler",
5078
+ confirm: "Supprimer"
5079
+ },
5734
5080
  importDialog: {
5735
5081
  title: "Importer des données",
5736
5082
  uploadDescription: "Téléchargez un fichier CSV ou JSON pour importer en masse",
@@ -5791,6 +5137,12 @@ const deDE = {
5791
5137
  confirm: "Löschen",
5792
5138
  confirming: "Wird gelöscht..."
5793
5139
  },
5140
+ bulkDeleteModal: {
5141
+ title: "Mehrfaches Löschen bestätigen",
5142
+ description: (count) => `Diese Aktion kann nicht rückgängig gemacht werden. Möchten Sie die ${count} ausgewählten Datensätze wirklich löschen?`,
5143
+ cancel: "Abbrechen",
5144
+ confirm: "Löschen"
5145
+ },
5794
5146
  importDialog: {
5795
5147
  title: "Daten importieren",
5796
5148
  uploadDescription: "CSV- oder JSON-Datei hochladen, um Daten massenweise zu importieren",
@@ -5851,6 +5203,12 @@ const esES = {
5851
5203
  confirm: "Eliminar",
5852
5204
  confirming: "Eliminando..."
5853
5205
  },
5206
+ bulkDeleteModal: {
5207
+ title: "Confirmar eliminación masiva",
5208
+ description: (count) => `Esta acción no se puede deshacer. ¿Está seguro de que desea eliminar los ${count} registros seleccionados?`,
5209
+ cancel: "Cancelar",
5210
+ confirm: "Eliminar"
5211
+ },
5854
5212
  importDialog: {
5855
5213
  title: "Importar datos",
5856
5214
  uploadDescription: "Suba un archivo CSV o JSON para importar datos de forma masiva",
@@ -5910,7 +5268,7 @@ function resolveLocale(localeProp) {
5910
5268
 
5911
5269
  //#endregion
5912
5270
  //#region src/components/auto-crud/crud-form-modal.tsx
5913
- function CrudFormModal({ open, onOpenChange, mode, schema, initialValues, onSubmit, loading = false, variant = "dialog", overrides, title, locale = zhCN.formModal, gridColumns, labelAlign, labelWidth, className }) {
5271
+ function CrudFormModal({ open, onOpenChange, mode, schema, initialValues, onSubmit, loading = false, variant = "dialog", overrides, scope, title, locale = zhCN.formModal, gridColumns, labelAlign, labelWidth, className }) {
5914
5272
  const defaultTitle = mode === "create" ? locale.createTitle : locale.editTitle;
5915
5273
  const formRef = useRef(null);
5916
5274
  const handleSubmit = async () => {
@@ -5944,6 +5302,7 @@ function CrudFormModal({ open, onOpenChange, mode, schema, initialValues, onSubm
5944
5302
  loading,
5945
5303
  onSubmit,
5946
5304
  overrides,
5305
+ scope,
5947
5306
  gridColumns,
5948
5307
  labelAlign,
5949
5308
  labelWidth,
@@ -6022,7 +5381,7 @@ function ImportDialog({ open, onOpenChange, onImport, columns = [], title, local
6022
5381
  open,
6023
5382
  onOpenChange: handleOpenChange,
6024
5383
  children: /* @__PURE__ */ jsxs(DialogContent, {
6025
- className: "sm:max-w-[600px]",
5384
+ className: "w-[calc(100vw-2rem)] sm:max-w-5xl",
6026
5385
  children: [/* @__PURE__ */ jsxs(DialogHeader, { children: [/* @__PURE__ */ jsx(DialogTitle, { children: title ?? locale.title }), /* @__PURE__ */ jsxs(DialogDescription, { children: [
6027
5386
  step === "upload" && locale.uploadDescription,
6028
5387
  step === "preview" && locale.previewDescription(parsedData?.rows.length ?? 0),
@@ -6083,41 +5442,30 @@ function ImportDialog({ open, onOpenChange, onImport, columns = [], title, local
6083
5442
  /* @__PURE__ */ jsx("div", {
6084
5443
  className: "rounded-md border",
6085
5444
  children: /* @__PURE__ */ jsx("div", {
6086
- className: "max-h-[300px] overflow-auto",
5445
+ className: "max-h-[45vh] overflow-auto",
6087
5446
  children: /* @__PURE__ */ jsxs("table", {
6088
- className: "w-full text-sm",
5447
+ className: "min-w-max text-sm",
6089
5448
  children: [/* @__PURE__ */ jsx("thead", {
6090
5449
  className: "sticky top-0 bg-muted",
6091
- children: /* @__PURE__ */ jsxs("tr", { children: [
6092
- /* @__PURE__ */ jsx("th", {
6093
- className: "px-3 py-2 text-left font-medium text-muted-foreground",
6094
- children: "#"
6095
- }),
6096
- parsedData.headers.slice(0, 6).map((h) => /* @__PURE__ */ jsx("th", {
6097
- className: "px-3 py-2 text-left font-medium text-muted-foreground",
6098
- children: h
6099
- }, h)),
6100
- parsedData.headers.length > 6 && /* @__PURE__ */ jsx("th", {
6101
- className: "px-3 py-2 text-left font-medium text-muted-foreground",
6102
- children: locale.moreColumns(parsedData.headers.length - 6)
6103
- })
6104
- ] })
5450
+ children: /* @__PURE__ */ jsxs("tr", { children: [/* @__PURE__ */ jsx("th", {
5451
+ className: "sticky left-0 z-10 bg-muted px-3 py-2 text-left font-medium text-muted-foreground",
5452
+ children: "#"
5453
+ }), parsedData.headers.map((h) => /* @__PURE__ */ jsx("th", {
5454
+ className: "max-w-[180px] whitespace-nowrap px-3 py-2 text-left font-medium text-muted-foreground",
5455
+ children: h
5456
+ }, h))] })
6105
5457
  }), /* @__PURE__ */ jsx("tbody", { children: parsedData.rows.slice(0, 10).map((row, i) => /* @__PURE__ */ jsxs("tr", {
6106
5458
  className: "border-t",
6107
- children: [
6108
- /* @__PURE__ */ jsx("td", {
6109
- className: "px-3 py-2 text-muted-foreground",
6110
- children: i + 1
6111
- }),
6112
- parsedData.headers.slice(0, 6).map((h) => /* @__PURE__ */ jsx("td", {
6113
- className: "px-3 py-2 max-w-[150px] truncate",
5459
+ children: [/* @__PURE__ */ jsx("td", {
5460
+ className: "sticky left-0 bg-background px-3 py-2 text-muted-foreground",
5461
+ children: i + 1
5462
+ }), parsedData.headers.map((h) => /* @__PURE__ */ jsx("td", {
5463
+ className: "max-w-[180px] truncate px-3 py-2",
5464
+ children: /* @__PURE__ */ jsx("span", {
5465
+ title: String(row[h] ?? ""),
6114
5466
  children: String(row[h] ?? "")
6115
- }, h)),
6116
- parsedData.headers.length > 6 && /* @__PURE__ */ jsx("td", {
6117
- className: "px-3 py-2 text-muted-foreground",
6118
- children: "..."
6119
5467
  })
6120
- ]
5468
+ }, h))]
6121
5469
  }, i)) })]
6122
5470
  })
6123
5471
  })
@@ -6241,6 +5589,153 @@ function ImportDialog({ open, onOpenChange, onImport, columns = [], title, local
6241
5589
  });
6242
5590
  }
6243
5591
 
5592
+ //#endregion
5593
+ //#region src/lib/crud-actions.ts
5594
+ const entries = /* @__PURE__ */ new Map();
5595
+ const index = /* @__PURE__ */ new Map();
5596
+ const listeners = /* @__PURE__ */ new Set();
5597
+ let seq = 0;
5598
+ let version = 0;
5599
+ let notifyScheduled = false;
5600
+ function actionKey(registration, action$1, index$1) {
5601
+ return [
5602
+ registration.ownerId,
5603
+ registration.targetId,
5604
+ registration.zone,
5605
+ action$1.id ?? action$1.type,
5606
+ index$1
5607
+ ].join(":");
5608
+ }
5609
+ function indexKey(targetId, zone) {
5610
+ return `${targetId}:${zone}`;
5611
+ }
5612
+ function addToIndex(key, entry) {
5613
+ const bucketKey = indexKey(entry.targetId, entry.zone);
5614
+ const bucket = index.get(bucketKey) ?? /* @__PURE__ */ new Set();
5615
+ bucket.add(key);
5616
+ index.set(bucketKey, bucket);
5617
+ }
5618
+ function removeFromIndex(key, entry) {
5619
+ const bucketKey = indexKey(entry.targetId, entry.zone);
5620
+ const bucket = index.get(bucketKey);
5621
+ if (!bucket) return;
5622
+ bucket.delete(key);
5623
+ if (bucket.size === 0) index.delete(bucketKey);
5624
+ }
5625
+ function notify() {
5626
+ version += 1;
5627
+ if (notifyScheduled) return;
5628
+ notifyScheduled = true;
5629
+ queueMicrotask(() => {
5630
+ notifyScheduled = false;
5631
+ for (const listener of listeners) listener();
5632
+ });
5633
+ }
5634
+ function unregisterOwnerActions(ownerId, options = {}, shouldNotify = true) {
5635
+ let changed = false;
5636
+ for (const [key, entry] of entries) {
5637
+ if (entry.ownerId !== ownerId) continue;
5638
+ if (options.targetId && entry.targetId !== options.targetId) continue;
5639
+ if (options.zone && entry.zone !== options.zone) continue;
5640
+ entries.delete(key);
5641
+ removeFromIndex(key, entry);
5642
+ changed = true;
5643
+ }
5644
+ if (changed && shouldNotify) notify();
5645
+ }
5646
+ function sortEntries(left, right) {
5647
+ const orderDiff = left.order - right.order;
5648
+ if (orderDiff !== 0) return orderDiff;
5649
+ const ownerDiff = left.ownerId.localeCompare(right.ownerId);
5650
+ if (ownerDiff !== 0) return ownerDiff;
5651
+ return left.seq - right.seq;
5652
+ }
5653
+ function withoutRegistryMeta(action$1) {
5654
+ const { id, order,...rest } = action$1;
5655
+ return rest;
5656
+ }
5657
+ function isCustomAction$1(action$1) {
5658
+ return action$1.type === "custom";
5659
+ }
5660
+ function resolveActions$1(targetId, zone, ownerActions) {
5661
+ const baseActions = ownerActions.filter((action$1) => !action$1.hidden).map((action$1) => withoutRegistryMeta(action$1));
5662
+ if (!targetId) return baseActions;
5663
+ const registered = crudActions.get(targetId, zone);
5664
+ if (registered.length === 0) return baseActions;
5665
+ const nextActions = [...baseActions];
5666
+ const startCustomActions = [];
5667
+ const endCustomActions = [];
5668
+ for (const entry of registered) {
5669
+ const action$1 = entry.action;
5670
+ if (action$1.hidden && isCustomAction$1(action$1)) continue;
5671
+ if (isCustomAction$1(action$1)) {
5672
+ const custom = withoutRegistryMeta(action$1);
5673
+ if (custom.position === "start") startCustomActions.push(custom);
5674
+ else endCustomActions.push(custom);
5675
+ continue;
5676
+ }
5677
+ const existingIndex = nextActions.findIndex((item) => item.type === action$1.type);
5678
+ if (action$1.hidden) {
5679
+ if (existingIndex >= 0) nextActions.splice(existingIndex, 1);
5680
+ continue;
5681
+ }
5682
+ const builtin = withoutRegistryMeta(action$1);
5683
+ if (existingIndex >= 0) nextActions[existingIndex] = {
5684
+ ...nextActions[existingIndex],
5685
+ ...builtin
5686
+ };
5687
+ else nextActions.push(builtin);
5688
+ }
5689
+ return [
5690
+ ...startCustomActions,
5691
+ ...nextActions,
5692
+ ...endCustomActions
5693
+ ];
5694
+ }
5695
+ const crudActions = {
5696
+ register(registration) {
5697
+ unregisterOwnerActions(registration.ownerId, {
5698
+ targetId: registration.targetId,
5699
+ zone: registration.zone
5700
+ }, false);
5701
+ registration.actions.forEach((action$1, index$1) => {
5702
+ const key = actionKey(registration, action$1, index$1);
5703
+ const entry = {
5704
+ targetId: registration.targetId,
5705
+ zone: registration.zone,
5706
+ ownerId: registration.ownerId,
5707
+ action: action$1,
5708
+ order: action$1.order ?? 100,
5709
+ seq: seq++
5710
+ };
5711
+ entries.set(key, entry);
5712
+ addToIndex(key, entry);
5713
+ });
5714
+ notify();
5715
+ },
5716
+ unregister: unregisterOwnerActions,
5717
+ clear() {
5718
+ if (entries.size === 0) return;
5719
+ entries.clear();
5720
+ index.clear();
5721
+ notify();
5722
+ },
5723
+ get(targetId, zone) {
5724
+ return Array.from(index.get(indexKey(targetId, zone)) ?? []).flatMap((key) => {
5725
+ const entry = entries.get(key);
5726
+ return entry ? [entry] : [];
5727
+ }).sort(sortEntries);
5728
+ },
5729
+ resolve: resolveActions$1,
5730
+ subscribe(listener) {
5731
+ listeners.add(listener);
5732
+ return () => listeners.delete(listener);
5733
+ },
5734
+ getSnapshot() {
5735
+ return version;
5736
+ }
5737
+ };
5738
+
6244
5739
  //#endregion
6245
5740
  //#region src/components/auto-crud/auto-crud-table.tsx
6246
5741
  let autoCrudToolbarResolver = null;
@@ -6254,28 +5749,67 @@ function getDefaultToolbarActions() {
6254
5749
  { type: "create" }
6255
5750
  ];
6256
5751
  }
5752
+ function getDefaultRowActions() {
5753
+ return [
5754
+ { type: "view" },
5755
+ { type: "edit" },
5756
+ { type: "copy" },
5757
+ { type: "delete" }
5758
+ ];
5759
+ }
5760
+ function getDefaultBatchActions() {
5761
+ return [{ type: "batchUpdate" }, { type: "delete" }];
5762
+ }
5763
+ function isCustomAction(action$1) {
5764
+ return action$1.type === "custom";
5765
+ }
5766
+ function resolveOwnerActions(config, defaults) {
5767
+ const resolved = typeof config === "function" ? config([...defaults]) : config;
5768
+ const items = resolved !== void 0 && resolved.length > 0 ? [...resolved] : [];
5769
+ if (items.length === 0) return [...defaults];
5770
+ if (items.some((item) => !isCustomAction(item))) return items;
5771
+ const startItems = items.filter((item) => isCustomAction(item) && item.position === "start");
5772
+ const endItems = items.filter((item) => isCustomAction(item) && item.position !== "start");
5773
+ return [
5774
+ ...startItems,
5775
+ ...defaults,
5776
+ ...endItems
5777
+ ];
5778
+ }
6257
5779
  function resolveOwnerToolbarActions(toolbar) {
6258
- const resolved = typeof toolbar === "function" ? toolbar(getDefaultToolbarActions()) : toolbar;
6259
- return resolved !== void 0 && resolved.length > 0 ? [...resolved] : getDefaultToolbarActions();
5780
+ return resolveOwnerActions(toolbar, getDefaultToolbarActions());
5781
+ }
5782
+ function resolveOwnerRowActions(actions) {
5783
+ return resolveOwnerActions(actions, getDefaultRowActions());
5784
+ }
5785
+ function resolveOwnerBatchActions(actions) {
5786
+ return resolveOwnerActions(actions, getDefaultBatchActions());
5787
+ }
5788
+ function isUnifiedActionsConfig(actions) {
5789
+ return typeof actions === "object" && actions !== null && !Array.isArray(actions);
5790
+ }
5791
+ function readRowId(row, idKey) {
5792
+ const value = row[idKey];
5793
+ return value === null || value === void 0 ? void 0 : String(value);
6260
5794
  }
6261
5795
  function readRowIds(rows, idKey) {
6262
5796
  return rows.flatMap((row) => {
6263
- const value = row[idKey];
6264
- return value === null || value === void 0 ? [] : [String(value)];
5797
+ const value = readRowId(row, idKey);
5798
+ return value === void 0 ? [] : [value];
6265
5799
  });
6266
5800
  }
6267
5801
  function areStringArraysEqual(left, right) {
6268
- return left.length === right.length && left.every((value, index) => value === right[index]);
5802
+ return left.length === right.length && left.every((value, index$1) => value === right[index$1]);
6269
5803
  }
6270
5804
  function haveSameRowSelection(left, right, idKey) {
6271
5805
  if (left.length !== right.length) return false;
6272
5806
  const leftIds = readRowIds(left, idKey);
6273
5807
  const rightIds = readRowIds(right, idKey);
6274
5808
  if (leftIds.length === left.length && rightIds.length === right.length) return areStringArraysEqual(leftIds, rightIds);
6275
- return left.every((row, index) => row === right[index]);
5809
+ return left.every((row, index$1) => row === right[index$1]);
6276
5810
  }
6277
5811
  function readCreateAction(actions) {
6278
- return actions.find((action) => action.type === "create");
5812
+ return actions.find((action$1) => action$1.type === "create");
6279
5813
  }
6280
5814
  function resolveToolbarActionsWithResolver(targetId, ownerActions, context) {
6281
5815
  const resolver = autoCrudToolbarResolver;
@@ -6327,6 +5861,15 @@ function toBatchOptions(options) {
6327
5861
  value
6328
5862
  }));
6329
5863
  }
5864
+ const POPUP_SCROLL_LOAD_THRESHOLD = 24;
5865
+ function isNearPopupScrollBottom(target) {
5866
+ return target.scrollHeight - target.scrollTop - target.clientHeight <= POPUP_SCROLL_LOAD_THRESHOLD;
5867
+ }
5868
+ function mergeFilterOptions(current, incoming) {
5869
+ const optionsByValue = new Map((current ?? []).map((option) => [option.value, option]));
5870
+ for (const option of incoming) optionsByValue.set(option.value, option);
5871
+ return Array.from(optionsByValue.values());
5872
+ }
6330
5873
  function getFilterDataSource(config) {
6331
5874
  if (config.filter && typeof config.filter === "object") return config.filter.dataSource ?? config.dataSource;
6332
5875
  return config.dataSource;
@@ -6341,13 +5884,29 @@ function shouldLoadDynamicFilterOptions(config) {
6341
5884
  return normalizeDataSourceConfig(getFilterDataSource(config)) !== void 0;
6342
5885
  }
6343
5886
  function useRegistryVersion(subscribe) {
6344
- const [version, setVersion] = React.useState(0);
5887
+ const [version$1, setVersion] = React.useState(0);
6345
5888
  React.useEffect(() => subscribe(() => setVersion((current) => current + 1)), [subscribe]);
6346
- return version;
5889
+ return version$1;
5890
+ }
5891
+ function useCrudActionsVersion() {
5892
+ return React.useSyncExternalStore(crudActions.subscribe, crudActions.getSnapshot, crudActions.getSnapshot);
6347
5893
  }
6348
5894
  function useDynamicFilterOptions(fields) {
6349
5895
  const registryVersion = useRegistryVersion(dataSources.subscribe);
5896
+ const [searchValues, setSearchValues] = React.useState({});
5897
+ const [hasMoreByField, setHasMoreByField] = React.useState({});
5898
+ const [labelOptionsByField, setLabelOptionsByField] = React.useState({});
5899
+ const [loadingByField, setLoadingByField] = React.useState({});
6350
5900
  const [optionsByField, setOptionsByField] = React.useState({});
5901
+ const controllersRef = React.useRef({});
5902
+ const hasMoreByFieldRef = React.useRef({});
5903
+ const loadingByFieldRef = React.useRef({});
5904
+ const optionsByFieldRef = React.useRef({});
5905
+ const pageByFieldRef = React.useRef({});
5906
+ const requestVersionsRef = React.useRef({});
5907
+ const searchValuesRef = React.useRef({});
5908
+ const sourceEntriesByFieldRef = React.useRef({});
5909
+ const timersRef = React.useRef({});
6351
5910
  const sourceEntries = React.useMemo(() => {
6352
5911
  if (!fields) return [];
6353
5912
  return Object.entries(fields).flatMap(([field, config]) => {
@@ -6359,36 +5918,185 @@ function useDynamicFilterOptions(fields) {
6359
5918
  }] : [];
6360
5919
  });
6361
5920
  }, [fields]);
5921
+ const sourceEntriesByField = React.useMemo(() => Object.fromEntries(sourceEntries.map(({ field, source }) => [field, source])), [sourceEntries]);
6362
5922
  React.useEffect(() => {
6363
- if (sourceEntries.length === 0) {
6364
- setOptionsByField({});
5923
+ searchValuesRef.current = searchValues;
5924
+ }, [searchValues]);
5925
+ React.useEffect(() => {
5926
+ sourceEntriesByFieldRef.current = sourceEntriesByField;
5927
+ }, [sourceEntriesByField]);
5928
+ const registeredByField = React.useMemo(() => Object.fromEntries(sourceEntries.map(({ field, source }) => [field, dataSources.get(source.key) !== void 0])), [sourceEntries, registryVersion]);
5929
+ const searchableByField = React.useMemo(() => Object.fromEntries(sourceEntries.map(({ field, source }) => [field, dataSources.get(source.key)?.search === true])), [sourceEntries, registryVersion]);
5930
+ const setFieldState = React.useCallback((setter, ref, field, value) => {
5931
+ ref.current = {
5932
+ ...ref.current,
5933
+ [field]: value
5934
+ };
5935
+ setter(ref.current);
5936
+ }, []);
5937
+ const removeInactiveFieldState = React.useCallback((setter, ref, activeFields) => {
5938
+ const nextEntries = Object.entries(ref.current).filter(([field]) => activeFields.has(field));
5939
+ if (nextEntries.length === Object.keys(ref.current).length) return;
5940
+ ref.current = Object.fromEntries(nextEntries);
5941
+ setter(ref.current);
5942
+ }, []);
5943
+ const setFieldOptions = React.useCallback((field, options) => {
5944
+ optionsByFieldRef.current = {
5945
+ ...optionsByFieldRef.current,
5946
+ [field]: options
5947
+ };
5948
+ setOptionsByField(optionsByFieldRef.current);
5949
+ }, []);
5950
+ const setFieldLoading = React.useCallback((field, loading) => {
5951
+ setFieldState(setLoadingByField, loadingByFieldRef, field, loading);
5952
+ }, [setFieldState]);
5953
+ const setFieldHasMore = React.useCallback((field, hasMore) => {
5954
+ setFieldState(setHasMoreByField, hasMoreByFieldRef, field, hasMore);
5955
+ }, [setFieldState]);
5956
+ const clearFieldRequest = React.useCallback((field) => {
5957
+ const timer = timersRef.current[field];
5958
+ if (timer) clearTimeout(timer);
5959
+ timersRef.current[field] = void 0;
5960
+ controllersRef.current[field]?.abort();
5961
+ controllersRef.current[field] = void 0;
5962
+ }, []);
5963
+ const resetField = React.useCallback((field, { clearLabels = false } = {}) => {
5964
+ clearFieldRequest(field);
5965
+ setFieldOptions(field, []);
5966
+ setFieldLoading(field, false);
5967
+ setFieldHasMore(field, false);
5968
+ pageByFieldRef.current = {
5969
+ ...pageByFieldRef.current,
5970
+ [field]: 0
5971
+ };
5972
+ if (clearLabels) setLabelOptionsByField((current) => {
5973
+ const { [field]: _,...rest } = current;
5974
+ return rest;
5975
+ });
5976
+ }, [
5977
+ clearFieldRequest,
5978
+ setFieldHasMore,
5979
+ setFieldLoading,
5980
+ setFieldOptions
5981
+ ]);
5982
+ const loadFieldOptions = React.useCallback((field, append = false) => {
5983
+ if (append) {
5984
+ if (loadingByFieldRef.current[field] || !hasMoreByFieldRef.current[field]) return;
5985
+ }
5986
+ const source = sourceEntriesByFieldRef.current[field];
5987
+ const entry = source ? dataSources.get(source.key) : void 0;
5988
+ if (!source || !entry) {
5989
+ resetField(field);
6365
5990
  return;
6366
5991
  }
6367
- let active = true;
5992
+ const requestVersion = (requestVersionsRef.current[field] ?? 0) + 1;
5993
+ requestVersionsRef.current = {
5994
+ ...requestVersionsRef.current,
5995
+ [field]: requestVersion
5996
+ };
5997
+ clearFieldRequest(field);
6368
5998
  const controller = typeof AbortController === "undefined" ? void 0 : new AbortController();
6369
- Promise.all(sourceEntries.map(async ({ field, source }) => {
6370
- const entry = dataSources.get(source.key);
6371
- if (!entry) return [field, []];
6372
- try {
6373
- return [field, normalizeOptions(await entry.load({
6374
- field,
6375
- values: {},
6376
- signal: controller?.signal
6377
- }))];
6378
- } catch (error) {
6379
- if (!controller?.signal.aborted) console.warn(`[AutoCrud] Failed to load filter data source "${source.key}".`, error);
6380
- return [field, []];
6381
- }
6382
- })).then((entries) => {
6383
- if (!active) return;
6384
- setOptionsByField(Object.fromEntries(entries));
6385
- });
6386
- return () => {
6387
- active = false;
6388
- controller?.abort();
5999
+ controllersRef.current[field] = controller;
6000
+ const search = entry.search ? searchValuesRef.current[field] ?? "" : void 0;
6001
+ const page = entry.loadMore ? append ? (pageByFieldRef.current[field] ?? 0) + 1 : 1 : void 0;
6002
+ setFieldLoading(field, true);
6003
+ if (!append) setFieldHasMore(field, false);
6004
+ const timer = setTimeout(() => {
6005
+ Promise.resolve(entry.load({
6006
+ field,
6007
+ page,
6008
+ pageSize: entry.loadMore ? entry.pageSize : void 0,
6009
+ search,
6010
+ values: {},
6011
+ signal: controller?.signal
6012
+ })).then((result) => {
6013
+ if (requestVersionsRef.current[field] !== requestVersion || controller?.signal.aborted) return;
6014
+ const options = normalizeOptions(result);
6015
+ const nextOptions = append ? mergeFilterOptions(optionsByFieldRef.current[field], options) : options;
6016
+ const hasMore = normalizeHasMore(result);
6017
+ setFieldOptions(field, nextOptions);
6018
+ setLabelOptionsByField((current) => ({
6019
+ ...current,
6020
+ [field]: mergeFilterOptions(current[field], nextOptions)
6021
+ }));
6022
+ pageByFieldRef.current = {
6023
+ ...pageByFieldRef.current,
6024
+ [field]: page ?? 0
6025
+ };
6026
+ setFieldHasMore(field, hasMore);
6027
+ setFieldLoading(field, false);
6028
+ }, (error) => {
6029
+ if (controller?.signal.aborted) return;
6030
+ console.warn(`[AutoCrud] Failed to load filter data source "${source.key}".`, error);
6031
+ if (!append) {
6032
+ setFieldOptions(field, []);
6033
+ setFieldHasMore(field, false);
6034
+ pageByFieldRef.current = {
6035
+ ...pageByFieldRef.current,
6036
+ [field]: 0
6037
+ };
6038
+ }
6039
+ setFieldLoading(field, false);
6040
+ });
6041
+ }, !append && entry.search ? entry.debounceMs : 0);
6042
+ timersRef.current[field] = timer;
6043
+ }, [
6044
+ clearFieldRequest,
6045
+ resetField,
6046
+ setFieldHasMore,
6047
+ setFieldLoading,
6048
+ setFieldOptions
6049
+ ]);
6050
+ const setSearchValue = React.useCallback((field, value) => {
6051
+ searchValuesRef.current = {
6052
+ ...searchValuesRef.current,
6053
+ [field]: value
6389
6054
  };
6390
- }, [sourceEntries, registryVersion]);
6391
- return optionsByField;
6055
+ setSearchValues((current) => current[field] === value ? current : {
6056
+ ...current,
6057
+ [field]: value
6058
+ });
6059
+ }, []);
6060
+ React.useEffect(() => {
6061
+ if (sourceEntries.length === 0) {
6062
+ hasMoreByFieldRef.current = {};
6063
+ loadingByFieldRef.current = {};
6064
+ optionsByFieldRef.current = {};
6065
+ pageByFieldRef.current = {};
6066
+ Object.keys(controllersRef.current).forEach(clearFieldRequest);
6067
+ setHasMoreByField({});
6068
+ setLoadingByField({});
6069
+ setOptionsByField({});
6070
+ return;
6071
+ }
6072
+ const activeFields = new Set(sourceEntries.map(({ field }) => field));
6073
+ removeInactiveFieldState(setHasMoreByField, hasMoreByFieldRef, activeFields);
6074
+ removeInactiveFieldState(setLoadingByField, loadingByFieldRef, activeFields);
6075
+ removeInactiveFieldState(setOptionsByField, optionsByFieldRef, activeFields);
6076
+ for (const field of Object.keys(controllersRef.current)) if (!activeFields.has(field)) clearFieldRequest(field);
6077
+ for (const { field } of sourceEntries) loadFieldOptions(field);
6078
+ }, [
6079
+ clearFieldRequest,
6080
+ loadFieldOptions,
6081
+ removeInactiveFieldState,
6082
+ sourceEntries,
6083
+ registryVersion,
6084
+ searchValues
6085
+ ]);
6086
+ React.useEffect(() => () => {
6087
+ for (const field of Object.keys(controllersRef.current)) clearFieldRequest(field);
6088
+ }, [clearFieldRequest]);
6089
+ return {
6090
+ hasMoreByField,
6091
+ labelOptionsByField,
6092
+ loadingByField,
6093
+ optionsByField,
6094
+ registeredByField,
6095
+ searchValues,
6096
+ searchableByField,
6097
+ loadMore: (field) => loadFieldOptions(field, true),
6098
+ setSearchValue
6099
+ };
6392
6100
  }
6393
6101
  function getOptionLabel(value, options) {
6394
6102
  const stringValue = String(value);
@@ -6398,12 +6106,15 @@ function getOptionLabel(value, options) {
6398
6106
  * 从统一配置生成表格 overrides
6399
6107
  * 当 filter 配置存在时,合并到 meta 中(不影响列隐藏)
6400
6108
  */
6401
- function buildTableOverrides(fields, legacyOverrides, dynamicFilterOptions) {
6109
+ function buildTableOverrides(fields, legacyOverrides, dynamicFilterState) {
6402
6110
  const result = { ...legacyOverrides };
6403
6111
  if (fields) for (const [key, config] of Object.entries(fields)) {
6404
6112
  const fieldOptions = normalizeFieldOptions(config.enum);
6405
6113
  const tableOptions = toTableOptions(fieldOptions);
6406
- const dynamicOptions = !fieldOptions ? toTableOptions(normalizeFieldOptions(dynamicFilterOptions?.[key])) : void 0;
6114
+ const filterDataSourceRegistered = dynamicFilterState?.registeredByField[key];
6115
+ const filterDataSourceSearchable = dynamicFilterState?.searchableByField[key];
6116
+ const currentDynamicOptions = !fieldOptions && filterDataSourceRegistered ? toTableOptions(dynamicFilterState?.optionsByField[key] ?? []) : void 0;
6117
+ const dynamicOptions = !fieldOptions && filterDataSourceRegistered ? toTableOptions(dynamicFilterState?.labelOptionsByField[key] ?? dynamicFilterState?.optionsByField[key] ?? []) : void 0;
6407
6118
  const tableMeta = config.table !== false && typeof config.table === "object" ? config.table.meta : void 0;
6408
6119
  const fieldEnumMeta = tableOptions ? {
6409
6120
  options: tableOptions,
@@ -6411,7 +6122,19 @@ function buildTableOverrides(fields, legacyOverrides, dynamicFilterOptions) {
6411
6122
  } : void 0;
6412
6123
  const fieldDataSourceMeta = dynamicOptions ? {
6413
6124
  options: dynamicOptions,
6414
- variant: "multiSelect"
6125
+ autoCrudFilterOptions: currentDynamicOptions ?? dynamicOptions,
6126
+ autoCrudFilterHasMore: dynamicFilterState?.hasMoreByField[key] ?? false,
6127
+ autoCrudFilterLoading: dynamicFilterState?.loadingByField[key] ?? false,
6128
+ autoCrudFilterOnPopupScroll: (event) => {
6129
+ if (!isNearPopupScrollBottom(event.currentTarget)) return;
6130
+ dynamicFilterState?.loadMore(key);
6131
+ },
6132
+ variant: "multiSelect",
6133
+ ...filterDataSourceSearchable ? {
6134
+ autoCrudFilterSearchValue: dynamicFilterState?.searchValues[key] ?? "",
6135
+ autoCrudFilterOnSearch: (value) => dynamicFilterState?.setSearchValue(key, value),
6136
+ autoCrudFilterShouldFilter: false
6137
+ } : void 0
6415
6138
  } : void 0;
6416
6139
  let filterMeta;
6417
6140
  if (config.filter && typeof config.filter === "object") {
@@ -6463,46 +6186,6 @@ function buildTableOverrides(fields, legacyOverrides, dynamicFilterOptions) {
6463
6186
  return result;
6464
6187
  }
6465
6188
  /**
6466
- * 从统一配置生成表单 overrides
6467
- * Critical #2: 支持 denyFields 隐藏敏感字段
6468
- */
6469
- function buildFormOverrides(fields, legacyOverrides, denyFields) {
6470
- const result = { ...legacyOverrides };
6471
- if (denyFields) for (const field of denyFields) result[field] = {
6472
- ...result[field],
6473
- "x-hidden": true
6474
- };
6475
- if (fields) for (const [key, config] of Object.entries(fields)) {
6476
- const fieldOptions = normalizeFieldOptions((config.form && typeof config.form === "object" ? config.form : void 0)?.enum) ?? normalizeFieldOptions(config.enum);
6477
- const fieldDataSource = fieldOptions || !config.dataSource ? void 0 : normalizeDataSourceConfig(config.dataSource);
6478
- if (config.label) result[key] = {
6479
- ...result[key],
6480
- title: config.label
6481
- };
6482
- if (config.hidden) result[key] = {
6483
- ...result[key],
6484
- "x-hidden": true
6485
- };
6486
- if (fieldOptions) result[key] = {
6487
- ...result[key],
6488
- enum: fieldOptions
6489
- };
6490
- if (fieldDataSource) result[key] = {
6491
- ...result[key],
6492
- "x-data-source": config.dataSource
6493
- };
6494
- if (config.form === false) result[key] = {
6495
- ...result[key],
6496
- "x-hidden": true
6497
- };
6498
- else if (config.form && typeof config.form === "object") result[key] = {
6499
- ...result[key],
6500
- ...config.form
6501
- };
6502
- }
6503
- return result;
6504
- }
6505
- /**
6506
6189
  * 从统一配置生成隐藏列列表
6507
6190
  */
6508
6191
  function buildHiddenColumns(fields, legacyHidden, denyFields) {
@@ -6648,13 +6331,23 @@ function ViewModal({ open, onOpenChange, variant, data, schema, fields: fieldCon
6648
6331
  /**
6649
6332
  * 解析列表行操作
6650
6333
  */
6651
- function resolveActions(actionsOrFn, defaults, rowActionsLocale) {
6652
- const items = typeof actionsOrFn === "function" ? actionsOrFn([
6334
+ function resolveActions(actionsOrFn, defaults, rowActionsLocale, context) {
6335
+ const resolvedItems = typeof actionsOrFn === "function" ? actionsOrFn([
6653
6336
  { type: "view" },
6654
6337
  { type: "edit" },
6655
6338
  { type: "copy" },
6656
6339
  { type: "delete" }
6657
6340
  ]) : actionsOrFn;
6341
+ const getContext = (row) => ({
6342
+ crudId: context.crudId,
6343
+ idKey: context.idKey,
6344
+ row,
6345
+ rowId: readRowId(row, context.idKey),
6346
+ openView: defaults.openView,
6347
+ ...defaults.openEdit ? { openEdit: defaults.openEdit } : {},
6348
+ ...defaults.copyRow ? { copyRow: defaults.copyRow } : {},
6349
+ ...defaults.openDelete ? { openDelete: defaults.openDelete } : {}
6350
+ });
6658
6351
  const defaultItems = [
6659
6352
  {
6660
6353
  label: rowActionsLocale.view,
@@ -6675,17 +6368,23 @@ function resolveActions(actionsOrFn, defaults, rowActionsLocale) {
6675
6368
  variant: "destructive"
6676
6369
  }] : []
6677
6370
  ];
6678
- if (!items || items.length === 0) return defaultItems;
6371
+ if (resolvedItems === void 0) return defaultItems;
6372
+ const items = resolvedItems.filter((item) => !item.hidden);
6373
+ if (items.length === 0) return [];
6679
6374
  if (!items.some((i) => i.type !== "custom")) {
6680
- const startItems = items.filter((i) => i.type === "custom" && i.position === "start").map(({ label, onClick, separator, variant }) => ({
6375
+ const startItems = items.filter((i) => i.type === "custom" && i.position === "start").map(({ label, onClick, component, separator, variant }) => ({
6681
6376
  label,
6682
6377
  onClick,
6378
+ component,
6379
+ getContext,
6683
6380
  separator,
6684
6381
  variant
6685
6382
  }));
6686
- const endItems = items.filter((i) => i.type === "custom" && i.position !== "start").map(({ label, onClick, separator, variant }) => ({
6383
+ const endItems = items.filter((i) => i.type === "custom" && i.position !== "start").map(({ label, onClick, component, separator, variant }) => ({
6687
6384
  label,
6688
6385
  onClick,
6386
+ component,
6387
+ getContext,
6689
6388
  separator,
6690
6389
  variant
6691
6390
  }));
@@ -6712,6 +6411,8 @@ function resolveActions(actionsOrFn, defaults, rowActionsLocale) {
6712
6411
  if (item.type === "custom") return [{
6713
6412
  label: item.label,
6714
6413
  onClick: item.onClick,
6414
+ component: item.component,
6415
+ getContext,
6715
6416
  separator: item.separator,
6716
6417
  variant: item.variant
6717
6418
  }];
@@ -6751,7 +6452,32 @@ function AutoCrudTable({ id, title, description, schema, resource, fields, table
6751
6452
  const getSelectedRowsRef = React.useRef(null);
6752
6453
  const dynamicFilterOptions = useDynamicFilterOptions(resolvedFields);
6753
6454
  const resourceIdKey = resource.idKey ?? "id";
6754
- const ownerToolbarActions = React.useMemo(() => resolveOwnerToolbarActions(toolbar ?? toolbarActions), [toolbar, toolbarActions]);
6455
+ const actionRegistryVersion = useCrudActionsVersion();
6456
+ const unifiedActions = React.useMemo(() => {
6457
+ if (isUnifiedActionsConfig(actionItems)) return actionItems;
6458
+ return { row: actionItems };
6459
+ }, [actionItems]);
6460
+ const ownerToolbarConfig = unifiedActions.toolbar ?? toolbar ?? toolbarActions;
6461
+ const ownerRowConfig = unifiedActions.row;
6462
+ const ownerBatchConfig = unifiedActions.batch ?? tableConfig?.batchActions;
6463
+ const ownerToolbarActions = React.useMemo(() => resolveOwnerToolbarActions(ownerToolbarConfig), [ownerToolbarConfig]);
6464
+ const ownerRowActions = React.useMemo(() => resolveOwnerRowActions(ownerRowConfig), [ownerRowConfig]);
6465
+ const ownerBatchActions = React.useMemo(() => resolveOwnerBatchActions(ownerBatchConfig), [ownerBatchConfig]);
6466
+ const registryToolbarActions = React.useMemo(() => crudActions.resolve(id, "toolbar", ownerToolbarActions), [
6467
+ id,
6468
+ ownerToolbarActions,
6469
+ actionRegistryVersion
6470
+ ]);
6471
+ const registryRowActions = React.useMemo(() => crudActions.resolve(id, "row", ownerRowActions), [
6472
+ id,
6473
+ ownerRowActions,
6474
+ actionRegistryVersion
6475
+ ]);
6476
+ const registryBatchActions = React.useMemo(() => crudActions.resolve(id, "batch", ownerBatchActions), [
6477
+ id,
6478
+ ownerBatchActions,
6479
+ actionRegistryVersion
6480
+ ]);
6755
6481
  const selectedCount = selectedRows.length;
6756
6482
  const selectedRowIds = React.useMemo(() => readRowIds(selectedRows, resourceIdKey), [selectedRows, resourceIdKey]);
6757
6483
  const rowIds = React.useMemo(() => readRowIds(resource.tableData.data, resourceIdKey), [resource.tableData.data, resourceIdKey]);
@@ -6760,7 +6486,7 @@ function AutoCrudTable({ id, title, description, schema, resource, fields, table
6760
6486
  }, [resourceIdKey]);
6761
6487
  const ownerCreateAction = React.useMemo(() => readCreateAction(ownerToolbarActions), [ownerToolbarActions]);
6762
6488
  const toolbarOpenCreate = can.create ? ownerCreateAction?.onClick ?? onCreate ?? resource.handlers.openCreate : void 0;
6763
- const resolvedToolbarActions = resolveToolbarActionsWithResolver(id, ownerToolbarActions, React.useMemo(() => ({
6489
+ const toolbarContext = React.useMemo(() => ({
6764
6490
  crudId: id ?? "",
6765
6491
  idKey: resourceIdKey,
6766
6492
  rowIds,
@@ -6774,7 +6500,8 @@ function AutoCrudTable({ id, title, description, schema, resource, fields, table
6774
6500
  selectedRowIds,
6775
6501
  selectedCount,
6776
6502
  toolbarOpenCreate
6777
- ]));
6503
+ ]);
6504
+ const resolvedToolbarActions = resolveToolbarActionsWithResolver(id, registryToolbarActions, toolbarContext);
6778
6505
  const importColumns = React.useMemo(() => {
6779
6506
  const shape = resolvedSchema.shape;
6780
6507
  const denySet = new Set(denyFields ?? []);
@@ -6844,6 +6571,30 @@ function AutoCrudTable({ id, title, description, schema, resource, fields, table
6844
6571
  resource.modal.copySource,
6845
6572
  resource.modal.selected
6846
6573
  ]);
6574
+ const rowActionDefaults = React.useMemo(() => ({
6575
+ openView: resource.handlers.openView,
6576
+ openEdit: can.update ? resource.handlers.openEdit : void 0,
6577
+ copyRow: can.create ? resource.handlers.copyRow : void 0,
6578
+ openDelete: can.delete ? resource.handlers.openDelete : void 0
6579
+ }), [
6580
+ can.create,
6581
+ can.delete,
6582
+ can.update,
6583
+ resource.handlers.copyRow,
6584
+ resource.handlers.openDelete,
6585
+ resource.handlers.openEdit,
6586
+ resource.handlers.openView
6587
+ ]);
6588
+ const tableRowActions = React.useMemo(() => resolveActions(registryRowActions, rowActionDefaults, locale.rowActions, {
6589
+ crudId: id ?? "",
6590
+ idKey: resourceIdKey
6591
+ }), [
6592
+ id,
6593
+ locale.rowActions,
6594
+ registryRowActions,
6595
+ resourceIdKey,
6596
+ rowActionDefaults
6597
+ ]);
6847
6598
  return /* @__PURE__ */ jsxs("div", {
6848
6599
  className: "space-y-4",
6849
6600
  children: [
@@ -6880,17 +6631,11 @@ function AutoCrudTable({ id, title, description, schema, resource, fields, table
6880
6631
  }, "create");
6881
6632
  return null;
6882
6633
  };
6883
- if (!resolvedToolbarActions || resolvedToolbarActions.length === 0) return /* @__PURE__ */ jsxs("div", {
6884
- className: "flex items-center gap-2",
6885
- children: [
6886
- renderBuiltinButton("import"),
6887
- renderBuiltinButton("export"),
6888
- renderBuiltinButton("create")
6889
- ]
6890
- });
6634
+ const renderToolbarComponent = (component) => typeof component === "function" ? component(toolbarContext) : component;
6635
+ if (!resolvedToolbarActions || resolvedToolbarActions.length === 0) return /* @__PURE__ */ jsx("div", { className: "flex items-center gap-2" });
6891
6636
  if (!resolvedToolbarActions.some((i) => i.type !== "custom")) {
6892
- const startNodes = resolvedToolbarActions.filter((i) => i.type === "custom" && i.position === "start").map((a, i) => /* @__PURE__ */ jsx(React.Fragment, { children: a.component }, `start-${i}`));
6893
- const endNodes = resolvedToolbarActions.filter((i) => i.type === "custom" && i.position !== "start").map((a, i) => /* @__PURE__ */ jsx(React.Fragment, { children: a.component }, `end-${i}`));
6637
+ const startNodes = resolvedToolbarActions.filter((i) => i.type === "custom" && i.position === "start").map((a, i) => /* @__PURE__ */ jsx(React.Fragment, { children: renderToolbarComponent(a.component) }, `start-${i}`));
6638
+ const endNodes = resolvedToolbarActions.filter((i) => i.type === "custom" && i.position !== "start").map((a, i) => /* @__PURE__ */ jsx(React.Fragment, { children: renderToolbarComponent(a.component) }, `end-${i}`));
6894
6639
  return /* @__PURE__ */ jsxs("div", {
6895
6640
  className: "flex items-center gap-2",
6896
6641
  children: [
@@ -6904,13 +6649,13 @@ function AutoCrudTable({ id, title, description, schema, resource, fields, table
6904
6649
  }
6905
6650
  return /* @__PURE__ */ jsx("div", {
6906
6651
  className: "flex items-center gap-2",
6907
- children: resolvedToolbarActions.map((action, index) => {
6908
- if (action.type === "custom") return /* @__PURE__ */ jsx(React.Fragment, { children: action.component }, `custom-${index}`);
6909
- if (!(action.type === "import" ? canImport : action.type === "export" ? canExport : can.create)) return null;
6910
- if (action.component) return /* @__PURE__ */ jsx(React.Fragment, { children: action.component }, action.type);
6911
- return renderBuiltinButton(action.type, {
6912
- onClick: action.onClick,
6913
- label: action.label
6652
+ children: resolvedToolbarActions.map((action$1, index$1) => {
6653
+ if (action$1.type === "custom") return /* @__PURE__ */ jsx(React.Fragment, { children: renderToolbarComponent(action$1.component) }, `custom-${index$1}`);
6654
+ if (!(action$1.type === "import" ? canImport : action$1.type === "export" ? canExport : can.create)) return null;
6655
+ if (action$1.component) return /* @__PURE__ */ jsx(React.Fragment, { children: renderToolbarComponent(action$1.component) }, action$1.type);
6656
+ return renderBuiltinButton(action$1.type, {
6657
+ onClick: action$1.onClick,
6658
+ label: action$1.label
6914
6659
  });
6915
6660
  })
6916
6661
  });
@@ -6924,16 +6669,12 @@ function AutoCrudTable({ id, title, description, schema, resource, fields, table
6924
6669
  exclude: hiddenColumns,
6925
6670
  filterMode: tableConfig?.filterModes,
6926
6671
  search: searchConfig,
6927
- actions: resolveActions(actionItems, {
6928
- openView: resource.handlers.openView,
6929
- openEdit: can.update ? resource.handlers.openEdit : void 0,
6930
- copyRow: can.create ? resource.handlers.copyRow : void 0,
6931
- openDelete: can.delete ? resource.handlers.openDelete : void 0
6932
- }, locale.rowActions),
6672
+ actions: tableRowActions,
6933
6673
  onDeleteSelected: can.delete ? resource.handlers.deleteMany : void 0,
6934
6674
  onUpdateSelected: can.update ? resource.handlers.updateMany : void 0,
6935
6675
  batchUpdateFields: can.update ? batchFields : void 0,
6936
- actionBarActions: tableConfig?.batchActions,
6676
+ actionBarActions: registryBatchActions,
6677
+ deleteConfirmation: locale.bulkDeleteModal,
6937
6678
  enableExport: canExport,
6938
6679
  showDefaultExport: false,
6939
6680
  initialSorting: tableConfig?.defaultSort,
@@ -6953,6 +6694,7 @@ function AutoCrudTable({ id, title, description, schema, resource, fields, table
6953
6694
  loading: resource.mutations.isCreating || resource.mutations.isUpdating,
6954
6695
  variant: resource.modal.variant,
6955
6696
  overrides: formOverrides,
6697
+ scope: formConfig?.scope,
6956
6698
  locale: locale.formModal,
6957
6699
  gridColumns: formConfig?.columns,
6958
6700
  className: formConfig?.className
@@ -6964,7 +6706,7 @@ function AutoCrudTable({ id, title, description, schema, resource, fields, table
6964
6706
  data: resource.modal.selected,
6965
6707
  schema: resolvedSchema,
6966
6708
  fields: resolvedFields,
6967
- dynamicOptions: dynamicFilterOptions,
6709
+ dynamicOptions: dynamicFilterOptions.optionsByField,
6968
6710
  denyFields,
6969
6711
  locale
6970
6712
  }),
@@ -7087,15 +6829,25 @@ function DataTableAdvancedToolbar({ table, children, className,...props }) {
7087
6829
  function DataTableFacetedFilter({ column, title, options, multiple }) {
7088
6830
  const columnFilterValue = column?.getFilterValue();
7089
6831
  const selectedValues = Array.isArray(columnFilterValue) ? columnFilterValue.filter((value) => typeof value === "string") : [];
6832
+ const currentOptions = column?.columnDef.meta?.autoCrudFilterOptions ?? options;
6833
+ const currentOptionValues = new Set(currentOptions.map((option) => option.value));
6834
+ const selectedOptionValues = new Set(selectedValues);
6835
+ const comboboxOptions = currentOptions === options ? currentOptions : [...options.filter((option) => selectedOptionValues.has(option.value) && !currentOptionValues.has(option.value)), ...currentOptions];
7090
6836
  const onChange = (nextValues) => {
7091
6837
  column?.setFilterValue(nextValues.length ? nextValues : void 0);
7092
6838
  };
7093
- return /* @__PURE__ */ jsx(MultiCombobox, {
6839
+ return /* @__PURE__ */ jsx(MultiCombobox$1, {
7094
6840
  value: selectedValues,
7095
6841
  onChange,
7096
- options,
6842
+ options: comboboxOptions,
7097
6843
  selectionMode: multiple ? "multiple" : "single",
7098
6844
  searchPlaceholder: title,
6845
+ hasMore: column?.columnDef.meta?.autoCrudFilterHasMore,
6846
+ loading: column?.columnDef.meta?.autoCrudFilterLoading,
6847
+ searchValue: column?.columnDef.meta?.autoCrudFilterSearchValue,
6848
+ shouldFilter: column?.columnDef.meta?.autoCrudFilterShouldFilter,
6849
+ onPopupScroll: column?.columnDef.meta?.autoCrudFilterOnPopupScroll,
6850
+ onSearch: column?.columnDef.meta?.autoCrudFilterOnSearch,
7099
6851
  contentClassName: "w-50",
7100
6852
  matchTriggerWidth: false,
7101
6853
  clearText: "Clear filters",
@@ -7674,8 +7426,8 @@ function readMetadataFields(fields) {
7674
7426
  /**
7675
7427
  * Modal 状态 Reducer
7676
7428
  */
7677
- function modalReducer(state, action) {
7678
- switch (action.type) {
7429
+ function modalReducer(state, action$1) {
7430
+ switch (action$1.type) {
7679
7431
  case "OPEN_CREATE": return {
7680
7432
  ...state,
7681
7433
  createOpen: true,
@@ -7686,22 +7438,22 @@ function modalReducer(state, action) {
7686
7438
  ...state,
7687
7439
  createOpen: true,
7688
7440
  selected: null,
7689
- copySource: action.payload
7441
+ copySource: action$1.payload
7690
7442
  };
7691
7443
  case "OPEN_EDIT": return {
7692
7444
  ...state,
7693
7445
  editOpen: true,
7694
- selected: action.payload
7446
+ selected: action$1.payload
7695
7447
  };
7696
7448
  case "OPEN_DELETE": return {
7697
7449
  ...state,
7698
7450
  deleteOpen: true,
7699
- selected: action.payload
7451
+ selected: action$1.payload
7700
7452
  };
7701
7453
  case "OPEN_VIEW": return {
7702
7454
  ...state,
7703
7455
  viewOpen: true,
7704
- selected: action.payload
7456
+ selected: action$1.payload
7705
7457
  };
7706
7458
  case "CLOSE_ALL": return {
7707
7459
  ...state,
@@ -7714,7 +7466,7 @@ function modalReducer(state, action) {
7714
7466
  };
7715
7467
  case "SET_VARIANT": return {
7716
7468
  ...state,
7717
- variant: action.payload
7469
+ variant: action$1.payload
7718
7470
  };
7719
7471
  default: return state;
7720
7472
  }
@@ -8331,19 +8083,19 @@ function createMemoryDataSource(initialData = []) {
8331
8083
  return newItem;
8332
8084
  },
8333
8085
  async update(id, updateData) {
8334
- const index = data.findIndex((item) => item.id === id);
8335
- if (index === -1) throw new Error(`Item with id ${id} not found`);
8086
+ const index$1 = data.findIndex((item) => item.id === id);
8087
+ if (index$1 === -1) throw new Error(`Item with id ${id} not found`);
8336
8088
  const updated = {
8337
- ...data[index],
8089
+ ...data[index$1],
8338
8090
  ...updateData
8339
8091
  };
8340
- data[index] = updated;
8092
+ data[index$1] = updated;
8341
8093
  return updated;
8342
8094
  },
8343
8095
  async delete(id) {
8344
- const index = data.findIndex((item) => item.id === id);
8345
- if (index === -1) throw new Error(`Item with id ${id} not found`);
8346
- data.splice(index, 1);
8096
+ const index$1 = data.findIndex((item) => item.id === id);
8097
+ if (index$1 === -1) throw new Error(`Item with id ${id} not found`);
8098
+ data.splice(index$1, 1);
8347
8099
  },
8348
8100
  async deleteMany(ids) {
8349
8101
  data = data.filter((item) => !ids.includes(item.id));
@@ -8352,4 +8104,4 @@ function createMemoryDataSource(initialData = []) {
8352
8104
  }
8353
8105
 
8354
8106
  //#endregion
8355
- 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, 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 };
8107
+ 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 };