tosijs-ui 1.5.14 → 1.5.16
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/dist/data-table.d.ts +3 -0
- package/dist/data-table.js +144 -24
- package/dist/icon-data.js +1 -1
- package/dist/icons.js +1 -2
- package/dist/iife.js +36 -36
- package/dist/iife.js.map +6 -6
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/llms.txt +1 -1
- package/package.json +1 -1
package/dist/data-table.d.ts
CHANGED
|
@@ -130,6 +130,9 @@ export declare class TosiTable extends WebComponent {
|
|
|
130
130
|
private selectBinding;
|
|
131
131
|
maxVisibleRows: number;
|
|
132
132
|
private _grid;
|
|
133
|
+
private pinnedItemToCells;
|
|
134
|
+
private pinnedCellToItem;
|
|
135
|
+
private resolvePinnedItem;
|
|
133
136
|
get value(): TableData;
|
|
134
137
|
set value(data: TableData);
|
|
135
138
|
private rowData;
|
package/dist/data-table.js
CHANGED
|
@@ -96,8 +96,17 @@ test('table renders with data', () => {
|
|
|
96
96
|
expect(table.array.length).toBeGreaterThan(0)
|
|
97
97
|
})
|
|
98
98
|
|
|
99
|
-
test('row selection
|
|
100
|
-
|
|
99
|
+
test('row selection: data model + aria-selected on every cell (incl. custom dataCell)', async () => {
|
|
100
|
+
// Wait for listBinding to stamp DOM cells for the visible window
|
|
101
|
+
const items = table.visibleRows
|
|
102
|
+
await new Promise(resolve => {
|
|
103
|
+
const check = () => {
|
|
104
|
+
if (table.getCells(items[0]) && table.getCells(items[1])) return resolve()
|
|
105
|
+
setTimeout(check, 100)
|
|
106
|
+
}
|
|
107
|
+
check()
|
|
108
|
+
})
|
|
109
|
+
|
|
101
110
|
table.deSelect()
|
|
102
111
|
table.selectRow(items[0])
|
|
103
112
|
table.selectRow(items[1])
|
|
@@ -107,11 +116,27 @@ test('row selection via data model', () => {
|
|
|
107
116
|
expect(items[1][table.selectedKey]).toBe(true)
|
|
108
117
|
expect(table.selectedRows.length).toBe(2)
|
|
109
118
|
|
|
110
|
-
//
|
|
119
|
+
// DOM: every cell of a selected row has aria-selected, including the
|
|
120
|
+
// custom-rendered `name` column (regression test for custom cells being
|
|
121
|
+
// skipped by selectBinding).
|
|
122
|
+
const cells0 = table.getCells(items[0])
|
|
123
|
+
const cells1 = table.getCells(items[1])
|
|
124
|
+
expect(cells0.length).toBe(table.visibleColumns.length)
|
|
125
|
+
expect(cells1.length).toBe(table.visibleColumns.length)
|
|
126
|
+
for (const c of cells0) expect(c.hasAttribute('aria-selected')).toBe(true)
|
|
127
|
+
for (const c of cells1) expect(c.hasAttribute('aria-selected')).toBe(true)
|
|
128
|
+
// The `name` column (index 1) uses a dataCell input — confirm the custom
|
|
129
|
+
// element is the actual cell and that it carries aria-selected.
|
|
130
|
+
expect(cells0[1].tagName).toBe('INPUT')
|
|
131
|
+
expect(cells0[1].hasAttribute('aria-selected')).toBe(true)
|
|
132
|
+
|
|
133
|
+
// Deselect and verify both data model and DOM clear
|
|
111
134
|
table.deSelect()
|
|
112
135
|
expect(table.selectedRows.length).toBe(0)
|
|
113
136
|
expect(items[0][table.selectedKey]).not.toBe(true)
|
|
114
137
|
expect(items[1][table.selectedKey]).not.toBe(true)
|
|
138
|
+
for (const c of cells0) expect(c.hasAttribute('aria-selected')).toBe(false)
|
|
139
|
+
for (const c of cells1) expect(c.hasAttribute('aria-selected')).toBe(false)
|
|
115
140
|
})
|
|
116
141
|
|
|
117
142
|
test('getCells and getItem', async () => {
|
|
@@ -291,6 +316,54 @@ preview.append(table)
|
|
|
291
316
|
font-variant-numeric: tabular-nums;
|
|
292
317
|
}
|
|
293
318
|
```
|
|
319
|
+
```test
|
|
320
|
+
const tables = document.querySelectorAll('tosi-table')
|
|
321
|
+
const table = tables[tables.length - 1]
|
|
322
|
+
await new Promise(resolve => {
|
|
323
|
+
const check = () => {
|
|
324
|
+
if (table.querySelector('.pinned-bottom')) return resolve()
|
|
325
|
+
setTimeout(check, 100)
|
|
326
|
+
}
|
|
327
|
+
check()
|
|
328
|
+
})
|
|
329
|
+
|
|
330
|
+
test('pinned row honours dataCell, rowRendered, getCells, getItem, selection', () => {
|
|
331
|
+
const totals = table.array[table.array.length - 1]
|
|
332
|
+
const pinnedCells = Array.from(table.querySelectorAll('.pinned-bottom'))
|
|
333
|
+
|
|
334
|
+
// Sanity: number of pinned cells equals number of visible columns
|
|
335
|
+
expect(pinnedCells.length).toBe(table.visibleColumns.length)
|
|
336
|
+
|
|
337
|
+
// dataCell honoured: numeric columns kept their `num-cell` class, and the
|
|
338
|
+
// _actions column rendered its <button>
|
|
339
|
+
const numCells = pinnedCells.filter(c => c.classList.contains('num-cell'))
|
|
340
|
+
expect(numCells.length).toBeGreaterThan(0)
|
|
341
|
+
expect(pinnedCells.some(c => c.tagName === 'BUTTON')).toBe(true)
|
|
342
|
+
|
|
343
|
+
// rowRendered fired: totals row is negative on average, so all cells of
|
|
344
|
+
// this row should carry `row-negative`
|
|
345
|
+
const total = Object.keys(totals)
|
|
346
|
+
.filter(k => typeof totals[k] === 'number')
|
|
347
|
+
.reduce((s, k) => s + totals[k], 0)
|
|
348
|
+
if (total < 0) {
|
|
349
|
+
expect(pinnedCells.every(c => c.classList.contains('row-negative'))).toBe(true)
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// getCells / getItem round-trip works for pinned items
|
|
353
|
+
const cellsForTotals = table.getCells(totals)
|
|
354
|
+
expect(cellsForTotals?.length).toBe(table.visibleColumns.length)
|
|
355
|
+
expect(table.getItem(cellsForTotals[0])).toBe(totals)
|
|
356
|
+
|
|
357
|
+
// Selection on a pinned row applies aria-selected to every cell
|
|
358
|
+
table.multiple = true
|
|
359
|
+
table.deSelect()
|
|
360
|
+
table.selectRow(totals)
|
|
361
|
+
expect(cellsForTotals.every(c => c.hasAttribute('aria-selected'))).toBe(true)
|
|
362
|
+
|
|
363
|
+
table.deSelect()
|
|
364
|
+
expect(cellsForTotals.some(c => c.hasAttribute('aria-selected'))).toBe(false)
|
|
365
|
+
})
|
|
366
|
+
```
|
|
294
367
|
|
|
295
368
|
## Selection
|
|
296
369
|
|
|
@@ -567,6 +640,14 @@ export class TosiTable extends WebComponent {
|
|
|
567
640
|
};
|
|
568
641
|
maxVisibleRows = 10000;
|
|
569
642
|
_grid = null;
|
|
643
|
+
// Pinned rows live outside the listBinding's virtual window, so we keep a
|
|
644
|
+
// side-table for getCells, getItem, and click-to-select to traverse.
|
|
645
|
+
pinnedItemToCells = new Map();
|
|
646
|
+
pinnedCellToItem = new WeakMap();
|
|
647
|
+
resolvePinnedItem(target) {
|
|
648
|
+
const cell = target.closest('.pinned-top, .pinned-bottom');
|
|
649
|
+
return cell ? this.pinnedCellToItem.get(cell) : undefined;
|
|
650
|
+
}
|
|
570
651
|
get value() {
|
|
571
652
|
return {
|
|
572
653
|
array: this.array,
|
|
@@ -757,29 +838,54 @@ export class TosiTable extends WebComponent {
|
|
|
757
838
|
Object.assign(cell.style, style);
|
|
758
839
|
}
|
|
759
840
|
buildPinnedCells(rows, cols, stickyInfo, pin, rowHeight, startRowIndex) {
|
|
760
|
-
const
|
|
841
|
+
const allCells = [];
|
|
842
|
+
const selectBindingFn = this.selectBinding;
|
|
843
|
+
const { rowRendered } = this;
|
|
761
844
|
for (let r = 0; r < rows.length; r++) {
|
|
762
845
|
const rowItem = rows[r];
|
|
763
846
|
const offset = pin === 'top'
|
|
764
847
|
? (r + 1) * rowHeight + 'px'
|
|
765
848
|
: (rows.length - 1 - r) * rowHeight + 'px';
|
|
849
|
+
const rowCells = [];
|
|
766
850
|
for (let c = 0; c < cols.length; c++) {
|
|
767
851
|
const col = cols[c];
|
|
768
852
|
const si = stickyInfo[c];
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
}
|
|
853
|
+
const style = this.cellStyle(col, si, {
|
|
854
|
+
position: 'sticky',
|
|
855
|
+
[pin]: offset,
|
|
856
|
+
});
|
|
857
|
+
let cell;
|
|
858
|
+
if (col.dataCell !== undefined) {
|
|
859
|
+
cell = col.dataCell(col);
|
|
860
|
+
this.applyPinnedToCustomCell(cell, c, si, style);
|
|
861
|
+
cell.classList.add(`pinned-${pin}`);
|
|
862
|
+
cell.setAttribute('aria-rowindex', String(startRowIndex + r + 1));
|
|
863
|
+
}
|
|
864
|
+
else {
|
|
865
|
+
cell = span({
|
|
866
|
+
class: this.cellClasses(`td pinned-${pin}`, si),
|
|
867
|
+
role: 'gridcell',
|
|
868
|
+
tabindex: -1,
|
|
869
|
+
ariaRowindex: String(startRowIndex + r + 1),
|
|
870
|
+
ariaColindex: String(c + 1),
|
|
871
|
+
style,
|
|
872
|
+
}, String(rowItem[col.prop] ?? ''));
|
|
873
|
+
}
|
|
874
|
+
// Track cell → item so click-to-select and updateSelectionVisuals
|
|
875
|
+
// can resolve pinned cells (which aren't in the listBinding).
|
|
876
|
+
this.pinnedCellToItem.set(cell, rowItem);
|
|
877
|
+
// Apply any pre-existing selection state.
|
|
878
|
+
selectBindingFn(cell, rowItem);
|
|
879
|
+
rowCells.push(cell);
|
|
880
|
+
allCells.push(cell);
|
|
881
|
+
}
|
|
882
|
+
// Track cells per item so getCells works for pinned rows
|
|
883
|
+
this.pinnedItemToCells.set(tosiValue(rowItem), rowCells);
|
|
884
|
+
if (rowRendered) {
|
|
885
|
+
rowRendered(rowItem, rowCells);
|
|
780
886
|
}
|
|
781
887
|
}
|
|
782
|
-
return
|
|
888
|
+
return allCells;
|
|
783
889
|
}
|
|
784
890
|
getColumn(event) {
|
|
785
891
|
if (!this._grid)
|
|
@@ -866,7 +972,7 @@ export class TosiTable extends WebComponent {
|
|
|
866
972
|
if (!this._grid)
|
|
867
973
|
return;
|
|
868
974
|
for (const elt of Array.from(this._grid.children)) {
|
|
869
|
-
const item = getListItem(elt);
|
|
975
|
+
const item = getListItem(elt) ?? this.pinnedCellToItem.get(elt);
|
|
870
976
|
if (item != null) {
|
|
871
977
|
this.selectBinding(elt, item);
|
|
872
978
|
}
|
|
@@ -882,7 +988,7 @@ export class TosiTable extends WebComponent {
|
|
|
882
988
|
if (!(target instanceof HTMLElement)) {
|
|
883
989
|
return;
|
|
884
990
|
}
|
|
885
|
-
const pickedItem = getListItem(target);
|
|
991
|
+
const pickedItem = getListItem(target) ?? this.resolvePinnedItem(target);
|
|
886
992
|
if (pickedItem == null) {
|
|
887
993
|
return;
|
|
888
994
|
}
|
|
@@ -1252,16 +1358,20 @@ export class TosiTable extends WebComponent {
|
|
|
1252
1358
|
getCells(itemOrCell) {
|
|
1253
1359
|
if (!this._grid)
|
|
1254
1360
|
return undefined;
|
|
1361
|
+
const item = itemOrCell instanceof Element ? this.getItem(itemOrCell) : itemOrCell;
|
|
1362
|
+
if (item == null)
|
|
1363
|
+
return undefined;
|
|
1364
|
+
const key = tosiValue(item);
|
|
1365
|
+
const pinned = this.pinnedItemToCells.get(key);
|
|
1366
|
+
if (pinned)
|
|
1367
|
+
return pinned;
|
|
1255
1368
|
const binding = getListBinding(this._grid);
|
|
1256
1369
|
if (!binding)
|
|
1257
1370
|
return undefined;
|
|
1258
|
-
|
|
1259
|
-
if (item == null)
|
|
1260
|
-
return undefined;
|
|
1261
|
-
return binding.itemToElement.get(tosiValue(item));
|
|
1371
|
+
return binding.itemToElement.get(key);
|
|
1262
1372
|
}
|
|
1263
1373
|
getItem(cell) {
|
|
1264
|
-
return getListItem(cell);
|
|
1374
|
+
return getListItem(cell) ?? this.resolvePinnedItem(cell);
|
|
1265
1375
|
}
|
|
1266
1376
|
draggedColumn;
|
|
1267
1377
|
dropColumn = (event) => {
|
|
@@ -1281,6 +1391,7 @@ export class TosiTable extends WebComponent {
|
|
|
1281
1391
|
render() {
|
|
1282
1392
|
super.render();
|
|
1283
1393
|
this.textContent = '';
|
|
1394
|
+
this.pinnedItemToCells.clear();
|
|
1284
1395
|
// Prepare data
|
|
1285
1396
|
const pinnedTopData = this.pinnedTop > 0 ? this._array.slice(0, this.pinnedTop) : [];
|
|
1286
1397
|
const pinnedBottomData = this.pinnedBottom > 0 ? this._array.slice(-this.pinnedBottom) : [];
|
|
@@ -1378,7 +1489,16 @@ export class TosiTable extends WebComponent {
|
|
|
1378
1489
|
const customCell = col.dataCell(col);
|
|
1379
1490
|
this.applyPinnedToCustomCell(customCell, colIndex, si, style);
|
|
1380
1491
|
if (rowRenderedBinding && colIndex === lastCol) {
|
|
1381
|
-
bind(customCell, item, {
|
|
1492
|
+
bind(customCell, item, {
|
|
1493
|
+
toDOM(cell) {
|
|
1494
|
+
if (selectEnabled)
|
|
1495
|
+
selectBindingFn(cell, getListItem(cell));
|
|
1496
|
+
rowRenderedBinding(cell);
|
|
1497
|
+
},
|
|
1498
|
+
});
|
|
1499
|
+
}
|
|
1500
|
+
else if (selectEnabled) {
|
|
1501
|
+
bind(customCell, item, { toDOM: selectBindingFn });
|
|
1382
1502
|
}
|
|
1383
1503
|
return customCell;
|
|
1384
1504
|
}
|
package/dist/icon-data.js
CHANGED
|
@@ -87,7 +87,7 @@ export default {
|
|
|
87
87
|
gitPullRequest: "<svg class=\"stroked\" viewBox=\"0 0 24 24\"><circle cx=\"18\" cy=\"18\" r=\"3\"></circle><circle cx=\"6\" cy=\"6\" r=\"3\"></circle><path d=\"M13 6h3a2 2 0 0 1 2 2v7\"></path><line x1=\"6\" y1=\"9\" x2=\"6\" y2=\"21\"></line></svg>",
|
|
88
88
|
minimize: "<svg class=\"stroked\" viewBox=\"0 0 24 24\"><path d=\"M8 3v3a2 2 0 0 1-2 2H3m18 0h-3a2 2 0 0 1-2-2V3m0 18v-3a2 2 0 0 1 2-2h3M3 16h3a2 2 0 0 1 2 2v3\"></path></svg>",
|
|
89
89
|
minusSquare: "<svg class=\"stroked\" viewBox=\"0 0 24 24\"><rect x=\"3\" y=\"3\" width=\"18\" height=\"18\" rx=\"2\" ry=\"2\"></rect><line x1=\"8\" y1=\"12\" x2=\"16\" y2=\"12\"></line></svg>",
|
|
90
|
-
settings: "<svg class=\"stroked\" viewBox=\"0 0 24 24\"><
|
|
90
|
+
settings: "<svg class=\"stroked\" viewBox=\"0 0 24 24\"><g><path style=\"\" d=\"M4.78,6.75 C6.02,7.26,7.26,6.02,6.75,4.78 C6.75,4.78,6.24,3.57,6.24,3.57 C5.93,2.8,6.29,1.92,7.06,1.6 C7.06,1.6,8.14,1.15,8.14,1.15 C8.91,0.83,9.79,1.2,10.11,1.96 C10.11,1.96,10.61,3.18,10.61,3.18 C11.12,4.42,12.88,4.42,13.39,3.18 C13.39,3.18,13.89,1.97,13.89,1.97 C14.21,1.2,15.09,0.83,15.86,1.15 C15.86,1.15,16.94,1.6,16.94,1.6 C17.71,1.92,18.08,2.8,17.76,3.57 C17.76,3.57,17.15,5.03,17.15,5.03 C16.64,6.27,17.88,7.51,19.12,7 C19.12,7,20.51,6.42,20.51,6.42 C21.28,6.1,22.16,6.47,22.47,7.23 C22.47,7.23,22.92,8.32,22.92,8.32 C23.24,9.09,22.88,9.96,22.11,10.28 C22.11,10.28,21.07,10.71,21.07,10.71 C19.83,11.23,19.83,12.98,21.07,13.49 C21.07,13.49,22.04,13.89,22.04,13.89 C22.8,14.21,23.17,15.09,22.85,15.86 C22.85,15.86,22.4,16.94,22.4,16.94 C22.08,17.71,21.2,18.07,20.43,17.76 C20.43,17.76,19.22,17.25,19.22,17.25 C17.98,16.74,16.74,17.98,17.25,19.22 C17.25,19.22,17.76,20.43,17.76,20.43 C18.07,21.2,17.71,22.08,16.94,22.4 C16.94,22.4,15.86,22.85,15.86,22.85 C15.09,23.17,14.21,22.8,13.89,22.04 C13.89,22.04,13.39,20.82,13.39,20.82 C12.88,19.58,11.12,19.58,10.61,20.82 C10.61,20.82,10.11,22.04,10.11,22.04 C9.79,22.8,8.91,23.17,8.14,22.85 C8.14,22.85,7.06,22.4,7.06,22.4 C6.29,22.08,5.93,21.2,6.24,20.44 C6.24,20.44,6.64,19.47,6.64,19.47 C7.16,18.23,5.92,16.99,4.68,17.5 C4.68,17.5,3.64,17.93,3.64,17.93 C2.87,18.25,1.99,17.89,1.67,17.12 C1.67,17.12,1.22,16.04,1.22,16.04 C0.91,15.27,1.27,14.39,2.04,14.07 C2.04,14.07,3.43,13.49,3.43,13.49 C4.67,12.98,4.67,11.23,3.43,10.71 C3.43,10.71,1.96,10.11,1.96,10.11 C1.2,9.79,0.83,8.91,1.15,8.14 C1.15,8.14,1.6,7.06,1.6,7.06 C1.92,6.29,2.8,5.93,3.57,6.24 C3.57,6.24,4.78,6.75,4.78,6.75 z M14.31,11.04 C14.84,12.32,14.23,13.78,12.96,14.31 C11.68,14.84,10.22,14.23,9.69,12.96 C9.16,11.68,9.77,10.22,11.04,9.69 C12.32,9.16,13.78,9.77,14.31,11.04 z\"/></g></svg> ",
|
|
91
91
|
cloudSnow: "<svg class=\"stroked\" viewBox=\"0 0 24 24\"><path d=\"M20 17.58A5 5 0 0 0 18 8h-1.26A8 8 0 1 0 4 16.25\"></path><line x1=\"8\" y1=\"16\" x2=\"8.01\" y2=\"16\"></line><line x1=\"8\" y1=\"20\" x2=\"8.01\" y2=\"20\"></line><line x1=\"12\" y1=\"18\" x2=\"12.01\" y2=\"18\"></line><line x1=\"12\" y1=\"22\" x2=\"12.01\" y2=\"22\"></line><line x1=\"16\" y1=\"16\" x2=\"16.01\" y2=\"16\"></line><line x1=\"16\" y1=\"20\" x2=\"16.01\" y2=\"20\"></line></svg>",
|
|
92
92
|
type: "<svg class=\"stroked\" viewBox=\"0 0 24 24\"><polyline points=\"4 7 4 4 20 4 20 7\"></polyline><line x1=\"9\" y1=\"20\" x2=\"15\" y2=\"20\"></line><line x1=\"12\" y1=\"4\" x2=\"12\" y2=\"20\"></line></svg>",
|
|
93
93
|
archive: "<svg class=\"stroked\" viewBox=\"0 0 24 24\"><polyline points=\"21 8 21 21 3 21 3 8\"></polyline><rect x=\"1\" y=\"3\" width=\"22\" height=\"5\"></rect><line x1=\"10\" y1=\"12\" x2=\"14\" y2=\"12\"></line></svg>",
|
package/dist/icons.js
CHANGED
|
@@ -490,7 +490,6 @@ a single SVG. `svg2DataUrl()` will render only the base icon and log a
|
|
|
490
490
|
console error. Simple suffix transforms and plain icons work normally
|
|
491
491
|
with `svg2DataUrl`.
|
|
492
492
|
|
|
493
|
-
|
|
494
493
|
## Missing Icons
|
|
495
494
|
|
|
496
495
|
If you ask for an icon that isn't defined, the `icons` proxy will print a warning to console
|
|
@@ -757,7 +756,7 @@ function parseStyleSuffixes(name) {
|
|
|
757
756
|
if (rule.prefix instanceof RegExp) {
|
|
758
757
|
return rule.prefix.test(baseName);
|
|
759
758
|
}
|
|
760
|
-
return baseName.startsWith(rule.prefix) && baseName.length > rule.prefix.length;
|
|
759
|
+
return (baseName.startsWith(rule.prefix) && baseName.length > rule.prefix.length);
|
|
761
760
|
});
|
|
762
761
|
if (!matchesRule)
|
|
763
762
|
return null;
|