@gridkitjs/core 0.12.1 → 0.14.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
@@ -91,6 +91,9 @@ function buildKeyShortcuts(capabilities) {
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
93
  capabilities.groupable ? "Alt+ArrowDown" : "",
94
+ // Excel's own key for its filter dropdown is Alt+ArrowDown, which
95
+ // grouping already holds here; Shift sets the filter apart from it.
96
+ capabilities.filterable === true ? "Alt+Shift+ArrowDown" : "",
94
97
  // The ContextMenu key needs no mention: it says what it does on its face.
95
98
  capabilities.contextMenu === true ? "Shift+F10" : ""
96
99
  ].filter(Boolean).join(" ");
@@ -2185,6 +2188,1304 @@ function createCommandContext({
2185
2188
  };
2186
2189
  }
2187
2190
 
2191
+ // src/util/format.ts
2192
+ var DATE_FIELDS = {
2193
+ year: "numeric",
2194
+ month: "numeric",
2195
+ day: "numeric"
2196
+ };
2197
+ var TIME_FIELDS = {
2198
+ hour: "numeric",
2199
+ minute: "numeric",
2200
+ second: "numeric"
2201
+ };
2202
+ var DATE_TIME_FIELDS = {
2203
+ ...DATE_FIELDS,
2204
+ ...TIME_FIELDS
2205
+ };
2206
+ var CURRENCY_DIGITS = {
2207
+ minimumFractionDigits: 2,
2208
+ maximumFractionDigits: 2
2209
+ };
2210
+ var NO_FORMAT = {};
2211
+ var formatters = /* @__PURE__ */ new WeakMap();
2212
+ function cached(format, key, create) {
2213
+ const owner = format ?? NO_FORMAT;
2214
+ let byKey = formatters.get(owner);
2215
+ if (byKey === void 0) {
2216
+ byKey = /* @__PURE__ */ new Map();
2217
+ formatters.set(owner, byKey);
2218
+ }
2219
+ const existing = byKey.get(key);
2220
+ if (existing !== void 0) {
2221
+ return existing;
2222
+ }
2223
+ const formatter = create();
2224
+ byKey.set(key, formatter);
2225
+ return formatter;
2226
+ }
2227
+ function localeKey(locale) {
2228
+ return typeof locale === "string" ? locale : locale?.join(",") ?? "";
2229
+ }
2230
+ function isNumericType(type) {
2231
+ return type === "number" || type === "decimal" || type === "currency" || type === "percent";
2232
+ }
2233
+ function numberOptions(type, format) {
2234
+ switch (type) {
2235
+ case "currency":
2236
+ return format?.currency === void 0 ? { ...CURRENCY_DIGITS, ...format } : { style: "currency", ...format };
2237
+ case "percent":
2238
+ return { style: "percent", ...format };
2239
+ default:
2240
+ return format ?? {};
2241
+ }
2242
+ }
2243
+ function dateOptions(type, format) {
2244
+ if (format !== void 0) {
2245
+ return format;
2246
+ }
2247
+ if (type === "date") return DATE_FIELDS;
2248
+ if (type === "time") return TIME_FIELDS;
2249
+ return DATE_TIME_FIELDS;
2250
+ }
2251
+ function formatNumber(value, type, format, locale) {
2252
+ const formatter = cached(
2253
+ format,
2254
+ `number|${type ?? ""}|${localeKey(locale)}`,
2255
+ () => new Intl.NumberFormat(
2256
+ locale,
2257
+ numberOptions(type, format)
2258
+ )
2259
+ );
2260
+ return formatter.format(value);
2261
+ }
2262
+ function formatDate(value, type, format, locale) {
2263
+ if (Number.isNaN(value.getTime())) {
2264
+ return String(value);
2265
+ }
2266
+ const formatter = cached(
2267
+ format,
2268
+ `date|${type ?? ""}|${localeKey(locale)}`,
2269
+ () => new Intl.DateTimeFormat(
2270
+ locale,
2271
+ dateOptions(type, format)
2272
+ )
2273
+ );
2274
+ return formatter.format(value);
2275
+ }
2276
+ function formatOther(value) {
2277
+ if (typeof value === "function") {
2278
+ return "(function)";
2279
+ }
2280
+ if (typeof value === "object" && value !== null) {
2281
+ try {
2282
+ return JSON.stringify(value);
2283
+ } catch {
2284
+ return Object.prototype.toString.call(value);
2285
+ }
2286
+ }
2287
+ return String(value);
2288
+ }
2289
+ function formatCellValue(value, column, locale, row, messages) {
2290
+ if (column.valueFormatter !== void 0) {
2291
+ return column.valueFormatter(value, row);
2292
+ }
2293
+ if (value === null || value === void 0) {
2294
+ return "";
2295
+ }
2296
+ if (typeof value === "boolean" && messages !== void 0) {
2297
+ return value ? messages.booleanTrue : messages.booleanFalse;
2298
+ }
2299
+ const { type, format } = column;
2300
+ const formatted = format !== void 0 || locale !== void 0;
2301
+ if (value instanceof Date) {
2302
+ return formatted ? formatDate(value, type, format, locale) : value.toLocaleString();
2303
+ }
2304
+ if (formatted && (typeof value === "number" || typeof value === "bigint") && isNumericType(type)) {
2305
+ return formatNumber(value, type, format, locale);
2306
+ }
2307
+ if (typeof value === "string") {
2308
+ return value;
2309
+ }
2310
+ return formatOther(value);
2311
+ }
2312
+ function aggregateFormatColumn(value, spec, column) {
2313
+ const plain = {
2314
+ field: spec.columnId,
2315
+ type: "number"
2316
+ };
2317
+ if (column === void 0 || spec.fn === "count" || spec.fn === "countDistinct") {
2318
+ return plain;
2319
+ }
2320
+ const numeric = typeof value === "number" || typeof value === "bigint";
2321
+ return column.valueFormatter === void 0 && numeric && !isNumericType(column.type) ? plain : column;
2322
+ }
2323
+ function formatAggregateValue(value, spec, column, locale) {
2324
+ const target = aggregateFormatColumn(value, spec, column);
2325
+ if (typeof value === "number" && target.valueFormatter === void 0 && target.format === void 0 && locale === void 0) {
2326
+ return value.toLocaleString();
2327
+ }
2328
+ return formatCellValue(value, target, locale);
2329
+ }
2330
+ function formatGroupValue(value, column, locale, messages) {
2331
+ return value === null || value === void 0 ? messages.blankGroupValue : formatCellValue(value, column, locale, void 0, messages);
2332
+ }
2333
+
2334
+ // src/export/buildExportTable.ts
2335
+ var EMPTY_CELL = { value: null, text: "" };
2336
+ function isDataRow(entry) {
2337
+ return !("kind" in entry);
2338
+ }
2339
+ function pickColumns(view, selection) {
2340
+ let picked;
2341
+ if (selection === "visible") {
2342
+ picked = view.columns;
2343
+ } else if (selection === "all") {
2344
+ picked = view.dataColumns;
2345
+ } else {
2346
+ const byId = new Map(view.dataColumns.map((entry) => [entry.id, entry]));
2347
+ picked = [...new Set(selection)].flatMap((id) => byId.get(id) ?? []);
2348
+ }
2349
+ return picked.filter((entry) => entry.column.exportable !== false);
2350
+ }
2351
+ function describeColumns(picked, source) {
2352
+ const shown = new Map(source.view.columns.map((entry) => [entry.id, entry]));
2353
+ const resolved = resolveColumnWidths(
2354
+ picked.map((entry) => entry.column),
2355
+ source.state.columnSizing,
2356
+ { sizes: source.options.columnSizeDefaults }
2357
+ );
2358
+ return resolved.map((entry) => {
2359
+ const rendered = shown.get(entry.id) ?? entry;
2360
+ return {
2361
+ id: rendered.id,
2362
+ header: resolveColumnHeaderText(rendered.column),
2363
+ type: rendered.column.type ?? "string",
2364
+ format: rendered.column.format,
2365
+ alignment: rendered.alignment,
2366
+ width: rendered.width
2367
+ };
2368
+ });
2369
+ }
2370
+ function scopedRows(source, scope) {
2371
+ const { view, state } = source;
2372
+ switch (scope) {
2373
+ case "all":
2374
+ return view.rows;
2375
+ case "page":
2376
+ return view.displayDataRows;
2377
+ case "selection": {
2378
+ const selected = new Set(state.rowSelection);
2379
+ return view.rows.filter((entry) => selected.has(entry.rowId));
2380
+ }
2381
+ }
2382
+ }
2383
+ function typedValue(value, text) {
2384
+ if (value === null || value === void 0) {
2385
+ return null;
2386
+ }
2387
+ return typeof value === "string" || typeof value === "number" || typeof value === "boolean" || value instanceof Date ? value : text;
2388
+ }
2389
+ function dataCell(row, column, context) {
2390
+ const raw = accessDotted(row, column.field);
2391
+ const text = formatCellValue(
2392
+ raw,
2393
+ column,
2394
+ context.locale,
2395
+ row,
2396
+ context.messages
2397
+ );
2398
+ return {
2399
+ value: column.exportValue === void 0 ? typedValue(raw, text) : column.exportValue(raw, row),
2400
+ text
2401
+ };
2402
+ }
2403
+ function aggregateCell(results, specs, column, locale) {
2404
+ const parts = specs.flatMap((spec) => {
2405
+ const value = results.get(spec.id ?? spec.columnId);
2406
+ return value === null || value === void 0 ? [] : [{ value, text: formatAggregateValue(value, spec, column, locale) }];
2407
+ });
2408
+ const [only] = parts;
2409
+ if (only === void 0) {
2410
+ return EMPTY_CELL;
2411
+ }
2412
+ if (parts.length === 1) {
2413
+ return { value: typedValue(only.value, only.text), text: only.text };
2414
+ }
2415
+ const text = parts.map((part) => part.text).join(", ");
2416
+ return { value: text, text };
2417
+ }
2418
+ function aggregateCells(results, context) {
2419
+ return context.columns.map((entry) => {
2420
+ const specs = context.specsByColumn.get(entry.id);
2421
+ return specs === void 0 ? EMPTY_CELL : aggregateCell(results, specs, entry.column, context.locale);
2422
+ });
2423
+ }
2424
+ function groupLabel(entry, source) {
2425
+ const column = source.view.dataColumns.find(
2426
+ (candidate) => candidate.id === entry.columnId
2427
+ )?.column ?? { field: entry.columnId };
2428
+ const value = formatGroupValue(
2429
+ entry.value,
2430
+ column,
2431
+ source.options.locale,
2432
+ source.messages
2433
+ );
2434
+ return `${resolveColumnHeaderText(column)}: ${value}`;
2435
+ }
2436
+ function toExportRow(entry, source, context) {
2437
+ if (isDataRow(entry)) {
2438
+ return {
2439
+ kind: "data",
2440
+ rowId: entry.rowId,
2441
+ cells: context.columns.map(
2442
+ (column) => dataCell(entry.row, column.column, context)
2443
+ )
2444
+ };
2445
+ }
2446
+ if (entry.kind === "group") {
2447
+ return {
2448
+ kind: "group",
2449
+ level: entry.level,
2450
+ groupId: entry.groupId,
2451
+ label: groupLabel(entry, source),
2452
+ count: entry.count,
2453
+ cells: context.display === "inline" ? aggregateCells(entry.aggregates, context) : context.columns.map(() => EMPTY_CELL)
2454
+ };
2455
+ }
2456
+ return {
2457
+ kind: "groupSummary",
2458
+ level: entry.level,
2459
+ cells: aggregateCells(entry.aggregates, context)
2460
+ };
2461
+ }
2462
+ function specsByColumnOf(specs) {
2463
+ const byColumn = /* @__PURE__ */ new Map();
2464
+ for (const spec of specs) {
2465
+ const owned = byColumn.get(spec.columnId);
2466
+ if (owned === void 0) {
2467
+ byColumn.set(spec.columnId, [spec]);
2468
+ } else {
2469
+ owned.push(spec);
2470
+ }
2471
+ }
2472
+ return byColumn;
2473
+ }
2474
+ function buildExportTable(source, options = {}) {
2475
+ const { view, state } = source;
2476
+ const specs = options.includeAggregates ?? true ? source.options.aggregates ?? [] : [];
2477
+ const columns = pickColumns(view, options.columns ?? "visible");
2478
+ const context = {
2479
+ columns,
2480
+ specsByColumn: specsByColumnOf(specs),
2481
+ display: source.options.groupAggregateDisplay ?? "inline",
2482
+ locale: source.options.locale,
2483
+ messages: source.messages
2484
+ };
2485
+ const rows = scopedRows(source, options.scope ?? "all");
2486
+ const grouped = groupRows(rows, state.groupBy, [], view.dataColumns);
2487
+ const displayRows = options.includeGroups ?? true ? withGroupAggregates(
2488
+ grouped,
2489
+ rows,
2490
+ state.groupBy,
2491
+ specs,
2492
+ view.dataColumns,
2493
+ context.display
2494
+ ) : grouped.filter(isDataRow);
2495
+ let footer = null;
2496
+ if (specs.length > 0) {
2497
+ const totals = rows === view.rows ? view.grandTotal : computeAggregates(
2498
+ rows.map((entry) => entry.row),
2499
+ specs,
2500
+ view.dataColumns
2501
+ );
2502
+ footer = { kind: "total", cells: aggregateCells(totals, context) };
2503
+ }
2504
+ return {
2505
+ columns: describeColumns(columns, source),
2506
+ header: options.includeHeader ?? true,
2507
+ rows: displayRows.map((entry) => toExportRow(entry, source, context)),
2508
+ footer
2509
+ };
2510
+ }
2511
+
2512
+ // src/export/htmlTable.ts
2513
+ var ENTITIES = {
2514
+ "&": "&",
2515
+ "<": "&lt;",
2516
+ ">": "&gt;",
2517
+ '"': "&quot;",
2518
+ "'": "&#39;"
2519
+ };
2520
+ function escapeHtml(text) {
2521
+ return text.replace(/[&<>"']/g, (char) => ENTITIES[char] ?? char);
2522
+ }
2523
+ function cellHtml(tag, text, column) {
2524
+ const alignment = column?.alignment ?? "left";
2525
+ const style = alignment === "left" ? "" : ` style="text-align:${alignment}"`;
2526
+ return `<${tag}${style}>${escapeHtml(text)}</${tag}>`;
2527
+ }
2528
+ function cellsHtml(cells, columns) {
2529
+ return cells.map((cell, index) => cellHtml("td", cell.text, columns[index])).join("");
2530
+ }
2531
+ function groupRowHtml(row, columns) {
2532
+ const firstAggregate = row.cells.findIndex((cell) => cell.text !== "");
2533
+ const span = Math.max(
2534
+ 1,
2535
+ firstAggregate === -1 ? row.cells.length : firstAggregate
2536
+ );
2537
+ const colspan = span > 1 ? ` colspan="${String(span)}"` : "";
2538
+ const label = `<td${colspan}>${escapeHtml(row.label)}</td>`;
2539
+ const aggregates = row.cells.slice(span).map((cell, index) => cellHtml("td", cell.text, columns[span + index])).join("");
2540
+ return `<tr class="export-group" data-level="${String(row.level)}">${label}${aggregates}</tr>`;
2541
+ }
2542
+ function rowHtml(row, columns) {
2543
+ switch (row.kind) {
2544
+ case "data":
2545
+ return `<tr>${cellsHtml(row.cells, columns)}</tr>`;
2546
+ case "group":
2547
+ return groupRowHtml(row, columns);
2548
+ case "groupSummary":
2549
+ return `<tr class="export-group-summary" data-level="${String(row.level)}">${cellsHtml(row.cells, columns)}</tr>`;
2550
+ }
2551
+ }
2552
+ function toHtmlTable(table, options = {}) {
2553
+ const { columns } = table;
2554
+ const caption = options.caption === void 0 ? "" : `<caption>${escapeHtml(options.caption)}</caption>`;
2555
+ const head = table.header ? `<thead><tr>${columns.map((column) => cellHtml("th", column.header, column)).join("")}</tr></thead>` : "";
2556
+ const body = `<tbody>${table.rows.map((row) => rowHtml(row, columns)).join("")}</tbody>`;
2557
+ const foot = table.footer === null ? "" : `<tfoot><tr>${cellsHtml(table.footer.cells, columns)}</tr></tfoot>`;
2558
+ return `<table class="gridkit-export-table">${caption}${head}${body}${foot}</table>`;
2559
+ }
2560
+
2561
+ // src/export/records.ts
2562
+ function rawText(value) {
2563
+ if (value === null) {
2564
+ return "";
2565
+ }
2566
+ if (value instanceof Date) {
2567
+ return Number.isNaN(value.getTime()) ? "" : value.toISOString();
2568
+ }
2569
+ return String(value);
2570
+ }
2571
+ function cellField(cell, values) {
2572
+ return {
2573
+ text: values === "raw" ? rawText(cell.value) : cell.text,
2574
+ isNumber: typeof cell.value === "number"
2575
+ };
2576
+ }
2577
+ function textField(text) {
2578
+ return { text, isNumber: false };
2579
+ }
2580
+ function textRecords(table, values) {
2581
+ const records = [];
2582
+ if (table.header) {
2583
+ records.push(table.columns.map((column) => textField(column.header)));
2584
+ }
2585
+ for (const row of table.rows) {
2586
+ records.push(
2587
+ row.cells.map(
2588
+ (cell, index) => row.kind === "group" && index === 0 ? textField(row.label) : cellField(cell, values)
2589
+ )
2590
+ );
2591
+ }
2592
+ if (table.footer !== null) {
2593
+ records.push(table.footer.cells.map((cell) => cellField(cell, values)));
2594
+ }
2595
+ return records;
2596
+ }
2597
+
2598
+ // src/export/tsv.ts
2599
+ var SEPARATORS = /\r\n|[\t\r\n]/g;
2600
+ function toTsv(table, options = {}) {
2601
+ return textRecords(table, options.values ?? "text").map(
2602
+ (record) => record.map((field) => field.text.replace(SEPARATORS, " ")).join(" ")
2603
+ ).join("\n");
2604
+ }
2605
+
2606
+ // src/export/copy.ts
2607
+ function isCopyable(column) {
2608
+ return column.column.exportable !== false;
2609
+ }
2610
+ function hasRow(source, rowId) {
2611
+ return source.view.rows.some((entry) => entry.rowId === rowId);
2612
+ }
2613
+ function hasColumn(source, columnId) {
2614
+ return source.view.columns.some(
2615
+ (entry) => entry.id === columnId && isCopyable(entry)
2616
+ );
2617
+ }
2618
+ function selectionCopy(source) {
2619
+ const { state, view } = source;
2620
+ const selectedRows = new Set(state.rowSelection);
2621
+ const count = view.rows.filter(
2622
+ (entry) => selectedRows.has(entry.rowId)
2623
+ ).length;
2624
+ if (count > 0) {
2625
+ return { kind: "rows", count };
2626
+ }
2627
+ const selectedColumns = new Set(state.columnSelection);
2628
+ const columnIds = view.columns.filter((entry) => selectedColumns.has(entry.id) && isCopyable(entry)).map((entry) => entry.id);
2629
+ if (columnIds.length > 0 && view.rows.length > 0) {
2630
+ return { kind: "columns", columnIds };
2631
+ }
2632
+ const cell = state.cellSelection;
2633
+ if (cell !== null && hasRow(source, cell.rowId) && hasColumn(source, cell.columnId)) {
2634
+ return { kind: "cell", rowId: cell.rowId, columnId: cell.columnId };
2635
+ }
2636
+ return null;
2637
+ }
2638
+ function canCopySelection(source) {
2639
+ return selectionCopy(source) !== null;
2640
+ }
2641
+ function withRows(source, rowIds) {
2642
+ return { ...source, state: { ...source.state, rowSelection: rowIds } };
2643
+ }
2644
+ function contentOf(source, { scope, columns, header }, unit) {
2645
+ const table = buildExportTable(source, {
2646
+ scope,
2647
+ columns: columns ?? "visible",
2648
+ includeHeader: header,
2649
+ includeGroups: false,
2650
+ includeAggregates: false
2651
+ });
2652
+ if (table.columns.length === 0 || table.rows.length === 0) {
2653
+ return null;
2654
+ }
2655
+ return {
2656
+ text: toTsv(table),
2657
+ html: toHtmlTable(table),
2658
+ count: unit === "columns" ? table.columns.length : table.rows.length,
2659
+ unit
2660
+ };
2661
+ }
2662
+ function cellContent(source, rowId, columnId) {
2663
+ if (!hasRow(source, rowId) || !hasColumn(source, columnId)) {
2664
+ return null;
2665
+ }
2666
+ return contentOf(
2667
+ withRows(source, [rowId]),
2668
+ { scope: "selection", columns: [columnId], header: false },
2669
+ "cells"
2670
+ );
2671
+ }
2672
+ function rowContent(source, rowId) {
2673
+ if (!hasRow(source, rowId)) {
2674
+ return null;
2675
+ }
2676
+ return contentOf(
2677
+ withRows(source, [rowId]),
2678
+ { scope: "selection", header: false },
2679
+ "rows"
2680
+ );
2681
+ }
2682
+ function selectionContent(source) {
2683
+ const selection = selectionCopy(source);
2684
+ switch (selection?.kind) {
2685
+ case void 0:
2686
+ return null;
2687
+ case "rows":
2688
+ return contentOf(
2689
+ source,
2690
+ { scope: "selection", header: selection.count > 1 },
2691
+ "rows"
2692
+ );
2693
+ case "columns":
2694
+ return contentOf(
2695
+ source,
2696
+ { scope: "all", columns: selection.columnIds, header: true },
2697
+ "columns"
2698
+ );
2699
+ case "cell":
2700
+ return cellContent(source, selection.rowId, selection.columnId);
2701
+ }
2702
+ }
2703
+ function copyContent(source, request) {
2704
+ switch (request.kind) {
2705
+ case "cell":
2706
+ return cellContent(source, request.rowId, request.columnId);
2707
+ case "row":
2708
+ return rowContent(source, request.rowId);
2709
+ case "selection":
2710
+ return selectionContent(source);
2711
+ }
2712
+ }
2713
+
2714
+ // src/export/exportFile.ts
2715
+ function withExtension(fileName, extension) {
2716
+ const suffix = `.${extension}`;
2717
+ return fileName.toLowerCase().endsWith(suffix.toLowerCase()) ? fileName : fileName + suffix;
2718
+ }
2719
+ function writerFor(format, writers) {
2720
+ if (typeof format !== "string") {
2721
+ return format;
2722
+ }
2723
+ const writer = writers.get(format);
2724
+ if (writer === void 0) {
2725
+ reportError(
2726
+ `there is no export writer for \`${format}\` \u2014 register one through \`exportWriters\`.`
2727
+ );
2728
+ return null;
2729
+ }
2730
+ return writer;
2731
+ }
2732
+ function fileNameFor(options, writer) {
2733
+ const { fileName = "export" } = options;
2734
+ const name = typeof fileName === "function" ? fileName(writer.format) : fileName;
2735
+ return withExtension(name, writer.fileExtension);
2736
+ }
2737
+ function exportFile(source, format, options, writers) {
2738
+ const writer = writerFor(format, writers);
2739
+ if (writer === null) {
2740
+ return null;
2741
+ }
2742
+ const content = writer.write(buildExportTable(source, options), {
2743
+ locale: source.options.locale,
2744
+ messages: source.messages,
2745
+ options
2746
+ });
2747
+ const fileName = fileNameFor(options, writer);
2748
+ const { mimeType } = writer;
2749
+ return content instanceof Promise ? content.then((resolved) => ({ fileName, mimeType, content: resolved })) : { fileName, mimeType, content };
2750
+ }
2751
+
2752
+ // src/export/print.ts
2753
+ var PORTRAIT_COLUMN_LIMIT = 6;
2754
+ function resolveOrientation(table, options) {
2755
+ return options.orientation ?? (table.columns.length > PORTRAIT_COLUMN_LIMIT ? "landscape" : "portrait");
2756
+ }
2757
+ function timestampText(timestamp, locale) {
2758
+ const date = timestamp === true ? /* @__PURE__ */ new Date() : timestamp;
2759
+ return new Intl.DateTimeFormat(locale, {
2760
+ dateStyle: "medium",
2761
+ timeStyle: "short"
2762
+ }).format(date);
2763
+ }
2764
+ function line(tag, className, text) {
2765
+ return text === void 0 || text === "" ? "" : `<${tag} class="${className}">${escapeHtml(text)}</${tag}>`;
2766
+ }
2767
+ function headerHtml(options) {
2768
+ const { timestamp = false } = options;
2769
+ const lines = line("h1", "print-title", options.title) + line("p", "print-subtitle", options.subtitle) + (timestamp === false ? "" : line("p", "print-timestamp", timestampText(timestamp, options.locale)));
2770
+ return lines === "" ? "" : `<header class="gridkit-print-header">${lines}</header>`;
2771
+ }
2772
+ function buildPrintDocument(table, options = {}) {
2773
+ const footer = line("footer", "gridkit-print-footer", options.footer);
2774
+ return {
2775
+ title: options.title ?? "",
2776
+ bodyHtml: `${headerHtml(options)}${toHtmlTable(table)}${footer}`,
2777
+ page: {
2778
+ paperSize: options.paperSize ?? "A4",
2779
+ orientation: resolveOrientation(table, options),
2780
+ pageNumbers: options.pageNumbers ?? true,
2781
+ fitToWidth: options.fitToWidth ?? false
2782
+ }
2783
+ };
2784
+ }
2785
+ function exportPrintDocument(source, options = {}) {
2786
+ return buildPrintDocument(buildExportTable(source, options), {
2787
+ ...options,
2788
+ title: options.title ?? source.options.label,
2789
+ locale: options.locale ?? source.options.locale
2790
+ });
2791
+ }
2792
+
2793
+ // src/export/csv.ts
2794
+ var BOM = "\uFEFF";
2795
+ var FORMULA_START = /^[=+\-@\t\r]/;
2796
+ var SIGN_START = /^[+-]/;
2797
+ var NEEDS_QUOTES = /["\r\n]/;
2798
+ function autoDelimiter(locale) {
2799
+ const decimal = new Intl.NumberFormat(locale).formatToParts(1.5).find((part) => part.type === "decimal")?.value;
2800
+ return decimal === "," ? ";" : ",";
2801
+ }
2802
+ function neutralize(field) {
2803
+ const { text } = field;
2804
+ if (!FORMULA_START.test(text) || field.isNumber && SIGN_START.test(text)) {
2805
+ return text;
2806
+ }
2807
+ return `'${text}`;
2808
+ }
2809
+ function quote(text, delimiter) {
2810
+ return text.includes(delimiter) || NEEDS_QUOTES.test(text) ? `"${text.replaceAll('"', '""')}"` : text;
2811
+ }
2812
+ function toCsv(table, options = {}) {
2813
+ const delimiter = options.delimiter === "auto" ? autoDelimiter(options.locale) : options.delimiter ?? ",";
2814
+ const guard = options.neutralizeFormulas ?? true;
2815
+ const lines = textRecords(table, options.values ?? "text").map(
2816
+ (record) => record.map((field) => quote(guard ? neutralize(field) : field.text, delimiter)).join(delimiter)
2817
+ );
2818
+ return (options.bom ?? true ? BOM : "") + lines.join(options.eol ?? "\r\n");
2819
+ }
2820
+
2821
+ // src/export/json.ts
2822
+ function uniqueNames(names) {
2823
+ const used = /* @__PURE__ */ new Set();
2824
+ return names.map((name) => {
2825
+ let unique = name;
2826
+ for (let n = 2; used.has(unique); n += 1) {
2827
+ unique = `${name} (${String(n)})`;
2828
+ }
2829
+ used.add(unique);
2830
+ return unique;
2831
+ });
2832
+ }
2833
+ function toJson(table, options = {}) {
2834
+ const keys = options.keys === "header" ? uniqueNames(table.columns.map((column) => column.header)) : table.columns.map((column) => column.id);
2835
+ const records = table.rows.flatMap(
2836
+ (row) => row.kind === "data" ? [
2837
+ Object.fromEntries(
2838
+ keys.map((key, index) => [key, row.cells[index]?.value ?? null])
2839
+ )
2840
+ ] : []
2841
+ );
2842
+ return JSON.stringify(records, null, options.space);
2843
+ }
2844
+
2845
+ // src/export/writer.ts
2846
+ var csvWriter = {
2847
+ format: "csv",
2848
+ fileExtension: "csv",
2849
+ mimeType: "text/csv;charset=utf-8",
2850
+ write: (table, { locale, options }) => toCsv(table, { locale, ...options.csv })
2851
+ };
2852
+ var jsonWriter = {
2853
+ format: "json",
2854
+ fileExtension: "json",
2855
+ mimeType: "application/json",
2856
+ write: (table, { options }) => toJson(table, options.json)
2857
+ };
2858
+ function resolveExportWriters(writers) {
2859
+ return new Map(
2860
+ [csvWriter, jsonWriter, ...writers ?? []].map((writer) => [
2861
+ writer.format,
2862
+ writer
2863
+ ])
2864
+ );
2865
+ }
2866
+
2867
+ // src/filterMenu/operators.ts
2868
+ function filterOperatorFamily(type) {
2869
+ return kindOf(type);
2870
+ }
2871
+ var familyOperators = {
2872
+ text: [
2873
+ "contains",
2874
+ "notContains",
2875
+ "eq",
2876
+ "ne",
2877
+ "startsWith",
2878
+ "endsWith",
2879
+ "isEmpty",
2880
+ "isNotEmpty"
2881
+ ],
2882
+ number: [
2883
+ "eq",
2884
+ "ne",
2885
+ "gt",
2886
+ "gte",
2887
+ "lt",
2888
+ "lte",
2889
+ "between",
2890
+ "isEmpty",
2891
+ "isNotEmpty"
2892
+ ],
2893
+ date: ["eq", "ne", "lt", "gt", "between", "isEmpty", "isNotEmpty"],
2894
+ boolean: ["eq", "isEmpty", "isNotEmpty"]
2895
+ };
2896
+ var instantOperators = [
2897
+ "lt",
2898
+ "gt",
2899
+ "between",
2900
+ "isEmpty",
2901
+ "isNotEmpty"
2902
+ ];
2903
+ function defaultFilterOperators(type) {
2904
+ return type === "dateTime" || type === "time" ? instantOperators : familyOperators[filterOperatorFamily(type)];
2905
+ }
2906
+ function filterOperandArity(operator) {
2907
+ switch (operator) {
2908
+ case "isEmpty":
2909
+ case "isNotEmpty":
2910
+ return 0;
2911
+ case "between":
2912
+ return 2;
2913
+ case "eq":
2914
+ case "ne":
2915
+ case "contains":
2916
+ case "notContains":
2917
+ case "startsWith":
2918
+ case "endsWith":
2919
+ case "gt":
2920
+ case "gte":
2921
+ case "lt":
2922
+ case "lte":
2923
+ case "in":
2924
+ return 1;
2925
+ }
2926
+ }
2927
+ var NUMBER_INPUT = /^[+-]?(?:\d+\.?\d*|\.\d+)(?:e[+-]?\d+)?$/i;
2928
+ var DATE_INPUT = /^(\d{4})-(\d{2})-(\d{2})(?:T(\d{2}):(\d{2})(?::(\d{2})(?:\.(\d{1,3}))?)?)?$/;
2929
+ function parseNumber(text) {
2930
+ const value = Number(text);
2931
+ return NUMBER_INPUT.test(text) && Number.isFinite(value) ? value : void 0;
2932
+ }
2933
+ function parseDate(text) {
2934
+ const match = DATE_INPUT.exec(text);
2935
+ if (match === null) {
2936
+ return void 0;
2937
+ }
2938
+ const [
2939
+ ,
2940
+ year,
2941
+ month,
2942
+ day,
2943
+ hours = "0",
2944
+ minutes = "0",
2945
+ seconds = "0",
2946
+ fraction = ""
2947
+ ] = match;
2948
+ if (Number(hours) > 23 || Number(minutes) > 59 || Number(seconds) > 59) {
2949
+ return void 0;
2950
+ }
2951
+ const date = new Date(
2952
+ Number(year),
2953
+ Number(month) - 1,
2954
+ Number(day),
2955
+ Number(hours),
2956
+ Number(minutes),
2957
+ Number(seconds),
2958
+ Number(fraction.padEnd(3, "0"))
2959
+ );
2960
+ return date.getFullYear() === Number(year) && date.getMonth() === Number(month) - 1 && date.getDate() === Number(day) ? date : void 0;
2961
+ }
2962
+ function parseBoolean(text) {
2963
+ if (text === "true") {
2964
+ return true;
2965
+ }
2966
+ return text === "false" ? false : void 0;
2967
+ }
2968
+ function parseFilterOperand(text, type) {
2969
+ if (text === "") {
2970
+ return void 0;
2971
+ }
2972
+ switch (filterOperatorFamily(type)) {
2973
+ case "text":
2974
+ return text;
2975
+ case "number":
2976
+ return parseNumber(text.trim());
2977
+ case "date":
2978
+ return parseDate(text.trim());
2979
+ case "boolean":
2980
+ return parseBoolean(text.trim());
2981
+ }
2982
+ }
2983
+ function pad(value, width = 2) {
2984
+ return String(value).padStart(width, "0");
2985
+ }
2986
+ function dayInput(date) {
2987
+ return `${pad(date.getFullYear(), 4)}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}`;
2988
+ }
2989
+ function momentInput(date) {
2990
+ const minute = `${dayInput(date)}T${pad(date.getHours())}:${pad(date.getMinutes())}`;
2991
+ const seconds = date.getSeconds();
2992
+ const milliseconds = date.getMilliseconds();
2993
+ if (seconds === 0 && milliseconds === 0) {
2994
+ return minute;
2995
+ }
2996
+ const second = `${minute}:${pad(seconds)}`;
2997
+ return milliseconds === 0 ? second : `${second}.${pad(milliseconds, 3)}`;
2998
+ }
2999
+ function formatFilterOperand(value, type) {
3000
+ if (value === null || value === void 0) {
3001
+ return "";
3002
+ }
3003
+ const date = filterOperatorFamily(type) === "date" ? toDate(value) : void 0;
3004
+ if (date === void 0) {
3005
+ return String(value);
3006
+ }
3007
+ return type === "date" ? dayInput(date) : momentInput(date);
3008
+ }
3009
+
3010
+ // src/filterMenu/options.ts
3011
+ var DEFAULT_FILTER_VALUE_LIMIT = 1e4;
3012
+ function conditionOperators(operators) {
3013
+ return operators.includes("in") ? operators.filter((operator) => operator !== "in") : operators;
3014
+ }
3015
+ function resolveColumnFilterMenu(options, column) {
3016
+ if (!(column.filterable ?? options.filterableColumns ?? false)) {
3017
+ return null;
3018
+ }
3019
+ const grid = options.filterMenu;
3020
+ const own = column.filterMenu;
3021
+ return {
3022
+ ui: own?.ui ?? grid?.ui ?? "simple",
3023
+ operators: conditionOperators(
3024
+ own?.operators ?? defaultFilterOperators(column.type)
3025
+ ),
3026
+ family: filterOperatorFamily(column.type),
3027
+ showCounts: grid?.showCounts ?? false,
3028
+ valueLimit: grid?.valueLimit ?? DEFAULT_FILTER_VALUE_LIMIT
3029
+ };
3030
+ }
3031
+
3032
+ // src/messages.ts
3033
+ function localeKey2(locale) {
3034
+ return typeof locale === "string" ? locale : locale?.join(",") ?? "";
3035
+ }
3036
+ var countFormats = /* @__PURE__ */ new Map();
3037
+ function countFormat(locale) {
3038
+ const key = localeKey2(locale);
3039
+ let format = countFormats.get(key);
3040
+ if (format === void 0) {
3041
+ const numberFormat = new Intl.NumberFormat(locale, {
3042
+ maximumFractionDigits: 0
3043
+ });
3044
+ format = (count) => numberFormat.format(count);
3045
+ countFormats.set(key, format);
3046
+ }
3047
+ return format;
3048
+ }
3049
+ function columnCount(n, count, change) {
3050
+ return count === 1 ? `1 column ${change}` : `${n(count)} columns ${change}`;
3051
+ }
3052
+ function searchResults(n, count) {
3053
+ if (count === 0) {
3054
+ return "No rows found";
3055
+ }
3056
+ return count === 1 ? "1 row found" : `${n(count)} rows found`;
3057
+ }
3058
+ function selectedCount(n, count, target) {
3059
+ const noun = target === "rows" ? "row" : "column";
3060
+ if (count === 0) {
3061
+ return `No ${noun}s selected`;
3062
+ }
3063
+ return count === 1 ? `1 ${noun} selected` : `${n(count)} ${noun}s selected`;
3064
+ }
3065
+ var operatorLabels = {
3066
+ eq: "Equals",
3067
+ ne: "Does not equal",
3068
+ contains: "Contains",
3069
+ notContains: "Does not contain",
3070
+ startsWith: "Starts with",
3071
+ endsWith: "Ends with",
3072
+ gt: "Greater than",
3073
+ gte: "Greater than or equal to",
3074
+ lt: "Less than",
3075
+ lte: "Less than or equal to",
3076
+ between: "Between",
3077
+ in: "Is one of",
3078
+ isEmpty: "Is blank",
3079
+ isNotEmpty: "Is not blank"
3080
+ };
3081
+ var dateOperatorLabels = {
3082
+ lt: "Before",
3083
+ lte: "On or before",
3084
+ gt: "After",
3085
+ gte: "On or after"
3086
+ };
3087
+ function filterOperatorLabel(operator, family) {
3088
+ return (family === "date" ? dateOperatorLabels[operator] : void 0) ?? operatorLabels[operator];
3089
+ }
3090
+ function filterSearchFirst(n, total) {
3091
+ return total === 1 ? "Type to search 1 value" : `Type to search ${n(total)} values`;
3092
+ }
3093
+ function filterResults(n, shown, total) {
3094
+ return `${n(shown)} of ${n(total)} ${total === 1 ? "row" : "rows"}`;
3095
+ }
3096
+ var filterValueRequests = {
3097
+ text: "Enter a value",
3098
+ number: "Enter a number",
3099
+ date: "Enter a date",
3100
+ boolean: "Choose a value"
3101
+ };
3102
+ var filterOperatorMenus = {
3103
+ text: "Text filters",
3104
+ number: "Number filters",
3105
+ date: "Date filters",
3106
+ boolean: "Boolean filters"
3107
+ };
3108
+ function copied(n, count, unit) {
3109
+ return count === 1 ? `1 ${unit.slice(0, -1)} copied` : `${n(count)} ${unit} copied`;
3110
+ }
3111
+ function createDefaultMessages(locale) {
3112
+ const n = countFormat(locale);
3113
+ return {
3114
+ pagerLabel: "Pagination",
3115
+ firstPage: "First",
3116
+ previousPage: "Previous",
3117
+ nextPage: "Next",
3118
+ lastPage: "Last",
3119
+ pageSizeLabel: "Rows per page",
3120
+ pageButtonLabel: (page) => `Page ${n(page)}`,
3121
+ pageStatus: (page, pageCount) => `Page ${n(page)} of ${n(pageCount)}`,
3122
+ loadingMore: "Loading more\u2026",
3123
+ booleanTrue: "true",
3124
+ booleanFalse: "false",
3125
+ blankGroupValue: "(blank)",
3126
+ groupByBarLabel: "Grouped by",
3127
+ groupByBarPlaceholder: "Drag a column here to group by it",
3128
+ groupByChipLabel: (columnName2, level, levelCount) => levelCount > 1 ? `${columnName2}, level ${n(level)} of ${n(levelCount)}` : columnName2,
3129
+ removeGroupBy: (columnName2) => `Stop grouping by ${columnName2}`,
3130
+ columnResized: (columnName2, width) => `${columnName2}, ${n(width)} pixels wide`,
3131
+ columnMoved: (columnName2, position, columnCount2) => `${columnName2}, column ${n(position)} of ${n(columnCount2)}`,
3132
+ columnSorted: (columnName2, direction, key, keyCount) => {
3133
+ const sorted = `${columnName2} sorted ${direction === "asc" ? "ascending" : "descending"}`;
3134
+ return keyCount > 1 ? `${sorted}, key ${n(key)} of ${n(keyCount)}` : sorted;
3135
+ },
3136
+ sortCleared: (columnName2) => `${columnName2}, sort cleared`,
3137
+ groupedBy: (columnName2, level, levelCount) => levelCount > 1 ? `Grouped by ${columnName2}, level ${n(level)} of ${n(levelCount)}` : `Grouped by ${columnName2}`,
3138
+ groupingRemoved: (columnName2) => `${columnName2}, grouping removed`,
3139
+ groupExpanded: "Group expanded",
3140
+ groupCollapsed: "Group collapsed",
3141
+ allGroupsExpanded: "All groups expanded",
3142
+ allGroupsCollapsed: "All groups collapsed",
3143
+ selectedCount: (count, target) => selectedCount(n, count, target),
3144
+ cellSelected: (columnName2, rowNumber) => `${columnName2}, row ${n(rowNumber)}, selected`,
3145
+ columnHidden: (columnName2) => `${columnName2}, column hidden`,
3146
+ columnShown: (columnName2) => `${columnName2}, column shown`,
3147
+ columnsHidden: (count) => columnCount(n, count, "hidden"),
3148
+ columnsShown: (count) => columnCount(n, count, "shown"),
3149
+ columnsAutoFitted: (count) => columnCount(n, count, "autofitted"),
3150
+ headerMenuLabel: (columnName2) => `${columnName2} column menu`,
3151
+ cellMenuLabel: "Row menu",
3152
+ groupMenuLabel: "Group menu",
3153
+ bodyMenuLabel: "Grid menu",
3154
+ sortAscending: "Sort ascending",
3155
+ sortDescending: "Sort descending",
3156
+ clearSort: "Clear sort",
3157
+ clearAllSorting: "Clear all sorting",
3158
+ groupByColumn: (columnName2) => `Group by ${columnName2}`,
3159
+ ungroupAll: "Ungroup all",
3160
+ expandAllGroups: "Expand all groups",
3161
+ collapseAllGroups: "Collapse all groups",
3162
+ hideColumn: "Hide column",
3163
+ autoFitColumn: "Autofit column",
3164
+ autoFitAllColumns: "Autofit all columns",
3165
+ selectAllRows: "Select all rows",
3166
+ clearSelection: "Clear selection",
3167
+ clearFilters: "Clear filters",
3168
+ chooseColumns: "Choose columns",
3169
+ toolbarLabel: "Grid toolbar",
3170
+ searchLabel: "Search",
3171
+ searchPlaceholder: "Search\u2026",
3172
+ moreToolbarItems: "More",
3173
+ searchResults: (count) => searchResults(n, count),
3174
+ columnChooserLabel: "Columns",
3175
+ columnChooserSearchLabel: "Search columns",
3176
+ showAllColumns: "Show all",
3177
+ noMatchingColumns: "No matching columns",
3178
+ filterOperatorLabel,
3179
+ filterBlanks: "(Blanks)",
3180
+ filterSearchFirst: (total) => filterSearchFirst(n, total),
3181
+ filterMoreValues: (count) => `${n(count)} more, refine the search`,
3182
+ openFilter: "Filter\u2026",
3183
+ clearColumnFilter: "Clear filter",
3184
+ filterMenuLabel: (columnName2) => `${columnName2} filter`,
3185
+ columnFiltered: "Filtered",
3186
+ applyFilter: "OK",
3187
+ cancelFilter: "Cancel",
3188
+ customFilter: "Custom filter",
3189
+ filterResults: (shown, total) => filterResults(n, shown, total),
3190
+ filterConditionLabel: (position) => `Condition ${n(position)}`,
3191
+ filterOperatorFieldLabel: "Operator",
3192
+ filterValueLabel: "Value",
3193
+ filterFromLabel: "From",
3194
+ filterToLabel: "To",
3195
+ filterCombinatorLabel: "Combine conditions",
3196
+ filterAnd: "And",
3197
+ filterOr: "Or",
3198
+ filterValueInvalid: (family) => filterValueRequests[family],
3199
+ filterSearchLabel: "Search values",
3200
+ filterSelectAll: "Select all",
3201
+ filterValuesLabel: "Values",
3202
+ filterByCondition: "Filtered by condition",
3203
+ filterOperatorMenu: (family) => filterOperatorMenus[family],
3204
+ filterBackToValues: "Back to values",
3205
+ exportMenu: "Export",
3206
+ exportCsv: "Export to CSV",
3207
+ exportJson: "Export to JSON",
3208
+ exportXlsx: "Export to Excel",
3209
+ exportPdf: "Export to PDF",
3210
+ exportSelectionCsv: "Export selection to CSV",
3211
+ exportSelectionJson: "Export selection to JSON",
3212
+ exportSelectionXlsx: "Export selection to Excel",
3213
+ exportFailed: "Export failed",
3214
+ copyCell: "Copy cell",
3215
+ copyRow: "Copy row",
3216
+ copySelection: "Copy selection",
3217
+ copied: (count, unit) => copied(n, count, unit),
3218
+ copyFailed: "Copy failed",
3219
+ print: "Print"
3220
+ };
3221
+ }
3222
+ var defaultMessages = createDefaultMessages();
3223
+ var localeDefaults = /* @__PURE__ */ new Map();
3224
+ function defaultsFor(locale) {
3225
+ if (locale === void 0) {
3226
+ return defaultMessages;
3227
+ }
3228
+ const key = localeKey2(locale);
3229
+ let defaults = localeDefaults.get(key);
3230
+ if (defaults === void 0) {
3231
+ defaults = createDefaultMessages(locale);
3232
+ localeDefaults.set(key, defaults);
3233
+ }
3234
+ return defaults;
3235
+ }
3236
+ function resolveMessages(messages, locale) {
3237
+ const defaults = defaultsFor(locale);
3238
+ return messages === void 0 ? defaults : { ...defaults, ...messages };
3239
+ }
3240
+
3241
+ // src/util/filterEditor.ts
3242
+ function touchesColumn(entry, columnId) {
3243
+ return "combinator" in entry ? entry.entries.some((nested) => touchesColumn(nested, columnId)) : entry.columnId === columnId;
3244
+ }
3245
+ function isOperatorOn(entry, columnId) {
3246
+ return "operator" in entry && entry.columnId === columnId;
3247
+ }
3248
+ function isConditionOn(entry, columnId) {
3249
+ return isOperatorOn(entry, columnId) && entry.operator !== "in";
3250
+ }
3251
+ function valuesShape(entry, columnId) {
3252
+ const values = (candidate) => candidate !== void 0 && isOperatorOn(candidate, columnId) && candidate.operator === "in" && isScalarList(candidate.value) ? candidate.value : void 0;
3253
+ const blanks = (candidate) => candidate !== void 0 && isOperatorOn(candidate, columnId) && candidate.operator === "isEmpty";
3254
+ const alone = values(entry);
3255
+ if (alone !== void 0) {
3256
+ return { kind: "values", values: alone, includeBlanks: false };
3257
+ }
3258
+ if (!("combinator" in entry) || entry.combinator !== "or" || entry.entries.length !== 2) {
3259
+ return void 0;
3260
+ }
3261
+ const [first, second] = entry.entries;
3262
+ const listed = blanks(first) ? values(second) : values(first);
3263
+ return listed !== void 0 && (blanks(first) || blanks(second)) ? { kind: "values", values: listed, includeBlanks: true } : void 0;
3264
+ }
3265
+ function conditionsShape(touching, columnId) {
3266
+ const [only] = touching;
3267
+ const grouped = touching.length === 1 && only !== void 0 && "combinator" in only;
3268
+ const entries = grouped ? only.entries : touching;
3269
+ const combinator = grouped ? only.combinator : "and";
3270
+ if (entries.length === 0 || entries.length > 2) {
3271
+ return void 0;
3272
+ }
3273
+ const conditions = entries.filter((entry) => isConditionOn(entry, columnId));
3274
+ return conditions.length === entries.length ? { kind: "conditions", conditions, combinator } : void 0;
3275
+ }
3276
+ function describeColumnFilter(filter, columnId) {
3277
+ const touching = filter.filter((entry) => touchesColumn(entry, columnId));
3278
+ const [only] = touching;
3279
+ if (only === void 0) {
3280
+ return { kind: "none" };
3281
+ }
3282
+ return (touching.length === 1 ? valuesShape(only, columnId) : void 0) ?? conditionsShape(touching, columnId) ?? { kind: "custom" };
3283
+ }
3284
+ function isColumnFiltered(filter, columnId) {
3285
+ return filter.some((entry) => touchesColumn(entry, columnId));
3286
+ }
3287
+ function isOfferable(value) {
3288
+ return typeof value === "string" || typeof value === "number" || typeof value === "boolean" || value instanceof Date;
3289
+ }
3290
+ function matchesSearch(label, search) {
3291
+ return label.toLowerCase().includes(search.toLowerCase());
3292
+ }
3293
+ function columnValueOptions(rows, dataColumns, filter, columnId, query = {}) {
3294
+ const column = dataColumns.find((entry) => entry.id === columnId)?.column;
3295
+ if (column === void 0) {
3296
+ return { options: [], total: 0, truncated: false, blanks: 0 };
3297
+ }
3298
+ const keep = compileFilter(
3299
+ filter.filter((entry) => !touchesColumn(entry, columnId)),
3300
+ dataColumns
3301
+ );
3302
+ const buckets = /* @__PURE__ */ new Map();
3303
+ let blanks = 0;
3304
+ for (const row of rows) {
3305
+ if (keep !== void 0 && !keep(row)) {
3306
+ continue;
3307
+ }
3308
+ const value = accessDotted(row, column.field);
3309
+ if (isEmptyCell(value)) {
3310
+ blanks++;
3311
+ } else if (isOfferable(value)) {
3312
+ const key = filterValueKey(value, column.type);
3313
+ const bucket = buckets.get(key);
3314
+ if (bucket === void 0) {
3315
+ buckets.set(key, { value, row, count: 1 });
3316
+ } else {
3317
+ bucket.count++;
3318
+ }
3319
+ }
3320
+ }
3321
+ const { search = "", limit, locale } = query;
3322
+ const messages = resolveMessages(query.messages, locale);
3323
+ const options = [...buckets.values()].sort((a, b) => compareValues(a.value, b.value, column.type)).map(({ value, row, count }) => ({
3324
+ value,
3325
+ label: formatCellValue(value, column, locale, row, messages),
3326
+ count
3327
+ })).filter((option) => matchesSearch(option.label, search));
3328
+ const shown = limit === void 0 ? options : options.slice(0, limit);
3329
+ return {
3330
+ options: shown,
3331
+ total: options.length,
3332
+ truncated: shown.length < options.length,
3333
+ blanks,
3334
+ columnType: column.type
3335
+ };
3336
+ }
3337
+ function keyOf2(draft, value) {
3338
+ return filterValueKey(value, draft.columnType);
3339
+ }
3340
+ function everyOption(draft) {
3341
+ return new Map(
3342
+ draft.options.map((option) => [keyOf2(draft, option.value), option.value])
3343
+ );
3344
+ }
3345
+ function initialFilterEditorDraft(shape, values) {
3346
+ const draft = {
3347
+ mode: "values",
3348
+ options: values.options,
3349
+ blanks: values.blanks,
3350
+ search: "",
3351
+ checked: everyOption(values),
3352
+ includeBlanks: values.blanks > 0,
3353
+ conditions: [],
3354
+ combinator: "and",
3355
+ columnType: values.columnType
3356
+ };
3357
+ switch (shape.kind) {
3358
+ case "values":
3359
+ return {
3360
+ ...draft,
3361
+ checked: new Map(
3362
+ shape.values.filter((value) => !isEmptyCell(value)).map((value) => [keyOf2(draft, value), value])
3363
+ ),
3364
+ includeBlanks: shape.includeBlanks
3365
+ };
3366
+ case "conditions":
3367
+ return {
3368
+ ...draft,
3369
+ mode: "conditions",
3370
+ conditions: shape.conditions,
3371
+ combinator: shape.combinator
3372
+ };
3373
+ case "none":
3374
+ case "custom":
3375
+ return draft;
3376
+ }
3377
+ }
3378
+ function visibleFilterOptions(draft) {
3379
+ return draft.search === "" ? draft.options : draft.options.filter(
3380
+ (option) => matchesSearch(option.label, draft.search)
3381
+ );
3382
+ }
3383
+ function selectAllCoversBlanks(draft) {
3384
+ return draft.search === "" && draft.blanks > 0;
3385
+ }
3386
+ function selectAllState(draft) {
3387
+ const states = visibleFilterOptions(draft).map(
3388
+ (option) => draft.checked.has(keyOf2(draft, option.value))
3389
+ );
3390
+ if (selectAllCoversBlanks(draft)) {
3391
+ states.push(draft.includeBlanks);
3392
+ }
3393
+ if (states.length > 0 && states.every(Boolean)) {
3394
+ return "checked";
3395
+ }
3396
+ return states.some(Boolean) ? "mixed" : "unchecked";
3397
+ }
3398
+ function toggleAll(draft) {
3399
+ const check = selectAllState(draft) !== "checked";
3400
+ const checked = new Map(draft.checked);
3401
+ for (const option of visibleFilterOptions(draft)) {
3402
+ const key = keyOf2(draft, option.value);
3403
+ if (check) {
3404
+ checked.set(key, option.value);
3405
+ } else {
3406
+ checked.delete(key);
3407
+ }
3408
+ }
3409
+ return {
3410
+ ...draft,
3411
+ mode: "values",
3412
+ checked,
3413
+ includeBlanks: selectAllCoversBlanks(draft) ? check : draft.includeBlanks
3414
+ };
3415
+ }
3416
+ function filterEditorReducer(draft, action) {
3417
+ switch (action.type) {
3418
+ case "search":
3419
+ return { ...draft, search: action.search };
3420
+ case "toggleValue": {
3421
+ const key = keyOf2(draft, action.value);
3422
+ const checked = new Map(draft.checked);
3423
+ if (checked.has(key)) {
3424
+ checked.delete(key);
3425
+ } else {
3426
+ checked.set(key, action.value);
3427
+ }
3428
+ return { ...draft, mode: "values", checked };
3429
+ }
3430
+ case "toggleAll":
3431
+ return toggleAll(draft);
3432
+ case "setIncludeBlanks":
3433
+ return { ...draft, mode: "values", includeBlanks: action.includeBlanks };
3434
+ case "setConditions":
3435
+ return {
3436
+ ...draft,
3437
+ mode: "conditions",
3438
+ conditions: action.conditions,
3439
+ combinator: action.combinator ?? draft.combinator
3440
+ };
3441
+ case "clear":
3442
+ return {
3443
+ ...draft,
3444
+ search: "",
3445
+ checked: everyOption(draft),
3446
+ includeBlanks: draft.blanks > 0,
3447
+ conditions: []
3448
+ };
3449
+ }
3450
+ }
3451
+ function checksEverything(draft) {
3452
+ return draft.options.every(
3453
+ (option) => draft.checked.has(keyOf2(draft, option.value))
3454
+ ) && (draft.includeBlanks || draft.blanks === 0);
3455
+ }
3456
+ function draftEntries(draft, columnId) {
3457
+ if (draft.mode === "conditions") {
3458
+ const conditions = draft.conditions.map((condition) => ({
3459
+ ...condition,
3460
+ columnId
3461
+ }));
3462
+ return conditions.length > 1 ? [{ combinator: draft.combinator, entries: conditions }] : conditions;
3463
+ }
3464
+ if (checksEverything(draft)) {
3465
+ return [];
3466
+ }
3467
+ const picked = {
3468
+ columnId,
3469
+ operator: "in",
3470
+ value: [...draft.checked.values()]
3471
+ };
3472
+ return draft.includeBlanks ? [
3473
+ {
3474
+ combinator: "or",
3475
+ entries: [picked, { columnId, operator: "isEmpty" }]
3476
+ }
3477
+ ] : [picked];
3478
+ }
3479
+ function draftToFilter(draft, columnId, current) {
3480
+ const replacement = draftEntries(draft, columnId);
3481
+ const first = current.findIndex((entry) => touchesColumn(entry, columnId));
3482
+ if (first === -1) {
3483
+ return replacement.length === 0 ? current : [...current, ...replacement];
3484
+ }
3485
+ const kept = current.filter((entry) => !touchesColumn(entry, columnId));
3486
+ return [...kept.slice(0, first), ...replacement, ...kept.slice(first)];
3487
+ }
3488
+
2188
3489
  // src/commands/builtInCommands.ts
2189
3490
  function isOn(context, areas) {
2190
3491
  return areas.includes(context.target.area);
@@ -2207,6 +3508,10 @@ function groupedColumn(context) {
2207
3508
  const column = columnOn(context, ["header", "group"]);
2208
3509
  return column !== null && isGroupedBy(context, column.id) ? column : null;
2209
3510
  }
3511
+ function filterableColumn(context) {
3512
+ const column = columnOn(context, ["header"]);
3513
+ return column !== null && resolveColumnFilterMenu(context.options, column.column) !== null ? column : null;
3514
+ }
2210
3515
  function hideableColumn(context) {
2211
3516
  const column = columnOn(context, ["header"]);
2212
3517
  return column !== null && column.column.hideable !== false ? column : null;
@@ -2261,6 +3566,63 @@ function columnCommand({
2261
3566
  }
2262
3567
  };
2263
3568
  }
3569
+ var EXPORT_AREAS = ["grid", "toolbar", "body", "cell"];
3570
+ function exportCommand({
3571
+ id,
3572
+ format,
3573
+ label,
3574
+ selection = false
3575
+ }) {
3576
+ return {
3577
+ id,
3578
+ label: ({ messages }) => label(messages),
3579
+ isVisible: (context) => isOn(context, EXPORT_AREAS) && resolveExportWriters(context.options.exportWriters).has(format) && (!selection || context.state.rowSelection.length > 0),
3580
+ isEnabled: (context) => (selection ? context.selectedRows() : context.view.rows).length > 0,
3581
+ execute: (context) => {
3582
+ const { export: defaults = {}, exportWriters } = context.options;
3583
+ const file = exportFile(
3584
+ context,
3585
+ format,
3586
+ selection ? { ...defaults, scope: "selection" } : defaults,
3587
+ resolveExportWriters(exportWriters)
3588
+ );
3589
+ if (file !== null) {
3590
+ context.host.saveFile(file);
3591
+ }
3592
+ }
3593
+ };
3594
+ }
3595
+ function cellRequest(context, kind) {
3596
+ const { target } = context;
3597
+ if (target.area !== "cell") {
3598
+ return null;
3599
+ }
3600
+ return kind === "cell" ? { kind, rowId: target.rowId, columnId: target.columnId } : { kind, rowId: target.rowId };
3601
+ }
3602
+ function copyCommand({
3603
+ id,
3604
+ label,
3605
+ request,
3606
+ isEnabled
3607
+ }) {
3608
+ function contentFor(context) {
3609
+ const asked = request(context);
3610
+ return asked === null ? null : copyContent(context, asked);
3611
+ }
3612
+ return {
3613
+ id,
3614
+ label: ({ messages }) => label(messages),
3615
+ icon: "copy",
3616
+ isVisible: (context) => request(context) !== null,
3617
+ isEnabled: isEnabled ?? ((context) => contentFor(context) !== null),
3618
+ execute: (context) => {
3619
+ const content = contentFor(context);
3620
+ if (content !== null) {
3621
+ context.host.writeClipboard(content);
3622
+ }
3623
+ }
3624
+ };
3625
+ }
2264
3626
  function sortCommand(direction) {
2265
3627
  return columnCommand({
2266
3628
  id: direction === "asc" ? "sort.ascending" : "sort.descending",
@@ -2382,6 +3744,29 @@ function builtInCommands() {
2382
3744
  host.openPanel("columnChooser", target);
2383
3745
  }
2384
3746
  },
3747
+ // Opened below the header it was asked for, as the chooser is.
3748
+ columnCommand({
3749
+ id: "filter.open",
3750
+ label: ({ messages }) => messages.openFilter,
3751
+ find: filterableColumn,
3752
+ execute: (_column, { host, target }) => {
3753
+ host.openPanel("filter", target);
3754
+ }
3755
+ }),
3756
+ // Every entry about the column goes, whichever editor wrote it — or
3757
+ // none did, as with a predicate set in code — as applying an editor
3758
+ // replaces them all.
3759
+ columnCommand({
3760
+ id: "filter.clear",
3761
+ label: ({ messages }) => messages.clearColumnFilter,
3762
+ find: filterableColumn,
3763
+ isEnabled: (column, { state }) => isColumnFiltered(state.filter, column.id),
3764
+ execute: (column, { actions, state }) => {
3765
+ actions.setFilter(
3766
+ state.filter.filter((entry) => !touchesColumn(entry, column.id))
3767
+ );
3768
+ }
3769
+ }),
2385
3770
  {
2386
3771
  id: "selection.selectAllRows",
2387
3772
  label: ({ messages }) => messages.selectAllRows,
@@ -2408,6 +3793,75 @@ function builtInCommands() {
2408
3793
  execute: ({ actions }) => {
2409
3794
  actions.setFilter([]);
2410
3795
  }
3796
+ },
3797
+ exportCommand({
3798
+ id: "export.csv",
3799
+ format: "csv",
3800
+ label: ({ exportCsv }) => exportCsv
3801
+ }),
3802
+ exportCommand({
3803
+ id: "export.json",
3804
+ format: "json",
3805
+ label: ({ exportJson }) => exportJson
3806
+ }),
3807
+ exportCommand({
3808
+ id: "export.xlsx",
3809
+ format: "xlsx",
3810
+ label: ({ exportXlsx }) => exportXlsx
3811
+ }),
3812
+ exportCommand({
3813
+ id: "export.pdf",
3814
+ format: "pdf",
3815
+ label: ({ exportPdf }) => exportPdf
3816
+ }),
3817
+ exportCommand({
3818
+ id: "exportSelection.csv",
3819
+ format: "csv",
3820
+ label: ({ exportSelectionCsv }) => exportSelectionCsv,
3821
+ selection: true
3822
+ }),
3823
+ exportCommand({
3824
+ id: "exportSelection.json",
3825
+ format: "json",
3826
+ label: ({ exportSelectionJson }) => exportSelectionJson,
3827
+ selection: true
3828
+ }),
3829
+ exportCommand({
3830
+ id: "exportSelection.xlsx",
3831
+ format: "xlsx",
3832
+ label: ({ exportSelectionXlsx }) => exportSelectionXlsx,
3833
+ selection: true
3834
+ }),
3835
+ copyCommand({
3836
+ id: "copy.cell",
3837
+ label: ({ copyCell }) => copyCell,
3838
+ request: (context) => cellRequest(context, "cell")
3839
+ }),
3840
+ copyCommand({
3841
+ id: "copy.row",
3842
+ label: ({ copyRow }) => copyRow,
3843
+ request: (context) => cellRequest(context, "row")
3844
+ }),
3845
+ // Shown wherever the grid as a whole is asked for, as the exports are,
3846
+ // once anything can be selected; enabled once something is.
3847
+ copyCommand({
3848
+ id: "copy.selection",
3849
+ label: ({ copySelection }) => copySelection,
3850
+ request: (context) => isOn(context, EXPORT_AREAS) && isAnythingSelectable(context.options.selectable) ? { kind: "selection" } : null,
3851
+ isEnabled: canCopySelection
3852
+ }),
3853
+ // Shown where the exports are, under the grid's `print` defaults. A grid
3854
+ // with no rows still prints its header; one with no columns, nothing.
3855
+ {
3856
+ id: "print",
3857
+ label: ({ messages }) => messages.print,
3858
+ icon: "print",
3859
+ isVisible: (context) => isOn(context, EXPORT_AREAS),
3860
+ isEnabled: ({ view }) => view.columns.length > 0,
3861
+ execute: (context) => {
3862
+ const { print: options = {} } = context.options;
3863
+ context.host.print(exportPrintDocument(context, options), options);
3864
+ }
2411
3865
  }
2412
3866
  ];
2413
3867
  }
@@ -2554,6 +4008,9 @@ var defaultContextMenu = {
2554
4008
  "sort.descending",
2555
4009
  "sort.clear",
2556
4010
  "separator",
4011
+ "filter.open",
4012
+ "filter.clear",
4013
+ "separator",
2557
4014
  "group.add",
2558
4015
  "group.remove",
2559
4016
  "separator",
@@ -2564,6 +4021,10 @@ var defaultContextMenu = {
2564
4021
  "column.choose"
2565
4022
  ],
2566
4023
  cell: [
4024
+ "copy.cell",
4025
+ "copy.row",
4026
+ "copy.selection",
4027
+ "separator",
2567
4028
  "selection.selectAllRows",
2568
4029
  "selection.clear",
2569
4030
  "separator",
@@ -2680,6 +4141,23 @@ function toolbarEntries(toolbar) {
2680
4141
  function isSpacer(entry) {
2681
4142
  return entry.type === "spacer";
2682
4143
  }
4144
+ function exportMenu() {
4145
+ return {
4146
+ type: "submenu",
4147
+ id: "export",
4148
+ label: ({ messages }) => messages.exportMenu,
4149
+ items: [
4150
+ "export.csv",
4151
+ "export.json",
4152
+ "export.xlsx",
4153
+ "export.pdf",
4154
+ "separator",
4155
+ "exportSelection.csv",
4156
+ "exportSelection.json",
4157
+ "exportSelection.xlsx"
4158
+ ]
4159
+ };
4160
+ }
2683
4161
  function resolveToolbar(entries, options) {
2684
4162
  const { commands, contextFor } = options;
2685
4163
  function resolveEntry(entry, index) {
@@ -2695,11 +4173,12 @@ function resolveToolbar(entries, options) {
2695
4173
  if (entry === "separator") {
2696
4174
  return { type: "separator", key: `separator-${String(index)}` };
2697
4175
  }
2698
- const resolved = resolveMenuEntry(entry, index, {
4176
+ const menuEntry = entry === "export" ? exportMenu() : entry;
4177
+ const resolved = resolveMenuEntry(menuEntry, index, {
2699
4178
  commands,
2700
4179
  context: contextFor({
2701
4180
  area: "toolbar",
2702
- itemId: typeof entry === "string" ? entry : entry.id
4181
+ itemId: typeof menuEntry === "string" ? menuEntry : menuEntry.id
2703
4182
  })
2704
4183
  });
2705
4184
  if (resolved === null || resolved.type === "separator") {
@@ -2893,558 +4372,218 @@ function nextFocusForKey(key, modifiers, focus, rowCount, columnCount2, pageSize
2893
4372
  return null;
2894
4373
  }
2895
4374
  }
2896
-
2897
- // src/util/paginationWindow.ts
2898
- function paginationWindow(currentPage, pageCount, options) {
2899
- if (pageCount <= 0) {
2900
- return [];
2901
- }
2902
- const boundaryCount = Math.max(0, options?.boundaryCount ?? 1);
2903
- const siblingCount = Math.max(0, options?.siblingCount ?? 1);
2904
- const current = Math.min(Math.max(currentPage, 1), pageCount);
2905
- const pages = /* @__PURE__ */ new Set();
2906
- for (let page = 1; page <= Math.min(boundaryCount, pageCount); page++) {
2907
- pages.add(page);
2908
- }
2909
- for (let page = Math.max(pageCount - boundaryCount + 1, 1); page <= pageCount; page++) {
2910
- pages.add(page);
2911
- }
2912
- for (let page = Math.max(current - siblingCount, 1); page <= Math.min(current + siblingCount, pageCount); page++) {
2913
- pages.add(page);
2914
- }
2915
- const sorted = Array.from(pages).sort((a, b) => a - b);
2916
- const entries = [];
2917
- let previous;
2918
- for (const page of sorted) {
2919
- if (previous !== void 0) {
2920
- if (page - previous === 2) {
2921
- entries.push(previous + 1);
2922
- } else if (page - previous > 2) {
2923
- entries.push("ellipsis");
2924
- }
2925
- }
2926
- entries.push(page);
2927
- previous = page;
2928
- }
2929
- return entries;
2930
- }
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
-
3065
- // src/util/format.ts
3066
- var DATE_FIELDS = {
3067
- year: "numeric",
3068
- month: "numeric",
3069
- day: "numeric"
3070
- };
3071
- var TIME_FIELDS = {
3072
- hour: "numeric",
3073
- minute: "numeric",
3074
- second: "numeric"
3075
- };
3076
- var DATE_TIME_FIELDS = {
3077
- ...DATE_FIELDS,
3078
- ...TIME_FIELDS
3079
- };
3080
- var CURRENCY_DIGITS = {
3081
- minimumFractionDigits: 2,
3082
- maximumFractionDigits: 2
3083
- };
3084
- var NO_FORMAT = {};
3085
- var formatters = /* @__PURE__ */ new WeakMap();
3086
- function cached(format, key, create) {
3087
- const owner = format ?? NO_FORMAT;
3088
- let byKey = formatters.get(owner);
3089
- if (byKey === void 0) {
3090
- byKey = /* @__PURE__ */ new Map();
3091
- formatters.set(owner, byKey);
3092
- }
3093
- const existing = byKey.get(key);
3094
- if (existing !== void 0) {
3095
- return existing;
3096
- }
3097
- const formatter = create();
3098
- byKey.set(key, formatter);
3099
- return formatter;
3100
- }
3101
- function localeKey(locale) {
3102
- return typeof locale === "string" ? locale : locale?.join(",") ?? "";
3103
- }
3104
- function isNumericType(type) {
3105
- return type === "number" || type === "decimal" || type === "currency" || type === "percent";
3106
- }
3107
- function numberOptions(type, format) {
3108
- switch (type) {
3109
- case "currency":
3110
- return format?.currency === void 0 ? { ...CURRENCY_DIGITS, ...format } : { style: "currency", ...format };
3111
- case "percent":
3112
- return { style: "percent", ...format };
3113
- default:
3114
- return format ?? {};
3115
- }
3116
- }
3117
- function dateOptions(type, format) {
3118
- if (format !== void 0) {
3119
- return format;
3120
- }
3121
- if (type === "date") return DATE_FIELDS;
3122
- if (type === "time") return TIME_FIELDS;
3123
- return DATE_TIME_FIELDS;
3124
- }
3125
- function formatNumber(value, type, format, locale) {
3126
- const formatter = cached(
3127
- format,
3128
- `number|${type ?? ""}|${localeKey(locale)}`,
3129
- () => new Intl.NumberFormat(
3130
- locale,
3131
- numberOptions(type, format)
3132
- )
3133
- );
3134
- return formatter.format(value);
3135
- }
3136
- function formatDate(value, type, format, locale) {
3137
- if (Number.isNaN(value.getTime())) {
3138
- return String(value);
3139
- }
3140
- const formatter = cached(
3141
- format,
3142
- `date|${type ?? ""}|${localeKey(locale)}`,
3143
- () => new Intl.DateTimeFormat(
3144
- locale,
3145
- dateOptions(type, format)
3146
- )
3147
- );
3148
- return formatter.format(value);
3149
- }
3150
- function formatOther(value) {
3151
- if (typeof value === "function") {
3152
- return "(function)";
3153
- }
3154
- if (typeof value === "object" && value !== null) {
3155
- try {
3156
- return JSON.stringify(value);
3157
- } catch {
3158
- return Object.prototype.toString.call(value);
3159
- }
3160
- }
3161
- return String(value);
3162
- }
3163
- function formatCellValue(value, column, locale, row, messages) {
3164
- if (column.valueFormatter !== void 0) {
3165
- return column.valueFormatter(value, row);
3166
- }
3167
- if (value === null || value === void 0) {
3168
- return "";
3169
- }
3170
- if (typeof value === "boolean" && messages !== void 0) {
3171
- return value ? messages.booleanTrue : messages.booleanFalse;
3172
- }
3173
- const { type, format } = column;
3174
- const formatted = format !== void 0 || locale !== void 0;
3175
- if (value instanceof Date) {
3176
- return formatted ? formatDate(value, type, format, locale) : value.toLocaleString();
3177
- }
3178
- if (formatted && (typeof value === "number" || typeof value === "bigint") && isNumericType(type)) {
3179
- return formatNumber(value, type, format, locale);
3180
- }
3181
- if (typeof value === "string") {
3182
- return value;
3183
- }
3184
- return formatOther(value);
3185
- }
3186
- function aggregateFormatColumn(value, spec, column) {
3187
- const plain = {
3188
- field: spec.columnId,
3189
- type: "number"
3190
- };
3191
- if (column === void 0 || spec.fn === "count" || spec.fn === "countDistinct") {
3192
- return plain;
3193
- }
3194
- const numeric = typeof value === "number" || typeof value === "bigint";
3195
- return column.valueFormatter === void 0 && numeric && !isNumericType(column.type) ? plain : column;
3196
- }
3197
- function formatAggregateValue(value, spec, column, locale) {
3198
- const target = aggregateFormatColumn(value, spec, column);
3199
- if (typeof value === "number" && target.valueFormatter === void 0 && target.format === void 0 && locale === void 0) {
3200
- return value.toLocaleString();
3201
- }
3202
- return formatCellValue(value, target, locale);
3203
- }
3204
-
3205
- // src/util/filterEditor.ts
3206
- function touchesColumn(entry, columnId) {
3207
- return "combinator" in entry ? entry.entries.some((nested) => touchesColumn(nested, columnId)) : entry.columnId === columnId;
3208
- }
3209
- function isOperatorOn(entry, columnId) {
3210
- return "operator" in entry && entry.columnId === columnId;
3211
- }
3212
- function isConditionOn(entry, columnId) {
3213
- return isOperatorOn(entry, columnId) && entry.operator !== "in";
3214
- }
3215
- function valuesShape(entry, columnId) {
3216
- const values = (candidate) => candidate !== void 0 && isOperatorOn(candidate, columnId) && candidate.operator === "in" && isScalarList(candidate.value) ? candidate.value : void 0;
3217
- const blanks = (candidate) => candidate !== void 0 && isOperatorOn(candidate, columnId) && candidate.operator === "isEmpty";
3218
- const alone = values(entry);
3219
- if (alone !== void 0) {
3220
- return { kind: "values", values: alone, includeBlanks: false };
3221
- }
3222
- if (!("combinator" in entry) || entry.combinator !== "or" || entry.entries.length !== 2) {
3223
- return void 0;
3224
- }
3225
- const [first, second] = entry.entries;
3226
- const listed = blanks(first) ? values(second) : values(first);
3227
- return listed !== void 0 && (blanks(first) || blanks(second)) ? { kind: "values", values: listed, includeBlanks: true } : void 0;
3228
- }
3229
- function conditionsShape(touching, columnId) {
3230
- const [only] = touching;
3231
- const grouped = touching.length === 1 && only !== void 0 && "combinator" in only;
3232
- const entries = grouped ? only.entries : touching;
3233
- const combinator = grouped ? only.combinator : "and";
3234
- if (entries.length === 0 || entries.length > 2) {
3235
- return void 0;
3236
- }
3237
- const conditions = entries.filter((entry) => isConditionOn(entry, columnId));
3238
- return conditions.length === entries.length ? { kind: "conditions", conditions, combinator } : void 0;
3239
- }
3240
- function describeColumnFilter(filter, columnId) {
3241
- const touching = filter.filter((entry) => touchesColumn(entry, columnId));
3242
- const [only] = touching;
3243
- if (only === void 0) {
3244
- return { kind: "none" };
4375
+
4376
+ // src/util/paginationWindow.ts
4377
+ function paginationWindow(currentPage, pageCount, options) {
4378
+ if (pageCount <= 0) {
4379
+ return [];
3245
4380
  }
3246
- return (touching.length === 1 ? valuesShape(only, columnId) : void 0) ?? conditionsShape(touching, columnId) ?? { kind: "custom" };
4381
+ const boundaryCount = Math.max(0, Math.floor(options?.boundaryCount ?? 1));
4382
+ const siblingCount = Math.max(0, Math.floor(options?.siblingCount ?? 1));
4383
+ const slotCount = 2 * boundaryCount + 2 * siblingCount + 3;
4384
+ if (pageCount <= slotCount) {
4385
+ return Array.from({ length: pageCount }, (_, index) => index + 1);
4386
+ }
4387
+ const current = Math.min(Math.max(currentPage, 1), pageCount);
4388
+ const runStart = Math.min(
4389
+ Math.max(current - siblingCount, boundaryCount + 2),
4390
+ pageCount - boundaryCount - 1 - 2 * siblingCount
4391
+ );
4392
+ const runEnd = runStart + 2 * siblingCount;
4393
+ const entries = [];
4394
+ for (let page = 1; page <= boundaryCount; page++) {
4395
+ entries.push(page);
4396
+ }
4397
+ entries.push(runStart === boundaryCount + 2 ? boundaryCount + 1 : "ellipsis");
4398
+ for (let page = runStart; page <= runEnd; page++) {
4399
+ entries.push(page);
4400
+ }
4401
+ entries.push(
4402
+ runEnd === pageCount - boundaryCount - 1 ? pageCount - boundaryCount : "ellipsis"
4403
+ );
4404
+ for (let page = pageCount - boundaryCount + 1; page <= pageCount; page++) {
4405
+ entries.push(page);
4406
+ }
4407
+ return entries;
3247
4408
  }
3248
- function isOfferable(value) {
3249
- return typeof value === "string" || typeof value === "number" || typeof value === "boolean" || value instanceof Date;
4409
+
4410
+ // src/util/columnChooser.ts
4411
+ function isHideable(column) {
4412
+ return column.column.hideable !== false;
3250
4413
  }
3251
4414
  function matchesSearch2(label, search) {
3252
4415
  return label.toLowerCase().includes(search.toLowerCase());
3253
4416
  }
3254
- function columnValueOptions(rows, dataColumns, filter, columnId, query = {}) {
3255
- const column = dataColumns.find((entry) => entry.id === columnId)?.column;
3256
- if (column === void 0) {
3257
- return { options: [], total: 0, truncated: false, blanks: 0 };
4417
+ function selectAllStateOf(states) {
4418
+ if (states.length > 0 && states.every(Boolean)) {
4419
+ return "checked";
3258
4420
  }
3259
- const keep = compileFilter(
3260
- filter.filter((entry) => !touchesColumn(entry, columnId)),
3261
- dataColumns
4421
+ return states.some(Boolean) ? "mixed" : "unchecked";
4422
+ }
4423
+ function resolveColumnChooser({
4424
+ columns,
4425
+ visibility,
4426
+ search = ""
4427
+ }) {
4428
+ const all = columns.map((column) => ({
4429
+ column,
4430
+ label: resolveColumnHeaderText(column.column),
4431
+ visible: isColumnVisible(column.column, visibility)
4432
+ }));
4433
+ const shownCount = all.filter(({ visible }) => visible).length;
4434
+ const matched = all.filter(({ label }) => matchesSearch2(label, search));
4435
+ const entries = matched.map(
4436
+ ({ column, label, visible }) => ({
4437
+ columnId: column.id,
4438
+ label,
4439
+ visible,
4440
+ disabled: visible && (!isHideable(column) || shownCount === 1)
4441
+ })
3262
4442
  );
3263
- const buckets = /* @__PURE__ */ new Map();
3264
- let blanks = 0;
3265
- for (const row of rows) {
3266
- if (keep !== void 0 && !keep(row)) {
3267
- continue;
3268
- }
3269
- const value = accessDotted(row, column.field);
3270
- if (isEmptyCell(value)) {
3271
- blanks++;
3272
- } else if (isOfferable(value)) {
3273
- const key = filterValueKey(value, column.type);
3274
- const bucket = buckets.get(key);
3275
- if (bucket === void 0) {
3276
- buckets.set(key, { value, row, count: 1 });
3277
- } else {
3278
- bucket.count++;
3279
- }
3280
- }
3281
- }
3282
- const { search = "", limit, locale } = query;
3283
- const messages = resolveMessages(query.messages);
3284
- const options = [...buckets.values()].sort((a, b) => compareValues(a.value, b.value, column.type)).map(({ value, row, count }) => ({
3285
- value,
3286
- label: formatCellValue(value, column, locale, row, messages),
3287
- count
3288
- })).filter((option) => matchesSearch2(option.label, search));
3289
- const shown = limit === void 0 ? options : options.slice(0, limit);
4443
+ const covered = matched.filter(({ column }) => isHideable(column));
4444
+ const selectAll2 = selectAllStateOf(covered.map(({ visible }) => visible));
3290
4445
  return {
3291
- options: shown,
3292
- total: options.length,
3293
- truncated: shown.length < options.length,
3294
- blanks,
3295
- columnType: column.type
4446
+ entries,
4447
+ selectAll: selectAll2,
4448
+ toggleAll: toggleAllOf(covered, selectAll2, shownCount)
3296
4449
  };
3297
4450
  }
3298
- function keyOf2(draft, value) {
3299
- return filterValueKey(value, draft.columnType);
3300
- }
3301
- function everyOption(draft) {
3302
- return new Map(
3303
- draft.options.map((option) => [keyOf2(draft, option.value), option.value])
3304
- );
4451
+ function toggleAllOf(covered, selectAll2, shownCount) {
4452
+ const show = selectAll2 !== "checked";
4453
+ const changing = covered.filter(({ visible }) => visible !== show).map(({ column }) => column.id);
4454
+ const columnIds = !show && changing.length === shownCount ? changing.slice(1) : changing;
4455
+ return columnIds.length === 0 ? null : { visible: show, columnIds };
3305
4456
  }
3306
- function initialFilterEditorDraft(shape, values) {
3307
- const draft = {
3308
- mode: "values",
3309
- options: values.options,
3310
- blanks: values.blanks,
3311
- search: "",
3312
- checked: everyOption(values),
3313
- includeBlanks: values.blanks > 0,
3314
- conditions: [],
3315
- combinator: "and",
3316
- columnType: values.columnType
3317
- };
3318
- switch (shape.kind) {
3319
- case "values":
3320
- return {
3321
- ...draft,
3322
- checked: new Map(
3323
- shape.values.filter((value) => !isEmptyCell(value)).map((value) => [keyOf2(draft, value), value])
3324
- ),
3325
- includeBlanks: shape.includeBlanks
3326
- };
3327
- case "conditions":
3328
- return {
3329
- ...draft,
3330
- mode: "conditions",
3331
- conditions: shape.conditions,
3332
- combinator: shape.combinator
3333
- };
3334
- case "none":
3335
- case "custom":
3336
- return draft;
4457
+
4458
+ // src/filterMenu/conditions.ts
4459
+ var ROW_COUNT = 2;
4460
+ function rowOf(condition, type) {
4461
+ const { operator, value } = condition;
4462
+ if (isScalarList(value)) {
4463
+ const [low, high] = value;
4464
+ return {
4465
+ operator,
4466
+ text: formatFilterOperand(low, type),
4467
+ textTo: formatFilterOperand(high, type)
4468
+ };
3337
4469
  }
4470
+ return { operator, text: formatFilterOperand(value, type), textTo: "" };
3338
4471
  }
3339
- function visibleFilterOptions(draft) {
3340
- return draft.search === "" ? draft.options : draft.options.filter(
3341
- (option) => matchesSearch2(option.label, draft.search)
4472
+ function conditionRowsFrom(shape, operators, type) {
4473
+ const filled = shape.kind === "conditions" ? shape.conditions.map((condition) => rowOf(condition, type)) : [];
4474
+ const [first] = operators;
4475
+ if (first === void 0) {
4476
+ return filled;
4477
+ }
4478
+ const blank = { operator: first, text: "", textTo: "" };
4479
+ return Array.from(
4480
+ { length: ROW_COUNT },
4481
+ (_, index) => filled[index] ?? blank
3342
4482
  );
3343
4483
  }
3344
- function selectAllCoversBlanks(draft) {
3345
- return draft.search === "" && draft.blanks > 0;
3346
- }
3347
- function selectAllState(draft) {
3348
- const states = visibleFilterOptions(draft).map(
3349
- (option) => draft.checked.has(keyOf2(draft, option.value))
3350
- );
3351
- if (selectAllCoversBlanks(draft)) {
3352
- states.push(draft.includeBlanks);
3353
- }
3354
- if (states.length > 0 && states.every(Boolean)) {
3355
- return "checked";
4484
+ function isEmptyRow(row) {
4485
+ switch (filterOperandArity(row.operator)) {
4486
+ case 0:
4487
+ return false;
4488
+ case 1:
4489
+ return row.text === "";
4490
+ case 2:
4491
+ return row.text === "" && row.textTo === "";
4492
+ }
4493
+ }
4494
+ function conditionOf(row, columnId, type) {
4495
+ const { operator } = row;
4496
+ switch (filterOperandArity(operator)) {
4497
+ case 0:
4498
+ return { columnId, operator };
4499
+ case 1: {
4500
+ const value = parseFilterOperand(row.text, type);
4501
+ return value === void 0 ? void 0 : { columnId, operator, value };
4502
+ }
4503
+ case 2: {
4504
+ const low = parseFilterOperand(row.text, type);
4505
+ const high = parseFilterOperand(row.textTo, type);
4506
+ return low === void 0 || high === void 0 ? void 0 : { columnId, operator, value: [low, high] };
4507
+ }
3356
4508
  }
3357
- return states.some(Boolean) ? "mixed" : "unchecked";
3358
4509
  }
3359
- function toggleAll(draft) {
3360
- const check = selectAllState(draft) !== "checked";
3361
- const checked = new Map(draft.checked);
3362
- for (const option of visibleFilterOptions(draft)) {
3363
- const key = keyOf2(draft, option.value);
3364
- if (check) {
3365
- checked.set(key, option.value);
4510
+ function conditionsFromRows(rows, columnId, type) {
4511
+ const conditions = [];
4512
+ const invalid = [];
4513
+ for (const [index, row] of rows.entries()) {
4514
+ if (index > 0 && isEmptyRow(row)) {
4515
+ continue;
4516
+ }
4517
+ const condition = conditionOf(row, columnId, type);
4518
+ if (condition === void 0) {
4519
+ invalid.push(index);
3366
4520
  } else {
3367
- checked.delete(key);
4521
+ conditions.push(condition);
3368
4522
  }
3369
4523
  }
3370
- return {
3371
- ...draft,
3372
- mode: "values",
3373
- checked,
3374
- includeBlanks: selectAllCoversBlanks(draft) ? check : draft.includeBlanks
3375
- };
4524
+ return { conditions, invalid };
3376
4525
  }
3377
- function filterEditorReducer(draft, action) {
3378
- switch (action.type) {
3379
- case "search":
3380
- return { ...draft, search: action.search };
3381
- case "toggleValue": {
3382
- const key = keyOf2(draft, action.value);
3383
- const checked = new Map(draft.checked);
3384
- if (checked.has(key)) {
3385
- checked.delete(key);
3386
- } else {
3387
- checked.set(key, action.value);
3388
- }
3389
- return { ...draft, mode: "values", checked };
3390
- }
3391
- case "toggleAll":
3392
- return toggleAll(draft);
3393
- case "setIncludeBlanks":
3394
- return { ...draft, mode: "values", includeBlanks: action.includeBlanks };
3395
- case "setConditions":
3396
- return {
3397
- ...draft,
3398
- mode: "conditions",
3399
- conditions: action.conditions,
3400
- combinator: action.combinator ?? draft.combinator
3401
- };
3402
- case "clear":
3403
- return {
3404
- ...draft,
3405
- search: "",
3406
- checked: everyOption(draft),
3407
- includeBlanks: draft.blanks > 0,
3408
- conditions: []
3409
- };
3410
- }
4526
+
4527
+ // src/filterMenu/valueCache.ts
4528
+ function sameSource(a, b) {
4529
+ return a.rows === b.rows && a.dataColumns === b.dataColumns && a.locale === b.locale && a.messages === b.messages;
3411
4530
  }
3412
- function checksEverything(draft) {
3413
- return draft.options.every(
3414
- (option) => draft.checked.has(keyOf2(draft, option.value))
3415
- ) && (draft.includeBlanks || draft.blanks === 0);
4531
+ function sameEntries(a, b) {
4532
+ return a.length === b.length && a.every((entry, index) => entry === b[index]);
3416
4533
  }
3417
- function draftEntries(draft, columnId) {
3418
- if (draft.mode === "conditions") {
3419
- const conditions = draft.conditions.map((condition) => ({
3420
- ...condition,
3421
- columnId
3422
- }));
3423
- return conditions.length > 1 ? [{ combinator: draft.combinator, entries: conditions }] : conditions;
3424
- }
3425
- if (checksEverything(draft)) {
3426
- return [];
3427
- }
3428
- const picked = {
3429
- columnId,
3430
- operator: "in",
3431
- value: [...draft.checked.values()]
3432
- };
3433
- return draft.includeBlanks ? [
3434
- {
3435
- combinator: "or",
3436
- entries: [picked, { columnId, operator: "isEmpty" }]
4534
+ function createColumnValueOptionsCache() {
4535
+ let source;
4536
+ const lists = /* @__PURE__ */ new Map();
4537
+ return {
4538
+ get: (rows, dataColumns, filter, columnId, query = {}) => {
4539
+ const { locale, messages } = query;
4540
+ const current = { rows, dataColumns, locale, messages };
4541
+ if (source === void 0 || !sameSource(source, current)) {
4542
+ lists.clear();
4543
+ source = current;
4544
+ }
4545
+ const others = filter.filter((entry) => !touchesColumn(entry, columnId));
4546
+ const cached2 = lists.get(columnId);
4547
+ if (cached2 !== void 0 && sameEntries(cached2.others, others)) {
4548
+ return cached2.values;
4549
+ }
4550
+ const values = columnValueOptions(
4551
+ rows,
4552
+ dataColumns,
4553
+ filter,
4554
+ columnId,
4555
+ messages === void 0 ? { locale } : { locale, messages }
4556
+ );
4557
+ lists.set(columnId, { others, values });
4558
+ return values;
3437
4559
  }
3438
- ] : [picked];
4560
+ };
3439
4561
  }
3440
- function draftToFilter(draft, columnId, current) {
3441
- const replacement = draftEntries(draft, columnId);
3442
- const first = current.findIndex((entry) => touchesColumn(entry, columnId));
3443
- if (first === -1) {
3444
- return replacement.length === 0 ? current : [...current, ...replacement];
4562
+
4563
+ // src/filterMenu/checklist.ts
4564
+ function filterChecklist(draft, { valueLimit, blanksLabel }) {
4565
+ const visible = visibleFilterOptions(draft);
4566
+ const total = visible.length;
4567
+ if (draft.search === "" && total > valueLimit) {
4568
+ return { items: [], needsSearch: true, hiddenCount: 0, total };
4569
+ }
4570
+ const items = visible.slice(0, valueLimit).map(({ value, label, count }) => {
4571
+ const key = keyOf2(draft, value);
4572
+ const checked = draft.checked.has(key);
4573
+ return { kind: "value", key, value, label, count, checked };
4574
+ });
4575
+ const hiddenCount = total - items.length;
4576
+ if (selectAllCoversBlanks(draft)) {
4577
+ items.push({
4578
+ kind: "blanks",
4579
+ // Blank cells' own key, which no offered value has.
4580
+ key: keyOf2(draft, null),
4581
+ label: blanksLabel,
4582
+ count: draft.blanks,
4583
+ checked: draft.includeBlanks
4584
+ });
3445
4585
  }
3446
- const kept = current.filter((entry) => !touchesColumn(entry, columnId));
3447
- return [...kept.slice(0, first), ...replacement, ...kept.slice(first)];
4586
+ return { items, needsSearch: false, hiddenCount, total };
3448
4587
  }
3449
4588
 
3450
4589
  // src/util/virtualization.ts
@@ -3487,8 +4626,15 @@ function visibleRange(heights, estimatedHeight, scrollTop, viewportHeight, overs
3487
4626
  offsetAfter
3488
4627
  };
3489
4628
  }
4629
+
4630
+ // src/presets.ts
4631
+ var excelFilter = {
4632
+ filterableColumns: true,
4633
+ filterMenu: { ui: "excel", showCounts: true }
4634
+ };
3490
4635
  export {
3491
4636
  DEFAULT_COLUMN_SIZES,
4637
+ DEFAULT_FILTER_VALUE_LIMIT,
3492
4638
  HEADER_ROW,
3493
4639
  KEYBOARD_STEP,
3494
4640
  accessDotted,
@@ -3498,7 +4644,9 @@ export {
3498
4644
  applyColumnVisibility,
3499
4645
  applySelectionIntent,
3500
4646
  beginColumnResize,
4647
+ buildExportTable,
3501
4648
  buildKeyShortcuts,
4649
+ buildPrintDocument,
3502
4650
  builtInCommands,
3503
4651
  clampColumnWidth,
3504
4652
  clampFocus,
@@ -3508,34 +4656,51 @@ export {
3508
4656
  columnValueOptions,
3509
4657
  compareValues,
3510
4658
  computeAggregates,
4659
+ conditionRowsFrom,
4660
+ conditionsFromRows,
3511
4661
  contextMenuEntries,
4662
+ copyContent,
4663
+ createColumnValueOptionsCache,
3512
4664
  createCommandContext,
4665
+ createDefaultMessages,
3513
4666
  createGridController,
3514
4667
  createGridViewResolver,
4668
+ csvWriter,
3515
4669
  defaultContextMenu,
4670
+ defaultFilterOperators,
3516
4671
  defaultMessages,
3517
4672
  defaultToolbar,
3518
4673
  defineColumnsFromRows,
3519
4674
  describeColumnFilter,
3520
4675
  diffSelection,
3521
4676
  draftToFilter,
4677
+ excelFilter,
3522
4678
  expandAllGroups,
4679
+ exportFile,
4680
+ exportPrintDocument,
4681
+ filterChecklist,
3523
4682
  filterEditorReducer,
4683
+ filterOperandArity,
4684
+ filterOperatorFamily,
3524
4685
  filterQueryFor,
3525
4686
  filterRows,
3526
4687
  fitColumnsToWidth,
3527
4688
  fitToolbarItems,
3528
4689
  formatAggregateValue,
3529
4690
  formatCellValue,
4691
+ formatFilterOperand,
4692
+ formatGroupValue,
3530
4693
  getColumnId,
3531
4694
  groupRowId,
3532
4695
  groupRows,
3533
4696
  hasContextMenu,
3534
4697
  initialFilterEditorDraft,
3535
4698
  intentOf,
4699
+ isColumnFiltered,
3536
4700
  isColumnVisible,
3537
4701
  isPinnedToolbarItem,
3538
4702
  isSameCell,
4703
+ jsonWriter,
3539
4704
  matchesQuery,
3540
4705
  moveColumnBefore,
3541
4706
  moveGroupByBefore,
@@ -3545,9 +4710,11 @@ export {
3545
4710
  nextMenuIndex,
3546
4711
  paginateRows,
3547
4712
  paginationWindow,
4713
+ parseFilterOperand,
3548
4714
  resolveCell,
3549
4715
  resolveColumnChooser,
3550
4716
  resolveColumnConstraints,
4717
+ resolveColumnFilterMenu,
3551
4718
  resolveColumnHeaderText,
3552
4719
  resolveColumnLabel,
3553
4720
  resolveColumnWidths,
@@ -3555,6 +4722,7 @@ export {
3555
4722
  resolveCommands,
3556
4723
  resolveDataColumns,
3557
4724
  resolveDropBefore,
4725
+ resolveExportWriters,
3558
4726
  resolveGridView,
3559
4727
  resolveKeyboardDropTarget,
3560
4728
  resolveMenu,
@@ -3580,6 +4748,10 @@ export {
3580
4748
  sortPriorityFor,
3581
4749
  sortRows,
3582
4750
  splitToolbarItems,
4751
+ toCsv,
4752
+ toHtmlTable,
4753
+ toJson,
4754
+ toTsv,
3583
4755
  toggleCellSelection,
3584
4756
  toggleColumnSort,
3585
4757
  toggleGroupExpansion,