@opencorestack/opengridx 3.3.0 → 3.5.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 +64 -0
- package/README.md +10 -1
- package/dist/ai.cjs +2 -0
- package/dist/ai.cjs.map +1 -0
- package/dist/ai.d.ts +366 -0
- package/dist/ai.es.js +2 -0
- package/dist/ai.es.js.map +1 -0
- package/dist/index.d.ts +396 -6
- package/dist/opengridx.css +1 -1
- package/dist/opengridx.es.js +8029 -6090
- package/dist/opengridx.es.js.map +1 -1
- package/dist/opengridx.umd.js +16 -14
- package/dist/opengridx.umd.js.map +1 -1
- package/docs/API_REFERENCE.md +182 -4
- package/docs/README.md +5 -1
- package/docs/architecture/datagrid-orchestration.md +19 -5
- package/docs/components/filter-panel.md +1 -0
- package/docs/components/header.md +4 -1
- package/docs/components/toolbar.md +7 -0
- package/docs/customization/theming.md +12 -1
- package/docs/features/ai-toolkit.md +213 -0
- package/docs/features/cell-selection.md +8 -2
- package/docs/features/clipboard.md +88 -0
- package/docs/features/editing-reordering.md +50 -0
- package/docs/features/fill-handle.md +125 -0
- package/docs/features/filtering.md +3 -0
- package/docs/features/header-filters.md +138 -0
- package/docs/features/keyboard-navigation.md +31 -2
- package/docs/features/undo-redo.md +79 -0
- package/docs/roadmap.md +3 -4
- package/lib/ai/agentTools.ts +160 -0
- package/lib/ai/coerce.ts +90 -0
- package/lib/ai/columns.ts +90 -0
- package/lib/ai/index.ts +33 -0
- package/lib/ai/promptHandler.ts +109 -0
- package/lib/ai/schema.ts +214 -0
- package/lib/ai/types.ts +152 -0
- package/lib/ai/validate.ts +296 -0
- package/lib/components/AiAssistant/GridAiAssistantArea.tsx +26 -0
- package/lib/components/AiAssistant/GridAiAssistantPanel.css +171 -0
- package/lib/components/AiAssistant/GridAiAssistantPanel.tsx +145 -0
- package/lib/components/AiAssistant/useSpeechInput.ts +74 -0
- package/lib/components/Cell/Cell.css +33 -0
- package/lib/components/Cell/Cell.tsx +6 -2
- package/lib/components/Cell/GridEditInputCell.css +12 -4
- package/lib/components/Cell/GridEditInputCell.tsx +19 -12
- package/lib/components/ColumnVisibilityPanel/ColumnVisibilityPanel.css +14 -0
- package/lib/components/ColumnVisibilityPanel/ColumnVisibilityPanel.tsx +3 -2
- package/lib/components/DataGrid/DataGrid.tsx +138 -5
- package/lib/components/DataGrid/GridLiveRegion.tsx +5 -2
- package/lib/components/DataGrid/GridPinnedRows.tsx +5 -1
- package/lib/components/DataGrid/GridVirtualRows.tsx +5 -1
- package/lib/components/FilterPanel/FilterPanel.css +14 -0
- package/lib/components/FilterPanel/FilterPanel.tsx +5 -11
- package/lib/components/Header/Header.tsx +44 -23
- package/lib/components/Header/headerColumnStyle.ts +28 -0
- package/lib/components/HeaderFilter/HeaderFilter.css +160 -0
- package/lib/components/HeaderFilter/HeaderFilterCell.tsx +370 -0
- package/lib/components/HeaderFilter/HeaderFilterOperatorMenu.tsx +124 -0
- package/lib/components/HeaderFilter/HeaderFilterRow.tsx +157 -0
- package/lib/components/ListView/ListView.css +2 -4
- package/lib/components/ListView/ListViewRow.tsx +2 -2
- package/lib/components/Toolbar/GlobalSearch.css +16 -0
- package/lib/components/Toolbar/GlobalSearch.tsx +3 -2
- package/lib/components/Toolbar/GridToolbar.tsx +51 -0
- package/lib/components/ui/Checkbox.tsx +16 -5
- package/lib/components/ui/Input.css +43 -0
- package/lib/components/ui/Input.tsx +24 -5
- package/lib/hooks/core/gridApiPlaceholder.ts +16 -1
- package/lib/hooks/core/gridToolbarHostContext.ts +5 -0
- package/lib/hooks/core/useDataGrid.ts +27 -0
- package/lib/hooks/core/useGridApiMethods.ts +39 -0
- package/lib/hooks/core/useGridAriaRows.ts +5 -3
- package/lib/hooks/core/useGridCellSelection.ts +107 -13
- package/lib/hooks/core/useGridClipboardPaste.ts +283 -0
- package/lib/hooks/core/useGridColumnsPanel.ts +24 -1
- package/lib/hooks/core/useGridControlledState.ts +27 -0
- package/lib/hooks/core/useGridFillHandle.ts +323 -0
- package/lib/hooks/core/useGridKeyboardNavigation.ts +29 -10
- package/lib/hooks/core/useGridToolbarProps.ts +19 -0
- package/lib/hooks/core/useGridUndoRedo.ts +220 -0
- package/lib/hooks/features/useGridAiAssistant.ts +284 -0
- package/lib/hooks/features/useGridBatchEdit.ts +217 -0
- package/lib/hooks/features/useGridEditing.ts +24 -45
- package/lib/index.ts +10 -0
- package/lib/styles/opengridx.css +4 -1
- package/lib/theme/DataGridThemeProvider.tsx +2 -0
- package/lib/theme/types.ts +2 -0
- package/lib/types/index.ts +343 -1
- package/lib/utils/aiAssistant.ts +205 -0
- package/lib/utils/aiBrand.ts +17 -0
- package/lib/utils/cellSelection.ts +25 -5
- package/lib/utils/editing/commit.ts +69 -0
- package/lib/utils/editing/fill.ts +238 -0
- package/lib/utils/editing/targets.ts +109 -0
- package/lib/utils/filtering/index.ts +14 -0
- package/lib/utils/focus/index.ts +9 -3
- package/lib/utils/headerFilters.ts +189 -0
- package/lib/utils/parsing.ts +252 -0
- package/llms.txt +97 -3
- package/package.json +8 -2
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,70 @@
|
|
|
5
5
|
|
|
6
6
|
---
|
|
7
7
|
|
|
8
|
+
## [3.5.0] — 2026-10-09
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- **AI toolkit, part 2: the Ask AI panel** — new `aiAssistant` prop (`{ onPrompt, placeholder?, suggestions?, voice?, parts? }`). `GridToolbar` shows an **Ask AI** button (`localeText.aiAssistantButton`) that opens a prompt panel under the toolbar; `apiRef.openAiAssistant()` / `closeAiAssistant()` open and close it without a toolbar, and `slots.aiAssistantPanel` replaces it (`GridAiAssistantPanelProps`). The panel has a prompt input (Enter sends), suggestion chips, a microphone button only where the browser has `SpeechRecognition` (the microphone is requested on click, never on load; `voice: false` hides it), Stop (aborts `context.signal`), the reply text, the session's earlier prompts, and the list of ignored parts. A reply is applied at once as one undoable step: every change is a removable chip (removing one re-applies the reply without it) and **Undo** restores the models from before the reply. `role="dialog"` labelled "Ask AI", focus to the input on open and back on close, Escape closes, status in the grid's live region. New `onAiAssistantApply(result)` and `onAiAssistantError(error)`. Grids without `aiAssistant` are unchanged, and the core bundle still holds no AI code.
|
|
13
|
+
- **Only validated replies are applied.** `validateGridAiState` now brands its result and the result's `state` with the non-enumerable `Symbol.for('opengridx.ai.validated')` (shape, equality and JSON unchanged); the panel applies a state only when it carries it, and otherwise shows "The reply was not validated" with a development warning.
|
|
14
|
+
- **`createGridAiPromptHandler({ columns, callModel, parts?, schemaOptions? })`** in `@opencorestack/opengridx/ai` — builds `onPrompt` for the app's own model: the schema once, then `callModel({ prompt, schema, currentState, history }, { signal })`; the reply may be a state object, a JSON string or text with a fenced JSON block (the text or a `message` key becomes the reply message). A `callModel` failure becomes an error result; an abort rejects with an `AbortError`.
|
|
15
|
+
- **`createGridAgentTools(apiRef, columns, options?)`** in `/ai` — plain tool objects (`name`, `description`, `inputSchema`, `execute`) for the Vercel AI SDK, CopilotKit, OpenAI or Anthropic tool calling and WebMCP: `get_grid_state`, `set_filter`, `set_sort`, `set_grouping`, `set_aggregation`, `set_column_visibility`, `clear_filters`, and `get_row_summary` only with `allowRowAccess: true` (the one tool that sends row data). Inputs are validated and applied all or nothing; `execute` never throws.
|
|
16
|
+
- **`GridApi`**: `setSortModel`, `setRowGroupingModel`, `setAggregationModel`, `setColumnVisibilityModel`, `setPivotModel` (each fires its change callback) and `getGridAiState()`, plus `openAiAssistant()` / `closeAiAssistant()`.
|
|
17
|
+
- **`onRowGroupingModelChange`** — `rowGroupingModel` is now a controlled/uncontrolled pair: without the prop the grid keeps its own grouping model, which `apiRef.setRowGroupingModel` and the assistant change. A grid that passes `rowGroupingModel` stays controlled. (v2.0 removed this callback because nothing called it; the assistant and `apiRef` now do.)
|
|
18
|
+
- New types: `GridAiAssistantOptions`, `GridAiAssistantPanelProps`, `GridAiAssistantStatus`, `GridAiChip`, `GridAiPromptContext`, `GridAiPromptResult`, `GridAiHistoryEntry`, `GridAiModelRequest`, `GridAiPromptHandlerOptions`, `GridAgentTool`, `GridAgentToolResult`, `GridAgentToolsOptions`, `GridAgentApi` (exported from `/ai`); `GridToolbarProps.onAiAssistantToggle` / `aiAssistantOpen` / `aiAssistantLabel`; `GridLocaleText.aiAssistantButton`.
|
|
19
|
+
- Demo page *AI Assistant* (`/ai-assistant`): a simulated, rule-based model (no API key, no network), suggestion chips, the real `fetch` integration sample, and a scripted agent driving the grid through `createGridAgentTools`. Package smoke suite: `ai-assistant` scenario in both fixtures.
|
|
20
|
+
- **Header filter row** — `headerFilters` (default off) adds a filter row under the column headers, inside the sticky header: a text box for `string`, a number box for `number` and a date box for `date` (the shared `Input`), an Any / Yes / No select for `boolean` and a `valueOptions` select for `singleSelect` (none for `image`), each with an operator button that opens a menu of the type's operators and a clear button. Typing is debounced by 300 ms; typing still pending is committed if the column scrolls out of the render window. New `GridColDef.headerFilter` (`false`: empty cell) and `GridColDef.headerFilterOperator` (starting operator; defaults `contains`, `=`, `is`), and `headerFilterHeight` (default 40, CSS variable `--ogx-header-filter-height`).
|
|
21
|
+
- **One shared `filterModel`.** Each cell owns only the root-level item `id: 'header:<field>'`: it is added at the end, updated in place and removed when cleared, and every other item, group, `logicOperator` and `quickFilterValues` passes through. The filter panel edits the same item and keeps its id. When the root `logicOperator` is `'or'`, the field sits inside a group, or the field has an untagged or second root item, the cell shows a read-only "Custom filter" label instead (a button that opens the `GridToolbar` filter panel); the row never rewrites a model it cannot show. With `filterMode="server"` changes are reported through `onFilterModelChange` as usual. Not rendered while `pivotMode` is active.
|
|
22
|
+
- **Layout.** Filter cells take each column's width, flex and sticky offset from the same calculation as the column headers, so they line up with pinned columns, column virtualization and horizontal scrolling. Scroll-into-view (keyboard, `scrollToIndexes`) and range auto-scroll measure the sticky header, so rows never end up under the taller header.
|
|
23
|
+
- **Keyboard and ARIA.** The row is a header row (`role="row"`, counted in `aria-rowcount` and the rows' `aria-rowindex`) of `role="columnheader"` cells labelled "Filter <header>". ArrowDown on a column header moves to its filter cell and on into the first row; Enter, Space or typing focuses the control, Escape returns to the cell, Alt+ArrowDown or Ctrl/Cmd+Enter opens the operator menu, Delete clears the filter. Controls have `tabIndex={-1}`: the grid stays a single Tab stop and Tab from a control leaves the grid.
|
|
24
|
+
- Public classes `ogx__header-filter-row`, `ogx__header-filter-cell` (`--focused`, `--active`, `--custom`, `--empty`, `--system`, `--pinned`, `--pinned-left`, `--pinned-right`, `--pinned-left-last`, `--pinned-right-first`), `ogx__header-filter-operator` (`--open`), `ogx__header-filter-menu`, `ogx__header-filter-input-wrapper`, `ogx__header-filter-input`, `ogx__header-filter-select`, `ogx__header-filter-operator-label`, `ogx__header-filter-clear`, `ogx__header-filter-custom`. The row uses the header's theme variables, so every built-in theme and dark mode style it.
|
|
25
|
+
- `GridToolbarProps.forceFiltersOpen` / `onFiltersPanelClose` (the grid passes them so "Custom filter" can open the panel); `HeaderProps.headerFilters`, `filterModel`, `onFilterModelChange`, `onOpenFilterPanel`, and `focusedCell.headerFilter`.
|
|
26
|
+
- Guide `docs/features/header-filters.md`, demo page *Header Filters* (`/header-filters`), and a `header-filters` scenario in both package smoke fixtures.
|
|
27
|
+
- **Fill handle** — with `cellSelection` on, the range's bottom-right cell (or the only selected cell) carries a fill handle. Drag it down, up, right or left (one direction: the side the pointer's cell lies beyond, the larger pointer movement on a diagonal; auto-scrolls past the grid edge like a range drag; touch drags work, `touch-action: none`) to fill the new cells from the range: numbers and dates with an exactly equal step continue as a series (decimals rounded to the source's places; dates by month on the same day of the month, clamped to the month end, else by calendar day), anything else repeats the source pattern, one cell copies. Alt at release copies instead of continuing; Escape cancels. **Ctrl/Cmd+D** fills down from the range's top row and **Ctrl/Cmd+R** right from its left column (focus in the grid, no editor open; the browser's bookmark and reload keys are prevented only then). Fills write through `valueSetter` and `processRowUpdate` once per row as one batch edit and one undo step; non-editable, synthetic and span-covered cells are skipped (`'notEditable'`); the range becomes the source plus the filled cells. New props `disableFillHandle` and `onFill(result)` (`GridBatchEditResult` plus `direction`), new `GridColDef.fillValue(params)` to decide a column's filled values (a throw skips the cell, `'invalidValue'`), new types `GridFillDirection`, `GridFillResult`, `GridFillValueParams`. Public class `ogx__cell-fill-handle` (24 px hit area), variable `--ogx-fill-handle-color` (defaults to the range border; theme key `grid.fillHandleColor`, set by every built-in theme). Guide: `docs/features/fill-handle.md`. Demo page *Fill Handle* (`/fill-handle`).
|
|
28
|
+
- Package smoke suite: `fill` scenario (handle drag series, Ctrl/Cmd+D, undo) in both fixtures.
|
|
29
|
+
|
|
30
|
+
### Changed
|
|
31
|
+
|
|
32
|
+
- `GridAiState`, `GridAiPart` and `GridAiValidationError` are declared in the grid's types so the `aiAssistant` prop can use them; `@opencorestack/opengridx/ai` re-exports them, so existing imports keep working.
|
|
33
|
+
- The `/ai` size budget in `check-bundle` is 8 kB gzipped (was 5 kB); the entry point is about 6.7 kB with the handler and tools.
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## [3.4.0] — 2026-10-08
|
|
38
|
+
|
|
39
|
+
### Added
|
|
40
|
+
|
|
41
|
+
- **`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'`.
|
|
42
|
+
- **`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.
|
|
43
|
+
- **`Input` `inputClassName`** — classes for the `<input>` itself; `className` still goes to the wrapper `<div>`. `error` now also sets `aria-invalid="true"` on the `<input>`.
|
|
44
|
+
- **`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.
|
|
45
|
+
- 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`.
|
|
46
|
+
- **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`).
|
|
47
|
+
- **`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.
|
|
48
|
+
- **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.
|
|
49
|
+
- **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`).
|
|
50
|
+
- **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`.
|
|
51
|
+
- Package smoke suite: `paste` scenario (paste, undo, redo with the real clipboard) in both fixtures.
|
|
52
|
+
- **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.
|
|
53
|
+
- `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.
|
|
54
|
+
- `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.
|
|
55
|
+
- **`GridColDef.aiExamples`** — opt-in example values for the schema. They are sent to the model, so list only data you are happy to share.
|
|
56
|
+
- Types `GridAiColumn`, `GridAiPart`, `GridAiPartOptions`, `GridAiSchema`, `GridAiSchemaOptions`, `GridAiJsonSchema`, `GridAiState`, `GridAiValidateOptions`, `GridAiValidationError`, `GridAiValidationResult`, exported from `/ai`.
|
|
57
|
+
- 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.
|
|
58
|
+
|
|
59
|
+
### Changed
|
|
60
|
+
|
|
61
|
+
- 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.
|
|
62
|
+
- 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.
|
|
63
|
+
- `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*`.
|
|
64
|
+
- 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.
|
|
65
|
+
- `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.
|
|
66
|
+
- A committed single-cell edit is stored through the same path as batch edits; behaviour is unchanged.
|
|
67
|
+
|
|
68
|
+
### Fixed
|
|
69
|
+
|
|
70
|
+
- **Cell range selection: a drag that auto-scrolled could end short of the pointer** (since 3.3.0). Dragging past the bottom of the grid scrolls it; on a slow machine the rows scrolled into view were not rendered yet when the pointer was hit-tested, so the range's head stayed on an earlier row (and the release could leave it there). When no cell is under the pointer during a drag, the head row is now worked out from the scroll position and the row heights.
|
|
71
|
+
|
|
8
72
|
## [3.3.0] — 2026-10-07
|
|
9
73
|
|
|
10
74
|
### Added
|
package/README.md
CHANGED
|
@@ -76,7 +76,9 @@ 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
|
+
- **Header Filters** (v3.5): `headerFilters` adds a filter row under the column headers (text, number, date and select controls with an operator menu) that edits the same `filterModel` as the filter panel and the quick filter.
|
|
79
80
|
- **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`).
|
|
81
|
+
- **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.
|
|
80
82
|
- **Accessibility**: WCAG 2.1 AA — full ARIA roles and keyboard navigation.
|
|
81
83
|
- **Theming**: CSS variable API, `DataGridThemeProvider` with 5 built-in themes (`darkTheme`, `roseTheme`, `emeraldTheme`, `amberTheme`, `compactTheme`) and custom themes.
|
|
82
84
|
- **Column & Row Reordering**: Drag-and-drop column reordering and row reordering.
|
|
@@ -156,6 +158,8 @@ This means that **Cursor**, **GitHub Copilot**, **Windsurf**, and other AI agent
|
|
|
156
158
|
| `sortable` | `boolean` | `true` | Enable/disable column sorting. |
|
|
157
159
|
| `sortComparator` | `(v1, v2, p1, p2) => number` | — | Custom ascending comparator (sees nulls; negated for desc). v3.1.0+ |
|
|
158
160
|
| `filterable` | `boolean` | `true` | Enable/disable column filtering. |
|
|
161
|
+
| `headerFilter` | `boolean` | `true` | `false` leaves the column's cell in the header filter row empty (v3.5). |
|
|
162
|
+
| `headerFilterOperator` | `GridFilterOperator` | by type | Operator a new header filter starts with (v3.5). |
|
|
159
163
|
| `resizable` | `boolean` | `true` | Allow drag-resize; double-click the handle to auto-size to content (v3.1.0+). |
|
|
160
164
|
| `hideable` | `boolean` | `true` | Allow hiding via the column panel. |
|
|
161
165
|
| `pinnable` | `boolean` | `true` | Allow pinning via the UI. |
|
|
@@ -187,6 +191,8 @@ This means that **Cursor**, **GitHub Copilot**, **Windsurf**, and other AI agent
|
|
|
187
191
|
| `onSortModelChange` | `(model: GridSortItem[]) => void` | Fires on sort change. |
|
|
188
192
|
| `filterModel` | `GridFilterModel` | Controlled filter model. |
|
|
189
193
|
| `onFilterModelChange` | `(model: GridFilterModel) => void` | Fires on filter change. |
|
|
194
|
+
| `headerFilters` | `boolean` | Filter row under the column headers, sharing `filterModel` (v3.5). |
|
|
195
|
+
| `headerFilterHeight` | `number` | Height of the header filter row. Default `40`. |
|
|
190
196
|
| `multiSort` | `boolean` | Every header click adds to the sort instead of replacing it (Shift+click always does). |
|
|
191
197
|
|
|
192
198
|
### Columns
|
|
@@ -519,8 +525,11 @@ Full documentation at 👉 **[opencorestack.github.io/OpenGridX](https://opencor
|
|
|
519
525
|
- **[Infinite Scroll](docs/features/infinite-scroll.md)** — Seamless lazy-loading
|
|
520
526
|
- **[Export Guide](docs/features/export-guide.md)** — Excel, CSV, JSON, and Print
|
|
521
527
|
- **[PDF Export](docs/features/pdf-export.md)** — Branded PDF reports
|
|
522
|
-
- **[Clipboard](docs/features/clipboard.md)** — Copy rows as TSV
|
|
528
|
+
- **[Clipboard](docs/features/clipboard.md)** — Copy rows as TSV, paste from Excel / Sheets, clear a range with Delete
|
|
523
529
|
- **[Cell Range Selection](docs/features/cell-selection.md)** — Select, total and copy cell ranges
|
|
530
|
+
- **[Undo & Redo](docs/features/undo-redo.md)** — Undo and redo edits, pastes and range clears
|
|
531
|
+
- **[Fill Handle](docs/features/fill-handle.md)** — Drag the range corner to fill a series or copy; Ctrl+D / Ctrl+R
|
|
532
|
+
- **[AI Toolkit](docs/features/ai-toolkit.md)** — Schema and validator for driving the grid from your own model
|
|
524
533
|
- **[Keyboard & Accessibility](docs/features/keyboard-navigation.md)** — Keys, focus and ARIA
|
|
525
534
|
- **[Data Source](docs/features/data-source.md)** — Server-side integration
|
|
526
535
|
- **[Loading States](docs/features/loading-states.md)** — Skeleton and shimmer overlays
|
package/dist/ai.cjs
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const Q=new Set;function de(e,t,r){process.env.NODE_ENV==="production"||Q.has(e)||(Q.add(e),console.warn(`[OpenGridX] ${t}.`,r))}function pe(e,t,r){const o=e[t];if(!r?.valueGetter)return{value:o};try{return{value:r.valueGetter({row:e,field:t,value:o})}}catch(n){return{value:void 0,error:n??new Error("valueGetter failed")}}}function ge(e,t,r){const{value:o,error:n}=pe(e,t,r);return n!==void 0&&de(`valueGetter:${t}`,`valueGetter for column "${t}" threw; the value is read as undefined`,n),o}const me=/^(\d{4})-(\d{2})-(\d{2})$/;function ee(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 r=e.trim();if(r==="")return null;const o=me.exec(r);if(o){const n=Number(o[1]),i=Number(o[2])-1,s=Number(o[3]);if(t=new Date(n,i,s),t.getMonth()!==i||t.getDate()!==s)return null}else t=new Date(r)}else return null;return Number.isNaN(t.getTime())?null:t}function ye(e){const t=ee(e);if(!t)return"";const r=String(t.getFullYear()).padStart(4,"0"),o=String(t.getMonth()+1).padStart(2,"0"),n=String(t.getDate()).padStart(2,"0");return`${r}-${o}-${n}`}function be(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 he=["sum","avg","count","min","max","unique"],B=["sum","avg","count","min","max"],te=["isEmpty","isNotEmpty"],D=3,U=["filter","sort","grouping","aggregation","pivot","columnVisibility"],H={filter:"filterModel",sort:"sortModel",grouping:"rowGroupingModel",aggregation:"aggregationModel",pivot:"pivotModel",columnVisibility:"columnVisibilityModel"};function S(e,t){return Object.prototype.hasOwnProperty.call(e,t)}function $(e){return e!==null&&typeof e=="object"&&!Array.isArray(e)}function re(e){const t=Array.isArray(e?.include)?e.include:U,r=Array.isArray(e?.exclude)?e.exclude:[];return U.filter(o=>t.includes(o)&&!r.includes(o))}function J(e){if(!Array.isArray(e))return[];const t=new Set,r=[];for(const o of e){if(!$(o))continue;const n=o.field;typeof n!="string"||n===""||n.startsWith("__")||o.isSpacer===!0||t.has(n)||(t.add(n),r.push(o))}return r}function ne(e){return be(e.type)}const ie=e=>e.filterable!==!1,oe=e=>e.sortable!==!1,T=e=>e.groupable!==!1,se=e=>e.hideable!==!1;function F(e,t=he){if(e.aggregable===!1)return[];const r=e.type==="number"||e.aggregable===!0?t:t.filter(n=>n==="count"),o=e.availableAggregationFunctions;return Array.isArray(o)?r.filter(n=>o.includes(n)):[...r]}function $e(e){const t=[];if(!Array.isArray(e.valueOptions))return t;for(const r of e.valueOptions){const o=r!==null&&typeof r=="object"?r.value:r;(typeof o=="string"||typeof o=="number"&&Number.isFinite(o))&&!t.includes(o)&&t.push(o)}return t}const Ae="OpenGridX grid state",Se=10;function j(e){const t=typeof e.headerName=="string"&&e.headerName!==""?e.headerName:e.field,r=typeof e.description=="string"&&e.description!==""?` (${e.description})`:"";return`${t}${r}`}function O(e,t,r){return r.descriptions?{...e,description:t}:e}function Oe(e,t){return!t.examples||!Array.isArray(e.aiExamples)?[]:e.aiExamples.filter(r=>typeof r=="string"||typeof r=="boolean"||typeof r=="number"&&Number.isFinite(r)).slice(0,Se)}function ve(e,t){let r;switch(e.type){case"number":r={type:"number"};break;case"date":r={type:"string",format:"date"};break;case"boolean":r={type:"boolean"};break;case"singleSelect":{const n=$e(e),i=n.length>0?{enum:n}:{type:"string"};r={anyOf:[i,{type:"array",items:i}]};break}default:r={type:"string"}}const o=Oe(e,t);return o.length>0?{...r,examples:o}:r}function Me(e,t){const r=ne(e),o=r.filter(s=>te.includes(s)),n=e.type==="singleSelect"?"; isAnyOf takes an array":"",i=o.length>0?`${o.join(" and ")} take no value${n}`:n.slice(2);return O({type:"object",properties:{field:{const:e.field},operator:{enum:r},value:ve(e,t)},required:["field","operator"],additionalProperties:!1},i?`${j(e)}: ${i}`:j(e),t)}function Ee(e,t,r){const o=e.filter(ie),n={},i={enum:["and","or"]};if(o.length>0){r.filterItem={oneOf:o.map(s=>Me(s,t))};for(let s=D;s>=1;s--){const l=s===D?{$ref:"#/$defs/filterItem"}:{anyOf:[{$ref:"#/$defs/filterItem"},{$ref:`#/$defs/filterGroup${s+1}`}]};r[`filterGroup${s}`]={type:"object",properties:{logicOperator:i,items:{type:"array",items:l}},required:["logicOperator","items"],additionalProperties:!1}}n.items={type:"array",items:{anyOf:[{$ref:"#/$defs/filterItem"},{$ref:"#/$defs/filterGroup1"}]}},n.logicOperator=i}return n.quickFilterValues=O({type:"array",items:{type:"string"}},"Words a row must contain, in any column",t),{type:"object",properties:n,additionalProperties:!1}}function q(e){return{enum:e.map(t=>t.field)}}function we(e,t,r,o){switch(e){case"filter":return Ee(t,r,o);case"sort":{const n=t.filter(oe);return n.length===0?null:O({type:"array",items:{type:"object",properties:{field:q(n),sort:{enum:["asc","desc"]}},required:["field","sort"],additionalProperties:!1}},"Most significant first",r)}case"grouping":{const n=t.filter(T);return n.length===0?null:O({type:"array",items:q(n),uniqueItems:!0},"Outermost first",r)}case"aggregation":{const n={};for(const i of t){const s=F(i);s.length>0&&(n[i.field]=O({enum:s},j(i),r))}return Object.keys(n).length===0?null:{type:"object",properties:n,additionalProperties:!1}}case"pivot":{const n=t.filter(T),i=t.map(l=>({col:l,fns:F(l,B)})).filter(l=>l.fns.length>0).map(({col:l,fns:a})=>O({type:"object",properties:{field:{const:l.field},aggFn:{enum:a}},required:["field","aggFn"],additionalProperties:!1},j(l),r));if(n.length===0||i.length===0)return null;const s={type:"array",items:q(n),uniqueItems:!0};return{type:"object",properties:{rowFields:s,columnFields:s,valueFields:{type:"array",items:{oneOf:i}}},required:["rowFields","columnFields","valueFields"],additionalProperties:!1}}case"columnVisibility":{const n={};for(const i of t.filter(se))n[i.field]=O({type:"boolean"},j(i),r);return Object.keys(n).length===0?null:O({type:"object",properties:n,additionalProperties:!1},"false hides a column",r)}}}function Y(e,t={}){const r={descriptions:t.descriptions!==!1,examples:t.examples!==!1},o=J(e),n={},i={};for(const l of re(t)){const a=we(l,o,r,n);a&&(i[H[l]]=a)}const s={$schema:"https://json-schema.org/draft/2020-12/schema",schemaVersion:1,title:Ae,type:"object",properties:i,additionalProperties:!1};return r.descriptions&&(s.description="Grid state to apply. Include only the parts the user asked to change."),Object.keys(n).length>0&&(s.$defs=n),s}const Fe=Symbol.for("opengridx.ai.validated");function I(e){return Object.defineProperty(e,Fe,{value:!0,enumerable:!1}),e}const M={ok:!1},h=e=>({ok:!0,value:e}),Ne=/^\d+(?:\.\d*)?(?:e[+-]?\d+)?$/i,_e=/^\.\d+(?:e[+-]?\d+)?$/i,ke=/^\d{1,3}(?:,\d{3})+(?:\.\d*)?$/,je=/[\p{Sc}\s]/gu;function Te(e){let t=e.replace(je,"");if(t==="")return e.trim()===""?h(null):M;let r=!1;t.startsWith("(")&&t.endsWith(")")&&(r=!0,t=t.slice(1,-1)),(t.startsWith("-")||t.startsWith("+")||t.startsWith("\u2212"))&&(t[0]!=="+"&&(r=!r),t=t.slice(1));let o=!1;if(t.endsWith("%")&&(o=!0,t=t.slice(0,-1)),ke.test(t))t=t.split(",").join("");else if(!Ne.test(t)&&!_e.test(t))return M;let n=Number(t);return Number.isFinite(n)?(o&&(n/=100),r&&(n=-n),h(n)):M}function Ge(e,t){const r=e.trim();if(r==="")return h(null);const o=ee(r);return o?h(o):M}const Ve=new Set(["true","yes","1"]),Pe=new Set(["false","no","0"]);function Re(e,t){const r=e.trim().toLowerCase();return r===""?h(null):Ve.has(r)?h(!0):Pe.has(r)?h(!1):M}function De(e,t){const r=e.trim();if(r==="")return h(null);if(!t||t.length===0)return M;const o=r.toLowerCase();for(const n of t){const i=typeof n=="object"?n.value:n;if(String(i).toLowerCase()===o)return h(i)}for(const n of t)if(typeof n=="object"&&n.label.toLowerCase()===o)return h(n.value);return M}const d=e=>({ok:!0,value:e}),A={ok:!1,reason:"invalidValue"},L=e=>e.ok?d(e.value):A;function C(e){return e==null||typeof e=="string"&&e.trim()===""}function Ie(e){return e==null?d(""):typeof e=="string"?d(e):typeof e=="number"||typeof e=="boolean"?d(String(e)):A}function Le(e){return C(e)?d(null):typeof e=="number"?Number.isFinite(e)?d(e):A:typeof e!="string"?A:L(Te(e))}function Ce(e){return C(e)?d(null):e instanceof Date?Number.isNaN(e.getTime())?A:d(e):typeof e=="number"?Number.isFinite(e)?d(new Date(e)):A:typeof e!="string"?A:L(Ge(e))}function le(e){return C(e)?d(null):typeof e=="boolean"?d(e):typeof e!="string"&&typeof e!="number"?A:L(Re(String(e)))}function qe(e,t){if(C(e))return d(null);if(typeof e!="string"&&typeof e!="number"&&typeof e!="boolean")return A;const r=t.valueOptions;return!r||r.length===0?d(e):L(De(String(e),r))}function We(e,t){switch(t.type){case"number":return Le(e);case"date":return Ce(e);case"boolean":return le(e);case"singleSelect":return qe(e,t);default:return Ie(e)}}const W=200,Ue=100;function u(e,t,r){return e.errors.length<Ue&&e.errors.push({path:t,message:r}),null}function p(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 ae(e){return typeof e=="string"?e.trim().toLowerCase():null}function K(e){return Object.keys(e).filter(t=>t!=="__proto__")}function N(e,t,r,o,n){const i=typeof t=="string"?e.byField.get(t):void 0;return i?o(i)?i:u(e,r,`Field ${p(t)} cannot be ${n}`):u(e,r,`Unknown field ${p(t)}`)}function _(e,t,r){if(t==null)return[];const o=Array.isArray(t)?t:[t];return o.length>W&&u(e,r,`Only the first ${W} entries are read`),o.slice(0,W)}function G(e,t,r,o){const n=ae(t);return o.find(i=>i.toLowerCase()===n)??u(e,r,`${p(t)} is not one of ${o.join(", ")}`)}function Z(e,t,r,o){const n=We(t,r);if(!n.ok)return u(e,o,`${p(t)} is not a valid ${r.type??"string"} value`);const i=n.value instanceof Date?ye(n.value):n.value;return i==null||i===""?u(e,o,"Missing value"):{v:i}}function Xe(e,t,r){const o=N(e,t.field,`${r}.field`,ie,"filtered");if(!o)return null;const n=G(e,t.operator,`${r}.operator`,ne(o));if(n===null)return null;const i={field:o.field,operator:n};if(te.includes(i.operator))return i;const s=S(t,"value")?t.value:void 0;if(i.operator==="isAnyOf"){const a=[];return _(e,s,`${r}.value`).forEach((c,v)=>{const g=Z(e,c,o,`${r}.value[${v}]`);g&&!a.includes(g.v)&&a.push(g.v)}),a.length===0?u(e,`${r}.value`,"No valid values"):(i.value=a,i)}if(Array.isArray(s))return u(e,`${r}.value`,"Expected one value, not a list");const l=Z(e,s,o,`${r}.value`);return l?(i.value=l.v,i):null}function ue(e,t,r){return t===void 0?null:G(e,t,r,["and","or"])}function fe(e,t,r,o){const n=[];return _(e,t,r).forEach((i,s)=>{const l=`${r}[${s}]`;if(!$(i))u(e,l,`Expected a filter, got ${p(i)}`);else if(S(i,"items")||S(i,"logicOperator")){if(o>=D){u(e,l,`Groups nest at most ${D} levels`);return}const a=fe(e,i.items,`${l}.items`,o+1);a.length===0?u(e,l,"Empty group removed"):n.push({logicOperator:ue(e,i.logicOperator,`${l}.logicOperator`)??"and",items:a})}else{const a=Xe(e,i,l);a&&n.push(a)}}),n}function Be(e,t,r){const o={};S(t,"items")&&(o.items=fe(e,t.items,`${r}.items`,0));const n=ue(e,t.logicOperator,`${r}.logicOperator`);return n&&(o.logicOperator=n),S(t,"quickFilterValues")&&(o.quickFilterValues=[],_(e,t.quickFilterValues,`${r}.quickFilterValues`).forEach((i,s)=>{const l=typeof i=="string"||typeof i=="number"?String(i).trim():null;l===null?u(e,`${r}.quickFilterValues[${s}]`,`Expected text, got ${p(i)}`):l!==""&&o.quickFilterValues?.push(l)})),o}function He(e,t,r){const o=[];return _(e,t,r).forEach((n,i)=>{const s=`${r}[${i}]`;if(!$(n))return u(e,s,`Expected { field, sort }, got ${p(n)}`);const l=N(e,n.field,`${s}.field`,oe,"sorted"),a=l&&G(e,n.sort,`${s}.sort`,["asc","desc"]);!l||!a||(o.some(c=>c.field===l.field)?u(e,s,`${p(l.field)} is sorted twice`):o.push({field:l.field,sort:a}))}),o}function X(e,t,r,o,n){const i=[];return _(e,t,r).forEach((s,l)=>{const a=N(e,s,`${r}[${l}]`,o,n);a&&!i.includes(a.field)&&i.push(a.field)}),i}const Je=e=>F(e).length>0,Ye=e=>F(e,B).length>0;function Ke(e,t,r){const o={};for(const n of K(t)){if(ae(t[n])==="none")continue;const i=N(e,n,`${r}.${n}`,Je,"aggregated"),s=i&&G(e,t[n],`${r}.${n}`,F(i));i&&s&&(o[i.field]=s)}return o}function ze(e,t,r){const o=[];return _(e,t.valueFields,`${r}.valueFields`).forEach((n,i)=>{const s=`${r}.valueFields[${i}]`;if(!$(n))return u(e,s,`Expected { field, aggFn }, got ${p(n)}`);const l=N(e,n.field,`${s}.field`,Ye,"a pivot value"),a=l&&G(e,n.aggFn,`${s}.aggFn`,F(l,B));l&&a&&!o.some(c=>c.field===l.field&&c.aggFn===a)&&o.push({field:l.field,aggFn:a})}),{rowFields:X(e,t.rowFields,`${r}.rowFields`,T,"a pivot label"),columnFields:X(e,t.columnFields,`${r}.columnFields`,T,"a pivot label"),valueFields:o}}function Qe(e,t,r){const o={};for(const n of K(t)){const i=N(e,n,`${r}.${n}`,se,"hidden"),s=le(t[n]);i&&(s.ok&&s.value!==null?o[i.field]=s.value:u(e,`${r}.${n}`,`Expected true or false, got ${p(t[n])}`))}return o}function Ze(e,t,r,o,n){t==="sort"?n.sortModel=He(e,r,o):t==="grouping"?n.rowGroupingModel=X(e,r,o,T,"grouped"):$(r)?t==="filter"?n.filterModel=Be(e,r,o):t==="aggregation"?n.aggregationModel=Ke(e,r,o):t==="pivot"?n.pivotModel=ze(e,r,o):n.columnVisibilityModel=Qe(e,r,o):u(e,o,`Expected an object, got ${p(r)}`)}function z(e,t,r={}){const o=xe(e,t,r);return I(o.state),I(o)}function xe(e,t,r){const o={errors:[],byField:new Map},n={};try{for(const l of J(t))o.byField.set(l.field,l);let i=e;if(typeof e=="string")try{i=JSON.parse(e)}catch{return u(o,"","Not valid JSON"),{state:n,errors:o.errors}}if(!$(i))return u(o,"",`Expected an object, got ${p(i)}`),{state:n,errors:o.errors};const s=re(r);for(const l of K(i)){const a=U.find(c=>H[c]===l);l==="schemaVersion"?i[l]!==1&&u(o,l,`Unsupported schemaVersion ${p(i[l])}`):a?s.includes(a)?Ze(o,a,i[l],l,n):u(o,l,"Part not allowed"):u(o,l,"Unknown part")}}catch{return u(o,"","Could not read the input"),{state:{},errors:o.errors}}return{state:n,errors:o.errors}}const k="```";function V(){return typeof DOMException=="function"?new DOMException("The operation was aborted.","AbortError"):Object.assign(new Error("The operation was aborted."),{name:"AbortError"})}function et(e){const t=e.trim();if(t.startsWith("{"))return{json:t,message:""};const r=e.indexOf(k),o=r<0?-1:e.indexOf(`
|
|
2
|
+
`,r+k.length),n=o<0?-1:e.indexOf(k,o+1),i=n<0?"":e.slice(r+k.length,o).trim().toLowerCase();return n<0||i!==""&&i!=="json"?{json:null,message:t}:{json:e.slice(o+1,n),message:`${e.slice(0,r)}${e.slice(n+k.length)}`.trim()}}function tt(e){return e instanceof Error&&e.message?e.message:typeof e=="string"&&e?e:"Unknown error"}function ce(e,t){const r={state:I({}),errors:e};return t&&(r.message=t),I(r)}function rt(e,t,r){let o=e,n="";if(typeof e=="string"){const s=et(e);if(s.json===null)return ce([],s.message||void 0);o=s.json,n=s.message}else if($(e)&&S(e,"message")&&typeof e.message=="string"){const{message:s,...l}=e;o=l,n=s}const i=z(o,t,r);return n&&(i.message=n),i}function nt(e){const{columns:t,callModel:r,parts:o,schemaOptions:n={}}=e,i={include:o??n.include,exclude:n.exclude},s=Y(t,{...n,...i});return async(l,a)=>{const c=a?.signal??new AbortController().signal;if(c.aborted)throw V();const v=new Promise((m,y)=>{c.addEventListener("abort",()=>y(V()),{once:!0})});v.catch(()=>{});let g;try{const m={prompt:String(l),schema:s,currentState:a?.currentState??{},history:Array.isArray(a?.history)?a.history:[]};g=await Promise.race([r(m,{signal:c}),v])}catch(m){if(c.aborted)throw V();return ce([{path:"",message:`The model call failed: ${tt(m)}`}])}if(c.aborted)throw V();return rt(g,t,i)}}const it=20,ot=1e3,st=[{name:"set_filter",part:"filter",description:"Replace the filter model of the grid (filters and quick-filter words)."},{name:"set_sort",part:"sort",description:"Replace the sort model of the grid, most significant field first. An empty list clears sorting."},{name:"set_grouping",part:"grouping",description:"Group the rows by these fields, outermost first. An empty list removes grouping."},{name:"set_aggregation",part:"aggregation",description:"Replace the summaries: field to aggregation function. An empty object removes them."},{name:"set_column_visibility",part:"columnVisibility",description:"Show (true) or hide (false) columns. Columns not listed keep their visibility."}];function w(e,t=""){return{ok:!1,errors:[{path:t,message:e}]}}async function P(e){try{return e()}catch(t){return w(t instanceof Error&&t.message?t.message:"The tool failed")}}function R(e){const t=e?.current;return t&&typeof t.getGridAiState=="function"?t:null}function lt(e){return e instanceof Date?Number.isNaN(e.getTime())?null:e.toISOString():typeof e=="number"?Number.isFinite(e)?e:null:typeof e=="string"||typeof e=="boolean"?e:null}function at(e,t,r,o,n){const i=H[o],s=Y(t,{include:[o]});if(!S(s.properties,i))return null;const{$schema:l,schemaVersion:a,title:c,description:v,...g}=s;return{name:r,description:n,inputSchema:{...g,required:[i]},execute:m=>P(()=>{const y=R(e);if(!y)return w("The grid is not ready");if(!$(m)||!S(m,i))return w(`Expected an object with "${i}"`,i);const{state:f,errors:E}=z({[i]:m[i]},t,{include:[o]});if(E.length>0)return{ok:!1,errors:E};const b={};return f.filterModel&&y.setFilterModel(b.filterModel=f.filterModel),f.sortModel&&y.setSortModel(b.sortModel=f.sortModel),f.rowGroupingModel&&y.setRowGroupingModel(b.rowGroupingModel=f.rowGroupingModel),f.aggregationModel&&y.setAggregationModel(b.aggregationModel=f.aggregationModel),f.columnVisibilityModel&&(b.columnVisibilityModel={...y.getGridAiState().columnVisibilityModel,...f.columnVisibilityModel},y.setColumnVisibilityModel(b.columnVisibilityModel)),{ok:!0,applied:b}})}}const x={type:"object",properties:{},additionalProperties:!1};function ut(e,t,r={}){const o=J(t),n=[{name:"get_grid_state",description:"Read the current filter, sort, grouping, aggregation, pivot and column visibility of the grid, and its columns.",inputSchema:x,execute:()=>P(()=>{const i=R(e);return i?{ok:!0,state:i.getGridAiState(),columns:o.map(s=>({field:s.field,headerName:typeof s.headerName=="string"?s.headerName:s.field,type:typeof s.type=="string"?s.type:"string"}))}:w("The grid is not ready")})}];for(const{name:i,part:s,description:l}of st){const a=at(e,o,i,s,l);a&&n.push(a)}if(n.push({name:"clear_filters",description:"Remove every filter and the quick-filter words.",inputSchema:x,execute:()=>P(()=>{const i=R(e);if(!i)return w("The grid is not ready");const s={items:[]};return i.setFilterModel(s),{ok:!0,applied:{filterModel:s}}})}),r.allowRowAccess===!0){const i=typeof r.maxRows=="number"&&r.maxRows>=1?Math.min(Math.floor(r.maxRows),ot):it,s=Array.isArray(r.fields)?o.filter(l=>r.fields?.includes(l.field)):o;n.push({name:"get_row_summary",description:`Count the rows that pass the current filter and read the first ones (at most ${i}, fields: ${s.map(l=>l.field).join(", ")}).`,inputSchema:{type:"object",properties:{limit:{type:"integer",minimum:1,maximum:i}},additionalProperties:!1},execute:l=>P(()=>{const a=R(e);if(!a)return w("The grid is not ready");const c=$(l)&&S(l,"limit")?l.limit:i,v=typeof c=="number"&&c>=1?Math.min(Math.floor(c),i):i,g=a.getAllFilteredRows(),m=Array.isArray(g)?g:[],y=m.slice(0,v).map(f=>{const E={};if(!$(f))return E;for(const b of s)E[b.field]=lt(ge(f,b.field,b));return E});return{ok:!0,rowCount:m.length,rows:y}})})}return n}exports.createGridAgentTools=ut,exports.createGridAiPromptHandler=nt,exports.getGridAiSchema=Y,exports.validateGridAiState=z;
|
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/aiBrand.ts","../lib/utils/parsing.ts","../lib/ai/coerce.ts","../lib/ai/validate.ts","../lib/ai/promptHandler.ts","../lib/ai/agentTools.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\n/**\n * The text an input shows for a committed filter value: dates as `YYYY-MM-DD`, lists joined with\n * commas. Never used to emit anything (the filter panel and the header filter row).\n */\nexport function filterValueToInputText(value: unknown, colType?: string): string {\n if (value == null) return '';\n if (colType === 'date' || value instanceof Date) {\n const day = toLocalDateString(value);\n return day || (typeof value === 'string' ? value : '');\n }\n if (Array.isArray(value)) return value.map(v => String(v)).join(', ');\n return String(value);\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","// The \"validated\" brand of the AI toolkit. `validateGridAiState` (in the `/ai` entry point) puts it on\n// its result and on the result's `state`; the grid's `aiAssistant` applies a state only when it carries\n// it. Shared by both bundles, so it holds no AI code: just the registered symbol and two helpers.\n\n/** `Symbol.for('opengridx.ai.validated')`: the same symbol in every bundle and realm. */\nexport const GRID_AI_VALIDATED_BRAND: symbol = Symbol.for('opengridx.ai.validated');\n\n/** Brands `value` (non-enumerable, so it does not change equality, spreading or JSON) and returns it. */\nexport function brandGridAiValidated<T extends object>(value: T): T {\n Object.defineProperty(value, GRID_AI_VALIDATED_BRAND, { value: true, enumerable: false });\n return value;\n}\n\n/** Whether `value` carries the brand. */\nexport function isGridAiValidated(value: unknown): boolean {\n return typeof value === 'object' && value !== null && (value as Record<symbol, unknown>)[GRID_AI_VALIDATED_BRAND] === true;\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 { brandGridAiValidated } from '../utils/aiBrand';\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 *\n * The result and its `state` carry the brand `Symbol.for('opengridx.ai.validated')` (not enumerable):\n * the grid's `aiAssistant` applies only a branded state. Spreading the result keeps `state` branded.\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 result = validate(json, columns, options);\n brandGridAiValidated(result.state);\n return brandGridAiValidated(result);\n}\n\nfunction validate(json: unknown, columns: readonly GridAiColumn[], options: GridAiValidateOptions): 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","// createGridAiPromptHandler: the `aiAssistant.onPrompt` function for an app's own model. It builds the\n// schema once, calls the app's `callModel`, reads the reply (an object, JSON text, or text with a fenced\n// JSON block) and returns the validated, branded result. The grid never sees the raw reply.\nimport type { GridAiPromptContext, GridAiPromptResult } from '../types';\nimport { brandGridAiValidated } from '../utils/aiBrand';\nimport { hasOwn, isPlainObject } from './columns';\nimport { getGridAiSchema } from './schema';\nimport type { GridAiColumn, GridAiPartOptions, GridAiPromptHandlerOptions, GridAiValidationError } from './types';\nimport { validateGridAiState } from './validate';\n\nconst FENCE = '```';\n\n/** A DOMException named `AbortError`, as `fetch` rejects with. */\nexport function abortError(): Error {\n return typeof DOMException === 'function'\n ? new DOMException('The operation was aborted.', 'AbortError')\n : Object.assign(new Error('The operation was aborted.'), { name: 'AbortError' });\n}\n\n/**\n * Splits a text reply into the JSON it holds and the text around it: the whole text when it starts\n * with `{`, else the first fenced block (with no language or `json`). A plain loop, no regex.\n */\nexport function readModelText(text: string): { json: string | null; message: string } {\n const trimmed = text.trim();\n if (trimmed.startsWith('{')) return { json: trimmed, message: '' };\n const open = text.indexOf(FENCE);\n const lineEnd = open < 0 ? -1 : text.indexOf('\\n', open + FENCE.length);\n const close = lineEnd < 0 ? -1 : text.indexOf(FENCE, lineEnd + 1);\n const lang = close < 0 ? '' : text.slice(open + FENCE.length, lineEnd).trim().toLowerCase();\n if (close < 0 || (lang !== '' && lang !== 'json')) return { json: null, message: trimmed };\n return {\n json: text.slice(lineEnd + 1, close),\n message: `${text.slice(0, open)}${text.slice(close + FENCE.length)}`.trim(),\n };\n}\n\nfunction errorText(error: unknown): string {\n if (error instanceof Error && error.message) return error.message;\n return typeof error === 'string' && error ? error : 'Unknown error';\n}\n\n/** A branded result with an empty state: nothing to apply, the errors explain why. */\nfunction emptyResult(errors: GridAiValidationError[], message?: string): GridAiPromptResult {\n const result: GridAiPromptResult = { state: brandGridAiValidated({}), errors };\n if (message) result.message = message;\n return brandGridAiValidated(result);\n}\n\n/** Validates a model reply into a branded result. Never throws. */\nexport function readModelReply(reply: unknown, columns: readonly GridAiColumn[], parts: GridAiPartOptions): GridAiPromptResult {\n let json: unknown = reply;\n let message = '';\n if (typeof reply === 'string') {\n const text = readModelText(reply);\n if (text.json === null) return emptyResult([], text.message || undefined);\n json = text.json;\n message = text.message;\n } else if (isPlainObject(reply) && hasOwn(reply, 'message') && typeof reply.message === 'string') {\n // A reply such as { filterModel, message: 'Showing paid orders' }: the message is not state.\n const { message: text, ...rest } = reply;\n json = rest;\n message = text as string;\n }\n const result: GridAiPromptResult = validateGridAiState(json, columns, parts);\n if (message) result.message = message;\n return result;\n}\n\n/**\n * Builds `aiAssistant.onPrompt` for your own model. On each prompt it calls\n * `callModel({ prompt, schema, currentState, history }, { signal })`, reads the reply (a state object,\n * a JSON string, or a fenced JSON block inside text), validates it with `validateGridAiState` and\n * returns the branded result. A `callModel` throw or rejection becomes\n * `{ state: {}, errors: [{ path: '', message }] }`; an abort rejects with an `AbortError`.\n * @since v3.5\n */\nexport function createGridAiPromptHandler<C extends GridAiColumn>(\n options: GridAiPromptHandlerOptions<C>,\n): (prompt: string, context: GridAiPromptContext) => Promise<GridAiPromptResult> {\n const { columns, callModel, parts, schemaOptions = {} } = options;\n const partOptions: GridAiPartOptions = { include: parts ?? schemaOptions.include, exclude: schemaOptions.exclude };\n const schema = getGridAiSchema(columns, { ...schemaOptions, ...partOptions });\n\n return async (prompt, context) => {\n const signal = context?.signal ?? new AbortController().signal;\n if (signal.aborted) throw abortError();\n // Stop resolves the prompt at once, even when callModel ignores the signal.\n const aborted = new Promise<never>((_, reject) => {\n signal.addEventListener('abort', () => reject(abortError()), { once: true });\n });\n aborted.catch(() => undefined);\n let reply: unknown;\n try {\n const request = {\n prompt: String(prompt),\n schema,\n currentState: context?.currentState ?? {},\n history: Array.isArray(context?.history) ? context.history : [],\n };\n reply = await Promise.race([callModel(request, { signal }), aborted]);\n } catch (error) {\n if (signal.aborted) throw abortError();\n return emptyResult([{ path: '', message: `The model call failed: ${errorText(error)}` }]);\n }\n if (signal.aborted) throw abortError();\n return readModelReply(reply, columns, partOptions);\n };\n}\n","// createGridAgentTools: plain tool objects ({ name, description, inputSchema, execute }) that let an AI\n// agent read and change the grid through its apiRef. No SDK: they fit the Vercel AI SDK, CopilotKit,\n// OpenAI or Anthropic tool calling and WebMCP. Every input is validated; execute never throws.\nimport type { GridAiState, GridRowModel } from '../types';\nimport { getCellValue } from '../utils/values';\n\ntype CellColumn = Parameters<typeof getCellValue>[2];\nimport { hasOwn, isPlainObject, PART_KEYS, usableColumns } from './columns';\nimport { getGridAiSchema } from './schema';\nimport type {\n GridAgentApi,\n GridAgentTool,\n GridAgentToolResult,\n GridAgentToolsOptions,\n GridAiColumn,\n GridAiJsonSchema,\n GridAiPart,\n} from './types';\nimport { validateGridAiState } from './validate';\n\nconst DEFAULT_MAX_ROWS = 20;\nconst MAX_ROWS_LIMIT = 1000;\n\nconst SET_TOOLS: { name: string; part: GridAiPart; description: string }[] = [\n { name: 'set_filter', part: 'filter', description: 'Replace the filter model of the grid (filters and quick-filter words).' },\n { name: 'set_sort', part: 'sort', description: 'Replace the sort model of the grid, most significant field first. An empty list clears sorting.' },\n { name: 'set_grouping', part: 'grouping', description: 'Group the rows by these fields, outermost first. An empty list removes grouping.' },\n { name: 'set_aggregation', part: 'aggregation', description: 'Replace the summaries: field to aggregation function. An empty object removes them.' },\n { name: 'set_column_visibility', part: 'columnVisibility', description: 'Show (true) or hide (false) columns. Columns not listed keep their visibility.' },\n];\n\ntype ApiRef = { readonly current: GridAgentApi | null | undefined };\n\nfunction failure(message: string, path = ''): GridAgentToolResult {\n return { ok: false, errors: [{ path, message }] };\n}\n\n/** Runs a tool body; a throw becomes `{ ok: false }`. */\nasync function run(body: () => GridAgentToolResult): Promise<GridAgentToolResult> {\n try {\n return body();\n } catch (error) {\n return failure(error instanceof Error && error.message ? error.message : 'The tool failed');\n }\n}\n\nfunction apiOf(apiRef: ApiRef): GridAgentApi | null {\n const api = apiRef?.current;\n return api && typeof api.getGridAiState === 'function' ? api : null;\n}\n\n/** A JSON-safe cell value: primitives as they are, a Date as an ISO string, anything else null. */\nfunction jsonValue(value: unknown): unknown {\n if (value instanceof Date) return Number.isNaN(value.getTime()) ? null : value.toISOString();\n if (typeof value === 'number') return Number.isFinite(value) ? value : null;\n return typeof value === 'string' || typeof value === 'boolean' ? value : null;\n}\n\nfunction setTool(apiRef: ApiRef, columns: readonly GridAiColumn[], name: string, part: GridAiPart, description: string): GridAgentTool | null {\n const key = PART_KEYS[part] as keyof GridAiState;\n const schema = getGridAiSchema(columns, { include: [part] });\n if (!hasOwn(schema.properties, key)) return null;\n const { $schema: _version, schemaVersion: _schemaVersion, title: _title, description: _description, ...inputSchema } = schema;\n return {\n name,\n description,\n inputSchema: { ...inputSchema, required: [key] },\n execute: (input) => run(() => {\n const api = apiOf(apiRef);\n if (!api) return failure('The grid is not ready');\n if (!isPlainObject(input) || !hasOwn(input, key)) return failure(`Expected an object with \"${key}\"`, key);\n const { state, errors } = validateGridAiState({ [key]: input[key] }, columns, { include: [part] });\n // All or nothing: the agent gets the errors back and can correct its call.\n if (errors.length > 0) return { ok: false, errors };\n const applied: GridAiState = {};\n if (state.filterModel) api.setFilterModel(applied.filterModel = state.filterModel);\n if (state.sortModel) api.setSortModel(applied.sortModel = state.sortModel);\n if (state.rowGroupingModel) api.setRowGroupingModel(applied.rowGroupingModel = state.rowGroupingModel);\n if (state.aggregationModel) api.setAggregationModel(applied.aggregationModel = state.aggregationModel);\n if (state.columnVisibilityModel) {\n applied.columnVisibilityModel = { ...api.getGridAiState().columnVisibilityModel, ...state.columnVisibilityModel };\n api.setColumnVisibilityModel(applied.columnVisibilityModel);\n }\n return { ok: true, applied };\n }),\n };\n}\n\nconst NO_INPUT: GridAiJsonSchema = { type: 'object', properties: {}, additionalProperties: false };\n\n/**\n * Tools for an AI agent: `get_grid_state`, `set_filter`, `set_sort`, `set_grouping`, `set_aggregation`,\n * `set_column_visibility`, `clear_filters`, and `get_row_summary` only with `allowRowAccess: true` (the\n * one tool that sends row values). Each tool is `{ name, description, inputSchema, execute }`; inputs are\n * checked with `validateGridAiState` and applied through `apiRef` only when valid. `execute` never throws.\n * @since v3.5\n */\nexport function createGridAgentTools<C extends GridAiColumn>(\n apiRef: ApiRef,\n columns: readonly C[],\n options: GridAgentToolsOptions = {},\n): GridAgentTool[] {\n const cols = usableColumns(columns);\n const tools: GridAgentTool[] = [{\n name: 'get_grid_state',\n description: 'Read the current filter, sort, grouping, aggregation, pivot and column visibility of the grid, and its columns.',\n inputSchema: NO_INPUT,\n execute: () => run(() => {\n const api = apiOf(apiRef);\n if (!api) return failure('The grid is not ready');\n return {\n ok: true,\n state: api.getGridAiState(),\n columns: cols.map((c) => ({ field: c.field, headerName: typeof c.headerName === 'string' ? c.headerName : c.field, type: typeof c.type === 'string' ? c.type : 'string' })),\n };\n }),\n }];\n for (const { name, part, description } of SET_TOOLS) {\n const tool = setTool(apiRef, cols, name, part, description);\n if (tool) tools.push(tool);\n }\n tools.push({\n name: 'clear_filters',\n description: 'Remove every filter and the quick-filter words.',\n inputSchema: NO_INPUT,\n execute: () => run(() => {\n const api = apiOf(apiRef);\n if (!api) return failure('The grid is not ready');\n const filterModel = { items: [] };\n api.setFilterModel(filterModel);\n return { ok: true, applied: { filterModel } };\n }),\n });\n if (options.allowRowAccess === true) {\n const maxRows = typeof options.maxRows === 'number' && options.maxRows >= 1 ? Math.min(Math.floor(options.maxRows), MAX_ROWS_LIMIT) : DEFAULT_MAX_ROWS;\n const allowed = Array.isArray(options.fields) ? cols.filter((c) => options.fields?.includes(c.field)) : cols;\n tools.push({\n name: 'get_row_summary',\n description: `Count the rows that pass the current filter and read the first ones (at most ${maxRows}, fields: ${allowed.map((c) => c.field).join(', ')}).`,\n inputSchema: { type: 'object', properties: { limit: { type: 'integer', minimum: 1, maximum: maxRows } }, additionalProperties: false },\n execute: (input) => run(() => {\n const api = apiOf(apiRef);\n if (!api) return failure('The grid is not ready');\n const raw = isPlainObject(input) && hasOwn(input, 'limit') ? input.limit : maxRows;\n const limit = typeof raw === 'number' && raw >= 1 ? Math.min(Math.floor(raw), maxRows) : maxRows;\n const all = api.getAllFilteredRows();\n const list = Array.isArray(all) ? all : [];\n const rows = list.slice(0, limit).map((row) => {\n const out: Record<string, unknown> = {};\n if (!isPlainObject(row)) return out;\n // Through the column's valueGetter, as the grid shows the cell.\n for (const col of allowed) out[col.field] = jsonValue(getCellValue(row as GridRowModel, col.field, col as CellColumn));\n return out;\n });\n return { ok: true, rowCount: list.length, rows };\n }),\n });\n }\n return tools;\n}\n"],"names":["warnedCallbacks","warnCallbackFailure","key","message","error","tryGetCellValue","row","field","colDef","raw","getCellValue","value","DATE_ONLY","toDate","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","isPlainObject","selectedParts","options","include","exclude","part","usableColumns","columns","seen","out","col","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","GRID_AI_VALIDATED_BRAND","brandGridAiValidated","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","coerceValue","MAX_LIST","MAX_ERRORS","fail","path","show","keysOf","column","what","list","items","oneOf","choices","filterValue","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","validate","input","p","FENCE","abortError","readModelText","open","lineEnd","close","lang","errorText","emptyResult","errors","readModelReply","reply","parts","rest","createGridAiPromptHandler","callModel","schemaOptions","partOptions","prompt","context","signal","aborted","_","reject","request","DEFAULT_MAX_ROWS","MAX_ROWS_LIMIT","SET_TOOLS","failure","run","body","apiOf","apiRef","api","jsonValue","setTool","name","description","_version","_schemaVersion","_title","_description","inputSchema","applied","NO_INPUT","createGridAgentTools","tools","tool","maxRows","limit","all","rows"],"mappings":"gFAEA,MAAMA,MAAsB,IAMrB,SAASC,GAAoBC,EAAaC,EAAiBC,EAAsB,CAChF,QAAQ,IAAI,WAAa,cAAgBJ,EAAgB,IAAIE,CAAG,IACpEF,EAAgB,IAAIE,CAAG,EACvB,QAAQ,KAAK,eAAeC,CAAO,IAAKC,CAAK,EACjD,CAqBO,SAASC,GACZC,EACAC,EACAC,EACmC,CACnC,MAAMC,EAAMH,EAAIC,CAAK,EACrB,GAAI,CAACC,GAAQ,YAAa,MAAO,CAAE,MAAOC,CAAA,EAC1C,GAAI,CACA,MAAO,CAAE,MAAOD,EAAO,YAAY,CAAE,IAAAF,EAAK,MAAAC,EAAO,MAAOE,CAAA,CAAK,CAAA,CACjE,OAASL,EAAO,CACZ,MAAO,CAAE,MAAO,OAAW,MAAOA,GAAS,IAAI,MAAM,oBAAoB,CAAA,CAC7E,CACJ,CASO,SAASM,GAAqCJ,EAAQC,EAAeC,EAAwC,CAChH,KAAM,CAAE,MAAAG,EAAO,MAAAP,CAAA,EAAUC,GAAgBC,EAAKC,EAAOC,CAAM,EAC3D,OAAIJ,IAAU,QACVH,GAAoB,eAAeM,CAAK,GAAI,2BAA2BA,CAAK,0CAA2CH,CAAK,EAEzHO,CACX,CA4CA,MAAMC,GAAY,4BAOX,SAASC,GAAOF,EAA6B,CAChD,GAAIA,GAAS,KAAM,OAAO,KAC1B,IAAIG,EACJ,GAAIH,aAAiB,KACjBG,EAAOH,UACA,OAAOA,GAAU,SACxBG,EAAO,IAAI,KAAKH,CAAK,UACd,OAAOA,GAAU,SAAU,CAClC,MAAMI,EAAUJ,EAAM,KAAA,EACtB,GAAII,IAAY,GAAI,OAAO,KAC3B,MAAMC,EAAQJ,GAAU,KAAKG,CAAO,EACpC,GAAIC,EAAO,CACP,MAAMC,EAAO,OAAOD,EAAM,CAAC,CAAC,EACtBE,EAAQ,OAAOF,EAAM,CAAC,CAAC,EAAI,EAC3BG,EAAM,OAAOH,EAAM,CAAC,CAAC,EAG3B,GAFAF,EAAO,IAAI,KAAKG,EAAMC,EAAOC,CAAG,EAE5BL,EAAK,aAAeI,GAASJ,EAAK,QAAA,IAAcK,EAAK,OAAO,IACpE,MACIL,EAAO,IAAI,KAAKC,CAAO,CAE/B,KACI,QAAO,KAEX,OAAO,OAAO,MAAMD,EAAK,QAAA,CAAS,EAAI,KAAOA,CACjD,CAUO,SAASM,GAAkBT,EAAwB,CACtD,MAAMG,EAAOD,GAAOF,CAAK,EACzB,GAAI,CAACG,EAAM,MAAO,GAClB,MAAMO,EAAI,OAAOP,EAAK,YAAA,CAAa,EAAE,SAAS,EAAG,GAAG,EAC9CQ,EAAI,OAAOR,EAAK,SAAA,EAAa,CAAC,EAAE,SAAS,EAAG,GAAG,EAC/CS,EAAI,OAAOT,EAAK,QAAA,CAAS,EAAE,SAAS,EAAG,GAAG,EAChD,MAAO,GAAGO,CAAC,IAAIC,CAAC,IAAIC,CAAC,EACzB,CC0MO,SAASC,GAAoBC,EAAqC,CACvE,OAAQA,EAAA,CACN,IAAK,SACH,MAAO,CAAC,IAAK,KAAM,IAAK,KAAM,IAAK,KAAM,UAAW,YAAY,EAClE,IAAK,OACH,MAAO,CAAC,KAAM,MAAO,QAAS,YAAa,SAAU,aAAc,UAAW,YAAY,EAC5F,IAAK,UACH,MAAO,CAAC,IAAI,EACd,IAAK,eACH,MAAO,CAAC,UAAW,KAAM,KAAK,EAChC,QACE,MAAO,CAAC,WAAY,SAAU,aAAc,WAAY,UAAW,YAAY,CAAA,CAErF,CCzWO,MAAMC,GAAqC,CAAC,MAAO,MAAO,QAAS,MAAO,MAAO,QAAQ,EAEnFC,EAAuC,CAAC,MAAO,MAAO,QAAS,MAAO,KAAK,EAE3EC,GAAkC,CAAC,UAAW,YAAY,EAE1DC,EAAmB,EAEnBC,EAAmC,CAAC,SAAU,OAAQ,WAAY,cAAe,QAAS,kBAAkB,EAG5GC,EAAwC,CACnD,OAAQ,cACR,KAAM,YACN,SAAU,mBACV,YAAa,mBACb,MAAO,aACP,iBAAkB,uBACpB,EAEO,SAASC,EAAOC,EAAa/B,EAAsB,CACxD,OAAO,OAAO,UAAU,eAAe,KAAK+B,EAAK/B,CAAG,CACtD,CAEO,SAASgC,EAAcvB,EAAkD,CAC9E,OAAOA,IAAU,MAAQ,OAAOA,GAAU,UAAY,CAAC,MAAM,QAAQA,CAAK,CAC5E,CAEO,SAASwB,GAAcC,EAAsD,CAClF,MAAMC,EAAU,MAAM,QAAQD,GAAS,OAAO,EAAIA,EAAQ,QAAUN,EAC9DQ,EAAU,MAAM,QAAQF,GAAS,OAAO,EAAIA,EAAQ,QAAU,CAAA,EACpE,OAAON,EAAU,OAAQS,GAASF,EAAQ,SAASE,CAAI,GAAK,CAACD,EAAQ,SAASC,CAAI,CAAC,CACrF,CAMO,SAASC,EAAcC,EAA4D,CACxF,GAAI,CAAC,MAAM,QAAQA,CAAO,QAAU,CAAA,EACpC,MAAMC,MAAW,IACXC,EAAsB,CAAA,EAC5B,UAAWC,KAAOH,EAAsB,CACtC,GAAI,CAACP,EAAcU,CAAG,EAAG,SACzB,MAAMrC,EAAQqC,EAAI,MACd,OAAOrC,GAAU,UAAYA,IAAU,IAAMA,EAAM,WAAW,IAAI,GAAKqC,EAAI,WAAa,IAAQF,EAAK,IAAInC,CAAK,IAClHmC,EAAK,IAAInC,CAAK,EACdoC,EAAI,KAAKC,CAA8B,EACzC,CACA,OAAOD,CACT,CAEO,SAASE,GAAYD,EAAyC,CACnE,OAAOpB,GAAoBoB,EAAI,IAAI,CACrC,CAEO,MAAME,GAAgBF,GAA+BA,EAAI,aAAe,GAClEG,GAAcH,GAA+BA,EAAI,WAAa,GAC9DI,EAAeJ,GAA+BA,EAAI,YAAc,GAChEK,GAAcL,GAA+BA,EAAI,WAAa,GAOpE,SAASM,EAAiBN,EAAmBO,EAAyBzB,GAA2B,CACtG,GAAIkB,EAAI,aAAe,GAAO,MAAO,CAAA,EACrC,MAAMQ,EAAOR,EAAI,OAAS,UAAYA,EAAI,aAAe,GAAOO,EAAMA,EAAI,OAAQE,GAAOA,IAAO,OAAO,EACjGC,EAAUV,EAAI,8BACpB,OAAO,MAAM,QAAQU,CAAO,EAAIF,EAAK,OAAQC,GAAOC,EAAQ,SAASD,CAAE,CAAC,EAAI,CAAC,GAAGD,CAAI,CACtF,CAGO,SAASG,GAAaX,EAAwC,CACnE,MAAMD,EAA2B,CAAA,EACjC,GAAI,CAAC,MAAM,QAAQC,EAAI,YAAY,EAAG,OAAOD,EAC7C,UAAWa,KAAUZ,EAAI,aAAc,CACrC,MAAMjC,EAAiB6C,IAAW,MAAQ,OAAOA,GAAW,SAAWA,EAAO,MAAQA,GACjF,OAAO7C,GAAU,UAAa,OAAOA,GAAU,UAAY,OAAO,SAASA,CAAK,IAAO,CAACgC,EAAI,SAAShC,CAAK,GAAGgC,EAAI,KAAKhC,CAAK,CAClI,CACA,OAAOgC,CACT,CCrEO,MAAMc,GAAuB,uBAE9BC,GAAe,GAOrB,SAASC,EAAMf,EAA2B,CACxC,MAAMgB,EAAS,OAAOhB,EAAI,YAAe,UAAYA,EAAI,aAAe,GAAKA,EAAI,WAAaA,EAAI,MAC5FiB,EAAO,OAAOjB,EAAI,aAAgB,UAAYA,EAAI,cAAgB,GAAK,KAAKA,EAAI,WAAW,IAAM,GACvG,MAAO,GAAGgB,CAAM,GAAGC,CAAI,EACzB,CAEA,SAASC,EAASC,EAAwBC,EAAcC,EAA4B,CAClF,OAAOA,EAAI,aAAe,CAAE,GAAGF,EAAM,YAAaC,GAASD,CAC7D,CAEA,SAASG,GAAWtB,EAAmBqB,EAAqB,CAC1D,MAAI,CAACA,EAAI,UAAY,CAAC,MAAM,QAAQrB,EAAI,UAAU,EAAU,CAAA,EACrDA,EAAI,WACR,OAAQuB,GAAM,OAAOA,GAAM,UAAY,OAAOA,GAAM,WAAc,OAAOA,GAAM,UAAY,OAAO,SAASA,CAAC,CAAE,EAC9G,MAAM,EAAGT,EAAY,CAC1B,CAGA,SAASU,GAAYxB,EAAmBqB,EAA4B,CAClE,IAAIF,EACJ,OAAQnB,EAAI,KAAA,CACV,IAAK,SACHmB,EAAO,CAAE,KAAM,QAAA,EACf,MACF,IAAK,OACHA,EAAO,CAAE,KAAM,SAAU,OAAQ,MAAA,EACjC,MACF,IAAK,UACHA,EAAO,CAAE,KAAM,SAAA,EACf,MACF,IAAK,eAAgB,CACnB,MAAMM,EAASd,GAAaX,CAAG,EACzB0B,EAAwBD,EAAO,OAAS,EAAI,CAAE,KAAMA,CAAA,EAAW,CAAE,KAAM,QAAA,EAC7EN,EAAO,CAAE,MAAO,CAACO,EAAK,CAAE,KAAM,QAAS,MAAOA,CAAA,CAAK,CAAA,EACnD,KACF,CACA,QACEP,EAAO,CAAE,KAAM,QAAA,CAAS,CAE5B,MAAMQ,EAAWL,GAAWtB,EAAKqB,CAAG,EACpC,OAAOM,EAAS,OAAS,EAAI,CAAE,GAAGR,EAAM,SAAAQ,GAAaR,CACvD,CAEA,SAASS,GAAiB5B,EAAmBqB,EAA4B,CACvE,MAAMQ,EAAY5B,GAAYD,CAAG,EAC3B8B,EAAUD,EAAU,OAAQE,GAAO/C,GAAa,SAAS+C,CAAE,CAAC,EAC5DC,EAAOhC,EAAI,OAAS,eAAiB,2BAA6B,GAClEiC,EAAMH,EAAQ,OAAS,EAAI,GAAGA,EAAQ,KAAK,OAAO,CAAC,iBAAiBE,CAAI,GAAKA,EAAK,MAAM,CAAC,EAC/F,OAAOd,EACL,CACE,KAAM,SACN,WAAY,CACV,MAAO,CAAE,MAAOlB,EAAI,KAAA,EACpB,SAAU,CAAE,KAAM6B,CAAA,EAClB,MAAOL,GAAYxB,EAAKqB,CAAG,CAAA,EAE7B,SAAU,CAAC,QAAS,UAAU,EAC9B,qBAAsB,EAAA,EAExBY,EAAM,GAAGlB,EAAMf,CAAG,CAAC,KAAKiC,CAAG,GAAKlB,EAAMf,CAAG,EACzCqB,CAAA,CAEJ,CAEA,SAASa,GAAaC,EAAsBd,EAAUe,EAAiE,CACrH,MAAMC,EAAaF,EAAK,OAAOjC,EAAY,EACrCoC,EAA+C,CAAA,EAC/CC,EAAQ,CAAE,KAAM,CAAC,MAAO,IAAI,CAAA,EAClC,GAAIF,EAAW,OAAS,EAAG,CACzBD,EAAK,WAAa,CAAE,MAAOC,EAAW,IAAKrC,GAAQ4B,GAAiB5B,EAAKqB,CAAG,CAAC,CAAA,EAE7E,QAASmB,EAAQvD,EAAkBuD,GAAS,EAAGA,IAAS,CACtD,MAAMC,EAAQD,IAAUvD,EAAmB,CAAE,KAAM,oBAAA,EAAyB,CAAE,MAAO,CAAC,CAAE,KAAM,oBAAA,EAAwB,CAAE,KAAM,sBAAsBuD,EAAQ,CAAC,EAAA,CAAI,CAAA,EACjKJ,EAAK,cAAcI,CAAK,EAAE,EAAI,CAC5B,KAAM,SACN,WAAY,CAAE,cAAeD,EAAO,MAAO,CAAE,KAAM,QAAS,MAAOE,EAAM,EACzE,SAAU,CAAC,gBAAiB,OAAO,EACnC,qBAAsB,EAAA,CAE1B,CACAH,EAAW,MAAQ,CAAE,KAAM,QAAS,MAAO,CAAE,MAAO,CAAC,CAAE,KAAM,sBAAwB,CAAE,KAAM,sBAAA,CAAwB,EAAE,EACvHA,EAAW,cAAgBC,CAC7B,CACA,OAAAD,EAAW,kBAAoBpB,EAAS,CAAE,KAAM,QAAS,MAAO,CAAE,KAAM,QAAA,GAAc,0CAA2CG,CAAG,EAC7H,CAAE,KAAM,SAAU,WAAAiB,EAAY,qBAAsB,EAAA,CAC7D,CAEA,SAASI,EAAUP,EAAwC,CACzD,MAAO,CAAE,KAAMA,EAAK,IAAKQ,GAAMA,EAAE,KAAK,CAAA,CACxC,CAEA,SAASC,GAAWjD,EAAkBwC,EAAsBd,EAAUe,EAAiE,CACrI,OAAQzC,EAAA,CACN,IAAK,SACH,OAAOuC,GAAaC,EAAMd,EAAKe,CAAI,EACrC,IAAK,OAAQ,CACX,MAAMS,EAAWV,EAAK,OAAOhC,EAAU,EACvC,OAAI0C,EAAS,SAAW,EAAU,KAC3B3B,EACL,CACE,KAAM,QACN,MAAO,CACL,KAAM,SACN,WAAY,CAAE,MAAOwB,EAAUG,CAAQ,EAAG,KAAM,CAAE,KAAM,CAAC,MAAO,MAAM,EAAE,EACxE,SAAU,CAAC,QAAS,MAAM,EAC1B,qBAAsB,EAAA,CACxB,EAEF,yBACAxB,CAAA,CAEJ,CACA,IAAK,WAAY,CACf,MAAMyB,EAAYX,EAAK,OAAO/B,CAAW,EACzC,OAAI0C,EAAU,SAAW,EAAU,KAC5B5B,EAAS,CAAE,KAAM,QAAS,MAAOwB,EAAUI,CAAS,EAAG,YAAa,IAAQ,kBAAmBzB,CAAG,CAC3G,CACA,IAAK,cAAe,CAClB,MAAMiB,EAA+C,CAAA,EACrD,UAAWtC,KAAOmC,EAAM,CACtB,MAAM5B,EAAMD,EAAiBN,CAAG,EAC5BO,EAAI,OAAS,IAAG+B,EAAWtC,EAAI,KAAK,EAAIkB,EAAS,CAAE,KAAMX,CAAA,EAAOQ,EAAMf,CAAG,EAAGqB,CAAG,EACrF,CACA,OAAI,OAAO,KAAKiB,CAAU,EAAE,SAAW,EAAU,KAC1C,CAAE,KAAM,SAAU,WAAAA,EAAY,qBAAsB,EAAA,CAC7D,CACA,IAAK,QAAS,CACZ,MAAMS,EAAOZ,EAAK,OAAO/B,CAAW,EAC9BqB,EAASU,EACZ,IAAKnC,IAAS,CAAE,IAAAA,EAAK,IAAKM,EAAiBN,EAAKjB,CAAS,CAAA,EAAI,EAC7D,OAAQwC,GAAMA,EAAE,IAAI,OAAS,CAAC,EAC9B,IAAI,CAAC,CAAE,IAAAvB,EAAK,IAAAO,CAAA,IAAUW,EAAS,CAC9B,KAAM,SACN,WAAY,CAAE,MAAO,CAAE,MAAOlB,EAAI,KAAA,EAAS,MAAO,CAAE,KAAMO,EAAI,EAC9D,SAAU,CAAC,QAAS,OAAO,EAC3B,qBAAsB,EAAA,EACrBQ,EAAMf,CAAG,EAAGqB,CAAG,CAAC,EACrB,GAAI0B,EAAK,SAAW,GAAKtB,EAAO,SAAW,EAAG,OAAO,KACrD,MAAMuB,EAAW,CAAE,KAAM,QAAS,MAAON,EAAUK,CAAI,EAAG,YAAa,EAAA,EACvE,MAAO,CACL,KAAM,SACN,WAAY,CAAE,UAAWC,EAAU,aAAcA,EAAU,YAAa,CAAE,KAAM,QAAS,MAAO,CAAE,MAAOvB,CAAA,EAAS,EAClH,SAAU,CAAC,YAAa,eAAgB,aAAa,EACrD,qBAAsB,EAAA,CAE1B,CACA,IAAK,mBAAoB,CACvB,MAAMa,EAA+C,CAAA,EACrD,UAAWtC,KAAOmC,EAAK,OAAO9B,EAAU,IAAcL,EAAI,KAAK,EAAIkB,EAAS,CAAE,KAAM,SAAA,EAAaH,EAAMf,CAAG,EAAGqB,CAAG,EAChH,OAAI,OAAO,KAAKiB,CAAU,EAAE,SAAW,EAAU,KAC1CpB,EAAS,CAAE,KAAM,SAAU,WAAAoB,EAAY,qBAAsB,EAAA,EAAS,uBAAwBjB,CAAG,CAC1G,CAAA,CAEJ,CASO,SAAS4B,EAAwCpD,EAAuBL,EAA+B,GAAkB,CAC9H,MAAM6B,EAAW,CAAE,aAAc7B,EAAQ,eAAiB,GAAO,SAAUA,EAAQ,WAAa,EAAA,EAC1F2C,EAAOvC,EAAcC,CAAO,EAC5BuC,EAAyC,CAAA,EACzCE,EAA+C,CAAA,EACrD,UAAW3C,KAAQJ,GAAcC,CAAO,EAAG,CACzC,MAAM2B,EAAOyB,GAAWjD,EAAMwC,EAAMd,EAAKe,CAAI,EACzCjB,IAAMmB,EAAWnD,EAAUQ,CAAI,CAAC,EAAIwB,EAC1C,CACA,MAAM+B,EAAuB,CAC3B,QAAS,+CACT,cAAe,EACf,MAAOrC,GACP,KAAM,SACN,WAAAyB,EACA,qBAAsB,EAAA,EAExB,OAAIjB,EAAI,eACN6B,EAAO,YAAc,yEAEnB,OAAO,KAAKd,CAAI,EAAE,OAAS,MAAU,MAAQA,GAC1Cc,CACT,CChNO,MAAMC,GAAkC,OAAO,IAAI,wBAAwB,EAG3E,SAASC,EAAuCrF,EAAa,CAChE,cAAO,eAAeA,EAAOoF,GAAyB,CAAE,MAAO,GAAM,WAAY,GAAO,EACjFpF,CACX,CCGA,MAAMsF,EAA2B,CAAE,GAAI,EAAA,EACjCC,EAASvF,IAAqC,CAAE,GAAI,GAAM,MAAAA,CAAA,GA6E1DwF,GAAe,iCACfC,GAAqB,yBACrBC,GAAiB,iCAGjBC,GAAiB,eAQhB,SAASC,GAAgBvC,EAA+B,CAC3D,IAAIwC,EAAIxC,EAAK,QAAQsC,GAAgB,EAAE,EACvC,GAAIE,IAAM,GAAI,OAAOxC,EAAK,SAAW,GAAKkC,EAAM,IAAI,EAAID,EACxD,IAAIQ,EAAW,GACXD,EAAE,WAAW,GAAG,GAAKA,EAAE,SAAS,GAAG,IACnCC,EAAW,GACXD,EAAIA,EAAE,MAAM,EAAG,EAAE,IAEjBA,EAAE,WAAW,GAAG,GAAKA,EAAE,WAAW,GAAG,GAAKA,EAAE,WAAW,GAAG,KACtDA,EAAE,CAAC,IAAM,QAAgB,CAACC,GAC9BD,EAAIA,EAAE,MAAM,CAAC,GAEjB,IAAIE,EAAU,GAKd,GAJIF,EAAE,SAAS,GAAG,IACdE,EAAU,GACVF,EAAIA,EAAE,MAAM,EAAG,EAAE,GAEjBH,GAAe,KAAKG,CAAC,EAAGA,EAAIA,EAAE,MAAM,GAAG,EAAE,KAAK,EAAE,UAC3C,CAACL,GAAa,KAAKK,CAAC,GAAK,CAACJ,GAAmB,KAAKI,CAAC,EAAG,OAAOP,EACtE,IAAItF,EAAQ,OAAO6F,CAAC,EACpB,OAAK,OAAO,SAAS7F,CAAK,GACtB+F,IAAS/F,GAAS,KAClB8F,MAAkB,CAAC9F,GAChBuF,EAAMvF,CAAK,GAHkBsF,CAIxC,CAYO,SAASU,GAAc3C,EAAc4C,EAAoC,CAC5E,MAAM7F,EAAUiD,EAAK,KAAA,EACrB,GAAIjD,IAAY,GAAI,OAAOmF,EAAM,IAAI,EACrC,MAAMpF,EAAOD,GAAOE,CAAO,EAC3B,OAAKD,EAMEoF,EAAMpF,CAAI,EANCmF,CAOtB,CAGA,MAAMY,GAAa,IAAI,IAAI,CAAC,OAAQ,MAAO,GAAG,CAAC,EACzCC,GAAc,IAAI,IAAI,CAAC,QAAS,KAAM,GAAG,CAAC,EAazC,SAASC,GAAiB/C,EAAcgD,EAA6C,CACxF,MAAMR,EAAIxC,EAAK,KAAA,EAAO,YAAA,EACtB,OAAIwC,IAAM,GAAWN,EAAM,IAAI,EAG3BW,GAAW,IAAIL,CAAC,EAAUN,EAAM,EAAI,EACpCY,GAAY,IAAIN,CAAC,EAAUN,EAAM,EAAK,EACnCD,CACX,CAQO,SAASgB,GAAsBjD,EAAc5B,EAA8D,CAC9G,MAAMoE,EAAIxC,EAAK,KAAA,EACf,GAAIwC,IAAM,GAAI,OAAON,EAAM,IAAI,EAC/B,GAAI,CAAC9D,GAAWA,EAAQ,SAAW,EAAG,OAAO6D,EAC7C,MAAMiB,EAAQV,EAAE,YAAA,EAChB,UAAWhD,KAAUpB,EAAS,CAC1B,MAAMzB,EAAQ,OAAO6C,GAAW,SAAWA,EAAO,MAAQA,EAC1D,GAAI,OAAO7C,CAAK,EAAE,YAAA,IAAkBuG,EAAO,OAAOhB,EAAMvF,CAAK,CACjE,CACA,UAAW6C,KAAUpB,EACjB,GAAI,OAAOoB,GAAW,UAAYA,EAAO,MAAM,YAAA,IAAkB0D,EAAO,OAAOhB,EAAM1C,EAAO,KAAK,EAErG,OAAOyC,CACX,CClLA,MAAMkB,EAASxG,IAA+B,CAAE,GAAI,GAAM,MAAAA,CAAA,GACpDsF,EAA+B,CAAE,GAAI,GAAO,OAAQ,cAAA,EAEpDmB,EAAiBC,GACrBA,EAAO,GAAKF,EAAGE,EAAO,KAAU,EAAIpB,EAEtC,SAASqB,EAAQtD,EAAwB,CACvC,OAAOA,GAAQ,MAAS,OAAOA,GAAS,UAAYA,EAAK,SAAW,EACtE,CAGO,SAASuD,GAAavD,EAAqC,CAChE,OAAIA,GAAQ,KAAamD,EAAG,EAAE,EAC1B,OAAOnD,GAAS,SAAiBmD,EAAGnD,CAAI,EACxC,OAAOA,GAAS,UAAY,OAAOA,GAAS,UAAkBmD,EAAG,OAAOnD,CAAI,CAAC,EAC1EiC,CACT,CAGO,SAASuB,GAAaxD,EAA4C,CACvE,OAAIsD,EAAQtD,CAAI,EAAUmD,EAAG,IAAI,EAC7B,OAAOnD,GAAS,SAAiB,OAAO,SAASA,CAAI,EAAImD,EAAGnD,CAAI,EAAIiC,EACpE,OAAOjC,GAAS,SAAiBiC,EAC9BmB,EAAWb,GAAgBvC,CAAI,CAAC,CACzC,CAGO,SAASyD,GAAWzD,EAA0C,CACnE,OAAIsD,EAAQtD,CAAI,EAAUmD,EAAG,IAAI,EAC7BnD,aAAgB,KAAa,OAAO,MAAMA,EAAK,SAAS,EAAIiC,EAAUkB,EAAGnD,CAAI,EAC7E,OAAOA,GAAS,SAAiB,OAAO,SAASA,CAAI,EAAImD,EAAG,IAAI,KAAKnD,CAAI,CAAC,EAAIiC,EAC9E,OAAOjC,GAAS,SAAiBiC,EAC9BmB,EAAWT,GAAc3C,CAAI,CAAC,CACvC,CAGO,SAAS0D,GAAc1D,EAA6C,CACzE,OAAIsD,EAAQtD,CAAI,EAAUmD,EAAG,IAAI,EAC7B,OAAOnD,GAAS,UAAkBmD,EAAGnD,CAAI,EACzC,OAAOA,GAAS,UAAY,OAAOA,GAAS,SAAiBiC,EAC1DmB,EAAWL,GAAiB,OAAO/C,CAAI,CAAC,CAAC,CAClD,CAMO,SAAS2D,GAAmB3D,EAAexD,EAA6C,CAC7F,GAAI8G,EAAQtD,CAAI,EAAG,OAAOmD,EAAG,IAAI,EACjC,GAAI,OAAOnD,GAAS,UAAY,OAAOA,GAAS,UAAY,OAAOA,GAAS,UAAW,OAAOiC,EAC9F,MAAM7D,EAAU5B,EAAO,aACvB,MAAI,CAAC4B,GAAWA,EAAQ,SAAW,EAAU+E,EAAGnD,CAAI,EAC7CoD,EAAWH,GAAsB,OAAOjD,CAAI,EAAG5B,CAAO,CAAC,CAChE,CAGO,SAASwF,GAAY5D,EAAexD,EAA6C,CACtF,OAAQA,EAAO,KAAA,CACb,IAAK,SACH,OAAOgH,GAAaxD,CAAI,EAC1B,IAAK,OACH,OAAOyD,GAAWzD,CAAI,EACxB,IAAK,UACH,OAAO0D,GAAc1D,CAAI,EAC3B,IAAK,eACH,OAAO2D,GAAmB3D,EAAMxD,CAAM,EACxC,QACE,OAAO+G,GAAavD,CAAI,CAAA,CAE9B,CCnDA,MAAM6D,EAAW,IAEXC,GAAa,IAUnB,SAASC,EAAK9D,EAAU+D,EAAc7H,EAAuB,CAC3D,OAAI8D,EAAI,OAAO,OAAS6D,IAAY7D,EAAI,OAAO,KAAK,CAAE,KAAA+D,EAAM,QAAA7H,EAAS,EAC9D,IACT,CAGA,SAAS8H,EAAKtH,EAAwB,CACpC,OAAI,OAAOA,GAAU,SAAiB,KAAK,UAAUA,EAAM,MAAM,EAAG,EAAE,CAAC,EAChEA,IAAU,MAAQ,OAAOA,GAAU,SAAW,OAAOA,CAAK,EAAI,MAAM,QAAQA,CAAK,EAAI,SAAW,WACzG,CAEA,SAASuG,GAAMvG,EAA+B,CAC5C,OAAO,OAAOA,GAAU,SAAWA,EAAM,KAAA,EAAO,cAAgB,IAClE,CAGA,SAASuH,EAAOjG,EAAwC,CACtD,OAAO,OAAO,KAAKA,CAAG,EAAE,OAAQ/B,GAAQA,IAAQ,WAAW,CAC7D,CAEA,SAASiI,EAAOlE,EAAUtD,EAAgBqH,EAAc1E,EAAkB8E,EAAmC,CAC3G,MAAMxF,EAAM,OAAOjC,GAAU,SAAWsD,EAAI,QAAQ,IAAItD,CAAK,EAAI,OACjE,OAAKiC,EACEU,EAAQV,CAAG,EAAIA,EAAMmF,EAAK9D,EAAK+D,EAAM,SAASC,EAAKtH,CAAK,CAAC,cAAcyH,CAAI,EAAE,EADnEL,EAAK9D,EAAK+D,EAAM,iBAAiBC,EAAKtH,CAAK,CAAC,EAAE,CAEjE,CAGA,SAAS0H,EAAKpE,EAAUtD,EAAgBqH,EAAyB,CAC/D,GAAIrH,GAAS,KAAM,MAAO,CAAA,EAC1B,MAAM2H,EAAQ,MAAM,QAAQ3H,CAAK,EAAIA,EAAQ,CAACA,CAAK,EACnD,OAAI2H,EAAM,OAAST,GAAUE,EAAK9D,EAAK+D,EAAM,kBAAkBH,CAAQ,mBAAmB,EACnFS,EAAM,MAAM,EAAGT,CAAQ,CAChC,CAGA,SAASU,EAAMtE,EAAUtD,EAAgBqH,EAAcQ,EAA2C,CAChG,MAAMrE,EAAI+C,GAAMvG,CAAK,EACrB,OAAO6H,EAAQ,KAAMjD,GAAMA,EAAE,YAAA,IAAkBpB,CAAC,GAAK4D,EAAK9D,EAAK+D,EAAM,GAAGC,EAAKtH,CAAK,CAAC,kBAAkB6H,EAAQ,KAAK,IAAI,CAAC,EAAE,CAC3H,CAGA,SAASC,EAAYxE,EAAUxD,EAAcmC,EAAmBoF,EAAqC,CACnG,MAAMU,EAASd,GAAYnH,EAAKmC,CAAG,EACnC,GAAI,CAAC8F,EAAO,GAAI,OAAOX,EAAK9D,EAAK+D,EAAM,GAAGC,EAAKxH,CAAG,CAAC,mBAAmBmC,EAAI,MAAQ,QAAQ,QAAQ,EAClG,MAAMuB,EAAIuE,EAAO,iBAAiB,KAAOtH,GAAkBsH,EAAO,KAAK,EAAIA,EAAO,MAClF,OAAOvE,GAAK,MAAQA,IAAM,GAAK4D,EAAK9D,EAAK+D,EAAM,eAAe,EAAI,CAAE,EAAA7D,CAAA,CACtE,CAEA,SAASwE,GAAW1E,EAAUxD,EAA8BuH,EAAqC,CAC/F,MAAMpF,EAAMuF,EAAOlE,EAAKxD,EAAI,MAAO,GAAGuH,CAAI,SAAUlF,GAAc,UAAU,EAC5E,GAAI,CAACF,EAAK,OAAO,KACjB,MAAMgG,EAAWL,EAAMtE,EAAKxD,EAAI,SAAU,GAAGuH,CAAI,YAAanF,GAAYD,CAAG,CAAC,EAC9E,GAAIgG,IAAa,KAAM,OAAO,KAC9B,MAAMC,EAAuB,CAAE,MAAOjG,EAAI,MAAO,SAAAgG,CAAA,EACjD,GAAIhH,GAAa,SAASiH,EAAK,QAAQ,EAAG,OAAOA,EACjD,MAAMC,EAAW9G,EAAOvB,EAAK,OAAO,EAAIA,EAAI,MAAQ,OACpD,GAAIoI,EAAK,WAAa,UAAW,CAC/B,MAAMxE,EAAoB,CAAA,EAK1B,OAJAgE,EAAKpE,EAAK6E,EAAU,GAAGd,CAAI,QAAQ,EAAE,QAAQ,CAACe,EAAOC,IAAM,CACzD,MAAMC,EAAIR,EAAYxE,EAAK8E,EAAOnG,EAAK,GAAGoF,CAAI,UAAUgB,CAAC,GAAG,EACxDC,GAAK,CAAC5E,EAAO,SAAS4E,EAAE,CAAC,GAAG5E,EAAO,KAAK4E,EAAE,CAAC,CACjD,CAAC,EACG5E,EAAO,SAAW,EAAU0D,EAAK9D,EAAK,GAAG+D,CAAI,SAAU,iBAAiB,GAC5Ea,EAAK,MAAQxE,EACNwE,EACT,CACA,GAAI,MAAM,QAAQC,CAAQ,EAAG,OAAOf,EAAK9D,EAAK,GAAG+D,CAAI,SAAU,gCAAgC,EAC/F,MAAMiB,EAAIR,EAAYxE,EAAK6E,EAAUlG,EAAK,GAAGoF,CAAI,QAAQ,EACzD,OAAKiB,GACLJ,EAAK,MAAQI,EAAE,EACRJ,GAFQ,IAGjB,CAEA,SAAS1D,GAAMlB,EAAUxD,EAAcuH,EAAmC,CACxE,OAAOvH,IAAQ,OAAY,KAAQ8H,EAAMtE,EAAKxD,EAAKuH,EAAM,CAAC,MAAO,IAAI,CAAC,CACxE,CAEA,SAASkB,GAAYjF,EAAUxD,EAAcuH,EAAc5C,EAA6B,CACtF,MAAMzC,EAAoB,CAAA,EAC1B,OAAA0F,EAAKpE,EAAKxD,EAAKuH,CAAI,EAAE,QAAQ,CAACe,EAAOC,IAAM,CACzC,MAAMG,EAAK,GAAGnB,CAAI,IAAIgB,CAAC,IACvB,GAAI,CAAC9G,EAAc6G,CAAK,EACtBhB,EAAK9D,EAAKkF,EAAI,0BAA0BlB,EAAKc,CAAK,CAAC,EAAE,UAC5C/G,EAAO+G,EAAO,OAAO,GAAK/G,EAAO+G,EAAO,eAAe,EAAG,CACnE,GAAI3D,GAASvD,EAAkB,CAC7BkG,EAAK9D,EAAKkF,EAAI,uBAAuBtH,CAAgB,SAAS,EAC9D,MACF,CACA,MAAMyG,EAAQY,GAAYjF,EAAK8E,EAAM,MAAO,GAAGI,CAAE,SAAU/D,EAAQ,CAAC,EAChEkD,EAAM,SAAW,EAAGP,EAAK9D,EAAKkF,EAAI,qBAAqB,EACtDxG,EAAI,KAAK,CAAE,cAAewC,GAAMlB,EAAK8E,EAAM,cAAe,GAAGI,CAAE,gBAAgB,GAAK,MAAO,MAAAb,EAAO,CACzG,KAAO,CACL,MAAMO,EAAOF,GAAW1E,EAAK8E,EAAOI,CAAE,EAClCN,GAAMlG,EAAI,KAAKkG,CAAI,CACzB,CACF,CAAC,EACMlG,CACT,CAEA,SAASyG,GAAYnF,EAAUxD,EAA8BuH,EAA+B,CAC1F,MAAMqB,EAAyB,CAAA,EAC3BrH,EAAOvB,EAAK,OAAO,IAAG4I,EAAM,MAAQH,GAAYjF,EAAKxD,EAAI,MAAO,GAAGuH,CAAI,SAAU,CAAC,GACtF,MAAMrD,EAAKQ,GAAMlB,EAAKxD,EAAI,cAAe,GAAGuH,CAAI,gBAAgB,EAChE,OAAIrD,MAAU,cAAgBA,GAC1B3C,EAAOvB,EAAK,mBAAmB,IACjC4I,EAAM,kBAAoB,CAAA,EAC1BhB,EAAKpE,EAAKxD,EAAI,kBAAmB,GAAGuH,CAAI,oBAAoB,EAAE,QAAQ,CAACe,EAAOC,IAAM,CAClF,MAAMM,EAAO,OAAOP,GAAU,UAAY,OAAOA,GAAU,SAAW,OAAOA,CAAK,EAAE,KAAA,EAAS,KACzFO,IAAS,KAAMvB,EAAK9D,EAAK,GAAG+D,CAAI,sBAAsBgB,CAAC,IAAK,sBAAsBf,EAAKc,CAAK,CAAC,EAAE,EAC1FO,IAAS,IAAID,EAAM,mBAAmB,KAAKC,CAAI,CAC1D,CAAC,GAEID,CACT,CAEA,SAASE,GAAUtF,EAAUxD,EAAcuH,EAA8B,CACvE,MAAMrF,EAAsB,CAAA,EAC5B,OAAA0F,EAAKpE,EAAKxD,EAAKuH,CAAI,EAAE,QAAQ,CAACe,EAAOC,IAAM,CACzC,MAAMG,EAAK,GAAGnB,CAAI,IAAIgB,CAAC,IACvB,GAAI,CAAC9G,EAAc6G,CAAK,EAAG,OAAOhB,EAAK9D,EAAKkF,EAAI,iCAAiClB,EAAKc,CAAK,CAAC,EAAE,EAC9F,MAAMnG,EAAMuF,EAAOlE,EAAK8E,EAAM,MAAO,GAAGI,CAAE,SAAUpG,GAAY,QAAQ,EAClEyG,EAAO5G,GAAO2F,EAAMtE,EAAK8E,EAAM,KAAM,GAAGI,CAAE,QAAS,CAAC,MAAO,MAAM,CAAC,EACpE,CAACvG,GAAO,CAAC4G,IACT7G,EAAI,KAAM6D,GAAMA,EAAE,QAAU5D,EAAI,KAAK,EAAGmF,EAAK9D,EAAKkF,EAAI,GAAGlB,EAAKrF,EAAI,KAAK,CAAC,kBAAkB,IACrF,KAAK,CAAE,MAAOA,EAAI,MAAO,KAAA4G,EAA8B,EAClE,CAAC,EACM7G,CACT,CAEA,SAAS8G,EAAUxF,EAAUxD,EAAcuH,EAAc1E,EAAkB8E,EAAwB,CACjG,MAAMzF,EAAgB,CAAA,EACtB,OAAA0F,EAAKpE,EAAKxD,EAAKuH,CAAI,EAAE,QAAQ,CAACe,EAAOC,IAAM,CACzC,MAAMpG,EAAMuF,EAAOlE,EAAK8E,EAAO,GAAGf,CAAI,IAAIgB,CAAC,IAAK1F,EAAS8E,CAAI,EACzDxF,GAAO,CAACD,EAAI,SAASC,EAAI,KAAK,GAAGD,EAAI,KAAKC,EAAI,KAAK,CACzD,CAAC,EACMD,CACT,CAEA,MAAM+G,GAAyB9G,GAAQM,EAAiBN,CAAG,EAAE,OAAS,EAChE+G,GAAqB/G,GAAQM,EAAiBN,EAAKjB,CAAS,EAAE,OAAS,EAE7E,SAASiI,GAAiB3F,EAAUxD,EAA8BuH,EAAoC,CACpG,MAAMqB,EAA8B,CAAA,EACpC,UAAWnJ,KAAOgI,EAAOzH,CAAG,EAAG,CAE7B,GAAIyG,GAAMzG,EAAIP,CAAG,CAAC,IAAM,OAAQ,SAChC,MAAM0C,EAAMuF,EAAOlE,EAAK/D,EAAK,GAAG8H,CAAI,IAAI9H,CAAG,GAAIwJ,GAAc,YAAY,EACnErG,EAAKT,GAAO2F,EAAMtE,EAAKxD,EAAIP,CAAG,EAAG,GAAG8H,CAAI,IAAI9H,CAAG,GAAIgD,EAAiBN,CAAG,CAAC,EAC1EA,GAAOS,IAAIgG,EAAMzG,EAAI,KAAK,EAAIS,EACpC,CACA,OAAOgG,CACT,CAEA,SAASQ,GAAW5F,EAAUxD,EAA8BuH,EAA8B,CACxF,MAAM8B,EAAqC,CAAA,EAC3C,OAAAzB,EAAKpE,EAAKxD,EAAI,YAAa,GAAGuH,CAAI,cAAc,EAAE,QAAQ,CAACe,EAAOC,IAAM,CACtE,MAAMG,EAAK,GAAGnB,CAAI,gBAAgBgB,CAAC,IACnC,GAAI,CAAC9G,EAAc6G,CAAK,EAAG,OAAOhB,EAAK9D,EAAKkF,EAAI,kCAAkClB,EAAKc,CAAK,CAAC,EAAE,EAC/F,MAAMnG,EAAMuF,EAAOlE,EAAK8E,EAAM,MAAO,GAAGI,CAAE,SAAUQ,GAAU,eAAe,EACvEI,EAAQnH,GAAQ2F,EAAMtE,EAAK8E,EAAM,MAAO,GAAGI,CAAE,SAAUjG,EAAiBN,EAAKjB,CAAS,CAAC,EACzFiB,GAAOmH,GAAS,CAACD,EAAY,KAAM3F,GAAMA,EAAE,QAAUvB,EAAI,OAASuB,EAAE,QAAU4F,CAAK,GAAGD,EAAY,KAAK,CAAE,MAAOlH,EAAI,MAAO,MAAAmH,EAAO,CACxI,CAAC,EACM,CACL,UAAWN,EAAUxF,EAAKxD,EAAI,UAAW,GAAGuH,CAAI,aAAchF,EAAa,eAAe,EAC1F,aAAcyG,EAAUxF,EAAKxD,EAAI,aAAc,GAAGuH,CAAI,gBAAiBhF,EAAa,eAAe,EACnG,YAAA8G,CAAA,CAEJ,CAEA,SAASE,GAAgB/F,EAAUxD,EAA8BuH,EAAyC,CACxG,MAAMqB,EAAmC,CAAA,EACzC,UAAWnJ,KAAOgI,EAAOzH,CAAG,EAAG,CAC7B,MAAMmC,EAAMuF,EAAOlE,EAAK/D,EAAK,GAAG8H,CAAI,IAAI9H,CAAG,GAAI+C,GAAY,QAAQ,EAC7DgH,EAAUvC,GAAcjH,EAAIP,CAAG,CAAC,EACjC0C,IACDqH,EAAQ,IAAMA,EAAQ,QAAU,KAAMZ,EAAMzG,EAAI,KAAK,EAAIqH,EAAQ,MAChElC,EAAK9D,EAAK,GAAG+D,CAAI,IAAI9H,CAAG,GAAI,+BAA+B+H,EAAKxH,EAAIP,CAAG,CAAC,CAAC,EAAE,EAClF,CACA,OAAOmJ,CACT,CAEA,SAASa,GAASjG,EAAU1B,EAAkB9B,EAAcuH,EAAcmC,EAA0B,CAC9F5H,IAAS,OAAQ4H,EAAM,UAAYZ,GAAUtF,EAAKxD,EAAKuH,CAAI,EACtDzF,IAAS,WAAY4H,EAAM,iBAAmBV,EAAUxF,EAAKxD,EAAKuH,EAAMhF,EAAa,SAAS,EAC7Fd,EAAczB,CAAG,EAClB8B,IAAS,SAAU4H,EAAM,YAAcf,GAAYnF,EAAKxD,EAAKuH,CAAI,EACjEzF,IAAS,cAAe4H,EAAM,iBAAmBP,GAAiB3F,EAAKxD,EAAKuH,CAAI,EAChFzF,IAAS,QAAS4H,EAAM,WAAaN,GAAW5F,EAAKxD,EAAKuH,CAAI,EAClEmC,EAAM,sBAAwBH,GAAgB/F,EAAKxD,EAAKuH,CAAI,EAJnCD,EAAK9D,EAAK+D,EAAM,2BAA2BC,EAAKxH,CAAG,CAAC,EAAE,CAKtF,CAaO,SAAS2J,EACdC,EACA5H,EACAL,EAAiC,CAAA,EACT,CACxB,MAAMsG,EAAS4B,GAASD,EAAM5H,EAASL,CAAO,EAC9C,OAAA4D,EAAqB0C,EAAO,KAAK,EAC1B1C,EAAqB0C,CAAM,CACpC,CAEA,SAAS4B,GAASD,EAAe5H,EAAkCL,EAAwD,CACzH,MAAM6B,EAAW,CAAE,OAAQ,CAAA,EAAI,QAAS,IAAI,GAAI,EAC1CkG,EAAqB,CAAA,EAC3B,GAAI,CACF,UAAWvH,KAAOJ,EAAcC,CAAO,IAAO,QAAQ,IAAIG,EAAI,MAAOA,CAAG,EACxE,IAAI2H,EAAQF,EACZ,GAAI,OAAOA,GAAS,SAClB,GAAI,CACFE,EAAQ,KAAK,MAAMF,CAAI,CACzB,MAAQ,CACN,OAAAtC,EAAK9D,EAAK,GAAI,gBAAgB,EACvB,CAAE,MAAAkG,EAAO,OAAQlG,EAAI,MAAA,CAC9B,CAEF,GAAI,CAAC/B,EAAcqI,CAAK,EACtB,OAAAxC,EAAK9D,EAAK,GAAI,2BAA2BgE,EAAKsC,CAAK,CAAC,EAAE,EAC/C,CAAE,MAAAJ,EAAO,OAAQlG,EAAI,MAAA,EAE9B,MAAMX,EAAUnB,GAAcC,CAAO,EACrC,UAAWlC,KAAOgI,EAAOqC,CAAK,EAAG,CAC/B,MAAMhI,EAAOT,EAAU,KAAM0I,GAAMzI,EAAUyI,CAAC,IAAMtK,CAAG,EACnDA,IAAQ,gBACNqK,EAAMrK,CAAG,IAAM,GAAG6H,EAAK9D,EAAK/D,EAAK,6BAA6B+H,EAAKsC,EAAMrK,CAAG,CAAC,CAAC,EAAE,EAC1EqC,EACFe,EAAQ,SAASf,CAAI,KACjB0B,EAAK1B,EAAMgI,EAAMrK,CAAG,EAAGA,EAAKiK,CAAK,EADbpC,EAAK9D,EAAK/D,EAAK,kBAAkB,EADjD6H,EAAK9D,EAAK/D,EAAK,cAAc,CAGjD,CACF,MAAQ,CAEN,OAAA6H,EAAK9D,EAAK,GAAI,0BAA0B,EACjC,CAAE,MAAO,CAAA,EAAI,OAAQA,EAAI,MAAA,CAClC,CACA,MAAO,CAAE,MAAAkG,EAAO,OAAQlG,EAAI,MAAA,CAC9B,CC7RA,MAAMwG,EAAQ,MAGP,SAASC,GAAoB,CAClC,OAAO,OAAO,cAAiB,WAC3B,IAAI,aAAa,6BAA8B,YAAY,EAC3D,OAAO,OAAO,IAAI,MAAM,4BAA4B,EAAG,CAAE,KAAM,aAAc,CACnF,CAMO,SAASC,GAAc3G,EAAwD,CACpF,MAAMjD,EAAUiD,EAAK,KAAA,EACrB,GAAIjD,EAAQ,WAAW,GAAG,QAAU,CAAE,KAAMA,EAAS,QAAS,EAAA,EAC9D,MAAM6J,EAAO5G,EAAK,QAAQyG,CAAK,EACzBI,EAAUD,EAAO,EAAI,GAAK5G,EAAK,QAAQ;AAAA,EAAM4G,EAAOH,EAAM,MAAM,EAChEK,EAAQD,EAAU,EAAI,GAAK7G,EAAK,QAAQyG,EAAOI,EAAU,CAAC,EAC1DE,EAAOD,EAAQ,EAAI,GAAK9G,EAAK,MAAM4G,EAAOH,EAAM,OAAQI,CAAO,EAAE,KAAA,EAAO,YAAA,EAC9E,OAAIC,EAAQ,GAAMC,IAAS,IAAMA,IAAS,OAAgB,CAAE,KAAM,KAAM,QAAShK,CAAA,EAC1E,CACL,KAAMiD,EAAK,MAAM6G,EAAU,EAAGC,CAAK,EACnC,QAAS,GAAG9G,EAAK,MAAM,EAAG4G,CAAI,CAAC,GAAG5G,EAAK,MAAM8G,EAAQL,EAAM,MAAM,CAAC,GAAG,KAAA,CAAK,CAE9E,CAEA,SAASO,GAAU5K,EAAwB,CACzC,OAAIA,aAAiB,OAASA,EAAM,QAAgBA,EAAM,QACnD,OAAOA,GAAU,UAAYA,EAAQA,EAAQ,eACtD,CAGA,SAAS6K,GAAYC,EAAiC/K,EAAsC,CAC1F,MAAMuI,EAA6B,CAAE,MAAO1C,EAAqB,CAAA,CAAE,EAAG,OAAAkF,CAAA,EACtE,OAAI/K,MAAgB,QAAUA,GACvB6F,EAAqB0C,CAAM,CACpC,CAGO,SAASyC,GAAeC,EAAgB3I,EAAkC4I,EAA8C,CAC7H,IAAIhB,EAAgBe,EAChBjL,EAAU,GACd,GAAI,OAAOiL,GAAU,SAAU,CAC7B,MAAMpH,EAAO2G,GAAcS,CAAK,EAChC,GAAIpH,EAAK,OAAS,KAAM,OAAOiH,GAAY,GAAIjH,EAAK,SAAW,MAAS,EACxEqG,EAAOrG,EAAK,KACZ7D,EAAU6D,EAAK,OACjB,SAAW9B,EAAckJ,CAAK,GAAKpJ,EAAOoJ,EAAO,SAAS,GAAK,OAAOA,EAAM,SAAY,SAAU,CAEhG,KAAM,CAAE,QAASpH,EAAM,GAAGsH,GAASF,EACnCf,EAAOiB,EACPnL,EAAU6D,CACZ,CACA,MAAM0E,EAA6B0B,EAAoBC,EAAM5H,EAAS4I,CAAK,EAC3E,OAAIlL,MAAgB,QAAUA,GACvBuI,CACT,CAUO,SAAS6C,GACdnJ,EAC+E,CAC/E,KAAM,CAAE,QAAAK,EAAS,UAAA+I,EAAW,MAAAH,EAAO,cAAAI,EAAgB,CAAA,GAAOrJ,EACpDsJ,EAAiC,CAAE,QAASL,GAASI,EAAc,QAAS,QAASA,EAAc,OAAA,EACnG3F,EAASD,EAAgBpD,EAAS,CAAE,GAAGgJ,EAAe,GAAGC,EAAa,EAE5E,MAAO,OAAOC,EAAQC,IAAY,CAChC,MAAMC,EAASD,GAAS,QAAU,IAAI,kBAAkB,OACxD,GAAIC,EAAO,QAAS,MAAMnB,EAAA,EAE1B,MAAMoB,EAAU,IAAI,QAAe,CAACC,EAAGC,IAAW,CAChDH,EAAO,iBAAiB,QAAS,IAAMG,EAAOtB,EAAA,CAAY,EAAG,CAAE,KAAM,GAAM,CAC7E,CAAC,EACDoB,EAAQ,MAAM,IAAA,EAAe,EAC7B,IAAIV,EACJ,GAAI,CACF,MAAMa,EAAU,CACd,OAAQ,OAAON,CAAM,EACrB,OAAA7F,EACA,aAAc8F,GAAS,cAAgB,CAAA,EACvC,QAAS,MAAM,QAAQA,GAAS,OAAO,EAAIA,EAAQ,QAAU,CAAA,CAAC,EAEhER,EAAQ,MAAM,QAAQ,KAAK,CAACI,EAAUS,EAAS,CAAE,OAAAJ,CAAA,CAAQ,EAAGC,CAAO,CAAC,CACtE,OAAS1L,EAAO,CACd,GAAIyL,EAAO,QAAS,MAAMnB,EAAA,EAC1B,OAAOO,GAAY,CAAC,CAAE,KAAM,GAAI,QAAS,0BAA0BD,GAAU5K,CAAK,CAAC,EAAA,CAAI,CAAC,CAC1F,CACA,GAAIyL,EAAO,QAAS,MAAMnB,EAAA,EAC1B,OAAOS,GAAeC,EAAO3I,EAASiJ,CAAW,CACnD,CACF,CCxFA,MAAMQ,GAAmB,GACnBC,GAAiB,IAEjBC,GAAuE,CAC3E,CAAE,KAAM,aAAc,KAAM,SAAU,YAAa,wEAAA,EACnD,CAAE,KAAM,WAAY,KAAM,OAAQ,YAAa,iGAAA,EAC/C,CAAE,KAAM,eAAgB,KAAM,WAAY,YAAa,kFAAA,EACvD,CAAE,KAAM,kBAAmB,KAAM,cAAe,YAAa,qFAAA,EAC7D,CAAE,KAAM,wBAAyB,KAAM,mBAAoB,YAAa,gFAAA,CAC1E,EAIA,SAASC,EAAQlM,EAAiB6H,EAAO,GAAyB,CAChE,MAAO,CAAE,GAAI,GAAO,OAAQ,CAAC,CAAE,KAAAA,EAAM,QAAA7H,CAAA,CAAS,CAAA,CAChD,CAGA,eAAemM,EAAIC,EAA+D,CAChF,GAAI,CACF,OAAOA,EAAA,CACT,OAASnM,EAAO,CACd,OAAOiM,EAAQjM,aAAiB,OAASA,EAAM,QAAUA,EAAM,QAAU,iBAAiB,CAC5F,CACF,CAEA,SAASoM,EAAMC,EAAqC,CAClD,MAAMC,EAAMD,GAAQ,QACpB,OAAOC,GAAO,OAAOA,EAAI,gBAAmB,WAAaA,EAAM,IACjE,CAGA,SAASC,GAAUhM,EAAyB,CAC1C,OAAIA,aAAiB,KAAa,OAAO,MAAMA,EAAM,SAAS,EAAI,KAAOA,EAAM,YAAA,EAC3E,OAAOA,GAAU,SAAiB,OAAO,SAASA,CAAK,EAAIA,EAAQ,KAChE,OAAOA,GAAU,UAAY,OAAOA,GAAU,UAAYA,EAAQ,IAC3E,CAEA,SAASiM,GAAQH,EAAgBhK,EAAkCoK,EAActK,EAAkBuK,EAA2C,CAC5I,MAAM5M,EAAM6B,EAAUQ,CAAI,EACpBuD,EAASD,EAAgBpD,EAAS,CAAE,QAAS,CAACF,CAAI,EAAG,EAC3D,GAAI,CAACP,EAAO8D,EAAO,WAAY5F,CAAG,EAAG,OAAO,KAC5C,KAAM,CAAE,QAAS6M,EAAU,cAAeC,EAAgB,MAAOC,EAAQ,YAAaC,EAAc,GAAGC,CAAA,EAAgBrH,EACvH,MAAO,CACL,KAAA+G,EACA,YAAAC,EACA,YAAa,CAAE,GAAGK,EAAa,SAAU,CAACjN,CAAG,CAAA,EAC7C,QAAUqK,GAAU+B,EAAI,IAAM,CAC5B,MAAMI,EAAMF,EAAMC,CAAM,EACxB,GAAI,CAACC,EAAK,OAAOL,EAAQ,uBAAuB,EAChD,GAAI,CAACnK,EAAcqI,CAAK,GAAK,CAACvI,EAAOuI,EAAOrK,CAAG,EAAG,OAAOmM,EAAQ,4BAA4BnM,CAAG,IAAKA,CAAG,EACxG,KAAM,CAAE,MAAAiK,EAAO,OAAAe,CAAA,EAAWd,EAAoB,CAAE,CAAClK,CAAG,EAAGqK,EAAMrK,CAAG,CAAA,EAAKuC,EAAS,CAAE,QAAS,CAACF,CAAI,EAAG,EAEjG,GAAI2I,EAAO,OAAS,QAAU,CAAE,GAAI,GAAO,OAAAA,CAAA,EAC3C,MAAMkC,EAAuB,CAAA,EAC7B,OAAIjD,EAAM,aAAauC,EAAI,eAAeU,EAAQ,YAAcjD,EAAM,WAAW,EAC7EA,EAAM,WAAWuC,EAAI,aAAaU,EAAQ,UAAYjD,EAAM,SAAS,EACrEA,EAAM,kBAAkBuC,EAAI,oBAAoBU,EAAQ,iBAAmBjD,EAAM,gBAAgB,EACjGA,EAAM,kBAAkBuC,EAAI,oBAAoBU,EAAQ,iBAAmBjD,EAAM,gBAAgB,EACjGA,EAAM,wBACRiD,EAAQ,sBAAwB,CAAE,GAAGV,EAAI,iBAAiB,sBAAuB,GAAGvC,EAAM,qBAAA,EAC1FuC,EAAI,yBAAyBU,EAAQ,qBAAqB,GAErD,CAAE,GAAI,GAAM,QAAAA,CAAA,CACrB,CAAC,CAAA,CAEL,CAEA,MAAMC,EAA6B,CAAE,KAAM,SAAU,WAAY,CAAA,EAAI,qBAAsB,EAAA,EASpF,SAASC,GACdb,EACAhK,EACAL,EAAiC,CAAA,EAChB,CACjB,MAAM2C,EAAOvC,EAAcC,CAAO,EAC5B8K,EAAyB,CAAC,CAC9B,KAAM,iBACN,YAAa,kHACb,YAAaF,EACb,QAAS,IAAMf,EAAI,IAAM,CACvB,MAAMI,EAAMF,EAAMC,CAAM,EACxB,OAAKC,EACE,CACL,GAAI,GACJ,MAAOA,EAAI,eAAA,EACX,QAAS3H,EAAK,IAAKQ,IAAO,CAAE,MAAOA,EAAE,MAAO,WAAY,OAAOA,EAAE,YAAe,SAAWA,EAAE,WAAaA,EAAE,MAAO,KAAM,OAAOA,EAAE,MAAS,SAAWA,EAAE,KAAO,QAAA,EAAW,CAAA,EAJ3J8G,EAAQ,uBAAuB,CAMlD,CAAC,CAAA,CACF,EACD,SAAW,CAAE,KAAAQ,EAAM,KAAAtK,EAAM,YAAAuK,CAAA,IAAiBV,GAAW,CACnD,MAAMoB,EAAOZ,GAAQH,EAAQ1H,EAAM8H,EAAMtK,EAAMuK,CAAW,EACtDU,GAAMD,EAAM,KAAKC,CAAI,CAC3B,CAaA,GAZAD,EAAM,KAAK,CACT,KAAM,gBACN,YAAa,kDACb,YAAaF,EACb,QAAS,IAAMf,EAAI,IAAM,CACvB,MAAMI,EAAMF,EAAMC,CAAM,EACxB,GAAI,CAACC,EAAK,OAAOL,EAAQ,uBAAuB,EAChD,MAAMjD,EAAc,CAAE,MAAO,EAAC,EAC9B,OAAAsD,EAAI,eAAetD,CAAW,EACvB,CAAE,GAAI,GAAM,QAAS,CAAE,YAAAA,EAAY,CAC5C,CAAC,CAAA,CACF,EACGhH,EAAQ,iBAAmB,GAAM,CACnC,MAAMqL,EAAU,OAAOrL,EAAQ,SAAY,UAAYA,EAAQ,SAAW,EAAI,KAAK,IAAI,KAAK,MAAMA,EAAQ,OAAO,EAAG+J,EAAc,EAAID,GAChI5I,EAAU,MAAM,QAAQlB,EAAQ,MAAM,EAAI2C,EAAK,OAAQQ,GAAMnD,EAAQ,QAAQ,SAASmD,EAAE,KAAK,CAAC,EAAIR,EACxGwI,EAAM,KAAK,CACT,KAAM,kBACN,YAAa,gFAAgFE,CAAO,aAAanK,EAAQ,IAAKiC,GAAMA,EAAE,KAAK,EAAE,KAAK,IAAI,CAAC,KACvJ,YAAa,CAAE,KAAM,SAAU,WAAY,CAAE,MAAO,CAAE,KAAM,UAAW,QAAS,EAAG,QAASkI,EAAQ,EAAK,qBAAsB,EAAA,EAC/H,QAAUlD,GAAU+B,EAAI,IAAM,CAC5B,MAAMI,EAAMF,EAAMC,CAAM,EACxB,GAAI,CAACC,EAAK,OAAOL,EAAQ,uBAAuB,EAChD,MAAM5L,EAAMyB,EAAcqI,CAAK,GAAKvI,EAAOuI,EAAO,OAAO,EAAIA,EAAM,MAAQkD,EACrEC,EAAQ,OAAOjN,GAAQ,UAAYA,GAAO,EAAI,KAAK,IAAI,KAAK,MAAMA,CAAG,EAAGgN,CAAO,EAAIA,EACnFE,EAAMjB,EAAI,mBAAA,EACVrE,EAAO,MAAM,QAAQsF,CAAG,EAAIA,EAAM,CAAA,EAClCC,EAAOvF,EAAK,MAAM,EAAGqF,CAAK,EAAE,IAAKpN,GAAQ,CAC7C,MAAMqC,EAA+B,CAAA,EACrC,GAAI,CAACT,EAAc5B,CAAG,EAAG,OAAOqC,EAEhC,UAAWC,KAAOU,EAASX,EAAIC,EAAI,KAAK,EAAI+J,GAAUjM,GAAaJ,EAAqBsC,EAAI,MAAOA,CAAiB,CAAC,EACrH,OAAOD,CACT,CAAC,EACD,MAAO,CAAE,GAAI,GAAM,SAAU0F,EAAK,OAAQ,KAAAuF,CAAA,CAC5C,CAAC,CAAA,CACF,CACH,CACA,OAAOL,CACT"}
|