@keenmate/web-grid 1.1.0 → 1.2.0-rc02

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.
Files changed (47) hide show
  1. package/README.md +20 -15
  2. package/ai/fill-handle.txt +1 -1
  3. package/ai/frozen-columns.txt +2 -2
  4. package/ai/row-locking.txt +1 -1
  5. package/ai/styling-theming.txt +13 -12
  6. package/component-variables.manifest.json +8 -7
  7. package/dist/grid.d.ts +98 -11
  8. package/dist/logger.d.ts +1 -0
  9. package/dist/modules/click-events/index.d.ts +5 -1
  10. package/dist/modules/contextmenu/index.d.ts +10 -1
  11. package/dist/modules/datepicker/datepicker.d.ts +2 -0
  12. package/dist/modules/rendering/tree-render.d.ts +8 -0
  13. package/dist/perf.d.ts +15 -0
  14. package/dist/types.d.ts +15 -4
  15. package/dist/web-component.d.ts +31 -3
  16. package/dist/web-grid.js +3009 -2411
  17. package/dist/web-grid.umd.js +109 -114
  18. package/package.json +9 -3
  19. package/src/css/animations.css +14 -0
  20. package/src/css/{_cells.css → cells.css} +1 -1
  21. package/src/css/controls.css +3 -0
  22. package/src/css/dark-mode.css +67 -0
  23. package/src/css/{_dialogs.css → dialogs.css} +9 -68
  24. package/src/css/{_dirty-indicator.css → dirty-indicator.css} +37 -37
  25. package/src/css/{_dropdown.css → dropdown.css} +1 -1
  26. package/src/css/{_editors.css → editors.css} +14 -8
  27. package/src/css/floating.css +71 -0
  28. package/src/css/{_freeze.css → freeze.css} +5 -5
  29. package/src/css/{_header.css → header.css} +1 -1
  30. package/src/css/main.css +53 -48
  31. package/src/css/{_navigation.css → navigation.css} +10 -5
  32. package/src/css/{_resize.css → resize.css} +1 -1
  33. package/src/css/{_selection.css → selection.css} +1 -1
  34. package/src/css/{_shortcuts.css → shortcuts.css} +4 -4
  35. package/src/css/{_modifiers.css → states.css} +8 -2
  36. package/src/css/tree.css +71 -0
  37. package/src/css/{_variables.css → variables.css} +55 -42
  38. package/src/css/_dark-mode.css +0 -93
  39. package/src/css/_tree.css +0 -73
  40. /package/src/css/{_cell-selection.css → cell-selection.css} +0 -0
  41. /package/src/css/{_fill-handle.css → fill-handle.css} +0 -0
  42. /package/src/css/{_pagination.css → pagination.css} +0 -0
  43. /package/src/css/{_reorder.css → reorder.css} +0 -0
  44. /package/src/css/{_row-locking.css → row-locking.css} +0 -0
  45. /package/src/css/{_table.css → table.css} +0 -0
  46. /package/src/css/{_toolbar.css → toolbar.css} +0 -0
  47. /package/src/css/{_virtual-scroll.css → virtual-scroll.css} +0 -0
package/README.md CHANGED
@@ -2,21 +2,24 @@
2
2
 
3
3
  A feature-rich, framework-agnostic data grid web component built with TypeScript. Sorting, filtering, pagination, inline editing (8 editor types), cell range selection, clipboard support, row toolbar, context menus, frozen columns, column reorder/resize, fill handle, virtual scroll, dark mode, and full CSS variable theming — all in a Shadow DOM encapsulated `<web-grid>` element.
4
4
 
5
- ## What's New in v1.1.0
5
+ ## What's New in v1.2.0-rc02
6
6
 
7
- - **Tree / hierarchy mode**: Render tree-structured data using ltree-style path strings (`"1.2.3"`, `"/a/b/c"`, `"C:\\Win\\Sys"` — separator auto-detected). Mark one column with `isTree: true` to add depth-based indentation and an expand/collapse chevron. Sort is sibling-aware, filter auto-expands ancestors of matches, pagination operates on visible rows. New props: `treePathMember`, `treeLevelMember`, `treeParentMember`, `treeSeparator`, `treeDataSorted`, `expandedPaths`, `defaultExpandDepth`. Methods: `toggleExpandedPath`, `expandAll`, `collapseAll`, `getRowTreeInfo`. Event: `onexpandedpathschange`.
8
- - **Custom chevron icons via `treeChevronCallback`**: Receives `{ expanded, hasChildren, row, level, path }`, returns HTML for the chevron's inner content. Result is cached per `(row, expanded, hasChildren)` so the callback fires at most a handful of times per row. Use it for file/folder icons, status indicators, anything per-row. Static fallbacks: `treeExpandedGlyph` / `treeCollapsedGlyph`.
9
- - **Double-click to expand/collapse**: New `treeDoubleClickBehavior: 'none' | 'toggle'`. When set to `'toggle'`, double-clicking anywhere in the tree column expands/collapses the node works reliably even when the cell DOM is re-rendered between clicks (uses `MouseEvent.detail`, not the unreliable native `dblclick`).
10
- - **No more spurious `onrowchange` events**: Entering edit mode and exiting via arrow keys without typing no longer fires phantom "X X" change events. `commitEdit` now only fires `onrowchange` when the value actually changed (or validation failed).
11
- - **Pathological filler-cell width fix**: Removed `min-width: max-content` from `.wg__table`it was redundant with `table-layout: fixed` and triggered intrinsic-size computation that ballooned the filler to hundreds of thousands of pixels in some browsers (Firefox especially) when cells contained absolutely-positioned editors.
7
+ - **Excel-like commit-on-click-away** new `shouldCommitOnClickAway` property (defaults to `true`). Clicking a different cell while editing now commits the in-progress value through `beforeCommitCallback` / `validateCallback` instead of silently discarding it. Set to `false` to keep the legacy discard behavior. Companion fix: row focus (`.wg__row--focused`) now applies on the first cell click previously it only landed on the second.
8
+ - **CSS surface taxonomy aligned with pure-admin's `--base-*` names** `--wg-surface-1/2/3` renamed to `--wg-main-bg` / `--wg-elevated-bg`, `--wg-surface-floating` `--wg-dropdown-bg` (`--wg-surface-3` merged into `--wg-hover-bg`). Consumer overrides of the old names need to migrate; see the CHANGELOG mapping table.
9
+ - **Dark mode reworked to respect consumer themes** the dark-mode override blocks used to set `--wg-*` to hardcoded literals, silently overriding pure-admin / theme-designer values. They now just flip `color-scheme` and let `light-dark()` in `variables.css` do the work. The consumer's `--base-*` chain stays the source of truth.
10
+ - **OS-aware dark mode via `light-dark()` fallbacks** every hardcoded fallback in `variables.css` is wrapped in `light-dark(<light>, <dark>)`. A consumer who only sets `body { color-scheme: dark }` (no overrides, no framework class) now gets a sensible dark palette automatically.
11
+ - **CSS architecture refactor: `@layer` cascade + canonical file set**`main.css` now declares `@layer variables, component, overrides;` so consumer rules beat library defaults without `!important`. CSS filenames dropped the underscore prefix (pure-CSS doesn't use SASS-partial convention). New canonical Tier-2 files (`controls.css` / `floating.css` / `states.css` / `animations.css`) match the cross-component pattern.
12
+ - **`--base-active-bg` + `--base-inverse-bg`** — two new cross-component theming hooks. `--base-active-bg` distinguishes pressed from hover; `--base-inverse-bg` covers tooltip and future inverse UI surfaces. Both are optional and additive.
13
+ - **Context menu was leaking through stale `--base-*` names** — was referencing `--base-layer-01` / `--base-stroke-default` / `--base-error-color` (all long-removed in pure-admin). Now uses the canonical names; consumers loading a pure-admin theme finally get themed context menus instead of silent fallbacks.
14
+ - **WCAG contrast e2e suite** — 18 assertions check header / body / hover / focused / selected-row / editing-cell contrast against the 3:1 threshold (WCAG AA for non-text UI). Catches theme regressions invisible to visual smoke tests.
12
15
 
13
- ## What's New in v1.0.5
16
+ ## What's New in v1.2.0-rc01
14
17
 
15
- - **Context menus flip at screen edges**: Both cell and header context menus now correctly flip to the opposite side when opened near a viewport edge (they were clipping before). Switched the root menu to `strategy: 'fixed'` + `position: fixed` so Floating UI's `flip`/`shift` run in viewport coordinates.
16
- - **Header submenus are viewport-aware**: Header menu submenus (e.g. Column Visibility) now use Floating UI with `placement: 'right-start'` and `flip` fallbacks, replacing the old CSS-only `left: 100%` positioning. A short hide delay lets the cursor cross the gap between parent item and submenu.
17
- - **Context menu closes on grid-internal scroll**: The menu now subscribes to both the `'window'` and `'container'` scroll sources. Previously, scrolling within the grid didn't close the menu because scroll events aren't composed across shadow DOM.
18
- - **Context menu offset flips with placement**: `contextMenuXOffset`/`contextMenuYOffset` are now applied via Floating UI's `offset` middleware (`mainAxis` / `alignmentAxis`), so the gap between cursor and menu stays correct even when the menu flips to `*-end` or `top-*`.
19
- - **Dropdown selected option readable in dark mode**: The selected option in select/combobox/autocomplete dropdowns no longer renders as pale blue against the dark surface. `--wg-accent-color-light` now falls back to a transparent `color-mix` that blends with the underlying surface in either theme.
18
+ - **Tree mode polish**: The column marked `isTree: true` is now read-only by default (editing it conflicts with the toggle); Tab / Shift-Tab in heterogeneous trees skip rows where every cell is read-only, and the next editable cell auto-opens its editor after a Tab commit; `'expand-all'`, `'collapse-all'`, `'expand-tree'`, `'collapse-tree'` are now usable as predefined context-menu items via string shorthand or `{ type: ... }` object form. Tree chevron is a single rotating glyph (120 ms transition) on an inline-flex layout. CSS class `wg__tree-cell-content` renamed to `wg__tree-cell-body`.
19
+ - **Tree-row expand is ~40× faster**: Memoized the data pipeline (`displayItems` / `paginatedItems`) and added a surgical render path that replaces only inner contents instead of rebuilding the table. Expanding a 700-row tree drops from ~700 ms to ~16 ms. Render timings surface via the new `GRID:PERF` logger category.
20
+ - **Paste API is first-class on `<web-grid>`**: `onpaste`, `onbeforepaste`, `pasteMode`, and `shouldValidateOnPaste` are now reachable directly on the GridElement instead of requiring `el.grid.*`. `onpaste` shadows the native HTMLElement property and delivers a `PasteDetail`, not a raw `ClipboardEvent`. TypeScript consumers should cast on assignment or use `el.grid.onpaste = fn` for full type fidelity; JS consumers have no friction.
21
+ - **Datepicker no longer closes when you click its own chrome**: previously, clicking the chevrons / month/year header / disabled days appeared as "outside the grid" to two internal listeners and silently tore the picker down. Now both listeners recognize our `document.body`-hosted popovers (datepicker + cell/header context menus) as part of the grid's logical scope. Same fix protects the cell and header context menus from the same class of bug.
22
+ - **Datepicker `outputFormat` is honored from the action pipeline**: a column declaring `editorOptions.outputFormat: 'timestamp'` (or `'date'`) now actually emits a number (or `Date`) instead of an ISO string when commit goes through the modern pipeline executor (`editTrigger='always'` and keyboard-driven select).
20
23
 
21
24
  ## Installation
22
25
 
@@ -930,7 +933,7 @@ web-grid {
930
933
  /* Or set base variables for all KeenMate components */
931
934
  :root {
932
935
  --base-accent-color: #10b981;
933
- --base-layer-1: #ffffff;
936
+ --base-main-bg: #ffffff;
934
937
  }
935
938
  ```
936
939
 
@@ -953,8 +956,10 @@ This means:
953
956
  |----------|-------------|
954
957
  | `--wg-accent-color` | Primary accent color |
955
958
  | `--wg-text-color-1` | Primary text color |
956
- | `--wg-surface-1` | Background color |
957
- | `--wg-surface-2` | Alternate row/header background |
959
+ | `--wg-main-bg` | Background color (table body, cells) |
960
+ | `--wg-elevated-bg` | Alternate row/header background |
961
+ | `--wg-hover-bg` | Hover background (rows, buttons) |
962
+ | `--wg-dropdown-bg` | Floating surfaces (toolbar, context menu, dropdowns) |
958
963
  | `--wg-border-color` | Border color |
959
964
 
960
965
  ### Component Variables Manifest
@@ -113,7 +113,7 @@ prevents accidental fills from clicks). During the drag:
113
113
 
114
114
  CSS variables for the fill handle visual:
115
115
  --wg-fill-handle-size Default: 8px
116
- --wg-fill-handle-bg Default: var(--wg-surface-1)
116
+ --wg-fill-handle-bg Default: var(--wg-main-bg)
117
117
  --wg-fill-handle-border-color Default: var(--wg-accent-color)
118
118
  --wg-fill-handle-border-width Default: 2px
119
119
  --wg-fill-range-bg Default: color-mix(accent 15%, transparent)
@@ -74,7 +74,7 @@ color against the opaque frozen column base color instead of transparent:
74
74
 
75
75
  .wg__row--selected > .wg__cell--frozen {
76
76
  background: color-mix(in srgb, var(--wg-accent-color) 15%,
77
- var(--wg-frozen-column-bg, var(--wg-surface-1))) !important;
77
+ var(--wg-frozen-column-bg, var(--wg-main-bg))) !important;
78
78
  }
79
79
 
80
80
 
@@ -105,7 +105,7 @@ The shadow only appears when the wg--scrolled-horizontal class is present
105
105
  on the container (set when scrollLeft > 0).
106
106
 
107
107
  Other frozen column CSS variables:
108
- --wg-frozen-column-bg Default: var(--wg-surface-1)
108
+ --wg-frozen-column-bg Default: var(--wg-main-bg)
109
109
  --wg-frozen-header-bg Default: color-mix(in srgb, accent 8%, header-bg)
110
110
  --wg-frozen-column-border Default: 2px solid var(--wg-border-color)
111
111
  --wg-cell-splitter-color Border color for frozen column separator
@@ -148,7 +148,7 @@ Locked rows receive the following visual treatment:
148
148
  - Lock icon has cursor: help and full opacity (overrides row opacity)
149
149
 
150
150
  CSS variables:
151
- --wg-row-locked-bg Default: var(--base-disabled-bg, var(--wg-surface-2))
151
+ --wg-row-locked-bg Default: var(--base-disabled-bg, var(--wg-elevated-bg))
152
152
  --wg-row-locked-opacity Default: 0.7
153
153
 
154
154
 
@@ -9,7 +9,7 @@ Each --wg-* variable falls back to a --base-* variable, then to a hardcoded defa
9
9
 
10
10
  :host {
11
11
  --wg-accent-color: var(--base-accent-color, #0078d4);
12
- --wg-surface-1: var(--base-main-bg, #ffffff);
12
+ --wg-main-bg: var(--base-main-bg, #ffffff);
13
13
  --wg-font-size-base: calc(var(--base-font-size-sm, 1.4) * var(--wg-rem));
14
14
  }
15
15
 
@@ -23,7 +23,7 @@ Example -- theme all KeenMate components at once:
23
23
 
24
24
  :root {
25
25
  --base-accent-color: #10b981;
26
- --base-layer-1: #ffffff;
26
+ --base-main-bg: #ffffff;
27
27
  --base-font-family: 'Inter', sans-serif;
28
28
  }
29
29
 
@@ -54,16 +54,17 @@ Colors:
54
54
  Falls back to: --base-text-color-1, default: #242424
55
55
  --wg-text-color-2 Secondary text (row numbers, labels)
56
56
  --wg-text-color-3 Muted text (placeholders, empty states)
57
- --wg-surface-1 Main background (table body, cells)
57
+ --wg-main-bg Main background (table body, cells)
58
58
  Falls back to: --base-main-bg, default: #ffffff
59
- --wg-surface-2 Elevated background (headers, striped rows)
59
+ --wg-elevated-bg Elevated background (headers, striped rows)
60
60
  Falls back to: --base-elevated-bg, default: #f5f5f5
61
- --wg-surface-3 Hover background (row hover, button hover)
61
+ --wg-hover-bg Hover background (row hover, button hover)
62
+ Falls back to: --base-hover-bg, default: color-mix derived
62
63
  --wg-border-color All borders (table, cells, header separators)
63
64
  Falls back to: --base-border-color, default: #e0e0e0
64
65
 
65
66
  Header:
66
- --wg-header-bg Header row background (defaults to --wg-surface-2)
67
+ --wg-header-bg Header row background (defaults to --wg-elevated-bg)
67
68
  --wg-header-bg-hover Sortable header on hover
68
69
  --wg-header-color Header text color
69
70
  --wg-header-border Bottom border below header (2px solid)
@@ -71,8 +72,8 @@ Header:
71
72
  --wg-header-font-weight Header text weight (semibold)
72
73
 
73
74
  Rows:
74
- --wg-row-bg-hover Row hover when isHoverable=true (defaults to --wg-surface-3)
75
- --wg-row-bg-even Striped even rows when isStriped=true (defaults to --wg-surface-2)
75
+ --wg-row-bg-hover Row hover when isHoverable=true (defaults to --wg-hover-bg)
76
+ --wg-row-bg-even Striped even rows when isStriped=true (defaults to --wg-elevated-bg)
76
77
  --wg-row-border Border between rows
77
78
 
78
79
  Typography:
@@ -104,15 +105,15 @@ Dark mode activates automatically. No configuration needed. Detection methods:
104
105
 
105
106
  All four methods apply the same dark palette overrides:
106
107
 
107
- --wg-surface-1: #1f1f1f
108
- --wg-surface-2: #2b2b2b
109
- --wg-surface-3: #333333
108
+ --wg-main-bg: #1f1f1f
109
+ --wg-elevated-bg: #2b2b2b
110
+ --wg-hover-bg: #3a3a3a
110
111
  --wg-text-color-1: #e0e0e0
111
112
  --wg-text-color-2: #c0c0c0
112
113
  --wg-text-color-3: #a0a0a0
113
114
  --wg-border-color: #3d3d3d
114
115
  --wg-input-bg: #1f1f1f
115
- --wg-hover-bg: #3a3a3a
116
+ --wg-active-bg: #4a4a4a
116
117
  --wg-danger-color: #f87c86
117
118
  --wg-danger-bg-light: #442726
118
119
  --wg-dirty-indicator-color: #ffa940
@@ -13,10 +13,12 @@
13
13
  { "name": "base-text-color-on-accent", "required": false, "usage": "Text color on accent-colored backgrounds" },
14
14
  { "name": "base-text-inverted", "required": false, "usage": "Text on dark backgrounds (selected row numbers)" },
15
15
  { "name": "base-main-bg", "required": true, "usage": "Table body background, cell backgrounds, input fields" },
16
- { "name": "base-elevated-bg", "required": true, "usage": "Header row, striped even rows, pagination bar" },
17
- { "name": "base-hover-bg", "required": false, "usage": "Row hover, sorted column header, button hover states" },
16
+ { "name": "base-elevated-bg", "required": true, "usage": "Header row, striped even rows, pagination bar; also secondary fallback for base-dropdown-bg" },
17
+ { "name": "base-hover-bg", "required": false, "usage": "Row hover, sorted column header, button hover background (primary lookup; falls back to text/main color-mix)" },
18
+ { "name": "base-active-bg", "required": false, "usage": "Pressed/active button background, selected option background (primary lookup; falls back to text/main color-mix)" },
18
19
  { "name": "base-disabled-bg", "required": false, "usage": "Read-only and disabled cell backgrounds, locked row backgrounds" },
19
- { "name": "base-dropdown-bg", "required": false, "usage": "Floating toolbar, context menu, dropdown panels" },
20
+ { "name": "base-dropdown-bg", "required": false, "usage": "Floating toolbar, context menu, dropdown panels (primary lookup; chains through base-elevated-bg)" },
21
+ { "name": "base-inverse-bg", "required": false, "usage": "Secondary fallback for tooltip background (inverse-colored surface — dark in light mode, light in dark mode)" },
20
22
  { "name": "base-border-color", "required": true, "usage": "Table border, cell borders, header separators" },
21
23
  { "name": "base-input-bg", "required": false, "usage": "Filter input fields, editor text inputs" },
22
24
  { "name": "base-input-color", "required": false, "usage": "Text color in filter and editor inputs" },
@@ -54,10 +56,9 @@
54
56
  { "name": "wg-text-color-3", "category": "text", "usage": "Placeholder text, disabled text, empty state message, date trigger" },
55
57
  { "name": "wg-text-on-accent", "category": "text", "usage": "Text on accent backgrounds (Go button, active pagination)" },
56
58
 
57
- { "name": "wg-surface-1", "category": "surface", "usage": "Table body, cell backgrounds, filter input background" },
58
- { "name": "wg-surface-2", "category": "surface", "usage": "Header row, striped even rows, read-only cells, shortcut key badges" },
59
- { "name": "wg-surface-3", "category": "surface", "usage": "Row hover background, sorted header, pagination button hover" },
60
- { "name": "wg-surface-floating", "category": "surface", "usage": "Floating toolbar background, context menu, dropdown panels" },
59
+ { "name": "wg-main-bg", "category": "surface", "usage": "Table body, cell backgrounds, filter input background" },
60
+ { "name": "wg-elevated-bg", "category": "surface", "usage": "Header row, striped even rows, read-only cells, shortcut key badges" },
61
+ { "name": "wg-dropdown-bg", "category": "surface", "usage": "Floating toolbar background, context menu, dropdown panels" },
61
62
 
62
63
  { "name": "wg-border-color", "category": "border", "usage": "Table border, cell borders, header bottom, toolbar dividers" },
63
64
  { "name": "wg-border-color-hover", "category": "border", "usage": "Pagination button border on hover" },
package/dist/grid.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { Column, CellValidationState, RowToolbarConfig, ContextMenuItem, RowShortcut, RowChangeDetail, ToolbarClickDetail, RowActionClickDetail, ContextMenuContext, HeaderMenuConfig, HeaderMenuContext, EditTrigger, EditStartSelection, EditingCell, FocusedCell, SortMode, SortState, DataRequestDetail, DataRequestTrigger, BeforeCommitResult, GridMode, ToggleVisibility, PaginationLabelsCallback, SummaryContentCallback, ValidationTooltipContext, ToolbarPosition, GridLabels, RowLockInfo, RowLockingOptions, RowLockChangeDetail, ColumnWidthState, ColumnResizeDetail, ColumnOrderState, ColumnReorderDetail, FillDragDetail, FillDirection, RangeShortcut, CellSelectionMode, CellRange, CellSelectionChangeDetail, RowFocusDetail, PasteMode, BeforePasteDetail, PasteDetail, CreateRowCallback, NewRowPosition, TreeExpandedChangeDetail, TreeChevronCallback, TreeDoubleClickBehavior } from './types.js';
1
+ import type { Column, CellValidationState, RowToolbarConfig, ContextMenuConfig, RowShortcut, RowChangeDetail, ToolbarClickDetail, RowActionClickDetail, ContextMenuContext, HeaderMenuConfig, HeaderMenuContext, EditTrigger, EditStartSelection, EditingCell, FocusedCell, SortMode, SortState, DataRequestDetail, DataRequestTrigger, BeforeCommitResult, GridMode, ToggleVisibility, PaginationLabelsCallback, SummaryContentCallback, ValidationTooltipContext, ToolbarPosition, GridLabels, RowLockInfo, RowLockingOptions, RowLockChangeDetail, ColumnWidthState, ColumnResizeDetail, ColumnOrderState, ColumnReorderDetail, FillDragDetail, FillDirection, RangeShortcut, CellSelectionMode, CellRange, CellSelectionChangeDetail, RowFocusDetail, PasteMode, BeforePasteDetail, PasteDetail, CreateRowCallback, NewRowPosition, TreeExpandedChangeDetail, TreeChevronCallback, TreeDoubleClickBehavior } from './types.js';
2
2
  type TreeNode = {
3
3
  path: string;
4
4
  parent: string | null;
@@ -29,8 +29,10 @@ export declare class WebGrid<T = unknown> {
29
29
  protected _isStriped: boolean;
30
30
  protected _isHoverable: boolean;
31
31
  protected _isEditable: boolean;
32
+ protected _isRowEditable: boolean | ((row: T) => boolean) | undefined;
32
33
  protected _editTrigger: EditTrigger;
33
34
  protected _editStartSelection: EditStartSelection;
35
+ protected _shouldCommitOnClickAway: boolean;
34
36
  protected _mode: GridMode;
35
37
  protected _dropdownToggleVisibility: ToggleVisibility;
36
38
  protected _shouldShowDropdownOnFocus: boolean;
@@ -53,7 +55,7 @@ export declare class WebGrid<T = unknown> {
53
55
  protected _cellToolbarOffset: number | string;
54
56
  protected _toolbarBtnMinWidth: string | undefined;
55
57
  protected _inlineActionsTitle: string;
56
- protected _contextMenu: ContextMenuItem<T>[] | undefined;
58
+ protected _contextMenu: ContextMenuConfig<T>[] | undefined;
57
59
  protected _contextMenuXOffset: number;
58
60
  protected _contextMenuYOffset: number;
59
61
  protected _headerContextMenu: HeaderMenuConfig<T>[] | undefined;
@@ -105,6 +107,8 @@ export declare class WebGrid<T = unknown> {
105
107
  }) => void) | undefined;
106
108
  protected _sort: SortState[];
107
109
  protected _filters: Record<string, string>;
110
+ protected _onfilterchange: ((filters: Record<string, string>) => void) | undefined;
111
+ protected _columnMinWidth: string | undefined;
108
112
  protected _currentPage: number;
109
113
  protected _totalItems: number | null;
110
114
  protected _showPagination: boolean | 'auto';
@@ -170,6 +174,8 @@ export declare class WebGrid<T = unknown> {
170
174
  protected _treePathMember: string | null;
171
175
  protected _treeLevelMember: string | null;
172
176
  protected _treeParentMember: string | null;
177
+ protected _idMember: string | null;
178
+ protected _hasWarnedAboutMissingRowId: boolean;
173
179
  protected _treeSeparator: string | null;
174
180
  protected _treeDataSorted: boolean;
175
181
  protected _expandedPaths: Set<string>;
@@ -182,6 +188,8 @@ export declare class WebGrid<T = unknown> {
182
188
  protected _treeCollapsedGlyph: string;
183
189
  protected _treeChevronCallback: TreeChevronCallback<T> | undefined;
184
190
  protected _treeChevronCache: WeakMap<object, Map<string, string>>;
191
+ protected _paginatedItemsCache: T[] | null;
192
+ protected _displayItemsCache: T[] | null;
185
193
  get items(): T[];
186
194
  set items(value: T[]);
187
195
  get columns(): Column<T>[];
@@ -190,6 +198,10 @@ export declare class WebGrid<T = unknown> {
190
198
  set sortMode(value: SortMode);
191
199
  get isFilterable(): boolean;
192
200
  set isFilterable(value: boolean);
201
+ get onfilterchange(): ((filters: Record<string, string>) => void) | undefined;
202
+ set onfilterchange(value: ((filters: Record<string, string>) => void) | undefined);
203
+ get columnMinWidth(): string | undefined;
204
+ set columnMinWidth(value: string | undefined);
193
205
  get isPageable(): boolean;
194
206
  set isPageable(value: boolean);
195
207
  get pageSize(): number;
@@ -204,10 +216,14 @@ export declare class WebGrid<T = unknown> {
204
216
  set isHoverable(value: boolean);
205
217
  get isEditable(): boolean;
206
218
  set isEditable(value: boolean);
219
+ get isRowEditable(): boolean | ((row: T) => boolean) | undefined;
220
+ set isRowEditable(value: boolean | ((row: T) => boolean) | undefined);
207
221
  get editTrigger(): EditTrigger;
208
222
  set editTrigger(value: EditTrigger);
209
223
  get editStartSelection(): EditStartSelection;
210
224
  set editStartSelection(value: EditStartSelection);
225
+ get shouldCommitOnClickAway(): boolean;
226
+ set shouldCommitOnClickAway(value: boolean);
211
227
  get mode(): GridMode;
212
228
  set mode(value: GridMode);
213
229
  get dropdownToggleVisibility(): ToggleVisibility;
@@ -285,8 +301,8 @@ export declare class WebGrid<T = unknown> {
285
301
  set toolbarBtnMinWidth(value: string | undefined);
286
302
  get inlineActionsTitle(): string;
287
303
  set inlineActionsTitle(value: string);
288
- get contextMenu(): ContextMenuItem<T>[] | undefined;
289
- set contextMenu(value: ContextMenuItem<T>[] | undefined);
304
+ get contextMenu(): ContextMenuConfig<T>[] | undefined;
305
+ set contextMenu(value: ContextMenuConfig<T>[] | undefined);
290
306
  get contextMenuXOffset(): number;
291
307
  set contextMenuXOffset(value: number);
292
308
  get contextMenuYOffset(): number;
@@ -377,6 +393,7 @@ export declare class WebGrid<T = unknown> {
377
393
  get ontoolbarclick(): ((detail: ToolbarClickDetail<T>) => void) | undefined;
378
394
  set ontoolbarclick(value: ((detail: ToolbarClickDetail<T>) => void) | undefined);
379
395
  set onrowaction(value: ((detail: RowActionClickDetail<T>) => void) | undefined);
396
+ get oncontextmenuopen(): ((context: ContextMenuContext<T>) => void) | undefined;
380
397
  set oncontextmenuopen(value: ((context: ContextMenuContext<T>) => void) | undefined);
381
398
  get onheadercontextmenuopen(): ((context: HeaderMenuContext<T>) => void) | undefined;
382
399
  set onheadercontextmenuopen(value: ((context: HeaderMenuContext<T>) => void) | undefined);
@@ -467,6 +484,17 @@ export declare class WebGrid<T = unknown> {
467
484
  set newRowIndicator(value: string);
468
485
  get createEmptyRowCallback(): (() => T | Promise<T>) | undefined;
469
486
  set createEmptyRowCallback(value: (() => T | Promise<T>) | undefined);
487
+ get idMember(): string | null;
488
+ set idMember(value: string | null);
489
+ /**
490
+ * Return a stable string key for a row. Coalesce order: idMember value → treePathMember
491
+ * value → displayed-index string fallback. Used to key drafts and invalid-cell markers
492
+ * so they don't drift when the displayed order changes (sort, filter, page, tree toggle).
493
+ *
494
+ * Distinct from `getRowId` which is the row-locking identifier (any type).
495
+ */
496
+ getStableRowKey(item: T, displayedIndex: number): string;
497
+ private maybeWarnAboutRowIdentity;
470
498
  get treePathMember(): string | null;
471
499
  set treePathMember(value: string | null);
472
500
  get treeLevelMember(): string | null;
@@ -488,6 +516,22 @@ export declare class WebGrid<T = unknown> {
488
516
  toggleExpandedPath(path: string): void;
489
517
  expandAll(): void;
490
518
  collapseAll(): void;
519
+ /**
520
+ * Expand a path's entire subtree (the path itself + all descendants).
521
+ * Used by the per-row "expand all" context-menu action — file-explorer convention
522
+ * is "expand everything *under here*", not the entire dataset.
523
+ */
524
+ expandSubtree(path: string): void;
525
+ /**
526
+ * Collapse a path's entire subtree (the path itself + all descendants).
527
+ */
528
+ collapseSubtree(path: string): void;
529
+ /**
530
+ * Walk up the path's ancestor chain looking for the nearest expanded ancestor.
531
+ * Used by Ctrl+ArrowLeft: when the focused row has no expanded children to collapse,
532
+ * step up to the closest ancestor that is expanded so the user gets meaningful action.
533
+ */
534
+ findNearestExpandedAncestor(path: string): string | null;
491
535
  /** Returns tree info for a row (used by rendering) */
492
536
  getRowTreeInfo(item: T): {
493
537
  path: string;
@@ -575,6 +619,15 @@ export declare class WebGrid<T = unknown> {
575
619
  copyCellSelectionToClipboard(): Promise<boolean>;
576
620
  get isNavigateMode(): boolean;
577
621
  get filteredItems(): T[];
622
+ /**
623
+ * Returns the set of fields whose current filter input is "incomplete" (column.filter
624
+ * returned null when probed against the first row). Renderers can use this to mark the
625
+ * input visually (red border) without excluding all rows.
626
+ */
627
+ getInvalidFilterFields(): Set<string>;
628
+ get filters(): Record<string, string>;
629
+ set filters(value: Record<string, string>);
630
+ setFilter(field: string, value: string): void;
578
631
  get sortedItems(): T[];
579
632
  get paginatedItems(): T[];
580
633
  get totalPages(): number;
@@ -630,6 +683,12 @@ export declare class WebGrid<T = unknown> {
630
683
  */
631
684
  protected checkToolbarConflicts(): void;
632
685
  protected requestUpdate(): void;
686
+ /**
687
+ * Drop memoized pipeline output. The web-component's requestUpdate override
688
+ * calls this on every state mutation, so callers don't need to invoke it
689
+ * directly — but it's safe to call any time the cache might be stale.
690
+ */
691
+ invalidateDisplayCache(): void;
633
692
  getRowDraft(rowIndex: number): T | undefined;
634
693
  hasRowDraft(rowIndex: number): boolean;
635
694
  /**
@@ -650,6 +709,8 @@ export declare class WebGrid<T = unknown> {
650
709
  discardAllDrafts(): void;
651
710
  getCellRawValue(item: T, rowIndex: number, field: string): unknown;
652
711
  getCellValue(item: T, column: Column<T>, rowIndex?: number): string;
712
+ /** Compute the stable rowKey for a displayed-row index, or null if no row at that index. */
713
+ private rowKeyForIndex;
653
714
  isCellInvalid(rowIndex: number, field: string): boolean;
654
715
  getCellValidationError(rowIndex: number, field: string): string | null;
655
716
  addInvalidCell(rowIndex: number, field: string, error: string): void;
@@ -696,16 +757,42 @@ export declare class WebGrid<T = unknown> {
696
757
  */
697
758
  updateDraftValue(rowIndex: number, field: string, newValue: unknown): void;
698
759
  /**
699
- * Check if a column's cells can be edited
700
- */
701
- isCellEditable(column: Column<T>): boolean;
702
- /**
703
- * Get all editable columns with their indices
704
- */
705
- getEditableColumns(): {
760
+ * Check if a column's cells can be edited.
761
+ * `row` is required to evaluate function-form predicates; pass it for per-row checks,
762
+ * omit it for static column-level checks (e.g. building toolbar UI). When omitted and a
763
+ * predicate exists, conservatively returns true so the column isn't hidden.
764
+ *
765
+ * Resolution order:
766
+ * 0. tree column in tree mode → not editable (the tree column displays hierarchy, not data)
767
+ * 1. column.isEditable === false → not editable (hard veto)
768
+ * 2. column.isEditable === function with row → must return true to continue
769
+ * 3. grid.isRowEditable with row → must pass to continue
770
+ * 4. column.isEditable === true → editable (overrides grid-level)
771
+ * 5. else → defer to grid-level isEditable
772
+ */
773
+ isCellEditable(column: Column<T>, row?: T): boolean;
774
+ /**
775
+ * Get all editable columns with their indices.
776
+ * Pass `row` to evaluate per-row predicates (function-form `isEditable`, `isRowEditable`).
777
+ * Without `row`, returns columns that are *potentially* editable (any row could pass).
778
+ */
779
+ getEditableColumns(row?: T): {
706
780
  index: number;
707
781
  column: Column<T>;
708
782
  }[];
783
+ /**
784
+ * Walk rows in `direction` looking for the next row that has at least one editable
785
+ * column at or beyond `fromColEditableIndex` (or beyond the last when going forward
786
+ * past the row's editable list). Used by Tab traversal in heterogeneous trees so the
787
+ * cursor skips read-only rows (e.g. team rows) entirely instead of landing on a cell
788
+ * with no tabindex and silently losing focus.
789
+ *
790
+ * Returns null if no editable cell exists in the requested direction.
791
+ */
792
+ findNextEditableCell(fromRowIndex: number, fromColEditableIndex: number, direction: 'forward' | 'backward'): {
793
+ rowIndex: number;
794
+ colIndex: number;
795
+ } | null;
709
796
  /**
710
797
  * Check if a specific cell is currently focused
711
798
  */
package/dist/logger.d.ts CHANGED
@@ -28,6 +28,7 @@ export declare const initLogger: any;
28
28
  export declare const dataLogger: any;
29
29
  export declare const uiLogger: any;
30
30
  export declare const interactionLogger: any;
31
+ export declare const perfLogger: any;
31
32
  export default log;
32
33
  /**
33
34
  * List of all logging categories for introspection
@@ -22,8 +22,12 @@ export interface ClickEventManager {
22
22
  * Initialize click listeners
23
23
  * @param container - The grid container element (.wg)
24
24
  * @param hostElement - The custom element host (<web-grid>) for outside detection
25
+ * @param isPathInOwnedScope - Optional predicate; when it returns true for a
26
+ * click's composed path, the click is treated as inside the grid (used for
27
+ * document.body-hosted popovers like the datepicker and context menus
28
+ * that need to extend the grid's "inside" scope beyond the host element).
25
29
  */
26
- init(container: HTMLElement, hostElement: HTMLElement): void;
30
+ init(container: HTMLElement, hostElement: HTMLElement, isPathInOwnedScope?: (path: EventTarget[]) => boolean): void;
27
31
  /**
28
32
  * Remove all listeners and subscriptions (called in disconnectedCallback)
29
33
  */
@@ -1,5 +1,14 @@
1
- import type { ContextMenuItem, ContextMenuContext, HeaderMenuConfig, HeaderMenuItem, HeaderMenuContext } from '../../types.js';
1
+ import type { ContextMenuItem, ContextMenuConfig, ContextMenuContext, HeaderMenuConfig, HeaderMenuItem, HeaderMenuContext } from '../../types.js';
2
2
  import type { GridContext } from '../types.js';
3
+ /**
4
+ * Normalize a ContextMenuConfig list (which may contain string shorthand and/or items
5
+ * with `type` set) into a flat list of fully-defined ContextMenuItem objects.
6
+ * Predefined types get default labels, icons, visibility predicates, and onclick handlers.
7
+ *
8
+ * The grid is needed at click time for `expand-all` etc. to call `expandSubtree(path)`,
9
+ * so we close over it rather than passing it through later.
10
+ */
11
+ export declare function normalizeContextMenu<T>(config: ContextMenuConfig<T>[], grid: GridContext<T>['grid']): ContextMenuItem<T>[];
3
12
  /**
4
13
  * Open context menu at position
5
14
  */
@@ -11,6 +11,8 @@ export declare class DatePicker {
11
11
  private anchor;
12
12
  private input;
13
13
  private cleanupAutoUpdate;
14
+ /** The picker's outer container (mounted in document.body), or null when closed. */
15
+ getElement(): HTMLElement | null;
14
16
  private previousInputValue;
15
17
  private boundHandleClickOutside;
16
18
  private boundHandleKeyDown;
@@ -4,6 +4,14 @@ import type { GridContext } from '../types.js';
4
4
  * Wrap cell content with tree indent + chevron when column is the tree column.
5
5
  * Returns the original innerHtml unchanged when tree mode is off or column isn't isTree.
6
6
  *
7
+ * Layout: inline-flex wrapper with `padding-inline-start: level * --wg-tree-indent`.
8
+ * Inside the wrapper sits either a chevron <button> (branch) or an empty
9
+ * <span class="wg__tree-leaf-spacer"> (leaf, keeps alignment with branches).
10
+ *
11
+ * Chevron rotation: when treeExpandedGlyph === treeCollapsedGlyph (the default
12
+ * setup) we add `.wg__tree-chevron--expanded` and CSS rotates the glyph 90°.
13
+ * When the glyphs differ, the glyph is swapped on state change instead.
14
+ *
7
15
  * Used by both the full-table render (table.ts) and the surgical single-cell
8
16
  * render (cell.ts), so both paths keep the chevron after focus/edit transitions.
9
17
  */
package/dist/perf.d.ts ADDED
@@ -0,0 +1,15 @@
1
+ export declare const gridPerf: {
2
+ counters: {
3
+ displayItems: number;
4
+ displayItemsMs: number;
5
+ paginatedItems: number;
6
+ paginatedItemsMs: number;
7
+ treeSorted: number;
8
+ treeSortedMs: number;
9
+ treeVisible: number;
10
+ treeVisibleMs: number;
11
+ cellDisplayItemsLookup: number;
12
+ };
13
+ reset(): void;
14
+ summary(): string;
15
+ };
package/dist/types.d.ts CHANGED
@@ -75,7 +75,7 @@ export type CustomEditorContext<T> = {
75
75
  cancel: () => void;
76
76
  };
77
77
  export type CellValidationState = {
78
- rowIndex: number;
78
+ rowKey: string;
79
79
  field: string;
80
80
  error: string;
81
81
  };
@@ -104,6 +104,8 @@ export type Column<T> = {
104
104
  maxWidth?: string;
105
105
  textOverflow?: 'wrap' | 'ellipsis';
106
106
  maxLines?: number;
107
+ nowrap?: boolean;
108
+ filter?: (filterValue: string, row: T) => boolean | null;
107
109
  horizontalAlign?: "left" | "center" | "right" | "justify";
108
110
  verticalAlign?: "top" | "middle" | "bottom";
109
111
  headerHorizontalAlign?: "left" | "center" | "right" | "justify";
@@ -113,7 +115,7 @@ export type Column<T> = {
113
115
  formatCallback?: (value: unknown, row: T) => string;
114
116
  templateCallback?: (row: T) => string;
115
117
  renderCallback?: CellRenderCallback<T>;
116
- isEditable?: boolean;
118
+ isEditable?: boolean | ((row: T) => boolean);
117
119
  editor?: EditorType;
118
120
  editTrigger?: EditTrigger;
119
121
  dropdownToggleVisibility?: ToggleVisibility;
@@ -214,23 +216,27 @@ export type ContextMenuContext<T> = {
214
216
  column: Column<T>;
215
217
  cellValue: unknown;
216
218
  };
219
+ export type PredefinedContextMenuItemType = 'expand-all' | 'collapse-all' | 'expand-tree' | 'collapse-tree';
217
220
  export type ContextMenuItem<T> = {
218
221
  id: string;
219
- label: string | ((context: ContextMenuContext<T>) => string);
222
+ label?: string | ((context: ContextMenuContext<T>) => string);
220
223
  icon?: string | ((context: ContextMenuContext<T>) => string);
221
224
  shortcut?: string;
222
225
  disabled?: boolean | ((context: ContextMenuContext<T>) => boolean);
223
226
  visible?: boolean | ((context: ContextMenuContext<T>) => boolean);
224
227
  danger?: boolean;
225
228
  dividerBefore?: boolean;
229
+ type?: PredefinedContextMenuItemType;
226
230
  onclick?: (context: ContextMenuContext<T>) => void | Promise<void>;
227
231
  };
232
+ export type ContextMenuConfig<T> = PredefinedContextMenuItemType | ContextMenuItem<T>;
228
233
  export type HeaderMenuContext<T> = {
229
234
  column: Column<T>;
230
235
  field: string;
231
236
  columnIndex: number;
232
237
  sortDirection: 'asc' | 'desc' | null;
233
238
  isFrozen: boolean;
239
+ isTreeMode: boolean;
234
240
  allColumns: Column<T>[];
235
241
  labels: GridLabels;
236
242
  };
@@ -301,6 +307,8 @@ export type QuickGridProps<T> = {
301
307
  items: T[];
302
308
  columns: Column<T>[];
303
309
  isFilterable?: boolean;
310
+ columnMinWidth?: string;
311
+ onfilterchange?: (filters: Record<string, string>) => void;
304
312
  isPageable?: boolean;
305
313
  pageSize?: number;
306
314
  isStriped?: boolean;
@@ -313,6 +321,7 @@ export type QuickGridProps<T> = {
313
321
  style?: string;
314
322
  customStylesCallback?: () => string;
315
323
  rowClassCallback?: (row: T, rowIndex: number) => string | null;
324
+ idMember?: keyof T | string;
316
325
  sort?: SortState[];
317
326
  sortMode?: SortMode;
318
327
  currentPage?: number;
@@ -327,8 +336,10 @@ export type QuickGridProps<T> = {
327
336
  summaryContentCallback?: SummaryContentCallback<T>;
328
337
  isSummaryInline?: boolean;
329
338
  isEditable?: boolean;
339
+ isRowEditable?: boolean | ((row: T) => boolean);
330
340
  editTrigger?: EditTrigger;
331
341
  editStartSelection?: EditStartSelection;
342
+ shouldCommitOnClickAway?: boolean;
332
343
  mode?: GridMode;
333
344
  dropdownToggleVisibility?: ToggleVisibility;
334
345
  shouldShowDropdownOnFocus?: boolean;
@@ -347,7 +358,7 @@ export type QuickGridProps<T> = {
347
358
  cellToolbarOffset?: number | string;
348
359
  toolbarBtnMinWidth?: string;
349
360
  inlineActionsTitle?: string;
350
- contextMenu?: ContextMenuItem<T>[];
361
+ contextMenu?: ContextMenuConfig<T>[];
351
362
  contextMenuXOffset?: number;
352
363
  contextMenuYOffset?: number;
353
364
  oncontextmenuopen?: (context: ContextMenuContext<T>) => void;