@keenmate/web-grid 1.0.5 → 1.2.0-rc01

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/README.md CHANGED
@@ -2,18 +2,21 @@
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.0.5
5
+ ## What's New in v1.2.0-rc01
6
6
 
7
- - **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.
8
- - **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.
9
- - **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.
10
- - **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-*`.
11
- - **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.
7
+ - **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`.
8
+ - **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.
9
+ - **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.
10
+ - **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.
11
+ - **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).
12
12
 
13
- ## What's New in v1.0.4
13
+ ## What's New in v1.1.0
14
14
 
15
- - **Dirty cell/row indicator**: New `isDirtyIndicatorVisible` property (default: `true`). Edited cells show a subtle orange tint + corner triangle; row numbers get an orange left border. Themable via `--wg-dirty-*` variables. Public methods: `isCellDirty()`, `isRowDirty()`.
16
- - **Dropdown positioning fix**: Fixed dropdown editors appearing offset in shadow DOM by switching from `position: fixed` to `position: absolute`.
15
+ - **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`.
16
+ - **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`.
17
+ - **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`).
18
+ - **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).
19
+ - **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.
17
20
 
18
21
  ## Installation
19
22
 
package/dist/grid.d.ts CHANGED
@@ -1,4 +1,16 @@
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 } 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
+ type TreeNode = {
3
+ path: string;
4
+ parent: string | null;
5
+ level: number;
6
+ rowIndex: number;
7
+ childPaths: string[];
8
+ };
9
+ type TreeIndex = {
10
+ separator: string;
11
+ nodes: Map<string, TreeNode>;
12
+ rootPaths: string[];
13
+ };
2
14
  /**
3
15
  * WebGrid - Core logic class for the data grid
4
16
  *
@@ -17,6 +29,7 @@ export declare class WebGrid<T = unknown> {
17
29
  protected _isStriped: boolean;
18
30
  protected _isHoverable: boolean;
19
31
  protected _isEditable: boolean;
32
+ protected _isRowEditable: boolean | ((row: T) => boolean) | undefined;
20
33
  protected _editTrigger: EditTrigger;
21
34
  protected _editStartSelection: EditStartSelection;
22
35
  protected _mode: GridMode;
@@ -41,7 +54,7 @@ export declare class WebGrid<T = unknown> {
41
54
  protected _cellToolbarOffset: number | string;
42
55
  protected _toolbarBtnMinWidth: string | undefined;
43
56
  protected _inlineActionsTitle: string;
44
- protected _contextMenu: ContextMenuItem<T>[] | undefined;
57
+ protected _contextMenu: ContextMenuConfig<T>[] | undefined;
45
58
  protected _contextMenuXOffset: number;
46
59
  protected _contextMenuYOffset: number;
47
60
  protected _headerContextMenu: HeaderMenuConfig<T>[] | undefined;
@@ -93,6 +106,8 @@ export declare class WebGrid<T = unknown> {
93
106
  }) => void) | undefined;
94
107
  protected _sort: SortState[];
95
108
  protected _filters: Record<string, string>;
109
+ protected _onfilterchange: ((filters: Record<string, string>) => void) | undefined;
110
+ protected _columnMinWidth: string | undefined;
96
111
  protected _currentPage: number;
97
112
  protected _totalItems: number | null;
98
113
  protected _showPagination: boolean | 'auto';
@@ -155,6 +170,25 @@ export declare class WebGrid<T = unknown> {
155
170
  protected _newRowIndicator: string;
156
171
  protected _createEmptyRowCallback: (() => T | Promise<T>) | undefined;
157
172
  protected _emptyRowDraft: T | null;
173
+ protected _treePathMember: string | null;
174
+ protected _treeLevelMember: string | null;
175
+ protected _treeParentMember: string | null;
176
+ protected _idMember: string | null;
177
+ protected _hasWarnedAboutMissingRowId: boolean;
178
+ protected _treeSeparator: string | null;
179
+ protected _treeDataSorted: boolean;
180
+ protected _expandedPaths: Set<string>;
181
+ protected _expandedPathsExternal: Set<string> | null;
182
+ protected _defaultExpandDepth: number | null;
183
+ protected _treeIndex: TreeIndex | null;
184
+ protected _onexpandedpathschange: ((detail: TreeExpandedChangeDetail) => void) | undefined;
185
+ protected _treeDoubleClickBehavior: TreeDoubleClickBehavior;
186
+ protected _treeExpandedGlyph: string;
187
+ protected _treeCollapsedGlyph: string;
188
+ protected _treeChevronCallback: TreeChevronCallback<T> | undefined;
189
+ protected _treeChevronCache: WeakMap<object, Map<string, string>>;
190
+ protected _paginatedItemsCache: T[] | null;
191
+ protected _displayItemsCache: T[] | null;
158
192
  get items(): T[];
159
193
  set items(value: T[]);
160
194
  get columns(): Column<T>[];
@@ -163,6 +197,10 @@ export declare class WebGrid<T = unknown> {
163
197
  set sortMode(value: SortMode);
164
198
  get isFilterable(): boolean;
165
199
  set isFilterable(value: boolean);
200
+ get onfilterchange(): ((filters: Record<string, string>) => void) | undefined;
201
+ set onfilterchange(value: ((filters: Record<string, string>) => void) | undefined);
202
+ get columnMinWidth(): string | undefined;
203
+ set columnMinWidth(value: string | undefined);
166
204
  get isPageable(): boolean;
167
205
  set isPageable(value: boolean);
168
206
  get pageSize(): number;
@@ -177,6 +215,8 @@ export declare class WebGrid<T = unknown> {
177
215
  set isHoverable(value: boolean);
178
216
  get isEditable(): boolean;
179
217
  set isEditable(value: boolean);
218
+ get isRowEditable(): boolean | ((row: T) => boolean) | undefined;
219
+ set isRowEditable(value: boolean | ((row: T) => boolean) | undefined);
180
220
  get editTrigger(): EditTrigger;
181
221
  set editTrigger(value: EditTrigger);
182
222
  get editStartSelection(): EditStartSelection;
@@ -258,8 +298,8 @@ export declare class WebGrid<T = unknown> {
258
298
  set toolbarBtnMinWidth(value: string | undefined);
259
299
  get inlineActionsTitle(): string;
260
300
  set inlineActionsTitle(value: string);
261
- get contextMenu(): ContextMenuItem<T>[] | undefined;
262
- set contextMenu(value: ContextMenuItem<T>[] | undefined);
301
+ get contextMenu(): ContextMenuConfig<T>[] | undefined;
302
+ set contextMenu(value: ContextMenuConfig<T>[] | undefined);
263
303
  get contextMenuXOffset(): number;
264
304
  set contextMenuXOffset(value: number);
265
305
  get contextMenuYOffset(): number;
@@ -350,6 +390,7 @@ export declare class WebGrid<T = unknown> {
350
390
  get ontoolbarclick(): ((detail: ToolbarClickDetail<T>) => void) | undefined;
351
391
  set ontoolbarclick(value: ((detail: ToolbarClickDetail<T>) => void) | undefined);
352
392
  set onrowaction(value: ((detail: RowActionClickDetail<T>) => void) | undefined);
393
+ get oncontextmenuopen(): ((context: ContextMenuContext<T>) => void) | undefined;
353
394
  set oncontextmenuopen(value: ((context: ContextMenuContext<T>) => void) | undefined);
354
395
  get onheadercontextmenuopen(): ((context: HeaderMenuContext<T>) => void) | undefined;
355
396
  set onheadercontextmenuopen(value: ((context: HeaderMenuContext<T>) => void) | undefined);
@@ -440,6 +481,91 @@ export declare class WebGrid<T = unknown> {
440
481
  set newRowIndicator(value: string);
441
482
  get createEmptyRowCallback(): (() => T | Promise<T>) | undefined;
442
483
  set createEmptyRowCallback(value: (() => T | Promise<T>) | undefined);
484
+ get idMember(): string | null;
485
+ set idMember(value: string | null);
486
+ /**
487
+ * Return a stable string key for a row. Coalesce order: idMember value → treePathMember
488
+ * value → displayed-index string fallback. Used to key drafts and invalid-cell markers
489
+ * so they don't drift when the displayed order changes (sort, filter, page, tree toggle).
490
+ *
491
+ * Distinct from `getRowId` which is the row-locking identifier (any type).
492
+ */
493
+ getStableRowKey(item: T, displayedIndex: number): string;
494
+ private maybeWarnAboutRowIdentity;
495
+ get treePathMember(): string | null;
496
+ set treePathMember(value: string | null);
497
+ get treeLevelMember(): string | null;
498
+ set treeLevelMember(value: string | null);
499
+ get treeParentMember(): string | null;
500
+ set treeParentMember(value: string | null);
501
+ get treeSeparator(): string | null;
502
+ set treeSeparator(value: string | null);
503
+ get treeDataSorted(): boolean;
504
+ set treeDataSorted(value: boolean);
505
+ get expandedPaths(): Set<string>;
506
+ set expandedPaths(value: Set<string> | null | undefined);
507
+ get defaultExpandDepth(): number | null;
508
+ set defaultExpandDepth(value: number | null);
509
+ get onexpandedpathschange(): ((detail: TreeExpandedChangeDetail) => void) | undefined;
510
+ set onexpandedpathschange(value: ((detail: TreeExpandedChangeDetail) => void) | undefined);
511
+ get isTreeMode(): boolean;
512
+ isPathExpanded(path: string): boolean;
513
+ toggleExpandedPath(path: string): void;
514
+ expandAll(): void;
515
+ collapseAll(): void;
516
+ /**
517
+ * Expand a path's entire subtree (the path itself + all descendants).
518
+ * Used by the per-row "expand all" context-menu action — file-explorer convention
519
+ * is "expand everything *under here*", not the entire dataset.
520
+ */
521
+ expandSubtree(path: string): void;
522
+ /**
523
+ * Collapse a path's entire subtree (the path itself + all descendants).
524
+ */
525
+ collapseSubtree(path: string): void;
526
+ /**
527
+ * Walk up the path's ancestor chain looking for the nearest expanded ancestor.
528
+ * Used by Ctrl+ArrowLeft: when the focused row has no expanded children to collapse,
529
+ * step up to the closest ancestor that is expanded so the user gets meaningful action.
530
+ */
531
+ findNearestExpandedAncestor(path: string): string | null;
532
+ /** Returns tree info for a row (used by rendering) */
533
+ getRowTreeInfo(item: T): {
534
+ path: string;
535
+ level: number;
536
+ hasChildren: boolean;
537
+ } | null;
538
+ get treeDoubleClickBehavior(): TreeDoubleClickBehavior;
539
+ set treeDoubleClickBehavior(value: TreeDoubleClickBehavior);
540
+ get treeExpandedGlyph(): string;
541
+ set treeExpandedGlyph(value: string);
542
+ get treeCollapsedGlyph(): string;
543
+ set treeCollapsedGlyph(value: string);
544
+ get treeChevronCallback(): TreeChevronCallback<T> | undefined;
545
+ set treeChevronCallback(value: TreeChevronCallback<T> | undefined);
546
+ /**
547
+ * Resolve the inner HTML for a row's chevron, using callback (cached) or static glyph.
548
+ * Cache is keyed per (row reference, expanded). When the row reference changes
549
+ * (immutable update) or items are replaced, stale entries are dropped via WeakMap GC
550
+ * or explicit cache clear.
551
+ */
552
+ getTreeChevronHtml(item: T, expanded: boolean, hasChildren: boolean, level: number, path: string): string;
553
+ /** Read the path field from a row */
554
+ protected getRowTreePath(item: T): string | null;
555
+ /** Detect separator from a sample path */
556
+ protected detectTreeSeparator(path: string): string;
557
+ /** Rebuild _treeIndex from current items */
558
+ protected rebuildTreeState(): void;
559
+ /** Re-initialize the active expanded set after items/config changes */
560
+ protected rebuildExpandedPaths(): void;
561
+ /** Compare two tree nodes using current sort state, falling back to path */
562
+ protected compareTreeNodes(a: TreeNode, b: TreeNode): number;
563
+ /** Compute paths matched by current text filters, plus their ancestors */
564
+ protected getTreeFilterAllowedPaths(): Set<string> | null;
565
+ /** Items in tree-aware sort order (depth-first, sibling-sorted), with filter applied */
566
+ protected getTreeSortedItems(): T[];
567
+ /** Items currently visible (post-collapse). When filter is active, ancestors of matches are auto-expanded. */
568
+ protected getTreeVisibleItems(): T[];
443
569
  selectCellRange(range: CellRange): void;
444
570
  clearCellSelection(): void;
445
571
  /** Clear cell selection state without triggering re-render (caller handles visuals) */
@@ -490,6 +616,15 @@ export declare class WebGrid<T = unknown> {
490
616
  copyCellSelectionToClipboard(): Promise<boolean>;
491
617
  get isNavigateMode(): boolean;
492
618
  get filteredItems(): T[];
619
+ /**
620
+ * Returns the set of fields whose current filter input is "incomplete" (column.filter
621
+ * returned null when probed against the first row). Renderers can use this to mark the
622
+ * input visually (red border) without excluding all rows.
623
+ */
624
+ getInvalidFilterFields(): Set<string>;
625
+ get filters(): Record<string, string>;
626
+ set filters(value: Record<string, string>);
627
+ setFilter(field: string, value: string): void;
493
628
  get sortedItems(): T[];
494
629
  get paginatedItems(): T[];
495
630
  get totalPages(): number;
@@ -545,6 +680,12 @@ export declare class WebGrid<T = unknown> {
545
680
  */
546
681
  protected checkToolbarConflicts(): void;
547
682
  protected requestUpdate(): void;
683
+ /**
684
+ * Drop memoized pipeline output. The web-component's requestUpdate override
685
+ * calls this on every state mutation, so callers don't need to invoke it
686
+ * directly — but it's safe to call any time the cache might be stale.
687
+ */
688
+ invalidateDisplayCache(): void;
548
689
  getRowDraft(rowIndex: number): T | undefined;
549
690
  hasRowDraft(rowIndex: number): boolean;
550
691
  /**
@@ -565,6 +706,8 @@ export declare class WebGrid<T = unknown> {
565
706
  discardAllDrafts(): void;
566
707
  getCellRawValue(item: T, rowIndex: number, field: string): unknown;
567
708
  getCellValue(item: T, column: Column<T>, rowIndex?: number): string;
709
+ /** Compute the stable rowKey for a displayed-row index, or null if no row at that index. */
710
+ private rowKeyForIndex;
568
711
  isCellInvalid(rowIndex: number, field: string): boolean;
569
712
  getCellValidationError(rowIndex: number, field: string): string | null;
570
713
  addInvalidCell(rowIndex: number, field: string, error: string): void;
@@ -611,16 +754,42 @@ export declare class WebGrid<T = unknown> {
611
754
  */
612
755
  updateDraftValue(rowIndex: number, field: string, newValue: unknown): void;
613
756
  /**
614
- * Check if a column's cells can be edited
615
- */
616
- isCellEditable(column: Column<T>): boolean;
617
- /**
618
- * Get all editable columns with their indices
619
- */
620
- getEditableColumns(): {
757
+ * Check if a column's cells can be edited.
758
+ * `row` is required to evaluate function-form predicates; pass it for per-row checks,
759
+ * omit it for static column-level checks (e.g. building toolbar UI). When omitted and a
760
+ * predicate exists, conservatively returns true so the column isn't hidden.
761
+ *
762
+ * Resolution order:
763
+ * 0. tree column in tree mode → not editable (the tree column displays hierarchy, not data)
764
+ * 1. column.isEditable === false → not editable (hard veto)
765
+ * 2. column.isEditable === function with row → must return true to continue
766
+ * 3. grid.isRowEditable with row → must pass to continue
767
+ * 4. column.isEditable === true → editable (overrides grid-level)
768
+ * 5. else → defer to grid-level isEditable
769
+ */
770
+ isCellEditable(column: Column<T>, row?: T): boolean;
771
+ /**
772
+ * Get all editable columns with their indices.
773
+ * Pass `row` to evaluate per-row predicates (function-form `isEditable`, `isRowEditable`).
774
+ * Without `row`, returns columns that are *potentially* editable (any row could pass).
775
+ */
776
+ getEditableColumns(row?: T): {
621
777
  index: number;
622
778
  column: Column<T>;
623
779
  }[];
780
+ /**
781
+ * Walk rows in `direction` looking for the next row that has at least one editable
782
+ * column at or beyond `fromColEditableIndex` (or beyond the last when going forward
783
+ * past the row's editable list). Used by Tab traversal in heterogeneous trees so the
784
+ * cursor skips read-only rows (e.g. team rows) entirely instead of landing on a cell
785
+ * with no tabindex and silently losing focus.
786
+ *
787
+ * Returns null if no editable cell exists in the requested direction.
788
+ */
789
+ findNextEditableCell(fromRowIndex: number, fromColEditableIndex: number, direction: 'forward' | 'backward'): {
790
+ rowIndex: number;
791
+ colIndex: number;
792
+ } | null;
624
793
  /**
625
794
  * Check if a specific cell is currently focused
626
795
  */
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
1
  export { GridElement } from './web-component.js';
2
2
  export { WebGrid } from './grid.js';
3
- export type { EditorType, EditTrigger, OptionsLoadTrigger, DateOutputFormat, EditorOption, EditorOptions, CustomEditorContext, CellValidationState, ValidationResult, BeforeCommitContext, BeforeCommitResult, Column, CellRenderCallback, RowChangeDetail, RowFocusDetail, PredefinedToolbarItemType, ToolbarPosition, NewRowPosition, ToolbarTooltip, RowToolbarItem, RowToolbarConfig, NormalizedToolbarItem, ToolbarClickDetail, RowActionType, RowActionClickDetail, ContextMenuContext, ContextMenuItem, QuickGridProps, SortState, DataRequestDetail, DataRequestTrigger, GridLabels, RowLockInfo, RowLockingOptions, RowLockChangeDetail, LockedRowEditBehavior, ColumnWidthState, ColumnResizeDetail, ColumnOrderState, ColumnReorderDetail, FillDragDetail, FillDirection, RangeShortcut, RangeShortcutContext, CellSelectionMode, CellRange, CellSelectionChangeDetail, PasteMode, PasteColumnMapping, BeforePasteDetail, PasteCellResult, PasteDetail, CreateRowCallback, EditingCell, FocusedCell, SortDirection, ToolbarRowGroup, PopupPosition, ConnectorArrowDir } from './types.js';
3
+ export type { EditorType, EditTrigger, OptionsLoadTrigger, DateOutputFormat, EditorOption, EditorOptions, CustomEditorContext, CellValidationState, ValidationResult, BeforeCommitContext, BeforeCommitResult, Column, CellRenderCallback, RowChangeDetail, RowFocusDetail, PredefinedToolbarItemType, ToolbarPosition, NewRowPosition, ToolbarTooltip, RowToolbarItem, RowToolbarConfig, NormalizedToolbarItem, ToolbarClickDetail, RowActionType, RowActionClickDetail, ContextMenuContext, ContextMenuItem, QuickGridProps, SortState, DataRequestDetail, DataRequestTrigger, GridLabels, RowLockInfo, RowLockingOptions, RowLockChangeDetail, LockedRowEditBehavior, ColumnWidthState, ColumnResizeDetail, ColumnOrderState, ColumnReorderDetail, FillDragDetail, FillDirection, RangeShortcut, RangeShortcutContext, CellSelectionMode, CellRange, CellSelectionChangeDetail, PasteMode, PasteColumnMapping, BeforePasteDetail, PasteCellResult, PasteDetail, CreateRowCallback, TreeExpandedChangeDetail, TreeChevronContext, TreeChevronCallback, TreeDoubleClickBehavior, EditingCell, FocusedCell, SortDirection, ToolbarRowGroup, PopupPosition, ConnectorArrowDir } from './types.js';
4
4
  export { GridElement as default } from './web-component.js';
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;
@@ -0,0 +1,18 @@
1
+ import type { Column } from '../../types.js';
2
+ import type { GridContext } from '../types.js';
3
+ /**
4
+ * Wrap cell content with tree indent + chevron when column is the tree column.
5
+ * Returns the original innerHtml unchanged when tree mode is off or column isn't isTree.
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
+ *
15
+ * Used by both the full-table render (table.ts) and the surgical single-cell
16
+ * render (cell.ts), so both paths keep the chevron after focus/edit transitions.
17
+ */
18
+ export declare function wrapTreeCell<T>(ctx: GridContext<T>, column: Column<T>, item: T, innerHtml: string): string;
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;
@@ -134,6 +136,7 @@ export type Column<T> = {
134
136
  isMovable?: boolean;
135
137
  fillDirection?: FillDirection;
136
138
  isHidden?: boolean;
139
+ isTree?: boolean;
137
140
  };
138
141
  export type ValidationTooltipContext<T> = {
139
142
  field: string;
@@ -213,23 +216,27 @@ export type ContextMenuContext<T> = {
213
216
  column: Column<T>;
214
217
  cellValue: unknown;
215
218
  };
219
+ export type PredefinedContextMenuItemType = 'expand-all' | 'collapse-all' | 'expand-tree' | 'collapse-tree';
216
220
  export type ContextMenuItem<T> = {
217
221
  id: string;
218
- label: string | ((context: ContextMenuContext<T>) => string);
222
+ label?: string | ((context: ContextMenuContext<T>) => string);
219
223
  icon?: string | ((context: ContextMenuContext<T>) => string);
220
224
  shortcut?: string;
221
225
  disabled?: boolean | ((context: ContextMenuContext<T>) => boolean);
222
226
  visible?: boolean | ((context: ContextMenuContext<T>) => boolean);
223
227
  danger?: boolean;
224
228
  dividerBefore?: boolean;
229
+ type?: PredefinedContextMenuItemType;
225
230
  onclick?: (context: ContextMenuContext<T>) => void | Promise<void>;
226
231
  };
232
+ export type ContextMenuConfig<T> = PredefinedContextMenuItemType | ContextMenuItem<T>;
227
233
  export type HeaderMenuContext<T> = {
228
234
  column: Column<T>;
229
235
  field: string;
230
236
  columnIndex: number;
231
237
  sortDirection: 'asc' | 'desc' | null;
232
238
  isFrozen: boolean;
239
+ isTreeMode: boolean;
233
240
  allColumns: Column<T>[];
234
241
  labels: GridLabels;
235
242
  };
@@ -300,6 +307,8 @@ export type QuickGridProps<T> = {
300
307
  items: T[];
301
308
  columns: Column<T>[];
302
309
  isFilterable?: boolean;
310
+ columnMinWidth?: string;
311
+ onfilterchange?: (filters: Record<string, string>) => void;
303
312
  isPageable?: boolean;
304
313
  pageSize?: number;
305
314
  isStriped?: boolean;
@@ -312,6 +321,7 @@ export type QuickGridProps<T> = {
312
321
  style?: string;
313
322
  customStylesCallback?: () => string;
314
323
  rowClassCallback?: (row: T, rowIndex: number) => string | null;
324
+ idMember?: keyof T | string;
315
325
  sort?: SortState[];
316
326
  sortMode?: SortMode;
317
327
  currentPage?: number;
@@ -326,6 +336,7 @@ export type QuickGridProps<T> = {
326
336
  summaryContentCallback?: SummaryContentCallback<T>;
327
337
  isSummaryInline?: boolean;
328
338
  isEditable?: boolean;
339
+ isRowEditable?: boolean | ((row: T) => boolean);
329
340
  editTrigger?: EditTrigger;
330
341
  editStartSelection?: EditStartSelection;
331
342
  mode?: GridMode;
@@ -346,7 +357,7 @@ export type QuickGridProps<T> = {
346
357
  cellToolbarOffset?: number | string;
347
358
  toolbarBtnMinWidth?: string;
348
359
  inlineActionsTitle?: string;
349
- contextMenu?: ContextMenuItem<T>[];
360
+ contextMenu?: ContextMenuConfig<T>[];
350
361
  contextMenuXOffset?: number;
351
362
  contextMenuYOffset?: number;
352
363
  oncontextmenuopen?: (context: ContextMenuContext<T>) => void;
@@ -401,6 +412,18 @@ export type QuickGridProps<T> = {
401
412
  cellSelectionMode?: CellSelectionMode;
402
413
  shouldCopyWithHeaders?: boolean;
403
414
  oncellselectionchange?: (detail: CellSelectionChangeDetail) => void;
415
+ treePathMember?: keyof T | string;
416
+ treeLevelMember?: keyof T | string;
417
+ treeParentMember?: keyof T | string;
418
+ treeSeparator?: string;
419
+ treeDataSorted?: boolean;
420
+ expandedPaths?: Set<string>;
421
+ defaultExpandDepth?: number;
422
+ treeDoubleClickBehavior?: TreeDoubleClickBehavior;
423
+ onexpandedpathschange?: (detail: TreeExpandedChangeDetail) => void;
424
+ treeExpandedGlyph?: string;
425
+ treeCollapsedGlyph?: string;
426
+ treeChevronCallback?: TreeChevronCallback<T>;
404
427
  isNewRowEnabled?: boolean;
405
428
  newRowPosition?: NewRowPosition;
406
429
  newRowIndicator?: string;
@@ -595,3 +618,25 @@ export type PasteDetail<T> = {
595
618
  };
596
619
  /** Callback to create new rows from pasted data */
597
620
  export type CreateRowCallback<T> = (pastedData: Record<string, unknown>, rowIndex: number) => T;
621
+ /** What happens when the user double-clicks a tree-column cell */
622
+ export type TreeDoubleClickBehavior = 'none' | 'toggle';
623
+ /** Detail passed to onexpandedpathschange when user toggles a tree node */
624
+ export type TreeExpandedChangeDetail = {
625
+ path: string;
626
+ expanded: boolean;
627
+ expandedPaths: Set<string>;
628
+ };
629
+ /** Context passed to treeChevronCallback */
630
+ export type TreeChevronContext<T> = {
631
+ expanded: boolean;
632
+ hasChildren: boolean;
633
+ row: T;
634
+ level: number;
635
+ path: string;
636
+ };
637
+ /**
638
+ * Callback that returns HTML for the chevron's inner content.
639
+ * Result is cached per (row, expanded) — the callback is invoked at most twice
640
+ * per row, once per expanded state. Invalidated when items or the callback change.
641
+ */
642
+ export type TreeChevronCallback<T> = (context: TreeChevronContext<T>) => string;