@keenmate/web-grid 1.0.3 → 1.0.5

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,21 +2,18 @@
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.3
5
+ ## What's New in v1.0.5
6
6
 
7
- - **`onrowfocus` fixes**: No longer fires during cell range selection (drag/shift+click). Mouse-triggered row focus now defers to click (mouseup) instead of mousedown; keyboard navigation still fires immediately.
8
- - **Cell selection visual fix**: Focused cell outline no longer persists during cell range drag.
9
- - **Toolbar fixes**: Tooltip hides when toolbar moves/closes. Selections cleared on toolbar action click. `triggerElement` in `ontoolbarclick` detail stays alive (no longer detached by synchronous re-render).
10
- - **Z-index layer system**: All z-index values now use CSS custom properties (`--wg-z-header`, `--wg-z-frozen`, etc.). Fixes cell selection bleeding through sticky header and frozen header stacking.
11
- - **Logging system**: loglevel-based logging with 4 categories (`GRID:INIT`, `GRID:DATA`, `GRID:UI`, `GRID:INTERACTION`). Enable via `window.components['web-grid'].logging.enableLogging()`.
12
- - **Runtime API**: `window.components['web-grid']` with `version()`, `config`, and `logging` — matching web-multiselect and web-daterangepicker.
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.
13
12
 
14
- ### v1.0.2
13
+ ## What's New in v1.0.4
15
14
 
16
- - **Tooltip positioning in transformed containers**: Fixed tooltips appearing at grid's top-left when ancestor has CSS `transform`. Switched to `position: absolute` with `:host` as positioning context.
17
- - **HTML tooltips**: New `isTooltipHtml` column option for rich tooltip content.
18
- - **`ontoolbarclick` detail**: Now includes `event` (MouseEvent) and `triggerElement` (HTMLElement) for anchoring popovers to toolbar buttons inside shadow DOM.
19
- - **Tooltip show delay**: Reduced default from 400ms to 200ms.
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`.
20
17
 
21
18
  ## Installation
22
19
 
@@ -24,37 +21,220 @@ A feature-rich, framework-agnostic data grid web component built with TypeScript
24
21
  npm install @keenmate/web-grid
25
22
  ```
26
23
 
27
- ## Quick Start
24
+ Or via CDN (no bundler):
28
25
 
29
- ### ES Module (recommended)
26
+ ```html
27
+ <script type="module" src="https://unpkg.com/@keenmate/web-grid"></script>
28
+ ```
29
+
30
+ ## Getting Started
31
+
32
+ ### Step 1 — Import the component
33
+
34
+ Importing the package registers the `<web-grid>` custom element globally. You only need to import it once anywhere in your app.
35
+
36
+ ```javascript
37
+ import '@keenmate/web-grid'
38
+ ```
39
+
40
+ For vanilla HTML without a bundler:
41
+
42
+ ```html
43
+ <script type="module" src="https://unpkg.com/@keenmate/web-grid"></script>
44
+ ```
45
+
46
+ ### Step 2 — Drop the element into your markup
30
47
 
31
48
  ```html
32
- <script type="module">
33
- import '@keenmate/web-grid'
34
- </script>
49
+ <web-grid id="grid" style="max-height: 400px"></web-grid>
50
+ ```
51
+
52
+ `max-height` (or `height`) on the host is how you control sizing — see [Height Modes](#height-modes).
53
+
54
+ ### Step 3 — Assign data and columns
55
+
56
+ The grid reads its configuration from properties on the element, not attributes. Grab a reference and set what you need:
57
+
58
+ ```javascript
59
+ const grid = document.getElementById('grid')
60
+
61
+ grid.items = [
62
+ { id: 1, name: 'Alice', age: 28, department: 'Engineering' },
63
+ { id: 2, name: 'Bob', age: 34, department: 'Marketing' }
64
+ ]
65
+
66
+ grid.columns = [
67
+ { field: 'id', title: 'ID', width: '60px' },
68
+ { field: 'name', title: 'Name', width: '160px' },
69
+ { field: 'age', title: 'Age', width: '80px', horizontalAlign: 'right' },
70
+ { field: 'department', title: 'Department', width: '160px' }
71
+ ]
72
+ ```
35
73
 
36
- <web-grid id="grid"></web-grid>
74
+ That's the minimum — a read-only grid with the headers, rows, and columns you defined.
37
75
 
38
- <script type="module">
39
- const grid = document.getElementById('grid')
40
- grid.items = [
41
- { id: 1, name: 'Alice', age: 28 },
42
- { id: 2, name: 'Bob', age: 34 }
43
- ]
44
- grid.columns = [
45
- { field: 'id', title: 'ID', width: '60px' },
46
- { field: 'name', title: 'Name' },
47
- { field: 'age', title: 'Age' }
48
- ]
49
- grid.sortMode = 'multi' // Enable multi-column sorting
50
- </script>
76
+ ### Step 4 — Enable the features you need
77
+
78
+ Turn on whatever the page calls for. Everything is off by default:
79
+
80
+ ```javascript
81
+ grid.isStriped = true // alternating row backgrounds
82
+ grid.isHoverable = true // hover highlight
83
+ grid.isRowNumbersVisible = true // leftmost # column
84
+ grid.sortMode = 'multi' // click headers to sort, Ctrl+click to add
85
+ grid.isPageable = true
86
+ grid.pageSize = 25
87
+ grid.isEditable = true
88
+ grid.editTrigger = 'navigate' // Excel-like: type to edit the focused cell
89
+ ```
90
+
91
+ ### Essential Properties
92
+
93
+ | Property | Type | Purpose |
94
+ |----------|------|---------|
95
+ | `items` | `T[]` | Row data — the only required "content" property |
96
+ | `columns` | `Column<T>[]` | Column definitions with at least a `field` each |
97
+ | `isStriped` / `isHoverable` / `isRowNumbersVisible` | `boolean` | Cosmetic toggles |
98
+ | `sortMode` | `'none' \| 'single' \| 'multi'` | Column sort behavior |
99
+ | `isPageable` + `pageSize` | `boolean` + `number` | Pagination |
100
+ | `isEditable` + `editTrigger` | `boolean` + `EditTrigger` | Enable inline editing and how it starts |
101
+ | `isFilterable` | `boolean` | Per-column filter row under the headers |
102
+ | `mode` | `'read-only' \| 'excel' \| 'input-matrix'` | Presets that set several of the above together |
103
+
104
+ ## Common Configurations
105
+
106
+ Short recipes for common setups. Each sets only what's necessary on top of the Step 3 minimum.
107
+
108
+ ### Sortable grid
109
+
110
+ ```javascript
111
+ grid.sortMode = 'multi' // or 'single'
112
+ ```
113
+
114
+ ### Paginated grid with top + bottom pager
115
+
116
+ ```javascript
117
+ grid.isPageable = true
118
+ grid.pageSize = 25
119
+ grid.paginationPosition = 'top-center|bottom-center'
120
+ ```
121
+
122
+ ### Editable grid (Excel-like navigation)
123
+
124
+ ```javascript
125
+ grid.mode = 'excel' // isEditable + editTrigger='navigate' + cellSelectionMode='click'
126
+ ```
127
+
128
+ Or opt in manually for finer control:
129
+
130
+ ```javascript
131
+ grid.isEditable = true
132
+ grid.editTrigger = 'navigate'
51
133
  ```
52
134
 
53
- ### UMD (Script Tag)
135
+ ### Per-column editors
136
+
137
+ ```javascript
138
+ grid.columns = [
139
+ { field: 'name', title: 'Name', editor: 'text' },
140
+ { field: 'salary', title: 'Salary', editor: 'number', editorOptions: { min: 0, step: 1000 } },
141
+ { field: 'department', title: 'Dept', editor: 'select',
142
+ editorOptions: {
143
+ options: [
144
+ { value: 'eng', label: 'Engineering' },
145
+ { value: 'sal', label: 'Sales' }
146
+ ]
147
+ }
148
+ }
149
+ ]
150
+ ```
151
+
152
+ ### Height modes
153
+
154
+ The grid's shadow DOM uses `max-height: inherit`, so setting `max-height` on the host controls its internal scroll container:
54
155
 
55
156
  ```html
56
- <script src="https://unpkg.com/@keenmate/web-grid"></script>
57
- <web-grid id="grid"></web-grid>
157
+ <!-- Container: grid caps at 400px, scrolls internally -->
158
+ <web-grid style="max-height: 400px"></web-grid>
159
+
160
+ <!-- Full height: grid expands with content, page handles scrolling -->
161
+ <web-grid style="height: 100%"></web-grid>
162
+ ```
163
+
164
+ For full-height mode, also set `tableBorderOnly = true` so the grid doesn't capture wheel events:
165
+
166
+ ```javascript
167
+ grid.tableBorderOnly = true
168
+ ```
169
+
170
+ ### Header context menu with column hide + visibility submenu
171
+
172
+ ```javascript
173
+ grid.headerContextMenu = [
174
+ 'sortAsc', 'sortDesc', 'clearSort',
175
+ { dividerBefore: true },
176
+ 'hideColumn', // hide the right-clicked column
177
+ 'columnVisibility' // submenu with a toggle per column
178
+ ]
179
+ ```
180
+
181
+ ### Hide a column programmatically
182
+
183
+ ```javascript
184
+ grid.columns.find(c => c.field === 'email').isHidden = true
185
+ grid.columns = [...grid.columns] // reassign to trigger re-render
186
+ ```
187
+
188
+ ### Row toolbar
189
+
190
+ ```javascript
191
+ grid.isRowToolbarVisible = true
192
+ grid.rowToolbar = ['add', 'delete', 'duplicate', 'moveUp', 'moveDown']
193
+ ```
194
+
195
+ ### Master/detail — react to row focus
196
+
197
+ ```javascript
198
+ grid.onrowfocus = ({ row, rowIndex }) => {
199
+ showDetailsFor(row)
200
+ }
201
+ ```
202
+
203
+ ### Validate a cell before commit
204
+
205
+ ```javascript
206
+ grid.columns = [{
207
+ field: 'email',
208
+ title: 'Email',
209
+ editor: 'text',
210
+ beforeCommitCallback: ({ value }) => {
211
+ if (!/^[^@]+@[^@]+$/.test(String(value))) {
212
+ return { valid: false, message: 'Invalid email address' }
213
+ }
214
+ return { valid: true }
215
+ }
216
+ }]
217
+ ```
218
+
219
+ ### TypeScript
220
+
221
+ Full type definitions ship with the package. Import types from the root:
222
+
223
+ ```typescript
224
+ import '@keenmate/web-grid'
225
+ import type { Column, RowChangeDetail } from '@keenmate/web-grid'
226
+
227
+ interface Employee {
228
+ id: number
229
+ name: string
230
+ salary: number
231
+ }
232
+
233
+ const columns: Column<Employee>[] = [
234
+ { field: 'id', title: 'ID', width: '60px' },
235
+ { field: 'name', title: 'Name', width: '200px' },
236
+ { field: 'salary', title: 'Salary', editor: 'number' }
237
+ ]
58
238
  ```
59
239
 
60
240
  ## Features
@@ -89,6 +89,7 @@ page-size pageSize number 10 Rows per pa
89
89
  is-editable isEditable boolean false Enable cell editing
90
90
  edit-trigger editTrigger string 'dblclick' How editing starts (see below)
91
91
  is-row-numbers-visible isRowNumbersVisible boolean false Show row number column
92
+ isDirtyIndicatorVisible boolean true Show dirty indicator on edited cells
92
93
  mode mode string (none) Grid mode (see below)
93
94
  is-scrollable isScrollable boolean false Enable scroll container
94
95
  freeze-columns freezeColumns number 0 Freeze first N columns
package/ai/editing.txt CHANGED
@@ -427,6 +427,27 @@ Draft behavior:
427
427
  - The onrowchange callback includes both row (original) and draftRow (with
428
428
  changes) so the consumer can compare.
429
429
 
430
+ Dirty indicator:
431
+ grid.isDirtyIndicatorVisible (boolean, default: true)
432
+ When enabled, edited cells show a subtle orange background tint and a small
433
+ corner triangle. Row numbers show an orange left border when any cell in
434
+ the row has been modified.
435
+
436
+ grid.isCellDirty(rowIndex, field) -- Returns true if the cell's draft value
437
+ differs from the original.
438
+ grid.isRowDirty(rowIndex) -- Returns true if any cell in the row
439
+ has been modified (draft exists).
440
+
441
+ CSS variables for theming:
442
+ --wg-dirty-indicator-color (default: #ed8b00)
443
+ --wg-dirty-indicator-size (default: 6px)
444
+ --wg-dirty-cell-bg (default: rgba(237, 139, 0, 0.08))
445
+ --wg-dirty-row-number-border-color (default: #ed8b00)
446
+
447
+ CSS classes applied:
448
+ .wg__cell--dirty -- on dirty data cells
449
+ .wg__row-number--dirty -- on row number cells of dirty rows
450
+
430
451
 
431
452
  EVENTS (CALLBACKS)
432
453
  ------------------
@@ -53,6 +53,12 @@ discardAllDrafts(): void
53
53
  Discard all draft changes across all rows. Reverts every edited cell
54
54
  to its original value.
55
55
 
56
+ isCellDirty(rowIndex: number, field: string): boolean
57
+ Check if a specific cell has been modified (draft value differs from original).
58
+
59
+ isRowDirty(rowIndex: number): boolean
60
+ Check if any cell in a row has been modified (draft exists for that row).
61
+
56
62
 
57
63
  ----------------------------------------------------------------------
58
64
  VALIDATION
@@ -115,6 +115,9 @@ All four methods apply the same dark palette overrides:
115
115
  --wg-hover-bg: #3a3a3a
116
116
  --wg-danger-color: #f87c86
117
117
  --wg-danger-bg-light: #442726
118
+ --wg-dirty-indicator-color: #ffa940
119
+ --wg-dirty-cell-bg: rgba(255, 169, 64, 0.12)
120
+ --wg-dirty-row-number-border-color: #ffa940
118
121
 
119
122
  The attribute can be on the <web-grid> element itself or any ancestor:
120
123
 
package/dist/grid.d.ts CHANGED
@@ -28,6 +28,7 @@ export declare class WebGrid<T = unknown> {
28
28
  protected _isStickyRowNumbers: boolean;
29
29
  protected _freezeColumns: number;
30
30
  protected _invalidCells: CellValidationState[];
31
+ protected _isDirtyIndicatorVisible: boolean;
31
32
  protected _isRowToolbarVisible: boolean;
32
33
  protected _rowToolbar: RowToolbarConfig<T>[];
33
34
  protected _toolbarVerticalAlign: 'top' | 'center' | 'bottom';
@@ -200,6 +201,8 @@ export declare class WebGrid<T = unknown> {
200
201
  set isCheckboxAlwaysEditable(value: boolean);
201
202
  get isRowNumbersVisible(): boolean;
202
203
  set isRowNumbersVisible(value: boolean);
204
+ get isDirtyIndicatorVisible(): boolean;
205
+ set isDirtyIndicatorVisible(value: boolean);
203
206
  get isStickyRowNumbers(): boolean;
204
207
  set isStickyRowNumbers(value: boolean);
205
208
  get freezeColumns(): number;
@@ -544,6 +547,14 @@ export declare class WebGrid<T = unknown> {
544
547
  protected requestUpdate(): void;
545
548
  getRowDraft(rowIndex: number): T | undefined;
546
549
  hasRowDraft(rowIndex: number): boolean;
550
+ /**
551
+ * Check if a specific cell has been modified (draft value differs from original)
552
+ */
553
+ isCellDirty(rowIndex: number, field: string): boolean;
554
+ /**
555
+ * Check if any cell in a row has been modified
556
+ */
557
+ isRowDirty(rowIndex: number): boolean;
547
558
  discardRowDraft(rowIndex: number): void;
548
559
  /**
549
560
  * Discard draft value for a single cell (field)
package/dist/types.d.ts CHANGED
@@ -305,6 +305,7 @@ export type QuickGridProps<T> = {
305
305
  isStriped?: boolean;
306
306
  isHoverable?: boolean;
307
307
  isRowNumbersVisible?: boolean;
308
+ isDirtyIndicatorVisible?: boolean;
308
309
  isStickyRowNumbers?: boolean;
309
310
  freezeColumns?: number;
310
311
  class?: string;
@@ -113,6 +113,10 @@ export declare class GridElement<T = unknown> extends HTMLElement implements Gri
113
113
  set isCheckboxAlwaysEditable(value: boolean);
114
114
  get isRowNumbersVisible(): boolean;
115
115
  set isRowNumbersVisible(value: boolean);
116
+ get isDirtyIndicatorVisible(): boolean;
117
+ set isDirtyIndicatorVisible(value: boolean);
118
+ isCellDirty(rowIndex: number, field: string): boolean;
119
+ isRowDirty(rowIndex: number): boolean;
116
120
  get isStickyRowNumbers(): boolean;
117
121
  set isStickyRowNumbers(value: boolean);
118
122
  get freezeColumns(): number;