@wordrhyme/auto-crud 1.7.0 → 1.8.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
@@ -43,12 +43,12 @@ let __wordrhyme_ui = require("@wordrhyme/ui");
43
43
  __wordrhyme_ui = __toESM(__wordrhyme_ui);
44
44
  let zod = require("zod");
45
45
  zod = __toESM(zod);
46
+ let __wordrhyme_formily_shadcn = require("@wordrhyme/formily-shadcn");
47
+ __wordrhyme_formily_shadcn = __toESM(__wordrhyme_formily_shadcn);
46
48
  let __formily_core = require("@formily/core");
47
49
  __formily_core = __toESM(__formily_core);
48
50
  let __formily_reactive = require("@formily/reactive");
49
51
  __formily_reactive = __toESM(__formily_reactive);
50
- let __wordrhyme_formily_shadcn = require("@wordrhyme/formily-shadcn");
51
- __wordrhyme_formily_shadcn = __toESM(__wordrhyme_formily_shadcn);
52
52
  let sonner = require("sonner");
53
53
  sonner = __toESM(sonner);
54
54
  let __tanstack_react_query = require("@tanstack/react-query");
@@ -67,12 +67,66 @@ function DataTablePagination({ table, pageSizeOptions = [
67
67
  30,
68
68
  40,
69
69
  50
70
- ], className,...props }) {
70
+ ], alwaysShowFirstLast = false, responsive = false, showPageJump = false, pageJumpLabel = "Go to page", className,...props }) {
71
+ const pageJump = (0, react.useRef)(null);
72
+ const container = (0, react.useRef)(null);
73
+ const summary = (0, react.useRef)(null);
74
+ const controls = (0, react.useRef)(null);
75
+ const navigation = (0, react.useRef)(null);
76
+ const [hasSpace, setHasSpace] = (0, react.useState)(false);
77
+ const hideExtras = responsive && !hasSpace;
78
+ const hiddenClass = hideExtras ? "absolute invisible pointer-events-none left-0 top-0" : void 0;
79
+ const pageIndex = table.getState().pagination.pageIndex;
80
+ const pageCount = table.getPageCount();
81
+ const canJump = Number.isFinite(pageCount) && pageCount > 0;
71
82
  const total = table.options.rowCount ?? (table.options.manualPagination ? void 0 : table.getRowCount());
83
+ (0, react.useEffect)(() => {
84
+ if (!responsive || !container.current || !summary.current || !controls.current || !navigation.current) return;
85
+ if (typeof ResizeObserver === "undefined") return;
86
+ const root = container.current;
87
+ const info = summary.current;
88
+ const group = controls.current;
89
+ const nav = navigation.current;
90
+ const jump = pageJump.current;
91
+ const gap = (node) => parseFloat(getComputedStyle(node).columnGap) || 0;
92
+ const width = (node) => {
93
+ const children = Array.from(node.children);
94
+ return children.reduce((sum, child) => {
95
+ const style = getComputedStyle(child);
96
+ return sum + (child === nav || child === jump ? width(child) : child.getBoundingClientRect().width) + (parseFloat(style.marginLeft) || 0) + (parseFloat(style.marginRight) || 0);
97
+ }, 0) + Math.max(0, children.length - 1) * gap(node);
98
+ };
99
+ const measure = () => {
100
+ const style = getComputedStyle(root);
101
+ const available = root.clientWidth - (parseFloat(style.paddingLeft) || 0) - (parseFloat(style.paddingRight) || 0);
102
+ const required = style.flexDirection.startsWith("column") ? Math.max(width(info), width(group)) : width(info) + width(group) + gap(root);
103
+ setHasSpace(available + .5 >= required);
104
+ };
105
+ const observer = new ResizeObserver(measure);
106
+ for (const node of [
107
+ root,
108
+ info,
109
+ group,
110
+ nav,
111
+ ...info.children,
112
+ ...group.children,
113
+ ...nav.children,
114
+ ...jump?.children ?? []
115
+ ]) observer.observe(node);
116
+ measure();
117
+ return () => observer.disconnect();
118
+ }, [
119
+ responsive,
120
+ showPageJump,
121
+ alwaysShowFirstLast,
122
+ total !== void 0
123
+ ]);
72
124
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
73
- className: cn("flex w-full flex-col-reverse items-center justify-between gap-4 overflow-auto p-1 sm:flex-row sm:gap-8", className),
125
+ ref: container,
126
+ className: cn("flex w-full flex-col-reverse items-center justify-between gap-4 overflow-auto p-1 sm:flex-row sm:gap-8", responsive && "relative", className),
74
127
  ...props,
75
128
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
129
+ ref: summary,
76
130
  className: "flex flex-1 items-center gap-6 whitespace-nowrap text-muted-foreground text-sm",
77
131
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [
78
132
  table.getFilteredSelectedRowModel().rows.length,
@@ -82,7 +136,8 @@ function DataTablePagination({ table, pageSizeOptions = [
82
136
  " row(s) selected."
83
137
  ] }), total !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: ["Total: ", total] })]
84
138
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
85
- className: "flex flex-col-reverse items-center gap-4 sm:flex-row sm:gap-6 lg:gap-8",
139
+ ref: controls,
140
+ className: cn("flex items-center gap-4 sm:gap-6 lg:gap-8", responsive ? "flex-row flex-wrap justify-center whitespace-nowrap [&>*]:shrink-0" : "flex-col-reverse sm:flex-row"),
86
141
  children: [
87
142
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
88
143
  className: "flex items-center space-x-2",
@@ -110,19 +165,55 @@ function DataTablePagination({ table, pageSizeOptions = [
110
165
  className: "flex items-center justify-center font-medium text-sm",
111
166
  children: [
112
167
  "Page ",
113
- table.getState().pagination.pageIndex + 1,
168
+ pageIndex + 1,
114
169
  " of ",
115
- table.getPageCount()
170
+ pageCount
116
171
  ]
117
172
  }),
173
+ showPageJump && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
174
+ ref: pageJump,
175
+ className: hideExtras ? "pointer-events-none absolute inset-x-0 top-0 invisible overflow-hidden" : void 0,
176
+ "aria-hidden": hideExtras || void 0,
177
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("form", {
178
+ className: "flex w-max items-center gap-2",
179
+ onSubmit: (event) => {
180
+ event.preventDefault();
181
+ const page = Number(new FormData(event.currentTarget).get("page"));
182
+ if (canJump && Number.isInteger(page) && page >= 1 && page <= pageCount) table.setPageIndex(page - 1);
183
+ },
184
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
185
+ className: "flex items-center gap-2 whitespace-nowrap text-sm",
186
+ children: [pageJumpLabel, /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__wordrhyme_shadcn.Input, {
187
+ name: "page",
188
+ type: "number",
189
+ min: 1,
190
+ max: canJump ? pageCount : void 0,
191
+ step: 1,
192
+ required: true,
193
+ defaultValue: pageIndex + 1,
194
+ disabled: !canJump,
195
+ className: "h-8 w-20"
196
+ }, `${pageIndex}-${pageCount}`)]
197
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__wordrhyme_shadcn.Button, {
198
+ type: "submit",
199
+ variant: "outline",
200
+ size: "icon",
201
+ className: "size-8",
202
+ disabled: !canJump,
203
+ "aria-label": pageJumpLabel,
204
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.ChevronRight, {})
205
+ })]
206
+ })
207
+ }),
118
208
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
209
+ ref: navigation,
119
210
  className: "flex items-center space-x-2",
120
211
  children: [
121
212
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__wordrhyme_shadcn.Button, {
122
213
  "aria-label": "Go to first page",
123
214
  variant: "outline",
124
215
  size: "icon",
125
- className: "hidden size-8 lg:flex",
216
+ className: cn("size-8", alwaysShowFirstLast ? "flex" : "hidden lg:flex", hiddenClass),
126
217
  onClick: () => table.setPageIndex(0),
127
218
  disabled: !table.getCanPreviousPage(),
128
219
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.ChevronsLeft, {})
@@ -149,9 +240,9 @@ function DataTablePagination({ table, pageSizeOptions = [
149
240
  "aria-label": "Go to last page",
150
241
  variant: "outline",
151
242
  size: "icon",
152
- className: "hidden size-8 lg:flex",
243
+ className: cn("size-8", alwaysShowFirstLast ? "flex" : "hidden lg:flex", hiddenClass),
153
244
  onClick: () => table.setPageIndex(table.getPageCount() - 1),
154
- disabled: !table.getCanNextPage(),
245
+ disabled: !table.getCanNextPage() || alwaysShowFirstLast && !canJump,
155
246
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.ChevronsRight, {})
156
247
  })
157
248
  ]
@@ -497,7 +588,7 @@ function formatDate(date, opts = {}, preset) {
497
588
 
498
589
  //#endregion
499
590
  //#region src/components/data-table/data-table.tsx
500
- function DataTable({ table, actionBar, children, className, classNames,...props }) {
591
+ function DataTable({ table, actionBar, pagination, children, className, classNames,...props }) {
501
592
  useDateFormatterVersion();
502
593
  const columns = [
503
594
  ...table.getLeftVisibleLeafColumns(),
@@ -562,7 +653,10 @@ function DataTable({ table, actionBar, children, className, classNames,...props
562
653
  }),
563
654
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
564
655
  className: "flex flex-col gap-2.5",
565
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(DataTablePagination, { table }), actionBar && table.getFilteredSelectedRowModel().rows.length > 0 && actionBar]
656
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(DataTablePagination, {
657
+ table,
658
+ ...pagination
659
+ }), actionBar && table.getFilteredSelectedRowModel().rows.length > 0 && actionBar]
566
660
  })
567
661
  ]
568
662
  });
@@ -1101,6 +1195,59 @@ function parseCalendarDate(value) {
1101
1195
  const date = new Date(Number(value));
1102
1196
  return Number.isNaN(date.getTime()) ? void 0 : date;
1103
1197
  }
1198
+ /** Resolve bounds and the calendar's today marker in the Host's configured time zone. */
1199
+ function calendarMaxDate(value) {
1200
+ if (value !== "today") return parseCalendarDate(value);
1201
+ return parseCalendarDate(calendarDay(/* @__PURE__ */ new Date(), calendarDayFormatter()));
1202
+ }
1203
+ function calendarDayFormatter() {
1204
+ return new Intl.DateTimeFormat("en-CA", {
1205
+ timeZone: getDateLocaleOptions()?.timeZone,
1206
+ year: "numeric",
1207
+ month: "2-digit",
1208
+ day: "2-digit"
1209
+ });
1210
+ }
1211
+ function calendarDay(date, formatter) {
1212
+ const parts = Object.fromEntries(formatter.formatToParts(date).map((part) => [part.type, part.value]));
1213
+ return `${parts.year}-${parts.month}-${parts.day}`;
1214
+ }
1215
+ /** Keep both the today marker and relative bounds current while a filter stays mounted. */
1216
+ function useCalendarToday() {
1217
+ useDateFormatterVersion();
1218
+ const timeZone = getDateLocaleOptions()?.timeZone;
1219
+ const [today, setToday] = (0, react.useState)(() => calendarMaxDate("today"));
1220
+ (0, react.useEffect)(() => {
1221
+ const formatter = calendarDayFormatter();
1222
+ let timer;
1223
+ const refresh = () => {
1224
+ clearTimeout(timer);
1225
+ const now = Date.now();
1226
+ const day = calendarDay(new Date(now), formatter);
1227
+ setToday((previous) => serializeCalendarDate(previous) === day ? previous : parseCalendarDate(day));
1228
+ let before = now;
1229
+ let after = now + 2880 * 60 * 1e3;
1230
+ while (after - before > 1) {
1231
+ const middle = Math.floor((before + after) / 2);
1232
+ if (calendarDay(new Date(middle), formatter) === day) before = middle;
1233
+ else after = middle;
1234
+ }
1235
+ timer = setTimeout(refresh, Math.max(1, after - Date.now()));
1236
+ };
1237
+ const onVisibilityChange = () => {
1238
+ if (!document.hidden) refresh();
1239
+ };
1240
+ refresh();
1241
+ window.addEventListener("focus", refresh);
1242
+ document.addEventListener("visibilitychange", onVisibilityChange);
1243
+ return () => {
1244
+ clearTimeout(timer);
1245
+ window.removeEventListener("focus", refresh);
1246
+ document.removeEventListener("visibilitychange", onVisibilityChange);
1247
+ };
1248
+ }, [timeZone]);
1249
+ return today;
1250
+ }
1104
1251
  function calendarPresentation() {
1105
1252
  const localeOptions = getDateLocaleOptions();
1106
1253
  const locale = localeOptions?.locale ?? "en-US";
@@ -1322,7 +1469,7 @@ function DataTableFilterList({ table, debounceMs = DEBOUNCE_MS$2, throttleMs = T
1322
1469
  });
1323
1470
  }
1324
1471
  function DataTableFilterItem$1({ filter, index: index$1, filterItemId, joinOperator, setJoinOperator, columns, onFilterUpdate, onFilterRemove }) {
1325
- useDateFormatterVersion();
1472
+ const today = useCalendarToday();
1326
1473
  const [showFieldSelector, setShowFieldSelector] = react.useState(false);
1327
1474
  const [showOperatorSelector, setShowOperatorSelector] = react.useState(false);
1328
1475
  const [showValueSelector, setShowValueSelector] = react.useState(false);
@@ -1451,6 +1598,7 @@ function DataTableFilterItem$1({ filter, index: index$1, filterItemId, joinOpera
1451
1598
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1452
1599
  className: "min-w-36 max-w-60 flex-1",
1453
1600
  children: onFilterInputRender$1({
1601
+ today,
1454
1602
  filter,
1455
1603
  inputId,
1456
1604
  column,
@@ -1481,7 +1629,9 @@ function DataTableFilterItem$1({ filter, index: index$1, filterItemId, joinOpera
1481
1629
  })
1482
1630
  });
1483
1631
  }
1484
- function onFilterInputRender$1({ filter, inputId, column, columnMeta, onFilterUpdate, showValueSelector, setShowValueSelector }) {
1632
+ function onFilterInputRender$1({ today, filter, inputId, column, columnMeta, onFilterUpdate, showValueSelector, setShowValueSelector }) {
1633
+ const maxDate = column.columnDef.meta?.maxDate;
1634
+ const upperBound = maxDate === "today" ? today : calendarMaxDate(maxDate);
1485
1635
  if (filter.operator === "isEmpty" || filter.operator === "isNotEmpty") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1486
1636
  id: inputId,
1487
1637
  role: "status",
@@ -1637,6 +1787,8 @@ function onFilterInputRender$1({ filter, inputId, column, columnMeta, onFilterUp
1637
1787
  align: "start",
1638
1788
  className: "w-auto p-0",
1639
1789
  children: filter.operator === "isBetween" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__wordrhyme_shadcn.Calendar, {
1790
+ today,
1791
+ disabled: upperBound ? { after: upperBound } : void 0,
1640
1792
  lang: calendarPresentation().locale,
1641
1793
  weekStartsOn: calendarPresentation().weekStartsOn,
1642
1794
  formatters: calendarPresentation().formatters,
@@ -1645,6 +1797,7 @@ function onFilterInputRender$1({ filter, inputId, column, columnMeta, onFilterUp
1645
1797
  autoFocus: true,
1646
1798
  captionLayout: "dropdown",
1647
1799
  mode: "range",
1800
+ excludeDisabled: !!column.columnDef.meta?.maxDate,
1648
1801
  selected: dateValue.length === 2 ? {
1649
1802
  from: parseCalendarDate(dateValue[0]),
1650
1803
  to: parseCalendarDate(dateValue[1])
@@ -1656,6 +1809,8 @@ function onFilterInputRender$1({ filter, inputId, column, columnMeta, onFilterUp
1656
1809
  onFilterUpdate(filter.filterId, { value: date ? [date.from ? serializeCalendarDate(date.from) : "", date.to ? serializeCalendarDate(date.to) : ""] : [] });
1657
1810
  }
1658
1811
  }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__wordrhyme_shadcn.Calendar, {
1812
+ today,
1813
+ disabled: upperBound ? { after: upperBound } : void 0,
1659
1814
  lang: calendarPresentation().locale,
1660
1815
  weekStartsOn: calendarPresentation().weekStartsOn,
1661
1816
  formatters: calendarPresentation().formatters,
@@ -1878,7 +2033,7 @@ function DataTableFilterMenu({ table, debounceMs = DEBOUNCE_MS$1, throttleMs = T
1878
2033
  }
1879
2034
  function DataTableFilterItem({ filter, filterItemId, columns, onFilterUpdate, onFilterRemove }) {
1880
2035
  {
1881
- useDateFormatterVersion();
2036
+ const today = useCalendarToday();
1882
2037
  const [showFieldSelector, setShowFieldSelector] = react.useState(false);
1883
2038
  const [showOperatorSelector, setShowOperatorSelector] = react.useState(false);
1884
2039
  const [showValueSelector, setShowValueSelector] = react.useState(false);
@@ -1969,6 +2124,7 @@ function DataTableFilterItem({ filter, filterItemId, columns, onFilterUpdate, on
1969
2124
  })]
1970
2125
  }),
1971
2126
  onFilterInputRender({
2127
+ today,
1972
2128
  filter,
1973
2129
  column,
1974
2130
  inputId,
@@ -1989,7 +2145,9 @@ function DataTableFilterItem({ filter, filterItemId, columns, onFilterUpdate, on
1989
2145
  }
1990
2146
  }
1991
2147
  function FilterValueSelector({ column, value, onSelect }) {
1992
- useDateFormatterVersion();
2148
+ const today = useCalendarToday();
2149
+ const maxDate = column.columnDef.meta?.maxDate;
2150
+ const upperBound = maxDate === "today" ? today : calendarMaxDate(maxDate);
1993
2151
  switch (column.columnDef.meta?.variant ?? "text") {
1994
2152
  case "boolean": return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(__wordrhyme_shadcn.CommandGroup, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(__wordrhyme_shadcn.CommandItem, {
1995
2153
  value: "true",
@@ -2018,6 +2176,8 @@ function FilterValueSelector({ column, value, onSelect }) {
2018
2176
  }, option.value)) });
2019
2177
  case "date":
2020
2178
  case "dateRange": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__wordrhyme_shadcn.Calendar, {
2179
+ today,
2180
+ disabled: upperBound ? { after: upperBound } : void 0,
2021
2181
  lang: calendarPresentation().locale,
2022
2182
  weekStartsOn: calendarPresentation().weekStartsOn,
2023
2183
  formatters: calendarPresentation().formatters,
@@ -2046,7 +2206,9 @@ function FilterValueSelector({ column, value, onSelect }) {
2046
2206
  }
2047
2207
  }
2048
2208
  }
2049
- function onFilterInputRender({ filter, column, inputId, onFilterUpdate, showValueSelector, setShowValueSelector }) {
2209
+ function onFilterInputRender({ today, filter, column, inputId, onFilterUpdate, showValueSelector, setShowValueSelector }) {
2210
+ const maxDate = column.columnDef.meta?.maxDate;
2211
+ const upperBound = maxDate === "today" ? today : calendarMaxDate(maxDate);
2050
2212
  if (filter.operator === "isEmpty" || filter.operator === "isNotEmpty") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2051
2213
  id: inputId,
2052
2214
  role: "status",
@@ -2205,6 +2367,8 @@ function onFilterInputRender({ filter, column, inputId, onFilterUpdate, showValu
2205
2367
  align: "start",
2206
2368
  className: "w-auto p-0",
2207
2369
  children: filter.operator === "isBetween" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__wordrhyme_shadcn.Calendar, {
2370
+ today,
2371
+ disabled: upperBound ? { after: upperBound } : void 0,
2208
2372
  lang: calendarPresentation().locale,
2209
2373
  weekStartsOn: calendarPresentation().weekStartsOn,
2210
2374
  formatters: calendarPresentation().formatters,
@@ -2212,6 +2376,7 @@ function onFilterInputRender({ filter, column, inputId, onFilterUpdate, showValu
2212
2376
  autoFocus: true,
2213
2377
  captionLayout: "dropdown",
2214
2378
  mode: "range",
2379
+ excludeDisabled: !!column.columnDef.meta?.maxDate,
2215
2380
  selected: dateValue.length === 2 ? {
2216
2381
  from: parseCalendarDate(dateValue[0]),
2217
2382
  to: parseCalendarDate(dateValue[1])
@@ -2223,6 +2388,8 @@ function onFilterInputRender({ filter, column, inputId, onFilterUpdate, showValu
2223
2388
  onFilterUpdate(filter.filterId, { value: date ? [date.from ? serializeCalendarDate(date.from) : "", date.to ? serializeCalendarDate(date.to) : ""] : [] });
2224
2389
  }
2225
2390
  }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__wordrhyme_shadcn.Calendar, {
2391
+ today,
2392
+ disabled: upperBound ? { after: upperBound } : void 0,
2226
2393
  lang: calendarPresentation().locale,
2227
2394
  weekStartsOn: calendarPresentation().weekStartsOn,
2228
2395
  formatters: calendarPresentation().formatters,
@@ -2758,6 +2925,7 @@ function AutoTableSimpleFilters({ table, shallow = false, filters: externalFilte
2758
2925
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SimpleDateFilter, {
2759
2926
  title: meta.label ?? column.id,
2760
2927
  multiple: meta.variant === "dateRange",
2928
+ maxDate: meta.maxDate,
2761
2929
  value,
2762
2930
  onChange: (v) => updateFilter(column.id, v)
2763
2931
  })
@@ -2980,7 +3148,9 @@ function SimpleSliderFilter({ title, range, unit, value, onChange }) {
2980
3148
  ]
2981
3149
  })] });
2982
3150
  }
2983
- function SimpleDateFilter({ title, multiple, value, onChange }) {
3151
+ function SimpleDateFilter({ title, maxDate, multiple, value, onChange }) {
3152
+ const today = useCalendarToday();
3153
+ const upperBound = maxDate === "today" ? today : calendarMaxDate(maxDate);
2984
3154
  const [open, setOpen] = react.useState(false);
2985
3155
  const commit = (next) => {
2986
3156
  setOpen(false);
@@ -3047,6 +3217,8 @@ function SimpleDateFilter({ title, multiple, value, onChange }) {
3047
3217
  className: "w-auto p-0",
3048
3218
  align: "start",
3049
3219
  children: multiple ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__wordrhyme_shadcn.Calendar, {
3220
+ today,
3221
+ disabled: upperBound ? { after: upperBound } : void 0,
3050
3222
  lang: presentation.locale,
3051
3223
  weekStartsOn: presentation.weekStartsOn,
3052
3224
  formatters: presentation.formatters,
@@ -3055,6 +3227,7 @@ function SimpleDateFilter({ title, multiple, value, onChange }) {
3055
3227
  autoFocus: true,
3056
3228
  captionLayout: "dropdown",
3057
3229
  mode: "range",
3230
+ excludeDisabled: !!upperBound,
3058
3231
  selected: localRange,
3059
3232
  onSelect: (range) => {
3060
3233
  if (!range?.from && !range?.to) {
@@ -3106,6 +3279,8 @@ function SimpleDateFilter({ title, multiple, value, onChange }) {
3106
3279
  }
3107
3280
  }
3108
3281
  }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__wordrhyme_shadcn.Calendar, {
3282
+ today,
3283
+ disabled: upperBound ? { after: upperBound } : void 0,
3109
3284
  lang: presentation.locale,
3110
3285
  weekStartsOn: presentation.weekStartsOn,
3111
3286
  formatters: presentation.formatters,
@@ -3684,7 +3859,7 @@ function generateCSVTemplate(headers) {
3684
3859
  function dataToCSV(data, opts = {}) {
3685
3860
  if (data.length === 0) return "";
3686
3861
  const allHeaders = Object.keys(data[0]);
3687
- const headers = (opts.headers ?? allHeaders).filter((h) => !opts.excludeColumns?.includes(h));
3862
+ const headers = (opts.headers ?? allHeaders).filter((h) => h !== "__crudExtensionProjection" && !opts.excludeColumns?.includes(h));
3688
3863
  return [headers.map((h) => escapeCSVField(h)).join(","), ...data.map((row) => headers.map((h) => escapeCSVField(String(row[h] ?? ""))).join(","))].join("\n");
3689
3864
  }
3690
3865
  function escapeCSVField(field) {
@@ -3773,7 +3948,7 @@ function downloadCSVTemplate(headers, filename = "template") {
3773
3948
  }).join(",") + "\n", filename);
3774
3949
  }
3775
3950
  function downloadCSV(csvContent, filename) {
3776
- const blob = new Blob([csvContent], { type: "text/csv;charset=utf-8;" });
3951
+ const blob = new Blob(["", csvContent], { type: "text/csv;charset=utf-8;" });
3777
3952
  const url = URL.createObjectURL(blob);
3778
3953
  const link = document.createElement("a");
3779
3954
  link.setAttribute("href", url);
@@ -4248,7 +4423,7 @@ const filterModeConfig = {
4248
4423
  tooltip: "Linear-style command filters"
4249
4424
  }
4250
4425
  };
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 }) {
4426
+ function AutoTable({ schema, data, pageCount = 1, pagination, total, overrides, classNames, enableRowSelection = true, exclude, actions, pinnedColumns, filterMode, search = false, onDeleteSelected, onUpdateSelected, batchUpdateFields, actionBarExtra, actionBarActions, deleteConfirmation, initialSorting, enableExport = true, showDefaultExport = true, onSelectedCountChange, onSelectedRowsChange, getSelectedRows }) {
4252
4427
  const modes = filterMode ? Array.isArray(filterMode) ? filterMode : [filterMode] : DEFAULT_MODES;
4253
4428
  const [currentMode, setCurrentMode] = useQueryState("filterMode", parseAsStringEnum(modes).withDefault(modes[0] ?? "simple"));
4254
4429
  const showToggle = modes.length > 1;
@@ -4411,6 +4586,7 @@ function AutoTable({ schema, data, pageCount = 1, total, overrides, classNames,
4411
4586
  })]
4412
4587
  }),
4413
4588
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DataTable, {
4589
+ pagination,
4414
4590
  table,
4415
4591
  classNames
4416
4592
  }),
@@ -4484,6 +4660,45 @@ function FormModal({ open, onOpenChange, title, description, children, variant =
4484
4660
  });
4485
4661
  }
4486
4662
 
4663
+ //#endregion
4664
+ //#region src/components/auto-crud/date-only-picker.tsx
4665
+ const primitives = {
4666
+ Button: __wordrhyme_ui.Button,
4667
+ Calendar: __wordrhyme_ui.Calendar,
4668
+ Popover: __wordrhyme_ui.Popover,
4669
+ PopoverContent: __wordrhyme_ui.PopoverContent,
4670
+ PopoverTrigger: __wordrhyme_ui.PopoverTrigger
4671
+ };
4672
+ function DatePicker({ dateInput,...props }) {
4673
+ if (props.valueFormat !== void 0 && dateInput === "utc-projection") {
4674
+ const value = props.value;
4675
+ props = {
4676
+ ...props,
4677
+ value: value instanceof Date ? Number.isNaN(value.getTime()) ? "" : parseCalendarDate(value.toISOString().slice(0, 10)) : typeof value === "string" && /^\d{4}-\d{2}-\d{2}T/.test(value) && !Number.isNaN(Date.parse(value)) ? parseCalendarDate(value.slice(0, 10)) : value
4678
+ };
4679
+ }
4680
+ useDateFormatterVersion();
4681
+ const presentation = calendarPresentation();
4682
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__wordrhyme_formily_shadcn.DatePicker, {
4683
+ primitives,
4684
+ ...props.valueFormat !== void 0 ? {
4685
+ captionLayout: "dropdown-months",
4686
+ formatters: presentation.formatters,
4687
+ labels: presentation.labels,
4688
+ weekStartsOn: presentation.weekStartsOn,
4689
+ placeholder: props.valueFormat
4690
+ } : {},
4691
+ ...props
4692
+ });
4693
+ }
4694
+ /** @deprecated Use DatePicker with valueFormat="YYYY-MM-DD". */
4695
+ function DateOnlyPicker(props) {
4696
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DatePicker, {
4697
+ ...props,
4698
+ valueFormat: "YYYY-MM-DD"
4699
+ });
4700
+ }
4701
+
4487
4702
  //#endregion
4488
4703
  //#region src/lib/schema-bridge/zod-to-formily.ts
4489
4704
  const DEFAULT_FORM_MANAGED_FIELDS = [
@@ -4586,6 +4801,27 @@ function createEditFormSchema(schema, options) {
4586
4801
  });
4587
4802
  }
4588
4803
 
4804
+ //#endregion
4805
+ //#region src/lib/crud-reference-projection.ts
4806
+ function isRecord$1(value) {
4807
+ return typeof value === "object" && value !== null && !Array.isArray(value);
4808
+ }
4809
+ /** Read a label only while its ID still matches the field's current value. */
4810
+ function readCrudReferenceOption(row, field, value) {
4811
+ if (!isRecord$1(row)) return void 0;
4812
+ const { __crudExtensionProjection: projections } = row;
4813
+ if (!isRecord$1(projections)) return void 0;
4814
+ const projection = projections[field];
4815
+ if (!isRecord$1(projection)) return void 0;
4816
+ if (typeof value !== "string" && typeof value !== "number") return void 0;
4817
+ if (typeof projection.refId !== "string" && typeof projection.refId !== "number") return;
4818
+ if (String(value).length === 0 || String(projection.refId) !== String(value) || typeof projection.display !== "string" || projection.display.length === 0) return;
4819
+ return {
4820
+ value: String(value),
4821
+ label: projection.display
4822
+ };
4823
+ }
4824
+
4589
4825
  //#endregion
4590
4826
  //#region src/lib/registries.ts
4591
4827
  function createRegistry(label) {
@@ -4625,6 +4861,7 @@ function createRegistry(label) {
4625
4861
  }
4626
4862
  };
4627
4863
  }
4864
+ const tablePagination = createRegistry("table pagination");
4628
4865
  const formComponents = createRegistry("form component");
4629
4866
  const components = formComponents;
4630
4867
  function normalizeDataSourceRegistration(registration) {
@@ -4763,6 +5000,14 @@ function AutoCrudMultiCombobox({ options = [], className,...props }) {
4763
5000
  });
4764
5001
  }
4765
5002
  const defaultFieldComponents = {
5003
+ DatePicker: {
5004
+ component: DatePicker,
5005
+ decorator: "FormItem"
5006
+ },
5007
+ DateOnlyPicker: {
5008
+ component: DateOnlyPicker,
5009
+ decorator: "FormItem"
5010
+ },
4766
5011
  Combobox: {
4767
5012
  component: FormilyCombobox,
4768
5013
  decorator: "FormItem"
@@ -4838,6 +5083,13 @@ function mergeDataSourceOptions(current, incoming) {
4838
5083
  for (const option of incoming) optionsByValue.set(option.value, option);
4839
5084
  return Array.from(optionsByValue.values());
4840
5085
  }
5086
+ function setAutoCrudDataSourceOptions(field, options) {
5087
+ const projectedOption = readCrudReferenceOption(field.form.initialValues, field.path.toString(), field.value);
5088
+ field.setDataSource(projectedOption ? mergeDataSourceOptions([{
5089
+ ...projectedOption,
5090
+ disabled: true
5091
+ }], options) : options);
5092
+ }
4841
5093
  function isNearPopupScrollBottom$1(target) {
4842
5094
  return target.scrollHeight - target.scrollTop - target.clientHeight <= POPUP_SCROLL_LOAD_THRESHOLD$1;
4843
5095
  }
@@ -4853,6 +5105,7 @@ function loadAutoCrudDataSource({ entry, field, registryVersion, search, sourceK
4853
5105
  values,
4854
5106
  search: searchValue
4855
5107
  });
5108
+ setAutoCrudDataSourceOptions(field, state.options);
4856
5109
  if (!append && state.loadKey === loadKey && state.registryVersion === registryVersion) return;
4857
5110
  if (entry.reset && state.dependencyKey !== void 0 && state.dependencyKey !== dependencyKey) field.setValue(void 0);
4858
5111
  state.dependencyKey = dependencyKey;
@@ -4895,7 +5148,7 @@ function loadAutoCrudDataSource({ entry, field, registryVersion, search, sourceK
4895
5148
  state.page = page ?? 0;
4896
5149
  state.hasMore = entry.loadMore ? normalizeHasMore(result) : false;
4897
5150
  state.appendLoading = false;
4898
- field.setDataSource(nextOptions);
5151
+ setAutoCrudDataSourceOptions(field, nextOptions);
4899
5152
  field.setLoading(false);
4900
5153
  field.setComponentProps({
4901
5154
  loading: false,
@@ -4907,7 +5160,7 @@ function loadAutoCrudDataSource({ entry, field, registryVersion, search, sourceK
4907
5160
  if (!append) {
4908
5161
  state.options = [];
4909
5162
  state.hasMore = false;
4910
- field.setDataSource([]);
5163
+ setAutoCrudDataSourceOptions(field, []);
4911
5164
  }
4912
5165
  state.appendLoading = false;
4913
5166
  field.setLoading(false);
@@ -4931,7 +5184,7 @@ function applySearchComponentProps({ entry, field, registryState, sourceKey, sta
4931
5184
  const currentEntry = dataSources.get(sourceKey);
4932
5185
  if (!currentEntry) {
4933
5186
  state.controller?.abort();
4934
- field.setDataSource([]);
5187
+ setAutoCrudDataSourceOptions(field, []);
4935
5188
  field.setLoading(false);
4936
5189
  field.setComponentProps({ loading: false });
4937
5190
  return;
@@ -4961,7 +5214,7 @@ function applyLoadMoreComponentProps({ entry, field, registryState, sourceKey, s
4961
5214
  state.controller?.abort();
4962
5215
  state.hasMore = false;
4963
5216
  state.appendLoading = false;
4964
- field.setDataSource([]);
5217
+ setAutoCrudDataSourceOptions(field, []);
4965
5218
  field.setLoading(false);
4966
5219
  field.setComponentProps({
4967
5220
  hasMore: false,
@@ -4994,14 +5247,14 @@ function createAutoCrudDataSourceReaction(config, registryState) {
4994
5247
  state.initialized = true;
4995
5248
  }
4996
5249
  if (!source) {
4997
- field.setDataSource([]);
5250
+ setAutoCrudDataSourceOptions(field, []);
4998
5251
  field.setLoading(false);
4999
5252
  return;
5000
5253
  }
5001
5254
  const entry = dataSources.get(source.key);
5002
5255
  if (!entry) {
5003
5256
  state.controller?.abort();
5004
- field.setDataSource([]);
5257
+ setAutoCrudDataSourceOptions(field, []);
5005
5258
  field.setLoading(false);
5006
5259
  return;
5007
5260
  }
@@ -5099,6 +5352,7 @@ const AutoForm = (0, react.forwardRef)(AutoFormInner);
5099
5352
  //#endregion
5100
5353
  //#region src/i18n/locale.ts
5101
5354
  const zhCN = {
5355
+ pagination: { pageJump: "跳转到页" },
5102
5356
  toolbar: {
5103
5357
  refresh: "刷新",
5104
5358
  create: "新建",
@@ -5166,6 +5420,7 @@ const zhCN = {
5166
5420
  }
5167
5421
  };
5168
5422
  const enUS = {
5423
+ pagination: { pageJump: "Go to page" },
5169
5424
  toolbar: {
5170
5425
  refresh: "Refresh",
5171
5426
  create: "New",
@@ -5233,6 +5488,7 @@ const enUS = {
5233
5488
  }
5234
5489
  };
5235
5490
  const jaJP = {
5491
+ pagination: { pageJump: "ページへ移動" },
5236
5492
  toolbar: {
5237
5493
  refresh: "更新",
5238
5494
  create: "新規作成",
@@ -5300,6 +5556,7 @@ const jaJP = {
5300
5556
  }
5301
5557
  };
5302
5558
  const koKR = {
5559
+ pagination: { pageJump: "페이지로 이동" },
5303
5560
  toolbar: {
5304
5561
  refresh: "새로고침",
5305
5562
  create: "새로 만들기",
@@ -5367,6 +5624,7 @@ const koKR = {
5367
5624
  }
5368
5625
  };
5369
5626
  const frFR = {
5627
+ pagination: { pageJump: "Aller à la page" },
5370
5628
  toolbar: {
5371
5629
  refresh: "Actualiser",
5372
5630
  create: "Nouveau",
@@ -5434,6 +5692,7 @@ const frFR = {
5434
5692
  }
5435
5693
  };
5436
5694
  const deDE = {
5695
+ pagination: { pageJump: "Zur Seite springen" },
5437
5696
  toolbar: {
5438
5697
  refresh: "Aktualisieren",
5439
5698
  create: "Neu",
@@ -5501,6 +5760,7 @@ const deDE = {
5501
5760
  }
5502
5761
  };
5503
5762
  const esES = {
5763
+ pagination: { pageJump: "Ir a la página" },
5504
5764
  toolbar: {
5505
5765
  refresh: "Actualizar",
5506
5766
  create: "Nuevo",
@@ -5737,7 +5997,11 @@ function ImportDialog({ open, onOpenChange, onImport, columns = [], title, local
5737
5997
  open,
5738
5998
  onOpenChange: handleOpenChange,
5739
5999
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(__wordrhyme_shadcn.DialogContent, {
5740
- className: "w-[calc(100vw-2rem)] sm:max-w-5xl",
6000
+ className: "max-h-[85vh] overflow-y-auto",
6001
+ style: {
6002
+ width: "calc(100vw - 4rem)",
6003
+ maxWidth: "52rem"
6004
+ },
5741
6005
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(__wordrhyme_shadcn.DialogHeader, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(__wordrhyme_shadcn.DialogTitle, { children: title ?? locale.title }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(__wordrhyme_shadcn.DialogDescription, { children: [
5742
6006
  step === "upload" && locale.uploadDescription,
5743
6007
  step === "preview" && locale.previewDescription(parsedData?.rows.length ?? 0),
@@ -6686,12 +6950,26 @@ function useDynamicResolveOptions(fields, rows, hiddenColumns) {
6686
6950
  rows,
6687
6951
  sourceEntries
6688
6952
  ]);
6689
- return { optionsByField };
6953
+ const displayOptionsByField = { ...optionsByField };
6954
+ for (const field of Object.keys(fields ?? {}).filter((key) => !hiddenColumns.includes(key))) {
6955
+ const projectedOptions = rows.flatMap((row) => {
6956
+ const option = readCrudReferenceOption(row, field, row[field]);
6957
+ return option ? [option] : [];
6958
+ });
6959
+ if (projectedOptions.length > 0) displayOptionsByField[field] = mergeFilterOptions(projectedOptions, optionsByField[field] ?? []);
6960
+ }
6961
+ return { optionsByField: displayOptionsByField };
6690
6962
  }
6691
6963
  function getOptionLabel(value, options) {
6692
6964
  const stringValue = String(value);
6693
6965
  return options?.find((option) => option.value === stringValue)?.label ?? stringValue;
6694
6966
  }
6967
+ function formatExportRow(row, fields, values = row) {
6968
+ return Object.fromEntries(Object.entries(values).map(([key, value]) => [key, fields[key]?.format?.(value, {
6969
+ row,
6970
+ target: "export"
6971
+ }) ?? value]));
6972
+ }
6695
6973
  /**
6696
6974
  * 从统一配置生成表格 overrides
6697
6975
  * 当 filter 配置存在时,合并到 meta 中(不影响列隐藏)
@@ -6788,10 +7066,16 @@ function buildTableOverrides(fields, legacyOverrides, dynamicFilterState, dynami
6788
7066
  const options = tableConfig?.dataSource ? mergeFieldOptions(dynamicResolveOptions, presentationOptions) : presentationOptions ?? mergedSharedDynamicOptions;
6789
7067
  result[key] = {
6790
7068
  ...result[key],
6791
- cell: ({ row }) => renderFieldValue(row.getValue(key), "string", {
6792
- true: "true",
6793
- false: "false"
6794
- }, options, display ?? "auto")
7069
+ cell: ({ row, getValue }) => {
7070
+ const value = getValue();
7071
+ return config.format?.(value, {
7072
+ row: row.original,
7073
+ target: "display"
7074
+ }) ?? renderFieldValue(value, "string", {
7075
+ true: "true",
7076
+ false: "false"
7077
+ }, options, display ?? "auto");
7078
+ }
6795
7079
  };
6796
7080
  }
6797
7081
  }
@@ -7008,7 +7292,13 @@ function ViewModalContent({ open, onOpenChange, variant, data, schema, fields: f
7008
7292
  overrides[key] = {
7009
7293
  label: tableConfig?.label ?? config.label ?? listOverride?.label ?? humanize(key),
7010
7294
  index: tableConfig?.index ?? listOverride?.index,
7011
- cell: ({ getValue }) => renderFieldValue(getValue(), parseZodField(fieldSchema).type, locale.boolean, options, tableConfig?.display ?? "auto", Infinity),
7295
+ cell: ({ getValue, row }) => {
7296
+ const value = getValue();
7297
+ return config.format?.(value, {
7298
+ row: row.original,
7299
+ target: "display"
7300
+ }) ?? renderFieldValue(value, parseZodField(fieldSchema).type, locale.boolean, options, tableConfig?.display ?? "auto", Infinity);
7301
+ },
7012
7302
  ...view?.presentation === "table" ? listOverride : void 0,
7013
7303
  hidden: false,
7014
7304
  ...view?.overrides?.[key]
@@ -7040,18 +7330,18 @@ function ViewModalContent({ open, onOpenChange, variant, data, schema, fields: f
7040
7330
  })]
7041
7331
  }, cell.id))
7042
7332
  });
7043
- if (variant === "sheet") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__wordrhyme_shadcn.Sheet, {
7333
+ if (variant === "sheet") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__wordrhyme_ui.Sheet, {
7044
7334
  open,
7045
7335
  onOpenChange,
7046
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(__wordrhyme_shadcn.SheetContent, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(__wordrhyme_shadcn.SheetHeader, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__wordrhyme_shadcn.SheetTitle, { children: locale.viewModal.title }) }), content] })
7336
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(__wordrhyme_ui.SheetContent, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(__wordrhyme_ui.SheetHeader, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__wordrhyme_ui.SheetTitle, { children: locale.viewModal.title }) }), content] })
7047
7337
  });
7048
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__wordrhyme_shadcn.Dialog, {
7338
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__wordrhyme_ui.Dialog, {
7049
7339
  open,
7050
7340
  onOpenChange,
7051
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(__wordrhyme_shadcn.DialogContent, {
7341
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(__wordrhyme_ui.DialogContent, {
7052
7342
  "aria-describedby": void 0,
7053
7343
  className: "max-w-2xl max-h-[80vh] overflow-y-auto",
7054
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(__wordrhyme_shadcn.DialogHeader, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__wordrhyme_shadcn.DialogTitle, { children: locale.viewModal.title }) }), content]
7344
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(__wordrhyme_ui.DialogHeader, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__wordrhyme_ui.DialogTitle, { children: locale.viewModal.title }) }), content]
7055
7345
  })
7056
7346
  });
7057
7347
  }
@@ -7115,6 +7405,9 @@ function resolveActions(actions, defaults, rowActionsLocale, context) {
7115
7405
  */
7116
7406
  function AutoCrudTable({ id, title, description, schema, resource, fields, table: tableConfig, form: formConfig, view: viewConfig, permissions, actions: actionItems, toolbar, toolbarActions, locale: localeProp, onCreate }) {
7117
7407
  const locale = resolveLocale(localeProp);
7408
+ const getPagination = react.useCallback(() => id ? tablePagination.get(id) : void 0, [id]);
7409
+ const registeredPagination = react.useSyncExternalStore(tablePagination.subscribe, getPagination, getPagination);
7410
+ const pagination = tableConfig?.pagination ?? registeredPagination;
7118
7411
  const resolvedSchema = resource.schema ?? schema;
7119
7412
  const resolvedFields = react.useMemo(() => applyResourceTablePresentation(mergeFields(resource.fields, fields), resource.fields) ?? {}, [fields, resource.fields]);
7120
7413
  const can = {
@@ -7187,13 +7480,13 @@ function AutoCrudTable({ id, title, description, schema, resource, fields, table
7187
7480
  if (mode === "selected") {
7188
7481
  const selectedRows$1 = getSelectedRowsRef.current?.();
7189
7482
  if (!selectedRows$1 || selectedRows$1.length === 0) return;
7190
- exportAllToCSV(selectedRows$1, {
7483
+ exportAllToCSV(selectedRows$1.map((row) => formatExportRow(row, resolvedFields)), {
7191
7484
  filename,
7192
7485
  excludeColumns
7193
7486
  });
7194
7487
  } else {
7195
7488
  if (!resource.handlers.export) return;
7196
- exportAllToCSV(await resource.handlers.export(), {
7489
+ exportAllToCSV((await resource.handlers.export()).map((row) => formatExportRow(row, resolvedFields)), {
7197
7490
  filename,
7198
7491
  excludeColumns
7199
7492
  });
@@ -7201,7 +7494,8 @@ function AutoCrudTable({ id, title, description, schema, resource, fields, table
7201
7494
  }, [
7202
7495
  resource.handlers.export,
7203
7496
  title,
7204
- denyFields
7497
+ denyFields,
7498
+ resolvedFields
7205
7499
  ]);
7206
7500
  const handleExportClick = react.useCallback(async () => {
7207
7501
  const mode = selectedCount > 0 ? "selected" : "filtered";
@@ -7212,6 +7506,26 @@ function AutoCrudTable({ id, title, description, schema, resource, fields, table
7212
7506
  setExporting(false);
7213
7507
  }
7214
7508
  }, [selectedCount, handleExport]);
7509
+ const exportBatchActions = react.useMemo(() => {
7510
+ if (!Object.values(resolvedFields).some((field) => field.format)) return registryBatchActions;
7511
+ return registryBatchActions.map((action$1) => {
7512
+ if (action$1.type !== "export" || action$1.onClick !== void 0 || Boolean(action$1.component)) return action$1;
7513
+ return {
7514
+ ...action$1,
7515
+ onClick: (_rows, { table }) => {
7516
+ const headers = table.getAllLeafColumns().map((column) => column.id).filter((key) => key !== "select" && key !== "actions" && !denyFields?.includes(key));
7517
+ exportAllToCSV(table.getFilteredSelectedRowModel().rows.map((row) => formatExportRow(row.original, resolvedFields, Object.fromEntries(headers.map((key) => [key, row.getValue(key)])))), {
7518
+ filename: "table",
7519
+ headers
7520
+ });
7521
+ }
7522
+ };
7523
+ });
7524
+ }, [
7525
+ registryBatchActions,
7526
+ resolvedFields,
7527
+ denyFields
7528
+ ]);
7215
7529
  const toolbarRefresh = react.useMemo(() => {
7216
7530
  const refresh = ownerRefreshAction?.onClick ?? resource.handlers.refresh;
7217
7531
  if (!refresh) return void 0;
@@ -7265,7 +7579,30 @@ function AutoCrudTable({ id, title, description, schema, resource, fields, table
7265
7579
  toolbarOpenCreate
7266
7580
  ]);
7267
7581
  const resolvedToolbarActions = resolveToolbarActionsWithResolver(id, registryToolbarActions, toolbarContext);
7268
- const tableOverrides = react.useMemo(() => mergeTableOverrides(buildTableOverrides(resolvedFields, tableConfig?.overrides, dynamicFilterOptions, dynamicResolveOptions), buildCapabilityTableOverrides(resolvedSchema, resource.capabilities)), [
7582
+ const { tableOverrides, detailTableOverrides } = react.useMemo(() => {
7583
+ const presentationOverrides = mergeTableOverrides(buildTableOverrides(resolvedFields, tableConfig?.overrides, dynamicFilterOptions, dynamicResolveOptions), buildCapabilityTableOverrides(resolvedSchema, resource.capabilities));
7584
+ const formattedOverrides = { ...presentationOverrides };
7585
+ for (const [key, config] of Object.entries(resolvedFields)) {
7586
+ const override = formattedOverrides[key];
7587
+ if (config.format && override?.cell === void 0 && resolvedSchema.shape[key]) {
7588
+ const { type } = parseZodField(resolvedSchema.shape[key]);
7589
+ formattedOverrides[key] = {
7590
+ ...override,
7591
+ cell: (context) => {
7592
+ const value = context.getValue();
7593
+ return config.format?.(value, {
7594
+ row: context.row.original,
7595
+ target: "display"
7596
+ }) ?? renderCell(value, type, context.column.columnDef.meta?.options);
7597
+ }
7598
+ };
7599
+ }
7600
+ }
7601
+ return {
7602
+ tableOverrides: formattedOverrides,
7603
+ detailTableOverrides: presentationOverrides
7604
+ };
7605
+ }, [
7269
7606
  resolvedFields,
7270
7607
  tableConfig?.overrides,
7271
7608
  dynamicFilterOptions,
@@ -7431,6 +7768,10 @@ function AutoCrudTable({ id, title, description, schema, resource, fields, table
7431
7768
  data: resource.tableData.data,
7432
7769
  schema: resolvedSchema,
7433
7770
  pageCount: resource.tableData.pageCount,
7771
+ pagination: pagination ? {
7772
+ pageJumpLabel: locale.pagination?.pageJump,
7773
+ ...pagination
7774
+ } : void 0,
7434
7775
  ...resource.tableData.total !== void 0 ? { total: resource.tableData.total } : {},
7435
7776
  overrides: tableOverrides,
7436
7777
  classNames: tableConfig?.classNames,
@@ -7441,7 +7782,7 @@ function AutoCrudTable({ id, title, description, schema, resource, fields, table
7441
7782
  onDeleteSelected: can.delete ? resource.handlers.deleteMany : void 0,
7442
7783
  onUpdateSelected: can.update ? resource.handlers.updateMany : void 0,
7443
7784
  batchUpdateFields: can.update ? batchFields : void 0,
7444
- actionBarActions: registryBatchActions,
7785
+ actionBarActions: exportBatchActions,
7445
7786
  deleteConfirmation: locale.bulkDeleteModal,
7446
7787
  enableExport: canExport,
7447
7788
  showDefaultExport: false,
@@ -7478,13 +7819,13 @@ function AutoCrudTable({ id, title, description, schema, resource, fields, table
7478
7819
  dynamicOptions: dynamicFilterOptions.labelOptionsByField,
7479
7820
  denyFields,
7480
7821
  locale,
7481
- tableOverrides,
7822
+ tableOverrides: detailTableOverrides,
7482
7823
  view: viewConfig
7483
7824
  }),
7484
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__wordrhyme_shadcn.AlertDialog, {
7825
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__wordrhyme_ui.AlertDialog, {
7485
7826
  open: resource.modal.deleteOpen,
7486
7827
  onOpenChange: (open) => !open && resource.handlers.closeModals(),
7487
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(__wordrhyme_shadcn.AlertDialogContent, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(__wordrhyme_shadcn.AlertDialogHeader, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(__wordrhyme_shadcn.AlertDialogTitle, { children: locale.deleteModal.title }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__wordrhyme_shadcn.AlertDialogDescription, { children: locale.deleteModal.description })] }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(__wordrhyme_shadcn.AlertDialogFooter, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(__wordrhyme_shadcn.AlertDialogCancel, { children: locale.deleteModal.cancel }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__wordrhyme_shadcn.AlertDialogAction, {
7828
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(__wordrhyme_ui.AlertDialogContent, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(__wordrhyme_ui.AlertDialogHeader, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(__wordrhyme_ui.AlertDialogTitle, { children: locale.deleteModal.title }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__wordrhyme_ui.AlertDialogDescription, { children: locale.deleteModal.description })] }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(__wordrhyme_ui.AlertDialogFooter, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(__wordrhyme_ui.AlertDialogCancel, { children: locale.deleteModal.cancel }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__wordrhyme_ui.AlertDialogAction, {
7488
7829
  onClick: resource.handlers.confirmDelete,
7489
7830
  disabled: resource.mutations.isDeleting,
7490
7831
  children: resource.mutations.isDeleting ? locale.deleteModal.confirming : locale.deleteModal.confirm
@@ -7687,6 +8028,9 @@ function parseColumnFilterValue(value) {
7687
8028
  return [];
7688
8029
  }
7689
8030
  function DataTableDateFilter({ column, title, multiple }) {
8031
+ const today = useCalendarToday();
8032
+ const maxDate = column.columnDef.meta?.maxDate;
8033
+ const upperBound = maxDate === "today" ? today : calendarMaxDate(maxDate);
7690
8034
  const formatterVersion = useDateFormatterVersion();
7691
8035
  const presentation = react.useMemo(() => calendarPresentation(), [formatterVersion]);
7692
8036
  const { formatDate: formatDate$1 } = presentation;
@@ -7840,6 +8184,8 @@ function DataTableDateFilter({ column, title, multiple }) {
7840
8184
  className: "w-auto p-0",
7841
8185
  align: "start",
7842
8186
  children: multiple ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__wordrhyme_shadcn.Calendar, {
8187
+ today,
8188
+ disabled: upperBound ? { after: upperBound } : void 0,
7843
8189
  lang: presentation.locale,
7844
8190
  weekStartsOn: presentation.weekStartsOn,
7845
8191
  formatters: presentation.formatters,
@@ -7847,10 +8193,13 @@ function DataTableDateFilter({ column, title, multiple }) {
7847
8193
  autoFocus: true,
7848
8194
  captionLayout: "dropdown",
7849
8195
  mode: "range",
8196
+ excludeDisabled: !!upperBound,
7850
8197
  defaultMonth: localRange.from,
7851
8198
  selected: localRange,
7852
8199
  onSelect
7853
8200
  }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__wordrhyme_shadcn.Calendar, {
8201
+ today,
8202
+ disabled: upperBound ? { after: upperBound } : void 0,
7854
8203
  lang: presentation.locale,
7855
8204
  weekStartsOn: presentation.weekStartsOn,
7856
8205
  formatters: presentation.formatters,
@@ -8968,6 +9317,7 @@ exports.resolveLocale = resolveLocale;
8968
9317
  exports.setDateFormatter = setDateFormatter;
8969
9318
  exports.setSearchParams = setSearchParams;
8970
9319
  exports.setToolbarResolver = setToolbarResolver;
9320
+ exports.tablePagination = tablePagination;
8971
9321
  exports.useAutoCrudResource = useAutoCrudResource;
8972
9322
  exports.useDataTable = useDataTable;
8973
9323
  exports.useQueryState = useQueryState;