@bicharts/chart-host 0.6.112 → 0.6.115
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/README.md +77 -6
- package/dist/adapters.mjs +3 -3
- package/dist/card.mjs +2 -2
- package/dist/{chunk-2KLFIGTQ.mjs → chunk-2GW3S5VD.mjs} +130 -16
- package/dist/{chunk-TE54AFRN.mjs → chunk-7K3F3RZ6.mjs} +473 -128
- package/dist/{chunk-7XT5OJ6M.mjs → chunk-AQO4ROMU.mjs} +1 -1
- package/dist/{chunk-IL5M7K6Z.mjs → chunk-CVTCR7HQ.mjs} +2 -2
- package/dist/{chunk-GTJG3SMD.mjs → chunk-DILJDN6C.mjs} +1 -1
- package/dist/{chunk-KY4FI2HA.mjs → chunk-GXR7T6G5.mjs} +1 -1
- package/dist/{chunk-KGVNZR23.mjs → chunk-PQKLWYKK.mjs} +1 -1
- package/dist/{chunk-PELRAFYZ.mjs → chunk-QLIRQJ6B.mjs} +1 -1
- package/dist/{chunk-FIG5DIW7.mjs → chunk-VPFP35XI.mjs} +72 -72
- package/dist/{chunk-AEO5SFEQ.mjs → chunk-YOCYWON4.mjs} +1 -1
- package/dist/{chunk-OLCUKXKL.mjs → chunk-YSRMSDRP.mjs} +1 -1
- package/dist/dax.mjs +1 -1
- package/dist/index.mjs +43 -9
- package/dist/legend-fit.mjs +3 -3
- package/dist/page.mjs +7 -5
- package/dist/provinces.mjs +1 -1
- package/dist/react.mjs +15 -12
- package/dist/testing.mjs +1 -1
- package/dist/types/contract.d.ts +14 -3
- package/dist/types/filterScope.d.ts +42 -3
- package/dist/types/host.d.ts +9 -5
- package/dist/types/index.d.ts +3 -1
- package/dist/types/keyboardMarks.d.ts +1 -1
- package/dist/types/markFollower.d.ts +21 -0
- package/dist/types/page.d.ts +1 -1
- package/dist/types/react.d.ts +11 -3
- package/dist/types/selectionMarker.d.ts +92 -0
- package/dist/zoom-memory.mjs +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @bicharts/chart-host
|
|
2
2
|
|
|
3
|
-
> **Make Generative AI for
|
|
3
|
+
> **Make Generative AI for Data Apps and Maps *Better*.**
|
|
4
4
|
|
|
5
5
|
**AI Data Components, running in your page:** a bivariate map with its two-way key, a what-if projection with its
|
|
6
6
|
controls inside the chart, a flow map, and about 90 everyday chart types, with selection, cross-filtering and the legend
|
|
@@ -113,6 +113,50 @@ of the affordance, they are re-applied on every render (so a manual `classList`
|
|
|
113
113
|
the next data change), and getting row indices right across a filtered payload is the hazard
|
|
114
114
|
above.
|
|
115
115
|
|
|
116
|
+
### A panel whose query takes the page filter
|
|
117
|
+
|
|
118
|
+
A chart whose DAX query is filtered by a page filter (`filterQuery(dax, treatAs(filter, "Table[Column]"))` from
|
|
119
|
+
`@bicharts/chart-host/dax`) gets rows that are already narrowed, so it can't see the filter. `scopedBy` tells it,
|
|
120
|
+
and its titles say so ("North America · United States only"):
|
|
121
|
+
|
|
122
|
+
```tsx
|
|
123
|
+
<BicChart id="kpis" code={kpiCode} d3={d3} data={kpiRows} scopedBy={country} />
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
`treatAs` maps the key columns it's given, in the filter's order, so a query can take only part of a compound key.
|
|
127
|
+
`treatAs(cell, "Accounts[Region]")` on a Region × Stage pick filters by the region alone, and a stage-label pick
|
|
128
|
+
(any region) doesn't filter that query at all. Name the key columns the query takes, so the scope line names only
|
|
129
|
+
those:
|
|
130
|
+
|
|
131
|
+
```tsx
|
|
132
|
+
<BicChart id="cases" code={casesCode} d3={d3} data={caseRows} scopedBy={{ filter: cell, takes: ["Region"] }} />
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
A cell pick then reads "Europe", not "Europe → 3-Propose", and a pick whose taken parts are all "any" adds
|
|
136
|
+
nothing to the line, since that query keeps every row. A bare filter, `{ filter }` or a `takes` that names every
|
|
137
|
+
key column says the whole key. `label` sets what the line calls the filter. Outside React, `filterScopeOf` from
|
|
138
|
+
`@bicharts/chart-host/page` takes the same list and returns `options.filterScope`.
|
|
139
|
+
|
|
140
|
+
### Several panels that pick the same key
|
|
141
|
+
|
|
142
|
+
A map, a table and a scatter that each pick a country are ONE pick. Give their filters one `group`, and keep each
|
|
143
|
+
panel's query taking every member but its own:
|
|
144
|
+
|
|
145
|
+
```tsx
|
|
146
|
+
const fromMap = useBicFilter("Country", { label: "Country (map)", group: "country" });
|
|
147
|
+
const fromTable = useBicFilter("Country", { label: "Country (table)", group: "country" });
|
|
148
|
+
const mapQuery = filterQuery(MAP_DAX, treatAs(fromTable, "Geo[Country]"));
|
|
149
|
+
const tableQuery = filterQuery(TABLE_DAX, treatAs(fromMap, "Geo[Country]"));
|
|
150
|
+
// <MapChart selects={fromMap} scopedBy={[fromTable]} /> <TableChart selects={fromTable} scopedBy={[fromMap]} />
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
A pick in one member clears the others, so the most recent pick is the page's one pick, and only the member holding
|
|
154
|
+
it is active: one chip, one scope line. Every member's chart shows the pick, so clicking the picked mark again in
|
|
155
|
+
whichever panel shows it clears it, and the panel the reader picked in keeps all its marks. A list beside them (the
|
|
156
|
+
open items for the picked countries) reads the members' keys, which are that one pick. Without `group` the filters
|
|
157
|
+
are separate picks, ANDed: a country picked on the map and then in the table is held twice, and clicking it again on
|
|
158
|
+
the map leaves the page filtered by the table's. Outside React, `scope.filter(column, { group })` does the same.
|
|
159
|
+
|
|
116
160
|
## Vanilla
|
|
117
161
|
|
|
118
162
|
```js
|
|
@@ -287,8 +331,34 @@ the host's own write cannot be told apart from it.
|
|
|
287
331
|
createChartHost(el, { code, data, options: { dimOpacity: 0.15 } });
|
|
288
332
|
```
|
|
289
333
|
|
|
290
|
-
**
|
|
291
|
-
|
|
334
|
+
**The selection marker: an outline or a glyph on each selected mark.** The dim alone fails where a
|
|
335
|
+
selected mark's colour sits close to the canvas: a diverging ramp's centre is a neutral just off the
|
|
336
|
+
canvas, and the dim pulls every other mark toward it too, so a selected region near the centre reads
|
|
337
|
+
the same as the dimmed ones. Two options choose what marks the selection beyond the dim:
|
|
338
|
+
|
|
339
|
+
- `options.selectionGlyph`, the *selection marker* on every chart that isn't a region map (the key
|
|
340
|
+
keeps its first name): `"auto"` (the default), `"none"`, `"dot"`, `"check"` or `"outline"`.
|
|
341
|
+
`"auto"` is an outline on a chart that declares a neutral-centre ramp (the shared value ramp's
|
|
342
|
+
diverging legend, `.llm-value-ramp[data-kind="diverging"]`) and nothing on any other chart, which
|
|
343
|
+
draws exactly as it did before.
|
|
344
|
+
- `options.mapSelectionGlyph`, the marker on a *region map* (a chart whose rows carry the host's
|
|
345
|
+
`__geoIso__` join column: a choropleth, a cartogram, a province map): `"outline"` (the default),
|
|
346
|
+
`"none"`, `"dot"` or `"check"`. A point map places its rows by coordinates and has no join column,
|
|
347
|
+
so it takes `selectionGlyph`.
|
|
348
|
+
|
|
349
|
+
An outline (`.lch-sel-outline`, in the same host layer as the glyphs) is each selected AREA mark's own
|
|
350
|
+
shape (a filled path, rect or polygon) stroked 2px in the chart's text colour (`themeFg`, else the
|
|
351
|
+
colour the container's text inherits) over a halo of its canvas (`themeBg`). It's drawn over the chart,
|
|
352
|
+
so a border shared with a neighbour isn't painted over, and the marks themselves are neither restyled
|
|
353
|
+
nor moved: the keyboard's order, a chart's transitions and what a click hits are unchanged. The chart's own
|
|
354
|
+
controls (every element it declares `lch-control`, such as a map's zoom pad) are holes in the outline (`controlBoxesIn`, a mask): a
|
|
355
|
+
region that runs under the pad is outlined up to it, never across the buttons. A line or a
|
|
356
|
+
point keeps its geometry and gets no outline. `resolveOptions` gives the whole choice as
|
|
357
|
+
`selectionMarker` and `mapSelectionMarker`; `selectionMarkerFor` and the traits behind it
|
|
358
|
+
(`isRegionMapData`, `hasNeutralCentreRamp`) and the painter (`collectOutlineMarks`,
|
|
359
|
+
`paintSelectionOutlines`) are exported for a host that draws its own layer.
|
|
360
|
+
|
|
361
|
+
A glyph (`"dot"` or `"check"`) is a small mark on every selected mark, in a layer the host
|
|
292
362
|
owns inside the container (`.bic-selection-glyphs`, each glyph `.lch-sel-glyph`, never hit-tested).
|
|
293
363
|
It is painted after every selection change and, on a redraw, after the layout pass and the fit, so
|
|
294
364
|
nothing measures it as part of the chart. Only a mark the reader can see gets one: a mark hidden by
|
|
@@ -311,13 +381,14 @@ every point. The generate answer says so (`suppressSelectionGlyph`, read by shap
|
|
|
311
381
|
`parseGenerateResponse`); pass it as `options.suppressSelectionGlyph`, or leave it in the payload's
|
|
312
382
|
`meta` (`data.meta.suppressSelectionGlyph`) and the host promotes it, the way it promotes
|
|
313
383
|
`geoPoint`. Keep it with the chart's code, so a redraw from a cache honors it too.
|
|
314
|
-
`onSelectionGlyphs` reports what each paint did
|
|
315
|
-
|
|
384
|
+
The same fact keeps an outline off such a chart. `onSelectionGlyphs` reports what each paint did
|
|
385
|
+
(`{ mode, drawn, total, suppressed }`, `mode` one of `"dot"`, `"check"` or `"outline"`), and is called
|
|
386
|
+
only when the chart's marker is one of those:
|
|
316
387
|
|
|
317
388
|
```js
|
|
318
389
|
createChartHost(el, {
|
|
319
390
|
code, data,
|
|
320
|
-
options: { selectionGlyph: "dot", suppressSelectionGlyph: answer.suppressSelectionGlyph },
|
|
391
|
+
options: { selectionGlyph: "dot", mapSelectionGlyph: "outline", suppressSelectionGlyph: answer.suppressSelectionGlyph },
|
|
321
392
|
onSelectionGlyphs: r => console.debug("glyphs", r),
|
|
322
393
|
});
|
|
323
394
|
```
|
package/dist/adapters.mjs
CHANGED
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
censusHitBands,
|
|
4
4
|
ensureCrossfilterHitTargets,
|
|
5
5
|
hitBandFlag
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-YOCYWON4.mjs";
|
|
7
7
|
import {
|
|
8
8
|
clickWasDrag,
|
|
9
9
|
collectD3GlyphCenters,
|
|
@@ -12,13 +12,13 @@ import {
|
|
|
12
12
|
markKeyOf,
|
|
13
13
|
rowIdxsFromMark,
|
|
14
14
|
selectionRuleOf
|
|
15
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-PQKLWYKK.mjs";
|
|
16
16
|
import {
|
|
17
17
|
AXIS_FILTER_CLASS,
|
|
18
18
|
LEGEND_MARK_CLASS,
|
|
19
19
|
MARK_CLASS,
|
|
20
20
|
ROW_IDX_ATTR
|
|
21
|
-
} from "./chunk-
|
|
21
|
+
} from "./chunk-GXR7T6G5.mjs";
|
|
22
22
|
import "./chunk-GHODWOAL.mjs";
|
|
23
23
|
|
|
24
24
|
// src/adapters/types.ts
|
package/dist/card.mjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/*! @bicharts/chart-host — Apache-2.0. Bundled reference data: GeoNames (https://www.geonames.org/) CC BY 4.0; Natural Earth and US Census/TIGER (public domain). Full text: NOTICE in this package. */
|
|
2
2
|
import {
|
|
3
3
|
computeSelectionCard
|
|
4
|
-
} from "./chunk-
|
|
5
|
-
import "./chunk-
|
|
4
|
+
} from "./chunk-YSRMSDRP.mjs";
|
|
5
|
+
import "./chunk-VPFP35XI.mjs";
|
|
6
6
|
import "./chunk-GHODWOAL.mjs";
|
|
7
7
|
|
|
8
8
|
// src/selectionCardChrome.ts
|
|
@@ -43,6 +43,11 @@ function toKeys(input, width) {
|
|
|
43
43
|
return input.length === width ? [input.slice()] : [];
|
|
44
44
|
}
|
|
45
45
|
var nextId = 0;
|
|
46
|
+
var peersOf = /* @__PURE__ */ new WeakMap();
|
|
47
|
+
var pickSubsOf = /* @__PURE__ */ new WeakMap();
|
|
48
|
+
var firePick = (f) => {
|
|
49
|
+
for (const m of Array.from(peersOf.get(f) ?? [f])) for (const cb of Array.from(pickSubsOf.get(m) ?? [])) cb();
|
|
50
|
+
};
|
|
46
51
|
function createFilter(columns, opts = {}) {
|
|
47
52
|
const cols = Array.isArray(columns) ? columns.slice() : [columns];
|
|
48
53
|
if (!cols.length || cols.some((c) => typeof c !== "string" || !c)) {
|
|
@@ -71,7 +76,11 @@ function createFilter(columns, opts = {}) {
|
|
|
71
76
|
rows = nextRows;
|
|
72
77
|
reason = why;
|
|
73
78
|
version++;
|
|
79
|
+
if (nextKeys.length) {
|
|
80
|
+
for (const p of Array.from(peersOf.get(filter) ?? [])) if (p !== filter && p.active) p.clear(why);
|
|
81
|
+
}
|
|
74
82
|
for (const cb of Array.from(subs)) cb(filter, why);
|
|
83
|
+
firePick(filter);
|
|
75
84
|
};
|
|
76
85
|
const dedupe = (ks) => {
|
|
77
86
|
const seen2 = /* @__PURE__ */ new Set();
|
|
@@ -94,6 +103,15 @@ function createFilter(columns, opts = {}) {
|
|
|
94
103
|
const text = parts.filter(shown).map(String).join(" \u2192 ");
|
|
95
104
|
return text || k.filter(shown).map(String).join(" \u2192 ");
|
|
96
105
|
};
|
|
106
|
+
const partsOf = (k, more, names) => {
|
|
107
|
+
const s = keyString(k);
|
|
108
|
+
const pos = cols.map((c, i) => names.includes(c) && !isAnyValue(k[i]) ? i : -1).filter((i) => i >= 0);
|
|
109
|
+
const row = (more ?? []).find((r) => keyString(keyOfRow(r, cols)) === s) ?? seen.get(s);
|
|
110
|
+
const speaksFor = (d, j) => cols.includes(d) ? cols.indexOf(d) : display.length === cols.length ? j : -1;
|
|
111
|
+
const shown = (p) => p != null && p !== "" && !isAnyValue(p);
|
|
112
|
+
const text = row ? display.filter((d, j) => pos.includes(speaksFor(d, j))).map((d) => row[d]).filter(shown).map(String).join(" \u2192 ") : "";
|
|
113
|
+
return text || pos.map((i) => k[i]).filter(shown).map(String).join(" \u2192 ");
|
|
114
|
+
};
|
|
97
115
|
let warnedKeep = false;
|
|
98
116
|
const filter = {
|
|
99
117
|
id: opts.id ?? `f${++nextId}`,
|
|
@@ -137,9 +155,9 @@ function createFilter(columns, opts = {}) {
|
|
|
137
155
|
get text() {
|
|
138
156
|
return keys.map((k) => displayOf(k)).join(", ");
|
|
139
157
|
},
|
|
140
|
-
textOf(key, more) {
|
|
141
|
-
const k = Array.isArray(key) ? key : [key];
|
|
142
|
-
return
|
|
158
|
+
textOf(key, more, parts) {
|
|
159
|
+
const k = (Array.isArray(key) ? key : [key]).slice(0, cols.length);
|
|
160
|
+
return parts && cols.some((c) => !parts.includes(c)) ? partsOf(k, more, parts) : displayOf(k, more);
|
|
143
161
|
},
|
|
144
162
|
get reason() {
|
|
145
163
|
return reason;
|
|
@@ -198,8 +216,23 @@ function createFilter(columns, opts = {}) {
|
|
|
198
216
|
return () => {
|
|
199
217
|
subs.delete(cb);
|
|
200
218
|
};
|
|
219
|
+
},
|
|
220
|
+
group: opts.group || null,
|
|
221
|
+
get pick() {
|
|
222
|
+
const peers = peersOf.get(filter);
|
|
223
|
+
if (!peers) return keys.length ? filter : null;
|
|
224
|
+
for (const p of peers) if (p.active) return p;
|
|
225
|
+
return null;
|
|
226
|
+
},
|
|
227
|
+
onPick(cb) {
|
|
228
|
+
pickSubs.add(cb);
|
|
229
|
+
return () => {
|
|
230
|
+
pickSubs.delete(cb);
|
|
231
|
+
};
|
|
201
232
|
}
|
|
202
233
|
};
|
|
234
|
+
const pickSubs = /* @__PURE__ */ new Set();
|
|
235
|
+
pickSubsOf.set(filter, pickSubs);
|
|
203
236
|
return filter;
|
|
204
237
|
}
|
|
205
238
|
function createFilterScope() {
|
|
@@ -211,6 +244,35 @@ function createFilterScope() {
|
|
|
211
244
|
const notify = () => {
|
|
212
245
|
for (const cb of Array.from(subs)) cb(scope);
|
|
213
246
|
};
|
|
247
|
+
const groups = /* @__PURE__ */ new Map();
|
|
248
|
+
const join = (f) => {
|
|
249
|
+
const name = f.group;
|
|
250
|
+
if (!name) return;
|
|
251
|
+
const members = groups.get(name) ?? /* @__PURE__ */ new Set();
|
|
252
|
+
const first = members.values().next().value;
|
|
253
|
+
if (first && first.columns.join("") !== f.columns.join("")) {
|
|
254
|
+
try {
|
|
255
|
+
console.warn(`[chart-host] filter "${f.label}" is keyed by ${f.columns.join(", ")} but its group "${name}" by ${first.columns.join(", ")}, so it stays out of the group: give one pick's filters the same key columns.`);
|
|
256
|
+
} catch {
|
|
257
|
+
}
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
if (f.active && [...members].some((m) => m.active)) f.clear("page");
|
|
261
|
+
members.add(f);
|
|
262
|
+
groups.set(name, members);
|
|
263
|
+
peersOf.set(f, members);
|
|
264
|
+
firePick(f);
|
|
265
|
+
};
|
|
266
|
+
const leave = (f) => {
|
|
267
|
+
const members = f.group ? groups.get(f.group) : void 0;
|
|
268
|
+
if (!members?.has(f)) return;
|
|
269
|
+
members.delete(f);
|
|
270
|
+
peersOf.delete(f);
|
|
271
|
+
if (!members.size) groups.delete(f.group);
|
|
272
|
+
firePick(f);
|
|
273
|
+
const rest = members.values().next().value;
|
|
274
|
+
if (rest) firePick(rest);
|
|
275
|
+
};
|
|
214
276
|
const scope = {
|
|
215
277
|
get filters() {
|
|
216
278
|
return list.slice();
|
|
@@ -227,6 +289,7 @@ function createFilterScope() {
|
|
|
227
289
|
if (!offs.has(f)) {
|
|
228
290
|
list.push(f);
|
|
229
291
|
offs.set(f, f.onChange(() => notify()));
|
|
292
|
+
join(f);
|
|
230
293
|
notify();
|
|
231
294
|
}
|
|
232
295
|
return () => {
|
|
@@ -236,6 +299,7 @@ function createFilterScope() {
|
|
|
236
299
|
offs.delete(f);
|
|
237
300
|
const i = list.indexOf(f);
|
|
238
301
|
if (i >= 0) list.splice(i, 1);
|
|
302
|
+
leave(f);
|
|
239
303
|
notify();
|
|
240
304
|
};
|
|
241
305
|
},
|
|
@@ -278,10 +342,15 @@ function createFilterScope() {
|
|
|
278
342
|
},
|
|
279
343
|
restore(view) {
|
|
280
344
|
if (!view || view.v !== 1) return;
|
|
345
|
+
const restored = /* @__PURE__ */ new Set();
|
|
281
346
|
for (const f of list) {
|
|
282
347
|
const keys = view.filters?.[f.key];
|
|
283
|
-
|
|
284
|
-
|
|
348
|
+
const g = f.group && peersOf.get(f) ? f.group : null;
|
|
349
|
+
if (g && restored.has(g)) continue;
|
|
350
|
+
if (keys && keys.length) {
|
|
351
|
+
f.set(keys, "page");
|
|
352
|
+
if (g) restored.add(g);
|
|
353
|
+
} else f.clear("page");
|
|
285
354
|
}
|
|
286
355
|
for (const [k, c] of ctl) {
|
|
287
356
|
const values = view.controls?.[k];
|
|
@@ -291,19 +360,47 @@ function createFilterScope() {
|
|
|
291
360
|
};
|
|
292
361
|
return scope;
|
|
293
362
|
}
|
|
363
|
+
function scopeFilterOf(src) {
|
|
364
|
+
if (!src || typeof src !== "object") return null;
|
|
365
|
+
if (Array.isArray(src.keys)) return src;
|
|
366
|
+
const f = src.filter;
|
|
367
|
+
return f && typeof f === "object" ? f : null;
|
|
368
|
+
}
|
|
369
|
+
var warnedTakes = /* @__PURE__ */ new Set();
|
|
294
370
|
function filterScopeOf(filters) {
|
|
295
371
|
const list = Array.isArray(filters) ? filters : filters ? [filters] : [];
|
|
296
|
-
const active = list.filter((f) => f && f.active);
|
|
297
372
|
const values = [];
|
|
298
|
-
|
|
373
|
+
const fields = [];
|
|
374
|
+
for (const src of list) {
|
|
375
|
+
const f = scopeFilterOf(src);
|
|
376
|
+
if (!f || !f.active) continue;
|
|
377
|
+
const entry = src === f ? null : src;
|
|
378
|
+
const takes = entry?.takes;
|
|
379
|
+
for (const c of takes ?? []) {
|
|
380
|
+
if (f.columns.includes(c) || warnedTakes.has(`${f.id}${c}`)) continue;
|
|
381
|
+
warnedTakes.add(`${f.id}${c}`);
|
|
382
|
+
try {
|
|
383
|
+
console.warn(`[chart-host] scopedBy: filter "${f.label}" isn't keyed by ${c} (its key: ${f.columns.join(", ")}), so its scope line can't take it - name the filter's own key columns in takes.`);
|
|
384
|
+
} catch {
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
const part = takes ? f.columns.filter((c) => takes.includes(c)) : f.columns;
|
|
388
|
+
const whole = part.length === f.columns.length;
|
|
389
|
+
if (!whole) {
|
|
390
|
+
const at = part.map((c) => f.columns.indexOf(c));
|
|
391
|
+
if (!at.length || f.keys.some((k) => at.every((i) => isAnyValue(k[i])))) continue;
|
|
392
|
+
}
|
|
393
|
+
let said = false;
|
|
299
394
|
for (const k of f.keys) {
|
|
300
|
-
const t = f.textOf(k).trim();
|
|
395
|
+
const t = (whole ? f.textOf(k) : f.textOf(k, void 0, part)).trim();
|
|
396
|
+
if (t) said = true;
|
|
301
397
|
if (t && !values.includes(t)) values.push(t);
|
|
302
398
|
}
|
|
399
|
+
if (!whole && !said) continue;
|
|
400
|
+
const label = entry?.label || (whole || f.label !== f.columns.join(" / ") ? f.label : part.join(" / "));
|
|
401
|
+
if (label && !fields.includes(label)) fields.push(label);
|
|
303
402
|
}
|
|
304
403
|
if (!values.length) return void 0;
|
|
305
|
-
const fields = [];
|
|
306
|
-
for (const f of active) if (f.label && !fields.includes(f.label)) fields.push(f.label);
|
|
307
404
|
return { field: fields.join(", "), values };
|
|
308
405
|
}
|
|
309
406
|
var LINKED_HOVER_CLASS = "bic-linked-hover";
|
|
@@ -339,18 +436,21 @@ var sortedEq = (a, b) => {
|
|
|
339
436
|
return x.every((v, i) => v === y[i]);
|
|
340
437
|
};
|
|
341
438
|
function bindFilter(host, filter, opts) {
|
|
439
|
+
const pickOf = () => filter.pick ?? filter;
|
|
342
440
|
const wanted = () => {
|
|
343
|
-
|
|
441
|
+
const src = pickOf();
|
|
442
|
+
if (!src.active) return [];
|
|
344
443
|
const out = [];
|
|
345
444
|
const n = opts.rowCount();
|
|
346
445
|
for (let i = 0; i < n; i++) {
|
|
347
446
|
const r = opts.rowAt(i);
|
|
348
|
-
if (r &&
|
|
447
|
+
if (r && src.has(r)) out.push(i);
|
|
349
448
|
}
|
|
350
449
|
return out;
|
|
351
450
|
};
|
|
352
451
|
const hiddenKeys = () => {
|
|
353
|
-
|
|
452
|
+
const src = pickOf();
|
|
453
|
+
if (!src.active) return [];
|
|
354
454
|
const carried = /* @__PURE__ */ new Set();
|
|
355
455
|
const n = opts.rowCount();
|
|
356
456
|
for (let i = 0; i < n; i++) {
|
|
@@ -364,7 +464,7 @@ function bindFilter(host, filter, opts) {
|
|
|
364
464
|
}
|
|
365
465
|
return false;
|
|
366
466
|
};
|
|
367
|
-
return
|
|
467
|
+
return src.keys.filter((k) => k.some(isAnyValue) ? !anyRowMatches(k) : !carried.has(keyString(k)));
|
|
368
468
|
};
|
|
369
469
|
const paint = () => {
|
|
370
470
|
const want = wanted();
|
|
@@ -379,7 +479,8 @@ function bindFilter(host, filter, opts) {
|
|
|
379
479
|
if (source === "host") return;
|
|
380
480
|
const keep = meta?.keepHidden ? hiddenKeys() : [];
|
|
381
481
|
if (!idxs.length && !keep.length) {
|
|
382
|
-
|
|
482
|
+
const src = pickOf();
|
|
483
|
+
if (src.active) src.clear("clear");
|
|
383
484
|
return;
|
|
384
485
|
}
|
|
385
486
|
const rows = idxs.map((i) => opts.rowAt(i)).filter((r) => !!r);
|
|
@@ -389,8 +490,20 @@ function bindFilter(host, filter, opts) {
|
|
|
389
490
|
filter.set([label], "click");
|
|
390
491
|
return;
|
|
391
492
|
}
|
|
493
|
+
const group = meta?.fromMark && !keep.length ? sharedKey(rows) : null;
|
|
494
|
+
if (group) {
|
|
495
|
+
filter.set([group], "click");
|
|
496
|
+
return;
|
|
497
|
+
}
|
|
392
498
|
filter.selectRows(rows, "click", keep);
|
|
393
499
|
});
|
|
500
|
+
const sharedKey = (rows) => {
|
|
501
|
+
const key = filter.columns.map((c) => {
|
|
502
|
+
const v = rows[0][c];
|
|
503
|
+
return rows.every((r) => keyString([r[c]]) === keyString([v])) ? v : ANY_VALUE;
|
|
504
|
+
});
|
|
505
|
+
return key.some(isAnyValue) && !key.every(isAnyValue) ? key : null;
|
|
506
|
+
};
|
|
394
507
|
const labelKey = (rows) => {
|
|
395
508
|
const n = opts.rowCount();
|
|
396
509
|
const key = filter.columns.map((c) => {
|
|
@@ -404,7 +517,7 @@ function bindFilter(host, filter, opts) {
|
|
|
404
517
|
});
|
|
405
518
|
return key.some(isAnyValue) && !key.every(isAnyValue) ? key : null;
|
|
406
519
|
};
|
|
407
|
-
const offFilter = filter.onChange(() => paint());
|
|
520
|
+
const offFilter = filter.onPick ? filter.onPick(() => paint()) : filter.onChange(() => paint());
|
|
408
521
|
const el = opts.container ?? null;
|
|
409
522
|
let offHoverDom = () => {
|
|
410
523
|
};
|
|
@@ -486,6 +599,7 @@ export {
|
|
|
486
599
|
isAnyValue,
|
|
487
600
|
createFilter,
|
|
488
601
|
createFilterScope,
|
|
602
|
+
scopeFilterOf,
|
|
489
603
|
filterScopeOf,
|
|
490
604
|
LINKED_HOVER_CLASS,
|
|
491
605
|
payloadRowReader,
|