@bicharts/chart-host 0.6.56 → 0.6.59

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/index.mjs CHANGED
@@ -1,63 +1,28 @@
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
- ACTIVE_TICK_CLASS,
4
- ANIMATION_OPTION_KEYS,
5
- ANIM_LOOP_DELAY_DEFAULT,
6
- ANIM_LOOP_DELAY_MIN,
7
- ANIM_MAX_IDEAL_FRAMES_DEFAULT,
8
- ANIM_MAX_IDEAL_FRAMES_MAX,
9
- ANIM_MAX_IDEAL_FRAMES_MIN,
10
- ANIM_PLAY_SPEED_DEFAULT,
11
- ANIM_PLAY_SPEED_MAX,
12
- ANIM_PLAY_SPEED_MIN,
13
3
  ANNOTATION_CLASS,
14
4
  ANNOTATION_LAYER_CLASS,
15
- APPROXIMATE_POSITIONS_DEFAULT,
16
5
  AUTO_RAMP_CLASSES,
17
6
  AUTO_RAMP_DRAWN_OPACITY,
18
7
  AUTO_RAMP_MIN_STEP_DELTA_E,
19
- AXIS_FILTER_CLASS,
20
8
  AXIS_PIN_BAND_PAD_PX,
21
9
  AXIS_PIN_MAX_BAND_FRACTION,
22
10
  AXIS_PIN_MIN_LABELS,
23
11
  AXIS_PIN_TRACK_REACH_PX,
24
12
  BACKING_MAJORITY,
25
- COLOR_SCALE_SELF_CLAMP_PCT_DEFAULT,
26
- COLOR_SCALE_SELF_CLAMP_PCT_MAX,
27
- COLOR_SCALE_SELF_CLAMP_PCT_MIN,
28
- CONTAINER_SLOT_ANIM_STOP,
29
- CONTAINER_SLOT_INITIAL_XF_MARK,
30
- CONTAINER_SLOT_UI_STATE,
31
- CONTAINER_SLOT_XF_CLEAR,
32
- CONTROL_CLASS,
33
13
  D3_PLUGIN_PACKAGES,
34
14
  DARK_TEXT,
35
- DIM_OPACITY_DEFAULT,
36
- DIM_OPACITY_VAR,
37
15
  FIT_CONTENT_SELECTOR,
38
- FLIP_DIRECTION_DEFAULT,
39
- FLIP_INTERVAL_MAX_MS,
40
- FLIP_INTERVAL_MIN_MS,
41
- FLIP_MODE_DEFAULT,
42
- GEO_POINT_PRECISIONS,
43
16
  GLYPH_SAMPLE_N,
44
- HOST_CONTAINER_CLASS,
45
- HOST_CONTRACT_VERSION,
46
17
  L,
47
18
  LABEL_CONTRAST_CAP,
48
19
  LABEL_CONTRAST_DONE_ATTR,
49
20
  LABEL_PILL_CLASS,
50
- LEGEND_MARK_CLASS,
51
- LIFT_SELECTED_CLASS,
52
21
  LIGHT_TEXT,
53
- MARK_CLASS,
54
- MARK_SELECTED_CLASS,
55
22
  MAX_FRAME_GROW_FACTOR,
56
- MAX_MAP_POINTS_DEFAULT,
57
23
  MIN_CONTRAST,
58
24
  MIN_HIT_BAND_PX,
59
25
  MIN_RAMP_FILLS,
60
- NO_ROW_MARKS_ATTR,
61
26
  PAGE_MATCH_TOLERANCE,
62
27
  PHANTOM_FRACTION,
63
28
  PILL_MIN_ALPHA,
@@ -65,23 +30,16 @@ import {
65
30
  PILL_OPAQUE_ALPHA,
66
31
  Qe,
67
32
  Qn,
68
- ROW_IDX_ATTR,
69
33
  SAME_SHADE_DELTA_E,
70
34
  SCROLL_SLACK_PX,
71
- SEASONAL_MARKERS_DEFAULT,
72
- SELECTION_ACTIVE_CLASS,
73
35
  SELECTION_GLYPH_CLASS,
74
- SNAPSHOT_SVG_ATTR,
75
36
  STRADDLE_COLS,
76
37
  STRADDLE_MIN_SHARE,
77
38
  STRADDLE_PILL_ALPHA,
78
39
  STRADDLE_ROWS,
79
40
  STRADDLE_TARGET_CONTRAST,
80
- VALUE_AXIS_BASELINE_DEFAULT,
81
41
  VALUE_AXIS_MIN_FIT,
82
- VIEW_ONLY_UI_STATE_KEYS,
83
42
  WHITE_TEXT_BG_LUM,
84
- XFILTER_REFRESH_EVENT,
85
43
  Zn,
86
44
  _3,
87
45
  applyLabelContrast,
@@ -97,7 +55,6 @@ import {
97
55
  censusHitBands,
98
56
  censusMarks,
99
57
  censusValuePlacement,
100
- chartOwnsTimeline,
101
58
  clearGeoCache,
102
59
  clearSelectionGlyphs,
103
60
  collectD3GlyphCenters,
@@ -138,7 +95,6 @@ import {
138
95
  noopViewStateProvider,
139
96
  o,
140
97
  paintSelectionGlyphs,
141
- periodTickSuppressesFeedback,
142
98
  pickColorOver,
143
99
  pillBacksGlyph,
144
100
  pinScrolledAxis,
@@ -163,10 +119,56 @@ import {
163
119
  toRGBA,
164
120
  toSourceRows,
165
121
  unpinScrolledAxis,
166
- valuePlacementFlag,
167
- withoutViewOnlyKeys
168
- } from "./chunk-CMHGGW3Y.mjs";
122
+ valuePlacementFlag
123
+ } from "./chunk-5NSU3MYW.mjs";
169
124
  import "./chunk-A2GMXZP7.mjs";
125
+ import {
126
+ ACTIVE_TICK_CLASS,
127
+ ANIMATION_OPTION_KEYS,
128
+ ANIM_LOOP_DELAY_DEFAULT,
129
+ ANIM_LOOP_DELAY_MIN,
130
+ ANIM_MAX_IDEAL_FRAMES_DEFAULT,
131
+ ANIM_MAX_IDEAL_FRAMES_MAX,
132
+ ANIM_MAX_IDEAL_FRAMES_MIN,
133
+ ANIM_PLAY_SPEED_DEFAULT,
134
+ ANIM_PLAY_SPEED_MAX,
135
+ ANIM_PLAY_SPEED_MIN,
136
+ APPROXIMATE_POSITIONS_DEFAULT,
137
+ AXIS_FILTER_CLASS,
138
+ COLOR_SCALE_SELF_CLAMP_PCT_DEFAULT,
139
+ COLOR_SCALE_SELF_CLAMP_PCT_MAX,
140
+ COLOR_SCALE_SELF_CLAMP_PCT_MIN,
141
+ CONTAINER_SLOT_ANIM_STOP,
142
+ CONTAINER_SLOT_INITIAL_XF_MARK,
143
+ CONTAINER_SLOT_UI_STATE,
144
+ CONTAINER_SLOT_XF_CLEAR,
145
+ CONTROL_CLASS,
146
+ DIM_OPACITY_DEFAULT,
147
+ DIM_OPACITY_VAR,
148
+ FLIP_DIRECTION_DEFAULT,
149
+ FLIP_INTERVAL_MAX_MS,
150
+ FLIP_INTERVAL_MIN_MS,
151
+ FLIP_MODE_DEFAULT,
152
+ GEO_POINT_PRECISIONS,
153
+ HOST_CONTAINER_CLASS,
154
+ HOST_CONTRACT_VERSION,
155
+ LEGEND_MARK_CLASS,
156
+ LIFT_SELECTED_CLASS,
157
+ MARK_CLASS,
158
+ MARK_SELECTED_CLASS,
159
+ MAX_MAP_POINTS_DEFAULT,
160
+ NO_ROW_MARKS_ATTR,
161
+ ROW_IDX_ATTR,
162
+ SEASONAL_MARKERS_DEFAULT,
163
+ SELECTION_ACTIVE_CLASS,
164
+ SNAPSHOT_SVG_ATTR,
165
+ VALUE_AXIS_BASELINE_DEFAULT,
166
+ VIEW_ONLY_UI_STATE_KEYS,
167
+ XFILTER_REFRESH_EVENT,
168
+ chartOwnsTimeline,
169
+ periodTickSuppressesFeedback,
170
+ withoutViewOnlyKeys
171
+ } from "./chunk-WNGJUPEI.mjs";
170
172
  import "./chunk-GHODWOAL.mjs";
171
173
 
172
174
  // src/optionsVocabulary.ts
package/dist/page.mjs ADDED
@@ -0,0 +1,34 @@
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
+ import {
3
+ LINKED_HOVER_CLASS,
4
+ attachControls,
5
+ badgeKey,
6
+ bindFilter,
7
+ createControls,
8
+ createFilter,
9
+ createFilterScope,
10
+ fromVegaInteraction,
11
+ noteBadges,
12
+ notesFor,
13
+ payloadRowReader,
14
+ readControls,
15
+ viewFromToken,
16
+ viewToken
17
+ } from "./chunk-XIAL6EEC.mjs";
18
+ import "./chunk-GHODWOAL.mjs";
19
+ export {
20
+ LINKED_HOVER_CLASS,
21
+ attachControls,
22
+ badgeKey,
23
+ bindFilter,
24
+ createControls,
25
+ createFilter,
26
+ createFilterScope,
27
+ fromVegaInteraction,
28
+ noteBadges,
29
+ notesFor,
30
+ payloadRowReader,
31
+ readControls,
32
+ viewFromToken,
33
+ viewToken
34
+ };
package/dist/react.mjs CHANGED
@@ -1,4 +1,19 @@
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
+ import {
3
+ LINKED_HOVER_CLASS,
4
+ attachControls,
5
+ badgeKey,
6
+ bindFilter,
7
+ createControls,
8
+ createFilter,
9
+ createFilterScope,
10
+ fromVegaInteraction,
11
+ noteBadges,
12
+ notesFor,
13
+ payloadRowReader,
14
+ viewFromToken,
15
+ viewToken
16
+ } from "./chunk-XIAL6EEC.mjs";
2
17
  import {
3
18
  assembleD3,
4
19
  createChartGroup,
@@ -7,13 +22,14 @@ import {
7
22
  loadGeo,
8
23
  syncMemberSelection,
9
24
  toSourceRows
10
- } from "./chunk-CMHGGW3Y.mjs";
25
+ } from "./chunk-5NSU3MYW.mjs";
11
26
  import "./chunk-A2GMXZP7.mjs";
27
+ import "./chunk-WNGJUPEI.mjs";
12
28
  import "./chunk-GHODWOAL.mjs";
13
29
 
14
30
  // src/react.tsx
15
- import { createContext, useContext, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
16
- import { jsx } from "react/jsx-runtime";
31
+ import { createContext, useContext, useEffect, useLayoutEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
32
+ import { jsx, jsxs } from "react/jsx-runtime";
17
33
  var Ctx = createContext(null);
18
34
  function useBicSelection() {
19
35
  const g = useContext(Ctx);
@@ -56,7 +72,10 @@ function BicChart(props) {
56
72
  annotations,
57
73
  onAnnotationClick,
58
74
  className,
59
- style
75
+ style,
76
+ selects,
77
+ controls,
78
+ ariaLabel
60
79
  } = props;
61
80
  const ref = useRef(null);
62
81
  const hostRef = useRef(null);
@@ -72,6 +91,18 @@ function BicChart(props) {
72
91
  }, [group, ctx?.source, filterSel && filterSel.join(",")]);
73
92
  const data = built ? built.payload : props.data;
74
93
  rowMapRef.current = built ? built.rowMap : null;
94
+ const dataRef = useRef(data);
95
+ dataRef.current = data;
96
+ const groupRef = useRef(group);
97
+ groupRef.current = group;
98
+ const bindingRef = useRef(null);
99
+ const controlsRef = useRef(null);
100
+ const readControlsSoon = () => {
101
+ const h = hostRef.current, c = controlsRef.current;
102
+ if (h && c) h.rendered.then(c.read, () => {
103
+ });
104
+ };
105
+ const userSelectedRef = useRef(false);
75
106
  const optKey = useMemo(() => JSON.stringify(options ?? {}), [options]);
76
107
  const builtWithRef = useRef(null);
77
108
  const onSelectRef = useRef(onSelect);
@@ -102,11 +133,14 @@ function BicChart(props) {
102
133
  useLayoutEffect(() => {
103
134
  const el = ref.current;
104
135
  if (!el || !code && !renderFn || !data) return;
136
+ const ctl = controls ? attachControls(() => hostRef.current, el, controls) : null;
137
+ controlsRef.current = ctl;
138
+ const hostOptions = ctl ? { ...options ?? {}, ...ctl.options } : options;
105
139
  const host = createChartHost(el, {
106
140
  code,
107
141
  renderFn,
108
142
  data,
109
- options,
143
+ options: hostOptions,
110
144
  d3,
111
145
  geoKind,
112
146
  viewState,
@@ -127,23 +161,54 @@ function BicChart(props) {
127
161
  builtAnnKeyRef.current = annKey;
128
162
  const off = host.selection.onChange((payloadIdxs, source) => {
129
163
  if (source === "host") return;
164
+ userSelectedRef.current = payloadIdxs.length > 0;
130
165
  const sourceIdxs = toSourceRows(rowMapRef.current, payloadIdxs);
131
166
  if (group && id) group.publish(id, sourceIdxs);
132
167
  onSelectRef.current?.(sourceIdxs);
133
168
  });
134
169
  host.render();
170
+ readControlsSoon();
135
171
  builtWithRef.current = { data, optKey };
136
172
  return () => {
137
173
  off();
174
+ ctl?.detach();
175
+ if (controlsRef.current === ctl) controlsRef.current = null;
138
176
  host.destroy();
139
177
  hostRef.current = null;
140
178
  };
141
- }, [code, renderFn, d3, geoKind, labelContrast]);
179
+ }, [code, renderFn, d3, geoKind, labelContrast, controls]);
180
+ useLayoutEffect(() => {
181
+ const host = hostRef.current;
182
+ if (!host || !selects) return;
183
+ const payloadRows = payloadRowReader(() => dataRef.current);
184
+ const b = bindFilter(host, selects, {
185
+ container: ref.current,
186
+ rowAt(i) {
187
+ const g = groupRef.current, map = rowMapRef.current;
188
+ if (g && map) {
189
+ const s = map[i];
190
+ return s === void 0 ? null : g.rows[s] ?? null;
191
+ }
192
+ return payloadRows.rowAt(i);
193
+ },
194
+ rowCount() {
195
+ const map = rowMapRef.current;
196
+ return groupRef.current && map ? map.length : payloadRows.rowCount();
197
+ }
198
+ });
199
+ bindingRef.current = b;
200
+ b.reconcile();
201
+ return () => {
202
+ b.detach();
203
+ if (bindingRef.current === b) bindingRef.current = null;
204
+ };
205
+ }, [selects, code, renderFn, d3, geoKind, labelContrast]);
142
206
  useEffect(() => {
143
207
  if (!hostRef.current || !options) return;
144
208
  if (builtWithRef.current?.optKey === optKey) return;
145
209
  builtWithRef.current = { ...builtWithRef.current, optKey };
146
210
  hostRef.current.setOptions(options);
211
+ readControlsSoon();
147
212
  }, [optKey]);
148
213
  const builtAnnKeyRef = useRef(null);
149
214
  useEffect(() => {
@@ -156,13 +221,220 @@ function BicChart(props) {
156
221
  if (builtWithRef.current?.data === data) return;
157
222
  builtWithRef.current = { ...builtWithRef.current, data };
158
223
  hostRef.current.setData(data);
224
+ bindingRef.current?.reconcile();
225
+ readControlsSoon();
159
226
  }, [data]);
227
+ const lastSourceRef = useRef(ctx?.source);
160
228
  useEffect(() => {
161
229
  const host = hostRef.current;
162
230
  if (!host || !ctx) return;
231
+ const sourceChanged = lastSourceRef.current !== ctx.source;
232
+ lastSourceRef.current = ctx.source;
233
+ if (selects) return;
234
+ const before = (host.selection.current ?? []).length;
163
235
  syncMemberSelection(host, ctx.selection, id, highlightMode, incoming, rowMapRef.current);
164
- }, [ctx?.selection, id, ctx, highlightMode, incoming && incoming.join(",")]);
165
- return /* @__PURE__ */ jsx("div", { ref, className, style });
236
+ if (sourceChanged && before && !(host.selection.current ?? []).length && userSelectedRef.current) {
237
+ userSelectedRef.current = false;
238
+ onSelectRef.current?.([]);
239
+ }
240
+ }, [ctx?.selection, id, ctx, highlightMode, incoming && incoming.join(","), selects]);
241
+ return /* @__PURE__ */ jsx(
242
+ "div",
243
+ {
244
+ ref,
245
+ className,
246
+ style,
247
+ role: ariaLabel ? "figure" : void 0,
248
+ "aria-label": ariaLabel || void 0
249
+ }
250
+ );
251
+ }
252
+ var defaultScope = createFilterScope();
253
+ var PageCtx = createContext(defaultScope);
254
+ function BicPage({ children }) {
255
+ const ref = useRef(null);
256
+ if (!ref.current) ref.current = createFilterScope();
257
+ return /* @__PURE__ */ jsx(PageCtx.Provider, { value: ref.current, children });
258
+ }
259
+ function useBicFilter(columns, opts) {
260
+ const scope = useContext(PageCtx);
261
+ const ref = useRef(null);
262
+ if (!ref.current) ref.current = createFilter(columns, opts);
263
+ const f = ref.current;
264
+ useEffect(() => scope.add(f), [scope, f]);
265
+ useSyncExternalStore((cb) => f.onChange(() => cb()), () => f.version, () => f.version);
266
+ return f;
267
+ }
268
+ function useBicControls(initial, opts) {
269
+ const scope = useContext(PageCtx);
270
+ const ref = useRef(null);
271
+ if (!ref.current) ref.current = createControls(initial);
272
+ const c = ref.current;
273
+ useEffect(() => scope.addControls(opts?.id ?? "controls", c), [scope, c, opts?.id]);
274
+ useSyncExternalStore((cb) => c.onChange(() => cb()), () => c.version, () => c.version);
275
+ return c;
276
+ }
277
+ function useBicView() {
278
+ const scope = useContext(PageCtx);
279
+ return useMemo(() => ({
280
+ save: () => scope.save(),
281
+ restore: (v) => scope.restore(v),
282
+ token: () => viewToken(scope.save()),
283
+ restoreToken: (t) => scope.restore(viewFromToken(t))
284
+ }), [scope]);
285
+ }
286
+ function useBicUrlState(param = "view") {
287
+ const scope = useContext(PageCtx);
288
+ useEffect(() => {
289
+ if (typeof window === "undefined") return;
290
+ const url = new URL(window.location.href);
291
+ scope.restore(viewFromToken(url.searchParams.get(param)));
292
+ return scope.onChange(() => {
293
+ const u = new URL(window.location.href);
294
+ const view = scope.save();
295
+ const empty = !view.filters && !view.controls;
296
+ if (empty) u.searchParams.delete(param);
297
+ else u.searchParams.set(param, viewToken(view));
298
+ if (u.href !== window.location.href) window.history.replaceState(window.history.state, "", u.href);
299
+ });
300
+ }, [scope, param]);
301
+ }
302
+ function BicDevtools({ max = 20 }) {
303
+ const scope = useContext(PageCtx);
304
+ const [, tick] = useState(0);
305
+ const log = useRef([]);
306
+ useEffect(() => scope.onChange(() => {
307
+ const at = (/* @__PURE__ */ new Date()).toLocaleTimeString();
308
+ const changed = scope.filters.map((f) => `${f.label}: ${f.active ? f.text : "(none)"}${f.reason ? ` [${f.reason}]` : ""}`);
309
+ log.current = [`${at} ${changed.join(" | ")}`, ...log.current].slice(0, max);
310
+ tick((t) => t + 1);
311
+ }), [scope, max]);
312
+ const box = {
313
+ position: "fixed",
314
+ right: 8,
315
+ bottom: 8,
316
+ zIndex: 2147483e3,
317
+ maxWidth: 420,
318
+ maxHeight: "50vh",
319
+ overflow: "auto",
320
+ font: "12px/1.4 ui-monospace, monospace",
321
+ background: "Canvas",
322
+ color: "CanvasText",
323
+ border: "1px solid GrayText",
324
+ borderRadius: 6,
325
+ padding: 6,
326
+ opacity: 0.95
327
+ };
328
+ return /* @__PURE__ */ jsxs("details", { className: "bic-devtools", style: box, children: [
329
+ /* @__PURE__ */ jsxs("summary", { style: { cursor: "pointer" }, children: [
330
+ "chart-host: ",
331
+ scope.active.length,
332
+ " filter(s) active"
333
+ ] }),
334
+ scope.filters.map((f) => /* @__PURE__ */ jsxs("div", { children: [
335
+ /* @__PURE__ */ jsx("b", { children: f.label }),
336
+ " [",
337
+ f.key,
338
+ "] ",
339
+ f.active ? JSON.stringify(f.keys) : "(none)",
340
+ " ",
341
+ f.text && `"${f.text}"`,
342
+ f.reason ? ` - last: ${f.reason}` : "",
343
+ " v",
344
+ f.version
345
+ ] }, f.id)),
346
+ [...scope.controls].map(([k, c]) => /* @__PURE__ */ jsxs("div", { children: [
347
+ /* @__PURE__ */ jsx("b", { children: "controls" }),
348
+ " [",
349
+ k,
350
+ "] ",
351
+ c.summary || JSON.stringify(c.values)
352
+ ] }, k)),
353
+ /* @__PURE__ */ jsx("div", { style: { marginTop: 4, opacity: 0.8 }, children: log.current.map((l, i) => /* @__PURE__ */ jsx("div", { children: l }, i)) })
354
+ ] });
355
+ }
356
+ function useBicFilters() {
357
+ const scope = useContext(PageCtx);
358
+ const [, tick] = useState(0);
359
+ useEffect(() => scope.onChange(() => tick((t) => t + 1)), [scope]);
360
+ return { filters: scope.filters, active: scope.active, clearAll: () => scope.clearAll() };
361
+ }
362
+ function BicFilterChips({ className, chipClassName, clearAllText = "Clear all", style }) {
363
+ const { active, clearAll } = useBicFilters();
364
+ if (!active.length) return null;
365
+ const chip = {
366
+ display: "inline-flex",
367
+ alignItems: "center",
368
+ gap: 6,
369
+ padding: "2px 4px 2px 10px",
370
+ border: "1px solid currentColor",
371
+ borderRadius: 999,
372
+ font: "inherit",
373
+ lineHeight: 1.6
374
+ };
375
+ const x = {
376
+ border: 0,
377
+ background: "transparent",
378
+ color: "inherit",
379
+ cursor: "pointer",
380
+ font: "inherit",
381
+ padding: "0 6px",
382
+ lineHeight: 1
383
+ };
384
+ return /* @__PURE__ */ jsxs(
385
+ "div",
386
+ {
387
+ className: ["bic-filter-chips", className].filter(Boolean).join(" "),
388
+ role: "group",
389
+ "aria-label": "Filters",
390
+ style: { display: "flex", flexWrap: "wrap", gap: 8, alignItems: "center", ...style },
391
+ children: [
392
+ active.map((f) => /* @__PURE__ */ jsxs(
393
+ "span",
394
+ {
395
+ className: ["bic-filter-chip", chipClassName].filter(Boolean).join(" "),
396
+ style: chipClassName ? void 0 : chip,
397
+ children: [
398
+ /* @__PURE__ */ jsxs("span", { className: "bic-filter-chip-label", children: [
399
+ f.label,
400
+ ": "
401
+ ] }),
402
+ /* @__PURE__ */ jsx("span", { className: "bic-filter-chip-value", children: f.text }),
403
+ /* @__PURE__ */ jsx(
404
+ "button",
405
+ {
406
+ type: "button",
407
+ className: "bic-filter-chip-clear",
408
+ "aria-label": `Clear ${f.label}`,
409
+ style: chipClassName ? void 0 : x,
410
+ onClick: () => f.clear(),
411
+ children: "\xD7"
412
+ }
413
+ )
414
+ ]
415
+ },
416
+ f.id
417
+ )),
418
+ active.length > 1 && /* @__PURE__ */ jsx(
419
+ "button",
420
+ {
421
+ type: "button",
422
+ className: "bic-filter-chips-clear-all",
423
+ onClick: clearAll,
424
+ style: {
425
+ font: "inherit",
426
+ background: "transparent",
427
+ border: 0,
428
+ color: "inherit",
429
+ textDecoration: "underline",
430
+ cursor: "pointer"
431
+ },
432
+ children: clearAllText
433
+ }
434
+ )
435
+ ]
436
+ }
437
+ );
166
438
  }
167
439
  function useMeasuredSize(ref) {
168
440
  const [size, setSize] = useState({ width: 0, height: 0 });
@@ -185,7 +457,25 @@ function useMeasuredSize(ref) {
185
457
  export {
186
458
  BicChart,
187
459
  BicChartGroup,
460
+ BicDevtools,
461
+ BicFilterChips,
462
+ BicPage,
463
+ LINKED_HOVER_CLASS,
188
464
  assembleD3,
465
+ badgeKey,
466
+ createControls,
467
+ createFilter,
468
+ createFilterScope,
469
+ fromVegaInteraction,
470
+ noteBadges,
471
+ notesFor,
472
+ useBicControls,
473
+ useBicFilter,
474
+ useBicFilters,
189
475
  useBicSelection,
190
- useMeasuredSize
476
+ useBicUrlState,
477
+ useBicView,
478
+ useMeasuredSize,
479
+ viewFromToken,
480
+ viewToken
191
481
  };
package/dist/testing.mjs CHANGED
@@ -1,6 +1,45 @@
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
+ import {
3
+ HOST_CONTAINER_CLASS,
4
+ MARK_CLASS,
5
+ MARK_SELECTED_CLASS,
6
+ ROW_IDX_ATTR
7
+ } from "./chunk-WNGJUPEI.mjs";
2
8
  import "./chunk-GHODWOAL.mjs";
3
9
 
10
+ // src/testing/interact.ts
11
+ function chartContainers(root) {
12
+ return Array.from(root.querySelectorAll(`.${HOST_CONTAINER_CLASS}`));
13
+ }
14
+ function marksOf(chart) {
15
+ return Array.from(chart.querySelectorAll(`.${MARK_CLASS}[${ROW_IDX_ATTR}]`)).filter((m) => (m.getAttribute(ROW_IDX_ATTR) ?? "") !== "");
16
+ }
17
+ function findMark(chart, which) {
18
+ const marks = marksOf(chart);
19
+ if (typeof which === "function") return marks.find(which) ?? null;
20
+ return marks.find((m) => (m.getAttribute(ROW_IDX_ATTR) ?? "").split(",").map(Number).includes(which)) ?? null;
21
+ }
22
+ function clickMark(mark, opts = {}) {
23
+ const win = mark.ownerDocument?.defaultView;
24
+ const Ev = win?.MouseEvent ?? MouseEvent;
25
+ mark.dispatchEvent(new Ev("click", { bubbles: true, cancelable: true, ctrlKey: !!opts.add }));
26
+ }
27
+ function clickEmpty(chart) {
28
+ const win = chart.ownerDocument?.defaultView;
29
+ const Ev = win?.MouseEvent ?? MouseEvent;
30
+ chart.dispatchEvent(new Ev("click", { bubbles: true, cancelable: true }));
31
+ }
32
+ function selectedRows(chart) {
33
+ const out = /* @__PURE__ */ new Set();
34
+ for (const m of Array.from(chart.querySelectorAll(`.${MARK_CLASS}.${MARK_SELECTED_CLASS}`))) {
35
+ for (const p of (m.getAttribute(ROW_IDX_ATTR) ?? "").split(",")) {
36
+ const n = parseInt(p, 10);
37
+ if (Number.isFinite(n)) out.add(n);
38
+ }
39
+ }
40
+ return [...out].sort((a, b) => a - b);
41
+ }
42
+
4
43
  // src/testing/index.ts
5
44
  var ConformanceError = class extends Error {
6
45
  contract;
@@ -63,5 +102,11 @@ async function assertMarkerStoreConformance(store) {
63
102
  }
64
103
  export {
65
104
  ConformanceError,
66
- assertMarkerStoreConformance
105
+ assertMarkerStoreConformance,
106
+ chartContainers,
107
+ clickEmpty,
108
+ clickMark,
109
+ findMark,
110
+ marksOf,
111
+ selectedRows
67
112
  };
@@ -0,0 +1,50 @@
1
+ import type { ChartHost } from "./host";
2
+ export interface ControlInfo {
3
+ /** The knob's name in the chart's bag ("rate", "horizon"). */
4
+ key: string;
5
+ /** Its value as the chart stores it (a number, or an ISO date string for a date knob). */
6
+ value: unknown;
7
+ /** The label the chart draws beside it ("growth per year"), or "" if none. */
8
+ label: string;
9
+ /** The readout the chart draws ("+5.5%", "7 years") - the value in the chart's own words. */
10
+ text: string;
11
+ }
12
+ export interface Controls {
13
+ /** Every knob's current value, by key - the opening values as soon as the chart has drawn. */
14
+ readonly values: Readonly<Record<string, unknown>>;
15
+ /** Each knob in the order the chart draws them, with its label and readout. */
16
+ readonly info: readonly ControlInfo[];
17
+ /** "growth per year +5.5% · horizon 7 years": what to show beside a saved scenario. */
18
+ readonly summary: string;
19
+ /** True once a chart has drawn and reported its controls. */
20
+ readonly ready: boolean;
21
+ /** Bumped on every change. */
22
+ readonly version: number;
23
+ /** Apply values (a saved scenario): the chart redraws with them. Unknown keys are ignored by the chart. */
24
+ set(values: Readonly<Record<string, unknown>>): void;
25
+ /** Back to the chart's own defaults. */
26
+ reset(): void;
27
+ onChange(cb: (c: Controls) => void): () => void;
28
+ }
29
+ /** What attachControls hands the host: the uiState pair the chart reads and writes. */
30
+ export interface ControlsAttachment {
31
+ /** Merge into the host's options at creation (and they stay: setOptions merges). */
32
+ readonly options: {
33
+ uiState: Record<string, unknown>;
34
+ setUiState: (s: unknown) => void;
35
+ };
36
+ /** Read the chart's controls now (call once a render has settled). */
37
+ read(): void;
38
+ detach(): void;
39
+ }
40
+ /** Make a controls handle: give it to one chart (a BicChart's `controls`, or attachControls). */
41
+ export declare function createControls(initial?: Readonly<Record<string, unknown>>): Controls;
42
+ /** Read each knob off a rendered chart: values from the bag, label and readout from what it drew. */
43
+ export declare function readControls(container: HTMLElement): ControlInfo[];
44
+ /**
45
+ * Wire a controls handle to one chart. Call it before creating the host and pass
46
+ * `attachment.options` into createChartHost's options (a raw uiState / setUiState pair takes
47
+ * precedence over any viewState provider); `getHost` returns the host once it exists. Call
48
+ * `read()` after each render settles.
49
+ */
50
+ export declare function attachControls(getHost: () => ChartHost | null, container: HTMLElement, controls: Controls): ControlsAttachment;