@gridkitjs/core 0.11.0 → 0.12.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -90,7 +90,9 @@ function buildKeyShortcuts(capabilities) {
90
90
  capabilities.sortable ? "Alt+ArrowUp" : "",
91
91
  // Down rather than reusing Up: it reads as "drill into groups", and Up is
92
92
  // already sort's own mnemonic on the same header.
93
- capabilities.groupable ? "Alt+ArrowDown" : ""
93
+ capabilities.groupable ? "Alt+ArrowDown" : "",
94
+ // The ContextMenu key needs no mention: it says what it does on its face.
95
+ capabilities.contextMenu === true ? "Shift+F10" : ""
94
96
  ].filter(Boolean).join(" ");
95
97
  }
96
98
  function accessDotted(obj, path) {
@@ -1902,6 +1904,9 @@ function createGridController(initial) {
1902
1904
  });
1903
1905
  }
1904
1906
  function commitPagination(next) {
1907
+ if (samePagination(state.pagination, next)) {
1908
+ return;
1909
+ }
1905
1910
  commit("pagination", next, () => {
1906
1911
  options.onPaginationChange?.({
1907
1912
  pagination: next,
@@ -2046,6 +2051,17 @@ function createGridController(initial) {
2046
2051
  options.onColumnSortChange?.({ columnId, sort: next });
2047
2052
  });
2048
2053
  },
2054
+ clearColumnSort(columnId) {
2055
+ const next = state.columnSort.filter(
2056
+ (entry) => entry.columnId !== columnId
2057
+ );
2058
+ if (next.length === state.columnSort.length) {
2059
+ return;
2060
+ }
2061
+ commit("columnSort", next, () => {
2062
+ options.onColumnSortChange?.({ columnId, sort: next });
2063
+ });
2064
+ },
2049
2065
  setGroupBy(groupBy) {
2050
2066
  const columnId = changedEntryColumnId(state.groupBy, groupBy);
2051
2067
  if (columnId !== void 0) {
@@ -2084,11 +2100,7 @@ function createGridController(initial) {
2084
2100
  collapseAllGroups() {
2085
2101
  commitGroupExpansion(collapseAllGroups(getView().displayRows), null);
2086
2102
  },
2087
- setPagination(pagination) {
2088
- if (!samePagination(state.pagination, pagination)) {
2089
- commitPagination(pagination);
2090
- }
2091
- },
2103
+ setPagination: commitPagination,
2092
2104
  goToPage,
2093
2105
  nextPage() {
2094
2106
  goToPage(shownPageIndex() + 1);
@@ -2142,14 +2154,653 @@ function createGridController(initial) {
2142
2154
  return controller;
2143
2155
  }
2144
2156
 
2157
+ // src/commands/context.ts
2158
+ function selectedRowsOf(rows, selection) {
2159
+ if (selection.length === 0) {
2160
+ return [];
2161
+ }
2162
+ const selected = new Set(selection);
2163
+ return rows.filter((entry) => selected.has(entry.rowId));
2164
+ }
2165
+ function createCommandContext({
2166
+ controller,
2167
+ target,
2168
+ messages,
2169
+ host
2170
+ }) {
2171
+ const state = controller.getState();
2172
+ const view = controller.getView();
2173
+ let selectedRows;
2174
+ return {
2175
+ target,
2176
+ state,
2177
+ view,
2178
+ options: controller.getOptions(),
2179
+ messages,
2180
+ actions: controller,
2181
+ host,
2182
+ row: target.area === "cell" ? view.rows.find((entry) => entry.rowId === target.rowId) ?? null : null,
2183
+ column: "columnId" in target ? view.dataColumns.find((entry) => entry.id === target.columnId) ?? null : null,
2184
+ selectedRows: () => selectedRows ??= selectedRowsOf(view.rows, state.rowSelection)
2185
+ };
2186
+ }
2187
+
2188
+ // src/commands/builtInCommands.ts
2189
+ function isOn(context, areas) {
2190
+ return areas.includes(context.target.area);
2191
+ }
2192
+ function columnOn(context, areas) {
2193
+ return isOn(context, areas) ? context.column : null;
2194
+ }
2195
+ function isGroupedBy(context, columnId) {
2196
+ return context.state.groupBy.some((entry) => entry.columnId === columnId);
2197
+ }
2198
+ function sortableColumn(context) {
2199
+ const column = columnOn(context, ["header"]);
2200
+ return column !== null && (column.column.sortable ?? context.options.sortableColumns ?? false) ? column : null;
2201
+ }
2202
+ function groupableColumn(context) {
2203
+ const column = columnOn(context, ["header"]);
2204
+ return column !== null && (column.column.groupable ?? context.options.groupableColumns ?? false) && !isGroupedBy(context, column.id) ? column : null;
2205
+ }
2206
+ function groupedColumn(context) {
2207
+ const column = columnOn(context, ["header", "group"]);
2208
+ return column !== null && isGroupedBy(context, column.id) ? column : null;
2209
+ }
2210
+ function hideableColumn(context) {
2211
+ const column = columnOn(context, ["header"]);
2212
+ return column !== null && column.column.hideable !== false ? column : null;
2213
+ }
2214
+ function resizableColumn(context) {
2215
+ const column = columnOn(context, ["header"]);
2216
+ if (column === null) {
2217
+ return null;
2218
+ }
2219
+ return context.view.columns.find(
2220
+ (entry) => entry.id === column.id && entry.resizable
2221
+ ) ?? null;
2222
+ }
2223
+ function resizableColumnIds(context) {
2224
+ return context.view.columns.filter((entry) => entry.resizable).map((entry) => entry.id);
2225
+ }
2226
+ function isAnythingSelectable(selectable) {
2227
+ return [selectable?.rows, selectable?.columns, selectable?.cells].some(
2228
+ (mode) => mode !== void 0 && mode !== false
2229
+ );
2230
+ }
2231
+ function columnName(context) {
2232
+ return context.column === null ? "" : resolveColumnHeaderText(context.column.column);
2233
+ }
2234
+ function columnCommand({
2235
+ id,
2236
+ label,
2237
+ icon,
2238
+ find,
2239
+ isEnabled,
2240
+ isChecked,
2241
+ execute
2242
+ }) {
2243
+ function test(predicate) {
2244
+ return (context) => {
2245
+ const column = find(context);
2246
+ return column !== null && predicate(column, context);
2247
+ };
2248
+ }
2249
+ return {
2250
+ id,
2251
+ label,
2252
+ icon,
2253
+ isVisible: (context) => find(context) !== null,
2254
+ isEnabled: isEnabled === void 0 ? void 0 : test(isEnabled),
2255
+ isChecked: isChecked === void 0 ? void 0 : test(isChecked),
2256
+ execute: (context) => {
2257
+ const column = find(context);
2258
+ if (column !== null) {
2259
+ execute(column, context);
2260
+ }
2261
+ }
2262
+ };
2263
+ }
2264
+ function sortCommand(direction) {
2265
+ return columnCommand({
2266
+ id: direction === "asc" ? "sort.ascending" : "sort.descending",
2267
+ label: ({ messages }) => direction === "asc" ? messages.sortAscending : messages.sortDescending,
2268
+ icon: direction === "asc" ? "sortAscending" : "sortDescending",
2269
+ find: sortableColumn,
2270
+ isChecked: (column, { state }) => sortDirectionFor(state.columnSort, column.id) === direction,
2271
+ execute: (column, { actions }) => {
2272
+ actions.setColumnSort([{ columnId: column.id, direction }]);
2273
+ }
2274
+ });
2275
+ }
2276
+ function builtInCommands() {
2277
+ return [
2278
+ sortCommand("asc"),
2279
+ sortCommand("desc"),
2280
+ // Cleared rather than set, so the change reports this column — the one
2281
+ // the announcement names — rather than a key that moved up past it.
2282
+ columnCommand({
2283
+ id: "sort.clear",
2284
+ label: ({ messages }) => messages.clearSort,
2285
+ find: sortableColumn,
2286
+ isEnabled: (column, { state }) => sortDirectionFor(state.columnSort, column.id) !== null,
2287
+ execute: (column, { actions }) => {
2288
+ actions.clearColumnSort(column.id);
2289
+ }
2290
+ }),
2291
+ {
2292
+ id: "sort.clearAll",
2293
+ label: ({ messages }) => messages.clearAllSorting,
2294
+ isVisible: (context) => isOn(context, ["grid", "toolbar", "header"]),
2295
+ isEnabled: ({ state }) => state.columnSort.length > 0,
2296
+ execute: ({ actions }) => {
2297
+ actions.setColumnSort([]);
2298
+ }
2299
+ },
2300
+ columnCommand({
2301
+ id: "group.add",
2302
+ label: (context) => context.messages.groupByColumn(columnName(context)),
2303
+ icon: "group",
2304
+ find: groupableColumn,
2305
+ execute: (column, { actions }) => {
2306
+ actions.toggleGroupBy(column.id);
2307
+ }
2308
+ }),
2309
+ // Toggled rather than set, so the change reports this column — the one
2310
+ // the announcement names — rather than the level that moved up.
2311
+ columnCommand({
2312
+ id: "group.remove",
2313
+ label: (context) => context.messages.removeGroupBy(columnName(context)),
2314
+ icon: "group",
2315
+ find: groupedColumn,
2316
+ execute: (column, { actions }) => {
2317
+ actions.toggleGroupBy(column.id);
2318
+ }
2319
+ }),
2320
+ {
2321
+ id: "group.clearAll",
2322
+ label: ({ messages }) => messages.ungroupAll,
2323
+ isVisible: (context) => isOn(context, ["grid", "toolbar"]) && context.state.groupBy.length > 0,
2324
+ execute: ({ actions }) => {
2325
+ actions.setGroupBy([]);
2326
+ }
2327
+ },
2328
+ {
2329
+ id: "group.expandAll",
2330
+ label: ({ messages }) => messages.expandAllGroups,
2331
+ isVisible: (context) => isOn(context, ["grid", "toolbar", "group", "cell"]) && context.state.groupBy.length > 0,
2332
+ execute: ({ actions }) => {
2333
+ actions.expandAllGroups();
2334
+ }
2335
+ },
2336
+ {
2337
+ id: "group.collapseAll",
2338
+ label: ({ messages }) => messages.collapseAllGroups,
2339
+ isVisible: (context) => isOn(context, ["grid", "toolbar", "group", "cell"]) && context.state.groupBy.length > 0,
2340
+ execute: ({ actions }) => {
2341
+ actions.collapseAllGroups();
2342
+ }
2343
+ },
2344
+ columnCommand({
2345
+ id: "column.hide",
2346
+ label: ({ messages }) => messages.hideColumn,
2347
+ icon: "hide",
2348
+ find: hideableColumn,
2349
+ // The last column shown stays: a grid with none has no header left to
2350
+ // bring one back from.
2351
+ isEnabled: (column, { view }) => view.columns.some((entry) => entry.id !== column.id),
2352
+ execute: (column, { actions }) => {
2353
+ actions.hideColumns([column.id]);
2354
+ }
2355
+ }),
2356
+ columnCommand({
2357
+ id: "column.autoFit",
2358
+ label: ({ messages }) => messages.autoFitColumn,
2359
+ icon: "autoFit",
2360
+ find: resizableColumn,
2361
+ execute: (column, { host }) => {
2362
+ host.autoFitColumns([column.id]);
2363
+ }
2364
+ }),
2365
+ {
2366
+ id: "column.autoFitAll",
2367
+ label: ({ messages }) => messages.autoFitAllColumns,
2368
+ icon: "autoFit",
2369
+ isVisible: (context) => isOn(context, ["grid", "toolbar", "header"]) && resizableColumnIds(context).length > 0,
2370
+ execute: (context) => {
2371
+ context.host.autoFitColumns(resizableColumnIds(context));
2372
+ }
2373
+ },
2374
+ // The target goes along, so the renderer opens the chooser where it was
2375
+ // asked for: beside the toolbar button, below the header, or at the
2376
+ // grid's corner for a call from nowhere in particular.
2377
+ {
2378
+ id: "column.choose",
2379
+ label: ({ messages }) => messages.chooseColumns,
2380
+ isVisible: (context) => isOn(context, ["grid", "toolbar", "header"]),
2381
+ execute: ({ host, target }) => {
2382
+ host.openPanel("columnChooser", target);
2383
+ }
2384
+ },
2385
+ {
2386
+ id: "selection.selectAllRows",
2387
+ label: ({ messages }) => messages.selectAllRows,
2388
+ isVisible: (context) => isOn(context, ["grid", "cell"]) && context.options.selectable?.rows === "multiple",
2389
+ isEnabled: ({ view }) => view.displayDataRows.length > 0,
2390
+ execute: ({ actions }) => {
2391
+ actions.selectAllRows();
2392
+ }
2393
+ },
2394
+ {
2395
+ id: "selection.clear",
2396
+ label: ({ messages }) => messages.clearSelection,
2397
+ isVisible: (context) => isOn(context, ["grid", "cell", "header"]) && isAnythingSelectable(context.options.selectable),
2398
+ isEnabled: ({ state }) => state.rowSelection.length > 0 || state.columnSelection.length > 0 || state.cellSelection !== null,
2399
+ execute: ({ actions }) => {
2400
+ actions.clearSelection();
2401
+ }
2402
+ },
2403
+ {
2404
+ id: "filter.clearAll",
2405
+ label: ({ messages }) => messages.clearFilters,
2406
+ isVisible: (context) => isOn(context, ["grid", "toolbar", "header"]),
2407
+ isEnabled: ({ state }) => state.filter.length > 0,
2408
+ execute: ({ actions }) => {
2409
+ actions.setFilter([]);
2410
+ }
2411
+ }
2412
+ ];
2413
+ }
2414
+
2415
+ // src/commands/registry.ts
2416
+ function isCommandVisible(command, context) {
2417
+ return command.isVisible?.(context) ?? true;
2418
+ }
2419
+ function isCommandEnabled(command, context) {
2420
+ return command.isEnabled?.(context) ?? true;
2421
+ }
2422
+ function resolveCommands(commands) {
2423
+ const registry = new Map(
2424
+ builtInCommands().map((command) => [command.id, command])
2425
+ );
2426
+ for (const command of commands ?? []) {
2427
+ if (command.id === "separator") {
2428
+ reportError(
2429
+ "a command cannot have the id `separator`, which a menu reads as a separator, so it is left out."
2430
+ );
2431
+ continue;
2432
+ }
2433
+ registry.set(command.id, command);
2434
+ }
2435
+ return registry;
2436
+ }
2437
+ function runCommand(command, context) {
2438
+ if (!isCommandVisible(command, context) || !isCommandEnabled(command, context)) {
2439
+ return false;
2440
+ }
2441
+ command.execute(context);
2442
+ return true;
2443
+ }
2444
+
2445
+ // src/menu/resolveMenu.ts
2446
+ var reportedUnknownIds = /* @__PURE__ */ new WeakMap();
2447
+ function reportUnknownId(commands, id) {
2448
+ let reported = reportedUnknownIds.get(commands);
2449
+ if (reported === void 0) {
2450
+ reported = /* @__PURE__ */ new Set();
2451
+ reportedUnknownIds.set(commands, reported);
2452
+ }
2453
+ if (reported.has(id)) {
2454
+ return;
2455
+ }
2456
+ reported.add(id);
2457
+ reportError(
2458
+ `the menu or toolbar entry \`${id}\` names no command, so it is left out.`
2459
+ );
2460
+ }
2461
+ function evaluate(value, context) {
2462
+ return typeof value === "function" ? value(context) : value;
2463
+ }
2464
+ function commandItem(command, context) {
2465
+ if (!isCommandVisible(command, context)) {
2466
+ return null;
2467
+ }
2468
+ return {
2469
+ type: "item",
2470
+ id: command.id,
2471
+ label: evaluate(command.label, context),
2472
+ icon: command.icon,
2473
+ shortcut: command.shortcut,
2474
+ disabled: !isCommandEnabled(command, context),
2475
+ checked: command.isChecked?.(context),
2476
+ run: () => runCommand(command, context)
2477
+ };
2478
+ }
2479
+ function inlineItem(entry, context) {
2480
+ if (evaluate(entry.hidden ?? false, context)) {
2481
+ return null;
2482
+ }
2483
+ const disabled = evaluate(entry.disabled ?? false, context);
2484
+ return {
2485
+ type: "item",
2486
+ id: entry.id,
2487
+ label: evaluate(entry.label, context),
2488
+ icon: entry.icon,
2489
+ shortcut: entry.shortcut,
2490
+ disabled,
2491
+ checked: entry.checked === void 0 ? void 0 : evaluate(entry.checked, context),
2492
+ run: () => {
2493
+ if (disabled) {
2494
+ return false;
2495
+ }
2496
+ entry.onSelect(context);
2497
+ return true;
2498
+ }
2499
+ };
2500
+ }
2501
+ function collapseSeparators(entries, isEdge = () => false) {
2502
+ const collapsed = [];
2503
+ for (const entry of entries) {
2504
+ const previous = collapsed.at(-1);
2505
+ if (entry.type !== "separator") {
2506
+ if (isEdge(entry) && previous?.type === "separator") {
2507
+ collapsed.pop();
2508
+ }
2509
+ collapsed.push(entry);
2510
+ } else if (previous !== void 0 && previous.type !== "separator" && !isEdge(previous)) {
2511
+ collapsed.push(entry);
2512
+ }
2513
+ }
2514
+ if (collapsed.at(-1)?.type === "separator") {
2515
+ collapsed.pop();
2516
+ }
2517
+ return collapsed;
2518
+ }
2519
+ function resolveMenuEntry(entry, index, options) {
2520
+ const { commands, context } = options;
2521
+ if (entry === "separator") {
2522
+ return { type: "separator", key: `separator-${String(index)}` };
2523
+ }
2524
+ if (typeof entry === "string") {
2525
+ const command = commands.get(entry);
2526
+ if (command === void 0) {
2527
+ reportUnknownId(commands, entry);
2528
+ return null;
2529
+ }
2530
+ return commandItem(command, context);
2531
+ }
2532
+ return entry.type === "item" ? inlineItem(entry, context) : resolveSubmenu(entry, options);
2533
+ }
2534
+ function resolveSubmenu(entry, options) {
2535
+ const items = resolveMenu(entry.items, options);
2536
+ return items.length === 0 ? null : {
2537
+ type: "submenu",
2538
+ id: entry.id,
2539
+ label: evaluate(entry.label, options.context),
2540
+ icon: entry.icon,
2541
+ items
2542
+ };
2543
+ }
2544
+ function resolveMenu(entries, options) {
2545
+ return collapseSeparators(
2546
+ entries.map((entry, index) => resolveMenuEntry(entry, index, options)).filter((entry) => entry !== null)
2547
+ );
2548
+ }
2549
+
2550
+ // src/menu/contextMenu.ts
2551
+ var defaultContextMenu = {
2552
+ header: [
2553
+ "sort.ascending",
2554
+ "sort.descending",
2555
+ "sort.clear",
2556
+ "separator",
2557
+ "group.add",
2558
+ "group.remove",
2559
+ "separator",
2560
+ "column.autoFit",
2561
+ "column.autoFitAll",
2562
+ "separator",
2563
+ "column.hide",
2564
+ "column.choose"
2565
+ ],
2566
+ cell: [
2567
+ "selection.selectAllRows",
2568
+ "selection.clear",
2569
+ "separator",
2570
+ "group.expandAll",
2571
+ "group.collapseAll"
2572
+ ],
2573
+ group: ["group.expandAll", "group.collapseAll", "separator", "group.remove"],
2574
+ body: []
2575
+ };
2576
+ function hasContextMenu(contextMenu, area) {
2577
+ if (contextMenu === void 0 || contextMenu === false) {
2578
+ return false;
2579
+ }
2580
+ const entries = (contextMenu === true ? void 0 : contextMenu[area]) ?? defaultContextMenu[area];
2581
+ return typeof entries === "function" || entries.length > 0;
2582
+ }
2583
+ function entriesFor(given, request) {
2584
+ if (given === void 0) {
2585
+ return request.defaults;
2586
+ }
2587
+ return typeof given === "function" ? given(request) ?? [] : given;
2588
+ }
2589
+ function contextMenuEntries(contextMenu, context) {
2590
+ if (contextMenu === void 0 || contextMenu === false) {
2591
+ return [];
2592
+ }
2593
+ const options = contextMenu === true ? {} : contextMenu;
2594
+ const { target } = context;
2595
+ switch (target.area) {
2596
+ case "header":
2597
+ return entriesFor(options.header, {
2598
+ target,
2599
+ defaults: defaultContextMenu.header,
2600
+ context
2601
+ });
2602
+ case "cell":
2603
+ return entriesFor(options.cell, {
2604
+ target,
2605
+ defaults: defaultContextMenu.cell,
2606
+ context
2607
+ });
2608
+ case "group":
2609
+ return entriesFor(options.group, {
2610
+ target,
2611
+ defaults: defaultContextMenu.group,
2612
+ context
2613
+ });
2614
+ case "body":
2615
+ return entriesFor(options.body, {
2616
+ target,
2617
+ defaults: defaultContextMenu.body,
2618
+ context
2619
+ });
2620
+ default:
2621
+ return [];
2622
+ }
2623
+ }
2624
+
2625
+ // src/menu/navigation.ts
2626
+ function startIndex(count, from, move) {
2627
+ const focused = from >= 0 && from < count;
2628
+ switch (move) {
2629
+ case "first":
2630
+ return 0;
2631
+ case "last":
2632
+ return count - 1;
2633
+ case "next":
2634
+ return focused ? from + 1 : 0;
2635
+ case "previous":
2636
+ return focused ? from - 1 : count - 1;
2637
+ }
2638
+ }
2639
+ function nextMenuIndex(entries, from, move) {
2640
+ const count = entries.length;
2641
+ const step = move === "next" || move === "first" ? 1 : -1;
2642
+ const start = startIndex(count, from, move);
2643
+ for (let offset = 0; offset < count; offset++) {
2644
+ const index = ((start + step * offset) % count + count) % count;
2645
+ if (entries[index]?.type !== "separator") {
2646
+ return index;
2647
+ }
2648
+ }
2649
+ return -1;
2650
+ }
2651
+ function typeaheadIndex(labels, from, query) {
2652
+ const typed = query.toLowerCase();
2653
+ const firstCodePoint = typed.codePointAt(0);
2654
+ if (firstCodePoint === void 0) {
2655
+ return -1;
2656
+ }
2657
+ const first = String.fromCodePoint(firstCodePoint);
2658
+ const repeated = typed.replaceAll(first, "") === "";
2659
+ const prefix = repeated ? first : typed;
2660
+ const start = repeated ? from + 1 : Math.max(from, 0);
2661
+ for (let offset = 0; offset < labels.length; offset++) {
2662
+ const index = (start + offset) % labels.length;
2663
+ if (labels[index]?.toLowerCase().startsWith(prefix) === true) {
2664
+ return index;
2665
+ }
2666
+ }
2667
+ return -1;
2668
+ }
2669
+
2670
+ // src/toolbar/options.ts
2671
+ var defaultToolbar = ["search", "spacer", "filter.clearAll", "column.choose"];
2672
+ function toolbarEntries(toolbar) {
2673
+ if (toolbar === void 0 || toolbar === false) {
2674
+ return [];
2675
+ }
2676
+ return (toolbar === true ? void 0 : toolbar.items) ?? defaultToolbar;
2677
+ }
2678
+
2679
+ // src/toolbar/resolveToolbar.ts
2680
+ function isSpacer(entry) {
2681
+ return entry.type === "spacer";
2682
+ }
2683
+ function resolveToolbar(entries, options) {
2684
+ const { commands, contextFor } = options;
2685
+ function resolveEntry(entry, index) {
2686
+ if (entry === "search") {
2687
+ return { type: "search", key: "search" };
2688
+ }
2689
+ if (entry === "spacer") {
2690
+ return { type: "spacer", key: `spacer-${String(index)}` };
2691
+ }
2692
+ if (typeof entry === "object" && entry.type === "custom") {
2693
+ return { type: "custom", id: entry.id, key: entry.id };
2694
+ }
2695
+ if (entry === "separator") {
2696
+ return { type: "separator", key: `separator-${String(index)}` };
2697
+ }
2698
+ const resolved = resolveMenuEntry(entry, index, {
2699
+ commands,
2700
+ context: contextFor({
2701
+ area: "toolbar",
2702
+ itemId: typeof entry === "string" ? entry : entry.id
2703
+ })
2704
+ });
2705
+ if (resolved === null || resolved.type === "separator") {
2706
+ return resolved;
2707
+ }
2708
+ if (resolved.type === "submenu") {
2709
+ return { ...resolved, type: "menu", key: resolved.id };
2710
+ }
2711
+ const { checked, ...item } = resolved;
2712
+ return { ...item, type: "button", key: item.id, pressed: checked };
2713
+ }
2714
+ return collapseSeparators(
2715
+ entries.map(resolveEntry).filter((entry) => entry !== null),
2716
+ isSpacer
2717
+ );
2718
+ }
2719
+
2720
+ // src/toolbar/overflow.ts
2721
+ function fitToolbarItems({
2722
+ widths,
2723
+ available,
2724
+ moreWidth,
2725
+ pinned
2726
+ }) {
2727
+ const total = widths.reduce((sum, width) => sum + width, 0);
2728
+ if (total <= available) {
2729
+ return widths.length;
2730
+ }
2731
+ let used = moreWidth + widths.reduce((sum, width, index) => sum + (pinned[index] ? width : 0), 0);
2732
+ for (const [index, width] of widths.entries()) {
2733
+ if (!pinned[index]) {
2734
+ used += width;
2735
+ if (used > available) {
2736
+ return index;
2737
+ }
2738
+ }
2739
+ }
2740
+ return widths.length;
2741
+ }
2742
+ function isPinnedToolbarItem(item) {
2743
+ return item.type === "search" || item.type === "custom";
2744
+ }
2745
+ function asMenuEntry(item) {
2746
+ switch (item.type) {
2747
+ case "button":
2748
+ return {
2749
+ type: "item",
2750
+ id: item.id,
2751
+ label: item.label,
2752
+ icon: item.icon,
2753
+ shortcut: item.shortcut,
2754
+ disabled: item.disabled,
2755
+ checked: item.pressed,
2756
+ run: item.run
2757
+ };
2758
+ case "menu":
2759
+ return {
2760
+ type: "submenu",
2761
+ id: item.id,
2762
+ label: item.label,
2763
+ icon: item.icon,
2764
+ items: item.items
2765
+ };
2766
+ case "separator":
2767
+ return item;
2768
+ default:
2769
+ return null;
2770
+ }
2771
+ }
2772
+ function splitToolbarItems(items, fit) {
2773
+ const fits = (item, index) => index < fit || isPinnedToolbarItem(item);
2774
+ return {
2775
+ shown: collapseSeparators(
2776
+ items.filter(fits),
2777
+ (item) => item.type === "spacer"
2778
+ ),
2779
+ overflow: collapseSeparators(
2780
+ items.filter((item, index) => !fits(item, index)).map(asMenuEntry).filter((entry) => entry !== null)
2781
+ )
2782
+ };
2783
+ }
2784
+
2785
+ // src/toolbar/search.ts
2786
+ function searchTextOf(filter) {
2787
+ const query = filterQueryFor(filter) ?? "";
2788
+ const start = query.startsWith("%") ? 1 : 0;
2789
+ const end = query.endsWith("%") ? -1 : void 0;
2790
+ return query.slice(start, end);
2791
+ }
2792
+ function setSearchText(filter, text) {
2793
+ return setColumnFilter(filter, void 0, text === "" ? "" : `%${text}%`);
2794
+ }
2795
+
2145
2796
  // src/util/navigation.ts
2146
2797
  var HEADER_ROW = -1;
2147
- function clampFocus(focus, rowCount, columnCount) {
2798
+ function clampFocus(focus, rowCount, columnCount2) {
2148
2799
  return {
2149
2800
  rowIndex: Math.min(Math.max(focus.rowIndex, HEADER_ROW), rowCount - 1),
2150
2801
  columnIndex: Math.min(
2151
2802
  Math.max(focus.columnIndex, 0),
2152
- Math.max(columnCount - 1, 0)
2803
+ Math.max(columnCount2 - 1, 0)
2153
2804
  )
2154
2805
  };
2155
2806
  }
@@ -2167,7 +2818,7 @@ function skipSkippableRows(rowIndex, direction, rowCount, isSkippableRow, fallba
2167
2818
  }
2168
2819
  return next;
2169
2820
  }
2170
- function nextFocusForKey(key, modifiers, focus, rowCount, columnCount, pageSize, isSkippableRow = noRowsSkippable) {
2821
+ function nextFocusForKey(key, modifiers, focus, rowCount, columnCount2, pageSize, isSkippableRow = noRowsSkippable) {
2171
2822
  if (modifiers.altKey) {
2172
2823
  return null;
2173
2824
  }
@@ -2214,8 +2865,8 @@ function nextFocusForKey(key, modifiers, focus, rowCount, columnCount, pageSize,
2214
2865
  isSkippableRow,
2215
2866
  rowIndex
2216
2867
  ),
2217
- columnIndex: columnCount - 1
2218
- } : { rowIndex, columnIndex: columnCount - 1 };
2868
+ columnIndex: columnCount2 - 1
2869
+ } : { rowIndex, columnIndex: columnCount2 - 1 };
2219
2870
  case "PageUp":
2220
2871
  return {
2221
2872
  rowIndex: skipSkippableRows(
@@ -2278,6 +2929,139 @@ function paginationWindow(currentPage, pageCount, options) {
2278
2929
  return entries;
2279
2930
  }
2280
2931
 
2932
+ // src/util/columnChooser.ts
2933
+ function isHideable(column) {
2934
+ return column.column.hideable !== false;
2935
+ }
2936
+ function matchesSearch(label, search) {
2937
+ return label.toLowerCase().includes(search.toLowerCase());
2938
+ }
2939
+ function selectAllStateOf(states) {
2940
+ if (states.length > 0 && states.every(Boolean)) {
2941
+ return "checked";
2942
+ }
2943
+ return states.some(Boolean) ? "mixed" : "unchecked";
2944
+ }
2945
+ function resolveColumnChooser({
2946
+ columns,
2947
+ visibility,
2948
+ search = ""
2949
+ }) {
2950
+ const all = columns.map((column) => ({
2951
+ column,
2952
+ label: resolveColumnHeaderText(column.column),
2953
+ visible: isColumnVisible(column.column, visibility)
2954
+ }));
2955
+ const shownCount = all.filter(({ visible }) => visible).length;
2956
+ const matched = all.filter(({ label }) => matchesSearch(label, search));
2957
+ const entries = matched.map(
2958
+ ({ column, label, visible }) => ({
2959
+ columnId: column.id,
2960
+ label,
2961
+ visible,
2962
+ disabled: visible && (!isHideable(column) || shownCount === 1)
2963
+ })
2964
+ );
2965
+ const covered = matched.filter(({ column }) => isHideable(column));
2966
+ const selectAll2 = selectAllStateOf(covered.map(({ visible }) => visible));
2967
+ return {
2968
+ entries,
2969
+ selectAll: selectAll2,
2970
+ toggleAll: toggleAllOf(covered, selectAll2, shownCount)
2971
+ };
2972
+ }
2973
+ function toggleAllOf(covered, selectAll2, shownCount) {
2974
+ const show = selectAll2 !== "checked";
2975
+ const changing = covered.filter(({ visible }) => visible !== show).map(({ column }) => column.id);
2976
+ const columnIds = !show && changing.length === shownCount ? changing.slice(1) : changing;
2977
+ return columnIds.length === 0 ? null : { visible: show, columnIds };
2978
+ }
2979
+
2980
+ // src/messages.ts
2981
+ function columnCount(count, change) {
2982
+ return count === 1 ? `1 column ${change}` : `${String(count)} columns ${change}`;
2983
+ }
2984
+ function searchResults(count) {
2985
+ if (count === 0) {
2986
+ return "No rows found";
2987
+ }
2988
+ return count === 1 ? "1 row found" : `${String(count)} rows found`;
2989
+ }
2990
+ function selectedCount(count, target) {
2991
+ const noun = target === "rows" ? "row" : "column";
2992
+ if (count === 0) {
2993
+ return `No ${noun}s selected`;
2994
+ }
2995
+ return count === 1 ? `1 ${noun} selected` : `${String(count)} ${noun}s selected`;
2996
+ }
2997
+ var defaultMessages = {
2998
+ pagerLabel: "Pagination",
2999
+ previousPage: "Previous",
3000
+ nextPage: "Next",
3001
+ pageSizeLabel: "Rows per page",
3002
+ pageButtonLabel: (page) => `Page ${String(page)}`,
3003
+ pageStatus: (page, pageCount) => `Page ${String(page)} of ${String(pageCount)}`,
3004
+ loadingMore: "Loading more\u2026",
3005
+ booleanTrue: "true",
3006
+ booleanFalse: "false",
3007
+ blankGroupValue: "(blank)",
3008
+ groupByBarLabel: "Grouped by",
3009
+ groupByBarPlaceholder: "Drag a column here to group by it",
3010
+ groupByChipLabel: (columnName2, level, levelCount) => levelCount > 1 ? `${columnName2}, level ${String(level)} of ${String(levelCount)}` : columnName2,
3011
+ removeGroupBy: (columnName2) => `Stop grouping by ${columnName2}`,
3012
+ columnResized: (columnName2, width) => `${columnName2}, ${String(width)} pixels wide`,
3013
+ columnMoved: (columnName2, position, columnCount2) => `${columnName2}, column ${String(position)} of ${String(columnCount2)}`,
3014
+ columnSorted: (columnName2, direction, key, keyCount) => {
3015
+ const sorted = `${columnName2} sorted ${direction === "asc" ? "ascending" : "descending"}`;
3016
+ return keyCount > 1 ? `${sorted}, key ${String(key)} of ${String(keyCount)}` : sorted;
3017
+ },
3018
+ sortCleared: (columnName2) => `${columnName2}, sort cleared`,
3019
+ groupedBy: (columnName2, level, levelCount) => levelCount > 1 ? `Grouped by ${columnName2}, level ${String(level)} of ${String(levelCount)}` : `Grouped by ${columnName2}`,
3020
+ groupingRemoved: (columnName2) => `${columnName2}, grouping removed`,
3021
+ groupExpanded: "Group expanded",
3022
+ groupCollapsed: "Group collapsed",
3023
+ allGroupsExpanded: "All groups expanded",
3024
+ allGroupsCollapsed: "All groups collapsed",
3025
+ selectedCount,
3026
+ cellSelected: (columnName2, rowNumber) => `${columnName2}, row ${String(rowNumber)}, selected`,
3027
+ columnHidden: (columnName2) => `${columnName2}, column hidden`,
3028
+ columnShown: (columnName2) => `${columnName2}, column shown`,
3029
+ columnsHidden: (count) => columnCount(count, "hidden"),
3030
+ columnsShown: (count) => columnCount(count, "shown"),
3031
+ columnsAutoFitted: (count) => columnCount(count, "autofitted"),
3032
+ headerMenuLabel: (columnName2) => `${columnName2} column menu`,
3033
+ cellMenuLabel: "Row menu",
3034
+ groupMenuLabel: "Group menu",
3035
+ bodyMenuLabel: "Grid menu",
3036
+ sortAscending: "Sort ascending",
3037
+ sortDescending: "Sort descending",
3038
+ clearSort: "Clear sort",
3039
+ clearAllSorting: "Clear all sorting",
3040
+ groupByColumn: (columnName2) => `Group by ${columnName2}`,
3041
+ ungroupAll: "Ungroup all",
3042
+ expandAllGroups: "Expand all groups",
3043
+ collapseAllGroups: "Collapse all groups",
3044
+ hideColumn: "Hide column",
3045
+ autoFitColumn: "Autofit column",
3046
+ autoFitAllColumns: "Autofit all columns",
3047
+ selectAllRows: "Select all rows",
3048
+ clearSelection: "Clear selection",
3049
+ clearFilters: "Clear filters",
3050
+ chooseColumns: "Choose columns",
3051
+ toolbarLabel: "Grid toolbar",
3052
+ searchLabel: "Search",
3053
+ searchPlaceholder: "Search\u2026",
3054
+ moreToolbarItems: "More",
3055
+ searchResults,
3056
+ columnChooserLabel: "Columns",
3057
+ columnChooserSearchLabel: "Search columns",
3058
+ showAllColumns: "Show all",
3059
+ noMatchingColumns: "No matching columns"
3060
+ };
3061
+ function resolveMessages(messages) {
3062
+ return messages === void 0 ? defaultMessages : { ...defaultMessages, ...messages };
3063
+ }
3064
+
2281
3065
  // src/util/format.ts
2282
3066
  var DATE_FIELDS = {
2283
3067
  year: "numeric",
@@ -2464,7 +3248,7 @@ function describeColumnFilter(filter, columnId) {
2464
3248
  function isOfferable(value) {
2465
3249
  return typeof value === "string" || typeof value === "number" || typeof value === "boolean" || value instanceof Date;
2466
3250
  }
2467
- function matchesSearch(label, search) {
3251
+ function matchesSearch2(label, search) {
2468
3252
  return label.toLowerCase().includes(search.toLowerCase());
2469
3253
  }
2470
3254
  function columnValueOptions(rows, dataColumns, filter, columnId, query = {}) {
@@ -2486,7 +3270,7 @@ function columnValueOptions(rows, dataColumns, filter, columnId, query = {}) {
2486
3270
  if (isEmptyCell(value)) {
2487
3271
  blanks++;
2488
3272
  } else if (isOfferable(value)) {
2489
- const key = bucketKey(value);
3273
+ const key = filterValueKey(value, column.type);
2490
3274
  const bucket = buckets.get(key);
2491
3275
  if (bucket === void 0) {
2492
3276
  buckets.set(key, { value, row, count: 1 });
@@ -2496,22 +3280,27 @@ function columnValueOptions(rows, dataColumns, filter, columnId, query = {}) {
2496
3280
  }
2497
3281
  }
2498
3282
  const { search = "", limit, locale } = query;
3283
+ const messages = resolveMessages(query.messages);
2499
3284
  const options = [...buckets.values()].sort((a, b) => compareValues(a.value, b.value, column.type)).map(({ value, row, count }) => ({
2500
3285
  value,
2501
- label: formatCellValue(value, column, locale, row),
3286
+ label: formatCellValue(value, column, locale, row, messages),
2502
3287
  count
2503
- })).filter((option) => matchesSearch(option.label, search));
3288
+ })).filter((option) => matchesSearch2(option.label, search));
2504
3289
  const shown = limit === void 0 ? options : options.slice(0, limit);
2505
3290
  return {
2506
3291
  options: shown,
2507
3292
  total: options.length,
2508
3293
  truncated: shown.length < options.length,
2509
- blanks
3294
+ blanks,
3295
+ columnType: column.type
2510
3296
  };
2511
3297
  }
2512
- function everyOption(options) {
3298
+ function keyOf2(draft, value) {
3299
+ return filterValueKey(value, draft.columnType);
3300
+ }
3301
+ function everyOption(draft) {
2513
3302
  return new Map(
2514
- options.map((option) => [bucketKey(option.value), option.value])
3303
+ draft.options.map((option) => [keyOf2(draft, option.value), option.value])
2515
3304
  );
2516
3305
  }
2517
3306
  function initialFilterEditorDraft(shape, values) {
@@ -2520,17 +3309,18 @@ function initialFilterEditorDraft(shape, values) {
2520
3309
  options: values.options,
2521
3310
  blanks: values.blanks,
2522
3311
  search: "",
2523
- checked: everyOption(values.options),
3312
+ checked: everyOption(values),
2524
3313
  includeBlanks: values.blanks > 0,
2525
3314
  conditions: [],
2526
- combinator: "and"
3315
+ combinator: "and",
3316
+ columnType: values.columnType
2527
3317
  };
2528
3318
  switch (shape.kind) {
2529
3319
  case "values":
2530
3320
  return {
2531
3321
  ...draft,
2532
3322
  checked: new Map(
2533
- shape.values.filter((value) => !isEmptyCell(value)).map((value) => [bucketKey(value), value])
3323
+ shape.values.filter((value) => !isEmptyCell(value)).map((value) => [keyOf2(draft, value), value])
2534
3324
  ),
2535
3325
  includeBlanks: shape.includeBlanks
2536
3326
  };
@@ -2548,7 +3338,7 @@ function initialFilterEditorDraft(shape, values) {
2548
3338
  }
2549
3339
  function visibleFilterOptions(draft) {
2550
3340
  return draft.search === "" ? draft.options : draft.options.filter(
2551
- (option) => matchesSearch(option.label, draft.search)
3341
+ (option) => matchesSearch2(option.label, draft.search)
2552
3342
  );
2553
3343
  }
2554
3344
  function selectAllCoversBlanks(draft) {
@@ -2556,7 +3346,7 @@ function selectAllCoversBlanks(draft) {
2556
3346
  }
2557
3347
  function selectAllState(draft) {
2558
3348
  const states = visibleFilterOptions(draft).map(
2559
- (option) => draft.checked.has(bucketKey(option.value))
3349
+ (option) => draft.checked.has(keyOf2(draft, option.value))
2560
3350
  );
2561
3351
  if (selectAllCoversBlanks(draft)) {
2562
3352
  states.push(draft.includeBlanks);
@@ -2570,7 +3360,7 @@ function toggleAll(draft) {
2570
3360
  const check = selectAllState(draft) !== "checked";
2571
3361
  const checked = new Map(draft.checked);
2572
3362
  for (const option of visibleFilterOptions(draft)) {
2573
- const key = bucketKey(option.value);
3363
+ const key = keyOf2(draft, option.value);
2574
3364
  if (check) {
2575
3365
  checked.set(key, option.value);
2576
3366
  } else {
@@ -2589,7 +3379,7 @@ function filterEditorReducer(draft, action) {
2589
3379
  case "search":
2590
3380
  return { ...draft, search: action.search };
2591
3381
  case "toggleValue": {
2592
- const key = bucketKey(action.value);
3382
+ const key = keyOf2(draft, action.value);
2593
3383
  const checked = new Map(draft.checked);
2594
3384
  if (checked.has(key)) {
2595
3385
  checked.delete(key);
@@ -2613,7 +3403,7 @@ function filterEditorReducer(draft, action) {
2613
3403
  return {
2614
3404
  ...draft,
2615
3405
  search: "",
2616
- checked: everyOption(draft.options),
3406
+ checked: everyOption(draft),
2617
3407
  includeBlanks: draft.blanks > 0,
2618
3408
  conditions: []
2619
3409
  };
@@ -2621,7 +3411,7 @@ function filterEditorReducer(draft, action) {
2621
3411
  }
2622
3412
  function checksEverything(draft) {
2623
3413
  return draft.options.every(
2624
- (option) => draft.checked.has(bucketKey(option.value))
3414
+ (option) => draft.checked.has(keyOf2(draft, option.value))
2625
3415
  ) && (draft.includeBlanks || draft.blanks === 0);
2626
3416
  }
2627
3417
  function draftEntries(draft, columnId) {
@@ -2657,49 +3447,6 @@ function draftToFilter(draft, columnId, current) {
2657
3447
  return [...kept.slice(0, first), ...replacement, ...kept.slice(first)];
2658
3448
  }
2659
3449
 
2660
- // src/messages.ts
2661
- function selectedCount(count, target) {
2662
- const noun = target === "rows" ? "row" : "column";
2663
- if (count === 0) {
2664
- return `No ${noun}s selected`;
2665
- }
2666
- return count === 1 ? `1 ${noun} selected` : `${String(count)} ${noun}s selected`;
2667
- }
2668
- var defaultMessages = {
2669
- pagerLabel: "Pagination",
2670
- previousPage: "Previous",
2671
- nextPage: "Next",
2672
- pageSizeLabel: "Rows per page",
2673
- pageButtonLabel: (page) => `Page ${String(page)}`,
2674
- pageStatus: (page, pageCount) => `Page ${String(page)} of ${String(pageCount)}`,
2675
- loadingMore: "Loading more\u2026",
2676
- booleanTrue: "true",
2677
- booleanFalse: "false",
2678
- blankGroupValue: "(blank)",
2679
- groupByBarLabel: "Grouped by",
2680
- groupByBarPlaceholder: "Drag a column here to group by it",
2681
- groupByChipLabel: (columnName, level, levelCount) => levelCount > 1 ? `${columnName}, level ${String(level)} of ${String(levelCount)}` : columnName,
2682
- removeGroupBy: (columnName) => `Stop grouping by ${columnName}`,
2683
- columnResized: (columnName, width) => `${columnName}, ${String(width)} pixels wide`,
2684
- columnMoved: (columnName, position, columnCount) => `${columnName}, column ${String(position)} of ${String(columnCount)}`,
2685
- columnSorted: (columnName, direction, key, keyCount) => {
2686
- const sorted = `${columnName} sorted ${direction === "asc" ? "ascending" : "descending"}`;
2687
- return keyCount > 1 ? `${sorted}, key ${String(key)} of ${String(keyCount)}` : sorted;
2688
- },
2689
- sortCleared: (columnName) => `${columnName}, sort cleared`,
2690
- groupedBy: (columnName, level, levelCount) => levelCount > 1 ? `Grouped by ${columnName}, level ${String(level)} of ${String(levelCount)}` : `Grouped by ${columnName}`,
2691
- groupingRemoved: (columnName) => `${columnName}, grouping removed`,
2692
- groupExpanded: "Group expanded",
2693
- groupCollapsed: "Group collapsed",
2694
- allGroupsExpanded: "All groups expanded",
2695
- allGroupsCollapsed: "All groups collapsed",
2696
- selectedCount,
2697
- cellSelected: (columnName, rowNumber) => `${columnName}, row ${String(rowNumber)}, selected`
2698
- };
2699
- function resolveMessages(messages) {
2700
- return messages === void 0 ? defaultMessages : { ...defaultMessages, ...messages };
2701
- }
2702
-
2703
3450
  // src/util/virtualization.ts
2704
3451
  function visibleRange(heights, estimatedHeight, scrollTop, viewportHeight, overscan) {
2705
3452
  const rowCount = heights.length;
@@ -2752,6 +3499,7 @@ export {
2752
3499
  applySelectionIntent,
2753
3500
  beginColumnResize,
2754
3501
  buildKeyShortcuts,
3502
+ builtInCommands,
2755
3503
  clampColumnWidth,
2756
3504
  clampFocus,
2757
3505
  clearAllFilters,
@@ -2760,9 +3508,13 @@ export {
2760
3508
  columnValueOptions,
2761
3509
  compareValues,
2762
3510
  computeAggregates,
3511
+ contextMenuEntries,
3512
+ createCommandContext,
2763
3513
  createGridController,
2764
3514
  createGridViewResolver,
3515
+ defaultContextMenu,
2765
3516
  defaultMessages,
3517
+ defaultToolbar,
2766
3518
  defineColumnsFromRows,
2767
3519
  describeColumnFilter,
2768
3520
  diffSelection,
@@ -2772,14 +3524,17 @@ export {
2772
3524
  filterQueryFor,
2773
3525
  filterRows,
2774
3526
  fitColumnsToWidth,
3527
+ fitToolbarItems,
2775
3528
  formatAggregateValue,
2776
3529
  formatCellValue,
2777
3530
  getColumnId,
2778
3531
  groupRowId,
2779
3532
  groupRows,
3533
+ hasContextMenu,
2780
3534
  initialFilterEditorDraft,
2781
3535
  intentOf,
2782
3536
  isColumnVisible,
3537
+ isPinnedToolbarItem,
2783
3538
  isSameCell,
2784
3539
  matchesQuery,
2785
3540
  moveColumnBefore,
@@ -2787,23 +3542,30 @@ export {
2787
3542
  movesColumn,
2788
3543
  movesGroupBy,
2789
3544
  nextFocusForKey,
3545
+ nextMenuIndex,
2790
3546
  paginateRows,
2791
3547
  paginationWindow,
2792
3548
  resolveCell,
3549
+ resolveColumnChooser,
2793
3550
  resolveColumnConstraints,
2794
3551
  resolveColumnHeaderText,
2795
3552
  resolveColumnLabel,
2796
3553
  resolveColumnWidths,
2797
3554
  resolveColumns,
3555
+ resolveCommands,
2798
3556
  resolveDataColumns,
2799
3557
  resolveDropBefore,
2800
3558
  resolveGridView,
2801
3559
  resolveKeyboardDropTarget,
3560
+ resolveMenu,
2802
3561
  resolveMessages,
2803
3562
  resolveRowId,
2804
3563
  resolveRows,
2805
3564
  resolveShownRows,
3565
+ resolveToolbar,
2806
3566
  revertColumnSize,
3567
+ runCommand,
3568
+ searchTextOf,
2807
3569
  selectAll,
2808
3570
  selectAllState,
2809
3571
  selectCell,
@@ -2812,15 +3574,19 @@ export {
2812
3574
  serializeGridState,
2813
3575
  setColumnFilter,
2814
3576
  setColumnVisibility,
3577
+ setSearchText,
2815
3578
  sizeColumnToContent,
2816
3579
  sortDirectionFor,
2817
3580
  sortPriorityFor,
2818
3581
  sortRows,
3582
+ splitToolbarItems,
2819
3583
  toggleCellSelection,
2820
3584
  toggleColumnSort,
2821
3585
  toggleGroupExpansion,
2822
3586
  toggleSelection,
3587
+ toolbarEntries,
2823
3588
  totalColumnWidth,
3589
+ typeaheadIndex,
2824
3590
  visibleFilterOptions,
2825
3591
  visibleRange,
2826
3592
  withGroupAggregates