@keenmate/web-grid 1.0.0-rc15 → 1.0.1
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 +13 -6
- package/ai/INDEX.txt +360 -0
- package/ai/basic-setup.txt +165 -0
- package/ai/callbacks-events.txt +297 -0
- package/ai/columns.txt +486 -0
- package/ai/dropdown-editors.txt +504 -0
- package/ai/editing.txt +545 -0
- package/ai/fill-handle.txt +128 -0
- package/ai/frozen-columns.txt +142 -0
- package/ai/grid-modes.txt +267 -0
- package/ai/keyboard-navigation.txt +429 -0
- package/ai/public-methods.txt +231 -0
- package/ai/row-locking.txt +214 -0
- package/ai/selection.txt +403 -0
- package/ai/sorting-filtering-pagination.txt +375 -0
- package/ai/styling-theming.txt +291 -0
- package/ai/toolbar-actions.txt +475 -0
- package/ai/typescript-types.txt +498 -0
- package/ai/virtual-scroll.txt +146 -0
- package/dist/grid.d.ts +4 -0
- package/dist/modules/navigation/focus.d.ts +4 -0
- package/dist/modules/navigation/index.d.ts +1 -1
- package/dist/modules/toolbar/index.d.ts +1 -1
- package/dist/types.d.ts +3 -0
- package/dist/web-grid.js +2038 -1997
- package/dist/web-grid.umd.js +80 -79
- package/package.json +2 -1
- package/src/css/_cell-selection.css +5 -3
- package/src/css/_dark-mode.css +44 -7
- package/src/css/_dialogs.css +1 -1
- package/src/css/_freeze.css +10 -5
- package/src/css/_navigation.css +15 -8
- package/src/css/_selection.css +5 -3
- package/src/css/_variables.css +3 -0
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
ROW LOCKING
|
|
2
|
+
===========
|
|
3
|
+
@keenmate/web-grid - Optimistic row locking with external lock management.
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
OVERVIEW
|
|
7
|
+
--------
|
|
8
|
+
Row locking prevents editing of rows that are locked by other users or
|
|
9
|
+
processes. Three lock sources exist: property-based, callback-based, and
|
|
10
|
+
external API. Locks are configured via the rowLocking property on the grid.
|
|
11
|
+
Row identification (idValueMember or idValueCallback) is required for
|
|
12
|
+
external lock methods.
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
CONFIGURATION
|
|
16
|
+
-------------
|
|
17
|
+
grid.rowLocking = {
|
|
18
|
+
lockedMember: 'isLocked',
|
|
19
|
+
lockInfoMember: 'lockInfo',
|
|
20
|
+
lockedEditBehavior: 'block',
|
|
21
|
+
lockTooltipCallback: (lockInfo, row) => {
|
|
22
|
+
return 'Locked by ' + lockInfo.lockedBy
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
LOCK SOURCE 1: PROPERTY-BASED
|
|
28
|
+
------------------------------
|
|
29
|
+
lockedMember (keyof T)
|
|
30
|
+
Property name on the row object containing a boolean lock state.
|
|
31
|
+
|
|
32
|
+
grid.rowLocking = { lockedMember: 'isLocked' }
|
|
33
|
+
// Row: { id: 1, name: 'Alice', isLocked: true }
|
|
34
|
+
|
|
35
|
+
lockInfoMember (keyof T)
|
|
36
|
+
Property name on the row object containing a RowLockInfo object.
|
|
37
|
+
|
|
38
|
+
grid.rowLocking = { lockInfoMember: 'lockInfo' }
|
|
39
|
+
// Row: { id: 1, name: 'Alice', lockInfo: { isLocked: true, lockedBy: 'Bob' } }
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
LOCK SOURCE 2: CALLBACK-BASED
|
|
43
|
+
------------------------------
|
|
44
|
+
isLockedCallback (row: T, rowIndex: number) => boolean
|
|
45
|
+
Callback that determines if a row is locked.
|
|
46
|
+
|
|
47
|
+
getLockInfoCallback (row: T, rowIndex: number) => RowLockInfo | null
|
|
48
|
+
Callback returning detailed lock information.
|
|
49
|
+
|
|
50
|
+
grid.rowLocking = {
|
|
51
|
+
isLockedCallback: (row) => row.status === 'in-review',
|
|
52
|
+
getLockInfoCallback: (row) => ({
|
|
53
|
+
isLocked: row.status === 'in-review',
|
|
54
|
+
lockedBy: row.reviewer,
|
|
55
|
+
reason: 'Under review'
|
|
56
|
+
})
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
LOCK SOURCE 3: EXTERNAL API
|
|
61
|
+
----------------------------
|
|
62
|
+
For real-time lock management (e.g., WebSocket-driven collaborative editing).
|
|
63
|
+
Requires idValueMember or idValueCallback to be set.
|
|
64
|
+
|
|
65
|
+
lockRowById(id, lockerInfo?)
|
|
66
|
+
Lock a row by its ID. Returns boolean (true if row found).
|
|
67
|
+
lockerInfo is an optional RowLockInfo object.
|
|
68
|
+
|
|
69
|
+
grid.lockRowById(42, {
|
|
70
|
+
isLocked: true,
|
|
71
|
+
lockedBy: 'Jane',
|
|
72
|
+
lockedAt: new Date()
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
unlockRowById(id)
|
|
76
|
+
Unlock an externally locked row. Returns boolean.
|
|
77
|
+
|
|
78
|
+
grid.unlockRowById(42)
|
|
79
|
+
|
|
80
|
+
getExternalLocks()
|
|
81
|
+
Returns Map<unknown, RowLockInfo> of all external locks.
|
|
82
|
+
|
|
83
|
+
clearExternalLocks()
|
|
84
|
+
Remove all external locks at once.
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
ROWLOCKINFO TYPE
|
|
88
|
+
----------------
|
|
89
|
+
{
|
|
90
|
+
isLocked: boolean
|
|
91
|
+
lockedBy?: string Who locked (user name or ID)
|
|
92
|
+
lockedAt?: Date | string When locked
|
|
93
|
+
reason?: string Why locked
|
|
94
|
+
[key: string]: unknown Extra properties allowed
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
LOCKED EDIT BEHAVIOR
|
|
99
|
+
--------------------
|
|
100
|
+
lockedEditBehavior on RowLockingOptions<T>
|
|
101
|
+
Controls what happens when a user tries to edit a locked row.
|
|
102
|
+
|
|
103
|
+
'block' (default)
|
|
104
|
+
Editing is completely blocked. Cells show cursor: not-allowed.
|
|
105
|
+
|
|
106
|
+
'allow'
|
|
107
|
+
Editing is allowed despite the lock. Only visual indicators are shown.
|
|
108
|
+
|
|
109
|
+
'callback'
|
|
110
|
+
Consumer decides per-row via canEditLockedCallback.
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
canEditLockedCallback (row: T, lockInfo: RowLockInfo) => boolean
|
|
114
|
+
Only used when lockedEditBehavior is 'callback'. Return true to allow
|
|
115
|
+
editing the specific locked row, false to block it.
|
|
116
|
+
|
|
117
|
+
grid.rowLocking = {
|
|
118
|
+
lockedEditBehavior: 'callback',
|
|
119
|
+
canEditLockedCallback: (row, lockInfo) => {
|
|
120
|
+
return lockInfo.lockedBy === currentUser
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
LOCK TOOLTIP
|
|
126
|
+
------------
|
|
127
|
+
lockTooltipCallback (lockInfo: RowLockInfo, row: T) => string | null
|
|
128
|
+
Returns HTML string for the tooltip shown when hovering the lock icon.
|
|
129
|
+
Return null for no tooltip.
|
|
130
|
+
|
|
131
|
+
grid.rowLocking = {
|
|
132
|
+
lockTooltipCallback: (lockInfo) => {
|
|
133
|
+
return '<strong>Locked by:</strong> ' + lockInfo.lockedBy +
|
|
134
|
+
'<br><strong>Since:</strong> ' + lockInfo.lockedAt
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
VISUAL INDICATORS
|
|
140
|
+
-----------------
|
|
141
|
+
Locked rows receive the following visual treatment:
|
|
142
|
+
- Row gets wg__row--locked CSS class
|
|
143
|
+
- Muted styling: cells have reduced opacity (--wg-row-locked-opacity: 0.7)
|
|
144
|
+
- Background: --wg-row-locked-bg (defaults to disabled/surface-2)
|
|
145
|
+
- Editable cells show cursor: not-allowed (when behavior is 'block')
|
|
146
|
+
- Hover effects on editable cells are suppressed
|
|
147
|
+
- Lock icon appears in the row number column (wg__row-number--locked class)
|
|
148
|
+
- Lock icon has cursor: help and full opacity (overrides row opacity)
|
|
149
|
+
|
|
150
|
+
CSS variables:
|
|
151
|
+
--wg-row-locked-bg Default: var(--base-disabled-bg, var(--wg-surface-2))
|
|
152
|
+
--wg-row-locked-opacity Default: 0.7
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
ONROWLOCKCHANGE EVENT
|
|
156
|
+
---------------------
|
|
157
|
+
Fires when a row's lock state changes.
|
|
158
|
+
|
|
159
|
+
grid.onrowlockchange = (detail) => {
|
|
160
|
+
console.log('Row', detail.rowId, 'lock changed')
|
|
161
|
+
console.log('Source:', detail.source) // 'property', 'callback', or 'external'
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
RowLockChangeDetail<T>:
|
|
165
|
+
rowId: unknown Row identifier
|
|
166
|
+
row: T | null Row data (null if row not found)
|
|
167
|
+
rowIndex: number Row index
|
|
168
|
+
lockInfo: RowLockInfo | null Current lock info (null if unlocked)
|
|
169
|
+
source: 'property' | 'callback' | 'external'
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
QUERY METHODS
|
|
173
|
+
-------------
|
|
174
|
+
isRowLocked(rowOrId)
|
|
175
|
+
Returns boolean. Accepts a row object or a row ID.
|
|
176
|
+
|
|
177
|
+
getRowLockInfo(rowOrId)
|
|
178
|
+
Returns RowLockInfo | null. Accepts a row object or a row ID.
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
WEBSOCKET INTEGRATION EXAMPLE
|
|
182
|
+
------------------------------
|
|
183
|
+
grid.idValueMember = 'id'
|
|
184
|
+
grid.rowLocking = {
|
|
185
|
+
lockedEditBehavior: 'block',
|
|
186
|
+
lockTooltipCallback: (info) => 'Locked by ' + info.lockedBy
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
websocket.onmessage = (event) => {
|
|
190
|
+
const msg = JSON.parse(event.data)
|
|
191
|
+
if (msg.type === 'row-locked') {
|
|
192
|
+
grid.lockRowById(msg.rowId, {
|
|
193
|
+
isLocked: true,
|
|
194
|
+
lockedBy: msg.user,
|
|
195
|
+
lockedAt: new Date()
|
|
196
|
+
})
|
|
197
|
+
}
|
|
198
|
+
if (msg.type === 'row-unlocked') {
|
|
199
|
+
grid.unlockRowById(msg.rowId)
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
REQUIREMENTS
|
|
205
|
+
------------
|
|
206
|
+
External lock methods (lockRowById, unlockRowById, etc.) require row
|
|
207
|
+
identification to be configured:
|
|
208
|
+
|
|
209
|
+
grid.idValueMember = 'id'
|
|
210
|
+
// or
|
|
211
|
+
grid.idValueCallback = (row) => row.id
|
|
212
|
+
|
|
213
|
+
Without this, the grid cannot find rows by ID and the lock methods will
|
|
214
|
+
return false.
|
package/ai/selection.txt
ADDED
|
@@ -0,0 +1,403 @@
|
|
|
1
|
+
SELECTION FEATURES IN @keenmate/web-grid
|
|
2
|
+
=========================================
|
|
3
|
+
The grid supports four distinct selection types: row selection, cell range
|
|
4
|
+
selection, column selection, and row focus. Each is independent -- selecting
|
|
5
|
+
in one mode clears the others (row, cell range, column are mutually exclusive).
|
|
6
|
+
Row focus is a separate concept that tracks which row the user last clicked.
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
ROW SELECTION
|
|
10
|
+
-------------
|
|
11
|
+
Rows are selected by clicking row number cells (requires isRowNumbersVisible).
|
|
12
|
+
Selected row indices are tracked in a Set internally. The selectedRows property
|
|
13
|
+
returns them as a sorted array.
|
|
14
|
+
|
|
15
|
+
Prerequisite:
|
|
16
|
+
grid.isRowNumbersVisible = true
|
|
17
|
+
|
|
18
|
+
Interaction patterns:
|
|
19
|
+
Click row number - Select single row (replaces previous selection)
|
|
20
|
+
Ctrl+Click row number - Toggle row in/out of selection (non-contiguous)
|
|
21
|
+
Shift+Click row number - Select range from last selected to clicked row
|
|
22
|
+
Click+Drag row numbers - Drag to select contiguous range (5px threshold)
|
|
23
|
+
Escape - Clear row selection
|
|
24
|
+
Click on data cell - Clears row selection (only row numbers select)
|
|
25
|
+
Click row number header - Select all cells (selectAll, creates cell range)
|
|
26
|
+
|
|
27
|
+
Properties (on grid element):
|
|
28
|
+
selectedRows - number[] (read-only, sorted ascending)
|
|
29
|
+
|
|
30
|
+
Methods:
|
|
31
|
+
selectRow(rowIndex, mode?)
|
|
32
|
+
mode: 'replace' (default) - clear previous, select this row
|
|
33
|
+
mode: 'toggle' - add/remove this row from selection
|
|
34
|
+
mode: 'range' - select from last selected row to this row
|
|
35
|
+
|
|
36
|
+
selectRowRange(fromIndex, toIndex)
|
|
37
|
+
Selects all rows between fromIndex and toIndex (inclusive, order agnostic).
|
|
38
|
+
|
|
39
|
+
clearSelection()
|
|
40
|
+
Clears all selected rows.
|
|
41
|
+
|
|
42
|
+
isRowSelected(rowIndex)
|
|
43
|
+
Returns boolean.
|
|
44
|
+
|
|
45
|
+
getSelectedRowsData()
|
|
46
|
+
Returns T[] - the actual data objects for selected rows.
|
|
47
|
+
|
|
48
|
+
copySelectedRowsToClipboard()
|
|
49
|
+
Returns Promise<boolean>. Copies selected rows as TSV (tab-separated).
|
|
50
|
+
Respects shouldCopyWithHeaders. Uses raw values (no beforeCopyCallback).
|
|
51
|
+
|
|
52
|
+
Example:
|
|
53
|
+
grid.isRowNumbersVisible = true
|
|
54
|
+
grid.selectRow(0, 'replace')
|
|
55
|
+
grid.selectRow(2, 'toggle')
|
|
56
|
+
grid.selectRow(5, 'range')
|
|
57
|
+
console.log(grid.selectedRows) // [0, 2, 3, 4, 5]
|
|
58
|
+
console.log(grid.getSelectedRowsData()) // array of row objects
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
CELL RANGE SELECTION
|
|
62
|
+
--------------------
|
|
63
|
+
Rectangular cell ranges are selected by click+drag or shift+click on data cells.
|
|
64
|
+
The behavior depends on cellSelectionMode.
|
|
65
|
+
|
|
66
|
+
Property:
|
|
67
|
+
cellSelectionMode - CellSelectionMode
|
|
68
|
+
'disabled' - No cell range selection
|
|
69
|
+
'click' - Click+drag on cells to select range (default, used in excel/read-only modes)
|
|
70
|
+
'shift' - Shift+click to select range; plain click edits (used in input-matrix mode)
|
|
71
|
+
|
|
72
|
+
selectedCellRange - CellRange | null (read-only)
|
|
73
|
+
shouldCopyWithHeaders - boolean (default false). Include column headers in clipboard copy.
|
|
74
|
+
|
|
75
|
+
CellRange type:
|
|
76
|
+
{
|
|
77
|
+
startRowIndex: number
|
|
78
|
+
startColIndex: number
|
|
79
|
+
endRowIndex: number
|
|
80
|
+
endColIndex: number
|
|
81
|
+
startField: string
|
|
82
|
+
endField: string
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
Interaction patterns (cellSelectionMode = 'click'):
|
|
86
|
+
Click+drag on cells - Select rectangular range (5px drag threshold)
|
|
87
|
+
Escape during drag - Cancel selection, restore focus to start cell
|
|
88
|
+
Return to start cell - Cancel selection, restore focus
|
|
89
|
+
Escape after selection - Clear cell selection
|
|
90
|
+
Ctrl+C - Copy selected cells to clipboard
|
|
91
|
+
|
|
92
|
+
Interaction patterns (cellSelectionMode = 'shift'):
|
|
93
|
+
Shift+Click - Select range from last clicked cell to current
|
|
94
|
+
Escape - Clear cell selection
|
|
95
|
+
|
|
96
|
+
Methods:
|
|
97
|
+
selectCellRange(range)
|
|
98
|
+
Programmatically select a CellRange. Clears row/column selection.
|
|
99
|
+
Fires oncellselectionchange callback.
|
|
100
|
+
|
|
101
|
+
clearCellSelection()
|
|
102
|
+
Clears cell range selection. Fires oncellselectionchange with null range.
|
|
103
|
+
|
|
104
|
+
getSelectedCells()
|
|
105
|
+
Returns Array<{ row: T, rowIndex: number, colIndex: number, field: string, value: unknown }>
|
|
106
|
+
Iterates through all cells in the rectangular range.
|
|
107
|
+
|
|
108
|
+
copyCellSelectionToClipboard()
|
|
109
|
+
Returns Promise<boolean>. Copies cells as TSV. Respects shouldCopyWithHeaders.
|
|
110
|
+
|
|
111
|
+
selectAll()
|
|
112
|
+
Selects all cells (entire visible data range as one CellRange).
|
|
113
|
+
Triggered by clicking the row number header cell (#).
|
|
114
|
+
|
|
115
|
+
Callback:
|
|
116
|
+
oncellselectionchange - (detail: { range: CellRange | null, cellCount: number }) => void
|
|
117
|
+
Fires when cell selection changes (created or cleared).
|
|
118
|
+
|
|
119
|
+
Example:
|
|
120
|
+
grid.cellSelectionMode = 'click'
|
|
121
|
+
|
|
122
|
+
grid.selectCellRange({
|
|
123
|
+
startRowIndex: 0, startColIndex: 1,
|
|
124
|
+
endRowIndex: 3, endColIndex: 4,
|
|
125
|
+
startField: 'name', endField: 'status'
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
grid.oncellselectionchange = (detail) => {
|
|
129
|
+
console.log('Selected', detail.cellCount, 'cells')
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const cells = grid.getSelectedCells()
|
|
133
|
+
// [{ row: {...}, rowIndex: 0, colIndex: 1, field: 'name', value: 'Alice' }, ...]
|
|
134
|
+
|
|
135
|
+
Grid mode defaults for cellSelectionMode:
|
|
136
|
+
mode: 'read-only' -> cellSelectionMode: 'click'
|
|
137
|
+
mode: 'excel' -> cellSelectionMode: 'click'
|
|
138
|
+
mode: 'input-matrix' -> cellSelectionMode: 'shift'
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
COLUMN SELECTION
|
|
142
|
+
----------------
|
|
143
|
+
Columns are selected by clicking column headers. Supports multi-column selection
|
|
144
|
+
with Ctrl and Shift modifiers, plus drag-to-select.
|
|
145
|
+
|
|
146
|
+
When isColumnReorderAllowed is true:
|
|
147
|
+
Plain click on header - Starts column reorder drag (NOT selection)
|
|
148
|
+
Ctrl+Click on header - Toggle column selection
|
|
149
|
+
Shift+Click on header - Range selection from last to current
|
|
150
|
+
Shift+Drag on header - Drag to select range of columns
|
|
151
|
+
|
|
152
|
+
When isColumnReorderAllowed is false:
|
|
153
|
+
Click on header - Select single column (replace)
|
|
154
|
+
Ctrl+Click on header - Toggle column selection
|
|
155
|
+
Shift+Click on header - Range selection from last to current
|
|
156
|
+
Click+Drag on header - Drag to select range of columns
|
|
157
|
+
|
|
158
|
+
Selecting columns clears row selection and cell range selection.
|
|
159
|
+
|
|
160
|
+
Properties (on grid instance):
|
|
161
|
+
selectedColumns - number[] (read-only, sorted ascending, visual indices)
|
|
162
|
+
|
|
163
|
+
Methods:
|
|
164
|
+
selectColumn(colIndex, mode?)
|
|
165
|
+
mode: 'replace' (default), 'toggle', 'range'
|
|
166
|
+
|
|
167
|
+
selectColumnRange(fromIndex, toIndex)
|
|
168
|
+
Selects all columns between fromIndex and toIndex (inclusive).
|
|
169
|
+
|
|
170
|
+
clearColumnSelection()
|
|
171
|
+
Clears all selected columns.
|
|
172
|
+
|
|
173
|
+
isColumnSelected(colIndex)
|
|
174
|
+
Returns boolean.
|
|
175
|
+
|
|
176
|
+
copySelectedColumnsToClipboard()
|
|
177
|
+
Returns Promise<boolean>. Copies all rows for selected columns as TSV.
|
|
178
|
+
Respects shouldCopyWithHeaders.
|
|
179
|
+
|
|
180
|
+
Visual feedback:
|
|
181
|
+
Selected column headers get the CSS class: wg__header--selected
|
|
182
|
+
Selected column cells get the CSS class: wg__cell--column-selected
|
|
183
|
+
A border overlay is drawn around contiguous column segments.
|
|
184
|
+
|
|
185
|
+
Example:
|
|
186
|
+
grid.isColumnReorderAllowed = false
|
|
187
|
+
// User clicks "Name" header -> column selected
|
|
188
|
+
// User Shift+clicks "Email" header -> range from Name to Email selected
|
|
189
|
+
console.log(grid.selectedColumns) // [1, 2, 3] (visual indices)
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
ROW FOCUS
|
|
193
|
+
---------
|
|
194
|
+
Row focus tracks which row the user most recently interacted with via a data
|
|
195
|
+
cell click or programmatic focus. It is separate from row selection.
|
|
196
|
+
|
|
197
|
+
Key distinction: clicking a ROW NUMBER selects the row but does NOT focus it.
|
|
198
|
+
Clicking a DATA CELL focuses the row (and clears row selection).
|
|
199
|
+
|
|
200
|
+
Property:
|
|
201
|
+
focusedRowIndex - number | null (readable/writable)
|
|
202
|
+
Set to a number to programmatically focus a row.
|
|
203
|
+
Set to null to clear focus.
|
|
204
|
+
|
|
205
|
+
Callback:
|
|
206
|
+
onrowfocus - (detail: RowFocusDetail<T>) => void
|
|
207
|
+
Fires when a different row is focused.
|
|
208
|
+
|
|
209
|
+
RowFocusDetail type:
|
|
210
|
+
{
|
|
211
|
+
rowIndex: number
|
|
212
|
+
row: T
|
|
213
|
+
previousRowIndex: number | null
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
Master/detail pattern:
|
|
217
|
+
Use onrowfocus to update a detail panel when the user clicks different rows.
|
|
218
|
+
|
|
219
|
+
grid.onrowfocus = (detail) => {
|
|
220
|
+
detailPanel.innerHTML = renderDetail(detail.row)
|
|
221
|
+
console.log('Moved from row', detail.previousRowIndex, 'to', detail.rowIndex)
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
Behavior:
|
|
225
|
+
- Click on data cell -> sets focusedRowIndex, fires onrowfocus
|
|
226
|
+
- Click on row number -> selects row but does NOT change focus
|
|
227
|
+
- Click outside grid -> clears focus (focusedRowIndex becomes null)
|
|
228
|
+
- Starting cell range drag -> clears focus
|
|
229
|
+
- Programmatic: grid.focusedRowIndex = 3 (focuses row 3, fires onrowfocus)
|
|
230
|
+
- Programmatic: grid.focusedRowIndex = null (clears focus)
|
|
231
|
+
|
|
232
|
+
The focused row gets CSS class: wg__row--focused
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
CLIPBOARD
|
|
236
|
+
---------
|
|
237
|
+
All clipboard operations produce TSV (tab-separated values) format, which is
|
|
238
|
+
compatible with Excel, Google Sheets, and other spreadsheet applications.
|
|
239
|
+
|
|
240
|
+
Format:
|
|
241
|
+
Columns separated by tab (\t), rows separated by newline (\n).
|
|
242
|
+
If shouldCopyWithHeaders is true, the first line contains column titles.
|
|
243
|
+
|
|
244
|
+
Built-in Ctrl+C behavior:
|
|
245
|
+
When a selection exists and the container is focused, Ctrl+C copies:
|
|
246
|
+
1. Cell range selection -> copyCellSelectionToClipboard()
|
|
247
|
+
2. Column selection -> copySelectedColumnsToClipboard()
|
|
248
|
+
3. Row selection -> copySelectedRowsToClipboard()
|
|
249
|
+
4. Single focused cell -> copies that cell's value
|
|
250
|
+
|
|
251
|
+
Priority order: cell range > column > row > single cell.
|
|
252
|
+
|
|
253
|
+
shouldCopyWithHeaders property:
|
|
254
|
+
grid.shouldCopyWithHeaders = true
|
|
255
|
+
|
|
256
|
+
When true, the first TSV row is column titles. Applies to all copy methods:
|
|
257
|
+
copySelectedRowsToClipboard(), copyCellSelectionToClipboard(),
|
|
258
|
+
copySelectedColumnsToClipboard().
|
|
259
|
+
|
|
260
|
+
Example output (shouldCopyWithHeaders = true):
|
|
261
|
+
Name\tAge\tEmail
|
|
262
|
+
Alice\t28\talice@example.com
|
|
263
|
+
Bob\t34\tbob@example.com
|
|
264
|
+
|
|
265
|
+
Example output (shouldCopyWithHeaders = false):
|
|
266
|
+
Alice\t28\talice@example.com
|
|
267
|
+
Bob\t34\tbob@example.com
|
|
268
|
+
|
|
269
|
+
Per-column beforeCopyCallback:
|
|
270
|
+
Transforms a cell value before it is written to the clipboard.
|
|
271
|
+
Currently applied only when copying a SINGLE focused cell via Ctrl+C.
|
|
272
|
+
NOT applied by the bulk copy methods (copySelectedRowsToClipboard, etc.).
|
|
273
|
+
|
|
274
|
+
column definition:
|
|
275
|
+
{ field: 'price', beforeCopyCallback: (value, row) => '$' + value }
|
|
276
|
+
|
|
277
|
+
Per-column beforePasteCallback:
|
|
278
|
+
Processes a pasted value before applying it to a cell.
|
|
279
|
+
|
|
280
|
+
column definition:
|
|
281
|
+
{ field: 'price', beforePasteCallback: (value, row) => parseFloat(value) }
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
RANGE SHORTCUTS
|
|
285
|
+
---------------
|
|
286
|
+
Range shortcuts are keyboard shortcuts that operate on multiple selected rows
|
|
287
|
+
or a cell range. They work with both row selection and cell range selection.
|
|
288
|
+
|
|
289
|
+
Property:
|
|
290
|
+
rangeShortcuts - RangeShortcut<T>[]
|
|
291
|
+
|
|
292
|
+
RangeShortcut type:
|
|
293
|
+
{
|
|
294
|
+
key: string // e.g., "Delete", "Ctrl+E", "Shift+F2"
|
|
295
|
+
id: string // Unique identifier
|
|
296
|
+
label: string // Display label for shortcuts help overlay
|
|
297
|
+
action: (ctx: RangeShortcutContext<T>) => void | Promise<void>
|
|
298
|
+
disabled?: boolean | ((ctx: RangeShortcutContext<T>) => boolean)
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
RangeShortcutContext type:
|
|
302
|
+
{
|
|
303
|
+
rows: T[] // Selected rows data (for row selection)
|
|
304
|
+
rowIndices: number[] // Selected row indices (sorted ascending)
|
|
305
|
+
cellRange?: CellRange // Present when cell range is selected
|
|
306
|
+
cells?: Array<{ // Present when cell range is selected
|
|
307
|
+
row: T
|
|
308
|
+
rowIndex: number
|
|
309
|
+
colIndex: number
|
|
310
|
+
field: string
|
|
311
|
+
value: unknown
|
|
312
|
+
}>
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
When triggered from row selection:
|
|
316
|
+
ctx.rows and ctx.rowIndices are populated.
|
|
317
|
+
ctx.cellRange and ctx.cells are undefined.
|
|
318
|
+
|
|
319
|
+
When triggered from cell range selection:
|
|
320
|
+
ctx.cellRange and ctx.cells are populated.
|
|
321
|
+
ctx.rows is empty array, ctx.rowIndices is empty array.
|
|
322
|
+
|
|
323
|
+
Range shortcuts are checked in TWO places:
|
|
324
|
+
1. Container keydown (when row/column/cell selection is active and container
|
|
325
|
+
is focused -- i.e., no cell is focused in navigate mode)
|
|
326
|
+
2. Cell keydown (when rows are selected while a cell has focus)
|
|
327
|
+
|
|
328
|
+
Built-in keyboard shortcuts (always active, no configuration needed):
|
|
329
|
+
Escape - Clear selection (cell range > column > row priority)
|
|
330
|
+
Ctrl+C - Copy selection to clipboard
|
|
331
|
+
|
|
332
|
+
Example:
|
|
333
|
+
grid.rangeShortcuts = [
|
|
334
|
+
{
|
|
335
|
+
key: 'Delete',
|
|
336
|
+
id: 'delete-rows',
|
|
337
|
+
label: 'Delete selected rows',
|
|
338
|
+
action: (ctx) => {
|
|
339
|
+
// Remove selected rows from data
|
|
340
|
+
grid.items = grid.items.filter((_, i) => !ctx.rowIndices.includes(i))
|
|
341
|
+
}
|
|
342
|
+
},
|
|
343
|
+
{
|
|
344
|
+
key: 'Ctrl+E',
|
|
345
|
+
id: 'export-selection',
|
|
346
|
+
label: 'Export selection',
|
|
347
|
+
action: (ctx) => {
|
|
348
|
+
if (ctx.cellRange) {
|
|
349
|
+
// Export cell range
|
|
350
|
+
const values = ctx.cells.map(c => c.value)
|
|
351
|
+
exportData(values)
|
|
352
|
+
} else {
|
|
353
|
+
// Export selected rows
|
|
354
|
+
exportData(ctx.rows)
|
|
355
|
+
}
|
|
356
|
+
},
|
|
357
|
+
disabled: (ctx) => ctx.rows.length === 0 && !ctx.cellRange
|
|
358
|
+
}
|
|
359
|
+
]
|
|
360
|
+
|
|
361
|
+
To show a help overlay listing shortcuts:
|
|
362
|
+
grid.isShortcutsHelpVisible = true
|
|
363
|
+
grid.shortcutsHelpPosition = 'top-right' // or 'top-left'
|
|
364
|
+
|
|
365
|
+
|
|
366
|
+
SELECTION MUTUAL EXCLUSIVITY
|
|
367
|
+
-----------------------------
|
|
368
|
+
Row selection, cell range selection, and column selection are mutually exclusive.
|
|
369
|
+
Activating one clears the others automatically:
|
|
370
|
+
|
|
371
|
+
selectRow() -> clears cell range and column selection
|
|
372
|
+
selectCellRange() -> clears row and column selection
|
|
373
|
+
selectColumn() -> clears row and cell range selection
|
|
374
|
+
|
|
375
|
+
Row focus (focusedRowIndex) is independent and can coexist with row selection,
|
|
376
|
+
but is cleared when starting a cell range drag.
|
|
377
|
+
|
|
378
|
+
The selectAll() method creates a cell range (not row selection), spanning all
|
|
379
|
+
visible rows and columns. It is triggered by clicking the row number header (#).
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
VISUAL FEEDBACK
|
|
383
|
+
---------------
|
|
384
|
+
CSS classes applied during selection:
|
|
385
|
+
|
|
386
|
+
Row selection:
|
|
387
|
+
.wg__row--selected - Applied to selected <tr> elements
|
|
388
|
+
.wg__row-selection-border - Overlay border around contiguous segments
|
|
389
|
+
.wg--selecting - On container during row drag selection
|
|
390
|
+
|
|
391
|
+
Cell range selection:
|
|
392
|
+
.wg__cell--in-range - Applied to cells within the range
|
|
393
|
+
.wg__cell-range-border - Overlay border around the range
|
|
394
|
+
.wg--selecting-cells - On container during cell drag selection
|
|
395
|
+
|
|
396
|
+
Column selection:
|
|
397
|
+
.wg__header--selected - Applied to selected column headers
|
|
398
|
+
.wg__cell--column-selected - Applied to cells in selected columns
|
|
399
|
+
.wg__column-selection-border - Overlay border around contiguous segments
|
|
400
|
+
.wg--selecting-columns - On container during column drag selection
|
|
401
|
+
|
|
402
|
+
Row focus:
|
|
403
|
+
.wg__row--focused - Applied to the focused row
|