@keenmate/web-grid 1.0.0-rc15 → 1.0.0
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 +1 -3
- 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/web-grid.js +747 -724
- package/dist/web-grid.umd.js +51 -50
- package/package.json +2 -1
- package/src/css/_cell-selection.css +5 -3
- 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 +2 -0
|
@@ -0,0 +1,498 @@
|
|
|
1
|
+
TYPESCRIPT TYPES
|
|
2
|
+
================
|
|
3
|
+
@keenmate/web-grid - Key exported types reference.
|
|
4
|
+
|
|
5
|
+
All types are exported from the package entry point:
|
|
6
|
+
import type { Column, EditorOptions, SortState } from '@keenmate/web-grid'
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
----------------------------------------------------------------------
|
|
10
|
+
COLUMN<T>
|
|
11
|
+
----------------------------------------------------------------------
|
|
12
|
+
Defines how a column renders, edits, and behaves.
|
|
13
|
+
|
|
14
|
+
type Column<T> = {
|
|
15
|
+
field: keyof T | string REQUIRED. Data field name.
|
|
16
|
+
title: string Column header text.
|
|
17
|
+
headerInfo?: string Info tooltip (shows i icon in header).
|
|
18
|
+
width?: string e.g., '100px', '20%'
|
|
19
|
+
minWidth?: string Minimum width during resize.
|
|
20
|
+
maxWidth?: string Maximum width during resize.
|
|
21
|
+
horizontalAlign?: 'left' | 'center' | 'right' | 'justify'
|
|
22
|
+
verticalAlign?: 'top' | 'middle' | 'bottom'
|
|
23
|
+
headerHorizontalAlign?: 'left' | 'center' | 'right' | 'justify'
|
|
24
|
+
headerVerticalAlign?: 'top' | 'middle' | 'bottom'
|
|
25
|
+
textOverflow?: 'wrap' | 'ellipsis'
|
|
26
|
+
maxLines?: number Line-clamp when textOverflow is 'wrap'.
|
|
27
|
+
cellClass?: string Static CSS class for all cells.
|
|
28
|
+
cellClassCallback?: (value: unknown, row: T) => string | null
|
|
29
|
+
formatCallback?: (value: unknown, row: T) => string
|
|
30
|
+
templateCallback?: (row: T) => string
|
|
31
|
+
renderCallback?: (row: T, element: HTMLElement) => void
|
|
32
|
+
isEditable?: boolean
|
|
33
|
+
editor?: EditorType
|
|
34
|
+
editTrigger?: EditTrigger
|
|
35
|
+
editorOptions?: EditorOptions<T>
|
|
36
|
+
dropdownToggleVisibility?: ToggleVisibility
|
|
37
|
+
shouldOpenDropdownOnEnter?: boolean
|
|
38
|
+
cellEditCallback?: (context: CustomEditorContext<T>) => void
|
|
39
|
+
isEditButtonVisible?: boolean
|
|
40
|
+
validateCallback?: (value: unknown, row: T) => string | null | Promise<string | null>
|
|
41
|
+
beforeCommitCallback?: (context: BeforeCommitContext<T>) => BeforeCommitResult | Promise<BeforeCommitResult>
|
|
42
|
+
validationTooltipCallback?: (context: ValidationTooltipContext<T>) => string | null
|
|
43
|
+
tooltipMember?: string
|
|
44
|
+
tooltipCallback?: (value: unknown, row: T) => string | null
|
|
45
|
+
beforeCopyCallback?: (value: unknown, row: T) => string
|
|
46
|
+
beforePasteCallback?: (value: string, row: T) => unknown
|
|
47
|
+
isFrozen?: boolean
|
|
48
|
+
isResizable?: boolean Default: true
|
|
49
|
+
isMovable?: boolean Default: true
|
|
50
|
+
isHidden?: boolean
|
|
51
|
+
isSortable?: boolean
|
|
52
|
+
isFilterable?: boolean
|
|
53
|
+
fillDirection?: FillDirection
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
----------------------------------------------------------------------
|
|
58
|
+
EDITOROPTIONS<T>
|
|
59
|
+
----------------------------------------------------------------------
|
|
60
|
+
Configuration for all editor types. Properties are grouped by editor type.
|
|
61
|
+
|
|
62
|
+
type EditorOptions<T> = {
|
|
63
|
+
// Shared (select/combobox/autocomplete)
|
|
64
|
+
options?: EditorOption[]
|
|
65
|
+
loadOptions?: (row: T, field: string) => Promise<EditorOption[]>
|
|
66
|
+
optionsLoadTrigger?: OptionsLoadTrigger
|
|
67
|
+
valueMember?: string
|
|
68
|
+
displayMember?: string
|
|
69
|
+
searchMember?: string
|
|
70
|
+
iconMember?: string
|
|
71
|
+
subtitleMember?: string
|
|
72
|
+
disabledMember?: string
|
|
73
|
+
groupMember?: string
|
|
74
|
+
getValueCallback?: (option: EditorOption) => string | number
|
|
75
|
+
getDisplayCallback?: (option: EditorOption) => string
|
|
76
|
+
getSearchCallback?: (option: EditorOption) => string
|
|
77
|
+
getIconCallback?: (option: EditorOption) => string
|
|
78
|
+
getSubtitleCallback?: (option: EditorOption) => string
|
|
79
|
+
getDisabledCallback?: (option: EditorOption) => boolean
|
|
80
|
+
getGroupCallback?: (option: EditorOption) => string
|
|
81
|
+
renderOptionCallback?: (option: EditorOption, context: OptionRenderContext) => string
|
|
82
|
+
onselect?: (option: EditorOption, row: T) => void
|
|
83
|
+
allowEmpty?: boolean
|
|
84
|
+
emptyLabel?: string
|
|
85
|
+
noOptionsText?: string
|
|
86
|
+
searchingText?: string
|
|
87
|
+
dropdownMinWidth?: string
|
|
88
|
+
placeholder?: string
|
|
89
|
+
// Text
|
|
90
|
+
maxLength?: number
|
|
91
|
+
pattern?: string
|
|
92
|
+
inputMode?: 'text' | 'numeric' | 'email' | 'tel' | 'url'
|
|
93
|
+
editStartSelection?: EditStartSelection
|
|
94
|
+
// Number
|
|
95
|
+
min?: number
|
|
96
|
+
max?: number
|
|
97
|
+
step?: number
|
|
98
|
+
decimalPlaces?: number
|
|
99
|
+
allowNegative?: boolean
|
|
100
|
+
// Checkbox
|
|
101
|
+
trueValue?: unknown
|
|
102
|
+
falseValue?: unknown
|
|
103
|
+
// Date
|
|
104
|
+
minDate?: Date | string
|
|
105
|
+
maxDate?: Date | string
|
|
106
|
+
dateFormat?: string
|
|
107
|
+
outputFormat?: DateOutputFormat
|
|
108
|
+
// Autocomplete
|
|
109
|
+
initialOptions?: EditorOption[]
|
|
110
|
+
searchCallback?: (query: string, row: T, signal?: AbortSignal) => Promise<EditorOption[]>
|
|
111
|
+
minSearchLength?: number
|
|
112
|
+
debounceMs?: number
|
|
113
|
+
multiple?: boolean
|
|
114
|
+
maxSelections?: number
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
----------------------------------------------------------------------
|
|
119
|
+
SIMPLE TYPE ALIASES
|
|
120
|
+
----------------------------------------------------------------------
|
|
121
|
+
|
|
122
|
+
type EditorType = 'text' | 'number' | 'checkbox' | 'select' | 'combobox' | 'date' | 'autocomplete' | 'custom'
|
|
123
|
+
|
|
124
|
+
type EditTrigger = 'click' | 'dblclick' | 'button' | 'always' | 'navigate'
|
|
125
|
+
|
|
126
|
+
type GridMode = 'read-only' | 'excel' | 'input-matrix'
|
|
127
|
+
|
|
128
|
+
type SortMode = 'none' | 'single' | 'multi'
|
|
129
|
+
|
|
130
|
+
type SortDirection = 'asc' | 'desc'
|
|
131
|
+
|
|
132
|
+
type CellSelectionMode = 'disabled' | 'click' | 'shift'
|
|
133
|
+
|
|
134
|
+
type ToggleVisibility = 'always' | 'on-focus'
|
|
135
|
+
|
|
136
|
+
type OptionsLoadTrigger = 'immediate' | 'oneditstart' | 'ondropdownopen'
|
|
137
|
+
|
|
138
|
+
type DateOutputFormat = 'date' | 'iso' | 'timestamp'
|
|
139
|
+
|
|
140
|
+
type EditStartSelection = 'mousePosition' | 'selectAll' | 'cursorAtStart' | 'cursorAtEnd'
|
|
141
|
+
|
|
142
|
+
type FillDirection = 'vertical' | 'all'
|
|
143
|
+
|
|
144
|
+
type ToolbarPosition = 'auto' | 'left' | 'right' | 'top' | 'inline'
|
|
145
|
+
|
|
146
|
+
type LockedRowEditBehavior = 'block' | 'allow' | 'callback'
|
|
147
|
+
|
|
148
|
+
type DataRequestTrigger = 'sort' | 'page' | 'pageSize' | 'init' | 'loadMore'
|
|
149
|
+
|
|
150
|
+
type DataRequestMode = 'replace' | 'append'
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
----------------------------------------------------------------------
|
|
154
|
+
SORTSTATE
|
|
155
|
+
----------------------------------------------------------------------
|
|
156
|
+
|
|
157
|
+
type SortState = {
|
|
158
|
+
column: string
|
|
159
|
+
direction: SortDirection 'asc' | 'desc'
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
----------------------------------------------------------------------
|
|
164
|
+
ROWTOOLBARCONFIG<T>
|
|
165
|
+
----------------------------------------------------------------------
|
|
166
|
+
|
|
167
|
+
type RowToolbarConfig<T> = PredefinedToolbarItemType | RowToolbarItem<T>
|
|
168
|
+
|
|
169
|
+
type PredefinedToolbarItemType = 'add' | 'delete' | 'duplicate' | 'moveUp' | 'moveDown'
|
|
170
|
+
|
|
171
|
+
type RowToolbarItem<T> = {
|
|
172
|
+
id: string
|
|
173
|
+
icon: string
|
|
174
|
+
title: string
|
|
175
|
+
label?: string
|
|
176
|
+
row?: number Row in multi-row toolbar (1 = closest)
|
|
177
|
+
group?: number Group number for divider placement
|
|
178
|
+
minWidth?: string
|
|
179
|
+
type?: PredefinedToolbarItemType
|
|
180
|
+
danger?: boolean
|
|
181
|
+
disabled?: boolean | ((row: T, rowIndex: number) => boolean)
|
|
182
|
+
hidden?: boolean | ((row: T, rowIndex: number) => boolean)
|
|
183
|
+
tooltip?: ToolbarTooltip
|
|
184
|
+
tooltipCallback?: (row: T, rowIndex: number) => string
|
|
185
|
+
onclick?: (detail: { row: T, rowIndex: number }) => void | Promise<void>
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
----------------------------------------------------------------------
|
|
190
|
+
CONTEXTMENUITEM<T>
|
|
191
|
+
----------------------------------------------------------------------
|
|
192
|
+
|
|
193
|
+
type ContextMenuItem<T> = {
|
|
194
|
+
id: string
|
|
195
|
+
label: string | ((context: ContextMenuContext<T>) => string)
|
|
196
|
+
icon?: string | ((context: ContextMenuContext<T>) => string)
|
|
197
|
+
shortcut?: string
|
|
198
|
+
disabled?: boolean | ((context: ContextMenuContext<T>) => boolean)
|
|
199
|
+
visible?: boolean | ((context: ContextMenuContext<T>) => boolean)
|
|
200
|
+
danger?: boolean
|
|
201
|
+
dividerBefore?: boolean
|
|
202
|
+
onclick?: (context: ContextMenuContext<T>) => void | Promise<void>
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
type ContextMenuContext<T> = {
|
|
206
|
+
row: T
|
|
207
|
+
rowIndex: number
|
|
208
|
+
colIndex: number
|
|
209
|
+
column: Column<T>
|
|
210
|
+
cellValue: unknown
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
----------------------------------------------------------------------
|
|
215
|
+
HEADERMENUCONFIG<T>
|
|
216
|
+
----------------------------------------------------------------------
|
|
217
|
+
|
|
218
|
+
type HeaderMenuConfig<T> = PredefinedHeaderMenuItemType | HeaderMenuItem<T>
|
|
219
|
+
|
|
220
|
+
type PredefinedHeaderMenuItemType = 'sortAsc' | 'sortDesc' | 'clearSort' | 'hideColumn' | 'freezeColumn' | 'unfreezeColumn' | 'columnVisibility'
|
|
221
|
+
|
|
222
|
+
type HeaderMenuItem<T> = {
|
|
223
|
+
id: string
|
|
224
|
+
label: string | ((context: HeaderMenuContext<T>) => string)
|
|
225
|
+
icon?: string | ((context: HeaderMenuContext<T>) => string)
|
|
226
|
+
shortcut?: string
|
|
227
|
+
disabled?: boolean | ((context: HeaderMenuContext<T>) => boolean)
|
|
228
|
+
visible?: boolean | ((context: HeaderMenuContext<T>) => boolean)
|
|
229
|
+
danger?: boolean
|
|
230
|
+
dividerBefore?: boolean
|
|
231
|
+
onclick?: (context: HeaderMenuContext<T>) => void | Promise<void>
|
|
232
|
+
children?: HeaderMenuItem<T>[]
|
|
233
|
+
submenu?: (context: HeaderMenuContext<T>) => HeaderMenuItem<T>[]
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
type HeaderMenuContext<T> = {
|
|
237
|
+
column: Column<T>
|
|
238
|
+
field: string
|
|
239
|
+
columnIndex: number
|
|
240
|
+
sortDirection: 'asc' | 'desc' | null
|
|
241
|
+
isFrozen: boolean
|
|
242
|
+
allColumns: Column<T>[]
|
|
243
|
+
labels: GridLabels
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
----------------------------------------------------------------------
|
|
248
|
+
ROWSHORTCUT<T> AND RANGESHORTCUT<T>
|
|
249
|
+
----------------------------------------------------------------------
|
|
250
|
+
|
|
251
|
+
type RowShortcut<T> = {
|
|
252
|
+
key: string e.g., 'Delete', 'Ctrl+D', 'F3'
|
|
253
|
+
id: string
|
|
254
|
+
label: string
|
|
255
|
+
action: (ctx: ShortcutContext<T>) => void | Promise<void>
|
|
256
|
+
disabled?: boolean | ((ctx: ShortcutContext<T>) => boolean)
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
type ShortcutContext<T> = {
|
|
260
|
+
row: T
|
|
261
|
+
rowIndex: number
|
|
262
|
+
colIndex: number
|
|
263
|
+
column: Column<T>
|
|
264
|
+
cellValue: unknown
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
type RangeShortcut<T> = {
|
|
268
|
+
key: string
|
|
269
|
+
id: string
|
|
270
|
+
label: string
|
|
271
|
+
action: (ctx: RangeShortcutContext<T>) => void | Promise<void>
|
|
272
|
+
disabled?: boolean | ((ctx: RangeShortcutContext<T>) => boolean)
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
type RangeShortcutContext<T> = {
|
|
276
|
+
rows: T[]
|
|
277
|
+
rowIndices: number[]
|
|
278
|
+
cellRange?: CellRange
|
|
279
|
+
cells?: Array<{ row: T, rowIndex: number, colIndex: number, field: string, value: unknown }>
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
----------------------------------------------------------------------
|
|
284
|
+
FILLDRAGDETAIL
|
|
285
|
+
----------------------------------------------------------------------
|
|
286
|
+
|
|
287
|
+
type FillDragDetail = {
|
|
288
|
+
sourceCell: {
|
|
289
|
+
rowIndex: number
|
|
290
|
+
colIndex: number
|
|
291
|
+
field: string
|
|
292
|
+
value: unknown
|
|
293
|
+
}
|
|
294
|
+
targetCells: Array<{
|
|
295
|
+
rowIndex: number
|
|
296
|
+
colIndex: number
|
|
297
|
+
field: string
|
|
298
|
+
}>
|
|
299
|
+
direction: 'up' | 'down' | 'left' | 'right'
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
----------------------------------------------------------------------
|
|
304
|
+
ROWLOCKINGOPTIONS<T> AND ROWLOCKINFO
|
|
305
|
+
----------------------------------------------------------------------
|
|
306
|
+
|
|
307
|
+
type RowLockingOptions<T> = {
|
|
308
|
+
lockedMember?: keyof T
|
|
309
|
+
lockInfoMember?: keyof T
|
|
310
|
+
isLockedCallback?: (row: T, rowIndex: number) => boolean
|
|
311
|
+
getLockInfoCallback?: (row: T, rowIndex: number) => RowLockInfo | null
|
|
312
|
+
lockedEditBehavior?: LockedRowEditBehavior
|
|
313
|
+
canEditLockedCallback?: (row: T, lockInfo: RowLockInfo) => boolean
|
|
314
|
+
lockTooltipCallback?: (lockInfo: RowLockInfo, row: T) => string | null
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
type RowLockInfo = {
|
|
318
|
+
isLocked: boolean
|
|
319
|
+
lockedBy?: string
|
|
320
|
+
lockedAt?: Date | string
|
|
321
|
+
reason?: string
|
|
322
|
+
[key: string]: unknown
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
type RowLockChangeDetail<T> = {
|
|
326
|
+
rowId: unknown
|
|
327
|
+
row: T | null
|
|
328
|
+
rowIndex: number
|
|
329
|
+
lockInfo: RowLockInfo | null
|
|
330
|
+
source: 'property' | 'callback' | 'external'
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
----------------------------------------------------------------------
|
|
335
|
+
BEFORECOMMITCONTEXT<T> AND BEFORECOMMITRESULT
|
|
336
|
+
----------------------------------------------------------------------
|
|
337
|
+
|
|
338
|
+
type BeforeCommitContext<T> = {
|
|
339
|
+
value: unknown
|
|
340
|
+
oldValue: unknown
|
|
341
|
+
row: T
|
|
342
|
+
rowIndex: number
|
|
343
|
+
field: string
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
type BeforeCommitResult = ValidationResult | boolean | string | null | undefined
|
|
347
|
+
|
|
348
|
+
type ValidationResult = {
|
|
349
|
+
valid: boolean
|
|
350
|
+
message?: string
|
|
351
|
+
transformedValue?: unknown
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
|
|
355
|
+
----------------------------------------------------------------------
|
|
356
|
+
ROWCHANGEDETAIL<T>
|
|
357
|
+
----------------------------------------------------------------------
|
|
358
|
+
|
|
359
|
+
type RowChangeDetail<T> = {
|
|
360
|
+
row: T Original row (unchanged)
|
|
361
|
+
draftRow: T Draft with user's changes (including invalid)
|
|
362
|
+
rowIndex: number
|
|
363
|
+
field: string
|
|
364
|
+
oldValue: unknown
|
|
365
|
+
newValue: unknown
|
|
366
|
+
isValid: boolean
|
|
367
|
+
validationError?: string | null
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
|
|
371
|
+
----------------------------------------------------------------------
|
|
372
|
+
DATAREQUESTDETAIL
|
|
373
|
+
----------------------------------------------------------------------
|
|
374
|
+
|
|
375
|
+
type DataRequestDetail = {
|
|
376
|
+
sort: SortState[]
|
|
377
|
+
page: number
|
|
378
|
+
pageSize: number
|
|
379
|
+
trigger: DataRequestTrigger
|
|
380
|
+
mode: DataRequestMode
|
|
381
|
+
skip: number
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
|
|
385
|
+
----------------------------------------------------------------------
|
|
386
|
+
PAGINATIONLABELSCALLBACK
|
|
387
|
+
----------------------------------------------------------------------
|
|
388
|
+
|
|
389
|
+
type PaginationLabelsCallback = (context: PaginationLabelsContext) => Partial<PaginationLabels>
|
|
390
|
+
|
|
391
|
+
type PaginationLabelsContext = {
|
|
392
|
+
currentPage: number
|
|
393
|
+
totalPages: number
|
|
394
|
+
totalItems: number
|
|
395
|
+
pageSize: number
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
type PaginationLabels = {
|
|
399
|
+
first: string
|
|
400
|
+
previous: string
|
|
401
|
+
next: string
|
|
402
|
+
last: string
|
|
403
|
+
pageInfo: string
|
|
404
|
+
itemCount: string
|
|
405
|
+
perPage: string
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
|
|
409
|
+
----------------------------------------------------------------------
|
|
410
|
+
GRIDLABELS
|
|
411
|
+
----------------------------------------------------------------------
|
|
412
|
+
|
|
413
|
+
type GridLabels = {
|
|
414
|
+
rowActions: string
|
|
415
|
+
inlineActionsHeader: string
|
|
416
|
+
keyboardShortcuts: string
|
|
417
|
+
paginationFirst: string
|
|
418
|
+
paginationPrevious: string
|
|
419
|
+
paginationNext: string
|
|
420
|
+
paginationLast: string
|
|
421
|
+
paginationPageInfo: string
|
|
422
|
+
paginationItemCount: string
|
|
423
|
+
paginationPerPage: string
|
|
424
|
+
dropdownNoOptions: string
|
|
425
|
+
dropdownSearching: string
|
|
426
|
+
contextMenu: {
|
|
427
|
+
sortAsc: string
|
|
428
|
+
sortDesc: string
|
|
429
|
+
clearSort: string
|
|
430
|
+
hideColumn: string
|
|
431
|
+
freezeColumn: string
|
|
432
|
+
unfreezeColumn: string
|
|
433
|
+
columnVisibility: string
|
|
434
|
+
showAll: string
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
|
|
439
|
+
----------------------------------------------------------------------
|
|
440
|
+
CELLVALIDATIONSTATE
|
|
441
|
+
----------------------------------------------------------------------
|
|
442
|
+
|
|
443
|
+
type CellValidationState = {
|
|
444
|
+
rowIndex: number
|
|
445
|
+
field: string
|
|
446
|
+
error: string
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
Used by the invalidCells grid property for external validation state.
|
|
450
|
+
|
|
451
|
+
|
|
452
|
+
----------------------------------------------------------------------
|
|
453
|
+
CELLRANGE
|
|
454
|
+
----------------------------------------------------------------------
|
|
455
|
+
|
|
456
|
+
type CellRange = {
|
|
457
|
+
startRowIndex: number
|
|
458
|
+
startColIndex: number
|
|
459
|
+
endRowIndex: number
|
|
460
|
+
endColIndex: number
|
|
461
|
+
startField: string
|
|
462
|
+
endField: string
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
|
|
466
|
+
----------------------------------------------------------------------
|
|
467
|
+
ADDITIONAL EXPORTED TYPES
|
|
468
|
+
----------------------------------------------------------------------
|
|
469
|
+
|
|
470
|
+
type EditorOption = { value: string | number | boolean, label: string, [key: string]: unknown }
|
|
471
|
+
type OptionRenderContext = { index: number, isHighlighted: boolean, isSelected: boolean, isDisabled: boolean }
|
|
472
|
+
type CustomEditorContext<T> = { value: unknown, row: T, rowIndex: number, field: string, commit: (newValue: unknown) => void, cancel: () => void }
|
|
473
|
+
type ValidationTooltipContext<T> = { field: string, error: string, value: unknown, row: T, rowIndex: number }
|
|
474
|
+
type RowFocusDetail<T> = { rowIndex: number, row: T, previousRowIndex: number | null }
|
|
475
|
+
type CellSelectionChangeDetail = { range: CellRange | null, cellCount: number }
|
|
476
|
+
type ColumnWidthState = { field: string, width: string }
|
|
477
|
+
type ColumnOrderState = { field: string, order: number }
|
|
478
|
+
type ColumnResizeDetail = { field: string, oldWidth: string, newWidth: string, allWidths: ColumnWidthState[] }
|
|
479
|
+
type ColumnReorderDetail = { field: string, fromIndex: number, toIndex: number, allOrder: ColumnOrderState[] }
|
|
480
|
+
type ToolbarClickDetail<T> = { item: NormalizedToolbarItem<T>, rowIndex: number, row: T }
|
|
481
|
+
type ToolbarTooltip = { description?: string, shortcut?: string }
|
|
482
|
+
type SummaryContext<T> = { items: T[], allItems: T[], totalItems: number, currentPage: number, pageSize: number, metadata: unknown }
|
|
483
|
+
type SummaryContentCallback<T> = (context: SummaryContext<T>) => string
|
|
484
|
+
type NewRowPosition = 'top' | 'bottom' (experimental)
|
|
485
|
+
|
|
486
|
+
type BeforePasteDetail<T> = {
|
|
487
|
+
rawText: string, parsedRows: string[][], hasHeaders: boolean,
|
|
488
|
+
headerMapping: PasteColumnMapping[] | null,
|
|
489
|
+
targetRowIndex: number, targetColIndex: number, newRowsCount: number,
|
|
490
|
+
cancel: boolean, skipCells: Set<string>
|
|
491
|
+
}
|
|
492
|
+
type PasteCellResult = { rowIndex: number, field: string, oldValue: unknown, newValue: unknown, success: boolean, error?: string }
|
|
493
|
+
type PasteDetail<T> = {
|
|
494
|
+
totalCells: number, successfulCells: number, failedCells: number,
|
|
495
|
+
skippedCells: number, newRowsCreated: number,
|
|
496
|
+
cellResults: PasteCellResult[], hadHeaders: boolean
|
|
497
|
+
}
|
|
498
|
+
type CreateRowCallback<T> = (pastedData: Record<string, unknown>, rowIndex: number) => T
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
VIRTUAL SCROLL AND INFINITE SCROLL
|
|
2
|
+
===================================
|
|
3
|
+
@keenmate/web-grid - Performance features for large datasets.
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
VIRTUAL SCROLL
|
|
7
|
+
--------------
|
|
8
|
+
Virtual scroll renders only the rows visible in the viewport plus a small
|
|
9
|
+
buffer, dramatically reducing DOM nodes for large datasets.
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
PROPERTIES
|
|
13
|
+
----------
|
|
14
|
+
isVirtualScrollEnabled (boolean, default: false)
|
|
15
|
+
Explicitly enable virtual scroll.
|
|
16
|
+
|
|
17
|
+
virtualScrollThreshold (number, default: 100)
|
|
18
|
+
Auto-enable virtual scroll when items.length >= this threshold. The grid
|
|
19
|
+
checks this automatically, so you often do not need to set
|
|
20
|
+
isVirtualScrollEnabled manually.
|
|
21
|
+
|
|
22
|
+
virtualScrollRowHeight (number, default: 38)
|
|
23
|
+
Fixed row height in pixels. All rows MUST be the same height for virtual
|
|
24
|
+
scroll to calculate positions correctly. This value is used to compute
|
|
25
|
+
total scroll height, visible row range, and scroll offsets.
|
|
26
|
+
|
|
27
|
+
virtualScrollBuffer (number, default: 10)
|
|
28
|
+
Number of extra rows rendered above and below the visible viewport. This
|
|
29
|
+
prevents blank flashes during fast scrolling. Higher values render more
|
|
30
|
+
DOM nodes but provide smoother scrolling.
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
HOW IT WORKS
|
|
34
|
+
------------
|
|
35
|
+
The grid calculates which rows are visible based on:
|
|
36
|
+
- scrollTop of the scroll container
|
|
37
|
+
- viewportHeight (clientHeight of container minus header height)
|
|
38
|
+
- virtualScrollRowHeight (fixed height per row)
|
|
39
|
+
|
|
40
|
+
A spacer element with the total height (rowCount * rowHeight) is placed in
|
|
41
|
+
the table to create the correct scrollbar. Only rows in the visible range
|
|
42
|
+
(plus buffer) are rendered as actual DOM elements. When the user scrolls,
|
|
43
|
+
the visible range is recalculated and rows are added/removed.
|
|
44
|
+
|
|
45
|
+
The grid uses shouldUseVirtualScroll() internally which returns true when
|
|
46
|
+
isVirtualScrollEnabled is set OR when items.length >= virtualScrollThreshold.
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
KEYBOARD NAVIGATION WITH VIRTUAL SCROLL
|
|
50
|
+
----------------------------------------
|
|
51
|
+
Arrow keys, PageUp, PageDown, Home, and End all work with virtual scroll.
|
|
52
|
+
When navigating to a row outside the current viewport:
|
|
53
|
+
- The grid scrolls to make the target row visible with minimal movement
|
|
54
|
+
- For PageUp/PageDown, the target row is positioned as the second visible
|
|
55
|
+
row (one row of context above)
|
|
56
|
+
- Focus is set after the scroll-triggered re-render completes
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
FIXED ROW HEIGHT REQUIREMENT
|
|
60
|
+
-----------------------------
|
|
61
|
+
Virtual scroll requires all rows to be exactly virtualScrollRowHeight pixels
|
|
62
|
+
tall. Variable row heights are NOT supported. If rows have different content
|
|
63
|
+
heights, set a fixed height via CSS:
|
|
64
|
+
|
|
65
|
+
web-grid {
|
|
66
|
+
--wg-row-min-height: 38px;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
Or use textOverflow: 'ellipsis' on columns to prevent text wrapping.
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
EXAMPLE
|
|
73
|
+
-------
|
|
74
|
+
grid.items = largeDataset // 10,000 rows
|
|
75
|
+
grid.virtualScrollRowHeight = 40 // Each row is 40px
|
|
76
|
+
grid.virtualScrollBuffer = 15 // Render 15 extra rows each side
|
|
77
|
+
// virtualScrollThreshold defaults to 100, so virtual scroll activates
|
|
78
|
+
// automatically for datasets >= 100 rows
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
INFINITE SCROLL
|
|
82
|
+
---------------
|
|
83
|
+
Infinite scroll triggers a data load when the user scrolls near the bottom.
|
|
84
|
+
It is designed for "load more" patterns with server-side data.
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
PROPERTIES
|
|
88
|
+
----------
|
|
89
|
+
isInfiniteScrollEnabled (boolean, default: false)
|
|
90
|
+
Enable infinite scroll behavior.
|
|
91
|
+
|
|
92
|
+
infiniteScrollThreshold (number, default: 100)
|
|
93
|
+
Distance from the bottom of the scroll container (in pixels) at which
|
|
94
|
+
the ondatarequest event fires to load more data.
|
|
95
|
+
|
|
96
|
+
hasMoreItems (boolean, default: true)
|
|
97
|
+
Set to false when there is no more data to load. This prevents further
|
|
98
|
+
ondatarequest events from firing.
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
HOW IT WORKS
|
|
102
|
+
------------
|
|
103
|
+
When the user scrolls within infiniteScrollThreshold pixels of the bottom,
|
|
104
|
+
the grid fires the ondatarequest event with:
|
|
105
|
+
trigger: 'loadMore'
|
|
106
|
+
mode: 'append'
|
|
107
|
+
skip: current items.length
|
|
108
|
+
|
|
109
|
+
The consumer should:
|
|
110
|
+
1. Fetch the next batch of data from the server
|
|
111
|
+
2. Append it to the existing items array
|
|
112
|
+
3. Set hasMoreItems = false when the server returns no more data
|
|
113
|
+
|
|
114
|
+
Example:
|
|
115
|
+
grid.isInfiniteScrollEnabled = true
|
|
116
|
+
grid.infiniteScrollThreshold = 200
|
|
117
|
+
grid.ondatarequest = async (detail) => {
|
|
118
|
+
if (detail.trigger === 'loadMore') {
|
|
119
|
+
const newItems = await fetchItems(detail.skip, detail.pageSize)
|
|
120
|
+
grid.items = [...grid.items, ...newItems]
|
|
121
|
+
if (newItems.length < detail.pageSize) {
|
|
122
|
+
grid.hasMoreItems = false
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
COMBINING VIRTUAL AND INFINITE SCROLL
|
|
129
|
+
--------------------------------------
|
|
130
|
+
Virtual scroll and infinite scroll can be used together. The grid renders
|
|
131
|
+
only visible rows (virtual scroll) while loading more data as the user
|
|
132
|
+
scrolls toward the bottom (infinite scroll). This is the recommended
|
|
133
|
+
pattern for very large server-side datasets.
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
PERFORMANCE CONSIDERATIONS
|
|
137
|
+
--------------------------
|
|
138
|
+
- Virtual scroll reduces DOM nodes from N rows to roughly
|
|
139
|
+
(viewportHeight / rowHeight) + (2 * buffer) rows
|
|
140
|
+
- For 10,000 rows with buffer=10, about 30-40 DOM rows exist at any time
|
|
141
|
+
- Column count still affects performance (each row has N cells)
|
|
142
|
+
- beforeCommitCallback, formatCallback, and other per-cell callbacks still
|
|
143
|
+
run only for rendered cells
|
|
144
|
+
- Cell selection and clipboard operations work across the full dataset,
|
|
145
|
+
not just visible rows
|
|
146
|
+
- Sorting and filtering operate on the full items array
|
package/dist/grid.d.ts
CHANGED
|
@@ -123,6 +123,7 @@ export declare class WebGrid<T = unknown> {
|
|
|
123
123
|
protected _focusedCell: FocusedCell;
|
|
124
124
|
protected _isCommittingFromKeyboard: boolean;
|
|
125
125
|
protected _skipNextDropdownAutoEdit: boolean;
|
|
126
|
+
protected _tabTraversalStartColIndex: number | null;
|
|
126
127
|
protected _hoveredRowIndex: number | null;
|
|
127
128
|
protected _focusedRowIndex: number | null;
|
|
128
129
|
protected _onrowfocus: ((detail: RowFocusDetail<T>) => void) | undefined;
|
|
@@ -627,6 +628,9 @@ export declare class WebGrid<T = unknown> {
|
|
|
627
628
|
* NOTE: Does NOT call requestUpdate() - GridElement handles DOM updates surgically
|
|
628
629
|
*/
|
|
629
630
|
clearFocusedCell(): void;
|
|
631
|
+
/** Tab traversal start column for Excel-like Enter behavior */
|
|
632
|
+
get tabTraversalStartColIndex(): number | null;
|
|
633
|
+
set tabTraversalStartColIndex(value: number | null);
|
|
630
634
|
/**
|
|
631
635
|
* Set the hovered row index (for toolbar/shortcuts)
|
|
632
636
|
* NOTE: Does NOT call requestUpdate() - GridElement handles UI updates
|
|
@@ -5,6 +5,10 @@ import type { GridContext } from '../types.js';
|
|
|
5
5
|
* In virtual scroll mode, only scroll if cell is outside viewport (minimal scroll)
|
|
6
6
|
*/
|
|
7
7
|
export declare function focusCellElement<T>(ctx: GridContext<T>, rowIndex: number, colIndex: number): void;
|
|
8
|
+
/**
|
|
9
|
+
* If cell is behind frozen columns, scroll horizontally to reveal it
|
|
10
|
+
*/
|
|
11
|
+
export declare function ensureCellNotBehindFrozen<T>(ctx: GridContext<T>, cell: HTMLElement, rowIndex: number): void;
|
|
8
12
|
/**
|
|
9
13
|
* Scroll to position a row as the second visible row (for PageUp/PageDown)
|
|
10
14
|
* Exported so keyboard handlers can use it directly
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export { focusCellElement, updateFocusVisual, clearEditingVisual, cleanupEditState, restoreEditingCellToDisplayMode, handleCellFocus, moveFocus, tryStartEdit, getCursorPositionFromClick, handleTableFocusOut, scrollToRowPosition } from './focus.js';
|
|
1
|
+
export { focusCellElement, ensureCellNotBehindFrozen, updateFocusVisual, clearEditingVisual, cleanupEditState, restoreEditingCellToDisplayMode, handleCellFocus, moveFocus, tryStartEdit, getCursorPositionFromClick, handleTableFocusOut, scrollToRowPosition } from './focus.js';
|