tosijs-ui 1.5.0 → 1.5.2
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 +199 -90
- package/dist/iife.js +60 -60
- package/dist/iife.js.map +4 -4
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/dist/data-table.d.ts
CHANGED
|
@@ -125,6 +125,7 @@ export declare class TosiTable extends WebComponent {
|
|
|
125
125
|
localized: boolean;
|
|
126
126
|
};
|
|
127
127
|
selectionChanged: SelectCallback;
|
|
128
|
+
rowRendered: ((item: any, cells: HTMLElement[]) => void) | null;
|
|
128
129
|
private selectedKey;
|
|
129
130
|
private selectBinding;
|
|
130
131
|
maxVisibleRows: number;
|
|
@@ -174,6 +175,8 @@ export declare class TosiTable extends WebComponent {
|
|
|
174
175
|
get visibleRows(): any[];
|
|
175
176
|
get visibleSelectedRows(): any[];
|
|
176
177
|
get selectedRows(): any[];
|
|
178
|
+
getCells(itemOrCell: any): HTMLElement[] | undefined;
|
|
179
|
+
getItem(cell: Element): any;
|
|
177
180
|
private draggedColumn?;
|
|
178
181
|
private dropColumn;
|
|
179
182
|
render(): void;
|
package/dist/data-table.js
CHANGED
|
@@ -45,13 +45,24 @@ const columns = [
|
|
|
45
45
|
},
|
|
46
46
|
]
|
|
47
47
|
|
|
48
|
-
|
|
48
|
+
const table = tosiTable({
|
|
49
49
|
multiple: true,
|
|
50
50
|
array: emojiData,
|
|
51
51
|
localized: true,
|
|
52
52
|
columns,
|
|
53
53
|
rowHeight: 40,
|
|
54
|
-
})
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
table.addEventListener('mouseover', (e) => {
|
|
57
|
+
for (const el of table.querySelectorAll('.row-hover')) {
|
|
58
|
+
el.classList.remove('row-hover')
|
|
59
|
+
}
|
|
60
|
+
const item = table.getItem(e.target)
|
|
61
|
+
if (!item) return
|
|
62
|
+
table.getCells(item)?.forEach(c => c.classList.add('row-hover'))
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
preview.append(table)
|
|
55
66
|
```
|
|
56
67
|
```css
|
|
57
68
|
.preview input.td {
|
|
@@ -64,6 +75,10 @@ preview.append(tosiTable({
|
|
|
64
75
|
.preview tosi-table {
|
|
65
76
|
height: 100%;
|
|
66
77
|
}
|
|
78
|
+
|
|
79
|
+
.preview .row-hover {
|
|
80
|
+
background: #08835810;
|
|
81
|
+
}
|
|
67
82
|
```
|
|
68
83
|
```test
|
|
69
84
|
const table = await waitFor('tosi-table')
|
|
@@ -98,6 +113,30 @@ test('row selection via data model', () => {
|
|
|
98
113
|
expect(items[0][table.selectedKey]).not.toBe(true)
|
|
99
114
|
expect(items[1][table.selectedKey]).not.toBe(true)
|
|
100
115
|
})
|
|
116
|
+
|
|
117
|
+
test('getCells and getItem', async () => {
|
|
118
|
+
// Wait for list binding to stamp DOM elements
|
|
119
|
+
const items = table.visibleRows
|
|
120
|
+
let cells
|
|
121
|
+
await new Promise(resolve => {
|
|
122
|
+
const check = () => {
|
|
123
|
+
cells = table.getCells(items[0])
|
|
124
|
+
if (cells) return resolve()
|
|
125
|
+
setTimeout(check, 100)
|
|
126
|
+
}
|
|
127
|
+
check()
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
expect(cells.length).toBe(table.visibleColumns.length)
|
|
131
|
+
|
|
132
|
+
// getItem round-trips back to the same item
|
|
133
|
+
const item = table.getItem(cells[0])
|
|
134
|
+
expect(item).toBe(items[0])
|
|
135
|
+
|
|
136
|
+
// getCells from a cell element
|
|
137
|
+
const cellsFromCell = table.getCells(cells[1])
|
|
138
|
+
expect(cellsFromCell).toBe(cells)
|
|
139
|
+
})
|
|
101
140
|
```
|
|
102
141
|
|
|
103
142
|
> In the preceding example, the `name` column is *editable* (and *bound*, try editing something and scrolling
|
|
@@ -143,15 +182,18 @@ rendering with no jitter.
|
|
|
143
182
|
import { elements } from 'tosijs'
|
|
144
183
|
import { tosiTable, icons } from 'tosijs-ui'
|
|
145
184
|
|
|
146
|
-
const { button } = elements
|
|
185
|
+
const { button, span } = elements
|
|
147
186
|
|
|
148
187
|
const count = 100
|
|
149
188
|
const cols = ['Q1', 'Q2', 'Q3', 'Q4']
|
|
189
|
+
const numKeys = []
|
|
150
190
|
const rows = Array.from({ length: count }, (_, i) => {
|
|
151
191
|
const row = { id: i + 1, name: 'Item ' + (i + 1) }
|
|
152
192
|
for (const year of [2024, 2025, 2026]) {
|
|
153
193
|
for (const q of cols) {
|
|
154
|
-
|
|
194
|
+
const key = q + ' ' + year
|
|
195
|
+
row[key] = Math.round((Math.random() * 200 - 100) * 100) / 100
|
|
196
|
+
if (i === 0) numKeys.push(key)
|
|
155
197
|
}
|
|
156
198
|
}
|
|
157
199
|
return row
|
|
@@ -159,25 +201,44 @@ const rows = Array.from({ length: count }, (_, i) => {
|
|
|
159
201
|
|
|
160
202
|
// totals row
|
|
161
203
|
const totals = { id: '', name: 'Total' }
|
|
162
|
-
for (const key of
|
|
163
|
-
if (key === 'id' || key === 'name') continue
|
|
204
|
+
for (const key of numKeys) {
|
|
164
205
|
totals[key] = Math.round(rows.reduce((sum, r) => sum + r[key], 0) * 100) / 100
|
|
165
206
|
}
|
|
166
207
|
rows.push(totals)
|
|
167
208
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
209
|
+
// custom cell that colors negative numbers red
|
|
210
|
+
function numCell(options) {
|
|
211
|
+
return span({
|
|
212
|
+
class: 'td num-cell',
|
|
213
|
+
bindText: '^.' + options.prop,
|
|
214
|
+
bind: {
|
|
215
|
+
value: '^.' + options.prop,
|
|
216
|
+
binding: {
|
|
217
|
+
toDOM(el, val) {
|
|
218
|
+
el.style.color = val < 0 ? '#c00' : ''
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
})
|
|
173
223
|
}
|
|
174
224
|
|
|
175
|
-
|
|
225
|
+
const dataColumns = numKeys.map(key => ({
|
|
226
|
+
prop: key, width: 100, align: 'right', dataCell: numCell,
|
|
227
|
+
}))
|
|
228
|
+
|
|
229
|
+
const table = tosiTable({
|
|
176
230
|
array: rows,
|
|
177
231
|
rowHeight: 32,
|
|
178
232
|
pinnedBottom: 1,
|
|
179
233
|
pinnedLeft: 2,
|
|
180
234
|
pinnedRight: 1,
|
|
235
|
+
rowRendered(item, cells) {
|
|
236
|
+
const total = numKeys.reduce((sum, key) => sum + (item[key] || 0), 0)
|
|
237
|
+
const cls = total < 0 ? 'row-negative' : ''
|
|
238
|
+
for (const c of cells) {
|
|
239
|
+
c.classList.toggle('row-negative', total < 0)
|
|
240
|
+
}
|
|
241
|
+
},
|
|
181
242
|
columns: [
|
|
182
243
|
{ prop: 'id', name: '#', width: 50, align: 'right' },
|
|
183
244
|
{ prop: 'name', width: 120 },
|
|
@@ -199,7 +260,9 @@ preview.append(tosiTable({
|
|
|
199
260
|
},
|
|
200
261
|
},
|
|
201
262
|
],
|
|
202
|
-
})
|
|
263
|
+
})
|
|
264
|
+
|
|
265
|
+
preview.append(table)
|
|
203
266
|
```
|
|
204
267
|
```css
|
|
205
268
|
.preview tosi-table {
|
|
@@ -217,6 +280,12 @@ preview.append(tosiTable({
|
|
|
217
280
|
background: #eee;
|
|
218
281
|
font-weight: bold;
|
|
219
282
|
}
|
|
283
|
+
.preview .row-negative {
|
|
284
|
+
background: #fdd;
|
|
285
|
+
}
|
|
286
|
+
.preview .num-cell {
|
|
287
|
+
font-variant-numeric: tabular-nums;
|
|
288
|
+
}
|
|
220
289
|
```
|
|
221
290
|
|
|
222
291
|
## Selection
|
|
@@ -237,6 +306,43 @@ The following methods are also provided:
|
|
|
237
306
|
|
|
238
307
|
These are rather fine-grained but they're used internally by the selection code so they may as well be documented.
|
|
239
308
|
|
|
309
|
+
## Row Access
|
|
310
|
+
|
|
311
|
+
Because the table uses a flat CSS grid (no `.tr` row elements), two methods
|
|
312
|
+
provide O(1) access between items and their cells:
|
|
313
|
+
|
|
314
|
+
- `<tosi-table>.getCells(itemOrCell)` — returns the `HTMLElement[]` of cells for a
|
|
315
|
+
given data item or any cell in the row, or `undefined` if the row isn't
|
|
316
|
+
currently rendered (virtual scroll)
|
|
317
|
+
- `<tosi-table>.getItem(cell)` — returns the data item bound to a cell element
|
|
318
|
+
|
|
319
|
+
These are useful for row-level hover effects, styling, and event handling:
|
|
320
|
+
|
|
321
|
+
```typescript
|
|
322
|
+
table.addEventListener('mouseover', (e) => {
|
|
323
|
+
for (const el of table.querySelectorAll('.row-hover')) {
|
|
324
|
+
el.classList.remove('row-hover')
|
|
325
|
+
}
|
|
326
|
+
const item = table.getItem(e.target)
|
|
327
|
+
if (!item) return
|
|
328
|
+
table.getCells(item)?.forEach(c => c.classList.add('row-hover'))
|
|
329
|
+
})
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
### `rowRendered` callback
|
|
333
|
+
|
|
334
|
+
For virtual tables, cells are created and destroyed as you scroll. The
|
|
335
|
+
`rowRendered` callback fires whenever a row's cells are rendered, letting
|
|
336
|
+
you apply styling that survives virtualisation:
|
|
337
|
+
|
|
338
|
+
```typescript
|
|
339
|
+
table.rowRendered = (item, cells) => {
|
|
340
|
+
if (item.overdue) {
|
|
341
|
+
cells.forEach(c => c.classList.add('overdue'))
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
```
|
|
345
|
+
|
|
240
346
|
## Sorting
|
|
241
347
|
|
|
242
348
|
By default, the user can sort the table by any column which doesn't have a `sort === false`.
|
|
@@ -300,7 +406,7 @@ You'll need to make sure your localized strings include:
|
|
|
300
406
|
|
|
301
407
|
As well as any column names you want localized.
|
|
302
408
|
*/
|
|
303
|
-
import { Component as WebComponent, elements, vars, varDefault, tosiValue, getListItem, tosi, } from 'tosijs';
|
|
409
|
+
import { Component as WebComponent, elements, vars, varDefault, tosiValue, getListItem, getListBinding, bind, tosi, } from 'tosijs';
|
|
304
410
|
import { trackDrag } from './track-drag';
|
|
305
411
|
import { icons } from './icons';
|
|
306
412
|
import { popMenu } from './menu';
|
|
@@ -445,6 +551,7 @@ export class TosiTable extends WebComponent {
|
|
|
445
551
|
selectionChanged = () => {
|
|
446
552
|
/* do not care */
|
|
447
553
|
};
|
|
554
|
+
rowRendered = null;
|
|
448
555
|
selectedKey = Symbol('selected');
|
|
449
556
|
selectBinding = (elt, obj) => {
|
|
450
557
|
if (obj == null)
|
|
@@ -590,12 +697,12 @@ export class TosiTable extends WebComponent {
|
|
|
590
697
|
return style;
|
|
591
698
|
}
|
|
592
699
|
applyPinnedToCustomCell(cell, colIndex, si, style) {
|
|
593
|
-
cell.
|
|
700
|
+
cell.setAttribute('aria-colindex', String(colIndex + 1));
|
|
594
701
|
cell.tabIndex = -1;
|
|
595
702
|
cell.classList.add(...this.cellClasses('td', si).split(' '));
|
|
596
703
|
Object.assign(cell.style, style);
|
|
597
704
|
}
|
|
598
|
-
buildPinnedCells(rows, cols, stickyInfo, pin, rowHeight) {
|
|
705
|
+
buildPinnedCells(rows, cols, stickyInfo, pin, rowHeight, startRowIndex) {
|
|
599
706
|
const cells = [];
|
|
600
707
|
for (let r = 0; r < rows.length; r++) {
|
|
601
708
|
const rowItem = rows[r];
|
|
@@ -607,13 +714,14 @@ export class TosiTable extends WebComponent {
|
|
|
607
714
|
const si = stickyInfo[c];
|
|
608
715
|
cells.push(span({
|
|
609
716
|
class: this.cellClasses(`td pinned-${pin}`, si),
|
|
610
|
-
role: '
|
|
717
|
+
role: 'gridcell',
|
|
611
718
|
tabindex: -1,
|
|
719
|
+
ariaRowindex: String(startRowIndex + r + 1),
|
|
720
|
+
ariaColindex: String(c + 1),
|
|
612
721
|
style: this.cellStyle(col, si, {
|
|
613
722
|
position: 'sticky',
|
|
614
723
|
[pin]: offset,
|
|
615
724
|
}),
|
|
616
|
-
dataCol: String(c),
|
|
617
725
|
}, String(rowItem[col.prop] ?? '')));
|
|
618
726
|
}
|
|
619
727
|
}
|
|
@@ -777,44 +885,20 @@ export class TosiTable extends WebComponent {
|
|
|
777
885
|
if (!this._grid)
|
|
778
886
|
return null;
|
|
779
887
|
const cols = this.visibleColumns.length;
|
|
780
|
-
// Header cells
|
|
888
|
+
// Header cells (row -1)
|
|
781
889
|
if (rowIndex === -1) {
|
|
782
|
-
return this._grid.querySelector(`.th[
|
|
783
|
-
}
|
|
784
|
-
// Pinned top cells
|
|
785
|
-
if (rowIndex < this.pinnedTop) {
|
|
786
|
-
let count = 0;
|
|
787
|
-
for (const child of this._grid.children) {
|
|
788
|
-
const el = child;
|
|
789
|
-
if (el.classList.contains('pinned-top') &&
|
|
790
|
-
el.dataset.col === String(colIndex)) {
|
|
791
|
-
if (count === rowIndex)
|
|
792
|
-
return el;
|
|
793
|
-
count++;
|
|
794
|
-
}
|
|
795
|
-
}
|
|
796
|
-
return null;
|
|
890
|
+
return this._grid.querySelector(`.th[aria-colindex="${colIndex + 1}"]`);
|
|
797
891
|
}
|
|
798
|
-
// Pinned
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
el.dataset.col === String(colIndex)) {
|
|
807
|
-
if (count === bottomIdx)
|
|
808
|
-
return el;
|
|
809
|
-
count++;
|
|
810
|
-
}
|
|
811
|
-
}
|
|
812
|
-
return null;
|
|
813
|
-
}
|
|
814
|
-
// Virtual data cells — find by aria-rowindex and aria-colindex
|
|
892
|
+
// Pinned cells use full-array-relative aria-rowindex
|
|
893
|
+
// Virtual data cells use visible-data-relative aria-rowindex (set by bindList)
|
|
894
|
+
// Try pinned first, then virtual
|
|
895
|
+
const cell = this._grid.querySelector(`.pinned-top[aria-rowindex="${rowIndex + 1}"][aria-colindex="${colIndex + 1}"],` +
|
|
896
|
+
`.pinned-bottom[aria-rowindex="${rowIndex + 1}"][aria-colindex="${colIndex + 1}"]`);
|
|
897
|
+
if (cell)
|
|
898
|
+
return cell;
|
|
899
|
+
// Virtual data cell: convert full-array index to visible-data index
|
|
815
900
|
const dataRowIndex = rowIndex - this.pinnedTop;
|
|
816
|
-
|
|
817
|
-
return cell;
|
|
901
|
+
return this._grid.querySelector(`[aria-rowindex="${dataRowIndex + 1}"][aria-colindex="${colIndex + 1}"]:not(.pinned-top):not(.pinned-bottom)`);
|
|
818
902
|
}
|
|
819
903
|
_pendingFocus = null;
|
|
820
904
|
onScrollEnd = () => {
|
|
@@ -850,45 +934,29 @@ export class TosiTable extends WebComponent {
|
|
|
850
934
|
const target = el.closest('.td') || el.closest('.th');
|
|
851
935
|
if (!target)
|
|
852
936
|
return;
|
|
853
|
-
const
|
|
854
|
-
if (isNaN(
|
|
937
|
+
const ariaCol = parseInt(target.getAttribute('aria-colindex') || '', 10);
|
|
938
|
+
if (isNaN(ariaCol))
|
|
855
939
|
return;
|
|
940
|
+
const colIndex = ariaCol - 1;
|
|
856
941
|
const cols = this.visibleColumns.length;
|
|
857
942
|
const totalRows = this._array.length;
|
|
858
943
|
const meta = event.metaKey || event.ctrlKey;
|
|
859
944
|
const isHeader = target.classList.contains('th');
|
|
860
|
-
// Determine current logical row index (-1 for header)
|
|
945
|
+
// Determine current logical row index (-1 for header, 0-based full-array for data)
|
|
861
946
|
let rowIndex;
|
|
862
947
|
if (isHeader) {
|
|
863
948
|
rowIndex = -1;
|
|
864
949
|
}
|
|
865
|
-
else if (target.classList.contains('pinned-top')
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
c.dataset.col === String(colIndex)) {
|
|
873
|
-
count++;
|
|
874
|
-
}
|
|
875
|
-
}
|
|
876
|
-
rowIndex = count;
|
|
877
|
-
}
|
|
878
|
-
else if (target.classList.contains('pinned-bottom')) {
|
|
879
|
-
let count = 0;
|
|
880
|
-
for (const child of this._grid.children) {
|
|
881
|
-
if (child === target)
|
|
882
|
-
break;
|
|
883
|
-
const c = child;
|
|
884
|
-
if (c.classList.contains('pinned-bottom') &&
|
|
885
|
-
c.dataset.col === String(colIndex)) {
|
|
886
|
-
count++;
|
|
887
|
-
}
|
|
888
|
-
}
|
|
889
|
-
rowIndex = totalRows - this.pinnedBottom + count;
|
|
950
|
+
else if (target.classList.contains('pinned-top') ||
|
|
951
|
+
target.classList.contains('pinned-bottom')) {
|
|
952
|
+
// Pinned cells: aria-rowindex is full-array-relative (1-based)
|
|
953
|
+
const ariaRow = parseInt(target.getAttribute('aria-rowindex') || '', 10);
|
|
954
|
+
if (isNaN(ariaRow))
|
|
955
|
+
return;
|
|
956
|
+
rowIndex = ariaRow - 1;
|
|
890
957
|
}
|
|
891
958
|
else {
|
|
959
|
+
// Virtual data cells: aria-rowindex is visible-data-relative (1-based)
|
|
892
960
|
const ariaRow = parseInt(target.getAttribute('aria-rowindex') || '', 10);
|
|
893
961
|
if (isNaN(ariaRow))
|
|
894
962
|
return;
|
|
@@ -986,9 +1054,9 @@ export class TosiTable extends WebComponent {
|
|
|
986
1054
|
if (this._grid) {
|
|
987
1055
|
const stickyInfo = this.computeStickyInfo(cols);
|
|
988
1056
|
for (const cell of this._grid.querySelectorAll('.col-pinned')) {
|
|
989
|
-
const
|
|
990
|
-
if (!isNaN(
|
|
991
|
-
const si = stickyInfo[
|
|
1057
|
+
const ci = parseInt(cell.getAttribute('aria-colindex') || '', 10) - 1;
|
|
1058
|
+
if (!isNaN(ci) && stickyInfo[ci]) {
|
|
1059
|
+
const si = stickyInfo[ci];
|
|
992
1060
|
if (si.left != null)
|
|
993
1061
|
cell.style.left = si.left;
|
|
994
1062
|
if (si.right != null)
|
|
@@ -1086,10 +1154,24 @@ export class TosiTable extends WebComponent {
|
|
|
1086
1154
|
get selectedRows() {
|
|
1087
1155
|
return this.array.filter((obj) => obj[this.selectedKey]);
|
|
1088
1156
|
}
|
|
1157
|
+
getCells(itemOrCell) {
|
|
1158
|
+
if (!this._grid)
|
|
1159
|
+
return undefined;
|
|
1160
|
+
const binding = getListBinding(this._grid);
|
|
1161
|
+
if (!binding)
|
|
1162
|
+
return undefined;
|
|
1163
|
+
const item = itemOrCell instanceof Element ? getListItem(itemOrCell) : itemOrCell;
|
|
1164
|
+
if (item == null)
|
|
1165
|
+
return undefined;
|
|
1166
|
+
return binding.itemToElement.get(tosiValue(item));
|
|
1167
|
+
}
|
|
1168
|
+
getItem(cell) {
|
|
1169
|
+
return getListItem(cell);
|
|
1170
|
+
}
|
|
1089
1171
|
draggedColumn;
|
|
1090
1172
|
dropColumn = (event) => {
|
|
1091
1173
|
const target = event.target.closest('.drag-over');
|
|
1092
|
-
const colIndex = parseInt(target.
|
|
1174
|
+
const colIndex = parseInt(target.getAttribute('aria-colindex') || '', 10) - 1;
|
|
1093
1175
|
const dropped = this.visibleColumns[colIndex];
|
|
1094
1176
|
const draggedIndex = this.columns.indexOf(this.draggedColumn);
|
|
1095
1177
|
const droppedIndex = this.columns.indexOf(dropped);
|
|
@@ -1151,8 +1233,8 @@ export class TosiTable extends WebComponent {
|
|
|
1151
1233
|
role: 'columnheader',
|
|
1152
1234
|
tabindex: -1,
|
|
1153
1235
|
ariaSort,
|
|
1236
|
+
ariaColindex: String(i + 1),
|
|
1154
1237
|
style: this.cellStyle(col, si),
|
|
1155
|
-
dataCol: String(i),
|
|
1156
1238
|
}, this.captionSpan({ style: { flex: '1' } }, typeof col.name === 'string' ? col.name : col.prop), menuButton);
|
|
1157
1239
|
// Apply sticky to custom headerCell
|
|
1158
1240
|
if (col.headerCell !== undefined) {
|
|
@@ -1174,11 +1256,23 @@ export class TosiTable extends WebComponent {
|
|
|
1174
1256
|
}
|
|
1175
1257
|
return cell;
|
|
1176
1258
|
});
|
|
1177
|
-
const pinnedTopCells = this.buildPinnedCells(pinnedTopData, cols, stickyInfo, 'top', rowHeight);
|
|
1178
|
-
const pinnedBottomCells = this.buildPinnedCells(pinnedBottomData, cols, stickyInfo, 'bottom', rowHeight);
|
|
1259
|
+
const pinnedTopCells = this.buildPinnedCells(pinnedTopData, cols, stickyInfo, 'top', rowHeight, 0);
|
|
1260
|
+
const pinnedBottomCells = this.buildPinnedCells(pinnedBottomData, cols, stickyInfo, 'bottom', rowHeight, this._array.length - this.pinnedBottom);
|
|
1179
1261
|
// Data cells via listBinding with itemsPerRow
|
|
1180
1262
|
const selectEnabled = this.select || this.multiple;
|
|
1181
1263
|
const selectBindingFn = this.selectBinding;
|
|
1264
|
+
const { rowRendered } = this;
|
|
1265
|
+
const lastCol = cols.length - 1;
|
|
1266
|
+
const rowRenderedBinding = rowRendered
|
|
1267
|
+
? (cell) => {
|
|
1268
|
+
const item = getListItem(cell);
|
|
1269
|
+
if (item != null) {
|
|
1270
|
+
const cells = this.getCells(item);
|
|
1271
|
+
if (cells)
|
|
1272
|
+
rowRendered(item, cells);
|
|
1273
|
+
}
|
|
1274
|
+
}
|
|
1275
|
+
: null;
|
|
1182
1276
|
const binding = this.rowData.visible.listBinding(({ span: s }, item, colIndex) => {
|
|
1183
1277
|
const col = cols[colIndex];
|
|
1184
1278
|
const si = stickyInfo[colIndex];
|
|
@@ -1186,14 +1280,16 @@ export class TosiTable extends WebComponent {
|
|
|
1186
1280
|
if (col.dataCell != null) {
|
|
1187
1281
|
const customCell = col.dataCell(col);
|
|
1188
1282
|
this.applyPinnedToCustomCell(customCell, colIndex, si, style);
|
|
1283
|
+
if (rowRenderedBinding && colIndex === lastCol) {
|
|
1284
|
+
bind(customCell, item, { toDOM: rowRenderedBinding });
|
|
1285
|
+
}
|
|
1189
1286
|
return customCell;
|
|
1190
1287
|
}
|
|
1191
1288
|
const props = {
|
|
1192
1289
|
class: this.cellClasses('td', si),
|
|
1193
|
-
role: '
|
|
1290
|
+
role: 'gridcell',
|
|
1194
1291
|
tabindex: -1,
|
|
1195
1292
|
style,
|
|
1196
|
-
dataCol: String(colIndex),
|
|
1197
1293
|
bindText: item[col.prop],
|
|
1198
1294
|
};
|
|
1199
1295
|
if (selectEnabled) {
|
|
@@ -1202,6 +1298,19 @@ export class TosiTable extends WebComponent {
|
|
|
1202
1298
|
binding: { toDOM: selectBindingFn },
|
|
1203
1299
|
};
|
|
1204
1300
|
}
|
|
1301
|
+
// Fire rowRendered on the last cell of each row
|
|
1302
|
+
if (rowRenderedBinding && colIndex === lastCol) {
|
|
1303
|
+
props.bind = {
|
|
1304
|
+
value: item,
|
|
1305
|
+
binding: {
|
|
1306
|
+
toDOM(cell) {
|
|
1307
|
+
if (selectEnabled)
|
|
1308
|
+
selectBindingFn(cell, getListItem(cell));
|
|
1309
|
+
rowRenderedBinding(cell);
|
|
1310
|
+
},
|
|
1311
|
+
},
|
|
1312
|
+
};
|
|
1313
|
+
}
|
|
1205
1314
|
return s(props);
|
|
1206
1315
|
}, {
|
|
1207
1316
|
virtual: {
|