@wordrhyme/auto-crud 1.5.7 → 1.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -39,6 +39,8 @@ let nanoid = require("nanoid");
39
39
  nanoid = __toESM(nanoid);
40
40
  let __wordrhyme_shadcn_ui = require("@wordrhyme/shadcn-ui");
41
41
  __wordrhyme_shadcn_ui = __toESM(__wordrhyme_shadcn_ui);
42
+ let __wordrhyme_ui = require("@wordrhyme/ui");
43
+ __wordrhyme_ui = __toESM(__wordrhyme_ui);
42
44
  let zod = require("zod");
43
45
  zod = __toESM(zod);
44
46
  let __formily_core = require("@formily/core");
@@ -420,13 +422,15 @@ function hasValidCanonicalDateTimePrefix(value) {
420
422
  const match = canonicalDateTimePrefixPattern.exec(value);
421
423
  return !match || isValidCalendarDate(match[1], match[2], match[3]);
422
424
  }
425
+ function getDateLocaleOptions() {
426
+ return hostDateFormatters[hostDateFormatters.length - 1]?.options;
427
+ }
423
428
  /**
424
- * Register the host's process-wide date presentation policy without coupling
425
- * AutoCrud to locale or timezone selection. The returned cleanup removes only
426
- * this registration, so overlapping registrations may be disposed out of order.
429
+ * Register the host's date formatter and optional calendar locale policy.
430
+ * Cleanup removes only this registration, including out-of-order cleanup.
427
431
  * Passing undefined clears every registration.
428
432
  */
429
- function setDateFormatter(formatter) {
433
+ function setDateFormatter(formatter, options) {
430
434
  if (formatter === void 0) {
431
435
  const hadFormatter = hostDateFormatters.length > 0;
432
436
  hostDateFormatters = [];
@@ -435,15 +439,16 @@ function setDateFormatter(formatter) {
435
439
  }
436
440
  const registration = {
437
441
  formatter,
442
+ ...options ? { options } : {},
438
443
  token: Symbol("date-formatter")
439
444
  };
440
445
  hostDateFormatters.push(registration);
441
446
  notifyDateFormatterChange();
442
447
  return () => {
443
- const currentFormatter = getHostDateFormatter();
448
+ const currentRegistration = hostDateFormatters[hostDateFormatters.length - 1];
444
449
  const previousLength = hostDateFormatters.length;
445
450
  hostDateFormatters = hostDateFormatters.filter(({ token }) => token !== registration.token);
446
- if (hostDateFormatters.length !== previousLength && getHostDateFormatter() !== currentFormatter) notifyDateFormatterChange();
451
+ if (hostDateFormatters.length !== previousLength && hostDateFormatters[hostDateFormatters.length - 1] !== currentRegistration) notifyDateFormatterChange();
447
452
  };
448
453
  }
449
454
  function formatDate(date, opts = {}, preset) {
@@ -492,8 +497,14 @@ function formatDate(date, opts = {}, preset) {
492
497
 
493
498
  //#endregion
494
499
  //#region src/components/data-table/data-table.tsx
495
- function DataTable({ table, actionBar, children, className,...props }) {
500
+ function DataTable({ table, actionBar, children, className, classNames,...props }) {
496
501
  useDateFormatterVersion();
502
+ const columns = [
503
+ ...table.getLeftVisibleLeafColumns(),
504
+ ...table.getCenterVisibleLeafColumns(),
505
+ ...table.getRightVisibleLeafColumns()
506
+ ];
507
+ const hasWidthCap = columns.some((column) => (column.columnDef.maxSize ?? Number.MAX_SAFE_INTEGER) < Number.MAX_SAFE_INTEGER);
497
508
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
498
509
  className: cn("flex w-full flex-col gap-2.5 overflow-auto", className),
499
510
  ...props,
@@ -501,21 +512,53 @@ function DataTable({ table, actionBar, children, className,...props }) {
501
512
  children,
502
513
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
503
514
  className: "overflow-hidden rounded-md border",
504
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(__wordrhyme_shadcn.Table, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(__wordrhyme_shadcn.TableHeader, { children: table.getHeaderGroups().map((headerGroup) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__wordrhyme_shadcn.TableRow, { children: headerGroup.headers.map((header) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__wordrhyme_shadcn.TableHead, {
505
- colSpan: header.colSpan,
506
- style: { ...getColumnPinningStyle({ column: header.column }) },
507
- children: header.isPlaceholder ? null : (0, __tanstack_react_table.flexRender)(header.column.columnDef.header, header.getContext())
508
- }, header.id)) }, headerGroup.id)) }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__wordrhyme_shadcn.TableBody, { children: table.getRowModel().rows?.length ? table.getRowModel().rows.map((row) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__wordrhyme_shadcn.TableRow, {
509
- "data-state": row.getIsSelected() && "selected",
510
- children: row.getVisibleCells().map((cell) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__wordrhyme_shadcn.TableCell, {
511
- style: { ...getColumnPinningStyle({ column: cell.column }) },
512
- children: (0, __tanstack_react_table.flexRender)(cell.column.columnDef.cell, cell.getContext())
513
- }, cell.id))
514
- }, row.id)) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__wordrhyme_shadcn.TableRow, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__wordrhyme_shadcn.TableCell, {
515
- colSpan: table.getAllColumns().length,
516
- className: "h-24 text-center",
517
- children: "No results."
518
- }) }) })] })
515
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(__wordrhyme_shadcn.Table, {
516
+ className: classNames?.table,
517
+ style: hasWidthCap ? {
518
+ tableLayout: "fixed",
519
+ width: columns.reduce((width, column) => width + column.getSize(), 0)
520
+ } : void 0,
521
+ children: [
522
+ hasWidthCap && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("colgroup", { children: columns.map((column) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("col", { style: { width: column.getSize() } }, column.id)) }),
523
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__wordrhyme_shadcn.TableHeader, {
524
+ className: classNames?.thead,
525
+ children: table.getHeaderGroups().map((headerGroup) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__wordrhyme_shadcn.TableRow, {
526
+ className: classNames?.tr,
527
+ children: headerGroup.headers.map((header) => {
528
+ const columnClassName = header.column.columnDef.meta?.classNames?.th;
529
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__wordrhyme_shadcn.TableHead, {
530
+ colSpan: header.colSpan,
531
+ className: cn(hasWidthCap && "overflow-hidden text-ellipsis", classNames?.th, typeof columnClassName === "string" ? columnClassName : void 0),
532
+ style: { ...getColumnPinningStyle({ column: header.column }) },
533
+ children: header.isPlaceholder ? null : (0, __tanstack_react_table.flexRender)(header.column.columnDef.header, header.getContext())
534
+ }, header.id);
535
+ })
536
+ }, headerGroup.id))
537
+ }),
538
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__wordrhyme_shadcn.TableBody, {
539
+ className: classNames?.tbody,
540
+ children: table.getRowModel().rows?.length ? table.getRowModel().rows.map((row) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__wordrhyme_shadcn.TableRow, {
541
+ className: classNames?.tr,
542
+ "data-state": row.getIsSelected() && "selected",
543
+ children: row.getVisibleCells().map((cell) => {
544
+ const columnClassName = cell.column.columnDef.meta?.classNames?.td;
545
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__wordrhyme_shadcn.TableCell, {
546
+ className: cn(hasWidthCap && "overflow-hidden text-ellipsis", classNames?.td, typeof columnClassName === "string" ? columnClassName : void 0),
547
+ style: { ...getColumnPinningStyle({ column: cell.column }) },
548
+ children: (0, __tanstack_react_table.flexRender)(cell.column.columnDef.cell, cell.getContext())
549
+ }, cell.id);
550
+ })
551
+ }, row.id)) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__wordrhyme_shadcn.TableRow, {
552
+ className: classNames?.tr,
553
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__wordrhyme_shadcn.TableCell, {
554
+ colSpan: table.getAllColumns().length,
555
+ className: cn("h-24 text-center", classNames?.td),
556
+ children: "No results."
557
+ })
558
+ })
559
+ })
560
+ ]
561
+ })
519
562
  }),
520
563
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
521
564
  className: "flex flex-col gap-2.5",
@@ -1043,6 +1086,71 @@ function useDebouncedCallback(callback, delay) {
1043
1086
  }, [handleCallback, delay]);
1044
1087
  }
1045
1088
 
1089
+ //#endregion
1090
+ //#region src/lib/calendar-date.ts
1091
+ /** Calendar days are wall dates, never instants in the host's time zone. */
1092
+ function serializeCalendarDate(date) {
1093
+ return `${String(date.getFullYear()).padStart(4, "0")}-${String(date.getMonth() + 1).padStart(2, "0")}-${String(date.getDate()).padStart(2, "0")}`;
1094
+ }
1095
+ function parseCalendarDate(value) {
1096
+ if (value === void 0 || value === "") return void 0;
1097
+ if (typeof value === "string" && /^\d{4}-\d{2}-\d{2}$/.test(value)) {
1098
+ const date$1 = /* @__PURE__ */ new Date(`${value}T00:00:00`);
1099
+ return !Number.isNaN(date$1.getTime()) && serializeCalendarDate(date$1) === value ? date$1 : void 0;
1100
+ }
1101
+ const date = new Date(Number(value));
1102
+ return Number.isNaN(date.getTime()) ? void 0 : date;
1103
+ }
1104
+ function calendarPresentation() {
1105
+ const localeOptions = getDateLocaleOptions();
1106
+ const locale = localeOptions?.locale ?? "en-US";
1107
+ const chinese = locale.toLowerCase().startsWith("zh");
1108
+ const localeInfo = new Intl.Locale(locale);
1109
+ const firstDay = localeInfo.getWeekInfo?.().firstDay ?? localeInfo.weekInfo?.firstDay ?? (chinese ? 1 : 7);
1110
+ const format = (date, options) => new Intl.DateTimeFormat(locale, {
1111
+ ...options,
1112
+ timeZone: "UTC"
1113
+ }).format(/* @__PURE__ */ new Date(`${serializeCalendarDate(date)}T00:00:00Z`));
1114
+ return {
1115
+ locale,
1116
+ weekStartsOn: firstDay % 7,
1117
+ formatDate: (date) => {
1118
+ if (!localeOptions) return formatDate(date);
1119
+ return date ? format(date, {
1120
+ year: "numeric",
1121
+ month: "long",
1122
+ day: "numeric"
1123
+ }) : "";
1124
+ },
1125
+ clearLabel: (title) => chinese ? `清除${title ?? ""}筛选` : `Clear ${title ?? ""} filter`,
1126
+ formatters: {
1127
+ formatCaption: (date) => format(date, {
1128
+ year: "numeric",
1129
+ month: "long"
1130
+ }),
1131
+ formatMonthDropdown: (date) => format(date, { month: "long" }),
1132
+ formatWeekdayName: (date) => format(date, { weekday: "short" })
1133
+ },
1134
+ labels: {
1135
+ labelNext: () => chinese ? "下个月" : "Go to the Next Month",
1136
+ labelPrevious: () => chinese ? "上个月" : "Go to the Previous Month",
1137
+ labelMonthDropdown: () => chinese ? "选择月份" : "Choose the Month",
1138
+ labelYearDropdown: () => chinese ? "选择年份" : "Choose the Year",
1139
+ labelGrid: (date) => format(date, {
1140
+ year: "numeric",
1141
+ month: "long"
1142
+ }),
1143
+ labelWeekday: (date) => format(date, { weekday: "long" }),
1144
+ labelDayButton: (date) => format(date, {
1145
+ year: "numeric",
1146
+ month: "long",
1147
+ day: "numeric",
1148
+ weekday: "long"
1149
+ })
1150
+ }
1151
+ };
1152
+ }
1153
+
1046
1154
  //#endregion
1047
1155
  //#region src/components/data-table/data-table-filter-list.tsx
1048
1156
  const DEBOUNCE_MS$2 = 300;
@@ -1214,6 +1322,7 @@ function DataTableFilterList({ table, debounceMs = DEBOUNCE_MS$2, throttleMs = T
1214
1322
  });
1215
1323
  }
1216
1324
  function DataTableFilterItem$1({ filter, index: index$1, filterItemId, joinOperator, setJoinOperator, columns, onFilterUpdate, onFilterRemove }) {
1325
+ useDateFormatterVersion();
1217
1326
  const [showFieldSelector, setShowFieldSelector] = react.useState(false);
1218
1327
  const [showOperatorSelector, setShowOperatorSelector] = react.useState(false);
1219
1328
  const [showValueSelector, setShowValueSelector] = react.useState(false);
@@ -1502,10 +1611,10 @@ function onFilterInputRender$1({ filter, inputId, column, columnMeta, onFilterUp
1502
1611
  case "dateRange": {
1503
1612
  const inputListboxId = `${inputId}-listbox`;
1504
1613
  const dateValue = Array.isArray(filter.value) ? filter.value.filter(Boolean) : [filter.value, filter.value].filter(Boolean);
1505
- const startDate = dateValue[0] ? new Date(Number(dateValue[0])) : void 0;
1506
- const endDate = dateValue[1] ? new Date(Number(dateValue[1])) : void 0;
1614
+ const startDate = dateValue[0] ? parseCalendarDate(dateValue[0]) : void 0;
1615
+ const endDate = dateValue[1] ? parseCalendarDate(dateValue[1]) : void 0;
1507
1616
  const isSameDate = startDate && endDate && startDate.toDateString() === endDate.toDateString();
1508
- const displayValue = filter.operator === "isBetween" && dateValue.length === 2 && !isSameDate ? `${formatDate(startDate, { month: "short" })} - ${formatDate(endDate, { month: "short" })}` : startDate ? formatDate(startDate, { month: "short" }) : "Pick a date";
1617
+ const displayValue = filter.operator === "isBetween" && dateValue.length === 2 && !isSameDate ? `${calendarPresentation().formatDate(startDate)} - ${calendarPresentation().formatDate(endDate)}` : startDate ? calendarPresentation().formatDate(startDate) : "Pick a date";
1509
1618
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(__wordrhyme_shadcn.Popover, {
1510
1619
  open: showValueSelector,
1511
1620
  onOpenChange: setShowValueSelector,
@@ -1528,28 +1637,36 @@ function onFilterInputRender$1({ filter, inputId, column, columnMeta, onFilterUp
1528
1637
  align: "start",
1529
1638
  className: "w-auto p-0",
1530
1639
  children: filter.operator === "isBetween" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__wordrhyme_shadcn.Calendar, {
1640
+ lang: calendarPresentation().locale,
1641
+ weekStartsOn: calendarPresentation().weekStartsOn,
1642
+ formatters: calendarPresentation().formatters,
1643
+ labels: calendarPresentation().labels,
1531
1644
  "aria-label": `Select ${columnMeta?.label} date range`,
1532
1645
  autoFocus: true,
1533
1646
  captionLayout: "dropdown",
1534
1647
  mode: "range",
1535
1648
  selected: dateValue.length === 2 ? {
1536
- from: new Date(Number(dateValue[0])),
1537
- to: new Date(Number(dateValue[1]))
1649
+ from: parseCalendarDate(dateValue[0]),
1650
+ to: parseCalendarDate(dateValue[1])
1538
1651
  } : {
1539
1652
  from: /* @__PURE__ */ new Date(),
1540
1653
  to: /* @__PURE__ */ new Date()
1541
1654
  },
1542
1655
  onSelect: (date) => {
1543
- onFilterUpdate(filter.filterId, { value: date ? [(date.from?.getTime() ?? "").toString(), (date.to?.getTime() ?? "").toString()] : [] });
1656
+ onFilterUpdate(filter.filterId, { value: date ? [date.from ? serializeCalendarDate(date.from) : "", date.to ? serializeCalendarDate(date.to) : ""] : [] });
1544
1657
  }
1545
1658
  }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__wordrhyme_shadcn.Calendar, {
1659
+ lang: calendarPresentation().locale,
1660
+ weekStartsOn: calendarPresentation().weekStartsOn,
1661
+ formatters: calendarPresentation().formatters,
1662
+ labels: calendarPresentation().labels,
1546
1663
  "aria-label": `Select ${columnMeta?.label} date`,
1547
1664
  autoFocus: true,
1548
1665
  captionLayout: "dropdown",
1549
1666
  mode: "single",
1550
- selected: dateValue[0] ? new Date(Number(dateValue[0])) : void 0,
1667
+ selected: dateValue[0] ? parseCalendarDate(dateValue[0]) : void 0,
1551
1668
  onSelect: (date) => {
1552
- onFilterUpdate(filter.filterId, { value: (date?.getTime() ?? "").toString() });
1669
+ onFilterUpdate(filter.filterId, { value: date ? serializeCalendarDate(date) : "" });
1553
1670
  setShowValueSelector(false);
1554
1671
  }
1555
1672
  })
@@ -1761,6 +1878,7 @@ function DataTableFilterMenu({ table, debounceMs = DEBOUNCE_MS$1, throttleMs = T
1761
1878
  }
1762
1879
  function DataTableFilterItem({ filter, filterItemId, columns, onFilterUpdate, onFilterRemove }) {
1763
1880
  {
1881
+ useDateFormatterVersion();
1764
1882
  const [showFieldSelector, setShowFieldSelector] = react.useState(false);
1765
1883
  const [showOperatorSelector, setShowOperatorSelector] = react.useState(false);
1766
1884
  const [showValueSelector, setShowValueSelector] = react.useState(false);
@@ -1871,6 +1989,7 @@ function DataTableFilterItem({ filter, filterItemId, columns, onFilterUpdate, on
1871
1989
  }
1872
1990
  }
1873
1991
  function FilterValueSelector({ column, value, onSelect }) {
1992
+ useDateFormatterVersion();
1874
1993
  switch (column.columnDef.meta?.variant ?? "text") {
1875
1994
  case "boolean": return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(__wordrhyme_shadcn.CommandGroup, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(__wordrhyme_shadcn.CommandItem, {
1876
1995
  value: "true",
@@ -1899,11 +2018,15 @@ function FilterValueSelector({ column, value, onSelect }) {
1899
2018
  }, option.value)) });
1900
2019
  case "date":
1901
2020
  case "dateRange": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__wordrhyme_shadcn.Calendar, {
2021
+ lang: calendarPresentation().locale,
2022
+ weekStartsOn: calendarPresentation().weekStartsOn,
2023
+ formatters: calendarPresentation().formatters,
2024
+ labels: calendarPresentation().labels,
1902
2025
  autoFocus: true,
1903
2026
  captionLayout: "dropdown",
1904
2027
  mode: "single",
1905
- selected: value ? new Date(value) : void 0,
1906
- onSelect: (date) => onSelect(date?.getTime().toString() ?? "")
2028
+ selected: parseCalendarDate(value),
2029
+ onSelect: (date) => onSelect(date ? serializeCalendarDate(date) : "")
1907
2030
  });
1908
2031
  default: {
1909
2032
  const isEmpty = !value.trim();
@@ -2057,10 +2180,10 @@ function onFilterInputRender({ filter, column, inputId, onFilterUpdate, showValu
2057
2180
  case "dateRange": {
2058
2181
  const inputListboxId = `${inputId}-listbox`;
2059
2182
  const dateValue = Array.isArray(filter.value) ? filter.value.filter(Boolean) : [filter.value, filter.value].filter(Boolean);
2060
- const startDate = dateValue[0] ? new Date(Number(dateValue[0])) : void 0;
2061
- const endDate = dateValue[1] ? new Date(Number(dateValue[1])) : void 0;
2183
+ const startDate = dateValue[0] ? parseCalendarDate(dateValue[0]) : void 0;
2184
+ const endDate = dateValue[1] ? parseCalendarDate(dateValue[1]) : void 0;
2062
2185
  const isSameDate = startDate && endDate && startDate.toDateString() === endDate.toDateString();
2063
- const displayValue = filter.operator === "isBetween" && dateValue.length === 2 && !isSameDate ? `${formatDate(startDate, { month: "short" })} - ${formatDate(endDate, { month: "short" })}` : startDate ? formatDate(startDate, { month: "short" }) : "Pick date...";
2186
+ const displayValue = filter.operator === "isBetween" && dateValue.length === 2 && !isSameDate ? `${calendarPresentation().formatDate(startDate)} - ${calendarPresentation().formatDate(endDate)}` : startDate ? calendarPresentation().formatDate(startDate) : "Pick date...";
2064
2187
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(__wordrhyme_shadcn.Popover, {
2065
2188
  open: showValueSelector,
2066
2189
  onOpenChange: setShowValueSelector,
@@ -2082,26 +2205,34 @@ function onFilterInputRender({ filter, column, inputId, onFilterUpdate, showValu
2082
2205
  align: "start",
2083
2206
  className: "w-auto p-0",
2084
2207
  children: filter.operator === "isBetween" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__wordrhyme_shadcn.Calendar, {
2208
+ lang: calendarPresentation().locale,
2209
+ weekStartsOn: calendarPresentation().weekStartsOn,
2210
+ formatters: calendarPresentation().formatters,
2211
+ labels: calendarPresentation().labels,
2085
2212
  autoFocus: true,
2086
2213
  captionLayout: "dropdown",
2087
2214
  mode: "range",
2088
2215
  selected: dateValue.length === 2 ? {
2089
- from: new Date(Number(dateValue[0])),
2090
- to: new Date(Number(dateValue[1]))
2216
+ from: parseCalendarDate(dateValue[0]),
2217
+ to: parseCalendarDate(dateValue[1])
2091
2218
  } : {
2092
2219
  from: /* @__PURE__ */ new Date(),
2093
2220
  to: /* @__PURE__ */ new Date()
2094
2221
  },
2095
2222
  onSelect: (date) => {
2096
- onFilterUpdate(filter.filterId, { value: date ? [(date.from?.getTime() ?? "").toString(), (date.to?.getTime() ?? "").toString()] : [] });
2223
+ onFilterUpdate(filter.filterId, { value: date ? [date.from ? serializeCalendarDate(date.from) : "", date.to ? serializeCalendarDate(date.to) : ""] : [] });
2097
2224
  }
2098
2225
  }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__wordrhyme_shadcn.Calendar, {
2226
+ lang: calendarPresentation().locale,
2227
+ weekStartsOn: calendarPresentation().weekStartsOn,
2228
+ formatters: calendarPresentation().formatters,
2229
+ labels: calendarPresentation().labels,
2099
2230
  autoFocus: true,
2100
2231
  captionLayout: "dropdown",
2101
2232
  mode: "single",
2102
- selected: dateValue[0] ? new Date(Number(dateValue[0])) : void 0,
2233
+ selected: dateValue[0] ? parseCalendarDate(dateValue[0]) : void 0,
2103
2234
  onSelect: (date) => {
2104
- onFilterUpdate(filter.filterId, { value: (date?.getTime() ?? "").toString() });
2235
+ onFilterUpdate(filter.filterId, { value: date ? serializeCalendarDate(date) : "" });
2105
2236
  }
2106
2237
  })
2107
2238
  })]
@@ -2850,11 +2981,17 @@ function SimpleSliderFilter({ title, range, unit, value, onChange }) {
2850
2981
  })] });
2851
2982
  }
2852
2983
  function SimpleDateFilter({ title, multiple, value, onChange }) {
2984
+ const [open, setOpen] = react.useState(false);
2985
+ const commit = (next) => {
2986
+ setOpen(false);
2987
+ onChange(next);
2988
+ };
2989
+ const formatterVersion = useDateFormatterVersion();
2990
+ const presentation = react.useMemo(() => calendarPresentation(), [formatterVersion]);
2991
+ const { formatDate: formatDate$1 } = presentation;
2853
2992
  const timestamps = Array.isArray(value) ? value : value ? [value] : [];
2854
- const fromTimestamp = timestamps[0] ? Number(timestamps[0]) : void 0;
2855
- const toTimestamp = timestamps[1] ? Number(timestamps[1]) : void 0;
2856
- const fromDate = fromTimestamp ? new Date(fromTimestamp) : void 0;
2857
- const toDate = toTimestamp ? new Date(toTimestamp) : void 0;
2993
+ const fromDate = parseCalendarDate(timestamps[0]);
2994
+ const toDate = parseCalendarDate(timestamps[1]);
2858
2995
  const [localRange, setLocalRange] = react.useState({
2859
2996
  from: void 0,
2860
2997
  to: void 0
@@ -2873,100 +3010,114 @@ function SimpleDateFilter({ title, multiple, value, onChange }) {
2873
3010
  const hasValue = multiple ? fromDate || toDate : fromDate;
2874
3011
  const onReset = (e) => {
2875
3012
  e.stopPropagation();
2876
- onChange(void 0);
3013
+ commit(void 0);
2877
3014
  };
2878
- const label = multiple ? fromDate && toDate ? `${formatDate(fromDate)} - ${formatDate(toDate)}` : fromDate ? formatDate(fromDate) : void 0 : fromDate ? formatDate(fromDate) : void 0;
2879
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(__wordrhyme_shadcn.Popover, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(__wordrhyme_shadcn.PopoverTrigger, {
2880
- asChild: true,
2881
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(__wordrhyme_shadcn.Button, {
2882
- variant: "outline",
2883
- size: "sm",
2884
- className: "border-dashed font-normal",
2885
- children: [
2886
- hasValue ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2887
- role: "button",
2888
- tabIndex: 0,
2889
- className: "rounded-sm opacity-70 hover:opacity-100",
2890
- onClick: onReset,
2891
- onKeyDown: (e) => {
2892
- if (e.key === "Enter" || e.key === " ") {
2893
- e.preventDefault();
2894
- onReset(e);
3015
+ const label = multiple ? fromDate && toDate ? `${formatDate$1(fromDate)} - ${formatDate$1(toDate)}` : fromDate ? formatDate$1(fromDate) : void 0 : fromDate ? formatDate$1(fromDate) : void 0;
3016
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(__wordrhyme_shadcn.Popover, {
3017
+ open,
3018
+ onOpenChange: setOpen,
3019
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(__wordrhyme_shadcn.PopoverTrigger, {
3020
+ asChild: true,
3021
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(__wordrhyme_shadcn.Button, {
3022
+ variant: "outline",
3023
+ size: "sm",
3024
+ className: "border-dashed font-normal",
3025
+ children: [
3026
+ hasValue ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3027
+ role: "button",
3028
+ tabIndex: 0,
3029
+ className: "rounded-sm opacity-70 hover:opacity-100",
3030
+ onClick: onReset,
3031
+ onKeyDown: (e) => {
3032
+ if (e.key === "Enter" || e.key === " ") {
3033
+ e.preventDefault();
3034
+ onReset(e);
3035
+ }
3036
+ },
3037
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.XCircle, {})
3038
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.CalendarIcon, {}),
3039
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: title }),
3040
+ label && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(__wordrhyme_shadcn.Separator, {
3041
+ orientation: "vertical",
3042
+ className: "mx-0.5 h-4"
3043
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: label })] })
3044
+ ]
3045
+ })
3046
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__wordrhyme_shadcn.PopoverContent, {
3047
+ className: "w-auto p-0",
3048
+ align: "start",
3049
+ children: multiple ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__wordrhyme_shadcn.Calendar, {
3050
+ lang: presentation.locale,
3051
+ weekStartsOn: presentation.weekStartsOn,
3052
+ formatters: presentation.formatters,
3053
+ labels: presentation.labels,
3054
+ defaultMonth: fromDate,
3055
+ autoFocus: true,
3056
+ captionLayout: "dropdown",
3057
+ mode: "range",
3058
+ selected: localRange,
3059
+ onSelect: (range) => {
3060
+ if (!range?.from && !range?.to) {
3061
+ commit(void 0);
3062
+ setLocalRange({
3063
+ from: void 0,
3064
+ to: void 0
3065
+ });
3066
+ return;
3067
+ }
3068
+ const hadFrom = Boolean(localRange.from);
3069
+ const hadTo = Boolean(localRange.to);
3070
+ const from = range.from?.getTime();
3071
+ const to = range.to?.getTime();
3072
+ if (from != null && to != null && from === to) {
3073
+ if (!hadFrom || hadTo) {
3074
+ setLocalRange({
3075
+ from: range.from,
3076
+ to: void 0
3077
+ });
3078
+ return;
2895
3079
  }
2896
- },
2897
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.XCircle, {})
2898
- }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.CalendarIcon, {}),
2899
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: title }),
2900
- label && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(__wordrhyme_shadcn.Separator, {
2901
- orientation: "vertical",
2902
- className: "mx-0.5 h-4"
2903
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: label })] })
2904
- ]
2905
- })
2906
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__wordrhyme_shadcn.PopoverContent, {
2907
- className: "w-auto p-0",
2908
- align: "start",
2909
- children: multiple ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__wordrhyme_shadcn.Calendar, {
2910
- autoFocus: true,
2911
- captionLayout: "dropdown",
2912
- mode: "range",
2913
- selected: localRange,
2914
- onSelect: (range) => {
2915
- if (!range?.from && !range?.to) {
2916
- onChange(void 0);
2917
- setLocalRange({
2918
- from: void 0,
2919
- to: void 0
2920
- });
2921
- return;
2922
- }
2923
- const hadFrom = Boolean(localRange.from);
2924
- const hadTo = Boolean(localRange.to);
2925
- const from = range.from?.getTime();
2926
- const to = range.to?.getTime();
2927
- if (from != null && to != null && from === to) {
2928
- if (!hadFrom || hadTo) {
3080
+ if (hadFrom && !hadTo && localRange.from) {
3081
+ const prevFrom = localRange.from;
3082
+ const newDate = range.from;
3083
+ const [finalFrom, finalTo] = prevFrom <= newDate ? [prevFrom, newDate] : [newDate, prevFrom];
3084
+ setLocalRange({
3085
+ from: finalFrom,
3086
+ to: finalTo
3087
+ });
3088
+ commit([serializeCalendarDate(finalFrom), serializeCalendarDate(finalTo)]);
3089
+ return;
3090
+ }
3091
+ }
3092
+ if (from != null && to != null && from !== to) {
2929
3093
  setLocalRange({
2930
3094
  from: range.from,
2931
- to: void 0
3095
+ to: range.to
2932
3096
  });
3097
+ commit([serializeCalendarDate(range.from), serializeCalendarDate(range.to)]);
2933
3098
  return;
2934
3099
  }
2935
- if (hadFrom && !hadTo && localRange.from) {
2936
- const prevFrom = localRange.from;
2937
- const newDate = range.from;
2938
- const [finalFrom, finalTo] = prevFrom <= newDate ? [prevFrom, newDate] : [newDate, prevFrom];
3100
+ if (range.from && !range.to) {
2939
3101
  setLocalRange({
2940
- from: finalFrom,
2941
- to: finalTo
3102
+ from: range.from,
3103
+ to: void 0
2942
3104
  });
2943
- onChange([finalFrom.getTime().toString(), finalTo.getTime().toString()]);
2944
3105
  return;
2945
3106
  }
2946
3107
  }
2947
- if (from != null && to != null && from !== to) {
2948
- setLocalRange({
2949
- from: range.from,
2950
- to: range.to
2951
- });
2952
- onChange([range.from.getTime().toString(), range.to.getTime().toString()]);
2953
- return;
2954
- }
2955
- if (range.from && !range.to) {
2956
- setLocalRange({
2957
- from: range.from,
2958
- to: void 0
2959
- });
2960
- return;
2961
- }
2962
- }
2963
- }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__wordrhyme_shadcn.Calendar, {
2964
- captionLayout: "dropdown",
2965
- mode: "single",
2966
- selected: fromDate,
2967
- onSelect: (date) => onChange(date ? date.getTime().toString() : void 0)
2968
- })
2969
- })] });
3108
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__wordrhyme_shadcn.Calendar, {
3109
+ lang: presentation.locale,
3110
+ weekStartsOn: presentation.weekStartsOn,
3111
+ formatters: presentation.formatters,
3112
+ labels: presentation.labels,
3113
+ defaultMonth: fromDate,
3114
+ captionLayout: "dropdown",
3115
+ mode: "single",
3116
+ selected: fromDate,
3117
+ onSelect: (date) => commit(date ? serializeCalendarDate(date) : void 0)
3118
+ })
3119
+ })]
3120
+ });
2970
3121
  }
2971
3122
 
2972
3123
  //#endregion
@@ -3301,10 +3452,7 @@ function renderCell(value, type, options) {
3301
3452
  className: "tabular-nums",
3302
3453
  children: String(value)
3303
3454
  });
3304
- default: return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3305
- className: "truncate max-w-48",
3306
- children: String(value)
3307
- });
3455
+ default: return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: String(value) });
3308
3456
  }
3309
3457
  }
3310
3458
  /**
@@ -3390,33 +3538,38 @@ function createSelectColumn() {
3390
3538
  size: 40
3391
3539
  };
3392
3540
  }
3541
+ function ActionsCell({ row, column }) {
3542
+ const items = column.columnDef.meta?.rowActions ?? [];
3543
+ const renderActionComponent = (item) => {
3544
+ if (!item.component) return null;
3545
+ return typeof item.component === "function" ? item.component(item.getContext?.(row.original)) : item.component;
3546
+ };
3547
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(__wordrhyme_ui.DropdownMenu, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(__wordrhyme_ui.DropdownMenuTrigger, {
3548
+ asChild: true,
3549
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__wordrhyme_shadcn.Button, {
3550
+ "aria-label": "Open menu",
3551
+ variant: "ghost",
3552
+ className: "flex size-8 p-0 data-[state=open]:bg-muted",
3553
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Ellipsis, { className: "size-4" })
3554
+ })
3555
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__wordrhyme_ui.DropdownMenuContent, {
3556
+ align: "end",
3557
+ className: "w-40",
3558
+ children: items.map((item, i) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react.Fragment, { children: [item.separator && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__wordrhyme_ui.DropdownMenuSeparator, {}), item.component ? renderActionComponent(item) : item.label && item.onClick ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__wordrhyme_ui.DropdownMenuItem, {
3559
+ className: item.variant === "destructive" ? "text-destructive" : "",
3560
+ onClick: () => item.onClick?.(row.original),
3561
+ children: item.label
3562
+ }) : null] }, i))
3563
+ })] });
3564
+ }
3393
3565
  /**
3394
3566
  * 创建操作列
3395
3567
  */
3396
3568
  function createActionsColumn(items) {
3397
- const renderActionComponent = (item, row) => {
3398
- if (!item.component) return null;
3399
- return typeof item.component === "function" ? item.component(item.getContext?.(row.original)) : item.component;
3400
- };
3401
3569
  return {
3402
3570
  id: "actions",
3403
- cell: ({ row }) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(__wordrhyme_shadcn.DropdownMenu, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(__wordrhyme_shadcn.DropdownMenuTrigger, {
3404
- asChild: true,
3405
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__wordrhyme_shadcn.Button, {
3406
- "aria-label": "Open menu",
3407
- variant: "ghost",
3408
- className: "flex size-8 p-0 data-[state=open]:bg-muted",
3409
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Ellipsis, { className: "size-4" })
3410
- })
3411
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__wordrhyme_shadcn.DropdownMenuContent, {
3412
- align: "end",
3413
- className: "w-40",
3414
- children: items.map((item, i) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react.Fragment, { children: [item.separator && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__wordrhyme_shadcn.DropdownMenuSeparator, {}), item.component ? renderActionComponent(item, row) : item.label && item.onClick ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__wordrhyme_shadcn.DropdownMenuItem, {
3415
- className: item.variant === "destructive" ? "text-destructive" : "",
3416
- onClick: () => item.onClick?.(row.original),
3417
- children: item.label
3418
- }) : null] }, i))
3419
- })] }),
3571
+ cell: ActionsCell,
3572
+ meta: { rowActions: items },
3420
3573
  size: 40
3421
3574
  };
3422
3575
  }
@@ -3974,7 +4127,8 @@ function useDataTable(props) {
3974
4127
  if (enableAdvancedFilter) return [];
3975
4128
  return Object.entries(filterValues).reduce((filters, [key, value]) => {
3976
4129
  if (value !== null) {
3977
- const processedValue = Array.isArray(value) ? value : typeof value === "string" && /[^a-zA-Z0-9]/.test(value) ? value.split(/[^a-zA-Z0-9]+/).filter(Boolean) : [value];
4130
+ const variant = filterableColumns.find((column) => column.id === key)?.meta?.variant;
4131
+ const processedValue = Array.isArray(value) ? value : typeof value === "string" && (variant === "date" || variant === "dateRange") ? value.split(",").filter(Boolean) : typeof value === "string" && /[^a-zA-Z0-9]/.test(value) ? value.split(/[^a-zA-Z0-9]+/).filter(Boolean) : [value];
3978
4132
  filters.push({
3979
4133
  id: key,
3980
4134
  value: processedValue
@@ -3982,7 +4136,11 @@ function useDataTable(props) {
3982
4136
  }
3983
4137
  return filters;
3984
4138
  }, []);
3985
- }, [filterValues, enableAdvancedFilter]);
4139
+ }, [
4140
+ filterValues,
4141
+ enableAdvancedFilter,
4142
+ filterableColumns
4143
+ ]);
3986
4144
  const [columnFilters, setColumnFilters] = react.useState(initialColumnFilters);
3987
4145
  const onColumnFiltersChange = react.useCallback((updaterOrValue) => {
3988
4146
  if (enableAdvancedFilter) return;
@@ -4090,7 +4248,7 @@ const filterModeConfig = {
4090
4248
  tooltip: "Linear-style command filters"
4091
4249
  }
4092
4250
  };
4093
- function AutoTable({ schema, data, pageCount = 1, total, overrides, enableRowSelection = true, exclude, actions, pinnedColumns, filterMode, search = false, onDeleteSelected, onUpdateSelected, batchUpdateFields, actionBarExtra, actionBarActions, deleteConfirmation, initialSorting, enableExport = true, showDefaultExport = true, onSelectedCountChange, onSelectedRowsChange, getSelectedRows }) {
4251
+ function AutoTable({ schema, data, pageCount = 1, total, overrides, classNames, enableRowSelection = true, exclude, actions, pinnedColumns, filterMode, search = false, onDeleteSelected, onUpdateSelected, batchUpdateFields, actionBarExtra, actionBarActions, deleteConfirmation, initialSorting, enableExport = true, showDefaultExport = true, onSelectedCountChange, onSelectedRowsChange, getSelectedRows }) {
4094
4252
  const modes = filterMode ? Array.isArray(filterMode) ? filterMode : [filterMode] : DEFAULT_MODES;
4095
4253
  const [currentMode, setCurrentMode] = useQueryState("filterMode", parseAsStringEnum(modes).withDefault(modes[0] ?? "simple"));
4096
4254
  const showToggle = modes.length > 1;
@@ -4252,7 +4410,10 @@ function AutoTable({ schema, data, pageCount = 1, total, overrides, enableRowSel
4252
4410
  ]
4253
4411
  })]
4254
4412
  }),
4255
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DataTable, { table }),
4413
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DataTable, {
4414
+ table,
4415
+ classNames
4416
+ }),
4256
4417
  enableRowSelection && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AutoTableActionBar, {
4257
4418
  table,
4258
4419
  onDeleteSelected,
@@ -4272,16 +4433,16 @@ function AutoTable({ schema, data, pageCount = 1, total, overrides, enableRowSel
4272
4433
  //#endregion
4273
4434
  //#region src/components/auto-crud/form-modal.tsx
4274
4435
  function FormModal({ open, onOpenChange, title, description, children, variant = "dialog", side = "right", className, footer }) {
4275
- if (variant === "sheet") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__wordrhyme_shadcn.Sheet, {
4436
+ if (variant === "sheet") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__wordrhyme_ui.Sheet, {
4276
4437
  open,
4277
4438
  onOpenChange,
4278
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(__wordrhyme_shadcn.SheetContent, {
4439
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(__wordrhyme_ui.SheetContent, {
4279
4440
  side,
4280
4441
  className: cn("sm:max-w-lg flex flex-col p-0", className),
4281
4442
  children: [
4282
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(__wordrhyme_shadcn.SheetHeader, {
4443
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(__wordrhyme_ui.SheetHeader, {
4283
4444
  className: "flex-shrink-0 px-6 pt-6",
4284
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(__wordrhyme_shadcn.SheetTitle, { children: title }), description && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__wordrhyme_shadcn.SheetDescription, { children: description })]
4445
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(__wordrhyme_ui.SheetTitle, { children: title }), description && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__wordrhyme_ui.SheetDescription, { children: description })]
4285
4446
  }),
4286
4447
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4287
4448
  className: "flex-1 overflow-y-auto min-h-0",
@@ -4297,15 +4458,15 @@ function FormModal({ open, onOpenChange, title, description, children, variant =
4297
4458
  ]
4298
4459
  })
4299
4460
  });
4300
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__wordrhyme_shadcn.Dialog, {
4461
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__wordrhyme_ui.Dialog, {
4301
4462
  open,
4302
4463
  onOpenChange,
4303
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(__wordrhyme_shadcn.DialogContent, {
4464
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(__wordrhyme_ui.DialogContent, {
4304
4465
  className: cn("max-w-2xl sm:max-w-2xl flex flex-col max-h-[85vh] overflow-hidden p-0", className),
4305
4466
  children: [
4306
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(__wordrhyme_shadcn.DialogHeader, {
4467
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(__wordrhyme_ui.DialogHeader, {
4307
4468
  className: "flex-shrink-0 px-6 pt-6",
4308
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(__wordrhyme_shadcn.DialogTitle, { children: title }), description && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__wordrhyme_shadcn.DialogDescription, { children: description })]
4469
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(__wordrhyme_ui.DialogTitle, { children: title }), description && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__wordrhyme_ui.DialogDescription, { children: description })]
4309
4470
  }),
4310
4471
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4311
4472
  className: "flex-1 overflow-y-auto min-h-0",
@@ -5481,6 +5642,31 @@ function CrudFormModal({ open, onOpenChange, mode, schema, initialValues, onSubm
5481
5642
  });
5482
5643
  }
5483
5644
 
5645
+ //#endregion
5646
+ //#region src/lib/row-action-dialog.tsx
5647
+ /** Keep custom dialogs at table level, independently of the source row's lifetime. */
5648
+ function useRowActionDialog() {
5649
+ const [dialog, setDialog] = react.useState(null);
5650
+ const session = react.useRef(0);
5651
+ return {
5652
+ openComponent: react.useCallback((next) => {
5653
+ const id = ++session.current;
5654
+ const dismiss = () => {
5655
+ if (session.current === id) setDialog(null);
5656
+ };
5657
+ setDialog(react.cloneElement(next, {
5658
+ key: id,
5659
+ open: true,
5660
+ onOpenChange: (open) => {
5661
+ if (!open) dismiss();
5662
+ },
5663
+ onDismiss: dismiss
5664
+ }));
5665
+ }, []),
5666
+ dialog
5667
+ };
5668
+ }
5669
+
5484
5670
  //#endregion
5485
5671
  //#region src/components/auto-crud/import-dialog.tsx
5486
5672
  function ImportDialog({ open, onOpenChange, onImport, columns = [], title, locale = zhCN.importDialog }) {
@@ -5837,22 +6023,29 @@ function isCustomAction$1(action$1) {
5837
6023
  return action$1.type === "custom";
5838
6024
  }
5839
6025
  function resolveActions$1(targetId, zone, ownerActions) {
5840
- const baseActions = ownerActions.filter((action$1) => !action$1.hidden).map((action$1) => withoutRegistryMeta(action$1));
5841
- if (!targetId) return baseActions;
5842
- const registered = crudActions.get(targetId, zone);
6026
+ const registered = targetId ? crudActions.get(targetId, zone) : [];
6027
+ const hiddenCustomIds = new Set(registered.flatMap(({ action: action$1 }) => isCustomAction$1(action$1) && action$1.hidden && action$1.id ? [action$1.id] : []));
6028
+ const isMaskedCustom = (action$1) => isCustomAction$1(action$1) && Boolean(action$1.id && hiddenCustomIds.has(action$1.id));
6029
+ const customOverrides = /* @__PURE__ */ new Map();
6030
+ for (const entry of registered) {
6031
+ const { action: action$1 } = entry;
6032
+ if (isCustomAction$1(action$1) && action$1.id && !isMaskedCustom(action$1)) customOverrides.set(action$1.id, entry);
6033
+ }
6034
+ const ownerCustomIds = new Set(ownerActions.flatMap((action$1) => isCustomAction$1(action$1) && action$1.id ? [action$1.id] : []));
6035
+ const baseActions = ownerActions.filter((action$1) => !action$1.hidden && !isMaskedCustom(action$1)).map((action$1) => withoutRegistryMeta(isCustomAction$1(action$1) && action$1.id ? customOverrides.get(action$1.id)?.action ?? action$1 : action$1));
6036
+ const isReplacedCustom = ({ action: action$1, seq: seq$1 }) => isCustomAction$1(action$1) && Boolean(action$1.id && (ownerCustomIds.has(action$1.id) || customOverrides.get(action$1.id)?.seq !== seq$1));
5843
6037
  if (registered.length === 0) return baseActions;
5844
6038
  const nextActions = [...baseActions];
5845
6039
  const startCustomActions = [];
5846
6040
  const endCustomActions = [];
6041
+ const groups = /* @__PURE__ */ new Map();
5847
6042
  for (const entry of registered) {
6043
+ if (isMaskedCustom(entry.action)) continue;
6044
+ const group = groups.get(entry.ownerId) ?? [];
6045
+ group.push(entry);
6046
+ groups.set(entry.ownerId, group);
5848
6047
  const action$1 = entry.action;
5849
- if (action$1.hidden && isCustomAction$1(action$1)) continue;
5850
- if (isCustomAction$1(action$1)) {
5851
- const custom = withoutRegistryMeta(action$1);
5852
- if (custom.position === "start") startCustomActions.push(custom);
5853
- else endCustomActions.push(custom);
5854
- continue;
5855
- }
6048
+ if (isCustomAction$1(action$1)) continue;
5856
6049
  const existingIndex = nextActions.findIndex((item) => item.type === action$1.type);
5857
6050
  if (action$1.hidden) {
5858
6051
  if (existingIndex >= 0) nextActions.splice(existingIndex, 1);
@@ -5865,6 +6058,31 @@ function resolveActions$1(targetId, zone, ownerActions) {
5865
6058
  };
5866
6059
  else nextActions.push(builtin);
5867
6060
  }
6061
+ for (const group of groups.values()) {
6062
+ const items = group.sort((left, right) => left.seq - right.seq).flatMap((entry) => {
6063
+ const { action: action$1 } = entry;
6064
+ if (action$1.hidden || isReplacedCustom(entry)) return [];
6065
+ if (isCustomAction$1(action$1)) return [withoutRegistryMeta(action$1)];
6066
+ const builtin = nextActions.find((item) => item.type === action$1.type);
6067
+ return builtin ? [builtin] : [];
6068
+ });
6069
+ const firstBuiltin = items.find((item) => !isCustomAction$1(item));
6070
+ if (firstBuiltin) {
6071
+ let cursor = nextActions.indexOf(firstBuiltin);
6072
+ for (const item of items) {
6073
+ const existing = nextActions.indexOf(item);
6074
+ if (existing >= cursor) cursor = existing + 1;
6075
+ else {
6076
+ if (existing >= 0) {
6077
+ nextActions.splice(existing, 1);
6078
+ cursor -= 1;
6079
+ }
6080
+ nextActions.splice(cursor, 0, item);
6081
+ cursor += 1;
6082
+ }
6083
+ }
6084
+ } else for (const custom of items) (custom.position === "start" ? startCustomActions : endCustomActions).push(custom);
6085
+ }
5868
6086
  return [
5869
6087
  ...startCustomActions,
5870
6088
  ...nextActions,
@@ -6136,13 +6354,6 @@ function mergeFieldOptions(current, incoming) {
6136
6354
  if ((!current || current.length === 0) && (!incoming || incoming.length === 0)) return;
6137
6355
  return mergeFilterOptions(current, incoming ?? []);
6138
6356
  }
6139
- function mergeOptionsByField(left, right) {
6140
- const keys = new Set([...Object.keys(left ?? {}), ...Object.keys(right ?? {})]);
6141
- return Object.fromEntries(Array.from(keys).flatMap((key) => {
6142
- const options = mergeFieldOptions(left?.[key], right?.[key]);
6143
- return options ? [[key, options]] : [];
6144
- }));
6145
- }
6146
6357
  function shouldResolveOptions(field, config, hiddenColumns) {
6147
6358
  if (!config) return false;
6148
6359
  if (hiddenColumns.has(field)) return false;
@@ -6500,6 +6711,7 @@ function buildTableOverrides(fields, legacyOverrides, dynamicFilterState, dynami
6500
6711
  const mergedSharedDynamicOptions = mergeFieldOptions(dynamicFilterLabelOptions, tableConfig?.dataSource ? void 0 : dynamicResolveOptions);
6501
6712
  const dynamicOptions = !fieldOptions && mergedSharedDynamicOptions ? toTableOptions(mergedSharedDynamicOptions) : void 0;
6502
6713
  const tableMeta = config.table !== false && typeof config.table === "object" ? config.table.meta : void 0;
6714
+ const tableClassNames = tableConfig?.classNames;
6503
6715
  const fieldEnumMeta = tableOptions ? {
6504
6716
  options: tableOptions,
6505
6717
  variant: "multiSelect"
@@ -6539,13 +6751,14 @@ function buildTableOverrides(fields, legacyOverrides, dynamicFilterState, dynami
6539
6751
  enableColumnFilter: false
6540
6752
  };
6541
6753
  }
6542
- if (fieldEnumMeta || fieldDataSourceMeta || tableMeta || filterMeta) result[key] = {
6754
+ if (fieldEnumMeta || fieldDataSourceMeta || tableMeta || tableClassNames !== void 0 || filterMeta) result[key] = {
6543
6755
  ...result[key],
6544
6756
  meta: {
6545
6757
  ...result[key]?.meta ?? {},
6546
6758
  ...fieldEnumMeta ?? {},
6547
6759
  ...fieldDataSourceMeta ?? {},
6548
6760
  ...tableMeta ?? {},
6761
+ ...tableClassNames !== void 0 ? { classNames: tableClassNames } : {},
6549
6762
  ...filterMeta ?? {}
6550
6763
  }
6551
6764
  };
@@ -6563,6 +6776,7 @@ function buildTableOverrides(fields, legacyOverrides, dynamicFilterState, dynami
6563
6776
  };
6564
6777
  else if (config.table && typeof config.table === "object") {
6565
6778
  const { meta, display,...tableProps } = config.table;
6779
+ delete tableProps.classNames;
6566
6780
  delete tableProps.options;
6567
6781
  delete tableProps.dataSource;
6568
6782
  result[key] = {
@@ -6595,7 +6809,7 @@ function buildCapabilityTableOverrides(schema, capabilities) {
6595
6809
  const override = {};
6596
6810
  if (capabilities.filters) {
6597
6811
  if (!capabilities.filters.enabled) override.enableColumnFilter = false;
6598
- else if (filterSet) override.enableColumnFilter = filterSet.has(key);
6812
+ else if (filterSet && !filterSet.has(key)) override.enableColumnFilter = false;
6599
6813
  }
6600
6814
  if (capabilities.sort) {
6601
6815
  if (!capabilities.sort.enabled) override.enableSorting = false;
@@ -6679,7 +6893,7 @@ function buildBatchUpdateFields(schema, batchFields, fields) {
6679
6893
  /**
6680
6894
  * 渲染字段值
6681
6895
  */
6682
- function renderFieldValue(value, type, booleanLocale, options, display = "auto") {
6896
+ function renderFieldValue(value, type, booleanLocale, options, display = "auto", maxItems = 5) {
6683
6897
  if (value === null || value === void 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6684
6898
  className: "text-muted-foreground",
6685
6899
  children: "-"
@@ -6694,12 +6908,12 @@ function renderFieldValue(value, type, booleanLocale, options, display = "auto")
6694
6908
  if (display === "badge" && (!options || options.length === 0)) {
6695
6909
  if (Array.isArray(value)) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6696
6910
  className: "flex gap-1 flex-wrap",
6697
- children: [value.slice(0, 5).map((item, index$1) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__wordrhyme_shadcn.Badge, {
6911
+ children: [value.slice(0, maxItems).map((item, index$1) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__wordrhyme_shadcn.Badge, {
6698
6912
  variant: "secondary",
6699
6913
  children: String(item)
6700
- }, index$1)), value.length > 5 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(__wordrhyme_shadcn.Badge, {
6914
+ }, index$1)), value.length > maxItems && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(__wordrhyme_shadcn.Badge, {
6701
6915
  variant: "outline",
6702
- children: ["+", value.length - 5]
6916
+ children: ["+", value.length - maxItems]
6703
6917
  })]
6704
6918
  });
6705
6919
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__wordrhyme_shadcn.Badge, {
@@ -6711,14 +6925,29 @@ function renderFieldValue(value, type, booleanLocale, options, display = "auto")
6711
6925
  if ((display === "badge" || display === "auto") && options && options.length > 0) {
6712
6926
  if (Array.isArray(value)) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6713
6927
  className: "flex gap-1 flex-wrap",
6714
- children: [value.slice(0, 5).map((v, i) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__wordrhyme_shadcn.Badge, {
6928
+ children: [value.slice(0, maxItems).map((v, i) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__wordrhyme_shadcn.Badge, {
6715
6929
  variant: "secondary",
6716
6930
  children: getOptionLabel(v, options)
6717
- }, i)), value.length > 5 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(__wordrhyme_shadcn.Badge, {
6931
+ }, i)), value.length > maxItems && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(__wordrhyme_shadcn.Badge, {
6718
6932
  variant: "outline",
6719
- children: ["+", value.length - 5]
6933
+ children: ["+", value.length - maxItems]
6720
6934
  })]
6721
6935
  });
6936
+ const tone = options.find((option) => option.value === String(value))?.badgeTone;
6937
+ const toneClassName = tone && {
6938
+ neutral: "border-border bg-muted text-muted-foreground",
6939
+ success: "border-emerald-200 bg-emerald-50 text-emerald-700 dark:border-emerald-800 dark:bg-emerald-950 dark:text-emerald-300",
6940
+ warning: "border-amber-200 bg-amber-50 text-amber-700 dark:border-amber-800 dark:bg-amber-950 dark:text-amber-300",
6941
+ info: "border-blue-200 bg-blue-50 text-blue-700 dark:border-blue-800 dark:bg-blue-950 dark:text-blue-300"
6942
+ }[tone];
6943
+ if (toneClassName) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(__wordrhyme_shadcn.Badge, {
6944
+ variant: "outline",
6945
+ className: toneClassName,
6946
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6947
+ "aria-hidden": "true",
6948
+ className: "size-1.5 shrink-0 rounded-full bg-current"
6949
+ }), getOptionLabel(value, options)]
6950
+ });
6722
6951
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__wordrhyme_shadcn.Badge, {
6723
6952
  variant: "outline",
6724
6953
  className: "capitalize",
@@ -6735,48 +6964,81 @@ function renderFieldValue(value, type, booleanLocale, options, display = "auto")
6735
6964
  });
6736
6965
  case "array": return Array.isArray(value) ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6737
6966
  className: "flex gap-1 flex-wrap",
6738
- children: [value.slice(0, 5).map((v, i) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__wordrhyme_shadcn.Badge, {
6967
+ children: [value.slice(0, maxItems).map((v, i) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__wordrhyme_shadcn.Badge, {
6739
6968
  variant: "secondary",
6740
6969
  children: String(v)
6741
- }, i)), value.length > 5 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(__wordrhyme_shadcn.Badge, {
6970
+ }, i)), value.length > maxItems && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(__wordrhyme_shadcn.Badge, {
6742
6971
  variant: "outline",
6743
- children: ["+", value.length - 5]
6972
+ children: ["+", value.length - maxItems]
6744
6973
  })]
6745
6974
  }) : null;
6746
6975
  default: return String(value);
6747
6976
  }
6748
6977
  }
6749
- /**
6750
- * ViewModal 组件 - 详情查看弹窗
6751
- */
6752
- function ViewModal({ open, onOpenChange, variant, data, schema, fields: fieldConfig, dynamicOptions, denyFields, locale }) {
6753
- if (!data) return null;
6754
- const shape = schema.shape;
6755
- const denySet = new Set(denyFields ?? []);
6978
+ function ViewModal(props) {
6979
+ if (!props.open || !props.data) return null;
6980
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ViewModalContent, { ...props });
6981
+ }
6982
+ function ViewModalContent({ open, onOpenChange, variant, data, schema, fields: fieldConfig, dynamicOptions, denyFields, locale, tableOverrides, view }) {
6983
+ const excluded = react.useMemo(() => {
6984
+ const denied = new Set(denyFields ?? []);
6985
+ return Object.keys(schema.shape).filter((key) => {
6986
+ const config = fieldConfig?.[key];
6987
+ return denied.has(key) || config?.hidden || view?.overrides?.[key]?.hidden || isDefaultHiddenAuditField(key) && !isFieldTableExplicitlyVisible(config) && view?.overrides?.[key]?.hidden !== false;
6988
+ });
6989
+ }, [
6990
+ schema,
6991
+ fieldConfig,
6992
+ denyFields,
6993
+ view?.overrides
6994
+ ]);
6995
+ const rows = react.useMemo(() => data ? [data] : [], [data]);
6996
+ const resolvedOptions = useDynamicResolveOptions(fieldConfig, rows, excluded);
6756
6997
  const content = /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dl", {
6757
6998
  className: "grid gap-4 py-4",
6758
- children: Object.entries(shape).filter(([key]) => {
6759
- if (denySet.has(key)) return false;
6760
- const config = fieldConfig?.[key];
6761
- if (config?.hidden) return false;
6762
- if (isDefaultHiddenAuditField(key) && !isFieldTableExplicitlyVisible(config)) return false;
6763
- return true;
6764
- }).map(([key, fieldSchema]) => {
6765
- const parsed = parseZodField(fieldSchema);
6766
- const label = fieldConfig?.[key]?.label ?? humanize(key);
6767
- const value = data[key];
6768
- const options = normalizeFieldOptions(fieldConfig?.[key]?.enum) ?? normalizeFieldOptions(dynamicOptions?.[key]);
6769
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6770
- className: "grid grid-cols-3 items-start gap-4",
6771
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("dt", {
6772
- className: "text-sm font-medium text-muted-foreground",
6773
- children: label
6774
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dd", {
6775
- className: "col-span-2 text-sm",
6776
- children: renderFieldValue(value, parsed.type, locale.boolean, options)
6777
- })]
6778
- }, key);
6779
- })
6999
+ children: (0, __tanstack_react_table.useReactTable)({
7000
+ data: rows,
7001
+ columns: react.useMemo(() => {
7002
+ const overrides = {};
7003
+ for (const [key, fieldSchema] of Object.entries(schema.shape).filter(([fieldKey]) => !excluded.includes(fieldKey))) {
7004
+ const config = fieldConfig?.[key] ?? {};
7005
+ const tableConfig = getTableConfig(config);
7006
+ const listOverride = tableOverrides?.[key];
7007
+ const options = getTableOptions(config) ?? resolvedOptions.optionsByField[key] ?? (tableConfig?.dataSource !== void 0 ? void 0 : dynamicOptions?.[key]);
7008
+ overrides[key] = {
7009
+ label: tableConfig?.label ?? config.label ?? listOverride?.label ?? humanize(key),
7010
+ index: tableConfig?.index ?? listOverride?.index,
7011
+ cell: ({ getValue }) => renderFieldValue(getValue(), parseZodField(fieldSchema).type, locale.boolean, options, tableConfig?.display ?? "auto", Infinity),
7012
+ ...view?.presentation === "table" ? listOverride : void 0,
7013
+ hidden: false,
7014
+ ...view?.overrides?.[key]
7015
+ };
7016
+ }
7017
+ return createTableSchema(schema, {
7018
+ overrides,
7019
+ exclude: excluded
7020
+ });
7021
+ }, [
7022
+ schema,
7023
+ excluded,
7024
+ fieldConfig,
7025
+ tableOverrides,
7026
+ view,
7027
+ resolvedOptions.optionsByField,
7028
+ dynamicOptions,
7029
+ locale.boolean
7030
+ ]),
7031
+ getCoreRowModel: (0, __tanstack_react_table.getCoreRowModel)()
7032
+ }).getRowModel().rows[0]?.getAllCells().map((cell) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7033
+ className: "grid grid-cols-3 items-start gap-4",
7034
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("dt", {
7035
+ className: "text-sm font-medium text-muted-foreground",
7036
+ children: cell.column.columnDef.meta?.label ?? humanize(cell.column.id)
7037
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dd", {
7038
+ className: "col-span-2 min-w-0 whitespace-pre-wrap break-words text-sm",
7039
+ children: (0, __tanstack_react_table.flexRender)(cell.column.columnDef.cell, cell.getContext())
7040
+ })]
7041
+ }, cell.id))
6780
7042
  });
6781
7043
  if (variant === "sheet") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__wordrhyme_shadcn.Sheet, {
6782
7044
  open,
@@ -6796,14 +7058,9 @@ function ViewModal({ open, onOpenChange, variant, data, schema, fields: fieldCon
6796
7058
  /**
6797
7059
  * 解析列表行操作
6798
7060
  */
6799
- function resolveActions(actionsOrFn, defaults, rowActionsLocale, context) {
6800
- const resolvedItems = typeof actionsOrFn === "function" ? actionsOrFn([
6801
- { type: "view" },
6802
- { type: "edit" },
6803
- { type: "copy" },
6804
- { type: "delete" }
6805
- ]) : actionsOrFn;
7061
+ function resolveActions(actions, defaults, rowActionsLocale, context) {
6806
7062
  const getContext = (row) => ({
7063
+ MenuItem: __wordrhyme_ui.DropdownMenuItem,
6807
7064
  crudId: context.crudId,
6808
7065
  idKey: context.idKey,
6809
7066
  row,
@@ -6811,54 +7068,13 @@ function resolveActions(actionsOrFn, defaults, rowActionsLocale, context) {
6811
7068
  openView: defaults.openView,
6812
7069
  ...defaults.openEdit ? { openEdit: defaults.openEdit } : {},
6813
7070
  ...defaults.copyRow ? { copyRow: defaults.copyRow } : {},
6814
- ...defaults.openDelete ? { openDelete: defaults.openDelete } : {}
7071
+ ...defaults.openDelete ? { openDelete: defaults.openDelete } : {},
7072
+ open: (options) => {
7073
+ if (options.type === "custom") context.openComponent(options.component);
7074
+ else handlerMap[options.type]?.(options.row);
7075
+ }
6815
7076
  });
6816
- const defaultItems = [
6817
- {
6818
- label: rowActionsLocale.view,
6819
- onClick: defaults.openView
6820
- },
6821
- ...defaults.openEdit ? [{
6822
- label: rowActionsLocale.edit,
6823
- onClick: defaults.openEdit
6824
- }] : [],
6825
- ...defaults.copyRow ? [{
6826
- label: rowActionsLocale.copy,
6827
- onClick: defaults.copyRow
6828
- }] : [],
6829
- ...defaults.openDelete ? [{
6830
- label: rowActionsLocale.delete,
6831
- onClick: defaults.openDelete,
6832
- separator: true,
6833
- variant: "destructive"
6834
- }] : []
6835
- ];
6836
- if (resolvedItems === void 0) return defaultItems;
6837
- const items = resolvedItems.filter((item) => !item.hidden);
6838
- if (items.length === 0) return [];
6839
- if (!items.some((i) => i.type !== "custom")) {
6840
- const startItems = items.filter((i) => i.type === "custom" && i.position === "start").map(({ label, onClick, component, separator, variant }) => ({
6841
- label,
6842
- onClick,
6843
- component,
6844
- getContext,
6845
- separator,
6846
- variant
6847
- }));
6848
- const endItems = items.filter((i) => i.type === "custom" && i.position !== "start").map(({ label, onClick, component, separator, variant }) => ({
6849
- label,
6850
- onClick,
6851
- component,
6852
- getContext,
6853
- separator,
6854
- variant
6855
- }));
6856
- return [
6857
- ...startItems,
6858
- ...defaultItems,
6859
- ...endItems
6860
- ];
6861
- }
7077
+ const items = actions.filter((item) => !item.hidden);
6862
7078
  const handlerMap = {
6863
7079
  view: defaults.openView,
6864
7080
  edit: defaults.openEdit,
@@ -6897,7 +7113,7 @@ function resolveActions(actionsOrFn, defaults, rowActionsLocale, context) {
6897
7113
  *
6898
7114
  * 高级 CRUD 表格组件,封装了完整的增删改查流程
6899
7115
  */
6900
- function AutoCrudTable({ id, title, description, schema, resource, fields, table: tableConfig, form: formConfig, permissions, actions: actionItems, toolbar, toolbarActions, locale: localeProp, onCreate }) {
7116
+ function AutoCrudTable({ id, title, description, schema, resource, fields, table: tableConfig, form: formConfig, view: viewConfig, permissions, actions: actionItems, toolbar, toolbarActions, locale: localeProp, onCreate }) {
6901
7117
  const locale = resolveLocale(localeProp);
6902
7118
  const resolvedSchema = resource.schema ?? schema;
6903
7119
  const resolvedFields = react.useMemo(() => applyResourceTablePresentation(mergeFields(resource.fields, fields), resource.fields) ?? {}, [fields, resource.fields]);
@@ -6923,18 +7139,6 @@ function AutoCrudTable({ id, title, description, schema, resource, fields, table
6923
7139
  tableConfig?.overrides
6924
7140
  ]);
6925
7141
  const dynamicResolveOptions = useDynamicResolveOptions(resolvedFields, resource.tableData.data, hiddenColumns);
6926
- const sharedResolveOptions = useDynamicResolveOptions(react.useMemo(() => Object.fromEntries(Object.entries(resolvedFields).flatMap(([field, config]) => {
6927
- const table = getTableConfig(config);
6928
- if (table?.options === void 0 && table?.dataSource === void 0) return [];
6929
- const { table: _table,...sharedConfig } = config;
6930
- return [[field, sharedConfig]];
6931
- })), [resolvedFields]), resource.tableData.data, hiddenColumns);
6932
- const viewDynamicOptionsByField = react.useMemo(() => mergeOptionsByField(mergeOptionsByField(dynamicFilterOptions.labelOptionsByField, sharedResolveOptions.optionsByField), Object.fromEntries(Object.entries(dynamicResolveOptions.optionsByField).filter(([field]) => !getTableConfig(resolvedFields[field] ?? {})?.dataSource))), [
6933
- dynamicFilterOptions.labelOptionsByField,
6934
- dynamicResolveOptions.optionsByField,
6935
- sharedResolveOptions.optionsByField,
6936
- resolvedFields
6937
- ]);
6938
7142
  const resourceIdKey = resource.idKey ?? "id";
6939
7143
  const actionRegistryVersion = useCrudActionsVersion();
6940
7144
  const unifiedActions = react.useMemo(() => {
@@ -7122,15 +7326,18 @@ function AutoCrudTable({ id, title, description, schema, resource, fields, table
7122
7326
  resource.handlers.openEdit,
7123
7327
  resource.handlers.openView
7124
7328
  ]);
7329
+ const { openComponent, dialog: rowActionDialog } = useRowActionDialog();
7125
7330
  const tableRowActions = react.useMemo(() => resolveActions(registryRowActions, rowActionDefaults, locale.rowActions, {
7126
7331
  crudId: id ?? "",
7127
- idKey: resourceIdKey
7332
+ idKey: resourceIdKey,
7333
+ openComponent
7128
7334
  }), [
7129
7335
  id,
7130
7336
  locale.rowActions,
7131
7337
  registryRowActions,
7132
7338
  resourceIdKey,
7133
- rowActionDefaults
7339
+ rowActionDefaults,
7340
+ openComponent
7134
7341
  ]);
7135
7342
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7136
7343
  className: "space-y-4",
@@ -7226,6 +7433,7 @@ function AutoCrudTable({ id, title, description, schema, resource, fields, table
7226
7433
  pageCount: resource.tableData.pageCount,
7227
7434
  ...resource.tableData.total !== void 0 ? { total: resource.tableData.total } : {},
7228
7435
  overrides: tableOverrides,
7436
+ classNames: tableConfig?.classNames,
7229
7437
  exclude: hiddenColumns,
7230
7438
  filterMode: tableConfig?.filterModes,
7231
7439
  search: searchConfig,
@@ -7241,6 +7449,7 @@ function AutoCrudTable({ id, title, description, schema, resource, fields, table
7241
7449
  onSelectedRowsChange: handleSelectedRowsChange,
7242
7450
  getSelectedRows: getSelectedRowsRef
7243
7451
  }),
7452
+ rowActionDialog,
7244
7453
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CrudFormModal, {
7245
7454
  open: resource.modal.createOpen || resource.modal.editOpen,
7246
7455
  onOpenChange: (open) => !open && resource.handlers.closeModals(),
@@ -7266,9 +7475,11 @@ function AutoCrudTable({ id, title, description, schema, resource, fields, table
7266
7475
  data: resource.modal.selected,
7267
7476
  schema: resolvedSchema,
7268
7477
  fields: resolvedFields,
7269
- dynamicOptions: viewDynamicOptionsByField,
7478
+ dynamicOptions: dynamicFilterOptions.labelOptionsByField,
7270
7479
  denyFields,
7271
- locale
7480
+ locale,
7481
+ tableOverrides,
7482
+ view: viewConfig
7272
7483
  }),
7273
7484
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__wordrhyme_shadcn.AlertDialog, {
7274
7485
  open: resource.modal.deleteOpen,
@@ -7465,10 +7676,7 @@ function getIsDateRange(value) {
7465
7676
  return value && typeof value === "object" && !Array.isArray(value);
7466
7677
  }
7467
7678
  function parseAsDate(timestamp) {
7468
- if (!timestamp) return void 0;
7469
- const numericTimestamp = typeof timestamp === "string" ? Number(timestamp) : timestamp;
7470
- const date = new Date(numericTimestamp);
7471
- return !Number.isNaN(date.getTime()) ? date : void 0;
7679
+ return parseCalendarDate(timestamp);
7472
7680
  }
7473
7681
  function parseColumnFilterValue(value) {
7474
7682
  if (value === null || value === void 0) return [];
@@ -7479,6 +7687,9 @@ function parseColumnFilterValue(value) {
7479
7687
  return [];
7480
7688
  }
7481
7689
  function DataTableDateFilter({ column, title, multiple }) {
7690
+ const formatterVersion = useDateFormatterVersion();
7691
+ const presentation = react.useMemo(() => calendarPresentation(), [formatterVersion]);
7692
+ const { formatDate: formatDate$1 } = presentation;
7482
7693
  const columnFilterValue = column.getFilterValue();
7483
7694
  const selectedDates = react.useMemo(() => {
7484
7695
  if (!columnFilterValue) return multiple ? {
@@ -7552,12 +7763,12 @@ function DataTableDateFilter({ column, title, multiple }) {
7552
7763
  from: nextFrom,
7553
7764
  to: nextTo
7554
7765
  });
7555
- if (nextTo) column.setFilterValue([nextFrom.getTime(), nextTo.getTime()]);
7766
+ if (nextTo) column.setFilterValue([serializeCalendarDate(nextFrom), serializeCalendarDate(nextTo)]);
7556
7767
  return;
7557
7768
  }
7558
7769
  setLocalRange(date);
7559
- if (from && to) column.setFilterValue([from, to]);
7560
- } else if (!multiple && "getTime" in date) column.setFilterValue(date.getTime());
7770
+ if (from && to) column.setFilterValue([serializeCalendarDate(new Date(from)), serializeCalendarDate(new Date(to))]);
7771
+ } else if (!multiple && "getTime" in date) column.setFilterValue(serializeCalendarDate(date));
7561
7772
  }, [
7562
7773
  column,
7563
7774
  multiple,
@@ -7577,9 +7788,9 @@ function DataTableDateFilter({ column, title, multiple }) {
7577
7788
  }, [multiple, selectedDates]);
7578
7789
  const formatDateRange = react.useCallback((range) => {
7579
7790
  if (!range.from && !range.to) return "";
7580
- if (range.from && range.to) return `${formatDate(range.from)} - ${formatDate(range.to)}`;
7581
- return formatDate(range.from ?? range.to);
7582
- }, []);
7791
+ if (range.from && range.to) return `${formatDate$1(range.from)} - ${formatDate$1(range.to)}`;
7792
+ return formatDate$1(range.from ?? range.to);
7793
+ }, [formatDate$1]);
7583
7794
  const label = react.useMemo(() => {
7584
7795
  if (multiple) {
7585
7796
  if (!getIsDateRange(selectedDates)) return null;
@@ -7595,7 +7806,7 @@ function DataTableDateFilter({ column, title, multiple }) {
7595
7806
  }
7596
7807
  if (getIsDateRange(selectedDates)) return null;
7597
7808
  const hasSelectedDate = selectedDates.length > 0;
7598
- const dateText = hasSelectedDate ? formatDate(selectedDates[0]) : "Select date";
7809
+ const dateText = hasSelectedDate ? formatDate$1(selectedDates[0]) : "Select date";
7599
7810
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
7600
7811
  className: "flex items-center gap-2",
7601
7812
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: title }), hasSelectedDate && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(__wordrhyme_shadcn.Separator, {
@@ -7607,7 +7818,8 @@ function DataTableDateFilter({ column, title, multiple }) {
7607
7818
  selectedDates,
7608
7819
  multiple,
7609
7820
  formatDateRange,
7610
- title
7821
+ title,
7822
+ formatDate$1
7611
7823
  ]);
7612
7824
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(__wordrhyme_shadcn.Popover, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(__wordrhyme_shadcn.PopoverTrigger, {
7613
7825
  asChild: true,
@@ -7617,7 +7829,7 @@ function DataTableDateFilter({ column, title, multiple }) {
7617
7829
  className: "border-dashed font-normal",
7618
7830
  children: [hasValue ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
7619
7831
  role: "button",
7620
- "aria-label": `Clear ${title} filter`,
7832
+ "aria-label": presentation.clearLabel(title),
7621
7833
  tabIndex: 0,
7622
7834
  onClick: onReset,
7623
7835
  className: "rounded-sm opacity-70 transition-opacity hover:opacity-100 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring",
@@ -7628,14 +7840,24 @@ function DataTableDateFilter({ column, title, multiple }) {
7628
7840
  className: "w-auto p-0",
7629
7841
  align: "start",
7630
7842
  children: multiple ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__wordrhyme_shadcn.Calendar, {
7843
+ lang: presentation.locale,
7844
+ weekStartsOn: presentation.weekStartsOn,
7845
+ formatters: presentation.formatters,
7846
+ labels: presentation.labels,
7631
7847
  autoFocus: true,
7632
7848
  captionLayout: "dropdown",
7633
7849
  mode: "range",
7850
+ defaultMonth: localRange.from,
7634
7851
  selected: localRange,
7635
7852
  onSelect
7636
7853
  }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__wordrhyme_shadcn.Calendar, {
7854
+ lang: presentation.locale,
7855
+ weekStartsOn: presentation.weekStartsOn,
7856
+ formatters: presentation.formatters,
7857
+ labels: presentation.labels,
7637
7858
  captionLayout: "dropdown",
7638
7859
  mode: "single",
7860
+ defaultMonth: !getIsDateRange(selectedDates) ? selectedDates[0] : void 0,
7639
7861
  selected: !getIsDateRange(selectedDates) ? selectedDates[0] : void 0,
7640
7862
  onSelect
7641
7863
  })