@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 +12 -9
- package/dist/grid.d.ts +180 -11
- package/dist/index.d.ts +1 -1
- package/dist/logger.d.ts +1 -0
- package/dist/modules/click-events/index.d.ts +5 -1
- package/dist/modules/contextmenu/index.d.ts +10 -1
- package/dist/modules/datepicker/datepicker.d.ts +2 -0
- package/dist/modules/rendering/tree-render.d.ts +18 -0
- package/dist/perf.d.ts +15 -0
- package/dist/types.d.ts +49 -4
- package/dist/web-component.d.ts +49 -3
- package/dist/web-grid.js +4813 -3892
- package/dist/web-grid.umd.js +143 -148
- package/package.json +9 -3
- package/src/css/_navigation.css +7 -2
- package/src/css/_table.css +7 -1
- package/src/css/_tree.css +71 -0
- package/src/css/main.css +1 -0
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
|
+
## What's New in v1.2.0-rc01
|
|
6
6
|
|
|
7
|
-
- **
|
|
8
|
-
- **
|
|
9
|
-
- **
|
|
10
|
-
- **
|
|
11
|
-
- **
|
|
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
|
|
13
|
+
## What's New in v1.1.0
|
|
14
14
|
|
|
15
|
-
- **
|
|
16
|
-
- **
|
|
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,
|
|
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:
|
|
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():
|
|
262
|
-
set contextMenu(value:
|
|
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
|
-
|
|
617
|
-
|
|
618
|
-
*
|
|
619
|
-
|
|
620
|
-
|
|
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
|
-
|
|
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
|
|
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?:
|
|
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;
|