@gridkitjs/react 0.11.1 → 0.13.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.js CHANGED
@@ -2,16 +2,16 @@
2
2
 
3
3
  // src/DataGrid/DataGrid.tsx
4
4
  import {
5
- useEffect as useEffect5,
6
- useId,
7
- useImperativeHandle,
8
- useMemo as useMemo4,
9
- useRef as useRef13,
10
- useState as useState15
5
+ useEffect as useEffect7,
6
+ useId as useId2,
7
+ useImperativeHandle as useImperativeHandle3,
8
+ useMemo as useMemo5,
9
+ useRef as useRef21,
10
+ useState as useState20
11
11
  } from "react";
12
12
  import {
13
13
  moveColumnBefore,
14
- resolveColumnHeaderText,
14
+ resolveColumnHeaderText as resolveColumnHeaderText2,
15
15
  resolveMessages,
16
16
  resolveRows,
17
17
  totalColumnWidth
@@ -28,7 +28,9 @@ import { createPortal } from "react-dom";
28
28
  import {
29
29
  buildKeyShortcuts,
30
30
  intentOf,
31
+ isColumnFiltered,
31
32
  KEYBOARD_STEP,
33
+ resolveColumnFilterMenu,
32
34
  sortDirectionFor,
33
35
  sortPriorityFor
34
36
  } from "@gridkitjs/core";
@@ -76,6 +78,27 @@ function GroupIcon() {
76
78
  /* @__PURE__ */ jsx2("rect", { x: "8.5", y: "14", width: "7", height: "7", rx: "1" })
77
79
  ] });
78
80
  }
81
+ function FilterIcon() {
82
+ return /* @__PURE__ */ jsx2(Icon, { children: /* @__PURE__ */ jsx2("path", { d: "M22 3H2l8 9.46V19l4 2v-8.54z" }) });
83
+ }
84
+ function ChevronLeftIcon() {
85
+ return /* @__PURE__ */ jsx2(Icon, { children: /* @__PURE__ */ jsx2("path", { d: "m15 18-6-6 6-6" }) });
86
+ }
87
+ function ChevronRightIcon() {
88
+ return /* @__PURE__ */ jsx2(Icon, { children: /* @__PURE__ */ jsx2("path", { d: "m9 18 6-6-6-6" }) });
89
+ }
90
+ function ChevronsLeftIcon() {
91
+ return /* @__PURE__ */ jsxs(Icon, { children: [
92
+ /* @__PURE__ */ jsx2("path", { d: "m11 17-5-5 5-5" }),
93
+ /* @__PURE__ */ jsx2("path", { d: "m18 17-5-5 5-5" })
94
+ ] });
95
+ }
96
+ function ChevronsRightIcon() {
97
+ return /* @__PURE__ */ jsxs(Icon, { children: [
98
+ /* @__PURE__ */ jsx2("path", { d: "m6 17 5-5-5-5" }),
99
+ /* @__PURE__ */ jsx2("path", { d: "m13 17 5-5-5-5" })
100
+ ] });
101
+ }
79
102
 
80
103
  // src/DataGrid/isOwnEvent.ts
81
104
  function isOwnEvent(event) {
@@ -114,7 +137,11 @@ function useGridNavigation({
114
137
  };
115
138
  const [gridFocus] = useState(() => createGridFocus(options));
116
139
  gridFocus.setOptions(options);
117
- const focus = useSyncExternalStore(gridFocus.subscribe, gridFocus.getFocus);
140
+ const focus = useSyncExternalStore(
141
+ gridFocus.subscribe,
142
+ gridFocus.getFocus,
143
+ gridFocus.getFocus
144
+ );
118
145
  const [rows] = useState(() => ({
119
146
  getHeaderRow: () => headerTableRef.current?.tHead?.rows[0] ?? null,
120
147
  getBodyRow: (rowIndex) => findBodyRow(bodyTableRef.current, rowIndex)
@@ -195,18 +222,28 @@ function GridHeader({
195
222
  resize,
196
223
  drag,
197
224
  controller,
198
- columnSort,
225
+ state,
199
226
  sortableColumns,
200
- groupBy,
201
227
  groupableColumns,
202
228
  groupToggleIconColumns,
203
229
  headerMenu,
230
+ panels,
231
+ messages,
204
232
  nav,
205
233
  selection
206
234
  }) {
235
+ const { columnSort, groupBy, filter } = state;
207
236
  const beforeId = drag.dropTarget?.kind === "column-order" ? drag.dropTarget.beforeId : void 0;
208
237
  const draggedEntry = columns.find((entry) => entry.id === drag.draggedColumnId) ?? null;
209
- return /* @__PURE__ */ jsxs2("thead", { children: [
238
+ function toggleFilterMenu(columnId) {
239
+ const { panel } = panels;
240
+ if (panel?.kind === "filter" && panel.target.area === "header" && panel.target.columnId === columnId) {
241
+ panels.close();
242
+ } else {
243
+ panels.open("filter", { area: "header", columnId });
244
+ }
245
+ }
246
+ return /* @__PURE__ */ jsxs2("thead", { role: "rowgroup", children: [
210
247
  /* @__PURE__ */ jsx3("tr", { className: "grid-header", role: "row", "aria-rowindex": 1, children: columns.map((entry, index) => {
211
248
  const { column } = entry;
212
249
  const resizing = resize.activeColumnId === entry.id;
@@ -222,11 +259,14 @@ function GridHeader({
222
259
  const isGrouped = groupBy.some(
223
260
  (level) => level.columnId === entry.id
224
261
  );
262
+ const filterable = resolveColumnFilterMenu(controller.getOptions(), column) !== null;
263
+ const filtered = filterable && isColumnFiltered(filter, entry.id);
225
264
  const shortcuts = buildKeyShortcuts({
226
265
  reorderable: entry.reorderable,
227
266
  resizable: entry.resizable,
228
267
  sortable,
229
268
  groupable,
269
+ filterable,
230
270
  contextMenu: headerMenu
231
271
  });
232
272
  return /* @__PURE__ */ jsxs2(
@@ -249,7 +289,7 @@ function GridHeader({
249
289
  if (drag.justDragged()) {
250
290
  return;
251
291
  }
252
- if (event.target instanceof Element && (event.target.closest(".header-resize-handle") !== null || event.target.closest(".header-sort-toggle") !== null || event.target.closest(".header-group-toggle") !== null)) {
292
+ if (event.target instanceof Element && (event.target.closest(".header-resize-handle") !== null || event.target.closest(".header-sort-toggle") !== null || event.target.closest(".header-group-toggle") !== null || event.target.closest(".header-filter-toggle") !== null)) {
253
293
  return;
254
294
  }
255
295
  selection.selectColumn(entry.id, intentOf(event));
@@ -280,7 +320,12 @@ function GridHeader({
280
320
  });
281
321
  return;
282
322
  }
283
- if (event.key === "ArrowDown" && event.altKey && groupable) {
323
+ if (event.key === "ArrowDown" && event.altKey && event.shiftKey && filterable) {
324
+ event.preventDefault();
325
+ toggleFilterMenu(entry.id);
326
+ return;
327
+ }
328
+ if (event.key === "ArrowDown" && event.altKey && !event.shiftKey && groupable) {
284
329
  event.preventDefault();
285
330
  controller.toggleGroupBy(entry.id);
286
331
  return;
@@ -311,6 +356,11 @@ function GridHeader({
311
356
  column.headerClassName ?? ""
312
357
  ),
313
358
  ...ariaAttr(shortcuts !== "", "aria-keyshortcuts", shortcuts),
359
+ ...ariaAttr(
360
+ filtered,
361
+ "aria-description",
362
+ messages.columnFiltered
363
+ ),
314
364
  ...ariaAttr(
315
365
  sortable && sortDirection !== null,
316
366
  "aria-sort",
@@ -380,6 +430,30 @@ function GridHeader({
380
430
  ]
381
431
  }
382
432
  ),
433
+ filterable && /*
434
+ * A pointer affordance only, like the sort toggle: the
435
+ * keyboard opens the menu from the header itself, under
436
+ * Alt+Shift+ArrowDown, and a screen reader hears the
437
+ * filtered state as the header's description.
438
+ */
439
+ /* @__PURE__ */ jsx3(
440
+ "span",
441
+ {
442
+ className: classNames(
443
+ "header-filter-toggle",
444
+ filtered ? "is-filtered" : ""
445
+ ),
446
+ "aria-hidden": "true",
447
+ tabIndex: -1,
448
+ onPointerDown: (event) => {
449
+ event.stopPropagation();
450
+ },
451
+ onClick: () => {
452
+ toggleFilterMenu(entry.id);
453
+ },
454
+ children: /* @__PURE__ */ jsx3(FilterIcon, {})
455
+ }
456
+ ),
383
457
  entry.resizable && /*
384
458
  * A pointer affordance only, and hidden from assistive
385
459
  * technology accordingly: a focusable handle per column would
@@ -472,7 +546,7 @@ function createRowPropsResolver() {
472
546
  // src/DataGrid/components/GridGroupRow.tsx
473
547
  import { memo } from "react";
474
548
  import {
475
- formatCellValue,
549
+ formatGroupValue,
476
550
  resolveColumnLabel
477
551
  } from "@gridkitjs/core";
478
552
 
@@ -492,9 +566,6 @@ function renderAggregate(value, spec, column, rows, locale) {
492
566
 
493
567
  // src/DataGrid/components/GridGroupRow.tsx
494
568
  import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
495
- function formatGroupValue(value, column, locale, messages) {
496
- return value === null || value === void 0 ? messages.blankGroupValue : formatCellValue(value, column, locale, void 0, messages);
497
- }
498
569
  function GridGroupRowComponent({
499
570
  columnCount,
500
571
  groupId,
@@ -671,7 +742,7 @@ var GridGroupSummaryRow_default = GridGroupSummaryRow;
671
742
  // src/DataGrid/components/GridRow.tsx
672
743
  import {
673
744
  accessDotted,
674
- formatCellValue as formatCellValue2
745
+ formatCellValue
675
746
  } from "@gridkitjs/core";
676
747
  import { memo as memo3 } from "react";
677
748
  import { jsx as jsx6 } from "react/jsx-runtime";
@@ -741,7 +812,7 @@ function GridRowComponent({
741
812
  datasetIndex,
742
813
  rowId,
743
814
  selected
744
- }) : formatCellValue2(value, column, locale, row, cellMessages)
815
+ }) : formatCellValue(value, column, locale, row, cellMessages)
745
816
  },
746
817
  id
747
818
  );
@@ -855,6 +926,7 @@ function GridBody({
855
926
  return /* @__PURE__ */ jsxs5(
856
927
  "tbody",
857
928
  {
929
+ role: "rowgroup",
858
930
  className: "grid-body",
859
931
  onFocus: (event) => {
860
932
  const group = groupRowFrom(event.target);
@@ -1040,7 +1112,7 @@ function GridFooter({
1040
1112
  rows,
1041
1113
  locale
1042
1114
  }) {
1043
- return /* @__PURE__ */ jsx8("tfoot", { children: /* @__PURE__ */ jsx8("tr", { className: "grid-footer", role: "row", children: columns.map((entry) => {
1115
+ return /* @__PURE__ */ jsx8("tfoot", { role: "rowgroup", children: /* @__PURE__ */ jsx8("tr", { className: "grid-footer", role: "row", children: columns.map((entry) => {
1044
1116
  const specs = aggregates.filter(
1045
1117
  (candidate) => candidate.columnId === entry.id
1046
1118
  );
@@ -1071,10 +1143,35 @@ function GridFooter({
1071
1143
  // src/DataGrid/components/GridPager.tsx
1072
1144
  import { paginationWindow } from "@gridkitjs/core";
1073
1145
  import { jsx as jsx9, jsxs as jsxs6 } from "react/jsx-runtime";
1146
+ function PagerNavButton({
1147
+ className,
1148
+ label,
1149
+ disabled,
1150
+ onClick,
1151
+ children
1152
+ }) {
1153
+ return /* @__PURE__ */ jsx9(
1154
+ "button",
1155
+ {
1156
+ type: "button",
1157
+ className: `grid-pager-button grid-pager-nav ${className}`,
1158
+ "aria-label": label,
1159
+ title: label,
1160
+ disabled,
1161
+ onClick,
1162
+ children
1163
+ }
1164
+ );
1165
+ }
1074
1166
  function GridPager({ pager, config, messages }) {
1075
1167
  const { pagination, pageCount, currentPage } = pager;
1076
1168
  const variant = config?.variant ?? "compact";
1077
1169
  const sizeOptions = config?.sizeOptions;
1170
+ const atFirst = pagination.pageIndex <= 0;
1171
+ const atLast = pagination.pageIndex >= pageCount - 1;
1172
+ const barStyle = {
1173
+ "--gridkit-pager-digits": String(String(Math.max(1, pageCount)).length)
1174
+ };
1078
1175
  return /* @__PURE__ */ jsxs6(
1079
1176
  "div",
1080
1177
  {
@@ -1082,59 +1179,90 @@ function GridPager({ pager, config, messages }) {
1082
1179
  role: "group",
1083
1180
  "aria-label": messages.pagerLabel,
1084
1181
  children: [
1085
- /* @__PURE__ */ jsx9(
1086
- "button",
1087
- {
1088
- type: "button",
1089
- className: "grid-pager-button",
1090
- disabled: pagination.pageIndex <= 0,
1091
- onClick: () => {
1092
- pager.previousPage();
1093
- },
1094
- children: messages.previousPage
1095
- }
1096
- ),
1097
- variant === "numbered" ? paginationWindow(currentPage, pageCount, {
1098
- boundaryCount: config?.boundaryCount,
1099
- siblingCount: config?.siblingCount
1100
- }).map(
1101
- (entry, index) => entry === "ellipsis" ? /* @__PURE__ */ jsx9(
1102
- "span",
1182
+ /* @__PURE__ */ jsxs6("div", { className: "grid-pager-bar", style: barStyle, children: [
1183
+ /* @__PURE__ */ jsx9(
1184
+ PagerNavButton,
1103
1185
  {
1104
- className: "grid-pager-ellipsis",
1105
- "aria-hidden": "true",
1106
- children: "\u2026"
1107
- },
1108
- `ellipsis-${String(index)}`
1109
- ) : /* @__PURE__ */ jsx9(
1110
- "button",
1186
+ className: "grid-pager-first",
1187
+ label: messages.firstPage,
1188
+ disabled: atFirst,
1189
+ onClick: () => {
1190
+ pager.goToPage(0);
1191
+ },
1192
+ children: /* @__PURE__ */ jsx9(ChevronsLeftIcon, {})
1193
+ }
1194
+ ),
1195
+ /* @__PURE__ */ jsx9(
1196
+ PagerNavButton,
1111
1197
  {
1112
- type: "button",
1113
- className: "grid-pager-button",
1114
- "aria-label": messages.pageButtonLabel(entry),
1115
- "aria-current": entry === currentPage ? "page" : void 0,
1198
+ className: "grid-pager-previous",
1199
+ label: messages.previousPage,
1200
+ disabled: atFirst,
1116
1201
  onClick: () => {
1117
- pager.goToPage(entry - 1);
1202
+ pager.previousPage();
1118
1203
  },
1119
- children: entry
1120
- },
1121
- entry
1204
+ children: /* @__PURE__ */ jsx9(ChevronLeftIcon, {})
1205
+ }
1206
+ ),
1207
+ variant === "numbered" ? paginationWindow(currentPage, pageCount, {
1208
+ boundaryCount: config?.boundaryCount,
1209
+ siblingCount: config?.siblingCount
1210
+ }).map(
1211
+ (entry, index) => entry === "ellipsis" ? /* @__PURE__ */ jsx9(
1212
+ "span",
1213
+ {
1214
+ className: "grid-pager-ellipsis",
1215
+ "aria-hidden": "true",
1216
+ children: "\u2026"
1217
+ },
1218
+ `ellipsis-${String(index)}`
1219
+ ) : /* @__PURE__ */ jsx9(
1220
+ "button",
1221
+ {
1222
+ type: "button",
1223
+ className: "grid-pager-button grid-pager-page",
1224
+ "aria-label": messages.pageButtonLabel(entry),
1225
+ "aria-current": entry === currentPage ? "page" : void 0,
1226
+ onClick: () => {
1227
+ pager.goToPage(entry - 1);
1228
+ },
1229
+ children: entry
1230
+ },
1231
+ entry
1232
+ )
1233
+ ) : /* @__PURE__ */ jsxs6("span", { className: "grid-pager-status-slot", children: [
1234
+ /* @__PURE__ */ jsx9("span", { className: "grid-pager-status", children: messages.pageStatus(
1235
+ pageCount === 0 ? 0 : currentPage,
1236
+ pageCount
1237
+ ) }),
1238
+ /* @__PURE__ */ jsx9("span", { className: "grid-pager-status-sizer", "aria-hidden": "true", children: messages.pageStatus(pageCount, pageCount) })
1239
+ ] }),
1240
+ /* @__PURE__ */ jsx9(
1241
+ PagerNavButton,
1242
+ {
1243
+ className: "grid-pager-next",
1244
+ label: messages.nextPage,
1245
+ disabled: atLast,
1246
+ onClick: () => {
1247
+ pager.nextPage();
1248
+ },
1249
+ children: /* @__PURE__ */ jsx9(ChevronRightIcon, {})
1250
+ }
1251
+ ),
1252
+ /* @__PURE__ */ jsx9(
1253
+ PagerNavButton,
1254
+ {
1255
+ className: "grid-pager-last",
1256
+ label: messages.lastPage,
1257
+ disabled: atLast,
1258
+ onClick: () => {
1259
+ pager.goToPage(pageCount - 1);
1260
+ },
1261
+ children: /* @__PURE__ */ jsx9(ChevronsRightIcon, {})
1262
+ }
1122
1263
  )
1123
- ) : /* @__PURE__ */ jsx9("span", { className: "grid-pager-status", children: messages.pageStatus(pageCount === 0 ? 0 : currentPage, pageCount) }),
1124
- /* @__PURE__ */ jsx9(
1125
- "button",
1126
- {
1127
- type: "button",
1128
- className: "grid-pager-button",
1129
- disabled: pagination.pageIndex >= pageCount - 1,
1130
- onClick: () => {
1131
- pager.nextPage();
1132
- },
1133
- children: messages.nextPage
1134
- }
1135
- ),
1264
+ ] }),
1136
1265
  sizeOptions !== void 0 && sizeOptions.length > 0 && /* @__PURE__ */ jsxs6("label", { className: "grid-pager-page-size", children: [
1137
- messages.pageSizeLabel,
1138
1266
  /* @__PURE__ */ jsx9(
1139
1267
  "select",
1140
1268
  {
@@ -1144,7 +1272,8 @@ function GridPager({ pager, config, messages }) {
1144
1272
  },
1145
1273
  children: sizeOptions.map((size) => /* @__PURE__ */ jsx9("option", { value: size, children: size }, size))
1146
1274
  }
1147
- )
1275
+ ),
1276
+ /* @__PURE__ */ jsx9("span", { className: "grid-pager-page-size-label", children: messages.pageSizeLabel })
1148
1277
  ] })
1149
1278
  ]
1150
1279
  }
@@ -1498,13 +1627,155 @@ function announcedCallbacks({
1498
1627
  };
1499
1628
  }
1500
1629
 
1630
+ // src/DataGrid/clipboard/useGridCopy.ts
1631
+ import { runCommand } from "@gridkitjs/core";
1632
+ import { isNativeCopy } from "@gridkitjs/dom";
1633
+ function isCopyKey(event) {
1634
+ return (event.ctrlKey || event.metaKey) && !event.altKey && (event.key === "c" || event.key === "C");
1635
+ }
1636
+ function useGridCopy({
1637
+ controller,
1638
+ commands,
1639
+ nav
1640
+ }) {
1641
+ function run(commandId, target) {
1642
+ const command = commands.commands.get(commandId);
1643
+ return command !== void 0 && runCommand(command, commands.contextFor(target));
1644
+ }
1645
+ function focusedCell() {
1646
+ const { rowIndex, columnIndex } = nav.focus;
1647
+ if (rowIndex === HEADER_ROW) {
1648
+ return null;
1649
+ }
1650
+ const { pageRows, columns } = controller.getView();
1651
+ const entry = pageRows[rowIndex];
1652
+ const column = columns[columnIndex];
1653
+ return entry === void 0 || "kind" in entry || column === void 0 ? null : { area: "cell", rowId: entry.rowId, columnId: column.id };
1654
+ }
1655
+ return (event) => {
1656
+ if (!isCopyKey(event) || isNativeCopy(event.target)) {
1657
+ return;
1658
+ }
1659
+ const cell = focusedCell();
1660
+ if (run("copy.selection", { area: "grid" }) || cell !== null && run("copy.cell", cell)) {
1661
+ event.preventDefault();
1662
+ }
1663
+ };
1664
+ }
1665
+
1501
1666
  // src/DataGrid/commands/useGridCommands.ts
1502
1667
  import { useMemo as useMemo3 } from "react";
1503
1668
  import {
1504
1669
  createCommandContext,
1505
1670
  resolveCommands,
1506
- runCommand
1671
+ runCommand as runCommand2
1672
+ } from "@gridkitjs/core";
1673
+
1674
+ // src/DataGrid/clipboard/copyToClipboard.ts
1675
+ import { writeClipboard } from "@gridkitjs/dom";
1676
+ function copyToClipboard(content, { messages, announce }) {
1677
+ writeClipboard(content).then(
1678
+ () => {
1679
+ announce(messages.copied(content.count, content.unit));
1680
+ },
1681
+ (error) => {
1682
+ console.error("DataGrid: the clipboard could not be written.", error);
1683
+ announce(messages.copyFailed);
1684
+ }
1685
+ );
1686
+ }
1687
+
1688
+ // src/DataGrid/export/useSaveFile.ts
1689
+ import { useRef as useRef2 } from "react";
1690
+ import { downloadBlob } from "@gridkitjs/dom";
1691
+
1692
+ // src/DataGrid/export/exportApi.ts
1693
+ import {
1694
+ exportFile,
1695
+ resolveExportWriters
1696
+ } from "@gridkitjs/core";
1697
+ function exportBlob({ content, mimeType }) {
1698
+ const part = typeof content === "string" ? content : content.slice();
1699
+ return new Blob([part], { type: mimeType });
1700
+ }
1701
+ function exportApi({
1702
+ contextFor
1703
+ }) {
1704
+ return {
1705
+ exportData: async (format, options) => {
1706
+ const context = contextFor({ area: "grid" });
1707
+ const { export: defaults, exportWriters } = context.options;
1708
+ const file = exportFile(
1709
+ context,
1710
+ format,
1711
+ { ...defaults, ...options },
1712
+ resolveExportWriters(exportWriters)
1713
+ );
1714
+ if (file === null) {
1715
+ throw new Error(
1716
+ "DataGrid: there is no export writer for that format \u2014 register one through `exportWriters`."
1717
+ );
1718
+ }
1719
+ return exportBlob(await file);
1720
+ }
1721
+ };
1722
+ }
1723
+
1724
+ // src/DataGrid/export/useSaveFile.ts
1725
+ function download(file) {
1726
+ downloadBlob(exportBlob(file), file.fileName);
1727
+ }
1728
+ function useSaveFile({
1729
+ messages,
1730
+ announce
1731
+ }) {
1732
+ const writingRef = useRef2(false);
1733
+ return (file) => {
1734
+ if (writingRef.current) {
1735
+ console.error(
1736
+ "DataGrid: an export is still being written, so this one was dropped."
1737
+ );
1738
+ return;
1739
+ }
1740
+ if (!(file instanceof Promise)) {
1741
+ download(file);
1742
+ return;
1743
+ }
1744
+ writingRef.current = true;
1745
+ file.then(download, (error) => {
1746
+ console.error("DataGrid: the export's writer failed.", error);
1747
+ announce(messages.exportFailed);
1748
+ }).finally(() => {
1749
+ writingRef.current = false;
1750
+ });
1751
+ };
1752
+ }
1753
+
1754
+ // src/DataGrid/print/printApi.ts
1755
+ import {
1756
+ exportPrintDocument
1507
1757
  } from "@gridkitjs/core";
1758
+ import { printDocument, printStyles } from "@gridkitjs/dom";
1759
+ function printGrid(document2, { styles = "" }) {
1760
+ return printDocument({
1761
+ title: document2.title,
1762
+ bodyHtml: document2.bodyHtml,
1763
+ styles: printStyles(document2.page) + styles
1764
+ });
1765
+ }
1766
+ function printApi({
1767
+ contextFor
1768
+ }) {
1769
+ return {
1770
+ print: (options) => {
1771
+ const context = contextFor({ area: "grid" });
1772
+ const merged = { ...context.options.print, ...options };
1773
+ return printGrid(exportPrintDocument(context, merged), merged);
1774
+ }
1775
+ };
1776
+ }
1777
+
1778
+ // src/DataGrid/commands/useGridCommands.ts
1508
1779
  function useGridCommands({
1509
1780
  controller,
1510
1781
  commands: appCommands,
@@ -1515,6 +1786,7 @@ function useGridCommands({
1515
1786
  openPanel
1516
1787
  }) {
1517
1788
  const commands = useMemo3(() => resolveCommands(appCommands), [appCommands]);
1789
+ const saveFile = useSaveFile({ messages, announce });
1518
1790
  const host = {
1519
1791
  autoFitColumns: (columnIds) => {
1520
1792
  const { columns } = controller.getView();
@@ -1530,7 +1802,16 @@ function useGridCommands({
1530
1802
  }
1531
1803
  },
1532
1804
  openPanel,
1533
- announce
1805
+ announce,
1806
+ saveFile,
1807
+ writeClipboard: (content) => {
1808
+ copyToClipboard(content, { messages, announce });
1809
+ },
1810
+ print: (document2, options) => {
1811
+ printGrid(document2, options).catch((error) => {
1812
+ console.error("DataGrid: the print failed.", error);
1813
+ });
1814
+ }
1534
1815
  };
1535
1816
  function contextFor(target) {
1536
1817
  return createCommandContext({ controller, target, messages, host });
@@ -1549,7 +1830,7 @@ function useGridCommands({
1549
1830
  console.error(`DataGrid: there is no command \`${commandId}\` to run.`);
1550
1831
  return false;
1551
1832
  }
1552
- if (!runCommand(command, contextFor(target))) {
1833
+ if (!runCommand2(command, contextFor(target))) {
1553
1834
  return false;
1554
1835
  }
1555
1836
  report(commandId, target, source);
@@ -1559,19 +1840,22 @@ function useGridCommands({
1559
1840
  }
1560
1841
 
1561
1842
  // src/DataGrid/menu/GridMenu.tsx
1562
- import { useLayoutEffect as useLayoutEffect3, useRef as useRef3, useState as useState4 } from "react";
1843
+ import { useLayoutEffect as useLayoutEffect3, useRef as useRef4, useState as useState4 } from "react";
1563
1844
  import {
1564
1845
  attachMenu
1565
1846
  } from "@gridkitjs/dom";
1566
1847
 
1567
1848
  // src/DataGrid/overlay/Overlay.tsx
1568
- import { useLayoutEffect as useLayoutEffect2, useRef as useRef2 } from "react";
1849
+ import { useLayoutEffect as useLayoutEffect2, useRef as useRef3 } from "react";
1569
1850
  import { createPortal as createPortal3 } from "react-dom";
1570
1851
  import {
1571
1852
  attachOverlay
1572
1853
  } from "@gridkitjs/dom";
1573
1854
  import { jsx as jsx11 } from "react/jsx-runtime";
1574
1855
  var ANCHOR_GAP = 4;
1856
+ function attachedFocus(focus) {
1857
+ return typeof focus === "object" && !(focus instanceof HTMLElement) ? focus.current ?? "first" : focus;
1858
+ }
1575
1859
  function overlayThemeClassName(element) {
1576
1860
  const scope = element?.closest(".gridkit-light, .gridkit-dark");
1577
1861
  if (scope === null || scope === void 0) {
@@ -1587,12 +1871,13 @@ function Overlay({
1587
1871
  initialFocus,
1588
1872
  returnFocusTo,
1589
1873
  ignoreScrollAfterInput,
1874
+ trapFocus,
1590
1875
  themeClassName,
1591
1876
  onDismiss,
1592
1877
  children
1593
1878
  }) {
1594
- const panelRef = useRef2(null);
1595
- const onDismissRef = useRef2(onDismiss);
1879
+ const panelRef = useRef3(null);
1880
+ const onDismissRef = useRef3(onDismiss);
1596
1881
  useLayoutEffect2(() => {
1597
1882
  onDismissRef.current = onDismiss;
1598
1883
  });
@@ -1605,9 +1890,10 @@ function Overlay({
1605
1890
  placement,
1606
1891
  gap,
1607
1892
  offset,
1608
- initialFocus,
1893
+ initialFocus: attachedFocus(initialFocus),
1609
1894
  returnFocusTo,
1610
1895
  ignoreScrollAfterInput,
1896
+ trapFocus,
1611
1897
  onDismiss: (reason) => {
1612
1898
  onDismissRef.current(reason);
1613
1899
  }
@@ -1619,7 +1905,8 @@ function Overlay({
1619
1905
  offset,
1620
1906
  initialFocus,
1621
1907
  returnFocusTo,
1622
- ignoreScrollAfterInput
1908
+ ignoreScrollAfterInput,
1909
+ trapFocus
1623
1910
  ]);
1624
1911
  return createPortal3(
1625
1912
  /* @__PURE__ */ jsx11(
@@ -1662,6 +1949,15 @@ var BUILT_IN_ICONS = {
1662
1949
  /* @__PURE__ */ jsx12("path", { d: "m18 8 4 4-4 4" }),
1663
1950
  /* @__PURE__ */ jsx12("path", { d: "M2 12h20" }),
1664
1951
  /* @__PURE__ */ jsx12("path", { d: "m6 8-4 4 4 4" })
1952
+ ] }),
1953
+ copy: () => /* @__PURE__ */ jsxs8(Icon, { children: [
1954
+ /* @__PURE__ */ jsx12("rect", { x: "8", y: "8", width: "14", height: "14", rx: "2" }),
1955
+ /* @__PURE__ */ jsx12("path", { d: "M4 16a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2" })
1956
+ ] }),
1957
+ print: () => /* @__PURE__ */ jsxs8(Icon, { children: [
1958
+ /* @__PURE__ */ jsx12("path", { d: "M6 18H4a2 2 0 0 1-2-2v-5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-2" }),
1959
+ /* @__PURE__ */ jsx12("path", { d: "M6 9V3a1 1 0 0 1 1-1h10a1 1 0 0 1 1 1v6" }),
1960
+ /* @__PURE__ */ jsx12("rect", { x: "6", y: "14", width: "12", height: "8", rx: "1" })
1665
1961
  ] })
1666
1962
  };
1667
1963
  var ICONS = new Map(Object.entries(BUILT_IN_ICONS));
@@ -1675,7 +1971,10 @@ function CheckIcon() {
1675
1971
  function ChevronDownIcon() {
1676
1972
  return /* @__PURE__ */ jsx12(Icon, { children: /* @__PURE__ */ jsx12("path", { d: "m6 9 6 6 6-6" }) });
1677
1973
  }
1678
- function ChevronRightIcon() {
1974
+ function ChevronLeftIcon2() {
1975
+ return /* @__PURE__ */ jsx12(Icon, { children: /* @__PURE__ */ jsx12("path", { d: "m15 18-6-6 6-6" }) });
1976
+ }
1977
+ function ChevronRightIcon2() {
1679
1978
  return /* @__PURE__ */ jsx12(Icon, { children: /* @__PURE__ */ jsx12("path", { d: "m9 18 6-6-6-6" }) });
1680
1979
  }
1681
1980
 
@@ -1694,25 +1993,25 @@ function MenuItem({
1694
1993
  entry,
1695
1994
  expanded
1696
1995
  }) {
1697
- const isItem = entry.type === "item";
1698
- const checked = isItem ? entry.checked : void 0;
1699
- const shortcut = isItem ? entry.shortcut : void 0;
1996
+ const isItem2 = entry.type === "item";
1997
+ const checked = isItem2 ? entry.checked : void 0;
1998
+ const shortcut = isItem2 ? entry.shortcut : void 0;
1700
1999
  return /* @__PURE__ */ jsxs9(
1701
2000
  "div",
1702
2001
  {
1703
2002
  role: checked === void 0 ? "menuitem" : "menuitemcheckbox",
1704
2003
  "data-gridkit-menu-item": entry.id,
1705
2004
  className: "menu-item",
1706
- ...ariaAttr(isItem && entry.disabled, "aria-disabled", true),
2005
+ ...ariaAttr(isItem2 && entry.disabled, "aria-disabled", true),
1707
2006
  ...ariaAttr(checked !== void 0, "aria-checked", checked),
1708
2007
  ...ariaAttr(shortcut !== void 0, "aria-keyshortcuts", shortcut),
1709
- ...ariaAttr(!isItem, "aria-haspopup", "menu"),
1710
- ...ariaAttr(!isItem, "aria-expanded", expanded),
2008
+ ...ariaAttr(!isItem2, "aria-haspopup", "menu"),
2009
+ ...ariaAttr(!isItem2, "aria-expanded", expanded),
1711
2010
  children: [
1712
2011
  /* @__PURE__ */ jsx13("span", { className: "menu-item-icon", "aria-hidden": "true", children: checked === true ? /* @__PURE__ */ jsx13(CheckIcon, {}) : /* @__PURE__ */ jsx13(MenuIcon, { name: entry.icon }) }),
1713
2012
  /* @__PURE__ */ jsx13("span", { className: "menu-item-label", children: entry.label }),
1714
2013
  shortcut !== void 0 && /* @__PURE__ */ jsx13("span", { className: "menu-item-shortcut", "aria-hidden": "true", children: shortcut }),
1715
- !isItem && /* @__PURE__ */ jsx13("span", { className: "menu-submenu-indicator", "aria-hidden": "true", children: /* @__PURE__ */ jsx13(ChevronRightIcon, {}) })
2014
+ !isItem2 && /* @__PURE__ */ jsx13("span", { className: "menu-submenu-indicator", "aria-hidden": "true", children: /* @__PURE__ */ jsx13(ChevronRightIcon2, {}) })
1716
2015
  ]
1717
2016
  }
1718
2017
  );
@@ -1726,11 +2025,11 @@ function GridMenu({
1726
2025
  onCloseSubmenu,
1727
2026
  behaviourRef
1728
2027
  }) {
1729
- const menuRef = useRef3(null);
1730
- const submenuBehaviourRef = useRef3(null);
2028
+ const menuRef = useRef4(null);
2029
+ const submenuBehaviourRef = useRef4(null);
1731
2030
  const [submenu, setSubmenu] = useState4(null);
1732
2031
  const isSubmenu = onCloseSubmenu !== void 0;
1733
- const latest = useRef3({ entries, submenu, onRun, onClose, onCloseSubmenu });
2032
+ const latest = useRef4({ entries, submenu, onRun, onClose, onCloseSubmenu });
1734
2033
  useLayoutEffect3(() => {
1735
2034
  latest.current = { entries, submenu, onRun, onClose, onCloseSubmenu };
1736
2035
  });
@@ -1869,7 +2168,7 @@ function GridContextMenu({
1869
2168
  import {
1870
2169
  useEffect as useEffect2,
1871
2170
  useLayoutEffect as useLayoutEffect4,
1872
- useRef as useRef4,
2171
+ useRef as useRef5,
1873
2172
  useState as useState5
1874
2173
  } from "react";
1875
2174
  import {
@@ -2028,8 +2327,8 @@ function useContextMenu({
2028
2327
  columnName
2029
2328
  }) {
2030
2329
  const [menu, setMenu] = useState5(null);
2031
- const current = useRef4(null);
2032
- const openings = useRef4(0);
2330
+ const current = useRef5(null);
2331
+ const openings = useRef5(0);
2033
2332
  const enabled = contextMenu !== void 0 && contextMenu !== false;
2034
2333
  function parts() {
2035
2334
  const { columns, pageRows } = controller.getView();
@@ -2126,7 +2425,7 @@ function useContextMenu({
2126
2425
  const place = placeAt(target, parts());
2127
2426
  return place !== null && openAt(place, point);
2128
2427
  }
2129
- const latestRequest = useRef4(handleRequest);
2428
+ const latestRequest = useRef5(handleRequest);
2130
2429
  useLayoutEffect4(() => {
2131
2430
  latestRequest.current = handleRequest;
2132
2431
  });
@@ -2139,7 +2438,7 @@ function useContextMenu({
2139
2438
  onRequest: (request) => latestRequest.current(request)
2140
2439
  });
2141
2440
  }, [rootRef, enabled]);
2142
- const shown = useRef4(null);
2441
+ const shown = useRef5(null);
2143
2442
  useLayoutEffect4(() => {
2144
2443
  const closed = shown.current;
2145
2444
  shown.current = menu;
@@ -2168,8 +2467,11 @@ function useContextMenu({
2168
2467
  };
2169
2468
  }
2170
2469
 
2470
+ // src/DataGrid/panels/GridPanels.tsx
2471
+ import { useRef as useRef13 } from "react";
2472
+
2171
2473
  // src/DataGrid/columnChooser/ColumnChooser.tsx
2172
- import { useLayoutEffect as useLayoutEffect5, useRef as useRef5, useState as useState6 } from "react";
2474
+ import { useLayoutEffect as useLayoutEffect5, useRef as useRef6, useState as useState6 } from "react";
2173
2475
  import { resolveColumnChooser } from "@gridkitjs/core";
2174
2476
  import { Fragment as Fragment3, jsx as jsx15, jsxs as jsxs10 } from "react/jsx-runtime";
2175
2477
  function ColumnChooser({
@@ -2177,7 +2479,7 @@ function ColumnChooser({
2177
2479
  messages
2178
2480
  }) {
2179
2481
  const [search, setSearch] = useState6("");
2180
- const showAllRef = useRef5(null);
2482
+ const showAllRef = useRef6(null);
2181
2483
  const { entries, selectAll, toggleAll } = resolveColumnChooser({
2182
2484
  columns: controller.getView().dataColumns,
2183
2485
  visibility: controller.getState().columnVisibility,
@@ -2253,95 +2555,983 @@ function ColumnChooser({
2253
2555
  );
2254
2556
  }
2255
2557
 
2256
- // src/DataGrid/panels/GridPanels.tsx
2257
- import { jsx as jsx16 } from "react/jsx-runtime";
2258
- var panelComponents = {
2259
- columnChooser: ColumnChooser
2260
- };
2261
- function GridPanels({
2262
- panels,
2263
- controller,
2264
- messages
2265
- }) {
2266
- const { panel, close } = panels;
2267
- if (panel === null) {
2268
- return null;
2269
- }
2270
- const Panel = panelComponents[panel.kind];
2271
- return /* @__PURE__ */ jsx16(
2272
- Overlay,
2273
- {
2274
- anchor: panel.anchor,
2275
- placement: panel.placement,
2276
- gap: panel.anchor instanceof Element ? ANCHOR_GAP : 0,
2277
- returnFocusTo: panel.returnFocusTo,
2278
- ignoreScrollAfterInput: true,
2279
- themeClassName: panel.themeClassName,
2280
- onDismiss: close,
2281
- children: /* @__PURE__ */ jsx16(
2282
- Panel,
2283
- {
2284
- target: panel.target,
2285
- controller,
2286
- messages
2287
- }
2288
- )
2289
- },
2290
- panel.key
2291
- );
2292
- }
2558
+ // src/DataGrid/filterMenu/FilterMenu.tsx
2559
+ import { useEffect as useEffect3, useRef as useRef11 } from "react";
2560
+ import {
2561
+ resolveColumnFilterMenu as resolveColumnFilterMenu2,
2562
+ resolveColumnHeaderText,
2563
+ resolveMenu as resolveMenu2
2564
+ } from "@gridkitjs/core";
2293
2565
 
2294
- // src/DataGrid/panels/useGridPanels.ts
2295
- import { useLayoutEffect as useLayoutEffect6, useRef as useRef6, useState as useState7 } from "react";
2296
- function toolbarItem(root, itemId) {
2297
- const items = root.querySelectorAll(
2298
- ":scope > .gridkit-toolbar > [data-gridkit-toolbar-item]"
2299
- );
2300
- return Array.from(items).find(
2301
- (item) => item.getAttribute("data-gridkit-toolbar-item") === itemId
2302
- ) ?? // In "More" while the toolbar is narrow: the button it is chosen from.
2303
- Array.from(items).find(
2304
- (item) => item.hasAttribute("data-gridkit-toolbar-more")
2305
- ) ?? null;
2306
- }
2307
- function headerCell2(headerTable, columnId) {
2308
- const cells = headerTable?.tHead?.rows[0]?.cells;
2309
- return cells === void 0 ? null : Array.from(cells).find(
2310
- (cell) => cell.getAttribute("data-gridkit-column") === columnId
2311
- ) ?? null;
2566
+ // src/DataGrid/filterMenu/ExcelFilterEditor.tsx
2567
+ import {
2568
+ useImperativeHandle as useImperativeHandle2,
2569
+ useLayoutEffect as useLayoutEffect9,
2570
+ useMemo as useMemo4,
2571
+ useReducer,
2572
+ useRef as useRef10,
2573
+ useState as useState10
2574
+ } from "react";
2575
+ import {
2576
+ describeColumnFilter as describeColumnFilter2,
2577
+ draftToFilter as draftToFilter2,
2578
+ filterChecklist,
2579
+ filterEditorReducer as filterEditorReducer2,
2580
+ initialFilterEditorDraft as initialFilterEditorDraft2,
2581
+ selectAllState
2582
+ } from "@gridkitjs/core";
2583
+
2584
+ // src/DataGrid/filterMenu/OperatorMenuButton.tsx
2585
+ import { useLayoutEffect as useLayoutEffect6, useRef as useRef7, useState as useState7 } from "react";
2586
+ import { Fragment as Fragment4, jsx as jsx16, jsxs as jsxs11 } from "react/jsx-runtime";
2587
+ function gapBeside(button) {
2588
+ const dialog = button.closest('[role="dialog"]') ?? button;
2589
+ return dialog.getBoundingClientRect().right - button.getBoundingClientRect().right + ANCHOR_GAP;
2312
2590
  }
2313
- function useGridPanels({
2314
- rootRef,
2315
- headerTableRef,
2316
- nav
2591
+ function OperatorMenuButton({
2592
+ menu,
2593
+ active,
2594
+ messages,
2595
+ onChoose,
2596
+ ref
2317
2597
  }) {
2318
- const [panel, setPanel] = useState7(null);
2319
- const openings = useRef6(0);
2320
- function placeFor(root, target) {
2321
- const element = target.area === "toolbar" ? toolbarItem(root, target.itemId) : target.area === "header" ? headerCell2(headerTableRef.current, target.columnId) : null;
2322
- if (element !== null) {
2323
- return {
2324
- anchor: element,
2325
- // The toolbar's own button sits at its far end.
2326
- placement: target.area === "toolbar" ? "bottom-end" : "bottom-start",
2327
- returnFocusTo: element
2328
- };
2329
- }
2330
- const { right, top } = root.getBoundingClientRect();
2331
- return {
2332
- anchor: { x: right, y: top },
2333
- placement: "bottom-end",
2334
- returnFocusTo: void 0
2335
- };
2336
- }
2337
- const shown = useRef6(null);
2598
+ const [open, setOpen] = useState7(null);
2599
+ const menuBehaviourRef = useRef7(null);
2600
+ const label = messages.filterOperatorMenu(menu.family);
2338
2601
  useLayoutEffect6(() => {
2339
- const closed = shown.current;
2340
- shown.current = panel;
2341
- if (panel === null && closed?.returnFocusTo?.isConnected === false && document.activeElement === document.body) {
2342
- nav.focusTabStop();
2602
+ if (open !== null && open.focusItem > 0) {
2603
+ menuBehaviourRef.current?.focusItem(open.focusItem);
2343
2604
  }
2344
- });
2605
+ }, [open]);
2606
+ function show(button, onLast = false) {
2607
+ setOpen({
2608
+ anchor: button,
2609
+ themeClassName: overlayThemeClassName(button),
2610
+ gap: gapBeside(button),
2611
+ focusItem: onLast ? menu.operators.length - 1 : 0
2612
+ });
2613
+ }
2614
+ function close() {
2615
+ setOpen(null);
2616
+ }
2617
+ const entries = menu.operators.map((operator) => ({
2618
+ type: "item",
2619
+ id: operator,
2620
+ label: messages.filterOperatorLabel(operator, menu.family),
2621
+ disabled: false,
2622
+ checked: active?.includes(operator),
2623
+ run: () => {
2624
+ close();
2625
+ onChoose(operator);
2626
+ return true;
2627
+ }
2628
+ }));
2629
+ return /* @__PURE__ */ jsxs11(Fragment4, { children: [
2630
+ /* @__PURE__ */ jsxs11(
2631
+ "button",
2632
+ {
2633
+ ref,
2634
+ type: "button",
2635
+ className: "filter-menu-submenu",
2636
+ "aria-haspopup": "menu",
2637
+ "aria-expanded": open !== null,
2638
+ onClick: (event) => {
2639
+ if (open === null) {
2640
+ show(event.currentTarget);
2641
+ } else {
2642
+ close();
2643
+ }
2644
+ },
2645
+ onKeyDown: (event) => {
2646
+ if ((event.key === "ArrowDown" || event.key === "ArrowUp") && open === null) {
2647
+ event.preventDefault();
2648
+ show(event.currentTarget, event.key === "ArrowUp");
2649
+ }
2650
+ },
2651
+ children: [
2652
+ /* @__PURE__ */ jsx16("span", { className: "filter-menu-submenu-icon", "aria-hidden": "true", children: active !== void 0 && /* @__PURE__ */ jsx16(CheckIcon, {}) }),
2653
+ label,
2654
+ /* @__PURE__ */ jsx16("span", { className: "filter-menu-submenu-indicator", "aria-hidden": "true", children: /* @__PURE__ */ jsx16(ChevronRightIcon2, {}) })
2655
+ ]
2656
+ }
2657
+ ),
2658
+ open !== null && /* @__PURE__ */ jsx16(
2659
+ Overlay,
2660
+ {
2661
+ anchor: open.anchor,
2662
+ placement: "right-start",
2663
+ gap: open.gap,
2664
+ returnFocusTo: open.anchor,
2665
+ themeClassName: open.themeClassName,
2666
+ onDismiss: close,
2667
+ children: /* @__PURE__ */ jsx16(
2668
+ GridMenu,
2669
+ {
2670
+ entries,
2671
+ label,
2672
+ themeClassName: open.themeClassName,
2673
+ behaviourRef: menuBehaviourRef,
2674
+ onRun: (item) => {
2675
+ item.run();
2676
+ },
2677
+ onClose: close
2678
+ }
2679
+ )
2680
+ }
2681
+ )
2682
+ ] });
2683
+ }
2684
+
2685
+ // src/DataGrid/filterMenu/SimpleFilterEditor.tsx
2686
+ import {
2687
+ Fragment as Fragment5,
2688
+ useId,
2689
+ useImperativeHandle,
2690
+ useLayoutEffect as useLayoutEffect7,
2691
+ useRef as useRef8,
2692
+ useState as useState8
2693
+ } from "react";
2694
+ import {
2695
+ conditionRowsFrom,
2696
+ conditionsFromRows,
2697
+ describeColumnFilter,
2698
+ draftToFilter,
2699
+ filterEditorReducer,
2700
+ initialFilterEditorDraft
2701
+ } from "@gridkitjs/core";
2702
+
2703
+ // src/DataGrid/filterMenu/ConditionRow.tsx
2704
+ import {
2705
+ filterOperandArity,
2706
+ parseFilterOperand
2707
+ } from "@gridkitjs/core";
2708
+ import { jsx as jsx17, jsxs as jsxs12 } from "react/jsx-runtime";
2709
+ function inputType(family, type) {
2710
+ switch (family) {
2711
+ case "text":
2712
+ return "text";
2713
+ case "number":
2714
+ return "number";
2715
+ case "date":
2716
+ return type === "date" ? "date" : "datetime-local";
2717
+ }
2718
+ }
2719
+ function OperandField({
2720
+ family,
2721
+ type,
2722
+ text,
2723
+ label,
2724
+ invalidMessageId,
2725
+ fieldRef,
2726
+ messages,
2727
+ onChange
2728
+ }) {
2729
+ const field = {
2730
+ ref: fieldRef,
2731
+ className: "filter-menu-value",
2732
+ "aria-label": label,
2733
+ "aria-invalid": invalidMessageId === void 0 ? void 0 : true,
2734
+ "aria-describedby": invalidMessageId,
2735
+ value: text,
2736
+ onChange: (event) => {
2737
+ onChange(event.currentTarget.value);
2738
+ }
2739
+ };
2740
+ if (family === "boolean") {
2741
+ return /* @__PURE__ */ jsxs12("select", { ...field, children: [
2742
+ /* @__PURE__ */ jsx17("option", { value: "" }),
2743
+ /* @__PURE__ */ jsx17("option", { value: "true", children: messages.booleanTrue }),
2744
+ /* @__PURE__ */ jsx17("option", { value: "false", children: messages.booleanFalse })
2745
+ ] });
2746
+ }
2747
+ return /* @__PURE__ */ jsx17("input", { ...field, type: inputType(family, type) });
2748
+ }
2749
+ function ConditionRow({
2750
+ row,
2751
+ position,
2752
+ menu,
2753
+ type,
2754
+ messages,
2755
+ invalidMessageId,
2756
+ focusRef,
2757
+ onChange,
2758
+ ref
2759
+ }) {
2760
+ const arity = filterOperandArity(row.operator);
2761
+ const operators = menu.operators.includes(row.operator) ? menu.operators : [row.operator, ...menu.operators];
2762
+ const operand = { family: menu.family, type, messages };
2763
+ function messageIdFor(text) {
2764
+ return parseFilterOperand(text, type) === void 0 ? invalidMessageId : void 0;
2765
+ }
2766
+ return /* @__PURE__ */ jsxs12(
2767
+ "div",
2768
+ {
2769
+ ref,
2770
+ role: "group",
2771
+ "aria-label": messages.filterConditionLabel(position),
2772
+ className: "filter-menu-condition",
2773
+ children: [
2774
+ /* @__PURE__ */ jsx17(
2775
+ "select",
2776
+ {
2777
+ ref: arity === 0 ? focusRef : void 0,
2778
+ className: "filter-menu-operator",
2779
+ "aria-label": messages.filterOperatorFieldLabel,
2780
+ value: row.operator,
2781
+ onChange: (event) => {
2782
+ const operator = operators[event.currentTarget.selectedIndex];
2783
+ if (operator !== void 0) {
2784
+ onChange({ ...row, operator });
2785
+ }
2786
+ },
2787
+ children: operators.map((operator) => /* @__PURE__ */ jsx17("option", { value: operator, children: messages.filterOperatorLabel(operator, menu.family) }, operator))
2788
+ }
2789
+ ),
2790
+ arity > 0 && /* @__PURE__ */ jsx17(
2791
+ OperandField,
2792
+ {
2793
+ ...operand,
2794
+ text: row.text,
2795
+ label: arity === 2 ? messages.filterFromLabel : messages.filterValueLabel,
2796
+ invalidMessageId: messageIdFor(row.text),
2797
+ fieldRef: focusRef,
2798
+ onChange: (text) => {
2799
+ onChange({ ...row, text });
2800
+ }
2801
+ }
2802
+ ),
2803
+ arity === 2 && /* @__PURE__ */ jsx17(
2804
+ OperandField,
2805
+ {
2806
+ ...operand,
2807
+ text: row.textTo,
2808
+ label: messages.filterToLabel,
2809
+ invalidMessageId: messageIdFor(row.textTo),
2810
+ fieldRef: void 0,
2811
+ onChange: (textTo) => {
2812
+ onChange({ ...row, textTo });
2813
+ }
2814
+ }
2815
+ )
2816
+ ]
2817
+ }
2818
+ );
2819
+ }
2820
+
2821
+ // src/DataGrid/filterMenu/SimpleFilterEditor.tsx
2822
+ import { jsx as jsx18, jsxs as jsxs13 } from "react/jsx-runtime";
2823
+ var COMBINATORS = ["and", "or"];
2824
+ var NO_ROWS = [];
2825
+ var NO_VALUES = {
2826
+ options: [],
2827
+ total: 0,
2828
+ truncated: false,
2829
+ blanks: 0
2830
+ };
2831
+ function holdsUnreadable(row) {
2832
+ return Array.from(row?.querySelectorAll("input") ?? []).some(
2833
+ (input) => input.validity.badInput
2834
+ );
2835
+ }
2836
+ function CombinatorChoice({
2837
+ name,
2838
+ combinator,
2839
+ messages,
2840
+ onChange
2841
+ }) {
2842
+ return /* @__PURE__ */ jsx18(
2843
+ "div",
2844
+ {
2845
+ role: "radiogroup",
2846
+ "aria-label": messages.filterCombinatorLabel,
2847
+ className: "filter-menu-combinator",
2848
+ children: COMBINATORS.map((choice) => /* @__PURE__ */ jsxs13("label", { className: "filter-menu-combinator-option", children: [
2849
+ /* @__PURE__ */ jsx18(
2850
+ "input",
2851
+ {
2852
+ type: "radio",
2853
+ name,
2854
+ checked: choice === combinator,
2855
+ onChange: () => {
2856
+ onChange(choice);
2857
+ }
2858
+ }
2859
+ ),
2860
+ choice === "and" ? messages.filterAnd : messages.filterOr
2861
+ ] }, choice))
2862
+ }
2863
+ );
2864
+ }
2865
+ function SimpleFilterEditor({
2866
+ column,
2867
+ menu,
2868
+ controller,
2869
+ messages,
2870
+ initialFocusRef,
2871
+ applyFilter,
2872
+ operator,
2873
+ ref
2874
+ }) {
2875
+ const type = column.column.type;
2876
+ const [opened] = useState8(() => {
2877
+ const shape = describeColumnFilter(controller.getState().filter, column.id);
2878
+ const rows = conditionRowsFrom(shape, menu.operators, type);
2879
+ const draft2 = {
2880
+ // Its typed values kept, as picking the operator in the row keeps them.
2881
+ rows: operator === void 0 ? rows : rows.map(
2882
+ (row, index) => index === 0 ? { ...row, operator } : row
2883
+ ),
2884
+ combinator: shape.kind === "conditions" ? shape.combinator : "and"
2885
+ };
2886
+ return {
2887
+ custom: shape.kind === "custom" || shape.kind === "values" && operator === void 0,
2888
+ draft: draft2
2889
+ };
2890
+ });
2891
+ const [draft, setDraft] = useState8(opened.draft);
2892
+ const [invalid, setInvalid] = useState8(NO_ROWS);
2893
+ const rowElements = useRef8([]);
2894
+ const invalidMessageId = useId();
2895
+ const combinatorName = useId();
2896
+ function update(next) {
2897
+ setDraft(next);
2898
+ setInvalid(NO_ROWS);
2899
+ }
2900
+ function applyConditions(conditions) {
2901
+ const applied = filterEditorReducer(
2902
+ initialFilterEditorDraft({ kind: "none" }, NO_VALUES),
2903
+ { type: "setConditions", conditions, combinator: draft.combinator }
2904
+ );
2905
+ applyFilter(
2906
+ draftToFilter(applied, column.id, controller.getState().filter)
2907
+ );
2908
+ }
2909
+ useImperativeHandle(ref, () => ({
2910
+ apply: () => {
2911
+ const parsed = conditionsFromRows(draft.rows, column.id, type);
2912
+ const unreadable = draft.rows.map(
2913
+ (_, index) => holdsUnreadable(rowElements.current[index])
2914
+ );
2915
+ const marked = draft.rows.flatMap(
2916
+ (_, index) => parsed.invalid.includes(index) || unreadable[index] === true ? [index] : []
2917
+ );
2918
+ if (marked.length === 0) {
2919
+ applyConditions(parsed.conditions);
2920
+ return true;
2921
+ }
2922
+ if (draft === opened.draft && !unreadable.includes(true)) {
2923
+ return true;
2924
+ }
2925
+ setInvalid(marked);
2926
+ return false;
2927
+ }
2928
+ }));
2929
+ useLayoutEffect7(() => {
2930
+ const [first] = invalid;
2931
+ if (first !== void 0) {
2932
+ rowElements.current[first]?.querySelector('[aria-invalid="true"]')?.focus();
2933
+ }
2934
+ }, [invalid]);
2935
+ return /* @__PURE__ */ jsxs13("div", { className: "filter-menu-conditions", children: [
2936
+ opened.custom && /* @__PURE__ */ jsx18("p", { className: "filter-menu-custom", children: messages.customFilter }),
2937
+ draft.rows.map((row, index) => (
2938
+ // A row is its place: the first condition, or the second.
2939
+ /* @__PURE__ */ jsxs13(Fragment5, { children: [
2940
+ index > 0 && /* @__PURE__ */ jsx18(
2941
+ CombinatorChoice,
2942
+ {
2943
+ name: combinatorName,
2944
+ combinator: draft.combinator,
2945
+ messages,
2946
+ onChange: (combinator) => {
2947
+ update({ ...draft, combinator });
2948
+ }
2949
+ }
2950
+ ),
2951
+ /* @__PURE__ */ jsx18(
2952
+ ConditionRow,
2953
+ {
2954
+ ref: (element) => {
2955
+ rowElements.current[index] = element;
2956
+ },
2957
+ row,
2958
+ position: index + 1,
2959
+ menu,
2960
+ type,
2961
+ messages,
2962
+ invalidMessageId: invalid.includes(index) ? invalidMessageId : void 0,
2963
+ focusRef: index === 0 ? initialFocusRef : void 0,
2964
+ onChange: (next) => {
2965
+ update({
2966
+ ...draft,
2967
+ rows: draft.rows.map(
2968
+ (current, at) => at === index ? next : current
2969
+ )
2970
+ });
2971
+ }
2972
+ }
2973
+ )
2974
+ ] }, index)
2975
+ )),
2976
+ invalid.length > 0 && /* @__PURE__ */ jsx18("p", { id: invalidMessageId, className: "filter-menu-invalid", children: messages.filterValueInvalid(menu.family) })
2977
+ ] });
2978
+ }
2979
+
2980
+ // src/DataGrid/filterMenu/ValueChecklist.tsx
2981
+ import { useLayoutEffect as useLayoutEffect8, useRef as useRef9, useState as useState9 } from "react";
2982
+ import {
2983
+ attachChecklist,
2984
+ createRowVirtualizer
2985
+ } from "@gridkitjs/dom";
2986
+ import { Fragment as Fragment6, jsx as jsx19, jsxs as jsxs14 } from "react/jsx-runtime";
2987
+ var ESTIMATED_ITEM_HEIGHT = 28;
2988
+ var OVERSCAN = 4;
2989
+ var NO_RANGE = {
2990
+ startIndex: 0,
2991
+ endIndex: -1,
2992
+ offsetBefore: 0,
2993
+ offsetAfter: 0
2994
+ };
2995
+ function ValueChecklist({
2996
+ items,
2997
+ showCounts,
2998
+ label,
2999
+ onToggle
3000
+ }) {
3001
+ const listRef = useRef9(null);
3002
+ const checklistRef = useRef9(null);
3003
+ const [virtualizer, setVirtualizer] = useState9(null);
3004
+ const [range, setRange] = useState9(NO_RANGE);
3005
+ const latest = useRef9({ items, onToggle });
3006
+ useLayoutEffect8(() => {
3007
+ latest.current = { items, onToggle };
3008
+ });
3009
+ useLayoutEffect8(() => {
3010
+ const list = listRef.current;
3011
+ if (list === null) {
3012
+ return void 0;
3013
+ }
3014
+ const rows = createRowVirtualizer(list, {
3015
+ estimatedRowHeight: ESTIMATED_ITEM_HEIGHT,
3016
+ overscan: OVERSCAN,
3017
+ onRange: setRange
3018
+ });
3019
+ const checklist = attachChecklist(list, {
3020
+ onToggle: (index) => {
3021
+ const item = latest.current.items[index];
3022
+ if (item !== void 0) {
3023
+ latest.current.onToggle(item);
3024
+ }
3025
+ },
3026
+ scrollToIndex: (index) => {
3027
+ rows.scrollToIndex(index, { block: "nearest" });
3028
+ }
3029
+ });
3030
+ checklistRef.current = checklist;
3031
+ setVirtualizer(rows);
3032
+ return () => {
3033
+ checklistRef.current = null;
3034
+ checklist.detach();
3035
+ rows.destroy();
3036
+ };
3037
+ }, []);
3038
+ useLayoutEffect8(() => {
3039
+ virtualizer?.setRowCount(items.length);
3040
+ checklistRef.current?.setCount(items.length);
3041
+ }, [virtualizer, items.length]);
3042
+ const shown = items.slice(range.startIndex, range.endIndex + 1);
3043
+ return /* @__PURE__ */ jsxs14(
3044
+ "div",
3045
+ {
3046
+ ref: listRef,
3047
+ role: "listbox",
3048
+ "aria-multiselectable": "true",
3049
+ "aria-label": label,
3050
+ className: "filter-menu-checklist",
3051
+ children: [
3052
+ /* @__PURE__ */ jsx19("div", { role: "presentation", style: { height: range.offsetBefore } }),
3053
+ shown.map((item, offset) => {
3054
+ const index = range.startIndex + offset;
3055
+ return /* @__PURE__ */ jsxs14(
3056
+ "div",
3057
+ {
3058
+ ref: virtualizer?.measureRow(index),
3059
+ role: "option",
3060
+ "data-gridkit-checklist-item": "",
3061
+ "data-gridkit-checklist-index": index,
3062
+ "aria-checked": item.checked,
3063
+ "aria-setsize": items.length,
3064
+ "aria-posinset": index + 1,
3065
+ className: "filter-menu-option",
3066
+ children: [
3067
+ /* @__PURE__ */ jsx19("span", { className: "filter-menu-option-check", "aria-hidden": "true", children: /* @__PURE__ */ jsx19(CheckIcon, {}) }),
3068
+ /* @__PURE__ */ jsx19("span", { className: "filter-menu-option-label", children: item.label }),
3069
+ showCounts && /* @__PURE__ */ jsxs14(Fragment6, { children: [
3070
+ " ",
3071
+ /* @__PURE__ */ jsx19("span", { className: "filter-menu-option-count", children: String(item.count) })
3072
+ ] })
3073
+ ]
3074
+ },
3075
+ item.key
3076
+ );
3077
+ }),
3078
+ /* @__PURE__ */ jsx19("div", { role: "presentation", style: { height: range.offsetAfter } })
3079
+ ]
3080
+ }
3081
+ );
3082
+ }
3083
+
3084
+ // src/DataGrid/filterMenu/ExcelFilterEditor.tsx
3085
+ import { Fragment as Fragment7, jsx as jsx20, jsxs as jsxs15 } from "react/jsx-runtime";
3086
+ function toggleOf(item) {
3087
+ return item.kind === "blanks" ? { type: "setIncludeBlanks", includeBlanks: !item.checked } : { type: "toggleValue", value: item.value };
3088
+ }
3089
+ function ExcelFilterEditor({
3090
+ column,
3091
+ menu,
3092
+ controller,
3093
+ messages,
3094
+ initialFocusRef,
3095
+ applyFilter,
3096
+ columnValues,
3097
+ ref
3098
+ }) {
3099
+ const [opened] = useState10(() => {
3100
+ const shape2 = describeColumnFilter2(controller.getState().filter, column.id);
3101
+ return {
3102
+ shape: shape2,
3103
+ draft: initialFilterEditorDraft2(shape2, columnValues(column.id))
3104
+ };
3105
+ });
3106
+ const [draft, dispatch] = useReducer(filterEditorReducer2, opened.draft);
3107
+ const [operator, setOperator] = useState10(null);
3108
+ const conditions = useRef10(null);
3109
+ const conditionsFocus = useRef10(null);
3110
+ const operatorButton = useRef10(null);
3111
+ const switched = useRef10(false);
3112
+ const selectAllRef = useRef10(null);
3113
+ const selectAll = useMemo4(() => selectAllState(draft), [draft]);
3114
+ const checklist = useMemo4(
3115
+ () => filterChecklist(draft, {
3116
+ valueLimit: menu.valueLimit,
3117
+ blanksLabel: messages.filterBlanks
3118
+ }),
3119
+ [draft, menu.valueLimit, messages.filterBlanks]
3120
+ );
3121
+ useLayoutEffect9(() => {
3122
+ if (selectAllRef.current !== null) {
3123
+ selectAllRef.current.indeterminate = selectAll === "mixed";
3124
+ }
3125
+ });
3126
+ useLayoutEffect9(() => {
3127
+ if (switched.current) {
3128
+ switched.current = false;
3129
+ (operator === null ? operatorButton.current : conditionsFocus.current)?.focus();
3130
+ }
3131
+ }, [operator]);
3132
+ function switchTo(chosen) {
3133
+ switched.current = true;
3134
+ setOperator(chosen);
3135
+ }
3136
+ useImperativeHandle2(ref, () => ({
3137
+ apply: () => {
3138
+ if (operator !== null) {
3139
+ return conditions.current?.apply() ?? true;
3140
+ }
3141
+ const picked = draft.checked !== opened.draft.checked || draft.includeBlanks !== opened.draft.includeBlanks;
3142
+ if (picked) {
3143
+ applyFilter(
3144
+ draftToFilter2(draft, column.id, controller.getState().filter)
3145
+ );
3146
+ }
3147
+ return true;
3148
+ }
3149
+ }));
3150
+ if (operator !== null) {
3151
+ return /* @__PURE__ */ jsxs15(Fragment7, { children: [
3152
+ /* @__PURE__ */ jsxs15(
3153
+ "button",
3154
+ {
3155
+ type: "button",
3156
+ className: "filter-menu-back",
3157
+ onClick: () => {
3158
+ switchTo(null);
3159
+ },
3160
+ children: [
3161
+ /* @__PURE__ */ jsx20("span", { className: "filter-menu-back-icon", "aria-hidden": "true", children: /* @__PURE__ */ jsx20(ChevronLeftIcon2, {}) }),
3162
+ messages.filterBackToValues
3163
+ ]
3164
+ }
3165
+ ),
3166
+ /* @__PURE__ */ jsx20(
3167
+ SimpleFilterEditor,
3168
+ {
3169
+ ref: conditions,
3170
+ column,
3171
+ menu,
3172
+ controller,
3173
+ messages,
3174
+ initialFocusRef: (element) => {
3175
+ conditionsFocus.current = element;
3176
+ },
3177
+ applyFilter,
3178
+ operator
3179
+ }
3180
+ )
3181
+ ] });
3182
+ }
3183
+ const { shape } = opened;
3184
+ return /* @__PURE__ */ jsxs15("div", { className: "filter-menu-values", children: [
3185
+ menu.operators.length > 0 && /* @__PURE__ */ jsx20(
3186
+ OperatorMenuButton,
3187
+ {
3188
+ ref: operatorButton,
3189
+ menu,
3190
+ active: shape.kind === "conditions" ? shape.conditions.map((condition) => condition.operator) : void 0,
3191
+ messages,
3192
+ onChoose: switchTo
3193
+ }
3194
+ ),
3195
+ (shape.kind === "conditions" || shape.kind === "custom") && /* @__PURE__ */ jsx20("p", { className: "filter-menu-custom", children: shape.kind === "conditions" ? messages.filterByCondition : messages.customFilter }),
3196
+ /* @__PURE__ */ jsx20(
3197
+ "input",
3198
+ {
3199
+ ref: initialFocusRef,
3200
+ type: "search",
3201
+ className: "filter-menu-search",
3202
+ "aria-label": messages.filterSearchLabel,
3203
+ placeholder: messages.searchPlaceholder,
3204
+ value: draft.search,
3205
+ onChange: (event) => {
3206
+ dispatch({ type: "search", search: event.currentTarget.value });
3207
+ }
3208
+ }
3209
+ ),
3210
+ /* @__PURE__ */ jsxs15("label", { className: "filter-menu-select-all", children: [
3211
+ /* @__PURE__ */ jsx20(
3212
+ "input",
3213
+ {
3214
+ ref: selectAllRef,
3215
+ type: "checkbox",
3216
+ checked: selectAll === "checked",
3217
+ disabled: !checklist.needsSearch && checklist.items.length === 0,
3218
+ onChange: () => {
3219
+ dispatch({ type: "toggleAll" });
3220
+ }
3221
+ }
3222
+ ),
3223
+ messages.filterSelectAll
3224
+ ] }),
3225
+ checklist.needsSearch ? /* @__PURE__ */ jsx20("p", { className: "filter-menu-search-first", children: messages.filterSearchFirst(checklist.total) }) : /* @__PURE__ */ jsx20(
3226
+ ValueChecklist,
3227
+ {
3228
+ items: checklist.items,
3229
+ showCounts: menu.showCounts,
3230
+ label: messages.filterValuesLabel,
3231
+ onToggle: (item) => {
3232
+ dispatch(toggleOf(item));
3233
+ }
3234
+ }
3235
+ ),
3236
+ checklist.hiddenCount > 0 && /* @__PURE__ */ jsx20("p", { role: "status", className: "filter-menu-more", children: messages.filterMoreValues(checklist.hiddenCount) })
3237
+ ] });
3238
+ }
3239
+
3240
+ // src/DataGrid/filterMenu/FilterMenu.tsx
3241
+ import { jsx as jsx21, jsxs as jsxs16 } from "react/jsx-runtime";
3242
+ var SORT_COMMANDS = [
3243
+ "sort.ascending",
3244
+ "sort.descending",
3245
+ "sort.clear"
3246
+ ];
3247
+ function isItem(entry) {
3248
+ return entry.type === "item";
3249
+ }
3250
+ function FilterMenu({
3251
+ target,
3252
+ controller,
3253
+ commands,
3254
+ messages,
3255
+ close,
3256
+ initialFocusRef,
3257
+ applyFilter,
3258
+ columnValues
3259
+ }) {
3260
+ const editor = useRef11(null);
3261
+ const column = target.area === "header" ? controller.getView().dataColumns.find((entry) => entry.id === target.columnId) : void 0;
3262
+ const menu = column === void 0 ? null : resolveColumnFilterMenu2(controller.getOptions(), column.column);
3263
+ useEffect3(() => {
3264
+ if (menu === null) {
3265
+ close();
3266
+ }
3267
+ });
3268
+ if (column === void 0 || menu === null) {
3269
+ return null;
3270
+ }
3271
+ const resolveOptions = {
3272
+ commands: commands.commands,
3273
+ context: commands.contextFor(target)
3274
+ };
3275
+ const sortItems = resolveMenu2(SORT_COMMANDS, resolveOptions).filter(isItem);
3276
+ const [clearItem] = resolveMenu2(["filter.clear"], resolveOptions).filter(
3277
+ isItem
3278
+ );
3279
+ function run(item) {
3280
+ commands.run(item.id, target, "filterMenu");
3281
+ close();
3282
+ }
3283
+ const editorProps = {
3284
+ ref: editor,
3285
+ column,
3286
+ menu,
3287
+ controller,
3288
+ messages,
3289
+ initialFocusRef,
3290
+ applyFilter
3291
+ };
3292
+ function apply(event) {
3293
+ event.preventDefault();
3294
+ if (editor.current?.apply() ?? true) {
3295
+ close();
3296
+ }
3297
+ }
3298
+ return /* @__PURE__ */ jsx21(
3299
+ "div",
3300
+ {
3301
+ role: "dialog",
3302
+ "aria-label": messages.filterMenuLabel(
3303
+ resolveColumnHeaderText(column.column)
3304
+ ),
3305
+ className: "gridkit-filter-menu",
3306
+ children: /* @__PURE__ */ jsxs16(
3307
+ "form",
3308
+ {
3309
+ className: "filter-menu-form",
3310
+ noValidate: true,
3311
+ onSubmit: apply,
3312
+ onKeyDown: (event) => {
3313
+ if (event.key === "Enter" && event.target instanceof Element && event.target.closest("select, [role=listbox]") !== null) {
3314
+ event.preventDefault();
3315
+ event.currentTarget.requestSubmit();
3316
+ }
3317
+ },
3318
+ children: [
3319
+ sortItems.length > 0 && /* @__PURE__ */ jsx21("div", { className: "filter-menu-sort", children: sortItems.map((item) => /* @__PURE__ */ jsxs16(
3320
+ "button",
3321
+ {
3322
+ type: "button",
3323
+ className: "filter-menu-sort-item",
3324
+ disabled: item.disabled,
3325
+ ...ariaAttr(
3326
+ item.checked !== void 0,
3327
+ "aria-pressed",
3328
+ item.checked
3329
+ ),
3330
+ onClick: () => {
3331
+ run(item);
3332
+ },
3333
+ children: [
3334
+ /* @__PURE__ */ jsx21("span", { className: "filter-menu-sort-icon", "aria-hidden": "true", children: /* @__PURE__ */ jsx21(MenuIcon, { name: item.icon }) }),
3335
+ item.label
3336
+ ]
3337
+ },
3338
+ item.id
3339
+ )) }),
3340
+ menu.ui === "excel" ? /* @__PURE__ */ jsx21(ExcelFilterEditor, { ...editorProps, columnValues }) : /* @__PURE__ */ jsx21(SimpleFilterEditor, { ...editorProps }),
3341
+ /* @__PURE__ */ jsxs16("div", { className: "filter-menu-actions", children: [
3342
+ clearItem !== void 0 && /* @__PURE__ */ jsx21(
3343
+ "button",
3344
+ {
3345
+ type: "button",
3346
+ className: "filter-menu-clear",
3347
+ disabled: clearItem.disabled,
3348
+ onClick: () => {
3349
+ run(clearItem);
3350
+ },
3351
+ children: clearItem.label
3352
+ }
3353
+ ),
3354
+ /* @__PURE__ */ jsx21("button", { type: "button", className: "filter-menu-cancel", onClick: close, children: messages.cancelFilter }),
3355
+ /* @__PURE__ */ jsx21("button", { type: "submit", className: "filter-menu-apply", children: messages.applyFilter })
3356
+ ] })
3357
+ ]
3358
+ }
3359
+ )
3360
+ }
3361
+ );
3362
+ }
3363
+
3364
+ // src/DataGrid/filterMenu/useApplyFilter.ts
3365
+ import { useEffect as useEffect4, useRef as useRef12 } from "react";
3366
+ function useApplyFilter(controller, messages, announce) {
3367
+ const unannounced = useRef12(null);
3368
+ useEffect4(() => {
3369
+ if (unannounced.current === controller.getState().filter) {
3370
+ unannounced.current = null;
3371
+ announce(
3372
+ messages.filterResults(
3373
+ controller.getView().rows.length,
3374
+ controller.getOptions().dataSource?.length ?? 0
3375
+ )
3376
+ );
3377
+ }
3378
+ });
3379
+ return (filter) => {
3380
+ unannounced.current = filter;
3381
+ controller.setFilter(filter);
3382
+ };
3383
+ }
3384
+
3385
+ // src/DataGrid/filterMenu/useFilterValueCache.ts
3386
+ import { useState as useState11 } from "react";
3387
+ import {
3388
+ createColumnValueOptionsCache
3389
+ } from "@gridkitjs/core";
3390
+ var NO_ROWS2 = [];
3391
+ function useFilterValueCache(controller, messages) {
3392
+ const [cache] = useState11(createColumnValueOptionsCache);
3393
+ return (columnId) => {
3394
+ const { dataSource = NO_ROWS2, locale } = controller.getOptions();
3395
+ return cache.get(
3396
+ dataSource,
3397
+ controller.getView().dataColumns,
3398
+ controller.getState().filter,
3399
+ columnId,
3400
+ locale === void 0 ? { messages } : { locale, messages }
3401
+ );
3402
+ };
3403
+ }
3404
+
3405
+ // src/DataGrid/panels/GridPanels.tsx
3406
+ import { jsx as jsx22 } from "react/jsx-runtime";
3407
+ var panelKinds = {
3408
+ columnChooser: { component: ColumnChooser, trapFocus: false },
3409
+ filter: { component: FilterMenu, trapFocus: true }
3410
+ };
3411
+ function GridPanels({
3412
+ panels,
3413
+ controller,
3414
+ commands,
3415
+ messages
3416
+ }) {
3417
+ const initialFocus = useRef13(null);
3418
+ const applyFilter = useApplyFilter(
3419
+ controller,
3420
+ messages,
3421
+ commands.host.announce
3422
+ );
3423
+ const columnValues = useFilterValueCache(controller, messages);
3424
+ const { panel, close } = panels;
3425
+ if (panel === null) {
3426
+ return null;
3427
+ }
3428
+ const { component: Panel, trapFocus } = panelKinds[panel.kind];
3429
+ return /* @__PURE__ */ jsx22(
3430
+ Overlay,
3431
+ {
3432
+ anchor: panel.anchor,
3433
+ placement: panel.placement,
3434
+ gap: panel.anchor instanceof Element ? ANCHOR_GAP : 0,
3435
+ initialFocus,
3436
+ returnFocusTo: panel.returnFocusTo,
3437
+ ignoreScrollAfterInput: true,
3438
+ trapFocus,
3439
+ themeClassName: panel.themeClassName,
3440
+ onDismiss: close,
3441
+ children: /* @__PURE__ */ jsx22(
3442
+ Panel,
3443
+ {
3444
+ target: panel.target,
3445
+ controller,
3446
+ commands,
3447
+ messages,
3448
+ close,
3449
+ initialFocusRef: (element) => {
3450
+ initialFocus.current = element;
3451
+ },
3452
+ applyFilter,
3453
+ columnValues
3454
+ }
3455
+ )
3456
+ },
3457
+ panel.key
3458
+ );
3459
+ }
3460
+
3461
+ // src/DataGrid/panels/panelApi.ts
3462
+ import { resolveColumnFilterMenu as resolveColumnFilterMenu3 } from "@gridkitjs/core";
3463
+ function panelApi(panels, controller) {
3464
+ return {
3465
+ openColumnChooser: () => {
3466
+ panels.open("columnChooser", { area: "grid" });
3467
+ },
3468
+ openFilterMenu: (columnId) => {
3469
+ const shown = controller.getView().columns.find((entry) => entry.id === columnId);
3470
+ if (shown === void 0 || resolveColumnFilterMenu3(controller.getOptions(), shown.column) === null) {
3471
+ return false;
3472
+ }
3473
+ panels.open("filter", { area: "header", columnId });
3474
+ return true;
3475
+ },
3476
+ closeFilterMenu: () => {
3477
+ if (panels.panel?.kind === "filter") {
3478
+ panels.close();
3479
+ }
3480
+ }
3481
+ };
3482
+ }
3483
+
3484
+ // src/DataGrid/panels/useGridPanels.ts
3485
+ import { useLayoutEffect as useLayoutEffect10, useRef as useRef14, useState as useState12 } from "react";
3486
+ function toolbarItem(root, itemId) {
3487
+ const items = root.querySelectorAll(
3488
+ ":scope > .gridkit-toolbar > [data-gridkit-toolbar-item]"
3489
+ );
3490
+ return Array.from(items).find(
3491
+ (item) => item.getAttribute("data-gridkit-toolbar-item") === itemId
3492
+ ) ?? // In "More" while the toolbar is narrow: the button it is chosen from.
3493
+ Array.from(items).find(
3494
+ (item) => item.hasAttribute("data-gridkit-toolbar-more")
3495
+ ) ?? null;
3496
+ }
3497
+ function headerCell2(headerTable, columnId) {
3498
+ const cells = headerTable?.tHead?.rows[0]?.cells;
3499
+ return cells === void 0 ? null : Array.from(cells).find(
3500
+ (cell) => cell.getAttribute("data-gridkit-column") === columnId
3501
+ ) ?? null;
3502
+ }
3503
+ function useGridPanels({
3504
+ rootRef,
3505
+ headerTableRef,
3506
+ nav
3507
+ }) {
3508
+ const [panel, setPanel] = useState12(null);
3509
+ const openings = useRef14(0);
3510
+ function placeFor(root, target) {
3511
+ const element = target.area === "toolbar" ? toolbarItem(root, target.itemId) : target.area === "header" ? headerCell2(headerTableRef.current, target.columnId) : null;
3512
+ if (element !== null) {
3513
+ return {
3514
+ anchor: element,
3515
+ // The toolbar's own button sits at its far end.
3516
+ placement: target.area === "toolbar" ? "bottom-end" : "bottom-start",
3517
+ returnFocusTo: element
3518
+ };
3519
+ }
3520
+ const { right, top } = root.getBoundingClientRect();
3521
+ return {
3522
+ anchor: { x: right, y: top },
3523
+ placement: "bottom-end",
3524
+ returnFocusTo: void 0
3525
+ };
3526
+ }
3527
+ const shown = useRef14(null);
3528
+ useLayoutEffect10(() => {
3529
+ const closed = shown.current;
3530
+ shown.current = panel;
3531
+ if (panel === null && closed?.returnFocusTo?.isConnected === false && document.activeElement === document.body) {
3532
+ nav.focusTabStop();
3533
+ }
3534
+ });
2345
3535
  return {
2346
3536
  panel,
2347
3537
  open: (kind, target) => {
@@ -2365,7 +3555,7 @@ function useGridPanels({
2365
3555
  }
2366
3556
 
2367
3557
  // src/DataGrid/toolbar/GridToolbar.tsx
2368
- import { useLayoutEffect as useLayoutEffect9, useRef as useRef10 } from "react";
3558
+ import { useLayoutEffect as useLayoutEffect13, useRef as useRef18 } from "react";
2369
3559
  import {
2370
3560
  resolveToolbar,
2371
3561
  splitToolbarItems,
@@ -2374,8 +3564,8 @@ import {
2374
3564
  import { attachToolbar } from "@gridkitjs/dom";
2375
3565
 
2376
3566
  // src/DataGrid/toolbar/ToolbarMenuButton.tsx
2377
- import { useLayoutEffect as useLayoutEffect7, useRef as useRef7, useState as useState8 } from "react";
2378
- import { Fragment as Fragment4, jsx as jsx17, jsxs as jsxs11 } from "react/jsx-runtime";
3567
+ import { useLayoutEffect as useLayoutEffect11, useRef as useRef15, useState as useState13 } from "react";
3568
+ import { Fragment as Fragment8, jsx as jsx23, jsxs as jsxs17 } from "react/jsx-runtime";
2379
3569
  function ToolbarMenuButton({
2380
3570
  itemKey,
2381
3571
  label,
@@ -2384,9 +3574,9 @@ function ToolbarMenuButton({
2384
3574
  more = false,
2385
3575
  onMenuClose
2386
3576
  }) {
2387
- const [menu, setMenu] = useState8(null);
2388
- const menuBehaviourRef = useRef7(null);
2389
- useLayoutEffect7(() => {
3577
+ const [menu, setMenu] = useState13(null);
3578
+ const menuBehaviourRef = useRef15(null);
3579
+ useLayoutEffect11(() => {
2390
3580
  if (menu !== null && menu.focusItem > 0) {
2391
3581
  menuBehaviourRef.current?.focusItem(menu.focusItem);
2392
3582
  }
@@ -2402,8 +3592,8 @@ function ToolbarMenuButton({
2402
3592
  setMenu(null);
2403
3593
  onMenuClose();
2404
3594
  }
2405
- return /* @__PURE__ */ jsxs11(Fragment4, { children: [
2406
- /* @__PURE__ */ jsxs11(
3595
+ return /* @__PURE__ */ jsxs17(Fragment8, { children: [
3596
+ /* @__PURE__ */ jsxs17(
2407
3597
  "button",
2408
3598
  {
2409
3599
  type: "button",
@@ -2426,13 +3616,13 @@ function ToolbarMenuButton({
2426
3616
  }
2427
3617
  },
2428
3618
  children: [
2429
- icon !== void 0 && /* @__PURE__ */ jsx17("span", { className: "toolbar-button-icon", "aria-hidden": "true", children: /* @__PURE__ */ jsx17(MenuIcon, { name: icon }) }),
3619
+ icon !== void 0 && /* @__PURE__ */ jsx23("span", { className: "toolbar-button-icon", "aria-hidden": "true", children: /* @__PURE__ */ jsx23(MenuIcon, { name: icon }) }),
2430
3620
  label,
2431
- /* @__PURE__ */ jsx17("span", { className: "toolbar-button-indicator", "aria-hidden": "true", children: /* @__PURE__ */ jsx17(ChevronDownIcon, {}) })
3621
+ /* @__PURE__ */ jsx23("span", { className: "toolbar-button-indicator", "aria-hidden": "true", children: /* @__PURE__ */ jsx23(ChevronDownIcon, {}) })
2432
3622
  ]
2433
3623
  }
2434
3624
  ),
2435
- menu !== null && /* @__PURE__ */ jsx17(
3625
+ menu !== null && /* @__PURE__ */ jsx23(
2436
3626
  Overlay,
2437
3627
  {
2438
3628
  anchor: menu.anchor,
@@ -2441,7 +3631,7 @@ function ToolbarMenuButton({
2441
3631
  returnFocusTo: menu.anchor,
2442
3632
  themeClassName: menu.themeClassName,
2443
3633
  onDismiss: close,
2444
- children: /* @__PURE__ */ jsx17(
3634
+ children: /* @__PURE__ */ jsx23(
2445
3635
  GridMenu,
2446
3636
  {
2447
3637
  entries,
@@ -2461,12 +3651,12 @@ function ToolbarMenuButton({
2461
3651
  }
2462
3652
 
2463
3653
  // src/DataGrid/toolbar/ToolbarSearch.tsx
2464
- import { useEffect as useEffect3, useRef as useRef8, useState as useState9 } from "react";
3654
+ import { useEffect as useEffect5, useRef as useRef16, useState as useState14 } from "react";
2465
3655
  import {
2466
3656
  searchTextOf,
2467
3657
  setSearchText
2468
3658
  } from "@gridkitjs/core";
2469
- import { jsx as jsx18 } from "react/jsx-runtime";
3659
+ import { jsx as jsx24 } from "react/jsx-runtime";
2470
3660
  var SEARCH_DELAY_MS = 250;
2471
3661
  function ToolbarSearch({
2472
3662
  itemKey,
@@ -2475,9 +3665,9 @@ function ToolbarSearch({
2475
3665
  announce
2476
3666
  }) {
2477
3667
  const applied = searchTextOf(controller.getState().filter);
2478
- const [draft, setDraft] = useState9(null);
2479
- const timer = useRef8(void 0);
2480
- const unannounced = useRef8(null);
3668
+ const [draft, setDraft] = useState14(null);
3669
+ const timer = useRef16(void 0);
3670
+ const unannounced = useRef16(null);
2481
3671
  const text = draft !== null && draft.over === applied ? draft.text : applied;
2482
3672
  function apply(next, over) {
2483
3673
  clearTimeout(timer.current);
@@ -2492,19 +3682,19 @@ function ToolbarSearch({
2492
3682
  controller.setFilter(searched);
2493
3683
  }
2494
3684
  }
2495
- useEffect3(() => {
3685
+ useEffect5(() => {
2496
3686
  if (unannounced.current === applied) {
2497
3687
  unannounced.current = null;
2498
3688
  announce(messages.searchResults(controller.getView().rows.length));
2499
3689
  }
2500
3690
  });
2501
- useEffect3(
3691
+ useEffect5(
2502
3692
  () => () => {
2503
3693
  clearTimeout(timer.current);
2504
3694
  },
2505
3695
  []
2506
3696
  );
2507
- return /* @__PURE__ */ jsx18(
3697
+ return /* @__PURE__ */ jsx24(
2508
3698
  "input",
2509
3699
  {
2510
3700
  type: "search",
@@ -2534,12 +3724,12 @@ function ToolbarSearch({
2534
3724
  }
2535
3725
 
2536
3726
  // src/DataGrid/toolbar/useToolbarOverflow.ts
2537
- import { useLayoutEffect as useLayoutEffect8, useRef as useRef9, useState as useState10 } from "react";
3727
+ import { useLayoutEffect as useLayoutEffect12, useRef as useRef17, useState as useState15 } from "react";
2538
3728
  import { observeToolbarOverflow } from "@gridkitjs/dom";
2539
3729
  function useToolbarOverflow(toolbarRef, items) {
2540
- const [fit, setFit] = useState10(items.length);
2541
- const overflow = useRef9(null);
2542
- const measuredLayout = useRef9("");
3730
+ const [fit, setFit] = useState15(items.length);
3731
+ const overflow = useRef17(null);
3732
+ const measuredLayout = useRef17("");
2543
3733
  const layout = JSON.stringify(
2544
3734
  items.map((item) => [
2545
3735
  item.type,
@@ -2547,7 +3737,7 @@ function useToolbarOverflow(toolbarRef, items) {
2547
3737
  "label" in item ? item.label : null
2548
3738
  ])
2549
3739
  );
2550
- useLayoutEffect8(() => {
3740
+ useLayoutEffect12(() => {
2551
3741
  const toolbar = toolbarRef.current;
2552
3742
  if (toolbar === null || layout === measuredLayout.current) {
2553
3743
  return;
@@ -2560,7 +3750,7 @@ function useToolbarOverflow(toolbarRef, items) {
2560
3750
  overflow.current.update(options);
2561
3751
  }
2562
3752
  }, [toolbarRef, layout, items]);
2563
- useLayoutEffect8(
3753
+ useLayoutEffect12(
2564
3754
  () => () => {
2565
3755
  overflow.current?.detach();
2566
3756
  overflow.current = null;
@@ -2572,7 +3762,7 @@ function useToolbarOverflow(toolbarRef, items) {
2572
3762
  }
2573
3763
 
2574
3764
  // src/DataGrid/toolbar/GridToolbar.tsx
2575
- import { jsx as jsx19, jsxs as jsxs12 } from "react/jsx-runtime";
3765
+ import { jsx as jsx25, jsxs as jsxs18 } from "react/jsx-runtime";
2576
3766
  function reporting(run, report) {
2577
3767
  return () => {
2578
3768
  const ran = run();
@@ -2625,7 +3815,7 @@ function GridToolbar(props) {
2625
3815
  return null;
2626
3816
  }
2627
3817
  const label = (typeof props.toolbar === "object" ? props.toolbar.label : void 0) ?? props.messages.toolbarLabel;
2628
- return /* @__PURE__ */ jsx19(Toolbar, { ...props, entries, label });
3818
+ return /* @__PURE__ */ jsx25(Toolbar, { ...props, entries, label });
2629
3819
  }
2630
3820
  function Toolbar({
2631
3821
  entries,
@@ -2637,8 +3827,8 @@ function Toolbar({
2637
3827
  messages,
2638
3828
  announce
2639
3829
  }) {
2640
- const toolbarRef = useRef10(null);
2641
- const menuClosed = useRef10(false);
3830
+ const toolbarRef = useRef18(null);
3831
+ const menuClosed = useRef18(false);
2642
3832
  const items = resolveToolbar(entries, {
2643
3833
  commands: commands.commands,
2644
3834
  contextFor: commands.contextFor
@@ -2649,11 +3839,11 @@ function Toolbar({
2649
3839
  );
2650
3840
  const fit = useToolbarOverflow(toolbarRef, items);
2651
3841
  const { shown, overflow } = splitToolbarItems(items, fit);
2652
- useLayoutEffect9(() => {
3842
+ useLayoutEffect13(() => {
2653
3843
  const toolbar = toolbarRef.current;
2654
3844
  return toolbar === null ? void 0 : attachToolbar(toolbar);
2655
3845
  }, []);
2656
- useLayoutEffect9(() => {
3846
+ useLayoutEffect13(() => {
2657
3847
  if (!menuClosed.current) {
2658
3848
  return;
2659
3849
  }
@@ -2669,7 +3859,7 @@ function Toolbar({
2669
3859
  function draw(item) {
2670
3860
  switch (item.type) {
2671
3861
  case "button":
2672
- return /* @__PURE__ */ jsxs12(
3862
+ return /* @__PURE__ */ jsxs18(
2673
3863
  "button",
2674
3864
  {
2675
3865
  type: "button",
@@ -2690,14 +3880,14 @@ function Toolbar({
2690
3880
  item.run();
2691
3881
  },
2692
3882
  children: [
2693
- item.icon !== void 0 && /* @__PURE__ */ jsx19("span", { className: "toolbar-button-icon", "aria-hidden": "true", children: /* @__PURE__ */ jsx19(MenuIcon, { name: item.icon }) }),
3883
+ item.icon !== void 0 && /* @__PURE__ */ jsx25("span", { className: "toolbar-button-icon", "aria-hidden": "true", children: /* @__PURE__ */ jsx25(MenuIcon, { name: item.icon }) }),
2694
3884
  item.label
2695
3885
  ]
2696
3886
  },
2697
3887
  item.key
2698
3888
  );
2699
3889
  case "menu":
2700
- return /* @__PURE__ */ jsx19(
3890
+ return /* @__PURE__ */ jsx25(
2701
3891
  ToolbarMenuButton,
2702
3892
  {
2703
3893
  itemKey: item.key,
@@ -2709,7 +3899,7 @@ function Toolbar({
2709
3899
  item.key
2710
3900
  );
2711
3901
  case "search":
2712
- return /* @__PURE__ */ jsx19(
3902
+ return /* @__PURE__ */ jsx25(
2713
3903
  ToolbarSearch,
2714
3904
  {
2715
3905
  itemKey: item.key,
@@ -2720,7 +3910,7 @@ function Toolbar({
2720
3910
  item.key
2721
3911
  );
2722
3912
  case "separator":
2723
- return /* @__PURE__ */ jsx19(
3913
+ return /* @__PURE__ */ jsx25(
2724
3914
  "div",
2725
3915
  {
2726
3916
  role: "separator",
@@ -2731,7 +3921,7 @@ function Toolbar({
2731
3921
  item.key
2732
3922
  );
2733
3923
  case "spacer":
2734
- return /* @__PURE__ */ jsx19(
3924
+ return /* @__PURE__ */ jsx25(
2735
3925
  "div",
2736
3926
  {
2737
3927
  className: "toolbar-spacer",
@@ -2740,7 +3930,7 @@ function Toolbar({
2740
3930
  item.key
2741
3931
  );
2742
3932
  case "custom":
2743
- return /* @__PURE__ */ jsx19(
3933
+ return /* @__PURE__ */ jsx25(
2744
3934
  "div",
2745
3935
  {
2746
3936
  className: "toolbar-custom",
@@ -2751,7 +3941,7 @@ function Toolbar({
2751
3941
  );
2752
3942
  }
2753
3943
  }
2754
- return /* @__PURE__ */ jsxs12(
3944
+ return /* @__PURE__ */ jsxs18(
2755
3945
  "div",
2756
3946
  {
2757
3947
  ref: toolbarRef,
@@ -2761,7 +3951,7 @@ function Toolbar({
2761
3951
  className: "gridkit-toolbar",
2762
3952
  children: [
2763
3953
  shown.map(draw),
2764
- overflow.length > 0 && /* @__PURE__ */ jsx19(
3954
+ overflow.length > 0 && /* @__PURE__ */ jsx25(
2765
3955
  ToolbarMenuButton,
2766
3956
  {
2767
3957
  itemKey: "more",
@@ -2817,7 +4007,7 @@ function useColumnDrag({
2817
4007
 
2818
4008
  // src/DataGrid/useColumnResize.ts
2819
4009
  import {
2820
- useState as useState11
4010
+ useState as useState16
2821
4011
  } from "react";
2822
4012
  import {
2823
4013
  beginColumnResize,
@@ -2831,7 +4021,7 @@ function useColumnResize({
2831
4021
  controller,
2832
4022
  columnSizeDefaults
2833
4023
  }) {
2834
- const [activeColumnId, setActiveColumnId] = useState11(null);
4024
+ const [activeColumnId, setActiveColumnId] = useState16(null);
2835
4025
  function startResize(entry, event) {
2836
4026
  event.stopPropagation();
2837
4027
  const session = beginColumnResize(
@@ -2909,11 +4099,11 @@ function useColumnResize({
2909
4099
  }
2910
4100
 
2911
4101
  // src/DataGrid/useElementWidth.ts
2912
- import { useLayoutEffect as useLayoutEffect10, useState as useState12 } from "react";
4102
+ import { useLayoutEffect as useLayoutEffect14, useState as useState17 } from "react";
2913
4103
  import { observeElementWidth } from "@gridkitjs/dom";
2914
4104
  function useElementWidth(ref, enabled) {
2915
- const [width, setWidth] = useState12(null);
2916
- useLayoutEffect10(() => {
4105
+ const [width, setWidth] = useState17(null);
4106
+ useLayoutEffect14(() => {
2917
4107
  const element = ref.current;
2918
4108
  if (element === null || !enabled) {
2919
4109
  return void 0;
@@ -2924,29 +4114,33 @@ function useElementWidth(ref, enabled) {
2924
4114
  }
2925
4115
 
2926
4116
  // src/DataGrid/useGridController.ts
2927
- import { useState as useState13, useSyncExternalStore as useSyncExternalStore2 } from "react";
4117
+ import { useState as useState18, useSyncExternalStore as useSyncExternalStore2 } from "react";
2928
4118
  import {
2929
4119
  createGridController
2930
4120
  } from "@gridkitjs/core";
2931
4121
  function useGridController(options) {
2932
- const [controller] = useState13(
4122
+ const [controller] = useState18(
2933
4123
  () => createGridController(options)
2934
4124
  );
2935
4125
  controller.setOptions(options);
2936
- const state = useSyncExternalStore2(controller.subscribe, controller.getState);
4126
+ const state = useSyncExternalStore2(
4127
+ controller.subscribe,
4128
+ controller.getState,
4129
+ controller.getState
4130
+ );
2937
4131
  return { controller, state, view: controller.getView() };
2938
4132
  }
2939
4133
 
2940
4134
  // src/DataGrid/useInfiniteScroll.ts
2941
- import { useEffect as useEffect4, useRef as useRef11 } from "react";
4135
+ import { useEffect as useEffect6, useRef as useRef19 } from "react";
2942
4136
  import { attachInfiniteScroll } from "@gridkitjs/dom";
2943
4137
  function useInfiniteScroll({
2944
4138
  rootRef,
2945
4139
  sentinelRef,
2946
4140
  config
2947
4141
  }) {
2948
- const scroll = useRef11(null);
2949
- useEffect4(() => {
4142
+ const scroll = useRef19(null);
4143
+ useEffect6(() => {
2950
4144
  const root = rootRef.current;
2951
4145
  if (root === null) {
2952
4146
  return void 0;
@@ -2961,7 +4155,7 @@ function useInfiniteScroll({
2961
4155
  scroll.current = null;
2962
4156
  };
2963
4157
  }, [rootRef]);
2964
- useEffect4(() => {
4158
+ useEffect6(() => {
2965
4159
  scroll.current?.update({
2966
4160
  sentinel: sentinelRef.current,
2967
4161
  hasMore: config?.hasMore ?? false,
@@ -2973,11 +4167,11 @@ function useInfiniteScroll({
2973
4167
  }
2974
4168
 
2975
4169
  // src/DataGrid/useLiveAnnouncer.ts
2976
- import { useCallback, useLayoutEffect as useLayoutEffect11, useRef as useRef12 } from "react";
4170
+ import { useCallback, useLayoutEffect as useLayoutEffect15, useRef as useRef20 } from "react";
2977
4171
  import { createLiveAnnouncer } from "@gridkitjs/dom";
2978
4172
  function useLiveAnnouncer(containerRef) {
2979
- const announcer = useRef12(null);
2980
- useLayoutEffect11(() => {
4173
+ const announcer = useRef20(null);
4174
+ useLayoutEffect15(() => {
2981
4175
  const container = containerRef.current;
2982
4176
  if (container === null) {
2983
4177
  return void 0;
@@ -2995,8 +4189,8 @@ function useLiveAnnouncer(containerRef) {
2995
4189
  }
2996
4190
 
2997
4191
  // src/DataGrid/useRowVirtualizer.ts
2998
- import { useLayoutEffect as useLayoutEffect12, useState as useState14 } from "react";
2999
- import { createRowVirtualizer } from "@gridkitjs/dom";
4192
+ import { useLayoutEffect as useLayoutEffect16, useState as useState19 } from "react";
4193
+ import { createRowVirtualizer as createRowVirtualizer2 } from "@gridkitjs/dom";
3000
4194
  var EMPTY_RANGE = {
3001
4195
  startIndex: 0,
3002
4196
  endIndex: -1,
@@ -3013,14 +4207,14 @@ function useRowVirtualizer({
3013
4207
  estimatedRowHeight,
3014
4208
  overscan
3015
4209
  }) {
3016
- const [virtualizer, setVirtualizer] = useState14(null);
3017
- const [range, setRange] = useState14(EMPTY_RANGE);
3018
- useLayoutEffect12(() => {
4210
+ const [virtualizer, setVirtualizer] = useState19(null);
4211
+ const [range, setRange] = useState19(EMPTY_RANGE);
4212
+ useLayoutEffect16(() => {
3019
4213
  const element = bodyScrollRef.current;
3020
4214
  if (!enabled || element === null) {
3021
4215
  return void 0;
3022
4216
  }
3023
- const created = createRowVirtualizer(element, {
4217
+ const created = createRowVirtualizer2(element, {
3024
4218
  estimatedRowHeight,
3025
4219
  overscan,
3026
4220
  onRange: setRange
@@ -3030,7 +4224,7 @@ function useRowVirtualizer({
3030
4224
  created.destroy();
3031
4225
  };
3032
4226
  }, [enabled, bodyScrollRef, estimatedRowHeight, overscan]);
3033
- useLayoutEffect12(() => {
4227
+ useLayoutEffect16(() => {
3034
4228
  virtualizer?.setRowCount(rows.length);
3035
4229
  virtualizer?.resetHeights();
3036
4230
  }, [virtualizer, rows]);
@@ -3047,7 +4241,7 @@ function useRowVirtualizer({
3047
4241
  }
3048
4242
 
3049
4243
  // src/DataGrid/DataGrid.tsx
3050
- import { jsx as jsx20, jsxs as jsxs13 } from "react/jsx-runtime";
4244
+ import { jsx as jsx26, jsxs as jsxs19 } from "react/jsx-runtime";
3051
4245
  var NO_AGGREGATES = [];
3052
4246
  function paginationReader(controller) {
3053
4247
  let last;
@@ -3104,18 +4298,18 @@ function DataGrid(props) {
3104
4298
  const hoverRows = hoverable?.rows ?? true;
3105
4299
  const hoverColumns = hoverable?.columns ?? true;
3106
4300
  const hoverCells = hoverable?.cells ?? true;
3107
- const viewportRef = useRef13(null);
3108
- const headerTableRef = useRef13(null);
3109
- const bodyTableRef = useRef13(null);
3110
- const bodyScrollRef = useRef13(null);
3111
- const sentinelRef = useRef13(null);
4301
+ const viewportRef = useRef21(null);
4302
+ const headerTableRef = useRef21(null);
4303
+ const bodyTableRef = useRef21(null);
4304
+ const bodyScrollRef = useRef21(null);
4305
+ const sentinelRef = useRef21(null);
3112
4306
  const viewportWidth = useElementWidth(viewportRef, resizeMode === "fit");
3113
- const subscribersRef = useRef13(/* @__PURE__ */ new Set());
3114
- const gridId = useId();
4307
+ const subscribersRef = useRef21(/* @__PURE__ */ new Set());
4308
+ const gridId = useId2();
3115
4309
  const announce = useLiveAnnouncer(viewportRef);
3116
- const messages = useMemo4(
3117
- () => resolveMessages(messageOverrides),
3118
- [messageOverrides]
4310
+ const messages = useMemo5(
4311
+ () => resolveMessages(messageOverrides, locale),
4312
+ [messageOverrides, locale]
3119
4313
  );
3120
4314
  const { controller, state, view } = useGridController({
3121
4315
  ...props,
@@ -3128,8 +4322,8 @@ function DataGrid(props) {
3128
4322
  columnOf
3129
4323
  })
3130
4324
  });
3131
- const [readPagination] = useState15(() => paginationReader(controller));
3132
- const [readSelectedRows] = useState15(() => selectedRowsReader(controller));
4325
+ const [readPagination] = useState20(() => paginationReader(controller));
4326
+ const [readSelectedRows] = useState20(() => selectedRowsReader(controller));
3133
4327
  const virtualizer = useRowVirtualizer({
3134
4328
  enabled: virtualized,
3135
4329
  rows: view.pageRows,
@@ -3137,7 +4331,7 @@ function DataGrid(props) {
3137
4331
  estimatedRowHeight,
3138
4332
  overscan
3139
4333
  });
3140
- useEffect5(() => {
4334
+ useEffect7(() => {
3141
4335
  if (virtualized && height === void 0) {
3142
4336
  console.error(
3143
4337
  "DataGrid: `virtualized` has no effect without `height` \u2014 a body with no bounded height has no viewport to window rows against."
@@ -3145,7 +4339,7 @@ function DataGrid(props) {
3145
4339
  }
3146
4340
  }, [virtualized, height]);
3147
4341
  const hasInfiniteScroll = infiniteScroll !== void 0;
3148
- useEffect5(() => {
4342
+ useEffect7(() => {
3149
4343
  if (paginated && hasInfiniteScroll) {
3150
4344
  console.error(
3151
4345
  "DataGrid: `paginated` and `infiniteScroll` are mutually exclusive \u2014 only `paginated` takes effect while both are set."
@@ -3162,7 +4356,7 @@ function DataGrid(props) {
3162
4356
  }
3163
4357
  function columnName(columnId) {
3164
4358
  const column = columnOf(columnId);
3165
- return column === void 0 ? columnId : resolveColumnHeaderText(column);
4359
+ return column === void 0 ? columnId : resolveColumnHeaderText2(column);
3166
4360
  }
3167
4361
  const resize = useColumnResize({
3168
4362
  // `@gridkitjs/dom`'s `measureColumnContent` only ever matches
@@ -3206,19 +4400,20 @@ function DataGrid(props) {
3206
4400
  resize,
3207
4401
  openPanel: panels.open
3208
4402
  });
3209
- const orderedIds = useMemo4(
4403
+ const handleCopyKey = useGridCopy({ controller, commands, nav });
4404
+ const orderedIds = useMemo5(
3210
4405
  () => view.dataColumns.map((entry) => entry.id),
3211
4406
  [view.dataColumns]
3212
4407
  );
3213
- const displayedIds = useMemo4(
4408
+ const displayedIds = useMemo5(
3214
4409
  () => view.columns.map((entry) => entry.id),
3215
4410
  [view.columns]
3216
4411
  );
3217
- const definitions = useMemo4(
4412
+ const definitions = useMemo5(
3218
4413
  () => new Map(view.dataColumns.map((entry) => [entry.id, entry.column])),
3219
4414
  [view.dataColumns]
3220
4415
  );
3221
- const groupByDraggableIds = useMemo4(
4416
+ const groupByDraggableIds = useMemo5(
3222
4417
  () => new Set(
3223
4418
  view.columns.filter((entry) => entry.groupByDraggable).map((entry) => entry.id)
3224
4419
  ),
@@ -3277,12 +4472,12 @@ function DataGrid(props) {
3277
4472
  messages,
3278
4473
  columnName
3279
4474
  });
3280
- useEffect5(() => {
4475
+ useEffect7(() => {
3281
4476
  for (const listener of subscribersRef.current) {
3282
4477
  listener();
3283
4478
  }
3284
4479
  }, [view, state, nav.focus]);
3285
- useImperativeHandle(ref, () => ({
4480
+ useImperativeHandle3(ref, () => ({
3286
4481
  get element() {
3287
4482
  return viewportRef.current;
3288
4483
  },
@@ -3347,9 +4542,9 @@ function DataGrid(props) {
3347
4542
  },
3348
4543
  openContextMenu: contextMenu.open,
3349
4544
  closeContextMenu: contextMenu.close,
3350
- openColumnChooser: () => {
3351
- panels.open("columnChooser", { area: "grid" });
3352
- },
4545
+ ...exportApi(commands),
4546
+ ...printApi(commands),
4547
+ ...panelApi(panels, controller),
3353
4548
  runCommand: (commandId, target = { area: "grid" }) => commands.run(commandId, target, "api"),
3354
4549
  scrollToRow: (rowId, options) => {
3355
4550
  const index = controller.getView().pageRows.findIndex(
@@ -3391,8 +4586,8 @@ function DataGrid(props) {
3391
4586
  selection.columnMode === false ? "" : "selectable-columns",
3392
4587
  selection.cellMode === false ? "" : "selectable-cells"
3393
4588
  );
3394
- return /* @__PURE__ */ jsxs13("div", { className: "gridkit-data-grid-viewport", ref: viewportRef, children: [
3395
- /* @__PURE__ */ jsx20(
4589
+ return /* @__PURE__ */ jsxs19("div", { className: "gridkit-data-grid-viewport", ref: viewportRef, children: [
4590
+ /* @__PURE__ */ jsx26(
3396
4591
  GridToolbar,
3397
4592
  {
3398
4593
  toolbar: props.toolbar,
@@ -3404,7 +4599,7 @@ function DataGrid(props) {
3404
4599
  announce
3405
4600
  }
3406
4601
  ),
3407
- showGroupByBar && /* @__PURE__ */ jsx20(
4602
+ showGroupByBar && /* @__PURE__ */ jsx26(
3408
4603
  GroupByBar,
3409
4604
  {
3410
4605
  columns: view.columns,
@@ -3418,7 +4613,7 @@ function DataGrid(props) {
3418
4613
  messages
3419
4614
  }
3420
4615
  ),
3421
- /* @__PURE__ */ jsxs13(
4616
+ /* @__PURE__ */ jsxs19(
3422
4617
  "div",
3423
4618
  {
3424
4619
  id: gridId,
@@ -3435,6 +4630,7 @@ function DataGrid(props) {
3435
4630
  ),
3436
4631
  onKeyDown: (event) => {
3437
4632
  if (!isOwnEvent(event)) return;
4633
+ handleCopyKey(event);
3438
4634
  if (event.key === "Escape") {
3439
4635
  if (drag.draggedColumnId !== null || resize.activeColumnId !== null)
3440
4636
  return;
@@ -3448,27 +4644,29 @@ function DataGrid(props) {
3448
4644
  }
3449
4645
  },
3450
4646
  children: [
3451
- /* @__PURE__ */ jsxs13(
4647
+ /* @__PURE__ */ jsxs19(
3452
4648
  "table",
3453
4649
  {
3454
4650
  ref: headerTableRef,
4651
+ role: "presentation",
3455
4652
  style: { width: totalColumnWidth(view.columns) },
3456
4653
  className: classNames(gridTableClassName, "gridkit-data-grid-header"),
3457
4654
  children: [
3458
- /* @__PURE__ */ jsx20(GridColgroup, { columns: view.columns }),
3459
- /* @__PURE__ */ jsx20(
4655
+ /* @__PURE__ */ jsx26(GridColgroup, { columns: view.columns }),
4656
+ /* @__PURE__ */ jsx26(
3460
4657
  GridHeader,
3461
4658
  {
3462
4659
  columns: view.columns,
3463
4660
  resize,
3464
4661
  drag,
3465
4662
  controller,
3466
- columnSort: state.columnSort,
4663
+ state,
3467
4664
  sortableColumns,
3468
- groupBy: state.groupBy,
3469
4665
  groupableColumns,
3470
4666
  groupToggleIconColumns,
3471
4667
  headerMenu: contextMenu.opens("header"),
4668
+ panels,
4669
+ messages,
3472
4670
  nav,
3473
4671
  selection
3474
4672
  }
@@ -3476,7 +4674,7 @@ function DataGrid(props) {
3476
4674
  ]
3477
4675
  }
3478
4676
  ),
3479
- /* @__PURE__ */ jsx20(
4677
+ /* @__PURE__ */ jsx26(
3480
4678
  "div",
3481
4679
  {
3482
4680
  className: "gridkit-data-grid-body",
@@ -3484,15 +4682,16 @@ function DataGrid(props) {
3484
4682
  style: height === void 0 ? void 0 : {
3485
4683
  "--gridkit-body-height": typeof height === "number" ? `${String(height)}px` : height
3486
4684
  },
3487
- children: /* @__PURE__ */ jsxs13(
4685
+ children: /* @__PURE__ */ jsxs19(
3488
4686
  "table",
3489
4687
  {
3490
4688
  ref: bodyTableRef,
4689
+ role: "presentation",
3491
4690
  style: { width: totalColumnWidth(view.columns) },
3492
4691
  className: gridTableClassName,
3493
4692
  children: [
3494
- /* @__PURE__ */ jsx20(GridColgroup, { columns: view.columns }),
3495
- /* @__PURE__ */ jsx20(
4693
+ /* @__PURE__ */ jsx26(GridColgroup, { columns: view.columns }),
4694
+ /* @__PURE__ */ jsx26(
3496
4695
  GridBody,
3497
4696
  {
3498
4697
  columns: view.columns,
@@ -3521,17 +4720,18 @@ function DataGrid(props) {
3521
4720
  )
3522
4721
  }
3523
4722
  ),
3524
- aggregates.length > 0 && /* @__PURE__ */ jsxs13(
4723
+ aggregates.length > 0 && /* @__PURE__ */ jsxs19(
3525
4724
  "table",
3526
4725
  {
4726
+ role: "presentation",
3527
4727
  style: { width: totalColumnWidth(view.columns) },
3528
4728
  className: classNames(
3529
4729
  gridTableClassName,
3530
4730
  "gridkit-data-grid-footer"
3531
4731
  ),
3532
4732
  children: [
3533
- /* @__PURE__ */ jsx20(GridColgroup, { columns: view.columns }),
3534
- /* @__PURE__ */ jsx20(
4733
+ /* @__PURE__ */ jsx26(GridColgroup, { columns: view.columns }),
4734
+ /* @__PURE__ */ jsx26(
3535
4735
  GridFooter,
3536
4736
  {
3537
4737
  columns: view.columns,
@@ -3547,9 +4747,17 @@ function DataGrid(props) {
3547
4747
  ]
3548
4748
  }
3549
4749
  ),
3550
- /* @__PURE__ */ jsx20(GridContextMenu, { contextMenu }),
3551
- /* @__PURE__ */ jsx20(GridPanels, { panels, controller, messages }),
3552
- paginated && (pager?.template ? pager.template(pagerContext) : /* @__PURE__ */ jsx20(GridPager, { pager: pagerContext, config: pager, messages }))
4750
+ /* @__PURE__ */ jsx26(GridContextMenu, { contextMenu }),
4751
+ /* @__PURE__ */ jsx26(
4752
+ GridPanels,
4753
+ {
4754
+ panels,
4755
+ controller,
4756
+ commands,
4757
+ messages
4758
+ }
4759
+ ),
4760
+ paginated && (pager?.template ? pager.template(pagerContext) : /* @__PURE__ */ jsx26(GridPager, { pager: pagerContext, config: pager, messages }))
3553
4761
  ] });
3554
4762
  }
3555
4763
 
@@ -3573,10 +4781,14 @@ function useGridSnapshot(gridRef, getSnapshot, fallback) {
3573
4781
  },
3574
4782
  [gridRef]
3575
4783
  );
3576
- return useSyncExternalStore3(subscribe, () => {
3577
- const api = gridRef.current;
3578
- return api === null ? fallback : getSnapshot(api);
3579
- });
4784
+ return useSyncExternalStore3(
4785
+ subscribe,
4786
+ () => {
4787
+ const api = gridRef.current;
4788
+ return api === null ? fallback : getSnapshot(api);
4789
+ },
4790
+ () => fallback
4791
+ );
3580
4792
  }
3581
4793
 
3582
4794
  // src/DataGrid/hooks/usePaginationState.ts
@@ -3726,9 +4938,9 @@ function useColumnVisibilityState(gridRef) {
3726
4938
  }
3727
4939
 
3728
4940
  // src/DataGrid/hooks/useFilterState.ts
3729
- import { useCallback as useCallback11, useState as useState16 } from "react";
4941
+ import { useCallback as useCallback11, useState as useState21 } from "react";
3730
4942
  function useFilterState(gridRef) {
3731
- const [fallback] = useState16(() => []);
4943
+ const [fallback] = useState21(() => []);
3732
4944
  const filter = useGridSnapshot(
3733
4945
  gridRef,
3734
4946
  useCallback11((api) => api.getFilter(), []),
@@ -3741,8 +4953,12 @@ function useFilterState(gridRef) {
3741
4953
  }
3742
4954
  };
3743
4955
  }
4956
+
4957
+ // src/index.ts
4958
+ import { excelFilter } from "@gridkitjs/core";
3744
4959
  export {
3745
4960
  DataGrid,
4961
+ excelFilter,
3746
4962
  serializeGridState,
3747
4963
  useAggregateState,
3748
4964
  useColumnOrderState,