@zakkster/lite-table 1.0.0 → 1.2.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/CHANGELOG.md +344 -0
- package/README.md +564 -19
- package/Table.d.ts +258 -1
- package/Table.js +1644 -116
- package/llms.txt +291 -6
- package/package.json +15 -9
package/llms.txt
CHANGED
|
@@ -3,6 +3,17 @@
|
|
|
3
3
|
Headless reactive data tables. CSS Grid. Zero-GC scrolling. Built on
|
|
4
4
|
@zakkster/lite-signal, @zakkster/lite-virtual, @zakkster/lite-signal-dom.
|
|
5
5
|
|
|
6
|
+
Feature milestones:
|
|
7
|
+
M1 (v1.0) headless core + DOM mount, virtualized rows on pooled slots,
|
|
8
|
+
sort, selection, resize, reorder, pin, flex.
|
|
9
|
+
M1.1 (v1.1) export (CSV / JSON), scope-dispose leak fix, sort-chain UX.
|
|
10
|
+
M2 (v1.1) cell editing, per-column filtering.
|
|
11
|
+
M3 (v1.2) row grouping (multi-level), per-column aggregation, sticky
|
|
12
|
+
group headers, sticky grand total.
|
|
13
|
+
|
|
14
|
+
All feature additions are opt-in; the ungrouped / non-editing / non-filtering
|
|
15
|
+
fast path is bit-identical to v1.0.
|
|
16
|
+
|
|
6
17
|
## Architecture invariants
|
|
7
18
|
|
|
8
19
|
- No <table>. CSS Grid layout. role=grid / row / columnheader / gridcell.
|
|
@@ -130,10 +141,216 @@ TableCore methods (focus):
|
|
|
130
141
|
rowStart, rowEnd, pageUp, pageDown
|
|
131
142
|
skips hidden columns; follows current sort order
|
|
132
143
|
|
|
144
|
+
TableCore methods (export, M1.1):
|
|
145
|
+
exportCsv(opts?) -> string
|
|
146
|
+
opts.rows: "visible"|"all"|"selected"|Array (default "visible")
|
|
147
|
+
opts.columns: "visible"|"all"|Array<key> (default "visible")
|
|
148
|
+
opts.delimiter: string (default ",")
|
|
149
|
+
opts.quote: string (default '"')
|
|
150
|
+
opts.headers: boolean (default true)
|
|
151
|
+
opts.newline: string (default "\r\n")
|
|
152
|
+
opts.bom: boolean (default false; UTF-8 BOM for Excel-on-Windows)
|
|
153
|
+
opts.formatter: (row, col) => unknown
|
|
154
|
+
RFC 4180 escaping; column accessor honored; numbers stringify naturally;
|
|
155
|
+
null/undefined become empty fields.
|
|
156
|
+
|
|
157
|
+
exportJson(opts?) -> string | object[]
|
|
158
|
+
opts.rows: same as exportCsv
|
|
159
|
+
opts.columns: same as exportCsv
|
|
160
|
+
opts.indent: number (default 0 = compact single-line)
|
|
161
|
+
opts.format: "string"|"array" (default "string"; "array" skips JSON.stringify)
|
|
162
|
+
opts.formatter: (row, col) => unknown
|
|
163
|
+
Fast path: columns:"all"+no formatter returns rows.slice() (shallow).
|
|
164
|
+
|
|
165
|
+
Paginated-getter pitfall: rows:"all" and rows:"selected" resolve against
|
|
166
|
+
rowsGetter(), which for a function-form rows IS the current page. To
|
|
167
|
+
export across the master, pass the master array:
|
|
168
|
+
table.exportCsv({rows: allRows})
|
|
169
|
+
table.exportCsv({rows: table.selectedRows(allRows)})
|
|
170
|
+
|
|
171
|
+
TableCore filtering (M2):
|
|
172
|
+
Column opt-in: { key, filterable: true,
|
|
173
|
+
filter?: (value, query, row) => boolean,
|
|
174
|
+
filterPlaceholder?: string }
|
|
175
|
+
|
|
176
|
+
Default predicate: case-insensitive substring on stringified value.
|
|
177
|
+
Empty / whitespace-only query: filter is treated as inactive (predicate
|
|
178
|
+
not invoked).
|
|
179
|
+
|
|
180
|
+
Reactive surface:
|
|
181
|
+
columnFilters() -> ReadonlyMap<columnKey, queryString>
|
|
182
|
+
filteredRows() -> Computed rows post-filter, pre-sort
|
|
183
|
+
visibleRows() -> Computed rows post-filter AND post-sort
|
|
184
|
+
|
|
185
|
+
Methods:
|
|
186
|
+
setColumnFilter(key, value) value=null/""/whitespace clears that column
|
|
187
|
+
clearColumnFilters() no-op + no notify if already empty
|
|
188
|
+
|
|
189
|
+
Pipeline: rowsGetter -> filteredRows -> visibleRows -> exports/mount
|
|
190
|
+
Multiple active filters apply as AND.
|
|
191
|
+
|
|
192
|
+
DOM (when mounted): a .lt-filter-row is mounted between header + viewport
|
|
193
|
+
if any column is filterable. One <input> per filterable column, two-way
|
|
194
|
+
bound. Escape on input clears that column's filter. Sticky top.
|
|
195
|
+
|
|
196
|
+
TableCore editing (M2):
|
|
197
|
+
Column opt-in: { key, editable: true }
|
|
198
|
+
Config: createTable({ ...,
|
|
199
|
+
onCellEdit: ({row, columnKey, oldValue, newValue}) => ... })
|
|
200
|
+
|
|
201
|
+
lite-table does NOT mutate rows. onCellEdit is the consumer's write hook.
|
|
202
|
+
|
|
203
|
+
Reactive surface:
|
|
204
|
+
editingCell() -> { rowId, columnKey } | null
|
|
205
|
+
editingDraft() -> current in-progress string
|
|
206
|
+
|
|
207
|
+
Methods:
|
|
208
|
+
startEdit(rowId, columnKey) no-op on non-editable; auto-commits
|
|
209
|
+
in-flight on a different cell;
|
|
210
|
+
idempotent on the same cell.
|
|
211
|
+
commitEdit() reads editingDraft
|
|
212
|
+
commitEdit("explicit") commits a specific value
|
|
213
|
+
cancelEdit() discard; no onCellEdit
|
|
214
|
+
isEditing(rowId, columnKey) O(1) predicate
|
|
215
|
+
|
|
216
|
+
Commit-skip: onCellEdit is NOT called when the value is unchanged.
|
|
217
|
+
DOM edits produce string newValue; guard compares
|
|
218
|
+
String(oldValue) !== newValue (so 100 vs "100" is unchanged).
|
|
219
|
+
Explicit commitEdit(explicitValue) falls back to strict ===.
|
|
220
|
+
newValue is ALWAYS a string from the DOM path -- consumers
|
|
221
|
+
coerce in their handler (Number(newValue) / new Date(newValue) /
|
|
222
|
+
newValue === "true" / etc.).
|
|
223
|
+
Handler errors are caught + logged; subsequent edits work.
|
|
224
|
+
|
|
225
|
+
DOM keyboard (when mounted):
|
|
226
|
+
dblclick on editable cell -> startEdit
|
|
227
|
+
F2 / Enter on focused cell with editable column -> startEdit
|
|
228
|
+
Enter -> commit + moveFocus down
|
|
229
|
+
Tab -> commit + moveFocus right (Shift+Tab = left)
|
|
230
|
+
Escape -> cancel + return focus to root
|
|
231
|
+
blur -> commit
|
|
232
|
+
|
|
233
|
+
Edit state survives scroll: keyed on rowId, so contenteditable rebinds
|
|
234
|
+
when the row scrolls back into view. Draft preserved across scroll.
|
|
235
|
+
|
|
236
|
+
TableCore grouping + aggregation (M3):
|
|
237
|
+
Config:
|
|
238
|
+
createTable({ ...,
|
|
239
|
+
groupBy: "region" | ["region","status"] | null,
|
|
240
|
+
initialCollapsedGroups: [["Europe"], ["Asia","Books"]],
|
|
241
|
+
showGrandTotal: true })
|
|
242
|
+
Column opt-in for aggregates:
|
|
243
|
+
{ key, aggregate: "sum"|"avg"|"min"|"max"|"count" | (rows,col)=>any,
|
|
244
|
+
aggregateFormat?: (value, col, count) => string }
|
|
245
|
+
|
|
246
|
+
Aggregates fold over LEAF rows at every depth. Safe default for reducers
|
|
247
|
+
that don't compose associatively (median, last-value-wins, distinct).
|
|
248
|
+
Nullish values are skipped for sum/avg/min/max; count is unconditional.
|
|
249
|
+
aggregateFormat is display-only; entry.aggregates.get(key) stays raw for
|
|
250
|
+
export. Errors in aggregateFormat are caught + logged.
|
|
251
|
+
|
|
252
|
+
Pipeline: rowsGetter -> filteredRows -> groupedRows -> visibleEntries
|
|
253
|
+
Sort applies WITHIN each leaf group. Groups themselves are sorted by
|
|
254
|
+
group key ascending; nulls bucket last. Filter runs BEFORE grouping so
|
|
255
|
+
empty groups vanish.
|
|
256
|
+
|
|
257
|
+
Reactive surface:
|
|
258
|
+
groupBy() -> readonly string[] -- [] = ungrouped
|
|
259
|
+
collapsedGroups() -> ReadonlySet<string> -- path-strings
|
|
260
|
+
groupedRows() -> GroupNode[] | null -- null when ungrouped
|
|
261
|
+
visibleEntries() -> Entry[] -- interleaved rendering feed
|
|
262
|
+
entryCount() -> number -- drives virtual axis
|
|
263
|
+
visibleRows() -> Row[] -- DATA-ONLY, unchanged 1.1 contract
|
|
264
|
+
rowCount() -> number -- DATA-ONLY, unchanged
|
|
265
|
+
|
|
266
|
+
Entry union:
|
|
267
|
+
{ type: "data", row }
|
|
268
|
+
{ type: "group-header", depth, key, value, path, pathStr, count,
|
|
269
|
+
aggregates, isCollapsed }
|
|
270
|
+
{ type: "grand-total", aggregates, count }
|
|
271
|
+
|
|
272
|
+
GroupNode:
|
|
273
|
+
{ depth, key, value, path, pathStr, count,
|
|
274
|
+
aggregates: Map<colKey, any>,
|
|
275
|
+
subGroups: GroupNode[] | null, -- internal nodes only
|
|
276
|
+
rows: Row[] | null -- leaf nodes only
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
Methods:
|
|
280
|
+
setGroupBy(v) idempotent; unknown keys dropped
|
|
281
|
+
toggleGroup(path)
|
|
282
|
+
expandGroup(path) no-op if already expanded
|
|
283
|
+
collapseGroup(path) no-op if already collapsed
|
|
284
|
+
expandAllGroups() clears collapsed set
|
|
285
|
+
collapseAllGroups() walks groupedRows() once
|
|
286
|
+
isGroupCollapsed(path) O(1) predicate
|
|
287
|
+
groupAncestryAt(entryIndex) GroupHeaderEntry[]; for sticky headers.
|
|
288
|
+
Returns strictly-shallower ancestors --
|
|
289
|
+
a depth-0 group-header at entryIndex
|
|
290
|
+
yields [], so sticky doesn't duplicate
|
|
291
|
+
the inline row.
|
|
292
|
+
|
|
293
|
+
Ungrouped fast path: when groupBy() is empty, groupedRows short-circuits
|
|
294
|
+
to null and visibleRows returns sort-applied rows directly. One extra
|
|
295
|
+
signal read of overhead vs 1.1.0.
|
|
296
|
+
|
|
297
|
+
DOM (when mounted):
|
|
298
|
+
Pool-rendered group header:
|
|
299
|
+
<div class="lt-row lt-row-group-header"
|
|
300
|
+
data-depth data-collapsed>
|
|
301
|
+
<div class="lt-cell" data-key>chevron + value + (N)</div>
|
|
302
|
+
<div class="lt-cell" data-key>aggregate or empty</div>
|
|
303
|
+
...
|
|
304
|
+
</div>
|
|
305
|
+
Grand total (pool-rendered): same shape, class="lt-row lt-row-grand-total",
|
|
306
|
+
first cell reads "Total (N)".
|
|
307
|
+
|
|
308
|
+
Sticky group headers (always mounted; hidden when ungrouped):
|
|
309
|
+
viewport > .lt-sticky-groups -- position:sticky; top:rowHeight;
|
|
310
|
+
height:0; z-index:2
|
|
311
|
+
> .lt-row-group-header.lt-sticky-group[data-depth][data-collapsed]
|
|
312
|
+
absolute; top:depth*rowHeight; grid layout inline
|
|
313
|
+
Note: sticky rows do NOT carry .lt-row so querySelectorAll(".lt-row")
|
|
314
|
+
still counts only pool slots.
|
|
315
|
+
|
|
316
|
+
Sticky grand total (always mounted; hidden when showGrandTotal is off):
|
|
317
|
+
viewport > .lt-sticky-grand-total -- position:sticky; bottom:0;
|
|
318
|
+
height:0; z-index:2
|
|
319
|
+
> .lt-row-grand-total.lt-sticky-grand-total-row
|
|
320
|
+
absolute; top:-rowHeight to anchor bottom edge to container top
|
|
321
|
+
|
|
322
|
+
DOM order in viewport:
|
|
323
|
+
.lt-header (sticky top:0)
|
|
324
|
+
.lt-sticky-groups (sticky top:rowHeight, before inner)
|
|
325
|
+
.lt-inner (pool slots absolute translateY inside)
|
|
326
|
+
.lt-sticky-grand-total (sticky bottom:0, after inner)
|
|
327
|
+
|
|
328
|
+
Sticky ancestor source: axis.firstIndex() (from @zakkster/lite-virtual,
|
|
329
|
+
the pre-overscan first-visible entry). Using axis.start() would show
|
|
330
|
+
stale ancestors when scrolling across group boundaries because start
|
|
331
|
+
includes overscan slots ABOVE the viewport.
|
|
332
|
+
|
|
333
|
+
Click on sticky group row: same behavior as inline -- toggleGroup(path).
|
|
334
|
+
Click on grand-total row: ignored (no selection change).
|
|
335
|
+
|
|
336
|
+
Reactive column state carries over:
|
|
337
|
+
Sticky cells register the same colPlacement + pin effects that pool
|
|
338
|
+
cells do. Hiding the first-visible column moves the chevron+label to
|
|
339
|
+
the next; reordering keeps sticky + pool aligned; pin applies to sticky.
|
|
340
|
+
|
|
341
|
+
Zero-GC: 10k boundary scrolls, 100k rows, groupBy:"status",
|
|
342
|
+
showGrandTotal, sticky overlays active. signal-node delta 0,
|
|
343
|
+
link delta 2 (noise), pool delta 0. See bench/03-heap-grouped.js.
|
|
344
|
+
|
|
133
345
|
TableCore lifecycle:
|
|
134
346
|
dispose() releases all reactive nodes
|
|
135
347
|
cellId(rowId, columnKey) -> "lt_<rowId>__<columnKey>"
|
|
136
348
|
|
|
349
|
+
Note: v1.1.0 fixed a latent leak in dispose() (handles were called,
|
|
350
|
+
not freed). After dispose() every reactive node the table allocated
|
|
351
|
+
returns to the registry pool. 50 createTable+dispose cycles round-trip
|
|
352
|
+
cleanly with activeNodes flat.
|
|
353
|
+
|
|
137
354
|
### mountTable(host, table, options?) -> TableMount
|
|
138
355
|
|
|
139
356
|
options: { injectStyles?=true, initialViewportHeight?=480 }
|
|
@@ -163,20 +380,63 @@ Header: <div class="lt-header" role="row" aria-rowindex="1">
|
|
|
163
380
|
<span class="lt-header-resize"></span> <!-- if resizable -->
|
|
164
381
|
</div> ...
|
|
165
382
|
</div>
|
|
383
|
+
Filters (M2; mounted only when any column has filterable: true):
|
|
384
|
+
<div class="lt-filter-row" role="row" aria-rowindex="2">
|
|
385
|
+
<div class="lt-filter-cell" data-key data-pin
|
|
386
|
+
style="grid-column; left|right (if pinned)">
|
|
387
|
+
<input class="lt-filter-input"
|
|
388
|
+
aria-label="Filter <header>"
|
|
389
|
+
placeholder="Filter…">
|
|
390
|
+
<!-- empty cells for non-filterable columns -->
|
|
391
|
+
</div> ...
|
|
392
|
+
</div>
|
|
166
393
|
Body: <div class="lt-viewport"><div class="lt-inner">
|
|
167
|
-
<div class="lt-row [lt-row-alt] [is-selected]
|
|
394
|
+
<div class="lt-row [lt-row-alt] [is-selected]
|
|
395
|
+
[lt-row-group-header] [lt-row-grand-total]
|
|
396
|
+
[data-depth data-collapsed]" <!-- M3 header/total attrs -->
|
|
168
397
|
role="row"
|
|
169
398
|
aria-rowindex aria-selected
|
|
170
399
|
style="transform: translateY(<i*rowHeight>px)">
|
|
171
|
-
<div class="lt-cell
|
|
400
|
+
<div class="lt-cell [is-focused] [is-editing]"
|
|
401
|
+
role="gridcell"
|
|
172
402
|
id="lt_<rowId>__<columnKey>"
|
|
173
403
|
aria-colindex
|
|
174
404
|
data-pin
|
|
405
|
+
[data-editable="true"]
|
|
406
|
+
[contenteditable="true"] <!-- only the active edit cell -->
|
|
175
407
|
style="grid-column; left|right (if pinned)">text</div>
|
|
176
408
|
...
|
|
177
409
|
</div> ...
|
|
178
410
|
</div></div>
|
|
179
411
|
|
|
412
|
+
Sticky overlays (M3; direct children of .lt-viewport):
|
|
413
|
+
<div class="lt-sticky-groups" aria-hidden="true"
|
|
414
|
+
style="position:sticky; top:<rowHeight>px;
|
|
415
|
+
height:0; z-index:2">
|
|
416
|
+
<div class="lt-row-group-header lt-sticky-group"
|
|
417
|
+
data-depth data-collapsed
|
|
418
|
+
style="position:absolute; top:<depth*rowHeight>px;
|
|
419
|
+
height:<rowHeight>px;
|
|
420
|
+
display:grid; grid-template-columns:var(--lt-cols)">
|
|
421
|
+
<div class="lt-cell" data-key>chevron + value + (N)</div>
|
|
422
|
+
...
|
|
423
|
+
</div> ...
|
|
424
|
+
</div>
|
|
425
|
+
<div class="lt-sticky-grand-total" aria-hidden="true"
|
|
426
|
+
style="position:sticky; bottom:0; height:0; z-index:2">
|
|
427
|
+
<div class="lt-row-grand-total lt-sticky-grand-total-row"
|
|
428
|
+
style="position:absolute; top:-<rowHeight>px;
|
|
429
|
+
height:<rowHeight>px;
|
|
430
|
+
display:grid; grid-template-columns:var(--lt-cols)">
|
|
431
|
+
<div class="lt-cell" data-key>Total (N) / aggregate</div>
|
|
432
|
+
...
|
|
433
|
+
</div>
|
|
434
|
+
</div>
|
|
435
|
+
|
|
436
|
+
Sticky rows deliberately do NOT carry the base .lt-row class so consumers'
|
|
437
|
+
querySelectorAll(".lt-row") still counts only pool slots (backwards-compatible
|
|
438
|
+
with 1.0/1.1 code that inspects the DOM this way).
|
|
439
|
+
|
|
180
440
|
## Keyboard
|
|
181
441
|
|
|
182
442
|
ArrowUp/Down/Left/Right move focus (skips hidden cols)
|
|
@@ -185,14 +445,26 @@ Ctrl+Home/End grid corners (0,0) / (last,last)
|
|
|
185
445
|
PageUp/PageDown +/- floor(viewportHeight / rowHeight)
|
|
186
446
|
Space selectRow(focused.rowId, "set")
|
|
187
447
|
Ctrl+A selectAll()
|
|
188
|
-
Escape clearSelection()
|
|
448
|
+
Escape clearSelection() (when not editing)
|
|
449
|
+
F2 / Enter (on editable) startEdit(focused) (M2)
|
|
450
|
+
Dblclick on editable cell startEdit (M2)
|
|
189
451
|
Click cell selectRow(set) + focus
|
|
190
452
|
Shift+click selectRow(range)
|
|
191
453
|
Ctrl/Cmd+click selectRow(toggle)
|
|
192
454
|
Drag header reorder
|
|
193
455
|
Drag right edge of header resize
|
|
194
456
|
Click header toggleSort
|
|
457
|
+
(while editing):
|
|
458
|
+
Enter commitEdit + moveFocus("down")
|
|
459
|
+
Tab / Shift+Tab commitEdit + moveFocus("right"/"left")
|
|
460
|
+
Escape cancelEdit + return focus to root
|
|
461
|
+
blur commitEdit
|
|
462
|
+
(on a filter input):
|
|
463
|
+
Escape setColumnFilter(key, "")
|
|
195
464
|
Shift+click header toggleSort additive
|
|
465
|
+
Click group-header row toggleGroup(entry.path) (M3, inline OR sticky)
|
|
466
|
+
Click grand-total row ignored (M3)
|
|
467
|
+
Arrow-up/down stays inside data rows -- skips headers (M3)
|
|
196
468
|
|
|
197
469
|
## Performance
|
|
198
470
|
|
|
@@ -222,13 +494,23 @@ Classes for styling:
|
|
|
222
494
|
.lt-header-cell.is-drop-before / .is-drop-after
|
|
223
495
|
.lt-header-resize:hover / .is-active
|
|
224
496
|
|
|
497
|
+
M3 grouping / total classes:
|
|
498
|
+
.lt-row-group-header pool-rendered group header
|
|
499
|
+
.lt-row-group-header[data-depth] 0 / 1 / 2 / 3 / 4 -> indent
|
|
500
|
+
.lt-row-group-header[data-collapsed="true"|"false"]
|
|
501
|
+
.lt-row-grand-total pool-rendered grand total
|
|
502
|
+
.lt-sticky-groups sticky-header container (viewport child)
|
|
503
|
+
.lt-sticky-group sticky group-header row inside container
|
|
504
|
+
.lt-sticky-grand-total sticky grand-total container
|
|
505
|
+
.lt-sticky-grand-total-row sticky grand-total row inside container
|
|
506
|
+
|
|
225
507
|
## Files
|
|
226
508
|
|
|
227
|
-
Table.js single-file ESM source
|
|
509
|
+
Table.js single-file ESM source (~3100 lines)
|
|
228
510
|
Table.d.ts type declarations
|
|
229
|
-
demo/index.html QA demo with controls for all M1 features
|
|
511
|
+
demo/index.html QA demo with controls for all M1 / M2 / M3 features
|
|
230
512
|
demo/serve.js zero-dep static server (npm run demo)
|
|
231
|
-
test/*.test.js node:test suite (npm test,
|
|
513
|
+
test/*.test.js node:test suite (npm test, 257 tests across 14 files)
|
|
232
514
|
bench/*.js benchmarks vs clusterize.js + naive virtual (npm run bench)
|
|
233
515
|
llms.txt this file
|
|
234
516
|
README.md human docs with Mermaid diagrams + bench numbers
|
|
@@ -248,6 +530,9 @@ Headline:
|
|
|
248
530
|
- 10K boundary scrolls: signal nodes delta 0, signal links delta 0,
|
|
249
531
|
DOM pool delta 0, process heap noise floor
|
|
250
532
|
- mount 1M rows: ~56ms, 24-element pool, constant vs dataset size
|
|
533
|
+
- 10K boundary scrolls in a GROUPED 100k-row table with sticky group
|
|
534
|
+
headers + sticky grand total active: signal nodes delta 0, link
|
|
535
|
+
delta 2 (noise), pool delta 0. See bench/03-heap-grouped.js.
|
|
251
536
|
|
|
252
537
|
The reactive graph and DOM pool are stable across all steady-state ops.
|
|
253
538
|
Allocations happen only during mount and on demand (column reorder may
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zakkster/lite-table",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "Headless reactive data tables on @zakkster/lite-signal. CSS Grid (no <table>), pooled slots that never reparent, aria-activedescendant focus model. Zero-GC scroll path on @zakkster/lite-virtual.",
|
|
3
|
+
"version": "1.2.0",
|
|
4
|
+
"description": "Headless reactive data tables on @zakkster/lite-signal. CSS Grid (no <table>), pooled slots that never reparent, aria-activedescendant focus model. Zero-GC scroll path on @zakkster/lite-virtual. Grouping, aggregation, sticky group headers, and grand totals in v1.2.",
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"keywords": [
|
|
7
7
|
"table",
|
|
@@ -16,7 +16,9 @@
|
|
|
16
16
|
"lite-virtual",
|
|
17
17
|
"aria",
|
|
18
18
|
"a11y",
|
|
19
|
-
"admin"
|
|
19
|
+
"admin",
|
|
20
|
+
"grouping",
|
|
21
|
+
"aggregation"
|
|
20
22
|
],
|
|
21
23
|
"type": "module",
|
|
22
24
|
"main": "./Table.js",
|
|
@@ -24,8 +26,10 @@
|
|
|
24
26
|
"types": "./Table.d.ts",
|
|
25
27
|
"exports": {
|
|
26
28
|
".": {
|
|
29
|
+
"node": "./Table.js",
|
|
30
|
+
"import": "./Table.js",
|
|
27
31
|
"types": "./Table.d.ts",
|
|
28
|
-
"
|
|
32
|
+
"default": "./Table.js"
|
|
29
33
|
}
|
|
30
34
|
},
|
|
31
35
|
"files": [
|
|
@@ -42,24 +46,26 @@
|
|
|
42
46
|
"bench": "node --expose-gc --max-old-space-size=4096 bench/run.js"
|
|
43
47
|
},
|
|
44
48
|
"peerDependencies": {
|
|
45
|
-
"@zakkster/lite-signal": "^1.2.
|
|
49
|
+
"@zakkster/lite-signal": "^1.2.1",
|
|
46
50
|
"@zakkster/lite-signal-dom": "^1.0.1",
|
|
47
51
|
"@zakkster/lite-virtual": "^1.1.0"
|
|
48
52
|
},
|
|
49
53
|
"devDependencies": {
|
|
54
|
+
"@playwright/test": "^1.60.0",
|
|
50
55
|
"clusterize.js": "^1.0.0",
|
|
51
|
-
"happy-dom": "^15.11.7"
|
|
56
|
+
"happy-dom": "^15.11.7",
|
|
57
|
+
"playwright": "^1.60.0"
|
|
52
58
|
},
|
|
53
59
|
"engines": {
|
|
54
60
|
"node": ">=20"
|
|
55
61
|
},
|
|
56
|
-
"homepage": "https://github.com/PeshoVurtoleta/lite-
|
|
62
|
+
"homepage": "https://github.com/PeshoVurtoleta/lite-table#readme",
|
|
57
63
|
"repository": {
|
|
58
64
|
"type": "git",
|
|
59
|
-
"url": "git+https://github.com/PeshoVurtoleta/lite-
|
|
65
|
+
"url": "git+https://github.com/PeshoVurtoleta/lite-table.git"
|
|
60
66
|
},
|
|
61
67
|
"bugs": {
|
|
62
|
-
"url": "https://github.com/PeshoVurtoleta/lite-
|
|
68
|
+
"url": "https://github.com/PeshoVurtoleta/lite-table/issues",
|
|
63
69
|
"email": "shinikchiev@yahoo.com"
|
|
64
70
|
},
|
|
65
71
|
"funding": {
|