@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.
@@ -0,0 +1,475 @@
1
+ TOOLBAR, INLINE ACTIONS, CELL TOOLBAR, AND CONTEXT MENUS
2
+ =========================================================
3
+ Reference for @keenmate/web-grid row toolbar, inline actions,
4
+ cell-specific toolbar, and context menus (cell and header).
5
+
6
+
7
+ ROW TOOLBAR BASICS
8
+ ------------------
9
+ The row toolbar is a floating (or inline) set of action buttons
10
+ that appear per-row. Enable it with two properties:
11
+
12
+ grid.isRowToolbarVisible = true
13
+ grid.rowToolbar = ['add', 'delete', 'duplicate', 'moveUp', 'moveDown']
14
+
15
+ The rowToolbar array accepts predefined action strings or custom
16
+ item objects (mixed in any order).
17
+
18
+ Predefined action strings:
19
+ 'add' - Insert a new row after the current row
20
+ 'delete' - Delete the current row
21
+ 'duplicate' - Duplicate the current row
22
+ 'moveUp' - Move row up one position
23
+ 'moveDown' - Move row down one position
24
+
25
+ Each predefined string is expanded internally to a normalized item
26
+ with a built-in SVG icon, title, and (for 'delete') danger styling.
27
+
28
+ Toolbar trigger controls how the toolbar appears:
29
+ grid.toolbarTrigger = 'hover' (default) show on row hover
30
+ grid.toolbarTrigger = 'click' show on row click
31
+ grid.toolbarTrigger = 'button' show a trigger button in each row
32
+
33
+ When toolbarTrigger is 'button', a column with a vertical ellipsis
34
+ button is rendered. Clicking the button opens the floating toolbar.
35
+
36
+
37
+ CUSTOM TOOLBAR ITEMS
38
+ --------------------
39
+ Custom items are objects mixed into the rowToolbar array alongside
40
+ predefined strings.
41
+
42
+ Full type definition (RowToolbarItem<T>):
43
+
44
+ {
45
+ id: string // unique identifier (required)
46
+ icon: string // icon HTML/emoji/SVG (required)
47
+ title: string // tooltip title text (required)
48
+ label: string // optional text label next to icon
49
+ row: number // toolbar row (1 = closest to grid row, default: 1)
50
+ group: number // group number for visual dividers (default: 1)
51
+ danger: boolean // red/danger styling (default: false)
52
+ disabled: boolean | (row: T, rowIndex: number) => boolean
53
+ hidden: boolean | (row: T, rowIndex: number) => boolean
54
+ minWidth: string // per-button min-width override (e.g. '40px')
55
+ tooltip: {
56
+ description: string // additional description text
57
+ shortcut: string // keyboard shortcut hint (e.g. 'Ctrl+D')
58
+ }
59
+ tooltipCallback: (row: T, rowIndex: number) => string // custom HTML tooltip
60
+ onclick: (detail: { row: T, rowIndex: number }) => void | Promise<void>
61
+ }
62
+
63
+ Example with mixed predefined and custom items:
64
+
65
+ grid.rowToolbar = [
66
+ 'add',
67
+ 'delete',
68
+ {
69
+ id: 'edit',
70
+ icon: '<svg>...</svg>',
71
+ title: 'Edit Record',
72
+ label: 'Edit',
73
+ row: 1,
74
+ group: 2,
75
+ disabled: (row, idx) => row.locked,
76
+ hidden: (row, idx) => row.archived,
77
+ tooltip: { description: 'Open editor', shortcut: 'Enter' },
78
+ onclick: ({ row, rowIndex }) => openEditor(row)
79
+ },
80
+ {
81
+ id: 'archive',
82
+ icon: '📦',
83
+ title: 'Archive',
84
+ row: 2,
85
+ group: 1,
86
+ danger: true,
87
+ onclick: ({ row }) => archiveRow(row)
88
+ }
89
+ ]
90
+
91
+ Grouping and multi-row layout:
92
+ - Items with the same 'row' number appear on the same horizontal row.
93
+ - Items with different 'group' numbers within a row are separated by
94
+ a visual divider.
95
+ - Row 1 is closest to the grid data row. Higher row numbers stack
96
+ outward from the data row.
97
+
98
+
99
+ TOOLBAR POSITIONING
100
+ -------------------
101
+ Properties that control where and how the toolbar appears:
102
+
103
+ grid.toolbarPosition = 'auto' (default) prefers left, falls back to right, then top
104
+ grid.toolbarPosition = 'left' to the left of the row
105
+ grid.toolbarPosition = 'right' to the right of the row
106
+ grid.toolbarPosition = 'top' above the row
107
+ grid.toolbarPosition = 'inline' renders as a fixed table column (see INLINE ACTIONS)
108
+
109
+ For left/right positions, vertical alignment controls where the
110
+ toolbar aligns relative to the row:
111
+
112
+ grid.toolbarVerticalAlign = 'bottom' (default) toolbar top aligns with row, stacks downward
113
+ grid.toolbarVerticalAlign = 'center' toolbar centered on row
114
+ grid.toolbarVerticalAlign = 'top' toolbar bottom aligns with row, stacks upward
115
+
116
+ For top position, horizontal alignment controls left-right placement:
117
+
118
+ grid.toolbarHorizontalAlign = 'center' (default) centered above row
119
+ grid.toolbarHorizontalAlign = 'start' aligned with left edge
120
+ grid.toolbarHorizontalAlign = 'end' aligned with right edge
121
+ grid.toolbarHorizontalAlign = 'cursor' positioned at mouse cursor X
122
+
123
+ Additional positioning properties:
124
+
125
+ grid.toolbarFollowsCursor = false (default) toolbar stays at initial position
126
+ grid.toolbarFollowsCursor = true toolbar follows mouse cursor horizontally
127
+
128
+ grid.toolbarColumn = 'fieldName' pin toolbar above a specific column (for 'top' position)
129
+ grid.toolbarColumn = 2 or by column index
130
+
131
+ grid.toolbarBtnMinWidth = '32px' min-width for toolbar buttons (CSS value)
132
+ overrides --wg-toolbar-btn-min-width variable
133
+
134
+ grid.cellToolbarOffset = 0.2 (default) horizontal offset for cell toolbar
135
+ number 0-1: fraction of cell width
136
+ string: CSS length like '2rem' or '24px'
137
+
138
+ Deprecated aliases:
139
+ toolbarAlign -> use toolbarVerticalAlign
140
+ toolbarTopPosition -> use toolbarHorizontalAlign
141
+
142
+ The toolbar uses @floating-ui/dom for smart positioning with automatic
143
+ flip and shift. If the preferred position would go off-screen, it
144
+ falls back to alternative positions.
145
+
146
+ Connector arrow: When moveUp/moveDown actions are used, a bracket-shaped
147
+ SVG connector line tracks from the toolbar to the row's current position,
148
+ even when the row scrolls out of view.
149
+
150
+
151
+ INLINE ACTIONS
152
+ --------------
153
+ When toolbarPosition is set to 'inline', toolbar items render as a
154
+ fixed column in the table instead of a floating popup.
155
+
156
+ grid.isRowToolbarVisible = true
157
+ grid.toolbarPosition = 'inline'
158
+ grid.rowToolbar = ['add', 'delete', 'duplicate']
159
+
160
+ Behavior differences from floating toolbar:
161
+ - Actions render as buttons directly in each row's cell.
162
+ - No hover/click trigger needed; buttons are always visible.
163
+ - No floating-ui positioning; it is a regular table column.
164
+ - Hidden items (via hidden callback) are excluded from the DOM entirely.
165
+ - Disabled items render as disabled buttons.
166
+ - Multi-row layout (via the 'row' property) is supported within the cell.
167
+
168
+ Column header title:
169
+
170
+ grid.inlineActionsTitle = 'Actions' (default)
171
+ grid.inlineActionsTitle = '' no header text
172
+
173
+ The column width auto-sizes based on the maximum number of buttons
174
+ across all rows, using --wg-toolbar-btn-min-width and --wg-inline-actions-gap
175
+ CSS variables.
176
+
177
+ Clicking an inline action button fires the same ontoolbarclick callback
178
+ and calls the item's onclick handler, identical to the floating toolbar.
179
+
180
+
181
+ CELL-SPECIFIC TOOLBAR
182
+ ---------------------
183
+ The cellToolbar callback provides context-sensitive toolbar items
184
+ that appear when hovering/clicking a specific cell, not just a row.
185
+
186
+ grid.cellToolbar = (row, rowIndex, field, colIndex) => {
187
+ if (field === 'status') {
188
+ return [
189
+ { id: 'approve', icon: '✅', title: 'Approve', onclick: ({ row }) => approve(row) },
190
+ { id: 'reject', icon: '❌', title: 'Reject', danger: true, onclick: ({ row }) => reject(row) }
191
+ ]
192
+ }
193
+ return undefined // no cell toolbar for other columns
194
+ }
195
+
196
+ Callback signature:
197
+ cellToolbar: (row: T, rowIndex: number, field: string, colIndex: number)
198
+ => RowToolbarConfig<T>[] | undefined
199
+
200
+ Return an array of toolbar items (same format as rowToolbar items -
201
+ predefined strings or custom objects) or undefined for no cell toolbar.
202
+
203
+ The cell toolbar position is controlled by cellToolbarOffset:
204
+
205
+ grid.cellToolbarOffset = 0.2 (default) 20% from left edge of the cell
206
+ grid.cellToolbarOffset = 0 left edge of cell
207
+ grid.cellToolbarOffset = 0.5 center of cell
208
+ grid.cellToolbarOffset = '2rem' 2rem from left edge of cell
209
+
210
+ When cellToolbar returns items, the toolbar appears above the hovered
211
+ cell. When it returns undefined, the regular row toolbar (if configured)
212
+ is used instead. The toolbar uses 'top' position when showing
213
+ cell-specific items.
214
+
215
+
216
+ CONTEXT MENU
217
+ -------------
218
+ Right-click context menu for data cells/rows.
219
+
220
+ grid.contextMenu = [
221
+ {
222
+ id: 'view',
223
+ label: 'View Details',
224
+ icon: '👁️',
225
+ shortcut: 'Enter',
226
+ visible: true,
227
+ disabled: false,
228
+ danger: false,
229
+ dividerBefore: false,
230
+ onclick: (context) => viewDetails(context.row)
231
+ },
232
+ {
233
+ id: 'delete',
234
+ label: 'Delete Row',
235
+ icon: '🗑️',
236
+ shortcut: 'Delete',
237
+ danger: true,
238
+ dividerBefore: true,
239
+ disabled: (context) => context.row.locked,
240
+ onclick: (context) => deleteRow(context.rowIndex)
241
+ }
242
+ ]
243
+
244
+ ContextMenuItem<T> type:
245
+
246
+ {
247
+ id: string // unique identifier
248
+ label: string | (context: ContextMenuContext<T>) => string // display text (static or dynamic)
249
+ icon: string | (context: ContextMenuContext<T>) => string // icon (static or dynamic)
250
+ shortcut: string // display-only shortcut hint (also used as keyboard trigger)
251
+ visible: boolean | (context: ContextMenuContext<T>) => boolean // show/hide item
252
+ disabled: boolean | (context: ContextMenuContext<T>) => boolean // disable item
253
+ danger: boolean // red/danger styling
254
+ dividerBefore: boolean // render horizontal divider above this item
255
+ onclick: (context: ContextMenuContext<T>) => void | Promise<void>
256
+ }
257
+
258
+ onclick context object (ContextMenuContext<T>):
259
+
260
+ {
261
+ row: T // the row data object
262
+ rowIndex: number // index of the row in display items
263
+ colIndex: number // column index that was right-clicked
264
+ column: Column<T> // the column definition
265
+ cellValue: unknown // the cell's current value
266
+ }
267
+
268
+ Shortcut matching: When the context menu is open, pressing a key that
269
+ matches an item's shortcut string (case-insensitive for single letters)
270
+ triggers that item's onclick and closes the menu.
271
+
272
+ Positioning offsets:
273
+
274
+ grid.contextMenuXOffset = 0 (default) horizontal offset from click
275
+ grid.contextMenuYOffset = 4 (default) vertical offset from click
276
+
277
+ The context menu renders in the document body (outside Shadow DOM) and
278
+ uses @floating-ui/dom for viewport-aware positioning with flip/shift.
279
+
280
+ Closing behavior:
281
+ - Click outside the menu
282
+ - Press Escape
283
+ - Scroll the page or grid
284
+ - Press a matching shortcut key
285
+
286
+
287
+ HEADER CONTEXT MENU
288
+ -------------------
289
+ Right-click context menu for column headers with predefined actions
290
+ and custom items.
291
+
292
+ grid.headerContextMenu = [
293
+ 'sortAsc',
294
+ 'sortDesc',
295
+ 'clearSort',
296
+ 'hideColumn',
297
+ 'freezeColumn',
298
+ 'unfreezeColumn',
299
+ 'columnVisibility',
300
+ {
301
+ id: 'custom-action',
302
+ label: 'Custom Action',
303
+ icon: '⚙️',
304
+ onclick: (context) => doSomething(context.column)
305
+ }
306
+ ]
307
+
308
+ Predefined string actions:
309
+
310
+ 'sortAsc' - Sort column ascending (hidden if column not sortable)
311
+ 'sortDesc' - Sort column descending (hidden if column not sortable)
312
+ 'clearSort' - Clear sort for this column (hidden if not sorted)
313
+ 'hideColumn' - Hide this column (sets isHidden=true)
314
+ 'freezeColumn' - Freeze columns up to and including this one (hidden if already frozen)
315
+ 'unfreezeColumn' - Unfreeze this column (hidden if not frozen)
316
+ 'columnVisibility' - Submenu to toggle visibility of all columns
317
+
318
+ Each predefined action has automatic visibility rules:
319
+ - sortAsc/sortDesc: visible only if column.isSortable is not false
320
+ - clearSort: visible only if column is currently sorted
321
+ - freezeColumn: visible only if column is not frozen
322
+ - unfreezeColumn: visible only if column is frozen
323
+ - columnVisibility: always visible, generates a dynamic submenu with
324
+ a "Show all" option and individual toggles for each column
325
+
326
+ The columnVisibility submenu stays open after toggling items, updating
327
+ checkmarks in real time.
328
+
329
+ Custom header menu items (HeaderMenuItem<T>):
330
+
331
+ {
332
+ id: string
333
+ label: string | (context: HeaderMenuContext<T>) => string
334
+ icon: string | (context: HeaderMenuContext<T>) => string
335
+ shortcut: string
336
+ disabled: boolean | (context: HeaderMenuContext<T>) => boolean
337
+ visible: boolean | (context: HeaderMenuContext<T>) => boolean
338
+ danger: boolean
339
+ dividerBefore: boolean
340
+ children: HeaderMenuItem<T>[] // static submenu
341
+ submenu: (context: HeaderMenuContext<T>) => HeaderMenuItem<T>[] // dynamic submenu
342
+ onclick: (context: HeaderMenuContext<T>) => void | Promise<void>
343
+ }
344
+
345
+ onclick context object (HeaderMenuContext<T>):
346
+
347
+ {
348
+ column: Column<T> // the column definition
349
+ field: string // column field name
350
+ columnIndex: number // column index
351
+ sortDirection: 'asc' | 'desc' | null // current sort direction for this column
352
+ isFrozen: boolean // whether this column is frozen
353
+ allColumns: Column<T>[] // all columns including hidden (for visibility menus)
354
+ labels: GridLabels // grid labels for i18n
355
+ }
356
+
357
+ Submenu support: Use 'children' for a static array of sub-items, or
358
+ 'submenu' for a function that returns items dynamically based on context.
359
+ Submenus render as nested fly-out menus to the right.
360
+
361
+ Dividers: Set dividerBefore: true on an item to render a horizontal
362
+ line above it. You can also use a standalone divider marker object
363
+ { dividerBefore: true } in the array; the divider will be applied
364
+ to the next actual item.
365
+
366
+
367
+ EVENTS
368
+ ------
369
+ Three callback events relate to toolbars and context menus:
370
+
371
+ ontoolbarclick:
372
+ Fires when any toolbar button is clicked (floating or inline).
373
+ Set as a property on the grid element.
374
+
375
+ grid.ontoolbarclick = (detail) => {
376
+ // detail.item - NormalizedToolbarItem (id, icon, title, type, etc.)
377
+ // detail.rowIndex - current row index (updated if row moved)
378
+ // detail.row - the row data object
379
+ }
380
+
381
+ Type: ToolbarClickDetail<T>:
382
+ {
383
+ item: NormalizedToolbarItem<T>
384
+ rowIndex: number
385
+ row: T
386
+ }
387
+
388
+ This fires AFTER the item's own onclick handler. For predefined
389
+ actions (add, delete, duplicate, moveUp, moveDown), the built-in
390
+ action has already been applied to the items array by the time
391
+ ontoolbarclick fires.
392
+
393
+ Note: For move actions (moveUp, moveDown), the rowIndex in the
394
+ detail reflects the row's CURRENT position after the move.
395
+
396
+ oncontextmenuopen:
397
+ Fires when the cell/row context menu is opened (before rendering).
398
+ Set as a property on the grid element.
399
+
400
+ grid.oncontextmenuopen = (context) => {
401
+ // context.row, context.rowIndex, context.colIndex,
402
+ // context.column, context.cellValue
403
+ }
404
+
405
+ Type: ContextMenuContext<T> (same object passed to menu item callbacks)
406
+
407
+ onheadercontextmenuopen:
408
+ Fires when the header context menu is opened (before rendering).
409
+ Set as a property on the grid element.
410
+
411
+ grid.onheadercontextmenuopen = (context) => {
412
+ // context.column, context.field, context.columnIndex,
413
+ // context.sortDirection, context.isFrozen, context.allColumns, context.labels
414
+ }
415
+
416
+ Type: HeaderMenuContext<T> (same object passed to menu item callbacks)
417
+
418
+ Legacy event:
419
+ onrowaction is deprecated. Use ontoolbarclick instead.
420
+ onrowaction fires with { action, rowIndex, row } for predefined actions only.
421
+
422
+
423
+ I18N LABELS
424
+ -----------
425
+ Relevant labels that can be customized:
426
+
427
+ grid.labels = {
428
+ rowActions: 'Actions', // toolbar trigger button title
429
+ inlineActionsHeader: 'Actions', // default inline actions column header
430
+ contextMenu: {
431
+ sortAsc: 'Sort Ascending',
432
+ sortDesc: 'Sort Descending',
433
+ clearSort: 'Clear Sort',
434
+ hideColumn: 'Hide Column',
435
+ freezeColumn: 'Freeze Column',
436
+ unfreezeColumn: 'Unfreeze Column',
437
+ columnVisibility: 'Column Visibility',
438
+ showAll: 'Show all'
439
+ }
440
+ }
441
+
442
+ The inlineActionsTitle property overrides inlineActionsHeader label
443
+ when toolbarPosition is 'inline'.
444
+
445
+
446
+ CSS VARIABLES
447
+ -------------
448
+ Key CSS variables for styling toolbar and context menu:
449
+
450
+ Toolbar:
451
+ --wg-toolbar-btn-min-width min-width for toolbar buttons
452
+ --wg-inline-actions-gap gap between inline action buttons
453
+ --wg-spacing-sm used for inline actions cell padding
454
+
455
+ Context menu (set on .wg-context-menu-container):
456
+ --wg-cm-z-index z-index (default: 10000)
457
+ --wg-cm-background menu background
458
+ --wg-cm-border-color menu border color
459
+ --wg-cm-text-color text color
460
+ --wg-cm-text-secondary shortcut text color
461
+ --wg-cm-text-danger danger item text color
462
+ --wg-cm-hover-bg item hover background
463
+ --wg-cm-disabled-opacity opacity for disabled items
464
+ --wg-cm-font-family font family
465
+ --wg-cm-font-size font size
466
+ --wg-cm-padding menu padding
467
+ --wg-cm-item-padding item padding
468
+ --wg-cm-min-width menu minimum width
469
+ --wg-cm-border-radius border radius
470
+ --wg-cm-icon-size icon size
471
+ --wg-cm-icon-gap gap between icon and label
472
+ --wg-cm-shadow box shadow
473
+
474
+ Context menu variables fall back to --base-* theme variables from
475
+ @keenmate/theme-designer when available.