tosijs-ui 1.5.13 → 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 +4 -0
- package/dist/data-table.js +145 -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
|
@@ -35,6 +35,7 @@ export declare class TosiTable extends WebComponent {
|
|
|
35
35
|
overflow: string;
|
|
36
36
|
height: string;
|
|
37
37
|
overscrollBehavior: string;
|
|
38
|
+
alignContent: string;
|
|
38
39
|
};
|
|
39
40
|
':host .th, :host .td': {
|
|
40
41
|
overflow: string;
|
|
@@ -129,6 +130,9 @@ export declare class TosiTable extends WebComponent {
|
|
|
129
130
|
private selectBinding;
|
|
130
131
|
maxVisibleRows: number;
|
|
131
132
|
private _grid;
|
|
133
|
+
private pinnedItemToCells;
|
|
134
|
+
private pinnedCellToItem;
|
|
135
|
+
private resolvePinnedItem;
|
|
132
136
|
get value(): TableData;
|
|
133
137
|
set value(data: TableData);
|
|
134
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
|
|
|
@@ -466,6 +539,7 @@ export class TosiTable extends WebComponent {
|
|
|
466
539
|
overflow: 'auto',
|
|
467
540
|
height: '100%',
|
|
468
541
|
overscrollBehavior: 'none',
|
|
542
|
+
alignContent: 'start',
|
|
469
543
|
},
|
|
470
544
|
':host .th, :host .td': {
|
|
471
545
|
overflow: 'hidden',
|
|
@@ -566,6 +640,14 @@ export class TosiTable extends WebComponent {
|
|
|
566
640
|
};
|
|
567
641
|
maxVisibleRows = 10000;
|
|
568
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
|
+
}
|
|
569
651
|
get value() {
|
|
570
652
|
return {
|
|
571
653
|
array: this.array,
|
|
@@ -756,29 +838,54 @@ export class TosiTable extends WebComponent {
|
|
|
756
838
|
Object.assign(cell.style, style);
|
|
757
839
|
}
|
|
758
840
|
buildPinnedCells(rows, cols, stickyInfo, pin, rowHeight, startRowIndex) {
|
|
759
|
-
const
|
|
841
|
+
const allCells = [];
|
|
842
|
+
const selectBindingFn = this.selectBinding;
|
|
843
|
+
const { rowRendered } = this;
|
|
760
844
|
for (let r = 0; r < rows.length; r++) {
|
|
761
845
|
const rowItem = rows[r];
|
|
762
846
|
const offset = pin === 'top'
|
|
763
847
|
? (r + 1) * rowHeight + 'px'
|
|
764
848
|
: (rows.length - 1 - r) * rowHeight + 'px';
|
|
849
|
+
const rowCells = [];
|
|
765
850
|
for (let c = 0; c < cols.length; c++) {
|
|
766
851
|
const col = cols[c];
|
|
767
852
|
const si = stickyInfo[c];
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
}
|
|
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);
|
|
779
886
|
}
|
|
780
887
|
}
|
|
781
|
-
return
|
|
888
|
+
return allCells;
|
|
782
889
|
}
|
|
783
890
|
getColumn(event) {
|
|
784
891
|
if (!this._grid)
|
|
@@ -865,7 +972,7 @@ export class TosiTable extends WebComponent {
|
|
|
865
972
|
if (!this._grid)
|
|
866
973
|
return;
|
|
867
974
|
for (const elt of Array.from(this._grid.children)) {
|
|
868
|
-
const item = getListItem(elt);
|
|
975
|
+
const item = getListItem(elt) ?? this.pinnedCellToItem.get(elt);
|
|
869
976
|
if (item != null) {
|
|
870
977
|
this.selectBinding(elt, item);
|
|
871
978
|
}
|
|
@@ -881,7 +988,7 @@ export class TosiTable extends WebComponent {
|
|
|
881
988
|
if (!(target instanceof HTMLElement)) {
|
|
882
989
|
return;
|
|
883
990
|
}
|
|
884
|
-
const pickedItem = getListItem(target);
|
|
991
|
+
const pickedItem = getListItem(target) ?? this.resolvePinnedItem(target);
|
|
885
992
|
if (pickedItem == null) {
|
|
886
993
|
return;
|
|
887
994
|
}
|
|
@@ -1251,16 +1358,20 @@ export class TosiTable extends WebComponent {
|
|
|
1251
1358
|
getCells(itemOrCell) {
|
|
1252
1359
|
if (!this._grid)
|
|
1253
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;
|
|
1254
1368
|
const binding = getListBinding(this._grid);
|
|
1255
1369
|
if (!binding)
|
|
1256
1370
|
return undefined;
|
|
1257
|
-
|
|
1258
|
-
if (item == null)
|
|
1259
|
-
return undefined;
|
|
1260
|
-
return binding.itemToElement.get(tosiValue(item));
|
|
1371
|
+
return binding.itemToElement.get(key);
|
|
1261
1372
|
}
|
|
1262
1373
|
getItem(cell) {
|
|
1263
|
-
return getListItem(cell);
|
|
1374
|
+
return getListItem(cell) ?? this.resolvePinnedItem(cell);
|
|
1264
1375
|
}
|
|
1265
1376
|
draggedColumn;
|
|
1266
1377
|
dropColumn = (event) => {
|
|
@@ -1280,6 +1391,7 @@ export class TosiTable extends WebComponent {
|
|
|
1280
1391
|
render() {
|
|
1281
1392
|
super.render();
|
|
1282
1393
|
this.textContent = '';
|
|
1394
|
+
this.pinnedItemToCells.clear();
|
|
1283
1395
|
// Prepare data
|
|
1284
1396
|
const pinnedTopData = this.pinnedTop > 0 ? this._array.slice(0, this.pinnedTop) : [];
|
|
1285
1397
|
const pinnedBottomData = this.pinnedBottom > 0 ? this._array.slice(-this.pinnedBottom) : [];
|
|
@@ -1377,7 +1489,16 @@ export class TosiTable extends WebComponent {
|
|
|
1377
1489
|
const customCell = col.dataCell(col);
|
|
1378
1490
|
this.applyPinnedToCustomCell(customCell, colIndex, si, style);
|
|
1379
1491
|
if (rowRenderedBinding && colIndex === lastCol) {
|
|
1380
|
-
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 });
|
|
1381
1502
|
}
|
|
1382
1503
|
return customCell;
|
|
1383
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;
|