@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,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.