@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,375 @@
|
|
|
1
|
+
SORTING, FILTERING, AND PAGINATION
|
|
2
|
+
===================================
|
|
3
|
+
Reference for @keenmate/web-grid sorting, filtering, pagination, server-side
|
|
4
|
+
data, and summary bar features. All properties are set via JavaScript on the
|
|
5
|
+
<web-grid> element. See basic-setup.txt for installation and imports.
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
SORTING
|
|
9
|
+
-------
|
|
10
|
+
Sorting is controlled by two grid-level properties and one per-column flag.
|
|
11
|
+
|
|
12
|
+
Grid properties:
|
|
13
|
+
sortMode String. Default: 'none'. Values: 'none', 'single', 'multi'.
|
|
14
|
+
'none' disables sorting entirely.
|
|
15
|
+
'single' allows one column sorted at a time.
|
|
16
|
+
'multi' allows multiple columns sorted simultaneously.
|
|
17
|
+
|
|
18
|
+
sort Array of SortState objects. Default: [].
|
|
19
|
+
Each entry: { column: string, direction: 'asc' | 'desc' }
|
|
20
|
+
Set this to provide initial sort or reflect server-side sort.
|
|
21
|
+
Readable and writable at any time.
|
|
22
|
+
|
|
23
|
+
Per-column flag:
|
|
24
|
+
isSortable Boolean on column definition. When set, this column participates
|
|
25
|
+
in sorting when the grid's sortMode is 'single' or 'multi'.
|
|
26
|
+
|
|
27
|
+
User interaction:
|
|
28
|
+
- Click a sortable column header: sets that column as the sole sort (asc).
|
|
29
|
+
- Click the same header again: toggles to desc.
|
|
30
|
+
- Click the same header a third time: clears sorting.
|
|
31
|
+
- Ctrl+Click (or Cmd+Click) in multi mode: adds the column to the existing
|
|
32
|
+
sort. If already sorted asc, toggles to desc. If already desc, removes it.
|
|
33
|
+
|
|
34
|
+
When sorting changes, the grid resets currentPage to 1 and fires the
|
|
35
|
+
ondatarequest callback with trigger='sort'.
|
|
36
|
+
|
|
37
|
+
Client-side sorting (default): the grid sorts items automatically using
|
|
38
|
+
localeCompare for strings, numeric comparison for numbers, and string
|
|
39
|
+
coercion as fallback. Multi-column sort compares columns in priority order.
|
|
40
|
+
|
|
41
|
+
Server-side sorting: set ondatarequest to fetch data from your server.
|
|
42
|
+
The detail.sort array tells you the requested sort state.
|
|
43
|
+
|
|
44
|
+
Example -- enable multi-column sort with initial state:
|
|
45
|
+
|
|
46
|
+
const grid = document.getElementById('grid')
|
|
47
|
+
grid.sortMode = 'multi'
|
|
48
|
+
grid.sort = [
|
|
49
|
+
{ column: 'lastName', direction: 'asc' },
|
|
50
|
+
{ column: 'firstName', direction: 'asc' }
|
|
51
|
+
]
|
|
52
|
+
grid.columns = [
|
|
53
|
+
{ field: 'lastName', title: 'Last Name', isSortable: true },
|
|
54
|
+
{ field: 'firstName', title: 'First Name', isSortable: true },
|
|
55
|
+
{ field: 'notes', title: 'Notes', isSortable: false }
|
|
56
|
+
]
|
|
57
|
+
|
|
58
|
+
Sort state type:
|
|
59
|
+
|
|
60
|
+
type SortState = {
|
|
61
|
+
column: string // field name
|
|
62
|
+
direction: 'asc' | 'desc'
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
FILTERING
|
|
67
|
+
---------
|
|
68
|
+
Filtering adds a text input below each column header. Typing into it filters
|
|
69
|
+
rows by that column using case-insensitive substring matching.
|
|
70
|
+
|
|
71
|
+
Grid property:
|
|
72
|
+
isFilterable Boolean. Default: false. Set to true to show filter row.
|
|
73
|
+
|
|
74
|
+
Per-column flag:
|
|
75
|
+
isFilterable Boolean on column definition. Set to false to hide the filter
|
|
76
|
+
input for a specific column while the grid-level filter row is
|
|
77
|
+
visible.
|
|
78
|
+
|
|
79
|
+
Client-side filtering (default): the grid filters items before sorting and
|
|
80
|
+
pagination. For each active filter, the cell value is converted to a string
|
|
81
|
+
and tested for case-insensitive inclusion of the filter text.
|
|
82
|
+
|
|
83
|
+
Server-side filtering: use the ondatarequest callback. The filters are
|
|
84
|
+
currently managed client-side in the grid's internal state. For server-side
|
|
85
|
+
filtering, listen to ondatarequest and apply your own filter logic on the
|
|
86
|
+
server.
|
|
87
|
+
|
|
88
|
+
Example:
|
|
89
|
+
|
|
90
|
+
grid.isFilterable = true
|
|
91
|
+
grid.columns = [
|
|
92
|
+
{ field: 'name', title: 'Name', isFilterable: true },
|
|
93
|
+
{ field: 'email', title: 'Email', isFilterable: true },
|
|
94
|
+
{ field: 'actions', title: '', isFilterable: false }
|
|
95
|
+
]
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
PAGINATION
|
|
99
|
+
----------
|
|
100
|
+
Pagination splits items across pages. Supports both client-side slicing and
|
|
101
|
+
server-side data fetching.
|
|
102
|
+
|
|
103
|
+
Grid properties:
|
|
104
|
+
|
|
105
|
+
isPageable Boolean. Default: false. Enables pagination.
|
|
106
|
+
|
|
107
|
+
pageSize Number. Default: 10. Rows displayed per page.
|
|
108
|
+
|
|
109
|
+
pageSizes Number array. Available page sizes for the selector
|
|
110
|
+
dropdown. Example: [10, 25, 50, 100]. If not set,
|
|
111
|
+
the page size selector is not shown.
|
|
112
|
+
|
|
113
|
+
paginationMode String. Default: 'client'.
|
|
114
|
+
'client' -- the grid holds all items and slices them.
|
|
115
|
+
'server' -- items property contains only the current
|
|
116
|
+
page; the grid does not slice.
|
|
117
|
+
|
|
118
|
+
currentPage Number. Default: 1. Current page, 1-based. Writable.
|
|
119
|
+
Changing it triggers re-render. Automatically reset to
|
|
120
|
+
1 when sort or pageSize changes.
|
|
121
|
+
|
|
122
|
+
totalItems Number or null. Default: null. Required for server-side
|
|
123
|
+
pagination so the grid can calculate total pages. For
|
|
124
|
+
client-side mode, the grid uses items.length.
|
|
125
|
+
|
|
126
|
+
showPagination Boolean or 'auto'. Default: 'auto'.
|
|
127
|
+
true -- always show pagination bar.
|
|
128
|
+
false -- never show.
|
|
129
|
+
'auto' -- hide when total pages is 1 or less.
|
|
130
|
+
|
|
131
|
+
paginationPosition String. Default: 'bottom-center'. Positions the
|
|
132
|
+
pagination bar. Use pipe to show in multiple positions.
|
|
133
|
+
Values: 'top-left', 'top-center', 'top-right',
|
|
134
|
+
'bottom-left', 'bottom-center', 'bottom-right'.
|
|
135
|
+
Example: 'top-right|bottom-right' shows pagination in
|
|
136
|
+
both top-right and bottom-right corners.
|
|
137
|
+
|
|
138
|
+
paginationLayout String. Controls element order inside the pagination bar.
|
|
139
|
+
Default: 'pageSize|previous|pageInfo|next'.
|
|
140
|
+
Elements: 'first', 'previous', 'pageInfo', 'next',
|
|
141
|
+
'last', 'pageSize'.
|
|
142
|
+
Example: 'first|previous|pageInfo|next|last' shows
|
|
143
|
+
first/last buttons.
|
|
144
|
+
Example: 'pageSize|previous|pageInfo|next' shows page
|
|
145
|
+
size selector on the left.
|
|
146
|
+
|
|
147
|
+
paginationLabelsCallback
|
|
148
|
+
Function. Receives context, returns partial label
|
|
149
|
+
overrides for i18n/localization.
|
|
150
|
+
|
|
151
|
+
Type: (context: PaginationLabelsContext) => Partial<PaginationLabels>
|
|
152
|
+
|
|
153
|
+
PaginationLabelsContext:
|
|
154
|
+
{ currentPage, totalPages, totalItems, pageSize }
|
|
155
|
+
|
|
156
|
+
PaginationLabels fields:
|
|
157
|
+
first, previous, next, last, pageInfo, itemCount,
|
|
158
|
+
perPage
|
|
159
|
+
|
|
160
|
+
Example -- client-side pagination:
|
|
161
|
+
|
|
162
|
+
grid.isPageable = true
|
|
163
|
+
grid.pageSize = 25
|
|
164
|
+
grid.pageSizes = [10, 25, 50, 100]
|
|
165
|
+
grid.paginationPosition = 'bottom-center'
|
|
166
|
+
grid.paginationLayout = 'pageSize|previous|pageInfo|next'
|
|
167
|
+
grid.items = allData // grid slices automatically
|
|
168
|
+
|
|
169
|
+
Example -- custom pagination labels (i18n):
|
|
170
|
+
|
|
171
|
+
grid.paginationLabelsCallback = (ctx) => ({
|
|
172
|
+
first: 'Prvni',
|
|
173
|
+
previous: 'Predchozi',
|
|
174
|
+
next: 'Dalsi',
|
|
175
|
+
last: 'Posledni',
|
|
176
|
+
pageInfo: `Strana ${ctx.currentPage} z ${ctx.totalPages}`,
|
|
177
|
+
itemCount: `${ctx.totalItems} polozek`,
|
|
178
|
+
perPage: 'na stranku'
|
|
179
|
+
})
|
|
180
|
+
|
|
181
|
+
Static labels can also be set via the labels property:
|
|
182
|
+
|
|
183
|
+
grid.labels = {
|
|
184
|
+
paginationFirst: 'First',
|
|
185
|
+
paginationPrevious: 'Previous',
|
|
186
|
+
paginationNext: 'Next',
|
|
187
|
+
paginationLast: 'Last',
|
|
188
|
+
paginationPageInfo: 'Page {current} of {total}',
|
|
189
|
+
paginationItemCount: '{count} items',
|
|
190
|
+
paginationPerPage: 'per page'
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
SERVER-SIDE DATA (ondatarequest)
|
|
195
|
+
--------------------------------
|
|
196
|
+
The ondatarequest callback fires whenever the user changes sort, page, or
|
|
197
|
+
page size. Use it to fetch data from a server.
|
|
198
|
+
|
|
199
|
+
grid.ondatarequest = (detail) => {
|
|
200
|
+
// detail is DataRequestDetail
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
DataRequestDetail type:
|
|
204
|
+
|
|
205
|
+
{
|
|
206
|
+
sort: SortState[] // Current sort state array
|
|
207
|
+
page: number // Requested page (1-based)
|
|
208
|
+
pageSize: number // Requested page size
|
|
209
|
+
trigger: DataRequestTrigger // What caused the request
|
|
210
|
+
mode: DataRequestMode // How to handle the response
|
|
211
|
+
skip: number // Items to skip (offset for server query)
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
DataRequestTrigger: 'sort' | 'page' | 'pageSize' | 'init' | 'loadMore'
|
|
215
|
+
DataRequestMode: 'replace' | 'append'
|
|
216
|
+
|
|
217
|
+
The trigger field tells you what changed:
|
|
218
|
+
'sort' -- user clicked a sortable header
|
|
219
|
+
'page' -- user navigated to a different page
|
|
220
|
+
'pageSize' -- user changed the page size selector
|
|
221
|
+
'init' -- initial data load (if triggered programmatically)
|
|
222
|
+
'loadMore' -- infinite scroll requested more items
|
|
223
|
+
|
|
224
|
+
The mode field tells you how to apply the response:
|
|
225
|
+
'replace' -- replace grid.items with the response (normal case)
|
|
226
|
+
'append' -- append to grid.items (infinite scroll / loadMore)
|
|
227
|
+
|
|
228
|
+
The skip field is a convenience for SQL OFFSET. For pagination it equals
|
|
229
|
+
(page - 1) * pageSize. For loadMore it equals the current items.length.
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
SERVER-SIDE PAGINATION SETUP
|
|
233
|
+
-----------------------------
|
|
234
|
+
To use server-side pagination:
|
|
235
|
+
|
|
236
|
+
1. Set paginationMode to 'server'.
|
|
237
|
+
2. Set totalItems to the total row count from the server.
|
|
238
|
+
3. Set items to only the current page of data.
|
|
239
|
+
4. Implement ondatarequest to fetch new pages from the server.
|
|
240
|
+
|
|
241
|
+
Example:
|
|
242
|
+
|
|
243
|
+
const grid = document.getElementById('grid')
|
|
244
|
+
|
|
245
|
+
// Configure grid
|
|
246
|
+
grid.columns = [
|
|
247
|
+
{ field: 'id', title: 'ID', width: '80px', isSortable: true },
|
|
248
|
+
{ field: 'name', title: 'Name', isSortable: true },
|
|
249
|
+
{ field: 'email', title: 'Email', isSortable: true }
|
|
250
|
+
]
|
|
251
|
+
grid.isPageable = true
|
|
252
|
+
grid.pageSize = 25
|
|
253
|
+
grid.pageSizes = [10, 25, 50, 100]
|
|
254
|
+
grid.paginationMode = 'server'
|
|
255
|
+
grid.sortMode = 'single'
|
|
256
|
+
grid.paginationLayout = 'pageSize|first|previous|pageInfo|next|last'
|
|
257
|
+
|
|
258
|
+
// Fetch data from server
|
|
259
|
+
async function loadData(detail) {
|
|
260
|
+
const params = new URLSearchParams({
|
|
261
|
+
page: detail.page,
|
|
262
|
+
pageSize: detail.pageSize,
|
|
263
|
+
skip: detail.skip
|
|
264
|
+
})
|
|
265
|
+
|
|
266
|
+
// Add sort parameters
|
|
267
|
+
if (detail.sort.length > 0) {
|
|
268
|
+
params.set('sortField', detail.sort[0].column)
|
|
269
|
+
params.set('sortDir', detail.sort[0].direction)
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
const response = await fetch(`/api/users?${params}`)
|
|
273
|
+
const data = await response.json()
|
|
274
|
+
|
|
275
|
+
grid.items = data.rows // Current page only
|
|
276
|
+
grid.totalItems = data.total // Total count for pagination
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// Handle user interactions (page change, sort, page size change)
|
|
280
|
+
grid.ondatarequest = (detail) => {
|
|
281
|
+
loadData(detail)
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// Initial load
|
|
285
|
+
loadData({ page: 1, pageSize: 25, skip: 0, sort: [], trigger: 'init', mode: 'replace' })
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
SUMMARY BAR
|
|
289
|
+
-----------
|
|
290
|
+
The summary bar displays custom content (totals, aggregates, metadata) in a
|
|
291
|
+
configurable position, optionally sharing a row with the pagination bar.
|
|
292
|
+
|
|
293
|
+
Grid properties:
|
|
294
|
+
|
|
295
|
+
summaryPosition String. Position(s) for the summary bar.
|
|
296
|
+
Values: 'top-left', 'top-center', 'top-right',
|
|
297
|
+
'bottom-left', 'bottom-center', 'bottom-right'.
|
|
298
|
+
Use pipe for multiple: 'top-right|bottom-right'.
|
|
299
|
+
Not set by default (no summary shown).
|
|
300
|
+
|
|
301
|
+
summaryContentCallback
|
|
302
|
+
Function returning an HTML string for the summary.
|
|
303
|
+
Receives a SummaryContext object.
|
|
304
|
+
|
|
305
|
+
Type: (context: SummaryContext) => string
|
|
306
|
+
|
|
307
|
+
SummaryContext:
|
|
308
|
+
{
|
|
309
|
+
items: T[] // Current page items
|
|
310
|
+
allItems: T[] // All items (before pagination)
|
|
311
|
+
totalItems: number
|
|
312
|
+
currentPage: number
|
|
313
|
+
pageSize: number
|
|
314
|
+
metadata: unknown // From summaryMetadata property
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
isSummaryInline Boolean. Default: true. When true, the summary shares
|
|
318
|
+
the same row as the pagination bar when they are in the
|
|
319
|
+
same area (e.g., both at bottom). When false, the summary
|
|
320
|
+
gets its own row.
|
|
321
|
+
|
|
322
|
+
summaryMetadata Any value. Default: undefined. Server-provided metadata
|
|
323
|
+
passed through to summaryContentCallback as
|
|
324
|
+
context.metadata. Useful for server-calculated aggregates
|
|
325
|
+
(sums, averages) that cannot be computed client-side.
|
|
326
|
+
|
|
327
|
+
Example -- client-side summary:
|
|
328
|
+
|
|
329
|
+
grid.summaryPosition = 'bottom-left'
|
|
330
|
+
grid.summaryContentCallback = (ctx) => {
|
|
331
|
+
const total = ctx.allItems.reduce((sum, row) => sum + row.amount, 0)
|
|
332
|
+
return `<strong>Total:</strong> $${total.toFixed(2)} (${ctx.totalItems} rows)`
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
Example -- server-side summary with metadata:
|
|
336
|
+
|
|
337
|
+
grid.summaryPosition = 'bottom-right'
|
|
338
|
+
grid.summaryContentCallback = (ctx) => {
|
|
339
|
+
const meta = ctx.metadata
|
|
340
|
+
if (!meta) return ''
|
|
341
|
+
return `Sum: $${meta.sum} | Avg: $${meta.average}`
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// In your ondatarequest handler:
|
|
345
|
+
grid.ondatarequest = async (detail) => {
|
|
346
|
+
const response = await fetch(`/api/data?page=${detail.page}`)
|
|
347
|
+
const data = await response.json()
|
|
348
|
+
grid.items = data.rows
|
|
349
|
+
grid.totalItems = data.total
|
|
350
|
+
grid.summaryMetadata = data.aggregates // { sum: 12345, average: 67.8 }
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
Example -- summary with pagination in same row:
|
|
354
|
+
|
|
355
|
+
grid.isPageable = true
|
|
356
|
+
grid.paginationPosition = 'bottom-right'
|
|
357
|
+
grid.summaryPosition = 'bottom-left'
|
|
358
|
+
grid.isSummaryInline = true // shares the bottom row with pagination
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
DATA PIPELINE ORDER
|
|
362
|
+
-------------------
|
|
363
|
+
When using client-side mode, the grid processes items in this order:
|
|
364
|
+
|
|
365
|
+
items --> filteredItems --> sortedItems --> paginatedItems --> displayItems
|
|
366
|
+
|
|
367
|
+
1. filteredItems: applies column filters (case-insensitive substring match)
|
|
368
|
+
2. sortedItems: applies multi-column sort using localeCompare / numeric compare
|
|
369
|
+
3. paginatedItems: slices by currentPage and pageSize (client mode only;
|
|
370
|
+
server mode skips slicing)
|
|
371
|
+
4. displayItems: adds empty row if isNewRowEnabled
|
|
372
|
+
|
|
373
|
+
For server-side mode, the grid trusts that items already represents the
|
|
374
|
+
correct page with correct sort and filter applied. Set items, totalItems,
|
|
375
|
+
and optionally summaryMetadata after each server fetch.
|
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
CSS VARIABLE ARCHITECTURE
|
|
2
|
+
-------------------------
|
|
3
|
+
WebGrid uses a two-level CSS variable system for theming:
|
|
4
|
+
|
|
5
|
+
Level 1: --base-* variables (cross-component theme)
|
|
6
|
+
Level 2: --wg-* variables (grid-specific)
|
|
7
|
+
|
|
8
|
+
Each --wg-* variable falls back to a --base-* variable, then to a hardcoded default:
|
|
9
|
+
|
|
10
|
+
:host {
|
|
11
|
+
--wg-accent-color: var(--base-accent-color, #0078d4);
|
|
12
|
+
--wg-surface-1: var(--base-main-bg, #ffffff);
|
|
13
|
+
--wg-font-size-base: calc(var(--base-font-size-sm, 1.4) * var(--wg-rem));
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
Priority chain:
|
|
17
|
+
1. Setting --wg-accent-color on <web-grid> overrides ONLY the grid
|
|
18
|
+
2. Setting --base-accent-color on :root themes ALL KeenMate components
|
|
19
|
+
(web-grid, web-multiselect, web-daterangepicker, etc.)
|
|
20
|
+
3. If neither is set, the hardcoded default (#0078d4) is used
|
|
21
|
+
|
|
22
|
+
Example -- theme all KeenMate components at once:
|
|
23
|
+
|
|
24
|
+
:root {
|
|
25
|
+
--base-accent-color: #10b981;
|
|
26
|
+
--base-layer-1: #ffffff;
|
|
27
|
+
--base-font-family: 'Inter', sans-serif;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
Example -- override just the grid:
|
|
31
|
+
|
|
32
|
+
web-grid {
|
|
33
|
+
--wg-accent-color: #e11d48;
|
|
34
|
+
--wg-header-bg: #fafafa;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
Example -- override a specific grid instance:
|
|
38
|
+
|
|
39
|
+
web-grid#sales-grid {
|
|
40
|
+
--wg-accent-color: #7c3aed;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
The --wg-rem variable controls the base sizing unit. Default is 10px. All spacing,
|
|
44
|
+
font sizes, and component dimensions scale proportionally from this value. Set to
|
|
45
|
+
1rem for frameworks like Pure Admin where html { font-size: 10px }.
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
KEY VARIABLES
|
|
49
|
+
-------------
|
|
50
|
+
Colors:
|
|
51
|
+
--wg-accent-color Primary accent (focus outlines, active states, selection)
|
|
52
|
+
Falls back to: --base-accent-color, default: #0078d4
|
|
53
|
+
--wg-text-color-1 Primary text (cells, headers)
|
|
54
|
+
Falls back to: --base-text-color-1, default: #242424
|
|
55
|
+
--wg-text-color-2 Secondary text (row numbers, labels)
|
|
56
|
+
--wg-text-color-3 Muted text (placeholders, empty states)
|
|
57
|
+
--wg-surface-1 Main background (table body, cells)
|
|
58
|
+
Falls back to: --base-main-bg, default: #ffffff
|
|
59
|
+
--wg-surface-2 Elevated background (headers, striped rows)
|
|
60
|
+
Falls back to: --base-elevated-bg, default: #f5f5f5
|
|
61
|
+
--wg-surface-3 Hover background (row hover, button hover)
|
|
62
|
+
--wg-border-color All borders (table, cells, header separators)
|
|
63
|
+
Falls back to: --base-border-color, default: #e0e0e0
|
|
64
|
+
|
|
65
|
+
Header:
|
|
66
|
+
--wg-header-bg Header row background (defaults to --wg-surface-2)
|
|
67
|
+
--wg-header-bg-hover Sortable header on hover
|
|
68
|
+
--wg-header-color Header text color
|
|
69
|
+
--wg-header-border Bottom border below header (2px solid)
|
|
70
|
+
--wg-header-padding Header cell padding
|
|
71
|
+
--wg-header-font-weight Header text weight (semibold)
|
|
72
|
+
|
|
73
|
+
Rows:
|
|
74
|
+
--wg-row-bg-hover Row hover when isHoverable=true (defaults to --wg-surface-3)
|
|
75
|
+
--wg-row-bg-even Striped even rows when isStriped=true (defaults to --wg-surface-2)
|
|
76
|
+
--wg-row-border Border between rows
|
|
77
|
+
|
|
78
|
+
Typography:
|
|
79
|
+
--wg-font-family Font for all grid text
|
|
80
|
+
Falls back to: --base-font-family, default: inherit
|
|
81
|
+
--wg-font-size-base Cell and editor text (14px at default scale)
|
|
82
|
+
--wg-font-size-sm Filter inputs, pagination (12px)
|
|
83
|
+
--wg-font-size-xs Error messages, small labels (11px)
|
|
84
|
+
|
|
85
|
+
Layout:
|
|
86
|
+
--wg-cell-padding Cell content padding (shorthand)
|
|
87
|
+
--wg-cell-padding-block Cell vertical padding
|
|
88
|
+
--wg-cell-padding-inline Cell horizontal padding
|
|
89
|
+
--wg-border-radius-sm Buttons, inputs (4px)
|
|
90
|
+
--wg-border-radius-md Dialogs, cards (6px)
|
|
91
|
+
--wg-border-radius-lg Large elements (8px)
|
|
92
|
+
|
|
93
|
+
The full list is 120+ variables. See _variables.css or the manifest for the complete set.
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
DARK MODE
|
|
97
|
+
---------
|
|
98
|
+
Dark mode activates automatically. No configuration needed. Detection methods:
|
|
99
|
+
|
|
100
|
+
1. OS preference: @media (prefers-color-scheme: dark)
|
|
101
|
+
2. Data attribute: <html data-theme="dark">
|
|
102
|
+
3. Bootstrap 5.3+: <html data-bs-theme="dark">
|
|
103
|
+
4. Tailwind CSS: <html class="dark">
|
|
104
|
+
|
|
105
|
+
All four methods apply the same dark palette overrides:
|
|
106
|
+
|
|
107
|
+
--wg-surface-1: #1f1f1f
|
|
108
|
+
--wg-surface-2: #2b2b2b
|
|
109
|
+
--wg-surface-3: #333333
|
|
110
|
+
--wg-text-color-1: #e0e0e0
|
|
111
|
+
--wg-text-color-2: #c0c0c0
|
|
112
|
+
--wg-text-color-3: #a0a0a0
|
|
113
|
+
--wg-border-color: #3d3d3d
|
|
114
|
+
--wg-input-bg: #1f1f1f
|
|
115
|
+
--wg-hover-bg: #3a3a3a
|
|
116
|
+
--wg-danger-color: #f87c86
|
|
117
|
+
--wg-danger-bg-light: #442726
|
|
118
|
+
|
|
119
|
+
The attribute can be on the <web-grid> element itself or any ancestor:
|
|
120
|
+
|
|
121
|
+
<web-grid data-theme="dark"> (on the element)
|
|
122
|
+
<div data-bs-theme="dark"> (on an ancestor)
|
|
123
|
+
<web-grid></web-grid>
|
|
124
|
+
</div>
|
|
125
|
+
|
|
126
|
+
If --base-* variables are set by a theme-designer dark theme, those take priority
|
|
127
|
+
over the built-in dark mode overrides since --wg-* variables reference --base-*
|
|
128
|
+
variables.
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
DYNAMIC CELL AND ROW STYLING
|
|
132
|
+
-----------------------------
|
|
133
|
+
Three mechanisms for applying CSS classes to cells and rows:
|
|
134
|
+
|
|
135
|
+
1. cellClass (static, per column):
|
|
136
|
+
|
|
137
|
+
grid.columns = [{
|
|
138
|
+
field: 'status',
|
|
139
|
+
cellClass: 'status-cell'
|
|
140
|
+
}]
|
|
141
|
+
|
|
142
|
+
2. cellClassCallback (dynamic, per column):
|
|
143
|
+
|
|
144
|
+
grid.columns = [{
|
|
145
|
+
field: 'salary',
|
|
146
|
+
cellClassCallback: (value, row) => value > 90000 ? 'high-value' : null
|
|
147
|
+
}]
|
|
148
|
+
|
|
149
|
+
3. rowClassCallback (dynamic, per row):
|
|
150
|
+
|
|
151
|
+
grid.rowClassCallback = (row, index) => {
|
|
152
|
+
if (row.status === 'inactive') return 'row-inactive'
|
|
153
|
+
if (row.isNew) return 'row-new'
|
|
154
|
+
return null
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
IMPORTANT: These classes are applied INSIDE the Shadow DOM. External stylesheets
|
|
158
|
+
cannot reach them. You MUST use customStylesCallback to define the CSS rules for
|
|
159
|
+
these classes. Without it, the classes are added to elements but have no effect.
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
CUSTOM STYLES INJECTION
|
|
163
|
+
------------------------
|
|
164
|
+
The customStylesCallback property returns a CSS string that gets injected into the
|
|
165
|
+
Shadow DOM. This is the ONLY way to style custom classes from cellClassCallback or
|
|
166
|
+
rowClassCallback.
|
|
167
|
+
|
|
168
|
+
grid.customStylesCallback = () => `
|
|
169
|
+
.high-value {
|
|
170
|
+
background: #d1fae5 !important;
|
|
171
|
+
font-weight: 600;
|
|
172
|
+
}
|
|
173
|
+
.row-inactive {
|
|
174
|
+
opacity: 0.5;
|
|
175
|
+
}
|
|
176
|
+
.row-new {
|
|
177
|
+
background: #fef3c7 !important;
|
|
178
|
+
}
|
|
179
|
+
.status-cell {
|
|
180
|
+
text-transform: uppercase;
|
|
181
|
+
letter-spacing: 0.05em;
|
|
182
|
+
}
|
|
183
|
+
`
|
|
184
|
+
|
|
185
|
+
Notes:
|
|
186
|
+
- The callback is called during rendering and should return a CSS string
|
|
187
|
+
- Use !important on background to override hover/striped/selection styles
|
|
188
|
+
- The injected styles have access to all internal CSS classes (.wg__cell, etc.)
|
|
189
|
+
- This can also be used for general style overrides beyond custom classes
|
|
190
|
+
|
|
191
|
+
Full example combining all three:
|
|
192
|
+
|
|
193
|
+
grid.columns = [
|
|
194
|
+
{
|
|
195
|
+
field: 'salary',
|
|
196
|
+
cellClass: 'numeric',
|
|
197
|
+
cellClassCallback: (value, row) => {
|
|
198
|
+
if (value > 100000) return 'salary-high'
|
|
199
|
+
if (value < 30000) return 'salary-low'
|
|
200
|
+
return null
|
|
201
|
+
}
|
|
202
|
+
},
|
|
203
|
+
{
|
|
204
|
+
field: 'status',
|
|
205
|
+
cellClassCallback: (value) => 'status-' + value
|
|
206
|
+
}
|
|
207
|
+
]
|
|
208
|
+
|
|
209
|
+
grid.rowClassCallback = (row, index) =>
|
|
210
|
+
row.deleted ? 'row-deleted' : null
|
|
211
|
+
|
|
212
|
+
grid.customStylesCallback = () => `
|
|
213
|
+
.numeric { text-align: right; font-variant-numeric: tabular-nums; }
|
|
214
|
+
.salary-high { background: #d1fae5 !important; }
|
|
215
|
+
.salary-low { background: #fee2e2 !important; }
|
|
216
|
+
.status-active { color: #059669; }
|
|
217
|
+
.status-inactive { color: #dc2626; }
|
|
218
|
+
.row-deleted { text-decoration: line-through; opacity: 0.5; }
|
|
219
|
+
`
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
COMPONENT VARIABLES MANIFEST
|
|
223
|
+
-----------------------------
|
|
224
|
+
A machine-readable JSON manifest documents all CSS variables consumed and exposed
|
|
225
|
+
by the component. It is included in the npm package.
|
|
226
|
+
|
|
227
|
+
Import path:
|
|
228
|
+
|
|
229
|
+
import manifest from '@keenmate/web-grid/manifest'
|
|
230
|
+
|
|
231
|
+
Manifest structure:
|
|
232
|
+
|
|
233
|
+
manifest.component "@keenmate/web-grid"
|
|
234
|
+
manifest.prefix "wg"
|
|
235
|
+
manifest.baseVariables Array of --base-* variables the component consumes
|
|
236
|
+
manifest.componentVariables Array of --wg-* variables the component exposes
|
|
237
|
+
|
|
238
|
+
baseVariables entry format:
|
|
239
|
+
|
|
240
|
+
{
|
|
241
|
+
"name": "base-accent-color",
|
|
242
|
+
"required": true,
|
|
243
|
+
"usage": "Editor focus outline, active sort indicator, selected pagination button"
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
componentVariables entry format:
|
|
247
|
+
|
|
248
|
+
{
|
|
249
|
+
"name": "wg-accent-color",
|
|
250
|
+
"category": "accent",
|
|
251
|
+
"usage": "Editor focus outline, active pagination, sorted column indicator"
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
Counts (current):
|
|
255
|
+
- 37 baseVariables (--base-* consumed)
|
|
256
|
+
- 120+ componentVariables (--wg-* exposed)
|
|
257
|
+
|
|
258
|
+
Categories in componentVariables:
|
|
259
|
+
sizing, accent, text, surface, border, input, danger, state, typography,
|
|
260
|
+
radius, spacing, table, header, cell, row, filter, sort, pagination, empty,
|
|
261
|
+
error, editor, dropdown, toolbar, inline-actions, overlay, tooltip,
|
|
262
|
+
context-menu, focus, selection, row-focus, cell-selection, row-locking,
|
|
263
|
+
freeze, resize, fill-handle, transition, z-index
|
|
264
|
+
|
|
265
|
+
The manifest follows the component-variables schema:
|
|
266
|
+
https://raw.githubusercontent.com/keenmate/schemas/main/component-variables.schema.json
|
|
267
|
+
|
|
268
|
+
This manifest is consumed by @keenmate/theme-designer for visual theming.
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
THEME DESIGNER
|
|
272
|
+
--------------
|
|
273
|
+
Visual theming tool for all KeenMate components:
|
|
274
|
+
|
|
275
|
+
https://theme-designer.keenmate.dev
|
|
276
|
+
|
|
277
|
+
The Theme Designer:
|
|
278
|
+
- Reads the component-variables manifest to know which variables exist
|
|
279
|
+
- Provides a visual UI for adjusting --base-* variables
|
|
280
|
+
- Generates CSS that sets --base-* on :root
|
|
281
|
+
- Changes propagate to all KeenMate components using the fallback chain
|
|
282
|
+
|
|
283
|
+
Workflow:
|
|
284
|
+
1. Open theme-designer.keenmate.dev
|
|
285
|
+
2. Adjust colors, typography, spacing visually
|
|
286
|
+
3. Export generated CSS
|
|
287
|
+
4. Include the CSS in your application
|
|
288
|
+
5. All KeenMate components (web-grid, web-multiselect, etc.) pick up the theme
|
|
289
|
+
|
|
290
|
+
Since --wg-* falls back to --base-*, the theme applies automatically without
|
|
291
|
+
any grid-specific configuration.
|