tosijs-ui 1.9.7 → 1.9.8
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 +32 -0
- package/dist/data-table.d.ts +22 -1
- package/dist/data-table.js +81 -1
- package/dist/iife.js +63 -63
- package/dist/iife.js.map +5 -5
- package/dist/row-grouping.d.ts +18 -0
- package/dist/row-grouping.js +27 -0
- 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/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,37 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.9.8
|
|
4
|
+
|
|
5
|
+
**Grouped tables can now say how much of a group is showing.** The piece missing from 1.9.7's
|
|
6
|
+
row grouping: a cell that reports on its group ("showing 2 of 7") or offers a **show-all**
|
|
7
|
+
toggle needs to compare what is rendered against what exists — and that is the one comparison
|
|
8
|
+
a cell renderer cannot make for itself. The filter has already run by then, so a consumer
|
|
9
|
+
sees the survivors with nothing to measure them against. The table sees both sides.
|
|
10
|
+
|
|
11
|
+
```js
|
|
12
|
+
const id = table.groupIdFor(row)
|
|
13
|
+
const { visible, total } = table.rowGroupCounts.get(id) ?? { visible: 0, total: 0 }
|
|
14
|
+
cell.textContent = visible < total ? `showing ${visible} of ${total}` : `${total} lines`
|
|
15
|
+
|
|
16
|
+
// the toggle is just a set of ids handed back to the table
|
|
17
|
+
expanded.has(id) ? expanded.delete(id) : expanded.add(id)
|
|
18
|
+
table.visibleGroupedRowIds = [...expanded]
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
- **`table.rowGroupCounts`** — a `Map` from group id to `{ visible, total }`, rendered rows
|
|
22
|
+
against rows before filtering. Recomputed each render and readable while cells render.
|
|
23
|
+
- **`table.groupIdFor(row)`** — a row's group id, or `null` when ungrouped. This matters when
|
|
24
|
+
the grouping was **inferred** from `nonRepeatingGroupedRowCells`: you never wrote that
|
|
25
|
+
function, so its ids are otherwise unreproducible and the map would be keyed by strings you
|
|
26
|
+
could not construct.
|
|
27
|
+
|
|
28
|
+
Groups the filter removed **entirely** stay in the map with `visible: 0`, so "nothing here
|
|
29
|
+
matched" stays distinguishable from "no such group" — the first being exactly when a show-all
|
|
30
|
+
toggle is worth offering. `rowGroupCounts` is always a `Map` (empty when ungrouped), so
|
|
31
|
+
`.get()` needs no null check. Pinned rows sit outside grouping and are not counted.
|
|
32
|
+
|
|
33
|
+
Additive only; ungrouped tables are unaffected.
|
|
34
|
+
|
|
3
35
|
## 1.9.7
|
|
4
36
|
|
|
5
37
|
**`<tosi-table>` can group rows.** The motivating shape is invoice lines: rows that belong
|
package/dist/data-table.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Component as WebComponent, ElementCreator } from 'tosijs';
|
|
2
2
|
import { SortCallback } from './make-sorter.js';
|
|
3
|
-
import { RowGroupIdFn } from './row-grouping.js';
|
|
3
|
+
import { RowGroupIdFn, GroupCount } from './row-grouping.js';
|
|
4
4
|
import { ValueRendererType } from './value-renderer.js';
|
|
5
5
|
export interface ColumnOptions {
|
|
6
6
|
name?: string;
|
|
@@ -201,7 +201,28 @@ export declare class TosiTable extends WebComponent {
|
|
|
201
201
|
set nonRepeatingGroupedRowCells(props: string[] | null);
|
|
202
202
|
/** The grouping function in force, or null when the table is ungrouped. */
|
|
203
203
|
private get groupIdFn();
|
|
204
|
+
/**
|
|
205
|
+
* This row's group id, or null when the table is ungrouped.
|
|
206
|
+
*
|
|
207
|
+
* Public because the grouping may be INFERRED from `nonRepeatingGroupedRowCells`, in which
|
|
208
|
+
* case the consumer never wrote the function and cannot reproduce its ids — which would
|
|
209
|
+
* leave `rowGroupCounts` keyed by strings they have no way to construct.
|
|
210
|
+
*/
|
|
211
|
+
groupIdFor(row: any): string | null;
|
|
212
|
+
/**
|
|
213
|
+
* Per-group `{ visible, total }` counts — rendered rows against rows before filtering.
|
|
214
|
+
*
|
|
215
|
+
* For cells that report or control their own group: "showing 2 of 7", or a toggle that
|
|
216
|
+
* adds the group to `visibleGroupedRowIds`. The table is the only thing that sees both
|
|
217
|
+
* sides of the filter, so this is the piece a custom cell renderer cannot derive itself.
|
|
218
|
+
*
|
|
219
|
+
* Recomputed each render, and always a Map — empty when ungrouped — so callers can `.get()`
|
|
220
|
+
* without a null check. Groups filtered away entirely are present with `visible: 0`;
|
|
221
|
+
* pinned rows sit outside grouping and are not counted.
|
|
222
|
+
*/
|
|
223
|
+
get rowGroupCounts(): Map<string, GroupCount>;
|
|
204
224
|
private _grouping;
|
|
225
|
+
private _rowGroupCounts;
|
|
205
226
|
get sort(): SortCallback | undefined;
|
|
206
227
|
set sort(sortFunc: SortCallback | undefined);
|
|
207
228
|
get columns(): ColumnOptions[];
|
package/dist/data-table.js
CHANGED
|
@@ -587,6 +587,36 @@ For the same decision in JavaScript — inside a `dataCell` binding or a `rowRen
|
|
|
587
587
|
callback — **`table.isFirstInGroup(row)`** answers it directly. It is always `true` when the
|
|
588
588
|
table is ungrouped.
|
|
589
589
|
|
|
590
|
+
### `rowGroupCounts` — how much of each group is showing
|
|
591
|
+
|
|
592
|
+
A cell that reports on its group ("showing 2 of 7") or offers a **show-all** toggle needs to
|
|
593
|
+
know how many rows the group has, not just how many are on screen. That comparison is the
|
|
594
|
+
one thing a cell renderer cannot make for itself: the filter has already run, so a consumer
|
|
595
|
+
sees the survivors and has nothing to measure them against.
|
|
596
|
+
|
|
597
|
+
**`table.rowGroupCounts`** is a `Map` from group id to `{ visible, total }` — rendered rows
|
|
598
|
+
against rows before filtering — recomputed each render and available while cells render.
|
|
599
|
+
**`table.groupIdFor(row)`** gives a row's group id, which matters when the grouping was
|
|
600
|
+
*inferred* from `nonRepeatingGroupedRowCells`: you never wrote that function, so its ids are
|
|
601
|
+
otherwise unreproducible.
|
|
602
|
+
|
|
603
|
+
```typescript
|
|
604
|
+
// inside a dataCell binding, or rowRendered
|
|
605
|
+
const id = table.groupIdFor(row)
|
|
606
|
+
const { visible, total } = table.rowGroupCounts.get(id) ?? { visible: 0, total: 0 }
|
|
607
|
+
cell.textContent = visible < total ? `showing ${visible} of ${total}` : `${total} lines`
|
|
608
|
+
|
|
609
|
+
// …and the toggle is just a set of ids handed back to the table
|
|
610
|
+
expanded.has(id) ? expanded.delete(id) : expanded.add(id)
|
|
611
|
+
table.visibleGroupedRowIds = [...expanded]
|
|
612
|
+
```
|
|
613
|
+
|
|
614
|
+
Groups the filter removed **entirely** are still in the map, with `visible: 0` — otherwise
|
|
615
|
+
"nothing in this group matched" would be indistinguishable from "no such group", and the
|
|
616
|
+
first is precisely when a show-all toggle is worth offering. The map is always a `Map`
|
|
617
|
+
(empty when ungrouped), so `.get()` needs no null check. Pinned rows sit outside grouping
|
|
618
|
+
and are not counted.
|
|
619
|
+
|
|
590
620
|
### Example
|
|
591
621
|
|
|
592
622
|
Grouping the emoji table by category and subcategory. It is grouped by *inference* — only
|
|
@@ -679,6 +709,26 @@ test('stripes are per group and survive virtualisation', () => {
|
|
|
679
709
|
}
|
|
680
710
|
})
|
|
681
711
|
|
|
712
|
+
test('rowGroupCounts is keyed by groupIdFor, and totals the whole dataset', () => {
|
|
713
|
+
// The integration that matters: the grouping here is INFERRED, so `groupIdFor` is the
|
|
714
|
+
// only way to produce a key, and a mismatch between the two would make the map useless
|
|
715
|
+
// while both halves still looked fine on their own.
|
|
716
|
+
const counts = table.rowGroupCounts
|
|
717
|
+
expect(counts.size).toBe(groupIndex.size)
|
|
718
|
+
for (const row of rows) {
|
|
719
|
+
expect(counts.has(table.groupIdFor(row))).toBe(true)
|
|
720
|
+
}
|
|
721
|
+
// No filter is set on this example, so every group is fully visible…
|
|
722
|
+
let summed = 0
|
|
723
|
+
for (const { visible, total } of counts.values()) {
|
|
724
|
+
expect(visible).toBe(total)
|
|
725
|
+
expect(total).toBeGreaterThan(0)
|
|
726
|
+
summed += total
|
|
727
|
+
}
|
|
728
|
+
// …and the totals account for every row, rather than just the stamped ones.
|
|
729
|
+
expect(summed).toBe(rows.length)
|
|
730
|
+
})
|
|
731
|
+
|
|
682
732
|
test('category and subcategory are shown once per group, and nothing else is hidden', () => {
|
|
683
733
|
const visible = (cell) => getComputedStyle(cell).visibility !== 'hidden'
|
|
684
734
|
let shown = 0
|
|
@@ -763,7 +813,7 @@ As well as any column names you want localized.
|
|
|
763
813
|
import { Component as WebComponent, elements, vars, varDefault, tosiValue, getListItem, getListBinding, tosi, } from 'tosijs';
|
|
764
814
|
import { trackDrag } from './track-drag.js';
|
|
765
815
|
import { naturalSorter } from './natural-compare.js';
|
|
766
|
-
import { resolveRowGroupId, withForcedGroups, clusterByGroup, groupRenderMeta, } from './row-grouping.js';
|
|
816
|
+
import { resolveRowGroupId, withForcedGroups, clusterByGroup, groupRenderMeta, groupCounts, } from './row-grouping.js';
|
|
767
817
|
import { icons } from './icons.js';
|
|
768
818
|
import { valueRenderer, } from './value-renderer.js';
|
|
769
819
|
import { popMenu } from './menu.js';
|
|
@@ -1136,6 +1186,31 @@ export class TosiTable extends WebComponent {
|
|
|
1136
1186
|
get groupIdFn() {
|
|
1137
1187
|
return resolveRowGroupId(this._rowGroupId, this._nonRepeatingGroupedRowCells);
|
|
1138
1188
|
}
|
|
1189
|
+
/**
|
|
1190
|
+
* This row's group id, or null when the table is ungrouped.
|
|
1191
|
+
*
|
|
1192
|
+
* Public because the grouping may be INFERRED from `nonRepeatingGroupedRowCells`, in which
|
|
1193
|
+
* case the consumer never wrote the function and cannot reproduce its ids — which would
|
|
1194
|
+
* leave `rowGroupCounts` keyed by strings they have no way to construct.
|
|
1195
|
+
*/
|
|
1196
|
+
groupIdFor(row) {
|
|
1197
|
+
const groupId = this.groupIdFn;
|
|
1198
|
+
return groupId ? groupId(row) : null;
|
|
1199
|
+
}
|
|
1200
|
+
/**
|
|
1201
|
+
* Per-group `{ visible, total }` counts — rendered rows against rows before filtering.
|
|
1202
|
+
*
|
|
1203
|
+
* For cells that report or control their own group: "showing 2 of 7", or a toggle that
|
|
1204
|
+
* adds the group to `visibleGroupedRowIds`. The table is the only thing that sees both
|
|
1205
|
+
* sides of the filter, so this is the piece a custom cell renderer cannot derive itself.
|
|
1206
|
+
*
|
|
1207
|
+
* Recomputed each render, and always a Map — empty when ungrouped — so callers can `.get()`
|
|
1208
|
+
* without a null check. Groups filtered away entirely are present with `visible: 0`;
|
|
1209
|
+
* pinned rows sit outside grouping and are not counted.
|
|
1210
|
+
*/
|
|
1211
|
+
get rowGroupCounts() {
|
|
1212
|
+
return this._rowGroupCounts;
|
|
1213
|
+
}
|
|
1139
1214
|
/*
|
|
1140
1215
|
Grouping facts for the CURRENT visible rows, recomputed each render.
|
|
1141
1216
|
|
|
@@ -1144,6 +1219,7 @@ export class TosiTable extends WebComponent {
|
|
|
1144
1219
|
ungrouped, which is also what makes every grouping code path below a single null check.
|
|
1145
1220
|
*/
|
|
1146
1221
|
_grouping = null;
|
|
1222
|
+
_rowGroupCounts = new Map();
|
|
1147
1223
|
get sort() {
|
|
1148
1224
|
if (this._sort) {
|
|
1149
1225
|
return this._sort;
|
|
@@ -2080,9 +2156,13 @@ export class TosiTable extends WebComponent {
|
|
|
2080
2156
|
if (groupId) {
|
|
2081
2157
|
visibleData = clusterByGroup(visibleData, groupId);
|
|
2082
2158
|
this._grouping = groupRenderMeta(visibleData, groupId);
|
|
2159
|
+
// Counted from `scope` (pre-filter) against the final rendered set, and assigned
|
|
2160
|
+
// BEFORE the rows below are stamped, so a cell renderer can read it as it renders.
|
|
2161
|
+
this._rowGroupCounts = groupCounts(scope, visibleData, groupId);
|
|
2083
2162
|
}
|
|
2084
2163
|
else {
|
|
2085
2164
|
this._grouping = null;
|
|
2165
|
+
this._rowGroupCounts = new Map();
|
|
2086
2166
|
}
|
|
2087
2167
|
this.rowData.pinnedTopData = pinnedTopData;
|
|
2088
2168
|
this.rowData.pinnedBottomData = pinnedBottomData;
|