@opencorestack/opengridx 3.2.2 → 3.4.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/CHANGELOG.md +53 -0
- package/README.md +16 -2
- package/dist/ai.cjs +1 -0
- package/dist/ai.cjs.map +1 -0
- package/dist/ai.d.ts +166 -0
- package/dist/ai.es.js +1 -0
- package/dist/ai.es.js.map +1 -0
- package/dist/index.d.ts +321 -3
- package/dist/opengridx.css +1 -1
- package/dist/opengridx.es.js +6483 -5291
- package/dist/opengridx.es.js.map +1 -1
- package/dist/opengridx.umd.js +16 -13
- package/dist/opengridx.umd.js.map +1 -1
- package/docs/API_REFERENCE.md +132 -3
- package/docs/README.md +4 -1
- package/docs/architecture/datagrid-orchestration.md +23 -6
- package/docs/components/header.md +25 -0
- package/docs/customization/theming.md +12 -1
- package/docs/features/ai-toolkit.md +119 -0
- package/docs/features/cell-selection.md +131 -0
- package/docs/features/clipboard.md +90 -0
- package/docs/features/editing-reordering.md +50 -0
- package/docs/features/keyboard-navigation.md +25 -2
- package/docs/features/undo-redo.md +79 -0
- package/docs/roadmap.md +64 -7
- package/lib/ai/coerce.ts +90 -0
- package/lib/ai/columns.ts +90 -0
- package/lib/ai/index.ts +16 -0
- package/lib/ai/schema.ts +214 -0
- package/lib/ai/types.ts +88 -0
- package/lib/ai/validate.ts +286 -0
- package/lib/components/Cell/Cell.css +35 -0
- package/lib/components/Cell/Cell.tsx +25 -13
- package/lib/components/Cell/CellErrorBoundary.tsx +3 -0
- package/lib/components/Cell/GridEditInputCell.css +12 -4
- package/lib/components/Cell/GridEditInputCell.tsx +19 -12
- package/lib/components/CellSelectionStats/CellSelectionStats.css +31 -0
- package/lib/components/CellSelectionStats/CellSelectionStats.tsx +32 -0
- package/lib/components/CellSelectionStats/GridCellSelectionStatsArea.tsx +24 -0
- package/lib/components/ColumnVisibilityPanel/ColumnVisibilityPanel.css +14 -0
- package/lib/components/ColumnVisibilityPanel/ColumnVisibilityPanel.tsx +3 -2
- package/lib/components/DataGrid/DataGrid.tsx +123 -3
- package/lib/components/DataGrid/GridLiveRegion.tsx +8 -2
- package/lib/components/DataGrid/GridPinnedRows.tsx +9 -0
- package/lib/components/DataGrid/GridToolbarSlot.tsx +5 -0
- package/lib/components/DataGrid/GridVirtualRows.tsx +9 -0
- package/lib/components/FilterPanel/FilterPanel.css +14 -0
- package/lib/components/FilterPanel/FilterPanel.tsx +5 -3
- package/lib/components/Header/Header.css +12 -0
- package/lib/components/Header/Header.tsx +17 -4
- package/lib/components/ListView/ListView.css +2 -4
- package/lib/components/ListView/ListViewRow.tsx +2 -2
- package/lib/components/Row/Row.tsx +38 -1
- package/lib/components/Toolbar/GlobalSearch.css +16 -0
- package/lib/components/Toolbar/GlobalSearch.tsx +3 -2
- package/lib/components/Toolbar/GridToolbar.tsx +10 -4
- package/lib/components/ui/Checkbox.tsx +26 -10
- package/lib/components/ui/Input.css +43 -0
- package/lib/components/ui/Input.tsx +24 -5
- package/lib/hooks/core/gridApiPlaceholder.ts +14 -1
- package/lib/hooks/core/useDataGrid.ts +27 -0
- package/lib/hooks/core/useGridCellSelection.ts +631 -0
- package/lib/hooks/core/useGridCellSelectionApi.ts +97 -0
- package/lib/hooks/core/useGridClipboardApi.ts +8 -2
- package/lib/hooks/core/useGridClipboardPaste.ts +283 -0
- package/lib/hooks/core/useGridControlledState.ts +38 -0
- package/lib/hooks/core/useGridKeyboardNavigation.ts +32 -0
- package/lib/hooks/core/useGridRowInteractions.ts +8 -4
- package/lib/hooks/core/useGridUndoRedo.ts +220 -0
- package/lib/hooks/features/useAggregation.ts +8 -6
- package/lib/hooks/features/useGridBatchEdit.ts +217 -0
- package/lib/hooks/features/useGridClipboard.ts +47 -2
- package/lib/hooks/features/useGridEditing.ts +39 -51
- package/lib/index.ts +16 -0
- package/lib/styles/base/variables.css +6 -1
- package/lib/styles/opengridx.css +3 -1
- package/lib/theme/DataGridThemeProvider.tsx +4 -0
- package/lib/theme/presets.ts +8 -0
- package/lib/theme/types.ts +4 -0
- package/lib/types/index.ts +254 -0
- package/lib/utils/attempt.ts +14 -0
- package/lib/utils/cellSelection.ts +238 -0
- package/lib/utils/editing/commit.ts +69 -0
- package/lib/utils/editing/targets.ts +109 -0
- package/lib/utils/headerWrap.ts +23 -0
- package/lib/utils/parsing.ts +252 -0
- package/llms.txt +71 -2
- package/package.json +14 -4
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,59 @@
|
|
|
5
5
|
|
|
6
6
|
---
|
|
7
7
|
|
|
8
|
+
## [3.4.0] — 2026-10-08
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- **`Input` forwards `ref`** to its `<input>` (`React.forwardRef`), so `ref` works on React 18 as well as 19 (before, it only reached the `<input>` on React 19, through the props spread). `Input.displayName` is `'Input'`.
|
|
13
|
+
- **`Input` `variant`** — `'field'` (default, unchanged look) or `'cell'`: the wrapper fills its container (width and height 100%, `display: flex`) with no border, radius, background or focus shadow, and the `<input>` uses the cell's font and the editor padding. For custom cell editors in `renderEditCell`; adornments and `error` (an inset `--ogx-color-error` edge, drawn over the editing cell's outline) still work. New public class `ogx-input-wrapper--cell`. An editing cell that holds one stretches its content to the full cell height.
|
|
14
|
+
- **`Input` `inputClassName`** — classes for the `<input>` itself; `className` still goes to the wrapper `<div>`. `error` now also sets `aria-invalid="true"` on the `<input>`.
|
|
15
|
+
- **`Checkbox` forwards `ref`** to its `<input>` (alongside `inputRef`), and takes **`inputClassName`** for the `<input>`; `onMouseDown`, `tabIndex`, `aria-*` and other props reach the `<input>` as before.
|
|
16
|
+
- Demo page *Custom Cell Editors* (`/custom-editors`): price and weight editors built on `<Input variant="cell">` with a `$` / `kg` adornment and an error state; guide section "Building custom editors with `Input`" in `docs/features/editing-reordering.md`.
|
|
17
|
+
- **Clipboard paste** — with `cellSelection` on, Ctrl/Cmd+V pastes tab-separated text (Excel, Google Sheets) into the editable cells: one value fills a range, a block extends from the range's top-left cell (or the focused cell) and is cut to the range and the grid edges; no rows are created. The `paste` event is read, so no clipboard permission is asked. Each cell is parsed for its column type (`1,234.5`, `12%`, currency symbols, ISO and `Date.parse` dates keeping the cell's kind, `yes`/`no`/`1`/`0`, `singleSelect` values or labels; empty → `null`, `''` for strings) and written through `valueSetter` and `processRowUpdate` once per row. Non-editable cells, synthetic rows and span-covered cells are skipped (`'notEditable'`), text that does not parse too (`'invalidValue'`). The pasted area becomes the selection. New props `disableClipboardPaste`, `onClipboardPaste(result)` (`{ updated, failed, skipped, text }`) and `onBeforeClipboardPaste({ text, anchor })` (return `false` to cancel or a string to replace the text); new `apiRef.pasteText(text, anchor?)`; development warning above 10,000 cells. Guide: `docs/features/clipboard.md`. Demo page *Paste from Excel* (`/clipboard-paste`).
|
|
18
|
+
- **`GridColDef.valueParser(text, { row, field, colDef })`** — replaces the paste parser for one column (decimal-comma locales, custom codes); a throw or `undefined` skips the cell.
|
|
19
|
+
- **Clear a range with Delete** — with `cellSelection`, Delete / Backspace on a range of more than one cell empties its editable cells (`''` for strings, `null` otherwise) as one edit. `disableRangeClear` turns it off.
|
|
20
|
+
- **Undo / redo** — `undoRedo` (`boolean | { limit }`, default off, 100 actions) records committed edits, pastes and range clears (only after `processRowUpdate` succeeded, only the fields the user changed) and replays them through `valueSetter` and `processRowUpdate`. Ctrl/Cmd+Z, Ctrl/Cmd+Shift+Z and Ctrl+Y while focus is on a cell and no editor is open; the affected cells become the selection. A cell that changed since it was recorded is skipped (`'changed'`, values compared, not row objects), a removed row too (`'missing'`). New `onHistoryChange({ canUndo, canRedo, size })` and `apiRef.undo()`, `redo()`, `canUndo()`, `canRedo()`, `clearHistory()`. Guide: `docs/features/undo-redo.md`. Demo page *Undo & Redo* (`/undo-redo`).
|
|
21
|
+
- **Batch edits** — paste, range clear, undo and redo commit through one internal path (`useGridBatchEdit`): one `processRowUpdate` per row, rows in parallel, a failing row reported to `onProcessRowUpdateError` without blocking the others, and every successful row stored in **one** state update (a 1,000-row paste is one re-render). Result type `GridBatchEditResult` (`{ updated, failed, skipped }`) with `GridEditSkipReason`. New public types: `GridValueParserParams`, `GridEditSkipReason`, `GridBatchEditResult`, `GridClipboardPasteResult`, `GridBeforeClipboardPasteParams`, `GridHistoryChangeParams`, `GridUndoRedoOptions`.
|
|
22
|
+
- Package smoke suite: `paste` scenario (paste, undo, redo with the real clipboard) in both fixtures.
|
|
23
|
+
- **AI toolkit, part 1: `@opencorestack/opengridx/ai`** — a new entry point (`dist/ai.es.js`, `dist/ai.cjs`, `dist/ai.d.ts`; about 4.5 kB gzipped) with no React import and no dependencies; the core bundle does not contain it, and no AI service or SDK is involved.
|
|
24
|
+
- `getGridAiSchema(columns, options?)` returns a JSON Schema (draft 2020-12, `schemaVersion: 1`) of the `filterModel` (nested AND/OR groups up to 3 levels, `quickFilterValues`), `sortModel`, `rowGroupingModel`, `aggregationModel`, `pivotModel` and `columnVisibilityModel` the columns allow: fields from `filterable` / `sortable` / `groupable` / `hideable` / `aggregable`, operators per column type, functions from `availableAggregationFunctions`, `valueOptions` as enums. `options.include` / `exclude` choose parts; `options.descriptions` (default on) adds `headerName` and `description`. No row data is read; the output is deterministic.
|
|
25
|
+
- `validateGridAiState(json, columns, options?)` returns `{ state, errors }`: unknown parts, fields, operators and enum values are dropped with `{ path, message }` errors, values are coerced to the column type (`"1,200"` → 1200, `"12%"` → 0.12, yes/no → booleans, dates → `YYYY-MM-DD`, singleSelect by value or label), empty filter groups are removed. Accepts an object or a JSON string and never throws.
|
|
26
|
+
- **`GridColDef.aiExamples`** — opt-in example values for the schema. They are sent to the model, so list only data you are happy to share.
|
|
27
|
+
- Types `GridAiColumn`, `GridAiPart`, `GridAiPartOptions`, `GridAiSchema`, `GridAiSchemaOptions`, `GridAiJsonSchema`, `GridAiState`, `GridAiValidateOptions`, `GridAiValidationError`, `GridAiValidationResult`, exported from `/ai`.
|
|
28
|
+
- Demo page *AI: Grid Schema & Validator* (`/ai-schema`) and guide `docs/features/ai-toolkit.md`. `check-bundle` enforces the 5 kB budget and that the ai bundles import nothing; the package smoke suite imports `/ai` in both fixtures.
|
|
29
|
+
|
|
30
|
+
### Changed
|
|
31
|
+
|
|
32
|
+
- The toolbar's global search, the column panel's search box, the filter panel's text/date value box and the built-in text, number and date cell editors render through `Input`, so their `<input>` now sits inside a `div.ogx-input-wrapper` and also has the class `ogx-input`. **Selectors with a child combinator such as `.ogx-global-search > input` or `.ogx-filter__controls > input` must become descendant selectors** (`.ogx-global-search input`). The class names on the inputs (`ogx-global-search__input`, `ogx-column-visibility-panel__search-input`, `ogx-filter__value-input`, `ogx__edit-input`, `ogx__edit-input--number`), their ids, names and ARIA attributes are unchanged, and they look the same.
|
|
33
|
+
- The boolean cell editor and the list view's row checkbox are the shared `Checkbox` (the drawn box used by the row-selection column) instead of a native checkbox. The editor's `<input>` keeps `ogx__edit-checkbox` but is visually hidden; click the box (`.ogx-checkbox__box`). The Safari fix from 3.2.1 is kept: mousedown anywhere in the boolean editor is prevented, so focus stays in the editor until the click toggles it. The `.ogx-list-view__checkbox input[type="checkbox"]` size / `accent-color` rule is gone.
|
|
34
|
+
- `Input.css` now loads before the component styles in `opengridx.css` (it was after `ColumnVisibilityPanel.css` and `GridEditInputCell.css`), so component rules that restyle `Input` win at equal specificity. Nothing else in the library's CSS styled `.ogx-input*`.
|
|
35
|
+
- The built-in text, number and date editors now fill the editing cell's full height (they were as tall as their text); the look is unchanged.
|
|
36
|
+
- `GridApi` has six new members (`pasteText`, `undo`, `redo`, `canUndo`, `canRedo`, `clearHistory`); an object written to satisfy `GridApi` by hand must add them. `useGridApiRef()`'s placeholder has no-op versions.
|
|
37
|
+
- A committed single-cell edit is stored through the same path as batch edits; behaviour is unchanged.
|
|
38
|
+
|
|
39
|
+
## [3.3.0] — 2026-10-07
|
|
40
|
+
|
|
41
|
+
### Added
|
|
42
|
+
|
|
43
|
+
- **Wrapped header text** — new grid prop `wrapHeaderText` and per-column `GridColDef.wrapHeaderText` (the column wins). Long header titles wrap instead of being cut with an ellipsis, within the existing `headerHeight`: 2 lines at 56px, 3 at 72px, 4 at 88px, with an ellipsis on the last line if the title is still longer. The header never grows on its own; raise `headerHeight` for more lines. Wrapped header cells get the new class `ogx__header-cell--wrap` and show their full title as the `title` tooltip unless the column has a `description`. Sort icon, column menu button and resize handle are unchanged, and column auto-size still fits the title on one line. Demo: *Wrapped Header Text* (`/header-wrap`).
|
|
44
|
+
- **Cell range selection** (`cellSelection`) — select a rectangle of cells as in Excel or Google Sheets: press and drag (the grid auto-scrolls over virtualized rows and columns when the pointer nears or leaves an edge), Shift+click, Shift+Arrow (no row wrap), Shift+Home/End, Ctrl+Shift+Home/End, Shift+PageUp/PageDown, Ctrl/Cmd+A (every data cell of the page) and Escape (collapse to the anchor). Ctrl/Cmd+C copies the range (the focused cell at minimum) as TSV, formatted as shown, without a header line; `apiRef.current.copySelectedRows()` still copies rows. Ranges never change the row selection; the checkbox, detail-panel, reorder and `__group__` columns never join a range. Group rows, subtotals, tree auto-parents and the pivot Grand Total are highlighted but not copied. A range touching part of a `colSpan` / `rowSpan` grows to cover it. Off by default: grids without the prop are unchanged.
|
|
45
|
+
- **`cellSelectionModel` / `onCellSelectionModelChange(model, { reason })`** — controlled or uncontrolled model of `{ anchor, head }` corners addressed by row id and field, so a range follows its rows through sorting; it is cleared (`reason: 'dataChange'`) when a corner is no longer displayed. New types `GridCellCoordinates`, `GridCellRange`, `GridCellSelectionModel`, `GridCellSelectionReason`, `GridCellSelectionChangeDetails`, `GridSelectedCell`.
|
|
46
|
+
- **`GridApi`**: `getCellSelectionModel`, `setCellSelectionModel`, `selectCellRange`, `clearCellSelection`, `getSelectedCells`, `copySelectedCells`.
|
|
47
|
+
- **`showCellSelectionStats`** — a status bar with the Count, Sum and Average of the range (shared aggregation functions), replaceable through `slots.cellSelectionStats` (`GridCellSelectionStatsSlotProps`); the default component is exported as `CellSelectionStats`.
|
|
48
|
+
- **Accessibility** — range cells carry `aria-selected="true"`, focus stays on the anchor, and the live region announces the size ("12 cells selected, 3 rows by 4 columns", debounced 300 ms); translate it with `localeText.cellSelectionAnnouncement`.
|
|
49
|
+
- **Styling** — public classes `ogx__cell--range`, `ogx__cell--range-top|bottom|left|right` (inset outline, no layout shift) and `ogx--range-dragging`; CSS variables `--ogx-range-background` and `--ogx-range-border`, defined in every built-in theme and settable with the theme keys `grid.rangeBackground` / `grid.rangeBorder`.
|
|
50
|
+
- Demo page *Cell Range Selection* and guide `docs/features/cell-selection.md`.
|
|
51
|
+
|
|
52
|
+
### Changed
|
|
53
|
+
|
|
54
|
+
- `Row` is now memoised (its `cellRange` prop compared by value), and the row click / checkbox handlers keep their identity across renders, so rows whose props did not change no longer re-render.
|
|
55
|
+
|
|
56
|
+
### Internal
|
|
57
|
+
|
|
58
|
+
- **React Compiler compatibility checked** — the library works in apps that use React Compiler, and when `lib/` itself is compiled (unit and browser suites pass with `REACT_COMPILER=1`). Fixed what the compiler rejected: a prop mutated in `Checkbox`, a self-referencing callback in `useGridEditing`, memoization the compiler could not keep in `FilterPanel` / `GridToolbar`, value expressions inside `try` in `Cell` / `useAggregation` / `useGridEditing`, and `GridToolbar`'s `forceColumnsOpen` effect. `InlineToolbar` (`slots.toolbar`) and `CellRenderTarget` (`renderCell`) are marked `'use no memo'`: compiled, they skipped the consumer's toolbar / cell function on re-render and broke its hooks. ESLint uses `react-hooks` `recommended-latest` (7.1.1) with `immutability` and `preserve-manual-memoization` on; new `npm run check:compiler`. Findings, remaining issues and how to re-run: `docs/contributing/react-compiler.md`.
|
|
59
|
+
- **GitHub wiki** generated from `docs/` — `scripts/build-wiki.mjs` (`npm run wiki:build`) turns the docs, CHANGELOG and CONTRIBUTING into wiki pages with a sidebar, rewrites relative links to wiki pages (or GitHub URLs for source files and images), and adds hand-written Home, Getting Started and FAQ pages from `wiki/`. `.github/workflows/wiki.yml` publishes it on every push to `main` that touches them.
|
|
60
|
+
|
|
8
61
|
## [3.2.2] — 2026-10-01
|
|
9
62
|
|
|
10
63
|
### Security
|
package/README.md
CHANGED
|
@@ -76,6 +76,8 @@ Without a `height` prop the grid fills its container (`height: 100%`, or the fre
|
|
|
76
76
|
- **Fully Customizable**: Slots system for replacing any component (pagination, overlays, toolbar).
|
|
77
77
|
- **Export Functionality**: Built-in CSV, Excel (HTML `.xls`, or real `.xlsx` with the optional ExcelJS peer), JSON, PDF (optional jsPDF peer) and Print export.
|
|
78
78
|
- **Clipboard**: `Ctrl+C` / `Cmd+C` copies the selected rows' visible columns as TSV for Excel/Sheets.
|
|
79
|
+
- **Cell Range Selection** (v3.3): drag, Shift+click or Shift+arrows select a rectangle of cells; copy it as TSV and see its count, sum and average in a status bar (`cellSelection`, `showCellSelectionStats`).
|
|
80
|
+
- **AI Toolkit** (v3.4): `@opencorestack/opengridx/ai` turns your columns into a JSON Schema for your own model and validates its reply before the grid applies it (`getGridAiSchema`, `validateGridAiState`). No AI SDK, no row data sent by default.
|
|
79
81
|
- **Accessibility**: WCAG 2.1 AA — full ARIA roles and keyboard navigation.
|
|
80
82
|
- **Theming**: CSS variable API, `DataGridThemeProvider` with 5 built-in themes (`darkTheme`, `roseTheme`, `emeraldTheme`, `amberTheme`, `compactTheme`) and custom themes.
|
|
81
83
|
- **Column & Row Reordering**: Drag-and-drop column reordering and row reordering.
|
|
@@ -174,6 +176,9 @@ This means that **Cursor**, **GitHub Copilot**, **Windsurf**, and other AI agent
|
|
|
174
176
|
| `disableMultipleRowSelection` | `boolean` | Restrict to single-row selection. |
|
|
175
177
|
| `pinCheckboxColumn` | `boolean` | Keeps the checkbox column visible during horizontal scroll. Default `true`. |
|
|
176
178
|
| `disableClipboardCopy` | `boolean` | Turns off the grid's Ctrl/Cmd+C copy (`apiRef.current.copySelectedRows()` still works). |
|
|
179
|
+
| `cellSelection` | `boolean` | Spreadsheet-style cell range selection; Ctrl/Cmd+C then copies the range (v3.3). |
|
|
180
|
+
| `cellSelectionModel` / `onCellSelectionModelChange` | `GridCellSelectionModel` / `(model, { reason }) => void` | Controlled cell range. |
|
|
181
|
+
| `showCellSelectionStats` | `boolean` | Status bar with count, sum and average of the range. |
|
|
177
182
|
|
|
178
183
|
### Sorting & Filtering
|
|
179
184
|
|
|
@@ -316,6 +321,12 @@ apiRef.current.autosizeColumn('name') // fit to content (v3.1.0+)
|
|
|
316
321
|
// Clipboard
|
|
317
322
|
apiRef.current.copySelectedRows() // → Promise<void>, rejects if the clipboard write fails
|
|
318
323
|
|
|
324
|
+
// Cell range selection (cellSelection, v3.3.0+)
|
|
325
|
+
apiRef.current.selectCellRange({ id: 1, field: 'q1' }, { id: 12, field: 'q4' })
|
|
326
|
+
apiRef.current.getSelectedCells() // → { id, field, value }[]
|
|
327
|
+
apiRef.current.copySelectedCells() // → Promise<void>
|
|
328
|
+
apiRef.current.clearCellSelection()
|
|
329
|
+
|
|
319
330
|
// Aggregation
|
|
320
331
|
apiRef.current.getAggregationResult() // → Record<string, unknown> | null
|
|
321
332
|
apiRef.current.getAggregationModel() // → GridAggregationModel | null
|
|
@@ -490,7 +501,7 @@ Built-in themes: `darkTheme`, `roseTheme`, `emeraldTheme`, `amberTheme`, `compac
|
|
|
490
501
|
|
|
491
502
|
## 📚 Documentation
|
|
492
503
|
|
|
493
|
-
Full documentation at 👉 **[opencorestack.github.io/OpenGridX](https://opencorestack.github.io/OpenGridX/)**
|
|
504
|
+
Full documentation at 👉 **[opencorestack.github.io/OpenGridX](https://opencorestack.github.io/OpenGridX/)** · 📖 **[Wiki](https://github.com/OpenCoreStack/OpenGridX/wiki)**
|
|
494
505
|
|
|
495
506
|
### 🏛️ Components
|
|
496
507
|
- **[DataGrid](docs/components/datagrid.md)** — Main component props and slots
|
|
@@ -509,7 +520,10 @@ Full documentation at 👉 **[opencorestack.github.io/OpenGridX](https://opencor
|
|
|
509
520
|
- **[Infinite Scroll](docs/features/infinite-scroll.md)** — Seamless lazy-loading
|
|
510
521
|
- **[Export Guide](docs/features/export-guide.md)** — Excel, CSV, JSON, and Print
|
|
511
522
|
- **[PDF Export](docs/features/pdf-export.md)** — Branded PDF reports
|
|
512
|
-
- **[Clipboard](docs/features/clipboard.md)** — Copy rows as TSV
|
|
523
|
+
- **[Clipboard](docs/features/clipboard.md)** — Copy rows as TSV, paste from Excel / Sheets, clear a range with Delete
|
|
524
|
+
- **[Cell Range Selection](docs/features/cell-selection.md)** — Select, total and copy cell ranges
|
|
525
|
+
- **[Undo & Redo](docs/features/undo-redo.md)** — Undo and redo edits, pastes and range clears
|
|
526
|
+
- **[AI Toolkit](docs/features/ai-toolkit.md)** — Schema and validator for driving the grid from your own model
|
|
513
527
|
- **[Keyboard & Accessibility](docs/features/keyboard-navigation.md)** — Keys, focus and ARIA
|
|
514
528
|
- **[Data Source](docs/features/data-source.md)** — Server-side integration
|
|
515
529
|
- **[Loading States](docs/features/loading-states.md)** — Skeleton and shimmer overlays
|
package/dist/ai.cjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const z=/^(\d{4})-(\d{2})-(\d{2})$/;function T(e){if(e==null)return null;let t;if(e instanceof Date)t=e;else if(typeof e=="number")t=new Date(e);else if(typeof e=="string"){const n=e.trim();if(n==="")return null;const i=z.exec(n);if(i){const r=Number(i[1]),o=Number(i[2])-1,l=Number(i[3]);if(t=new Date(r,o,l),t.getMonth()!==o||t.getDate()!==l)return null}else t=new Date(n)}else return null;return Number.isNaN(t.getTime())?null:t}function Q(e){const t=T(e);if(!t)return"";const n=String(t.getFullYear()).padStart(4,"0"),i=String(t.getMonth()+1).padStart(2,"0"),r=String(t.getDate()).padStart(2,"0");return`${n}-${i}-${r}`}function Z(e){switch(e){case"number":return["=","!=",">",">=","<","<=","isEmpty","isNotEmpty"];case"date":return["is","not","after","onOrAfter","before","onOrBefore","isEmpty","isNotEmpty"];case"boolean":return["is"];case"singleSelect":return["isAnyOf","is","not"];default:return["contains","equals","startsWith","endsWith","isEmpty","isNotEmpty"]}}const x=["sum","avg","count","min","max","unique"],I=["sum","avg","count","min","max"],G=["isEmpty","isNotEmpty"],E=3,j=["filter","sort","grouping","aggregation","pivot","columnVisibility"],w={filter:"filterModel",sort:"sortModel",grouping:"rowGroupingModel",aggregation:"aggregationModel",pivot:"pivotModel",columnVisibility:"columnVisibilityModel"};function v(e,t){return Object.prototype.hasOwnProperty.call(e,t)}function $(e){return e!==null&&typeof e=="object"&&!Array.isArray(e)}function R(e){const t=Array.isArray(e?.include)?e.include:j,n=Array.isArray(e?.exclude)?e.exclude:[];return j.filter(i=>t.includes(i)&&!n.includes(i))}function q(e){if(!Array.isArray(e))return[];const t=new Set,n=[];for(const i of e){if(!$(i))continue;const r=i.field;typeof r!="string"||r===""||r.startsWith("__")||i.isSpacer===!0||t.has(r)||(t.add(r),n.push(i))}return n}function W(e){return Z(e.type)}const C=e=>e.filterable!==!1,U=e=>e.sortable!==!1,A=e=>e.groupable!==!1,B=e=>e.hideable!==!1;function b(e,t=x){if(e.aggregable===!1)return[];const n=e.type==="number"||e.aggregable===!0?t:t.filter(r=>r==="count"),i=e.availableAggregationFunctions;return Array.isArray(i)?n.filter(r=>i.includes(r)):[...n]}function ee(e){const t=[];if(!Array.isArray(e.valueOptions))return t;for(const n of e.valueOptions){const i=n!==null&&typeof n=="object"?n.value:n;(typeof i=="string"||typeof i=="number"&&Number.isFinite(i))&&!t.includes(i)&&t.push(i)}return t}const te="OpenGridX grid state",re=10;function O(e){const t=typeof e.headerName=="string"&&e.headerName!==""?e.headerName:e.field,n=typeof e.description=="string"&&e.description!==""?` (${e.description})`:"";return`${t}${n}`}function m(e,t,n){return n.descriptions?{...e,description:t}:e}function ne(e,t){return!t.examples||!Array.isArray(e.aiExamples)?[]:e.aiExamples.filter(n=>typeof n=="string"||typeof n=="boolean"||typeof n=="number"&&Number.isFinite(n)).slice(0,re)}function ie(e,t){let n;switch(e.type){case"number":n={type:"number"};break;case"date":n={type:"string",format:"date"};break;case"boolean":n={type:"boolean"};break;case"singleSelect":{const r=ee(e),o=r.length>0?{enum:r}:{type:"string"};n={anyOf:[o,{type:"array",items:o}]};break}default:n={type:"string"}}const i=ne(e,t);return i.length>0?{...n,examples:i}:n}function oe(e,t){const n=W(e),i=n.filter(l=>G.includes(l)),r=e.type==="singleSelect"?"; isAnyOf takes an array":"",o=i.length>0?`${i.join(" and ")} take no value${r}`:r.slice(2);return m({type:"object",properties:{field:{const:e.field},operator:{enum:n},value:ie(e,t)},required:["field","operator"],additionalProperties:!1},o?`${O(e)}: ${o}`:O(e),t)}function se(e,t,n){const i=e.filter(C),r={},o={enum:["and","or"]};if(i.length>0){n.filterItem={oneOf:i.map(l=>oe(l,t))};for(let l=E;l>=1;l--){const s=l===E?{$ref:"#/$defs/filterItem"}:{anyOf:[{$ref:"#/$defs/filterItem"},{$ref:`#/$defs/filterGroup${l+1}`}]};n[`filterGroup${l}`]={type:"object",properties:{logicOperator:o,items:{type:"array",items:s}},required:["logicOperator","items"],additionalProperties:!1}}r.items={type:"array",items:{anyOf:[{$ref:"#/$defs/filterItem"},{$ref:"#/$defs/filterGroup1"}]}},r.logicOperator=o}return r.quickFilterValues=m({type:"array",items:{type:"string"}},"Words a row must contain, in any column",t),{type:"object",properties:r,additionalProperties:!1}}function _(e){return{enum:e.map(t=>t.field)}}function le(e,t,n,i){switch(e){case"filter":return se(t,n,i);case"sort":{const r=t.filter(U);return r.length===0?null:m({type:"array",items:{type:"object",properties:{field:_(r),sort:{enum:["asc","desc"]}},required:["field","sort"],additionalProperties:!1}},"Most significant first",n)}case"grouping":{const r=t.filter(A);return r.length===0?null:m({type:"array",items:_(r),uniqueItems:!0},"Outermost first",n)}case"aggregation":{const r={};for(const o of t){const l=b(o);l.length>0&&(r[o.field]=m({enum:l},O(o),n))}return Object.keys(r).length===0?null:{type:"object",properties:r,additionalProperties:!1}}case"pivot":{const r=t.filter(A),o=t.map(s=>({col:s,fns:b(s,I)})).filter(s=>s.fns.length>0).map(({col:s,fns:u})=>m({type:"object",properties:{field:{const:s.field},aggFn:{enum:u}},required:["field","aggFn"],additionalProperties:!1},O(s),n));if(r.length===0||o.length===0)return null;const l={type:"array",items:_(r),uniqueItems:!0};return{type:"object",properties:{rowFields:l,columnFields:l,valueFields:{type:"array",items:{oneOf:o}}},required:["rowFields","columnFields","valueFields"],additionalProperties:!1}}case"columnVisibility":{const r={};for(const o of t.filter(B))r[o.field]=m({type:"boolean"},O(o),n);return Object.keys(r).length===0?null:m({type:"object",properties:r,additionalProperties:!1},"false hides a column",n)}}}function ue(e,t={}){const n={descriptions:t.descriptions!==!1,examples:t.examples!==!1},i=q(e),r={},o={};for(const s of R(t)){const u=le(s,i,n,r);u&&(o[w[s]]=u)}const l={$schema:"https://json-schema.org/draft/2020-12/schema",schemaVersion:1,title:te,type:"object",properties:o,additionalProperties:!1};return n.descriptions&&(l.description="Grid state to apply. Include only the parts the user asked to change."),Object.keys(r).length>0&&(l.$defs=r),l}const y={ok:!1},d=e=>({ok:!0,value:e}),fe=/^\d+(?:\.\d*)?(?:e[+-]?\d+)?$/i,ae=/^\.\d+(?:e[+-]?\d+)?$/i,ce=/^\d{1,3}(?:,\d{3})+(?:\.\d*)?$/,de=/[\p{Sc}\s]/gu;function pe(e){let t=e.replace(de,"");if(t==="")return e.trim()===""?d(null):y;let n=!1;t.startsWith("(")&&t.endsWith(")")&&(n=!0,t=t.slice(1,-1)),(t.startsWith("-")||t.startsWith("+")||t.startsWith("\u2212"))&&(t[0]!=="+"&&(n=!n),t=t.slice(1));let i=!1;if(t.endsWith("%")&&(i=!0,t=t.slice(0,-1)),ce.test(t))t=t.split(",").join("");else if(!fe.test(t)&&!ae.test(t))return y;let r=Number(t);return Number.isFinite(r)?(i&&(r/=100),n&&(r=-r),d(r)):y}function ge(e,t){const n=e.trim();if(n==="")return d(null);const i=T(n);return i?d(i):y}const me=new Set(["true","yes","1"]),ye=new Set(["false","no","0"]);function be(e,t){const n=e.trim().toLowerCase();return n===""?d(null):me.has(n)?d(!0):ye.has(n)?d(!1):y}function $e(e,t){const n=e.trim();if(n==="")return d(null);if(!t||t.length===0)return y;const i=n.toLowerCase();for(const r of t){const o=typeof r=="object"?r.value:r;if(String(o).toLowerCase()===i)return d(o)}for(const r of t)if(typeof r=="object"&&r.label.toLowerCase()===i)return d(r.value);return y}const a=e=>({ok:!0,value:e}),p={ok:!1,reason:"invalidValue"},N=e=>e.ok?a(e.value):p;function k(e){return e==null||typeof e=="string"&&e.trim()===""}function he(e){return e==null?a(""):typeof e=="string"?a(e):typeof e=="number"||typeof e=="boolean"?a(String(e)):p}function Se(e){return k(e)?a(null):typeof e=="number"?Number.isFinite(e)?a(e):p:typeof e!="string"?p:N(pe(e))}function Oe(e){return k(e)?a(null):e instanceof Date?Number.isNaN(e.getTime())?p:a(e):typeof e=="number"?Number.isFinite(e)?a(new Date(e)):p:typeof e!="string"?p:N(ge(e))}function X(e){return k(e)?a(null):typeof e=="boolean"?a(e):typeof e!="string"&&typeof e!="number"?p:N(be(String(e)))}function ve(e,t){if(k(e))return a(null);if(typeof e!="string"&&typeof e!="number"&&typeof e!="boolean")return p;const n=t.valueOptions;return!n||n.length===0?a(e):N($e(String(e),n))}function Ae(e,t){switch(t.type){case"number":return Se(e);case"date":return Oe(e);case"boolean":return X(e);case"singleSelect":return ve(e,t);default:return he(e)}}const P=200,Fe=100;function f(e,t,n){return e.errors.length<Fe&&e.errors.push({path:t,message:n}),null}function c(e){return typeof e=="string"?JSON.stringify(e.slice(0,60)):e===null||typeof e!="object"?String(e):Array.isArray(e)?"a list":"an object"}function H(e){return typeof e=="string"?e.trim().toLowerCase():null}function D(e){return Object.keys(e).filter(t=>t!=="__proto__")}function h(e,t,n,i,r){const o=typeof t=="string"?e.byField.get(t):void 0;return o?i(o)?o:f(e,n,`Field ${c(t)} cannot be ${r}`):f(e,n,`Unknown field ${c(t)}`)}function S(e,t,n){if(t==null)return[];const i=Array.isArray(t)?t:[t];return i.length>P&&f(e,n,`Only the first ${P} entries are read`),i.slice(0,P)}function F(e,t,n,i){const r=H(t);return i.find(o=>o.toLowerCase()===r)??f(e,n,`${c(t)} is not one of ${i.join(", ")}`)}function L(e,t,n,i){const r=Ae(t,n);if(!r.ok)return f(e,i,`${c(t)} is not a valid ${n.type??"string"} value`);const o=r.value instanceof Date?Q(r.value):r.value;return o==null||o===""?f(e,i,"Missing value"):{v:o}}function Ee(e,t,n){const i=h(e,t.field,`${n}.field`,C,"filtered");if(!i)return null;const r=F(e,t.operator,`${n}.operator`,W(i));if(r===null)return null;const o={field:i.field,operator:r};if(G.includes(o.operator))return o;const l=v(t,"value")?t.value:void 0;if(o.operator==="isAnyOf"){const u=[];return S(e,l,`${n}.value`).forEach((g,K)=>{const M=L(e,g,i,`${n}.value[${K}]`);M&&!u.includes(M.v)&&u.push(M.v)}),u.length===0?f(e,`${n}.value`,"No valid values"):(o.value=u,o)}if(Array.isArray(l))return f(e,`${n}.value`,"Expected one value, not a list");const s=L(e,l,i,`${n}.value`);return s?(o.value=s.v,o):null}function J(e,t,n){return t===void 0?null:F(e,t,n,["and","or"])}function Y(e,t,n,i){const r=[];return S(e,t,n).forEach((o,l)=>{const s=`${n}[${l}]`;if(!$(o))f(e,s,`Expected a filter, got ${c(o)}`);else if(v(o,"items")||v(o,"logicOperator")){if(i>=E){f(e,s,`Groups nest at most ${E} levels`);return}const u=Y(e,o.items,`${s}.items`,i+1);u.length===0?f(e,s,"Empty group removed"):r.push({logicOperator:J(e,o.logicOperator,`${s}.logicOperator`)??"and",items:u})}else{const u=Ee(e,o,s);u&&r.push(u)}}),r}function Ne(e,t,n){const i={};v(t,"items")&&(i.items=Y(e,t.items,`${n}.items`,0));const r=J(e,t.logicOperator,`${n}.logicOperator`);return r&&(i.logicOperator=r),v(t,"quickFilterValues")&&(i.quickFilterValues=[],S(e,t.quickFilterValues,`${n}.quickFilterValues`).forEach((o,l)=>{const s=typeof o=="string"||typeof o=="number"?String(o).trim():null;s===null?f(e,`${n}.quickFilterValues[${l}]`,`Expected text, got ${c(o)}`):s!==""&&i.quickFilterValues?.push(s)})),i}function ke(e,t,n){const i=[];return S(e,t,n).forEach((r,o)=>{const l=`${n}[${o}]`;if(!$(r))return f(e,l,`Expected { field, sort }, got ${c(r)}`);const s=h(e,r.field,`${l}.field`,U,"sorted"),u=s&&F(e,r.sort,`${l}.sort`,["asc","desc"]);!s||!u||(i.some(g=>g.field===s.field)?f(e,l,`${c(s.field)} is sorted twice`):i.push({field:s.field,sort:u}))}),i}function V(e,t,n,i,r){const o=[];return S(e,t,n).forEach((l,s)=>{const u=h(e,l,`${n}[${s}]`,i,r);u&&!o.includes(u.field)&&o.push(u.field)}),o}const Me=e=>b(e).length>0,_e=e=>b(e,I).length>0;function Pe(e,t,n){const i={};for(const r of D(t)){if(H(t[r])==="none")continue;const o=h(e,r,`${n}.${r}`,Me,"aggregated"),l=o&&F(e,t[r],`${n}.${r}`,b(o));o&&l&&(i[o.field]=l)}return i}function je(e,t,n){const i=[];return S(e,t.valueFields,`${n}.valueFields`).forEach((r,o)=>{const l=`${n}.valueFields[${o}]`;if(!$(r))return f(e,l,`Expected { field, aggFn }, got ${c(r)}`);const s=h(e,r.field,`${l}.field`,_e,"a pivot value"),u=s&&F(e,r.aggFn,`${l}.aggFn`,b(s,I));s&&u&&!i.some(g=>g.field===s.field&&g.aggFn===u)&&i.push({field:s.field,aggFn:u})}),{rowFields:V(e,t.rowFields,`${n}.rowFields`,A,"a pivot label"),columnFields:V(e,t.columnFields,`${n}.columnFields`,A,"a pivot label"),valueFields:i}}function Ve(e,t,n){const i={};for(const r of D(t)){const o=h(e,r,`${n}.${r}`,B,"hidden"),l=X(t[r]);o&&(l.ok&&l.value!==null?i[o.field]=l.value:f(e,`${n}.${r}`,`Expected true or false, got ${c(t[r])}`))}return i}function Ie(e,t,n,i,r){t==="sort"?r.sortModel=ke(e,n,i):t==="grouping"?r.rowGroupingModel=V(e,n,i,A,"grouped"):$(n)?t==="filter"?r.filterModel=Ne(e,n,i):t==="aggregation"?r.aggregationModel=Pe(e,n,i):t==="pivot"?r.pivotModel=je(e,n,i):r.columnVisibilityModel=Ve(e,n,i):f(e,i,`Expected an object, got ${c(n)}`)}function De(e,t,n={}){const i={errors:[],byField:new Map},r={};try{for(const s of q(t))i.byField.set(s.field,s);let o=e;if(typeof e=="string")try{o=JSON.parse(e)}catch{return f(i,"","Not valid JSON"),{state:r,errors:i.errors}}if(!$(o))return f(i,"",`Expected an object, got ${c(o)}`),{state:r,errors:i.errors};const l=R(n);for(const s of D(o)){const u=j.find(g=>w[g]===s);s==="schemaVersion"?o[s]!==1&&f(i,s,`Unsupported schemaVersion ${c(o[s])}`):u?l.includes(u)?Ie(i,u,o[s],s,r):f(i,s,"Part not allowed"):f(i,s,"Unknown part")}}catch{return f(i,"","Could not read the input"),{state:{},errors:i.errors}}return{state:r,errors:i.errors}}exports.getGridAiSchema=ue,exports.validateGridAiState=De;
|
package/dist/ai.cjs.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ai.cjs","sources":["../lib/utils/values.ts","../lib/utils/filtering/index.ts","../lib/ai/columns.ts","../lib/ai/schema.ts","../lib/utils/parsing.ts","../lib/ai/coerce.ts","../lib/ai/validate.ts"],"sourcesContent":["import type { GridColDef, GridRowModel } from '../types';\n\nconst warnedCallbacks = new Set<string>();\n\n/**\n * Warn once per key (callback kind and column), in development only, that a consumer callback\n * threw and the grid fell back to a default instead of crashing.\n */\nexport function warnCallbackFailure(key: string, message: string, error: unknown): void {\n if (process.env.NODE_ENV === 'production' || warnedCallbacks.has(key)) return;\n warnedCallbacks.add(key);\n console.warn(`[OpenGridX] ${message}.`, error);\n}\n\n/**\n * Call a consumer callback; when it throws, warn once per `key` (dev only) and return `fallback`.\n * Used for callbacks whose failure has an obvious default (a detail-panel height, a group label).\n */\nexport function callSafely<T>(fn: () => T, fallback: T, key: string, message: string): T {\n try {\n return fn();\n } catch (error) {\n warnCallbackFailure(key, message, error);\n return fallback;\n }\n}\n\ntype ValueGetterColumn<R extends GridRowModel> = Pick<GridColDef<R>, 'valueGetter'>;\n\n/**\n * Read a cell through the column's `valueGetter` (or `row[field]`), returning a throw as `error`\n * instead of swallowing it. The row renderer uses this to show the error in that one cell.\n */\nexport function tryGetCellValue<R extends GridRowModel>(\n row: R,\n field: string,\n colDef?: ValueGetterColumn<R>\n): { value: unknown; error?: unknown } {\n const raw = row[field];\n if (!colDef?.valueGetter) return { value: raw };\n try {\n return { value: colDef.valueGetter({ row, field, value: raw }) };\n } catch (error) {\n return { value: undefined, error: error ?? new Error('valueGetter failed') };\n }\n}\n\n/**\n * The value a cell holds before formatting: the column's `valueGetter` result when it has one,\n * otherwise `row[field]`. Filtering, sorting, the quick filter, aggregation, row grouping, pivot,\n * list view, clipboard and editing all read cells through this, so a computed column behaves the\n * same everywhere. A `valueGetter` that throws reads as `undefined` (with a one-time dev warning\n * per column), so one bad record cannot take the whole grid down.\n */\nexport function getCellValue<R extends GridRowModel>(row: R, field: string, colDef?: ValueGetterColumn<R>): unknown {\n const { value, error } = tryGetCellValue(row, field, colDef);\n if (error !== undefined) {\n warnCallbackFailure(`valueGetter:${field}`, `valueGetter for column \"${field}\" threw; the value is read as undefined`, error);\n }\n return value;\n}\n\n/**\n * The column's `valueFormatter` text for `value`, or undefined when the column has no formatter\n * or the formatter throws (with a one-time dev warning per column).\n */\nexport function getFormattedValue<R extends GridRowModel>(\n row: R,\n field: string,\n value: unknown,\n colDef?: Pick<GridColDef<R>, 'valueFormatter'>\n): string | undefined {\n const formatter = colDef?.valueFormatter;\n if (!formatter) return undefined;\n return callSafely(\n () => formatter({ value, row, field }),\n undefined,\n `valueFormatter:${field}`,\n `valueFormatter for column \"${field}\" threw; the value is shown unformatted`,\n );\n}\n\n/**\n * Coerce a cell or filter value to a number. Returns null for null/undefined, blank strings,\n * NaN and anything that does not parse, so an empty cell is never read as 0.\n */\nexport function toNumber(value: unknown): number | null {\n if (value == null) return null;\n if (typeof value === 'number') return Number.isNaN(value) ? null : value;\n if (typeof value === 'string') {\n const trimmed = value.trim();\n if (trimmed === '') return null;\n const n = Number(trimmed);\n return Number.isNaN(n) ? null : n;\n }\n if (typeof value === 'boolean') return null;\n if (value instanceof Date) {\n const t = value.getTime();\n return Number.isNaN(t) ? null : t;\n }\n if (typeof value === 'bigint') return Number(value);\n return null;\n}\n\nconst DATE_ONLY = /^(\\d{4})-(\\d{2})-(\\d{2})$/;\n\n/**\n * Parse a Date object, epoch milliseconds or a date string. A plain `YYYY-MM-DD` string is read\n * as a local calendar date: `new Date('2024-01-10')` is UTC midnight, which is the previous day\n * west of UTC. Returns null for empty, unparsable or invalid values.\n */\nexport function toDate(value: unknown): Date | null {\n if (value == null) return null;\n let date: Date;\n if (value instanceof Date) {\n date = value;\n } else if (typeof value === 'number') {\n date = new Date(value);\n } else if (typeof value === 'string') {\n const trimmed = value.trim();\n if (trimmed === '') return null;\n const match = DATE_ONLY.exec(trimmed);\n if (match) {\n const year = Number(match[1]);\n const month = Number(match[2]) - 1;\n const day = Number(match[3]);\n date = new Date(year, month, day);\n // Reject roll-overs such as 2024-02-31.\n if (date.getMonth() !== month || date.getDate() !== day) return null;\n } else {\n date = new Date(trimmed);\n }\n } else {\n return null;\n }\n return Number.isNaN(date.getTime()) ? null : date;\n}\n\n/** A sortable integer for the local calendar day of a date (YYYYMMDD), or null when unparsable. */\nexport function toLocalDayKey(value: unknown): number | null {\n const date = toDate(value);\n if (!date) return null;\n return date.getFullYear() * 10000 + (date.getMonth() + 1) * 100 + date.getDate();\n}\n\n/** The local calendar day of a date as `YYYY-MM-DD`, or '' when unparsable. */\nexport function toLocalDateString(value: unknown): string {\n const date = toDate(value);\n if (!date) return '';\n const y = String(date.getFullYear()).padStart(4, '0');\n const m = String(date.getMonth() + 1).padStart(2, '0');\n const d = String(date.getDate()).padStart(2, '0');\n return `${y}-${m}-${d}`;\n}\n\nlet localDateFormat: Intl.DateTimeFormat | null = null;\n\n/**\n * `date.toLocaleDateString()` (the default locale's numeric date), with the formatter created\n * once: building a new one per call is several times slower on large grids.\n */\nexport function formatLocalDate(date: Date): string {\n localDateFormat ??= new Intl.DateTimeFormat();\n return localDateFormat.format(date);\n}\n\ntype ValueOption = string | number | { value: unknown; label: string };\n\n/** The label of the `valueOptions` entry for `value`, or undefined when no option matches. */\nexport function getValueOptionLabel(options: readonly ValueOption[] | undefined, value: unknown): string | undefined {\n if (!options) return undefined;\n const optionValue = (option: ValueOption): unknown => (typeof option === 'object' ? option.value : option);\n const optionLabel = (option: ValueOption): string => (typeof option === 'object' ? option.label : String(option));\n // Exact match first; then by text, so a numeric option still labels a value read back as a string.\n const match = options.find(option => Object.is(optionValue(option), value))\n ?? options.find(option => String(optionValue(option)) === String(value));\n return match === undefined ? undefined : optionLabel(match);\n}\n\n/**\n * Default display text for a cell whose column has no `valueFormatter`, by column `type`:\n * - `date`: the local calendar date (`toLocaleDateString()`) of a Date, timestamp or date string;\n * text that does not parse as a date is shown as is.\n * - `boolean`: `Yes` / `No` for `true` / `false`.\n * - `singleSelect`: the matching `valueOptions` label; a value with no option is shown as is.\n * - anything else (and `image`, whose cell renders an image of this URL): `String(value)`.\n * null and undefined are ''. The grid cells and the text exporters (CSV, print, PDF) use this, so\n * a file shows what the grid shows.\n */\nexport function formatValueByType(value: unknown, colDef: Pick<GridColDef, 'type' | 'valueOptions'> | undefined): string {\n if (value === null || value === undefined) return '';\n switch (colDef?.type) {\n case 'date': {\n const date = toDate(value);\n return date ? formatLocalDate(date) : String(value);\n }\n case 'boolean':\n if (value === true) return 'Yes';\n if (value === false) return 'No';\n return String(value);\n case 'singleSelect':\n return getValueOptionLabel(colDef.valueOptions, value) ?? String(value);\n default:\n return String(value);\n }\n}\n","\nimport type { GridRowModel, GridFilterItem, GridFilterModel, GridFilterGroup, GridFilterOperator, GridColDef } from '../../types';\nimport type { GridColumnLookup } from '../columnLookup';\nimport { getCellValue, getFormattedValue, toLocalDateString, toLocalDayKey, toNumber } from '../values';\n\n/** Compares a cell value with the filter item's value. */\nexport type GridFilterOperatorFn = (value: unknown, filterValue?: unknown) => boolean;\n\ntype RowPredicate = (row: GridRowModel) => boolean;\n\n/** Operators that take no value. Every other operator ignores an item whose value is empty. */\nexport const NO_VALUE_OPERATORS: ReadonlySet<string> = new Set<GridFilterOperator>(['isEmpty', 'isNotEmpty']);\n\n/**\n * True for a filter value that means \"nothing entered yet\": undefined, null, a blank string or an\n * empty array. Items with such a value (other than isEmpty / isNotEmpty) do not filter.\n */\nexport function isEmptyFilterValue(value: unknown): boolean {\n if (value == null) return true;\n if (typeof value === 'string') return value.trim() === '';\n if (Array.isArray(value)) return value.length === 0;\n return false;\n}\n\nfunction isBlankCell(value: unknown): boolean {\n if (value == null) return true;\n if (typeof value === 'number') return Number.isNaN(value);\n return typeof value === 'string' && value.trim() === '';\n}\n\nconst numeric = (compare: (a: number, b: number) => boolean): GridFilterOperatorFn => (value, filterValue) => {\n const a = toNumber(value);\n const b = toNumber(filterValue);\n if (a === null || b === null) return false;\n return compare(a, b);\n};\n\nconst byDay = (compare: (a: number, b: number) => boolean): GridFilterOperatorFn => (value, filterValue) => {\n const a = toLocalDayKey(value);\n const b = toLocalDayKey(filterValue);\n if (a === null || b === null) return false;\n return compare(a, b);\n};\n\n/** Same local calendar day. Accepts Date objects, epoch milliseconds and date strings on both sides. */\nconst dateIs: GridFilterOperatorFn = byDay((a, b) => a === b);\n\n/** A different local calendar day. Empty or unparsable cells count as \"not that day\". */\nconst dateNot: GridFilterOperatorFn = (value, filterValue) => {\n const a = toLocalDayKey(value);\n const b = toLocalDayKey(filterValue);\n if (a === null || b === null) return true;\n return a !== b;\n};\n\nexport const FILTER_OPERATORS: Record<GridFilterOperator, GridFilterOperatorFn> = {\n contains: (value, filterValue) => {\n if (value == null || filterValue == null) return false;\n return String(value).toLowerCase().includes(String(filterValue).toLowerCase());\n },\n\n equals: (value, filterValue) => {\n if (value == null && filterValue == null) return true;\n if (value == null || filterValue == null) return false;\n return String(value).toLowerCase() === String(filterValue).toLowerCase();\n },\n\n startsWith: (value, filterValue) => {\n if (value == null || filterValue == null) return false;\n return String(value).toLowerCase().startsWith(String(filterValue).toLowerCase());\n },\n\n endsWith: (value, filterValue) => {\n if (value == null || filterValue == null) return false;\n return String(value).toLowerCase().endsWith(String(filterValue).toLowerCase());\n },\n\n isEmpty: (value) => {\n return value == null || String(value).trim() === '';\n },\n\n isNotEmpty: (value) => {\n return value != null && String(value).trim() !== '';\n },\n\n // Numeric operators: blank cells and non-numeric values never match (a blank cell is not 0).\n '=': numeric((a, b) => a === b),\n '>': numeric((a, b) => a > b),\n '>=': numeric((a, b) => a >= b),\n '<': numeric((a, b) => a < b),\n '<=': numeric((a, b) => a <= b),\n\n '!=': (value, filterValue) => {\n if (isEmptyFilterValue(filterValue)) return true;\n const a = toNumber(value);\n const b = toNumber(filterValue);\n if (a !== null && b !== null) return a !== b;\n // An empty cell is \"not equal\" to every value, whatever that value is.\n if (isBlankCell(value)) return true;\n return String(value).toLowerCase() !== String(filterValue).toLowerCase();\n },\n\n // A scalar value is treated as a one-element list, so a typed \"Active\" works like [\"Active\"].\n isAnyOf: (value, filterValue) => {\n if (value == null) return false;\n const options = Array.isArray(filterValue) ? filterValue : [filterValue];\n const cell = String(value).toLowerCase();\n return options.some(option => option != null && String(option).toLowerCase() === cell);\n },\n\n // Used by boolean and singleSelect (case-insensitive string comparison), and by dates.\n not: (value, filterValue) => {\n if (value instanceof Date) return dateNot(value, filterValue);\n if (value == null && filterValue == null) return false;\n if (value == null || filterValue == null) return true;\n return String(value).toLowerCase() !== String(filterValue).toLowerCase();\n },\n\n is: (value, filterValue) => {\n if (filterValue == null || filterValue === '') return true;\n if (value instanceof Date) return dateIs(value, filterValue);\n return String(value).toLowerCase() === String(filterValue).toLowerCase();\n },\n\n // Date operators compare local calendar days.\n after: byDay((a, b) => a > b),\n onOrAfter: byDay((a, b) => a >= b),\n before: byDay((a, b) => a < b),\n onOrBefore: byDay((a, b) => a <= b),\n};\n\n/** Date columns compare `is` / `not` by calendar day, so ISO strings and Date objects match the typed day. */\nconst DATE_COLUMN_OPERATORS: Partial<Record<GridFilterOperator, GridFilterOperatorFn>> = {\n is: dateIs,\n not: dateNot,\n};\n\nfunction resolveOperator(operator: string, colDef: GridColDef | undefined): GridFilterOperatorFn | undefined {\n if (!Object.prototype.hasOwnProperty.call(FILTER_OPERATORS, operator)) return undefined;\n const op = operator as GridFilterOperator;\n if (colDef?.type === 'date') {\n const dateFn = DATE_COLUMN_OPERATORS[op];\n if (dateFn) return dateFn;\n }\n return FILTER_OPERATORS[op];\n}\n\nfunction isFilterGroup(item: GridFilterItem | GridFilterGroup): item is GridFilterGroup {\n return 'items' in item && Array.isArray(item.items);\n}\n\ninterface CompileContext {\n columns?: GridColumnLookup;\n /** Unknown operators already reported in this pass, so the warning fires once per pass, not per row. */\n warned: Set<string>;\n}\n\nfunction compileItem(item: GridFilterItem, ctx: CompileContext): RowPredicate | null {\n const { field, operator, value } = item;\n // An item whose value has not been entered yet (or was cleared) does not filter.\n if (!NO_VALUE_OPERATORS.has(operator) && isEmptyFilterValue(value)) return null;\n\n const colDef = ctx.columns?.byField.get(field);\n const operatorFn = resolveOperator(operator, colDef);\n if (!operatorFn) {\n if (!ctx.warned.has(operator)) {\n ctx.warned.add(operator);\n console.warn(`Unknown filter operator: ${operator}`);\n }\n return null;\n }\n return (row) => operatorFn(getCellValue(row, field, colDef), value);\n}\n\nfunction compileGroup(\n items: (GridFilterItem | GridFilterGroup)[],\n logicOperator: 'and' | 'or',\n ctx: CompileContext\n): RowPredicate | null {\n const predicates: RowPredicate[] = [];\n for (const item of items) {\n const predicate = isFilterGroup(item)\n ? compileGroup(item.items, item.logicOperator, ctx)\n : compileItem(item, ctx);\n if (predicate) predicates.push(predicate);\n }\n if (predicates.length === 0) return null;\n if (predicates.length === 1) return predicates[0];\n return logicOperator === 'or'\n ? (row) => predicates.some(p => p(row))\n : (row) => predicates.every(p => p(row));\n}\n\nfunction pushSearchText(out: string[], value: unknown): void {\n if (value == null) return;\n switch (typeof value) {\n case 'string':\n out.push(value.toLowerCase());\n return;\n case 'number':\n if (!Number.isNaN(value)) out.push(String(value));\n return;\n case 'boolean':\n case 'bigint':\n out.push(String(value));\n return;\n default:\n break;\n }\n if (value instanceof Date) {\n // Search the calendar day, not Date.toString()'s \"Wed Jan 10 2024 ... GMT+0000\".\n const day = toLocalDateString(value);\n if (day) out.push(day);\n return;\n }\n if (Array.isArray(value)) {\n for (const entry of value) {\n if (entry == null || typeof entry !== 'object') pushSearchText(out, entry);\n }\n }\n // Other objects have no meaningful text (\"[object Object]\") and are skipped.\n}\n\nfunction getQuickFilterTexts(row: GridRowModel, columns: GridColumnLookup | undefined): string[] {\n const texts: string[] = [];\n if (!columns) {\n for (const value of Object.values(row)) pushSearchText(texts, value);\n return texts;\n }\n for (const col of columns.quickFilterColumns) {\n const value = getCellValue(row, col.field, col);\n const formatted = getFormattedValue(row, col.field, value, col);\n if (formatted != null && formatted !== '') texts.push(String(formatted).toLowerCase());\n pushSearchText(texts, value);\n }\n return texts;\n}\n\n// Search texts per column lookup and row object. The grid keeps one lookup while the columns and\n// hidden columns stay the same, so typing in the quick filter reads every cell once, not per keystroke.\nconst quickFilterTextCache = new WeakMap<GridColumnLookup, WeakMap<GridRowModel, string[]>>();\n\nfunction getCachedQuickFilterTexts(row: GridRowModel, columns: GridColumnLookup | undefined): string[] {\n if (!columns) return getQuickFilterTexts(row, columns);\n let byRow = quickFilterTextCache.get(columns);\n if (!byRow) {\n byRow = new WeakMap();\n quickFilterTextCache.set(columns, byRow);\n }\n let texts = byRow.get(row);\n if (!texts) {\n texts = getQuickFilterTexts(row, columns);\n byRow.set(row, texts);\n }\n return texts;\n}\n\nfunction compileQuickFilter(values: readonly string[] | undefined, columns: GridColumnLookup | undefined): RowPredicate | null {\n const terms = (values ?? [])\n .map(value => String(value ?? '').trim().toLowerCase())\n .filter(term => term !== '');\n if (terms.length === 0) return null;\n return (row) => {\n const texts = getCachedQuickFilterTexts(row, columns);\n return terms.every(term => texts.some(text => text.includes(term)));\n };\n}\n\n/**\n * Compile a filter model into a row predicate. Returns null when the model filters nothing\n * (no items with a value and no quick-filter terms), so callers can skip the pass entirely.\n *\n * Pass `columns` so that cells are read through `valueGetter`, date columns use calendar-day\n * comparison and the quick filter searches only visible, filterable columns (including their\n * `valueFormatter` text). Without it, `row[field]` is read and the quick filter searches every\n * primitive value in the row.\n */\nexport function createRowFilter(\n filterModel: GridFilterModel | GridFilterGroup | null | undefined,\n columns?: GridColumnLookup\n): RowPredicate | null {\n if (!filterModel) return null;\n const ctx: CompileContext = { columns, warned: new Set() };\n const quickFilterValues = 'quickFilterValues' in filterModel ? filterModel.quickFilterValues : undefined;\n const quick = compileQuickFilter(quickFilterValues, columns);\n const itemsPredicate = compileGroup(filterModel.items ?? [], filterModel.logicOperator ?? 'and', ctx);\n if (quick && itemsPredicate) return (row) => quick(row) && itemsPredicate(row);\n return quick ?? itemsPredicate;\n}\n\n/**\n * Apply a single filter item to a row\n */\nexport function applyFilterItem<R extends GridRowModel>(\n row: R,\n filterItem: GridFilterItem,\n colDef?: GridColDef\n): boolean {\n const columns: GridColumnLookup | undefined = colDef\n ? { byField: new Map([[filterItem.field, colDef]]), quickFilterColumns: [] }\n : undefined;\n const predicate = compileItem(filterItem, { columns, warned: new Set() });\n return predicate ? predicate(row) : true;\n}\n\n/**\n * Apply the quick filter (global search). Every term must be found in at least one searched value.\n */\nexport function applyQuickFilter<R extends GridRowModel>(\n row: R,\n quickFilterValues: string[],\n columns?: GridColumnLookup\n): boolean {\n const predicate = compileQuickFilter(quickFilterValues, columns);\n return predicate ? predicate(row) : true;\n}\n\n/**\n * Check if a row matches the filter model. Compiles the model on every call: to test many rows,\n * build a predicate once with createRowFilter.\n */\nexport function isRowMatchingFilter<R extends GridRowModel>(\n row: R,\n filterModel: GridFilterModel | GridFilterGroup,\n columns?: GridColumnLookup\n): boolean {\n const predicate = createRowFilter(filterModel, columns);\n return predicate ? predicate(row) : true;\n}\n\nexport function filterRows<R extends GridRowModel>(\n rows: R[],\n filterModel: GridFilterModel,\n columns?: GridColumnLookup\n): R[] {\n const predicate = createRowFilter(filterModel, columns);\n if (!predicate) return rows;\n return rows.filter(row => predicate(row));\n}\n\nexport function getDefaultOperator(type?: string): GridFilterOperator {\n switch (type) {\n case 'number':\n return '=';\n case 'date':\n return 'is';\n case 'boolean':\n return 'is';\n case 'singleSelect':\n return 'isAnyOf';\n default:\n return 'contains';\n }\n}\n\nexport function getOperatorsForType(type?: string): GridFilterOperator[] {\n switch (type) {\n case 'number':\n return ['=', '!=', '>', '>=', '<', '<=', 'isEmpty', 'isNotEmpty'];\n case 'date':\n return ['is', 'not', 'after', 'onOrAfter', 'before', 'onOrBefore', 'isEmpty', 'isNotEmpty'];\n case 'boolean':\n return ['is'];\n case 'singleSelect':\n return ['isAnyOf', 'is', 'not'];\n default:\n return ['contains', 'equals', 'startsWith', 'endsWith', 'isEmpty', 'isNotEmpty'];\n }\n}\n","// Which columns may take part in each part of the state. The schema and the validator both read\n// these, so the model is never offered a field the validator would drop.\nimport { getOperatorsForType } from '../utils/filtering';\nimport type { GridFilterOperator, GridPivotAggFn } from '../types';\nimport type { GridAiColumn, GridAiPart, GridAiPartOptions } from './types';\n\n/** The built-in aggregation functions (`AGGREGATION_FUNCTIONS` in lib/utils/aggregation). */\nexport const AGGREGATION_FNS: readonly string[] = ['sum', 'avg', 'count', 'min', 'max', 'unique'];\n/** The functions a pivot value field takes (`GridPivotAggFn`). */\nexport const PIVOT_FNS: readonly GridPivotAggFn[] = ['sum', 'avg', 'count', 'min', 'max'];\n/** Operators without a value. */\nexport const NO_VALUE_OPS: readonly string[] = ['isEmpty', 'isNotEmpty'];\n/** Deepest nesting of filter groups. */\nexport const MAX_FILTER_DEPTH = 3;\n\nexport const ALL_PARTS: readonly GridAiPart[] = ['filter', 'sort', 'grouping', 'aggregation', 'pivot', 'columnVisibility'];\n\n/** State key of each part, in output order. */\nexport const PART_KEYS: Record<GridAiPart, string> = {\n filter: 'filterModel',\n sort: 'sortModel',\n grouping: 'rowGroupingModel',\n aggregation: 'aggregationModel',\n pivot: 'pivotModel',\n columnVisibility: 'columnVisibilityModel',\n};\n\nexport function hasOwn(obj: object, key: string): boolean {\n return Object.prototype.hasOwnProperty.call(obj, key);\n}\n\nexport function isPlainObject(value: unknown): value is Record<string, unknown> {\n return value !== null && typeof value === 'object' && !Array.isArray(value);\n}\n\nexport function selectedParts(options: GridAiPartOptions | undefined): GridAiPart[] {\n const include = Array.isArray(options?.include) ? options.include : ALL_PARTS;\n const exclude = Array.isArray(options?.exclude) ? options.exclude : [];\n return ALL_PARTS.filter((part) => include.includes(part) && !exclude.includes(part));\n}\n\n/**\n * The usable columns, first one per field. System columns (`__check__`, `__group__`, …), spacers\n * and fields that are not plain strings are skipped.\n */\nexport function usableColumns(columns: readonly GridAiColumn[] | unknown): GridAiColumn[] {\n if (!Array.isArray(columns)) return [];\n const seen = new Set<string>();\n const out: GridAiColumn[] = [];\n for (const col of columns as unknown[]) {\n if (!isPlainObject(col)) continue;\n const field = col.field;\n if (typeof field !== 'string' || field === '' || field.startsWith('__') || col.isSpacer === true || seen.has(field)) continue;\n seen.add(field);\n out.push(col as unknown as GridAiColumn);\n }\n return out;\n}\n\nexport function operatorsOf(col: GridAiColumn): GridFilterOperator[] {\n return getOperatorsForType(col.type);\n}\n\nexport const isFilterable = (col: GridAiColumn): boolean => col.filterable !== false;\nexport const isSortable = (col: GridAiColumn): boolean => col.sortable !== false;\nexport const isGroupable = (col: GridAiColumn): boolean => col.groupable !== false;\nexport const isHideable = (col: GridAiColumn): boolean => col.hideable !== false;\n\n/**\n * Aggregation functions for a column, as the Summaries panel offers them: every built-in function\n * for number columns (or `aggregable: true`), `count` for the others, never with `aggregable: false`,\n * and limited by `availableAggregationFunctions`.\n */\nexport function aggregationFnsOf(col: GridAiColumn, fns: readonly string[] = AGGREGATION_FNS): string[] {\n if (col.aggregable === false) return [];\n const base = col.type === 'number' || col.aggregable === true ? fns : fns.filter((fn) => fn === 'count');\n const allowed = col.availableAggregationFunctions;\n return Array.isArray(allowed) ? base.filter((fn) => allowed.includes(fn)) : [...base];\n}\n\n/** JSON-safe option values of a singleSelect column, in order, without duplicates. */\nexport function optionValues(col: GridAiColumn): (string | number)[] {\n const out: (string | number)[] = [];\n if (!Array.isArray(col.valueOptions)) return out;\n for (const option of col.valueOptions) {\n const value: unknown = option !== null && typeof option === 'object' ? option.value : option;\n if ((typeof value === 'string' || (typeof value === 'number' && Number.isFinite(value))) && !out.includes(value)) out.push(value);\n }\n return out;\n}\n","// getGridAiSchema: a JSON Schema (draft 2020-12) of the grid state a model may produce. Built from\n// the column definitions only; no row data is read. Same columns and options, same schema.\nimport {\n MAX_FILTER_DEPTH,\n NO_VALUE_OPS,\n PART_KEYS,\n PIVOT_FNS,\n aggregationFnsOf,\n isFilterable,\n isGroupable,\n isHideable,\n isSortable,\n operatorsOf,\n optionValues,\n selectedParts,\n usableColumns,\n} from './columns';\nimport type { GridAiColumn, GridAiJsonSchema, GridAiPart, GridAiSchema, GridAiSchemaOptions } from './types';\n\n/** Title of every schema. Also the marker `check-bundle` uses to keep this code out of the core bundle. */\nexport const GRID_AI_SCHEMA_TITLE = 'OpenGridX grid state';\n\nconst MAX_EXAMPLES = 10;\n\ninterface Ctx {\n descriptions: boolean;\n examples: boolean;\n}\n\nfunction label(col: GridAiColumn): string {\n const header = typeof col.headerName === 'string' && col.headerName !== '' ? col.headerName : col.field;\n const desc = typeof col.description === 'string' && col.description !== '' ? ` (${col.description})` : '';\n return `${header}${desc}`;\n}\n\nfunction describe(node: GridAiJsonSchema, text: string, ctx: Ctx): GridAiJsonSchema {\n return ctx.descriptions ? { ...node, description: text } : node;\n}\n\nfunction examplesOf(col: GridAiColumn, ctx: Ctx): unknown[] {\n if (!ctx.examples || !Array.isArray(col.aiExamples)) return [];\n return col.aiExamples\n .filter((v) => typeof v === 'string' || typeof v === 'boolean' || (typeof v === 'number' && Number.isFinite(v)))\n .slice(0, MAX_EXAMPLES);\n}\n\n/** Schema of one filter value for the column's type. */\nfunction valueSchema(col: GridAiColumn, ctx: Ctx): GridAiJsonSchema {\n let node: GridAiJsonSchema;\n switch (col.type) {\n case 'number':\n node = { type: 'number' };\n break;\n case 'date':\n node = { type: 'string', format: 'date' };\n break;\n case 'boolean':\n node = { type: 'boolean' };\n break;\n case 'singleSelect': {\n const values = optionValues(col);\n const one: GridAiJsonSchema = values.length > 0 ? { enum: values } : { type: 'string' };\n node = { anyOf: [one, { type: 'array', items: one }] };\n break;\n }\n default:\n node = { type: 'string' };\n }\n const examples = examplesOf(col, ctx);\n return examples.length > 0 ? { ...node, examples } : node;\n}\n\nfunction filterItemSchema(col: GridAiColumn, ctx: Ctx): GridAiJsonSchema {\n const operators = operatorsOf(col);\n const noValue = operators.filter((op) => NO_VALUE_OPS.includes(op));\n const hint = col.type === 'singleSelect' ? '; isAnyOf takes an array' : '';\n const ops = noValue.length > 0 ? `${noValue.join(' and ')} take no value${hint}` : hint.slice(2);\n return describe(\n {\n type: 'object',\n properties: {\n field: { const: col.field },\n operator: { enum: operators },\n value: valueSchema(col, ctx),\n },\n required: ['field', 'operator'],\n additionalProperties: false,\n },\n ops ? `${label(col)}: ${ops}` : label(col),\n ctx,\n );\n}\n\nfunction filterSchema(cols: GridAiColumn[], ctx: Ctx, defs: Record<string, GridAiJsonSchema>): GridAiJsonSchema | null {\n const filterable = cols.filter(isFilterable);\n const properties: Record<string, GridAiJsonSchema> = {};\n const logic = { enum: ['and', 'or'] };\n if (filterable.length > 0) {\n defs.filterItem = { oneOf: filterable.map((col) => filterItemSchema(col, ctx)) };\n // Groups are unrolled to MAX_FILTER_DEPTH levels: the deepest one holds items only.\n for (let depth = MAX_FILTER_DEPTH; depth >= 1; depth--) {\n const child = depth === MAX_FILTER_DEPTH ? { $ref: '#/$defs/filterItem' } : { anyOf: [{ $ref: '#/$defs/filterItem' }, { $ref: `#/$defs/filterGroup${depth + 1}` }] };\n defs[`filterGroup${depth}`] = {\n type: 'object',\n properties: { logicOperator: logic, items: { type: 'array', items: child } },\n required: ['logicOperator', 'items'],\n additionalProperties: false,\n };\n }\n properties.items = { type: 'array', items: { anyOf: [{ $ref: '#/$defs/filterItem' }, { $ref: '#/$defs/filterGroup1' }] } };\n properties.logicOperator = logic;\n }\n properties.quickFilterValues = describe({ type: 'array', items: { type: 'string' } }, 'Words a row must contain, in any column', ctx);\n return { type: 'object', properties, additionalProperties: false };\n}\n\nfunction fieldEnum(cols: GridAiColumn[]): GridAiJsonSchema {\n return { enum: cols.map((c) => c.field) };\n}\n\nfunction partSchema(part: GridAiPart, cols: GridAiColumn[], ctx: Ctx, defs: Record<string, GridAiJsonSchema>): GridAiJsonSchema | null {\n switch (part) {\n case 'filter':\n return filterSchema(cols, ctx, defs);\n case 'sort': {\n const sortable = cols.filter(isSortable);\n if (sortable.length === 0) return null;\n return describe(\n {\n type: 'array',\n items: {\n type: 'object',\n properties: { field: fieldEnum(sortable), sort: { enum: ['asc', 'desc'] } },\n required: ['field', 'sort'],\n additionalProperties: false,\n },\n },\n 'Most significant first',\n ctx,\n );\n }\n case 'grouping': {\n const groupable = cols.filter(isGroupable);\n if (groupable.length === 0) return null;\n return describe({ type: 'array', items: fieldEnum(groupable), uniqueItems: true }, 'Outermost first', ctx);\n }\n case 'aggregation': {\n const properties: Record<string, GridAiJsonSchema> = {};\n for (const col of cols) {\n const fns = aggregationFnsOf(col);\n if (fns.length > 0) properties[col.field] = describe({ enum: fns }, label(col), ctx);\n }\n if (Object.keys(properties).length === 0) return null;\n return { type: 'object', properties, additionalProperties: false };\n }\n case 'pivot': {\n const dims = cols.filter(isGroupable);\n const values = cols\n .map((col) => ({ col, fns: aggregationFnsOf(col, PIVOT_FNS) }))\n .filter((v) => v.fns.length > 0)\n .map(({ col, fns }) => describe({\n type: 'object',\n properties: { field: { const: col.field }, aggFn: { enum: fns } },\n required: ['field', 'aggFn'],\n additionalProperties: false,\n }, label(col), ctx));\n if (dims.length === 0 || values.length === 0) return null;\n const dimArray = { type: 'array', items: fieldEnum(dims), uniqueItems: true };\n return {\n type: 'object',\n properties: { rowFields: dimArray, columnFields: dimArray, valueFields: { type: 'array', items: { oneOf: values } } },\n required: ['rowFields', 'columnFields', 'valueFields'],\n additionalProperties: false,\n };\n }\n case 'columnVisibility': {\n const properties: Record<string, GridAiJsonSchema> = {};\n for (const col of cols.filter(isHideable)) properties[col.field] = describe({ type: 'boolean' }, label(col), ctx);\n if (Object.keys(properties).length === 0) return null;\n return describe({ type: 'object', properties, additionalProperties: false }, 'false hides a column', ctx);\n }\n }\n}\n\n/**\n * A JSON Schema (draft 2020-12) of the grid state a model may return for these columns: filter,\n * sort, grouping, aggregation, pivot and column visibility, each limited to the fields, operators\n * and functions the columns allow. Send it with the user's prompt to a model with structured\n * output, then pass the reply to `validateGridAiState`. Reads column definitions only, never rows.\n * @since v3.4\n */\nexport function getGridAiSchema<C extends GridAiColumn>(columns: readonly C[], options: GridAiSchemaOptions = {}): GridAiSchema {\n const ctx: Ctx = { descriptions: options.descriptions !== false, examples: options.examples !== false };\n const cols = usableColumns(columns);\n const defs: Record<string, GridAiJsonSchema> = {};\n const properties: Record<string, GridAiJsonSchema> = {};\n for (const part of selectedParts(options)) {\n const node = partSchema(part, cols, ctx, defs);\n if (node) properties[PART_KEYS[part]] = node;\n }\n const schema: GridAiSchema = {\n $schema: 'https://json-schema.org/draft/2020-12/schema',\n schemaVersion: 1,\n title: GRID_AI_SCHEMA_TITLE,\n type: 'object',\n properties,\n additionalProperties: false,\n };\n if (ctx.descriptions) {\n schema.description = 'Grid state to apply. Include only the parts the user asked to change.';\n }\n if (Object.keys(defs).length > 0) schema.$defs = defs;\n return schema;\n}\n","import type { GridColDef, GridValidRowModel } from '../types';\nimport { toDate } from './values';\n\n/**\n * Text → typed values: the TSV reader clipboard paste uses, and the per-column-type parsers paste,\n * `GridColDef.valueParser` defaults and the `/ai` validator share. Pure and React-free.\n *\n * Every pattern here is linear (CodeQL scans every push): no adjacent quantifiers that can match the\n * same characters.\n */\n\n/** A parsed value, or `{ ok: false }` when the text does not fit the column type. */\nexport type GridParsedValue = { ok: true; value: unknown } | { ok: false };\n\nconst INVALID: GridParsedValue = { ok: false };\nconst valid = (value: unknown): GridParsedValue => ({ ok: true, value });\n\n/**\n * Splits tab-separated text the way Excel, Google Sheets and LibreOffice write it: tabs between\n * columns, `\\r\\n`, `\\n` or `\\r` between rows. A field that starts with a double quote is quoted: it\n * may hold tabs, line breaks and doubled quotes (`\"\"` → `\"`), the reverse of `escapeTsvField`. A\n * quote inside an unquoted field is kept as is. A trailing empty line is ignored, so `\"a\\tb\\n\"` is one\n * row. Empty text gives no rows.\n */\nexport function parseTsv(text: string): string[][] {\n const rows: string[][] = [];\n if (text === '') return rows;\n let row: string[] = [];\n let field = '';\n let i = 0;\n const n = text.length;\n let fieldStart = true;\n while (i < n) {\n const ch = text[i];\n if (fieldStart && ch === '\"') {\n // A quoted field: read to the closing quote, `\"\"` being one quote.\n let j = i + 1;\n let value = '';\n let closed = false;\n while (j < n) {\n const q = text[j];\n if (q === '\"') {\n if (text[j + 1] === '\"') {\n value += '\"';\n j += 2;\n continue;\n }\n closed = true;\n j += 1;\n break;\n }\n value += q;\n j += 1;\n }\n if (!closed) {\n // No closing quote: not a quoted field after all, read the quote as text.\n field += ch;\n i += 1;\n fieldStart = false;\n continue;\n }\n field += value;\n i = j;\n fieldStart = false;\n continue;\n }\n fieldStart = false;\n if (ch === '\\t') {\n row.push(field);\n field = '';\n fieldStart = true;\n i += 1;\n } else if (ch === '\\n' || ch === '\\r') {\n row.push(field);\n rows.push(row);\n row = [];\n field = '';\n fieldStart = true;\n i += ch === '\\r' && text[i + 1] === '\\n' ? 2 : 1;\n } else {\n field += ch;\n i += 1;\n }\n }\n // The last line, unless the text ended with a line break (Excel always adds one).\n if (!fieldStart || row.length > 0 || field !== '') {\n row.push(field);\n rows.push(row);\n }\n return rows;\n}\n\nconst PLAIN_NUMBER = /^\\d+(?:\\.\\d*)?(?:e[+-]?\\d+)?$/i;\nconst LEADING_DOT_NUMBER = /^\\.\\d+(?:e[+-]?\\d+)?$/i;\nconst GROUPED_NUMBER = /^\\d{1,3}(?:,\\d{3})+(?:\\.\\d*)?$/;\n// Currency symbols (Unicode Sc) and every kind of space, including the no-break spaces Excel uses\n// as a thousands separator.\nconst STRIPPED_CHARS = /[\\p{Sc}\\s]/gu;\n\n/**\n * A number as spreadsheets copy it: `1234.5`, `1,234.5` (comma thousands groups only), `-12`,\n * `(12)` (accounting negative), `12%` (→ 0.12), `1e3`, with currency symbols and spaces stripped.\n * Empty text is `null`. `1,5` is not accepted (it is ambiguous): use a `valueParser` for\n * decimal-comma locales.\n */\nexport function parseNumberText(text: string): GridParsedValue {\n let s = text.replace(STRIPPED_CHARS, '');\n if (s === '') return text.trim() === '' ? valid(null) : INVALID;\n let negative = false;\n if (s.startsWith('(') && s.endsWith(')')) {\n negative = true;\n s = s.slice(1, -1);\n }\n if (s.startsWith('-') || s.startsWith('+') || s.startsWith('−')) {\n if (s[0] !== '+') negative = !negative;\n s = s.slice(1);\n }\n let percent = false;\n if (s.endsWith('%')) {\n percent = true;\n s = s.slice(0, -1);\n }\n if (GROUPED_NUMBER.test(s)) s = s.split(',').join('');\n else if (!PLAIN_NUMBER.test(s) && !LEADING_DOT_NUMBER.test(s)) return INVALID;\n let value = Number(s);\n if (!Number.isFinite(value)) return INVALID;\n if (percent) value /= 100;\n if (negative) value = -value;\n return valid(value);\n}\n\nconst ISO_DATE_PREFIX = /^(\\d{4}-\\d{2}-\\d{2})/;\nconst pad2 = (n: number) => String(n).padStart(2, '0');\nconst localDay = (date: Date) => `${date.getFullYear()}-${pad2(date.getMonth() + 1)}-${pad2(date.getDate())}`;\n\n/**\n * A date: ISO `2026-10-07` (read as that local calendar day) or anything `Date.parse` accepts. Empty\n * text is `null`. The result takes the kind of `current` (the value the cell holds now), as the date\n * editor does: a timestamp stays a timestamp, a date string stays a `YYYY-MM-DD` string (an ISO\n * date-time string keeps its time part); otherwise it is a `Date`.\n */\nexport function parseDateText(text: string, current?: unknown): GridParsedValue {\n const trimmed = text.trim();\n if (trimmed === '') return valid(null);\n const date = toDate(trimmed);\n if (!date) return INVALID;\n if (typeof current === 'number') return valid(date.getTime());\n if (typeof current === 'string') {\n const day = localDay(date);\n return valid(ISO_DATE_PREFIX.test(current) && current.length > 10 ? day + current.slice(10) : day);\n }\n return valid(date);\n}\n\n/** Text read as `true` / `false`, besides the grid's own `Yes` / `No` cell labels. Lower case. */\nconst TRUE_WORDS = new Set(['true', 'yes', '1']);\nconst FALSE_WORDS = new Set(['false', 'no', '0']);\n\nexport interface GridBooleanLabels {\n /** Text shown for `true` (the grid shows `Yes`). */\n trueLabel?: string;\n /** Text shown for `false` (the grid shows `No`). */\n falseLabel?: string;\n}\n\n/**\n * A boolean: `true`/`false`, `yes`/`no` (the grid's own `Yes` / `No` cell labels), `1`/`0` and any\n * `labels` given, case-insensitive. Empty text is `null`.\n */\nexport function parseBooleanText(text: string, labels?: GridBooleanLabels): GridParsedValue {\n const s = text.trim().toLowerCase();\n if (s === '') return valid(null);\n if (labels?.trueLabel !== undefined && s === labels.trueLabel.trim().toLowerCase()) return valid(true);\n if (labels?.falseLabel !== undefined && s === labels.falseLabel.trim().toLowerCase()) return valid(false);\n if (TRUE_WORDS.has(s)) return valid(true);\n if (FALSE_WORDS.has(s)) return valid(false);\n return INVALID;\n}\n\ntype ValueOption = NonNullable<GridColDef['valueOptions']>[number];\n\n/**\n * A `singleSelect` value: the option whose value or label matches the text, case-insensitive (values\n * first). Returns the option's own value, so a numeric option stays a number. Empty text is `null`.\n */\nexport function parseSingleSelectText(text: string, options: readonly ValueOption[] | undefined): GridParsedValue {\n const s = text.trim();\n if (s === '') return valid(null);\n if (!options || options.length === 0) return INVALID;\n const lower = s.toLowerCase();\n for (const option of options) {\n const value = typeof option === 'object' ? option.value : option;\n if (String(value).toLowerCase() === lower) return valid(value);\n }\n for (const option of options) {\n if (typeof option === 'object' && option.label.toLowerCase() === lower) return valid(option.value);\n }\n return INVALID;\n}\n\ntype ParserColumn = Pick<GridColDef, 'type' | 'valueOptions'>;\n\nexport interface GridParseTextOptions {\n /** The value the cell holds now: a parsed date takes its kind (see `parseDateText`). */\n current?: unknown;\n /** Extra boolean labels, besides `Yes` / `No`. */\n booleanLabels?: GridBooleanLabels;\n}\n\n/**\n * Parses cell text by the column `type`, without `valueParser`:\n *\n * | type | accepted |\n * | :--- | :--- |\n * | `string` (default), `image` | the text as is (an empty cell is `''`) |\n * | `number` | `parseNumberText` |\n * | `date` | `parseDateText` |\n * | `boolean` | `parseBooleanText` |\n * | `singleSelect` | `parseSingleSelectText` |\n *\n * An empty cell is `null` for every type but `string` / `image`.\n */\nexport function parseValueByType(text: string, colDef: ParserColumn | undefined, options?: GridParseTextOptions): GridParsedValue {\n switch (colDef?.type) {\n case 'number': return parseNumberText(text);\n case 'date': return parseDateText(text, options?.current);\n case 'boolean': return parseBooleanText(text, options?.booleanLabels);\n case 'singleSelect': return parseSingleSelectText(text, colDef.valueOptions);\n default: return valid(text);\n }\n}\n\n/**\n * Parses the text pasted into one cell: the column's `valueParser` when it has one (a throw, or a\n * returned `undefined`, rejects the text), otherwise `parseValueByType`.\n */\nexport function parseCellText<R extends GridValidRowModel>(\n text: string,\n colDef: GridColDef<R>,\n row: R,\n options?: GridParseTextOptions,\n): GridParsedValue {\n const { valueParser } = colDef;\n if (!valueParser) return parseValueByType(text, colDef as unknown as ParserColumn, options);\n let value: unknown;\n try {\n value = valueParser(text, { row, field: colDef.field, colDef });\n } catch {\n return INVALID;\n }\n return value === undefined ? INVALID : valid(value);\n}\n","// Value coercion for the AI validator. Text goes through the same parsers as clipboard paste\n// (`lib/utils/parsing.ts`), so a value the grid accepts from Excel is accepted from a model and the\n// other way round. Model output is JSON, so typed values (a number, a boolean, a Date) are taken as\n// they are. Empty input is valid: null, or '' for text columns.\nimport {\n parseBooleanText,\n parseDateText,\n parseNumberText,\n parseSingleSelectText,\n type GridParsedValue,\n} from '../utils/parsing';\n\nexport type CoerceResult<T = unknown> = { ok: true; value: T } | { ok: false; reason: 'invalidValue' };\n\n/** The column properties coercion reads. */\nexport interface CoerceColumn {\n type?: string;\n valueOptions?: readonly (string | number | { value: unknown; label: string })[];\n}\n\nconst ok = <T>(value: T): CoerceResult<T> => ({ ok: true, value });\nconst INVALID: CoerceResult<never> = { ok: false, reason: 'invalidValue' };\n\nconst fromParsed = <T>(parsed: GridParsedValue): CoerceResult<T> =>\n parsed.ok ? ok(parsed.value as T) : INVALID;\n\nfunction isBlank(text: unknown): boolean {\n return text == null || (typeof text === 'string' && text.trim() === '');\n}\n\n/** `string` and `image` columns: text as is. Numbers and booleans become their text. */\nexport function coerceString(text: unknown): CoerceResult<string> {\n if (text == null) return ok('');\n if (typeof text === 'string') return ok(text);\n if (typeof text === 'number' || typeof text === 'boolean') return ok(String(text));\n return INVALID;\n}\n\n/** `number` columns: a finite number, or text in the paste format (`1,234.5`, `12%`, `$40`). */\nexport function coerceNumber(text: unknown): CoerceResult<number | null> {\n if (isBlank(text)) return ok(null);\n if (typeof text === 'number') return Number.isFinite(text) ? ok(text) : INVALID;\n if (typeof text !== 'string') return INVALID;\n return fromParsed(parseNumberText(text));\n}\n\n/** `date` columns: a Date, epoch milliseconds, or text in the paste format (ISO `2026-10-07` …). */\nexport function coerceDate(text: unknown): CoerceResult<Date | null> {\n if (isBlank(text)) return ok(null);\n if (text instanceof Date) return Number.isNaN(text.getTime()) ? INVALID : ok(text);\n if (typeof text === 'number') return Number.isFinite(text) ? ok(new Date(text)) : INVALID;\n if (typeof text !== 'string') return INVALID;\n return fromParsed(parseDateText(text));\n}\n\n/** `boolean` columns: a boolean, or `true`/`false`, `yes`/`no`, `1`/`0` (case-insensitive). */\nexport function coerceBoolean(text: unknown): CoerceResult<boolean | null> {\n if (isBlank(text)) return ok(null);\n if (typeof text === 'boolean') return ok(text);\n if (typeof text !== 'string' && typeof text !== 'number') return INVALID;\n return fromParsed(parseBooleanText(String(text)));\n}\n\n/**\n * `singleSelect` columns: an option value or label, case-insensitive; returns the option's value.\n * Without `valueOptions` the model has nothing to choose from, so any scalar is kept as it is.\n */\nexport function coerceSingleSelect(text: unknown, colDef: CoerceColumn): CoerceResult<unknown> {\n if (isBlank(text)) return ok(null);\n if (typeof text !== 'string' && typeof text !== 'number' && typeof text !== 'boolean') return INVALID;\n const options = colDef.valueOptions;\n if (!options || options.length === 0) return ok(text);\n return fromParsed(parseSingleSelectText(String(text), options));\n}\n\n/** Coerces `text` for the column's `type`. */\nexport function coerceValue(text: unknown, colDef: CoerceColumn): CoerceResult<unknown> {\n switch (colDef.type) {\n case 'number':\n return coerceNumber(text);\n case 'date':\n return coerceDate(text);\n case 'boolean':\n return coerceBoolean(text);\n case 'singleSelect':\n return coerceSingleSelect(text, colDef);\n default:\n return coerceString(text);\n }\n}\n","// validateGridAiState: the safety layer between a model's reply and the grid. Anything the columns\n// do not allow is dropped with an error; values are coerced to the column type. Never throws.\n// Only own keys of the input are read (never `__proto__`), and every key written to the output is a\n// column field, so nothing in the reply can reach Object.prototype or the output's prototype.\nimport type {\n GridAggregationModel,\n GridColumnVisibilityModel,\n GridFilterGroup,\n GridFilterItem,\n GridFilterModel,\n GridFilterOperator,\n GridPivotModel,\n GridPivotValueField,\n GridSortItem,\n} from '../types';\nimport { toLocalDateString } from '../utils/values';\nimport { coerceBoolean, coerceValue } from './coerce';\nimport {\n ALL_PARTS,\n MAX_FILTER_DEPTH,\n NO_VALUE_OPS,\n PART_KEYS,\n PIVOT_FNS,\n aggregationFnsOf,\n hasOwn,\n isFilterable,\n isGroupable,\n isHideable,\n isPlainObject,\n isSortable,\n operatorsOf,\n selectedParts,\n usableColumns,\n} from './columns';\nimport type { GridAiColumn, GridAiPart, GridAiState, GridAiValidateOptions, GridAiValidationError, GridAiValidationResult } from './types';\n\n/** Longest list read from the model output; the rest is dropped with one error. */\nconst MAX_LIST = 200;\n/** Errors past this many are not listed, so a reply full of junk still gives a readable list. */\nconst MAX_ERRORS = 100;\n\ntype Allowed = (col: GridAiColumn) => boolean;\ntype FilterNode = GridFilterItem | GridFilterGroup;\n\ninterface Ctx {\n errors: GridAiValidationError[];\n byField: Map<string, GridAiColumn>;\n}\n\nfunction fail(ctx: Ctx, path: string, message: string): null {\n if (ctx.errors.length < MAX_ERRORS) ctx.errors.push({ path, message });\n return null;\n}\n\n/** A short, safe rendering of a value for an error message. */\nfunction show(value: unknown): string {\n if (typeof value === 'string') return JSON.stringify(value.slice(0, 60));\n return value === null || typeof value !== 'object' ? String(value) : Array.isArray(value) ? 'a list' : 'an object';\n}\n\nfunction lower(value: unknown): string | null {\n return typeof value === 'string' ? value.trim().toLowerCase() : null;\n}\n\n/** Own enumerable keys of the input, without `__proto__`. */\nfunction keysOf(obj: Record<string, unknown>): string[] {\n return Object.keys(obj).filter((key) => key !== '__proto__');\n}\n\nfunction column(ctx: Ctx, value: unknown, path: string, allowed: Allowed, what: string): GridAiColumn | null {\n const col = typeof value === 'string' ? ctx.byField.get(value) : undefined;\n if (!col) return fail(ctx, path, `Unknown field ${show(value)}`);\n return allowed(col) ? col : fail(ctx, path, `Field ${show(value)} cannot be ${what}`);\n}\n\n/** The first MAX_LIST entries of an array; a single value is read as a one-entry list. */\nfunction list(ctx: Ctx, value: unknown, path: string): unknown[] {\n if (value == null) return [];\n const items = Array.isArray(value) ? value : [value];\n if (items.length > MAX_LIST) fail(ctx, path, `Only the first ${MAX_LIST} entries are read`);\n return items.slice(0, MAX_LIST);\n}\n\n/** The choice `value` names, case-insensitively, in the choice's own spelling. */\nfunction oneOf(ctx: Ctx, value: unknown, path: string, choices: readonly string[]): string | null {\n const v = lower(value);\n return choices.find((c) => c.toLowerCase() === v) ?? fail(ctx, path, `${show(value)} is not one of ${choices.join(', ')}`);\n}\n\n/** One filter value, coerced; dates become `YYYY-MM-DD` as the filter panel writes them. */\nfunction filterValue(ctx: Ctx, raw: unknown, col: GridAiColumn, path: string): { v: unknown } | null {\n const result = coerceValue(raw, col);\n if (!result.ok) return fail(ctx, path, `${show(raw)} is not a valid ${col.type ?? 'string'} value`);\n const v = result.value instanceof Date ? toLocalDateString(result.value) : result.value;\n return v == null || v === '' ? fail(ctx, path, 'Missing value') : { v };\n}\n\nfunction filterItem(ctx: Ctx, raw: Record<string, unknown>, path: string): GridFilterItem | null {\n const col = column(ctx, raw.field, `${path}.field`, isFilterable, 'filtered');\n if (!col) return null;\n const operator = oneOf(ctx, raw.operator, `${path}.operator`, operatorsOf(col));\n if (operator === null) return null;\n const item: GridFilterItem = { field: col.field, operator: operator as GridFilterOperator };\n if (NO_VALUE_OPS.includes(item.operator)) return item;\n const rawValue = hasOwn(raw, 'value') ? raw.value : undefined;\n if (item.operator === 'isAnyOf') {\n const values: unknown[] = [];\n list(ctx, rawValue, `${path}.value`).forEach((entry, i) => {\n const r = filterValue(ctx, entry, col, `${path}.value[${i}]`);\n if (r && !values.includes(r.v)) values.push(r.v);\n });\n if (values.length === 0) return fail(ctx, `${path}.value`, 'No valid values');\n item.value = values;\n return item;\n }\n if (Array.isArray(rawValue)) return fail(ctx, `${path}.value`, 'Expected one value, not a list');\n const r = filterValue(ctx, rawValue, col, `${path}.value`);\n if (!r) return null;\n item.value = r.v;\n return item;\n}\n\nfunction logic(ctx: Ctx, raw: unknown, path: string): 'and' | 'or' | null {\n return raw === undefined ? null : (oneOf(ctx, raw, path, ['and', 'or']) as 'and' | 'or' | null);\n}\n\nfunction filterItems(ctx: Ctx, raw: unknown, path: string, depth: number): FilterNode[] {\n const out: FilterNode[] = [];\n list(ctx, raw, path).forEach((entry, i) => {\n const at = `${path}[${i}]`;\n if (!isPlainObject(entry)) {\n fail(ctx, at, `Expected a filter, got ${show(entry)}`);\n } else if (hasOwn(entry, 'items') || hasOwn(entry, 'logicOperator')) {\n if (depth >= MAX_FILTER_DEPTH) {\n fail(ctx, at, `Groups nest at most ${MAX_FILTER_DEPTH} levels`);\n return;\n }\n const items = filterItems(ctx, entry.items, `${at}.items`, depth + 1);\n if (items.length === 0) fail(ctx, at, 'Empty group removed');\n else out.push({ logicOperator: logic(ctx, entry.logicOperator, `${at}.logicOperator`) ?? 'and', items });\n } else {\n const item = filterItem(ctx, entry, at);\n if (item) out.push(item);\n }\n });\n return out;\n}\n\nfunction filterModel(ctx: Ctx, raw: Record<string, unknown>, path: string): GridFilterModel {\n const model: GridFilterModel = {};\n if (hasOwn(raw, 'items')) model.items = filterItems(ctx, raw.items, `${path}.items`, 0);\n const op = logic(ctx, raw.logicOperator, `${path}.logicOperator`);\n if (op) model.logicOperator = op;\n if (hasOwn(raw, 'quickFilterValues')) {\n model.quickFilterValues = [];\n list(ctx, raw.quickFilterValues, `${path}.quickFilterValues`).forEach((entry, i) => {\n const word = typeof entry === 'string' || typeof entry === 'number' ? String(entry).trim() : null;\n if (word === null) fail(ctx, `${path}.quickFilterValues[${i}]`, `Expected text, got ${show(entry)}`);\n else if (word !== '') model.quickFilterValues?.push(word);\n });\n }\n return model;\n}\n\nfunction sortModel(ctx: Ctx, raw: unknown, path: string): GridSortItem[] {\n const out: GridSortItem[] = [];\n list(ctx, raw, path).forEach((entry, i) => {\n const at = `${path}[${i}]`;\n if (!isPlainObject(entry)) return fail(ctx, at, `Expected { field, sort }, got ${show(entry)}`);\n const col = column(ctx, entry.field, `${at}.field`, isSortable, 'sorted');\n const sort = col && oneOf(ctx, entry.sort, `${at}.sort`, ['asc', 'desc']);\n if (!col || !sort) return;\n if (out.some((s) => s.field === col.field)) fail(ctx, at, `${show(col.field)} is sorted twice`);\n else out.push({ field: col.field, sort: sort as 'asc' | 'desc' });\n });\n return out;\n}\n\nfunction fieldList(ctx: Ctx, raw: unknown, path: string, allowed: Allowed, what: string): string[] {\n const out: string[] = [];\n list(ctx, raw, path).forEach((entry, i) => {\n const col = column(ctx, entry, `${path}[${i}]`, allowed, what);\n if (col && !out.includes(col.field)) out.push(col.field);\n });\n return out;\n}\n\nconst canAggregate: Allowed = (col) => aggregationFnsOf(col).length > 0;\nconst canPivot: Allowed = (col) => aggregationFnsOf(col, PIVOT_FNS).length > 0;\n\nfunction aggregationModel(ctx: Ctx, raw: Record<string, unknown>, path: string): GridAggregationModel {\n const model: GridAggregationModel = {};\n for (const key of keysOf(raw)) {\n // 'none' is how the grid spells \"no aggregation\": nothing to set.\n if (lower(raw[key]) === 'none') continue;\n const col = column(ctx, key, `${path}.${key}`, canAggregate, 'aggregated');\n const fn = col && oneOf(ctx, raw[key], `${path}.${key}`, aggregationFnsOf(col));\n if (col && fn) model[col.field] = fn;\n }\n return model;\n}\n\nfunction pivotModel(ctx: Ctx, raw: Record<string, unknown>, path: string): GridPivotModel {\n const valueFields: GridPivotValueField[] = [];\n list(ctx, raw.valueFields, `${path}.valueFields`).forEach((entry, i) => {\n const at = `${path}.valueFields[${i}]`;\n if (!isPlainObject(entry)) return fail(ctx, at, `Expected { field, aggFn }, got ${show(entry)}`);\n const col = column(ctx, entry.field, `${at}.field`, canPivot, 'a pivot value');\n const aggFn = col && (oneOf(ctx, entry.aggFn, `${at}.aggFn`, aggregationFnsOf(col, PIVOT_FNS)) as GridPivotValueField['aggFn'] | null);\n if (col && aggFn && !valueFields.some((v) => v.field === col.field && v.aggFn === aggFn)) valueFields.push({ field: col.field, aggFn });\n });\n return {\n rowFields: fieldList(ctx, raw.rowFields, `${path}.rowFields`, isGroupable, 'a pivot label'),\n columnFields: fieldList(ctx, raw.columnFields, `${path}.columnFields`, isGroupable, 'a pivot label'),\n valueFields,\n };\n}\n\nfunction visibilityModel(ctx: Ctx, raw: Record<string, unknown>, path: string): GridColumnVisibilityModel {\n const model: GridColumnVisibilityModel = {};\n for (const key of keysOf(raw)) {\n const col = column(ctx, key, `${path}.${key}`, isHideable, 'hidden');\n const visible = coerceBoolean(raw[key]);\n if (!col) continue;\n if (visible.ok && visible.value !== null) model[col.field] = visible.value;\n else fail(ctx, `${path}.${key}`, `Expected true or false, got ${show(raw[key])}`);\n }\n return model;\n}\n\nfunction readPart(ctx: Ctx, part: GridAiPart, raw: unknown, path: string, state: GridAiState): void {\n if (part === 'sort') state.sortModel = sortModel(ctx, raw, path);\n else if (part === 'grouping') state.rowGroupingModel = fieldList(ctx, raw, path, isGroupable, 'grouped');\n else if (!isPlainObject(raw)) fail(ctx, path, `Expected an object, got ${show(raw)}`);\n else if (part === 'filter') state.filterModel = filterModel(ctx, raw, path);\n else if (part === 'aggregation') state.aggregationModel = aggregationModel(ctx, raw, path);\n else if (part === 'pivot') state.pivotModel = pivotModel(ctx, raw, path);\n else state.columnVisibilityModel = visibilityModel(ctx, raw, path);\n}\n\n/**\n * Checks a model's reply against the columns and returns the state that is safe to apply.\n * `json` is an object or a JSON string (bad JSON gives one error and an empty state). Unknown\n * parts, fields, operators and enum values are dropped, each with an error; values are coerced to\n * the column type (`\"1,200\"` → 1200, a date → `\"YYYY-MM-DD\"`); filter groups left empty are\n * removed. Never throws, and never returns a field the columns do not allow.\n * @since v3.4\n */\nexport function validateGridAiState<C extends GridAiColumn>(\n json: unknown,\n columns: readonly C[],\n options: GridAiValidateOptions = {},\n): GridAiValidationResult {\n const ctx: Ctx = { errors: [], byField: new Map() };\n const state: GridAiState = {};\n try {\n for (const col of usableColumns(columns)) ctx.byField.set(col.field, col);\n let input = json;\n if (typeof json === 'string') {\n try {\n input = JSON.parse(json) as unknown;\n } catch {\n fail(ctx, '', 'Not valid JSON');\n return { state, errors: ctx.errors };\n }\n }\n if (!isPlainObject(input)) {\n fail(ctx, '', `Expected an object, got ${show(input)}`);\n return { state, errors: ctx.errors };\n }\n const allowed = selectedParts(options);\n for (const key of keysOf(input)) {\n const part = ALL_PARTS.find((p) => PART_KEYS[p] === key);\n if (key === 'schemaVersion') {\n if (input[key] !== 1) fail(ctx, key, `Unsupported schemaVersion ${show(input[key])}`);\n } else if (!part) fail(ctx, key, 'Unknown part');\n else if (!allowed.includes(part)) fail(ctx, key, 'Part not allowed');\n else readPart(ctx, part, input[key], key, state);\n }\n } catch {\n // Defensive only: a getter on a hand-built input object could throw.\n fail(ctx, '', 'Could not read the input');\n return { state: {}, errors: ctx.errors };\n }\n return { state, errors: ctx.errors };\n}\n"],"names":["DATE_ONLY","toDate","value","date","trimmed","match","year","month","day","toLocalDateString","y","m","d","getOperatorsForType","type","AGGREGATION_FNS","PIVOT_FNS","NO_VALUE_OPS","MAX_FILTER_DEPTH","ALL_PARTS","PART_KEYS","hasOwn","obj","key","isPlainObject","selectedParts","options","include","exclude","part","usableColumns","columns","seen","out","col","field","operatorsOf","isFilterable","isSortable","isGroupable","isHideable","aggregationFnsOf","fns","base","fn","allowed","optionValues","option","GRID_AI_SCHEMA_TITLE","MAX_EXAMPLES","label","header","desc","describe","node","text","ctx","examplesOf","v","valueSchema","values","one","examples","filterItemSchema","operators","noValue","op","hint","ops","filterSchema","cols","defs","filterable","properties","logic","depth","child","fieldEnum","c","partSchema","sortable","groupable","dims","dimArray","getGridAiSchema","schema","INVALID","valid","PLAIN_NUMBER","LEADING_DOT_NUMBER","GROUPED_NUMBER","STRIPPED_CHARS","parseNumberText","s","negative","percent","parseDateText","current","TRUE_WORDS","FALSE_WORDS","parseBooleanText","labels","parseSingleSelectText","lower","ok","fromParsed","parsed","isBlank","coerceString","coerceNumber","coerceDate","coerceBoolean","coerceSingleSelect","colDef","coerceValue","MAX_LIST","MAX_ERRORS","fail","path","message","show","keysOf","column","what","list","items","oneOf","choices","filterValue","raw","result","filterItem","operator","item","rawValue","entry","i","r","filterItems","at","filterModel","model","word","sortModel","sort","fieldList","canAggregate","canPivot","aggregationModel","pivotModel","valueFields","aggFn","visibilityModel","visible","readPart","state","validateGridAiState","json","input","p"],"mappings":"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"}
|
package/dist/ai.d.ts
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A JSON Schema (draft 2020-12) of the grid state a model may return for these columns: filter,
|
|
3
|
+
* sort, grouping, aggregation, pivot and column visibility, each limited to the fields, operators
|
|
4
|
+
* and functions the columns allow. Send it with the user's prompt to a model with structured
|
|
5
|
+
* output, then pass the reply to `validateGridAiState`. Reads column definitions only, never rows.
|
|
6
|
+
* @since v3.4
|
|
7
|
+
*/
|
|
8
|
+
export declare function getGridAiSchema<C extends GridAiColumn>(columns: readonly C[], options?: GridAiSchemaOptions): GridAiSchema;
|
|
9
|
+
|
|
10
|
+
declare interface GridAggregationModel {
|
|
11
|
+
[field: string]: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* The column properties the AI toolkit reads. A `GridColDef` (typed or untyped) is accepted as is;
|
|
16
|
+
* renderers, getters and formatters are never called, and no row data is read.
|
|
17
|
+
* @since v3.4
|
|
18
|
+
*/
|
|
19
|
+
export declare interface GridAiColumn {
|
|
20
|
+
field: string;
|
|
21
|
+
headerName?: string;
|
|
22
|
+
description?: string;
|
|
23
|
+
type?: string;
|
|
24
|
+
filterable?: boolean;
|
|
25
|
+
sortable?: boolean;
|
|
26
|
+
groupable?: boolean;
|
|
27
|
+
hideable?: boolean;
|
|
28
|
+
aggregable?: boolean;
|
|
29
|
+
availableAggregationFunctions?: readonly string[];
|
|
30
|
+
valueOptions?: readonly (string | number | {
|
|
31
|
+
value: unknown;
|
|
32
|
+
label: string;
|
|
33
|
+
})[];
|
|
34
|
+
/** Example values sent to the model. Real data: see `GridColDef.aiExamples`. */
|
|
35
|
+
aiExamples?: readonly unknown[];
|
|
36
|
+
/** Internal spacer columns are skipped. */
|
|
37
|
+
isSpacer?: boolean;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** A JSON Schema (draft 2020-12) node. */
|
|
41
|
+
export declare type GridAiJsonSchema = {
|
|
42
|
+
[keyword: string]: unknown;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
/** A part of the grid state the model may change. */
|
|
46
|
+
export declare type GridAiPart = 'filter' | 'sort' | 'grouping' | 'aggregation' | 'pivot' | 'columnVisibility';
|
|
47
|
+
|
|
48
|
+
/** Chooses the parts of the state; `exclude` wins over `include`. Default: every part. */
|
|
49
|
+
export declare interface GridAiPartOptions {
|
|
50
|
+
include?: readonly GridAiPart[];
|
|
51
|
+
exclude?: readonly GridAiPart[];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** The schema returned by `getGridAiSchema`. `schemaVersion` changes when its shape changes. */
|
|
55
|
+
export declare interface GridAiSchema extends GridAiJsonSchema {
|
|
56
|
+
$schema: 'https://json-schema.org/draft/2020-12/schema';
|
|
57
|
+
schemaVersion: 1;
|
|
58
|
+
type: 'object';
|
|
59
|
+
properties: Record<string, GridAiJsonSchema>;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export declare interface GridAiSchemaOptions extends GridAiPartOptions {
|
|
63
|
+
/**
|
|
64
|
+
* Put each column's `headerName` and `description` into the schema, so the model can map the
|
|
65
|
+
* user's words ("revenue") to a field (`amt_usd`).
|
|
66
|
+
* @default true
|
|
67
|
+
*/
|
|
68
|
+
descriptions?: boolean;
|
|
69
|
+
/**
|
|
70
|
+
* Include the columns' `aiExamples`. Columns without `aiExamples` never send example values.
|
|
71
|
+
* @default true
|
|
72
|
+
*/
|
|
73
|
+
examples?: boolean;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** The validated state: only parts, fields, operators and values the columns allow. */
|
|
77
|
+
export declare interface GridAiState {
|
|
78
|
+
filterModel?: GridFilterModel;
|
|
79
|
+
sortModel?: GridSortItem[];
|
|
80
|
+
rowGroupingModel?: GridRowGroupingModel;
|
|
81
|
+
aggregationModel?: GridAggregationModel;
|
|
82
|
+
pivotModel?: GridPivotModel;
|
|
83
|
+
columnVisibilityModel?: GridColumnVisibilityModel;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export declare type GridAiValidateOptions = GridAiPartOptions;
|
|
87
|
+
|
|
88
|
+
/** One thing the validator dropped or could not use. `path` points into the model output. */
|
|
89
|
+
export declare interface GridAiValidationError {
|
|
90
|
+
path: string;
|
|
91
|
+
message: string;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export declare interface GridAiValidationResult {
|
|
95
|
+
state: GridAiState;
|
|
96
|
+
errors: GridAiValidationError[];
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
declare type GridColumnVisibilityModel = Record<string, boolean>;
|
|
100
|
+
|
|
101
|
+
/** Describes a group of filters combined with a logical operator. */
|
|
102
|
+
declare interface GridFilterGroup {
|
|
103
|
+
/** Optional ID for the group. */
|
|
104
|
+
id?: string | number;
|
|
105
|
+
/** Logical operator to join child items ('and' | 'or'). */
|
|
106
|
+
logicOperator: 'and' | 'or';
|
|
107
|
+
/** Array of child filter items or groups. */
|
|
108
|
+
items: (GridFilterItem | GridFilterGroup)[];
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** Describes a filter condition for a specific column. */
|
|
112
|
+
declare interface GridFilterItem {
|
|
113
|
+
/** Optional ID for the filter item. */
|
|
114
|
+
id?: string | number;
|
|
115
|
+
/** The field name to filter. */
|
|
116
|
+
field: string;
|
|
117
|
+
/** The comparison operator. */
|
|
118
|
+
operator: GridFilterOperator;
|
|
119
|
+
/** The value to compare against. */
|
|
120
|
+
value?: unknown;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** The overall state of grid filtering. */
|
|
124
|
+
declare interface GridFilterModel {
|
|
125
|
+
/** Root-level filter items or groups. */
|
|
126
|
+
items?: (GridFilterItem | GridFilterGroup)[];
|
|
127
|
+
/** Global logical operator (defaults to 'and'). */
|
|
128
|
+
logicOperator?: 'and' | 'or';
|
|
129
|
+
/** Array of strings used for the quick search feature across all columns. */
|
|
130
|
+
quickFilterValues?: string[];
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
declare type GridFilterOperator = 'contains' | 'equals' | 'startsWith' | 'endsWith' | 'isEmpty' | 'isNotEmpty' | 'isAnyOf' | '=' | '>' | '>=' | '<' | '<=' | '!=' | 'is' | 'not' | 'after' | 'onOrAfter' | 'before' | 'onOrBefore';
|
|
134
|
+
|
|
135
|
+
declare type GridPivotAggFn = 'sum' | 'avg' | 'count' | 'min' | 'max';
|
|
136
|
+
|
|
137
|
+
declare interface GridPivotModel {
|
|
138
|
+
rowFields: string[];
|
|
139
|
+
columnFields: string[];
|
|
140
|
+
valueFields: GridPivotValueField[];
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
declare interface GridPivotValueField {
|
|
144
|
+
field: string;
|
|
145
|
+
aggFn: GridPivotAggFn;
|
|
146
|
+
headerName?: string;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
declare type GridRowGroupingModel = string[];
|
|
150
|
+
|
|
151
|
+
declare interface GridSortItem {
|
|
152
|
+
field: string;
|
|
153
|
+
sort: 'asc' | 'desc';
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Checks a model's reply against the columns and returns the state that is safe to apply.
|
|
158
|
+
* `json` is an object or a JSON string (bad JSON gives one error and an empty state). Unknown
|
|
159
|
+
* parts, fields, operators and enum values are dropped, each with an error; values are coerced to
|
|
160
|
+
* the column type (`"1,200"` → 1200, a date → `"YYYY-MM-DD"`); filter groups left empty are
|
|
161
|
+
* removed. Never throws, and never returns a field the columns do not allow.
|
|
162
|
+
* @since v3.4
|
|
163
|
+
*/
|
|
164
|
+
export declare function validateGridAiState<C extends GridAiColumn>(json: unknown, columns: readonly C[], options?: GridAiValidateOptions): GridAiValidationResult;
|
|
165
|
+
|
|
166
|
+
export { }
|
package/dist/ai.es.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const Q=/^(\d{4})-(\d{2})-(\d{2})$/;function D(e){if(e==null)return null;let t;if(e instanceof Date)t=e;else if(typeof e=="number")t=new Date(e);else if(typeof e=="string"){const n=e.trim();if(n==="")return null;const i=Q.exec(n);if(i){const r=Number(i[1]),o=Number(i[2])-1,l=Number(i[3]);if(t=new Date(r,o,l),t.getMonth()!==o||t.getDate()!==l)return null}else t=new Date(n)}else return null;return Number.isNaN(t.getTime())?null:t}function R(e){const t=D(e);if(!t)return"";const n=String(t.getFullYear()).padStart(4,"0"),i=String(t.getMonth()+1).padStart(2,"0"),r=String(t.getDate()).padStart(2,"0");return`${n}-${i}-${r}`}function Z(e){switch(e){case"number":return["=","!=",">",">=","<","<=","isEmpty","isNotEmpty"];case"date":return["is","not","after","onOrAfter","before","onOrBefore","isEmpty","isNotEmpty"];case"boolean":return["is"];case"singleSelect":return["isAnyOf","is","not"];default:return["contains","equals","startsWith","endsWith","isEmpty","isNotEmpty"]}}const K=["sum","avg","count","min","max","unique"],E=["sum","avg","count","min","max"],G=["isEmpty","isNotEmpty"],A=3,x=["filter","sort","grouping","aggregation","pivot","columnVisibility"],W={filter:"filterModel",sort:"sortModel",grouping:"rowGroupingModel",aggregation:"aggregationModel",pivot:"pivotModel",columnVisibility:"columnVisibilityModel"};function F(e,t){return Object.prototype.hasOwnProperty.call(e,t)}function $(e){return e!==null&&typeof e=="object"&&!Array.isArray(e)}function I(e){const t=Array.isArray(e?.include)?e.include:x,n=Array.isArray(e?.exclude)?e.exclude:[];return x.filter(i=>t.includes(i)&&!n.includes(i))}function C(e){if(!Array.isArray(e))return[];const t=new Set,n=[];for(const i of e){if(!$(i))continue;const r=i.field;typeof r!="string"||r===""||r.startsWith("__")||i.isSpacer===!0||t.has(r)||(t.add(r),n.push(i))}return n}function _(e){return Z(e.type)}const L=e=>e.filterable!==!1,J=e=>e.sortable!==!1,O=e=>e.groupable!==!1,U=e=>e.hideable!==!1;function b(e,t=K){if(e.aggregable===!1)return[];const n=e.type==="number"||e.aggregable===!0?t:t.filter(r=>r==="count"),i=e.availableAggregationFunctions;return Array.isArray(i)?n.filter(r=>i.includes(r)):[...n]}function ee(e){const t=[];if(!Array.isArray(e.valueOptions))return t;for(const n of e.valueOptions){const i=n!==null&&typeof n=="object"?n.value:n;(typeof i=="string"||typeof i=="number"&&Number.isFinite(i))&&!t.includes(i)&&t.push(i)}return t}const te="OpenGridX grid state",ne=10;function w(e){const t=typeof e.headerName=="string"&&e.headerName!==""?e.headerName:e.field,n=typeof e.description=="string"&&e.description!==""?` (${e.description})`:"";return`${t}${n}`}function y(e,t,n){return n.descriptions?{...e,description:t}:e}function re(e,t){return!t.examples||!Array.isArray(e.aiExamples)?[]:e.aiExamples.filter(n=>typeof n=="string"||typeof n=="boolean"||typeof n=="number"&&Number.isFinite(n)).slice(0,ne)}function ie(e,t){let n;switch(e.type){case"number":n={type:"number"};break;case"date":n={type:"string",format:"date"};break;case"boolean":n={type:"boolean"};break;case"singleSelect":{const r=ee(e),o=r.length>0?{enum:r}:{type:"string"};n={anyOf:[o,{type:"array",items:o}]};break}default:n={type:"string"}}const i=re(e,t);return i.length>0?{...n,examples:i}:n}function oe(e,t){const n=_(e),i=n.filter(l=>G.includes(l)),r=e.type==="singleSelect"?"; isAnyOf takes an array":"",o=i.length>0?`${i.join(" and ")} take no value${r}`:r.slice(2);return y({type:"object",properties:{field:{const:e.field},operator:{enum:n},value:ie(e,t)},required:["field","operator"],additionalProperties:!1},o?`${w(e)}: ${o}`:w(e),t)}function se(e,t,n){const i=e.filter(L),r={},o={enum:["and","or"]};if(i.length>0){n.filterItem={oneOf:i.map(l=>oe(l,t))};for(let l=A;l>=1;l--){const s=l===A?{$ref:"#/$defs/filterItem"}:{anyOf:[{$ref:"#/$defs/filterItem"},{$ref:`#/$defs/filterGroup${l+1}`}]};n[`filterGroup${l}`]={type:"object",properties:{logicOperator:o,items:{type:"array",items:s}},required:["logicOperator","items"],additionalProperties:!1}}r.items={type:"array",items:{anyOf:[{$ref:"#/$defs/filterItem"},{$ref:"#/$defs/filterGroup1"}]}},r.logicOperator=o}return r.quickFilterValues=y({type:"array",items:{type:"string"}},"Words a row must contain, in any column",t),{type:"object",properties:r,additionalProperties:!1}}function M(e){return{enum:e.map(t=>t.field)}}function le(e,t,n,i){switch(e){case"filter":return se(t,n,i);case"sort":{const r=t.filter(J);return r.length===0?null:y({type:"array",items:{type:"object",properties:{field:M(r),sort:{enum:["asc","desc"]}},required:["field","sort"],additionalProperties:!1}},"Most significant first",n)}case"grouping":{const r=t.filter(O);return r.length===0?null:y({type:"array",items:M(r),uniqueItems:!0},"Outermost first",n)}case"aggregation":{const r={};for(const o of t){const l=b(o);l.length>0&&(r[o.field]=y({enum:l},w(o),n))}return Object.keys(r).length===0?null:{type:"object",properties:r,additionalProperties:!1}}case"pivot":{const r=t.filter(O),o=t.map(s=>({col:s,fns:b(s,E)})).filter(s=>s.fns.length>0).map(({col:s,fns:u})=>y({type:"object",properties:{field:{const:s.field},aggFn:{enum:u}},required:["field","aggFn"],additionalProperties:!1},w(s),n));if(r.length===0||o.length===0)return null;const l={type:"array",items:M(r),uniqueItems:!0};return{type:"object",properties:{rowFields:l,columnFields:l,valueFields:{type:"array",items:{oneOf:o}}},required:["rowFields","columnFields","valueFields"],additionalProperties:!1}}case"columnVisibility":{const r={};for(const o of t.filter(U))r[o.field]=y({type:"boolean"},w(o),n);return Object.keys(r).length===0?null:y({type:"object",properties:r,additionalProperties:!1},"false hides a column",n)}}}function ue(e,t={}){const n={descriptions:t.descriptions!==!1,examples:t.examples!==!1},i=C(e),r={},o={};for(const s of I(t)){const u=le(s,i,n,r);u&&(o[W[s]]=u)}const l={$schema:"https://json-schema.org/draft/2020-12/schema",schemaVersion:1,title:te,type:"object",properties:o,additionalProperties:!1};return n.descriptions&&(l.description="Grid state to apply. Include only the parts the user asked to change."),Object.keys(r).length>0&&(l.$defs=r),l}const m={ok:!1},p=e=>({ok:!0,value:e}),ae=/^\d+(?:\.\d*)?(?:e[+-]?\d+)?$/i,ce=/^\.\d+(?:e[+-]?\d+)?$/i,fe=/^\d{1,3}(?:,\d{3})+(?:\.\d*)?$/,pe=/[\p{Sc}\s]/gu;function de(e){let t=e.replace(pe,"");if(t==="")return e.trim()===""?p(null):m;let n=!1;t.startsWith("(")&&t.endsWith(")")&&(n=!0,t=t.slice(1,-1)),(t.startsWith("-")||t.startsWith("+")||t.startsWith("\u2212"))&&(t[0]!=="+"&&(n=!n),t=t.slice(1));let i=!1;if(t.endsWith("%")&&(i=!0,t=t.slice(0,-1)),fe.test(t))t=t.split(",").join("");else if(!ae.test(t)&&!ce.test(t))return m;let r=Number(t);return Number.isFinite(r)?(i&&(r/=100),n&&(r=-r),p(r)):m}function ge(e,t){const n=e.trim();if(n==="")return p(null);const i=D(n);return i?p(i):m}const ye=new Set(["true","yes","1"]),me=new Set(["false","no","0"]);function $e(e,t){const n=e.trim().toLowerCase();return n===""?p(null):ye.has(n)?p(!0):me.has(n)?p(!1):m}function be(e,t){const n=e.trim();if(n==="")return p(null);if(!t||t.length===0)return m;const i=n.toLowerCase();for(const r of t){const o=typeof r=="object"?r.value:r;if(String(o).toLowerCase()===i)return p(o)}for(const r of t)if(typeof r=="object"&&r.label.toLowerCase()===i)return p(r.value);return m}const c=e=>({ok:!0,value:e}),d={ok:!1,reason:"invalidValue"},N=e=>e.ok?c(e.value):d;function j(e){return e==null||typeof e=="string"&&e.trim()===""}function he(e){return e==null?c(""):typeof e=="string"?c(e):typeof e=="number"||typeof e=="boolean"?c(String(e)):d}function ve(e){return j(e)?c(null):typeof e=="number"?Number.isFinite(e)?c(e):d:typeof e!="string"?d:N(de(e))}function Fe(e){return j(e)?c(null):e instanceof Date?Number.isNaN(e.getTime())?d:c(e):typeof e=="number"?Number.isFinite(e)?c(new Date(e)):d:typeof e!="string"?d:N(ge(e))}function T(e){return j(e)?c(null):typeof e=="boolean"?c(e):typeof e!="string"&&typeof e!="number"?d:N($e(String(e)))}function Oe(e,t){if(j(e))return c(null);if(typeof e!="string"&&typeof e!="number"&&typeof e!="boolean")return d;const n=t.valueOptions;return!n||n.length===0?c(e):N(be(String(e),n))}function we(e,t){switch(t.type){case"number":return ve(e);case"date":return Fe(e);case"boolean":return T(e);case"singleSelect":return Oe(e,t);default:return he(e)}}const V=200,ke=100;function a(e,t,n){return e.errors.length<ke&&e.errors.push({path:t,message:n}),null}function f(e){return typeof e=="string"?JSON.stringify(e.slice(0,60)):e===null||typeof e!="object"?String(e):Array.isArray(e)?"a list":"an object"}function B(e){return typeof e=="string"?e.trim().toLowerCase():null}function q(e){return Object.keys(e).filter(t=>t!=="__proto__")}function h(e,t,n,i,r){const o=typeof t=="string"?e.byField.get(t):void 0;return o?i(o)?o:a(e,n,`Field ${f(t)} cannot be ${r}`):a(e,n,`Unknown field ${f(t)}`)}function v(e,t,n){if(t==null)return[];const i=Array.isArray(t)?t:[t];return i.length>V&&a(e,n,`Only the first ${V} entries are read`),i.slice(0,V)}function k(e,t,n,i){const r=B(t);return i.find(o=>o.toLowerCase()===r)??a(e,n,`${f(t)} is not one of ${i.join(", ")}`)}function X(e,t,n,i){const r=we(t,n);if(!r.ok)return a(e,i,`${f(t)} is not a valid ${n.type??"string"} value`);const o=r.value instanceof Date?R(r.value):r.value;return o==null||o===""?a(e,i,"Missing value"):{v:o}}function Ae(e,t,n){const i=h(e,t.field,`${n}.field`,L,"filtered");if(!i)return null;const r=k(e,t.operator,`${n}.operator`,_(i));if(r===null)return null;const o={field:i.field,operator:r};if(G.includes(o.operator))return o;const l=F(t,"value")?t.value:void 0;if(o.operator==="isAnyOf"){const u=[];return v(e,l,`${n}.value`).forEach((g,H)=>{const S=X(e,g,i,`${n}.value[${H}]`);S&&!u.includes(S.v)&&u.push(S.v)}),u.length===0?a(e,`${n}.value`,"No valid values"):(o.value=u,o)}if(Array.isArray(l))return a(e,`${n}.value`,"Expected one value, not a list");const s=X(e,l,i,`${n}.value`);return s?(o.value=s.v,o):null}function Y(e,t,n){return t===void 0?null:k(e,t,n,["and","or"])}function z(e,t,n,i){const r=[];return v(e,t,n).forEach((o,l)=>{const s=`${n}[${l}]`;if(!$(o))a(e,s,`Expected a filter, got ${f(o)}`);else if(F(o,"items")||F(o,"logicOperator")){if(i>=A){a(e,s,`Groups nest at most ${A} levels`);return}const u=z(e,o.items,`${s}.items`,i+1);u.length===0?a(e,s,"Empty group removed"):r.push({logicOperator:Y(e,o.logicOperator,`${s}.logicOperator`)??"and",items:u})}else{const u=Ae(e,o,s);u&&r.push(u)}}),r}function Ne(e,t,n){const i={};F(t,"items")&&(i.items=z(e,t.items,`${n}.items`,0));const r=Y(e,t.logicOperator,`${n}.logicOperator`);return r&&(i.logicOperator=r),F(t,"quickFilterValues")&&(i.quickFilterValues=[],v(e,t.quickFilterValues,`${n}.quickFilterValues`).forEach((o,l)=>{const s=typeof o=="string"||typeof o=="number"?String(o).trim():null;s===null?a(e,`${n}.quickFilterValues[${l}]`,`Expected text, got ${f(o)}`):s!==""&&i.quickFilterValues?.push(s)})),i}function je(e,t,n){const i=[];return v(e,t,n).forEach((r,o)=>{const l=`${n}[${o}]`;if(!$(r))return a(e,l,`Expected { field, sort }, got ${f(r)}`);const s=h(e,r.field,`${l}.field`,J,"sorted"),u=s&&k(e,r.sort,`${l}.sort`,["asc","desc"]);!s||!u||(i.some(g=>g.field===s.field)?a(e,l,`${f(s.field)} is sorted twice`):i.push({field:s.field,sort:u}))}),i}function P(e,t,n,i,r){const o=[];return v(e,t,n).forEach((l,s)=>{const u=h(e,l,`${n}[${s}]`,i,r);u&&!o.includes(u.field)&&o.push(u.field)}),o}const Se=e=>b(e).length>0,Ee=e=>b(e,E).length>0;function xe(e,t,n){const i={};for(const r of q(t)){if(B(t[r])==="none")continue;const o=h(e,r,`${n}.${r}`,Se,"aggregated"),l=o&&k(e,t[r],`${n}.${r}`,b(o));o&&l&&(i[o.field]=l)}return i}function Me(e,t,n){const i=[];return v(e,t.valueFields,`${n}.valueFields`).forEach((r,o)=>{const l=`${n}.valueFields[${o}]`;if(!$(r))return a(e,l,`Expected { field, aggFn }, got ${f(r)}`);const s=h(e,r.field,`${l}.field`,Ee,"a pivot value"),u=s&&k(e,r.aggFn,`${l}.aggFn`,b(s,E));s&&u&&!i.some(g=>g.field===s.field&&g.aggFn===u)&&i.push({field:s.field,aggFn:u})}),{rowFields:P(e,t.rowFields,`${n}.rowFields`,O,"a pivot label"),columnFields:P(e,t.columnFields,`${n}.columnFields`,O,"a pivot label"),valueFields:i}}function Ve(e,t,n){const i={};for(const r of q(t)){const o=h(e,r,`${n}.${r}`,U,"hidden"),l=T(t[r]);o&&(l.ok&&l.value!==null?i[o.field]=l.value:a(e,`${n}.${r}`,`Expected true or false, got ${f(t[r])}`))}return i}function qe(e,t,n,i,r){t==="sort"?r.sortModel=je(e,n,i):t==="grouping"?r.rowGroupingModel=P(e,n,i,O,"grouped"):$(n)?t==="filter"?r.filterModel=Ne(e,n,i):t==="aggregation"?r.aggregationModel=xe(e,n,i):t==="pivot"?r.pivotModel=Me(e,n,i):r.columnVisibilityModel=Ve(e,n,i):a(e,i,`Expected an object, got ${f(n)}`)}function Pe(e,t,n={}){const i={errors:[],byField:new Map},r={};try{for(const s of C(t))i.byField.set(s.field,s);let o=e;if(typeof e=="string")try{o=JSON.parse(e)}catch{return a(i,"","Not valid JSON"),{state:r,errors:i.errors}}if(!$(o))return a(i,"",`Expected an object, got ${f(o)}`),{state:r,errors:i.errors};const l=I(n);for(const s of q(o)){const u=x.find(g=>W[g]===s);s==="schemaVersion"?o[s]!==1&&a(i,s,`Unsupported schemaVersion ${f(o[s])}`):u?l.includes(u)?qe(i,u,o[s],s,r):a(i,s,"Part not allowed"):a(i,s,"Unknown part")}}catch{return a(i,"","Could not read the input"),{state:{},errors:i.errors}}return{state:r,errors:i.errors}}export{ue as getGridAiSchema,Pe as validateGridAiState};
|