You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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.
What's New in v1.0.3
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.
Cell selection visual fix: Focused cell outline no longer persists during cell range drag.
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).
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.
Logging system: loglevel-based logging with 4 categories (GRID:INIT, GRID:DATA, GRID:UI, GRID:INTERACTION). Enable via window.components['web-grid'].logging.enableLogging().
Runtime API: window.components['web-grid'] with version(), config, and logging — matching web-multiselect and web-daterangepicker.
v1.0.2
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.
HTML tooltips: New isTooltipHtml column option for rich tooltip content.
ontoolbarclick detail: Now includes event (MouseEvent) and triggerElement (HTMLElement) for anchoring popovers to toolbar buttons inside shadow DOM.
Tooltip show delay: Reduced default from 400ms to 200ms.
Cell Range Selection — Click+drag or Shift+click to select rectangular ranges
Clipboard — Copy/paste TSV data (Excel-compatible), per-column transform callbacks
Row Toolbar — Floating, inline, or cell-specific action buttons
Context Menu — Right-click menu for cells and column headers (with predefined actions)
Keyboard Shortcuts — Per-row and per-range custom shortcuts with help overlay
Column Reordering — Drag-to-reorder with optional localStorage persistence
Column Resizing — Drag column borders with min/max constraints and persistence
Frozen Columns — Stick columns to the left during horizontal scroll
Fill Handle — Excel-like autofill by dragging a cell's corner
Row Locking — Optimistic locking with external lock management (WebSocket-ready)
Row Identification — idValueMember/idValueCallback for updateRowById, replaceRowById
Virtual Scroll — Render only visible rows for large datasets
Infinite Scroll — Load-more pattern triggered near bottom of scroll
Dark Mode — Auto-detects OS/attribute/class preferences
CSS Variable Theming — 120+ --wg-* variables with --base-* fallback for cross-component themes
Shadow DOM — Encapsulated styles, no CSS leakage
Summary Bar — Configurable summary content at any corner position
Validation — beforeCommitCallback with tooltip display (custom HTML supported)
Row Focus Tracking — onrowfocus for master/detail patterns
i18n — All UI labels customizable via labels property
TypeScript — Full type definitions exported
Architecture
WebGrid uses an action pipeline pattern for user interactions. DOM events are captured by the ActionPipelineAdapter, which translates them into typed action objects (34 action types). These actions flow through a pipeline that dispatches them to specialized executors (16 executors covering focus, editing, navigation, selection, clipboard, dropdown, fill-handle, etc.). Executors can emit child actions for composability — e.g., committing an edit can trigger a focus-move.
DOM Event → Adapter → Action → Pipeline → Executor → State Change → Render
This architecture separates mode detection (the adapter decides what action a click means based on grid mode, edit trigger, cell state) from business logic (executors handle how to focus, edit, select). It also allows incremental migration from legacy event handlers.
These options are shared across select, combobox, and autocomplete editor types.
Providing Options
Option
Type
Description
options
EditorOption[]
Static array of options: { value, label, ...extra }
loadOptions
(row, field) => Promise<EditorOption[]>
Async option loader
optionsLoadTrigger
OptionsLoadTrigger
When to call loadOptions: 'immediate', 'oneditstart' (default), 'ondropdownopen'
Display Mapping
By default, options use value and label properties. Override with member strings or callback functions:
Member Property
Callback Alternative
Description
valueMember
getValueCallback
Property/callback for the option value
displayMember
getDisplayCallback
Property/callback for display text
searchMember
getSearchCallback
Property/callback for searchable text (falls back to display)
iconMember
getIconCallback
Property/callback for icon/emoji
subtitleMember
getSubtitleCallback
Property/callback for subtitle/description
disabledMember
getDisabledCallback
Property/callback for disabled state
groupMember
getGroupCallback
Property/callback for grouping options
Rendering & Behavior
Option
Type
Description
renderOptionCallback
(option, context) => string
Custom HTML for each option. context has { index, isHighlighted, isSelected, isDisabled }
onselect
(option, row) => void
Fires when an option is selected
allowEmpty
boolean
Allow null/empty selection
emptyLabel
string
Label for empty option (default: '-- Select --')
noOptionsText
string
Override "No options" message
searchingText
string
Override "Searching..." message
dropdownMinWidth
string
Minimum width for dropdown (e.g., '300px')
placeholder
string
Placeholder text (combobox/autocomplete)
Autocomplete-Specific Options
Option
Type
Default
Description
searchCallback
(query, row, signal?) => Promise<EditorOption[]>
—
Async search function (receives AbortSignal for cancellation)
initialOptions
EditorOption[]
—
Options shown before user starts typing
minSearchLength
number
1
Minimum characters before triggering search
debounceMs
number
300
Debounce delay for search calls
multiple
boolean
false
Allow multiple selections
maxSelections
number
—
Maximum items when multiple is true
Custom Editor
Set editor: 'custom' and provide a cellEditCallback on the column:
{field: 'color',editor: 'custom',cellEditCallback: ({ value, row, rowIndex, field, commit, cancel })=>{// Open your own UI (modal, popover, etc.)// Call commit(newValue) to save, cancel() to discard}}
Grid Modes
The mode property sets sensible defaults for common use cases:
Mode
isEditable
editTrigger
cellSelectionMode
dropdownToggleVisibility
shouldShowDropdownOnFocus
'read-only'
false
—
'click'
'on-focus'
false
'excel'
true
'navigate'
'click'
'always'
false
'input-matrix'
true
'always'
'shift'
'always'
true
read-only — No editing. Click to select cells. Useful for display grids with copy support.
excel — Navigate cells with arrows, type to start editing, Escape to cancel. Click+drag for cell selection.
input-matrix — All cells are always in edit mode. Shift+click for cell selection. Tab to navigate between fields.
Setting mode applies these defaults, but you can override individual properties afterward.
These are callback properties set on the grid element, not DOM events. Use the naming convention: on* callbacks are fire-and-forget notifications; *Callback properties return a value that affects behavior (see column-level callbacks above).
Callback
Signature
Description
onrowchange
(detail: RowChangeDetail<T>) => void
Cell value changed. Detail includes row, draftRow, rowIndex, field, oldValue, newValue, isValid, validationError
Note: Only rowdelete is also dispatched as a DOM CustomEvent. All other callbacks are property-based only.
Public Methods
Focus & Editing
Method
Description
focusCell(rowIndex, colIndex)
Programmatically focus a cell
startEditing(rowIndex, colIndex)
Programmatically start editing a cell
openCustomEditor(rowIndex, colIndex)
Open the custom editor for a cell with editor: 'custom'
Draft Management
Method
Returns
Description
getRowDraft(rowIndex)
T | undefined
Get the draft (uncommitted changes) for a row
hasRowDraft(rowIndex)
boolean
Check if a row has uncommitted changes
discardRowDraft(rowIndex)
void
Discard all draft changes for a row
getDraftRowIndices()
number[]
Get indices of all rows with drafts
discardAllDrafts()
void
Discard all draft changes across all rows
Validation
Method
Returns
Description
isCellInvalid(rowIndex, field)
boolean
Check if a cell has a validation error
getCellValidationError(rowIndex, field)
string | null
Get the validation error message for a cell
canEditCell(rowIndex, field)
boolean
Check if a cell can be edited (considers row locking, column editability)
Row Identification
Method
Returns
Description
getRowId(row)
unknown | undefined
Get the ID of a row using idValueMember/idValueCallback
findRowById(id)
{ row, index } | null
Find a row by its ID
Row Updates
Method
Returns
Description
updateRowById(id, newData)
boolean
Merge partial data into a row (for WebSocket/live updates)
replaceRowById(id, newRow)
boolean
Replace an entire row by ID
Row Locking
Method
Returns
Description
isRowLocked(rowOrId)
boolean
Check if a row is locked
getRowLockInfo(rowOrId)
RowLockInfo | null
Get lock information for a row
lockRowById(id, lockerInfo?)
boolean
Lock a row externally (e.g., from WebSocket message)
unlockRowById(id)
boolean
Unlock an externally locked row
getExternalLocks()
Map<unknown, RowLockInfo>
Get all external locks
clearExternalLocks()
void
Remove all external locks
Row Selection
Method
Returns
Description
selectRow(rowIndex, mode?)
void
Select a row. Mode: 'replace' (default), 'toggle', 'range'
selectRowRange(fromIndex, toIndex)
void
Select a range of rows
clearSelection()
void
Clear row selection
isRowSelected(rowIndex)
boolean
Check if a row is selected
getSelectedRowsData()
T[]
Get data for all selected rows
copySelectedRowsToClipboard()
Promise<boolean>
Copy selected rows as TSV to clipboard
Cell Selection
Method
Returns
Description
selectCellRange(range)
void
Select a cell range programmatically
clearCellSelection()
void
Clear cell selection
getSelectedCells()
Array<{ row, rowIndex, colIndex, field, value }>
Get data for all selected cells
copyCellSelectionToClipboard()
Promise<boolean>
Copy selected cells as TSV to clipboard
Column Width
Method
Returns
Description
setColumnWidth(field, width)
void
Set width of a single column
setColumnWidths(widths)
void
Set widths for multiple columns. widths: ColumnWidthState[]
getColumnWidthsState()
ColumnWidthState[]
Get current widths of all columns
Column Order
Method
Returns
Description
setColumnOrder(order)
void
Set column order. order: ColumnOrderState[]
getColumnOrderState()
ColumnOrderState[]
Get current column order
Styling
CSS Variable Architecture
WebGrid uses a two-level CSS variable system:
web-grid {
/* Override component variables directly */--wg-accent-color:#10b981;
--wg-header-bg:#f5f5f5;
}
/* Or set base variables for all KeenMate components */:root {
--base-accent-color:#10b981;
--base-layer-1:#ffffff;
}
Each --wg-* variable falls back to a --base-* variable, then to a hardcoded default:
baseVariables — Theme variables (--base-*) the component consumes from @keenmate/theme-designer
componentVariables — Component-specific variables (--wg-*) with category and usage descriptions
Dark Mode
Dark mode is triggered automatically by:
OS preference: @media (prefers-color-scheme: dark)
Attribute: data-theme="dark" on any ancestor or host element
Bootstrap: data-bs-theme="dark" on any ancestor or host element
Class: .dark on any ancestor (Tailwind CSS)
To force light mode when the OS is in dark mode, set one of:
Attribute: data-theme="light" on any ancestor or host element
Bootstrap: data-bs-theme="light" on any ancestor or host element
Class: .light on any ancestor (Tailwind CSS)
This is useful when your app manages its own theme (e.g. via a toggle) and needs to override the OS preference. The light mode selectors restore the --base-* fallback chain so theme-designer values are respected.
Ancestor detection uses :host-context() to cross shadow DOM boundaries (Chrome 88+, Firefox 128+, Safari 15.4+).