@bicharts/chart-host 0.6.108 → 0.6.110

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.
Files changed (37) hide show
  1. package/dist/adapters.mjs +3 -3
  2. package/dist/card.mjs +2 -2
  3. package/dist/{chunk-ABGDJ3NJ.mjs → chunk-3JCWTEEH.mjs} +11 -1
  4. package/dist/{chunk-MO2GR56D.mjs → chunk-5VQ3IQF7.mjs} +426 -20
  5. package/dist/{chunk-OSREEGBN.mjs → chunk-7XT5OJ6M.mjs} +1 -1
  6. package/dist/{chunk-UKXGOQMS.mjs → chunk-AEO5SFEQ.mjs} +1 -1
  7. package/dist/{chunk-JU2PQ22J.mjs → chunk-FT75ONSB.mjs} +17 -1
  8. package/dist/{chunk-ESBH2L6E.mjs → chunk-IL5M7K6Z.mjs} +2 -2
  9. package/dist/{chunk-LGWTV5PB.mjs → chunk-KGVNZR23.mjs} +1 -1
  10. package/dist/{chunk-PHUUFCJY.mjs → chunk-KY4FI2HA.mjs} +3 -1
  11. package/dist/{chunk-SYUJ4TPX.mjs → chunk-NJWR3DFK.mjs} +133 -132
  12. package/dist/{chunk-FCLPMC6Q.mjs → chunk-PELRAFYZ.mjs} +1 -1
  13. package/dist/{chunk-MU4BWWO6.mjs → chunk-Q43WOKSV.mjs} +1 -1
  14. package/dist/{chunk-5WTQN5DL.mjs → chunk-S564YXNE.mjs} +1 -1
  15. package/dist/geo-world.mjs +1 -1
  16. package/dist/geo.mjs +1 -1
  17. package/dist/{geoWorld110m.generated-TD2TA7S7.mjs → geoWorld110m-3PRV3V4G.mjs} +1 -1
  18. package/dist/index.mjs +32 -12
  19. package/dist/legend-fit.mjs +3 -3
  20. package/dist/page.mjs +6 -4
  21. package/dist/provinces.mjs +2 -2
  22. package/dist/react.mjs +38 -12
  23. package/dist/testing.mjs +1 -1
  24. package/dist/types/annotations.d.ts +3 -1
  25. package/dist/types/contract.d.ts +19 -1
  26. package/dist/types/defaults.d.ts +2 -1
  27. package/dist/types/filterScope.d.ts +8 -0
  28. package/dist/types/focusKeep.d.ts +12 -0
  29. package/dist/types/geo-world.d.ts +1 -1
  30. package/dist/types/geoWorld110m.d.ts +1 -0
  31. package/dist/types/host.d.ts +10 -0
  32. package/dist/types/index.d.ts +4 -2
  33. package/dist/types/keyboardMarks.d.ts +44 -0
  34. package/dist/types/page.d.ts +1 -1
  35. package/dist/types/react.d.ts +10 -1
  36. package/dist/zoom-memory.mjs +1 -1
  37. package/package.json +1 -1
@@ -4,11 +4,11 @@ import {
4
4
  claimParkedLasso,
5
5
  installLasso,
6
6
  lassoEffect
7
- } from "./chunk-ESBH2L6E.mjs";
7
+ } from "./chunk-IL5M7K6Z.mjs";
8
8
  import {
9
9
  censusHitBands,
10
10
  ensureCrossfilterHitTargets
11
- } from "./chunk-UKXGOQMS.mjs";
11
+ } from "./chunk-AEO5SFEQ.mjs";
12
12
  import {
13
13
  clearPress,
14
14
  clearSelectionGlyphs,
@@ -25,25 +25,25 @@ import {
25
25
  selectionRuleOf,
26
26
  trackPress,
27
27
  validRowSet
28
- } from "./chunk-LGWTV5PB.mjs";
28
+ } from "./chunk-KGVNZR23.mjs";
29
29
  import {
30
30
  clipWindowOf,
31
31
  fitRenderedChart,
32
32
  rememberScroll,
33
33
  takeScroll,
34
34
  unpinScrolledAxis
35
- } from "./chunk-OSREEGBN.mjs";
35
+ } from "./chunk-7XT5OJ6M.mjs";
36
36
  import {
37
37
  geoFromCache
38
- } from "./chunk-5WTQN5DL.mjs";
38
+ } from "./chunk-S564YXNE.mjs";
39
39
  import {
40
- Dt,
41
40
  Fn,
42
41
  Q,
43
42
  Rr,
44
43
  Yn,
45
- tt
46
- } from "./chunk-SYUJ4TPX.mjs";
44
+ tt,
45
+ xt
46
+ } from "./chunk-NJWR3DFK.mjs";
47
47
  import {
48
48
  ACTIVE_TICK_CLASS,
49
49
  ANIM_LOOP_DELAY_DEFAULT,
@@ -75,6 +75,7 @@ import {
75
75
  HOST_CONTRACT_VERSION,
76
76
  LEGEND_MARK_CLASS,
77
77
  LIFT_SELECTED_CLASS,
78
+ MARKS_INERT_CLASS,
78
79
  MARK_CLASS,
79
80
  MARK_SELECTED_CLASS,
80
81
  MAX_MAP_POINTS_DEFAULT,
@@ -87,7 +88,7 @@ import {
87
88
  XFILTER_REFRESH_EVENT,
88
89
  chartOwnsTimeline,
89
90
  periodTickSuppressesFeedback
90
- } from "./chunk-PHUUFCJY.mjs";
91
+ } from "./chunk-KY4FI2HA.mjs";
91
92
 
92
93
  // src/labelContrast.ts
93
94
  function parseRGBA(c) {
@@ -1070,6 +1071,21 @@ function cleanHostSelection(v) {
1070
1071
  }
1071
1072
  return out;
1072
1073
  }
1074
+ function cleanFilterScope(v) {
1075
+ if (!v || typeof v !== "object" || !Array.isArray(v.values)) return void 0;
1076
+ const values = [];
1077
+ for (const x of v.values) {
1078
+ if (typeof x !== "string" && typeof x !== "number" && typeof x !== "boolean" && typeof x !== "bigint") continue;
1079
+ const s = String(x).trim();
1080
+ if (s && !values.includes(s)) values.push(s);
1081
+ }
1082
+ if (!values.length) return void 0;
1083
+ const field = typeof v.field === "string" ? v.field.trim() : "";
1084
+ const out = { field, values };
1085
+ const label = typeof v.label === "string" ? v.label.trim() : "";
1086
+ if (label) out.label = label;
1087
+ return out;
1088
+ }
1073
1089
  var clamp = (n, lo, hi) => Math.max(lo, Math.min(hi, n));
1074
1090
  var numberOr = (raw, dflt) => {
1075
1091
  const n = raw == null || raw === "" ? NaN : Number(raw);
@@ -1104,6 +1120,8 @@ function resolveOptions(p) {
1104
1120
  rowsFilteredOut: p.rowsFilteredOut,
1105
1121
  // A host FACT about its selection (contract 1.21.0), cleaned, never invented: absent stays absent.
1106
1122
  hostSelection: cleanHostSelection(p.hostSelection),
1123
+ // A host FACT about a filter applied before the rows arrived (contract 1.23.0), cleaned, never invented.
1124
+ filterScope: cleanFilterScope(p.filterScope),
1107
1125
  // Host services (contract 1.18.0): a FUNCTION or nothing, so a junk value never reaches a chart as something
1108
1126
  // it would try to call. Passed through untouched otherwise: the host owns what opening or copying means.
1109
1127
  openUrl: typeof p.openUrl === "function" ? p.openUrl : void 0,
@@ -1159,6 +1177,8 @@ function resolveOptions(p) {
1159
1177
  })(),
1160
1178
  // A host FACT from the generate answer, true only for a literal true: silence never suppresses anything.
1161
1179
  suppressSelectionGlyph: p.suppressSelectionGlyph === true ? true : void 0,
1180
+ // A host FACT (1.22.0): the chart works from the keyboard. True only for a literal true, like the one above.
1181
+ keyboardMarks: p.keyboardMarks === true ? true : void 0,
1162
1182
  onSelect: p.onSelect,
1163
1183
  // Control gestures (1.10.0). Passed straight through like onSelect: the host owns what a gesture MEANS,
1164
1184
  // and a host that supplies nothing leaves every control behaving exactly as before.
@@ -1309,7 +1329,7 @@ function resolvePointChannel(cols, rowObjs, point) {
1309
1329
  const hasPointBind = !!bind && !!(bind.city || bind.state || bind.zip || bind.country || bind.lat && bind.lon);
1310
1330
  if (hasPointBind) {
1311
1331
  const p = bind;
1312
- const built = Dt(rowObjs.map((r) => ({
1332
+ const built = xt(rowObjs.map((r) => ({
1313
1333
  lat: p.lat ? r[p.lat] : null,
1314
1334
  lon: p.lon ? r[p.lon] : null,
1315
1335
  city: p.city ? r[p.city] : null,
@@ -2322,7 +2342,7 @@ var DOT = 8;
2322
2342
  var RING = 1.5;
2323
2343
  var DOT_HALF = DOT / 2 + RING;
2324
2344
  var DOT_IN = 8;
2325
- function createAnnotationLayer(container, onClick) {
2345
+ function createAnnotationLayer(container, onClick, opts) {
2326
2346
  let list = [];
2327
2347
  let report = { shown: 0, notShown: 0 };
2328
2348
  let last = null;
@@ -2446,7 +2466,7 @@ function createAnnotationLayer(container, onClick) {
2446
2466
  const after = own.filter((t) => t.b.l >= run.r && t.b.b > run.t && t.b.t < run.b).map((t) => t.b.l);
2447
2467
  return { r: Math.min(run.r, seen), y: (run.t + run.b) / 2, next: after.length ? Math.min(...after) : v.r };
2448
2468
  };
2449
- const clear = (v, len, mark) => {
2469
+ const clear = (v, len, mark, whole = v) => {
2450
2470
  const hw = Math.max(16, len * 6 + 8) / 2, hh = 8, g = 2;
2451
2471
  const cx = (v.l + v.r) / 2, cy = (v.t + v.b) / 2;
2452
2472
  const at = (x, y) => ({ l: x - hw, t: y - hh, r: x + hw, b: y + hh });
@@ -2459,6 +2479,7 @@ function createAnnotationLayer(container, onClick) {
2459
2479
  const tries = [];
2460
2480
  const add = (x, y, px = x, py = y) => tries.push({ x: clampX(x, hw), y: clampY(y, hh), px, py });
2461
2481
  const label = rowLabel(v, mark);
2482
+ if (!label && whole !== v && rowLabel(whole, mark)) return null;
2462
2483
  if (label) {
2463
2484
  const x = label.r + g + hw;
2464
2485
  if (x + hw <= label.next - g || label.next >= v.r) add(x, label.y, Math.min(x, v.r - 1), label.y);
@@ -2523,7 +2544,7 @@ function createAnnotationLayer(container, onClick) {
2523
2544
  }
2524
2545
  }
2525
2546
  const dot = wantsDot(bestMark ?? hits2.values().next().value ?? null);
2526
- const pt = !best ? null : dot ? dotSpot(best, bestWhole) : clear(best, (a.label ?? "\u2022").length, bestMark);
2547
+ const pt = !best ? null : dot ? dotSpot(best, bestWhole) : clear(best, (a.label ?? "\u2022").length, bestMark, bestWhole ?? best);
2527
2548
  if (laidOut && !pt) continue;
2528
2549
  shown++;
2529
2550
  const badge = doc.createElement("div");
@@ -2564,6 +2585,15 @@ function createAnnotationLayer(container, onClick) {
2564
2585
  e.stopPropagation();
2565
2586
  onClick?.(a);
2566
2587
  });
2588
+ if (opts?.keyboard?.()) {
2589
+ badge.setAttribute("tabindex", "0");
2590
+ badge.addEventListener("keydown", (e) => {
2591
+ if (e.key !== "Enter" && e.key !== " " && e.key !== "Spacebar") return;
2592
+ e.preventDefault();
2593
+ e.stopPropagation();
2594
+ onClick?.(a);
2595
+ });
2596
+ }
2567
2597
  layer.appendChild(badge);
2568
2598
  }
2569
2599
  report = { shown, notShown: list.length - shown };
@@ -2590,6 +2620,235 @@ function createAnnotationLayer(container, onClick) {
2590
2620
  };
2591
2621
  }
2592
2622
 
2623
+ // src/focusKeep.ts
2624
+ function signatureOf(el) {
2625
+ const a = (n) => el.getAttribute(n) ?? "";
2626
+ const cls = Array.from(el.classList ?? []).filter((c) => !/^lch-|^bic-/.test(c)).sort().join(" ");
2627
+ return [el.localName, a("type"), cls, a("name"), a("placeholder"), a("aria-label"), a("role")].join("|");
2628
+ }
2629
+ function alike(container, sig) {
2630
+ const tag = sig.split("|", 1)[0];
2631
+ if (!tag) return [];
2632
+ return Array.from(container.querySelectorAll(tag)).filter((e) => signatureOf(e) === sig);
2633
+ }
2634
+ function rememberFocus(container) {
2635
+ try {
2636
+ const active = container.ownerDocument?.activeElement;
2637
+ if (!active || active === container || !container.contains(active)) return null;
2638
+ const sig = signatureOf(active);
2639
+ const nth = alike(container, sig).indexOf(active);
2640
+ const t = active;
2641
+ let start = null, end = null, dir = null;
2642
+ try {
2643
+ if (typeof t.selectionStart === "number") {
2644
+ start = t.selectionStart;
2645
+ end = t.selectionEnd;
2646
+ dir = t.selectionDirection ?? null;
2647
+ }
2648
+ } catch {
2649
+ }
2650
+ return { sig, nth: Math.max(0, nth), start, end, dir };
2651
+ } catch {
2652
+ return null;
2653
+ }
2654
+ }
2655
+ function restoreFocus(container, memory) {
2656
+ if (!memory) return false;
2657
+ try {
2658
+ const active = container.ownerDocument?.activeElement;
2659
+ if (active && active !== container && container.contains(active)) return false;
2660
+ const list = alike(container, memory.sig);
2661
+ const el = list[memory.nth] ?? (list.length === 1 ? list[0] : null);
2662
+ if (!el || typeof el.focus !== "function") return false;
2663
+ el.focus({ preventScroll: true });
2664
+ const t = el;
2665
+ if (memory.start != null && typeof t.setSelectionRange === "function") {
2666
+ const len = typeof t.value === "string" ? t.value.length : 0;
2667
+ try {
2668
+ t.setSelectionRange(Math.min(memory.start, len), Math.min(memory.end ?? memory.start, len), memory.dir ?? void 0);
2669
+ } catch {
2670
+ }
2671
+ }
2672
+ return container.ownerDocument?.activeElement === el;
2673
+ } catch {
2674
+ return false;
2675
+ }
2676
+ }
2677
+
2678
+ // src/keyboardMarks.ts
2679
+ var KEYBOARD_STAMP = "// bic:keyboard-marks";
2680
+ var STAMP_RE = /^[ \t]*\/\/[ \t]*bic:keyboard-marks\b/;
2681
+ var STAMP_HEAD_LINES = 12;
2682
+ function codeHasKeyboardStamp(code) {
2683
+ if (typeof code !== "string" || !code) return false;
2684
+ let start = 0;
2685
+ for (let n = 0; n < STAMP_HEAD_LINES && start <= code.length; n++) {
2686
+ const end = code.indexOf("\n", start);
2687
+ const line = code.slice(start, end < 0 ? code.length : end);
2688
+ if (STAMP_RE.test(line)) return true;
2689
+ if (end < 0) break;
2690
+ start = end + 1;
2691
+ }
2692
+ return false;
2693
+ }
2694
+ var KEYBOARD_CLASS = "lch-keyboard";
2695
+ var WIRED_ATTR = "data-lch-kb";
2696
+ var isDate = (v) => Object.prototype.toString.call(v) === "[object Date]";
2697
+ function rowNameOf(columns, row, culture) {
2698
+ if (!row) return null;
2699
+ const parts = [];
2700
+ (columns || []).forEach((c, i) => {
2701
+ const name = typeof c === "string" ? c : c?.name;
2702
+ const v = row[i];
2703
+ if (parts.length >= 4 || v == null || v === "" || typeof name === "string" && name.startsWith("__")) return;
2704
+ if (isDate(v)) {
2705
+ if (!Number.isNaN(v.getTime())) parts.push(v.toLocaleDateString(culture || void 0));
2706
+ return;
2707
+ }
2708
+ parts.push(typeof v === "number" ? v.toLocaleString(culture || void 0, { maximumFractionDigits: 2 }) : String(v));
2709
+ });
2710
+ return parts.length ? parts.join(", ") : null;
2711
+ }
2712
+ var isClearChord = (e) => !!e.ctrlKey && !!e.shiftKey && (e.key === "C" || e.key === "c");
2713
+ var NATIVE = /* @__PURE__ */ new Set(["input", "select", "textarea", "button", "a"]);
2714
+ var isNative = (el) => NATIVE.has((el.localName || "").toLowerCase()) || el.isContentEditable === true;
2715
+ var OWN_FOCUS = "input, select, textarea, button, a[href], [role=slider]";
2716
+ function isLeafControl(el, pressSelector) {
2717
+ try {
2718
+ return !el.querySelector(pressSelector) && !el.querySelector(OWN_FOCUS);
2719
+ } catch {
2720
+ return true;
2721
+ }
2722
+ }
2723
+ function installKeyboard(env) {
2724
+ const { container } = env;
2725
+ let rovingKey = null;
2726
+ let destroyed = false;
2727
+ const marksNow = () => {
2728
+ if (env.inert()) return [];
2729
+ return Array.from(container.querySelectorAll(env.markSelector)).filter((m) => !m.closest(env.skipSelector) && (m.getAttribute("data-row-idx") ?? "") !== "");
2730
+ };
2731
+ const wired = (el) => el.hasAttribute(WIRED_ATTR);
2732
+ const wire = (el, tabindex, role) => {
2733
+ if (el.hasAttribute("tabindex") && !wired(el)) return false;
2734
+ el.setAttribute(WIRED_ATTR, "");
2735
+ el.setAttribute("tabindex", tabindex);
2736
+ if (!el.hasAttribute("role")) el.setAttribute("role", role);
2737
+ return true;
2738
+ };
2739
+ const setRoving = (list, to) => {
2740
+ for (const m of list) if (wired(m)) m.setAttribute("tabindex", m === to ? "0" : "-1");
2741
+ rovingKey = env.keyOf(to);
2742
+ };
2743
+ const refresh = () => {
2744
+ if (destroyed) return;
2745
+ try {
2746
+ container.classList?.add(KEYBOARD_CLASS);
2747
+ const list = marksNow();
2748
+ for (const m of list) {
2749
+ if (!wire(m, "-1", "button")) continue;
2750
+ m.setAttribute("aria-pressed", env.isSelected(m) ? "true" : "false");
2751
+ if (!m.hasAttribute("aria-label") && !m.querySelector(":scope > title")) {
2752
+ const name = env.labelOf(m);
2753
+ if (name) m.setAttribute("aria-label", name);
2754
+ }
2755
+ }
2756
+ if (!list.length) {
2757
+ for (const m of Array.from(container.querySelectorAll(`[${WIRED_ATTR}]`)))
2758
+ if (m.matches(env.markSelector)) {
2759
+ m.removeAttribute("tabindex");
2760
+ m.removeAttribute(WIRED_ATTR);
2761
+ }
2762
+ }
2763
+ const own = list.filter(wired);
2764
+ if (own.length) {
2765
+ const keep = own.find((m) => env.keyOf(m) === rovingKey) ?? own.find((m) => env.isSelected(m)) ?? own[0];
2766
+ setRoving(own, keep);
2767
+ }
2768
+ for (const c of Array.from(container.querySelectorAll(env.pressSelector))) {
2769
+ if (isNative(c) || !isLeafControl(c, env.pressSelector)) continue;
2770
+ wire(c, "0", "button");
2771
+ }
2772
+ } catch {
2773
+ }
2774
+ };
2775
+ const onKey = (e) => {
2776
+ if (destroyed || e.defaultPrevented) return;
2777
+ const t = e.target;
2778
+ if (!t || !container.contains(t)) return;
2779
+ const k = e.key;
2780
+ if (isClearChord(e)) {
2781
+ e.preventDefault();
2782
+ env.clear();
2783
+ return;
2784
+ }
2785
+ const press = t.closest?.(env.pressSelector);
2786
+ if (press && wired(press) && container.contains(press)) {
2787
+ if (k === "Enter" || k === " " || k === "Spacebar") {
2788
+ e.preventDefault();
2789
+ const ME = container.ownerDocument.defaultView?.MouseEvent ?? MouseEvent;
2790
+ press.dispatchEvent(new ME("click", { bubbles: true, cancelable: true }));
2791
+ } else if (k === "Escape" && env.escapeClears !== false) env.clear();
2792
+ return;
2793
+ }
2794
+ const mark = t.closest?.(env.markSelector);
2795
+ if (!mark || !wired(mark) || !container.contains(mark)) return;
2796
+ const list = marksNow().filter(wired);
2797
+ const at = list.indexOf(mark);
2798
+ const go = (i) => {
2799
+ const to = list[Math.max(0, Math.min(list.length - 1, i))];
2800
+ if (!to) return;
2801
+ setRoving(list, to);
2802
+ try {
2803
+ to.focus({ preventScroll: false });
2804
+ } catch {
2805
+ }
2806
+ };
2807
+ switch (k) {
2808
+ case "ArrowRight":
2809
+ case "ArrowDown":
2810
+ e.preventDefault();
2811
+ go(at + 1);
2812
+ break;
2813
+ case "ArrowLeft":
2814
+ case "ArrowUp":
2815
+ e.preventDefault();
2816
+ go(at - 1);
2817
+ break;
2818
+ case "Home":
2819
+ e.preventDefault();
2820
+ go(0);
2821
+ break;
2822
+ case "End":
2823
+ e.preventDefault();
2824
+ go(list.length - 1);
2825
+ break;
2826
+ case "Enter":
2827
+ case " ":
2828
+ case "Spacebar":
2829
+ e.preventDefault();
2830
+ rovingKey = env.keyOf(mark);
2831
+ env.select(mark, { ctrl: !!(e.ctrlKey || e.metaKey), shift: !!e.shiftKey });
2832
+ break;
2833
+ case "Escape":
2834
+ if (env.escapeClears !== false) env.clear();
2835
+ break;
2836
+ }
2837
+ };
2838
+ container.addEventListener("keydown", onKey);
2839
+ return {
2840
+ refresh,
2841
+ destroy() {
2842
+ destroyed = true;
2843
+ container.removeEventListener("keydown", onKey);
2844
+ try {
2845
+ container.classList?.remove(KEYBOARD_CLASS);
2846
+ } catch {
2847
+ }
2848
+ }
2849
+ };
2850
+ }
2851
+
2593
2852
  // src/host.ts
2594
2853
  function sessionViewStateProvider(container) {
2595
2854
  const holder = container;
@@ -2738,6 +2997,9 @@ function createChartHost(container, config) {
2738
2997
  }
2739
2998
  }
2740
2999
  let lassoClaimed = false;
3000
+ let ownBag = null;
3001
+ const seedKey = (s) => s && typeof s === "object" ? JSON.stringify(s) : void 0;
3002
+ let lastSeed = seedKey(raw.uiState);
2741
3003
  if (typeof raw.setUiState !== "function") {
2742
3004
  const provider = config.viewState ?? sessionViewStateProvider(container);
2743
3005
  let bag;
@@ -2751,6 +3013,7 @@ function createChartHost(container, config) {
2751
3013
  if (raw.uiState && typeof raw.uiState === "object" && Object.keys(bag).length === 0)
2752
3014
  Object.assign(bag, raw.uiState);
2753
3015
  raw.uiState = bag;
3016
+ ownBag = { bag, provider };
2754
3017
  raw.setUiState = (s) => {
2755
3018
  if (!s || typeof s !== "object") return;
2756
3019
  try {
@@ -2759,6 +3022,26 @@ function createChartHost(container, config) {
2759
3022
  }
2760
3023
  };
2761
3024
  }
3025
+ const takeSeed = (partial) => {
3026
+ if (typeof partial.setUiState === "function") {
3027
+ ownBag = null;
3028
+ return partial;
3029
+ }
3030
+ if (!ownBag || !("uiState" in partial)) return partial;
3031
+ const { uiState: seed, ...rest } = partial;
3032
+ const key = seedKey(seed);
3033
+ if (key !== void 0 && key !== lastSeed) {
3034
+ lastSeed = key;
3035
+ const { bag, provider } = ownBag;
3036
+ for (const k of Object.keys(bag)) delete bag[k];
3037
+ Object.assign(bag, seed);
3038
+ try {
3039
+ provider.save({ ...bag });
3040
+ } catch {
3041
+ }
3042
+ }
3043
+ return rest;
3044
+ };
2762
3045
  const resolveForHost = (input) => {
2763
3046
  const out = resolveOptions(input);
2764
3047
  return out.allowTooltips == null ? { ...out, allowTooltips: true } : out;
@@ -2773,7 +3056,9 @@ function createChartHost(container, config) {
2773
3056
  let warnedLassoNoFilter = false;
2774
3057
  const idScope = newIdScope();
2775
3058
  let renderSeq = 0;
2776
- const annotationLayer = createAnnotationLayer(container, (a) => config.onAnnotationClick?.(a));
3059
+ const keyboardOn = codeHasKeyboardStamp(config.code);
3060
+ let keyboard = null;
3061
+ const annotationLayer = createAnnotationLayer(container, (a) => config.onAnnotationClick?.(a), { keyboard: () => keyboardOn });
2777
3062
  const drawAnnotations = () => annotationLayer.draw(
2778
3063
  { columns: data.columns, rows: data.rows },
2779
3064
  {
@@ -2809,6 +3094,8 @@ function createChartHost(container, config) {
2809
3094
  let selectionIsPeriod = false;
2810
3095
  let selectionFromChart = false;
2811
3096
  let selectionPublished = true;
3097
+ let publishedRows = [];
3098
+ let publishedFromChart = false;
2812
3099
  const clickedMarks = () => container[CONTAINER_SLOT_CLICKED_MARKS] ?? null;
2813
3100
  const setClickedMarks = (c) => {
2814
3101
  try {
@@ -2857,7 +3144,18 @@ function createChartHost(container, config) {
2857
3144
  anomaly badge, a highlight stroke) and must keep dimming with its datum. */
2858
3145
  .${HOST_CONTAINER_CLASS} .${MARK_CLASS} { cursor: pointer; transition: opacity 120ms ease-in-out, filter 100ms ease-in-out; }
2859
3146
  .${HOST_CONTAINER_CLASS} .${MARK_CLASS}:not([${ROW_IDX_ATTR}=""]):hover { filter: brightness(1.1) drop-shadow(0 0 1.5px rgba(0,0,0,0.5)) !important; }
2860
- .${HOST_CONTAINER_CLASS}:has(.${MARK_CLASS}:not([${ROW_IDX_ATTR}=""]):hover) .${MARK_CLASS}:not(:hover):not([${ROW_IDX_ATTR}=""]) { opacity: 0.55; }
3147
+ .${HOST_CONTAINER_CLASS}:not(.${MARKS_INERT_CLASS}):has(.${MARK_CLASS}:not([${ROW_IDX_ATTR}=""]):hover) .${MARK_CLASS}:not(:hover):not([${ROW_IDX_ATTR}=""]) { opacity: 0.55; }
3148
+ /* A MARK THAT SELECTS NOTHING DOESN'T LOOK CLICKABLE: no pointer, over the inline cursor generated code
3149
+ sets on its marks, and no dimming of the others on hover (the rule above). A control the chart draws keeps its own. */
3150
+ .${HOST_CONTAINER_CLASS}.${MARKS_INERT_CLASS} .${MARK_CLASS} { cursor: default !important; }
3151
+ /* THE KEYBOARD'S FOCUS: a ring on the mark, control or badge that holds keyboard focus, in the colour the
3152
+ chart's text is drawn in, so it reads in either theme. A pointer's focus shows none. */
3153
+ .${HOST_CONTAINER_CLASS}.${KEYBOARD_CLASS} [${WIRED_ATTR}]:focus:not(:focus-visible) { outline: none; }
3154
+ .${HOST_CONTAINER_CLASS}.${KEYBOARD_CLASS} [${WIRED_ATTR}]:focus-visible,
3155
+ .${HOST_CONTAINER_CLASS} .bic-annotation:focus-visible { outline: 2px solid var(--lch-focus-ring, currentColor) !important; outline-offset: 2px; }
3156
+ /* A dimmed mark that takes keyboard focus is drawn at full strength while it has it, ring included: the dim would
3157
+ otherwise fade the ring with the mark. */
3158
+ .${HOST_CONTAINER_CLASS}.${KEYBOARD_CLASS}.${SELECTION_ACTIVE_CLASS} .${MARK_CLASS}[${WIRED_ATTR}]:focus-visible:not(.${MARK_SELECTED_CLASS}) { opacity: 1 !important; }
2861
3159
  .${HOST_CONTAINER_CLASS}.${SELECTION_ACTIVE_CLASS} .${MARK_CLASS}:not(.${MARK_SELECTED_CLASS}):not([${ROW_IDX_ATTR}=""]) {
2862
3160
  opacity: var(${DIM_OPACITY_VAR}, ${DIM_OPACITY_DEFAULT}) !important;
2863
3161
  }
@@ -2890,9 +3188,18 @@ function createChartHost(container, config) {
2890
3188
  } catch {
2891
3189
  }
2892
3190
  };
3191
+ const marksInert = () => {
3192
+ if (typeof config.marksSelect !== "function") return false;
3193
+ try {
3194
+ return config.marksSelect() === false;
3195
+ } catch {
3196
+ return false;
3197
+ }
3198
+ };
2893
3199
  const paintSelection = () => {
2894
3200
  if (destroyed) return;
2895
3201
  try {
3202
+ container.classList?.[marksInert() ? "add" : "remove"](MARKS_INERT_CLASS);
2896
3203
  const sel = current;
2897
3204
  const active = !!sel && sel.length > 0;
2898
3205
  const dim = active && !selectionIsPeriod;
@@ -3155,12 +3462,17 @@ function createChartHost(container, config) {
3155
3462
  current = rowIdxs;
3156
3463
  selectionFromChart = rowIdxs.length > 0 && fromChart;
3157
3464
  selectionPublished = publish;
3465
+ if (publish) {
3466
+ publishedRows = rowIdxs;
3467
+ publishedFromChart = selectionFromChart;
3468
+ }
3158
3469
  activeTickEl = rowIdxs.length ? tick ?? null : null;
3159
3470
  selectionIsPeriod = rowIdxs.length > 0 && periodTickSuppressesFeedback(!!tick, chartOwnsTimeline(container));
3160
3471
  if (!rowIdxs.length && !meta?.keepHidden) hiddenHeld = 0;
3161
3472
  factFresh = true;
3162
3473
  paintSelection();
3163
3474
  paintGlyphLayer();
3475
+ keyboard?.refresh();
3164
3476
  if (publish) for (const cb of subs) {
3165
3477
  try {
3166
3478
  cb(rowIdxs, source, meta);
@@ -3169,6 +3481,10 @@ function createChartHost(container, config) {
3169
3481
  }
3170
3482
  tellSelection();
3171
3483
  };
3484
+ const restorePublished = (source) => {
3485
+ notify(publishedRows, source, void 0, publishedFromChart, false);
3486
+ selectionPublished = true;
3487
+ };
3172
3488
  const selectFromLasso = (rows) => {
3173
3489
  if (lassoEffect(resolved.lasso) === "highlight") {
3174
3490
  notify(rows, "user", null, true, false);
@@ -3195,7 +3511,10 @@ function createChartHost(container, config) {
3195
3511
  const onXf = (e) => {
3196
3512
  const d = e?.detail || {};
3197
3513
  xfAt = Date.now();
3198
- if (d.clear) notify([], d.source || "chart", void 0, false, selectionPublished || !(current && current.length));
3514
+ if (d.clear) {
3515
+ if (!selectionPublished && current && current.length) restorePublished(d.source || "chart");
3516
+ else notify([], d.source || "chart", void 0, false, true);
3517
+ } else if (marksInert()) return;
3199
3518
  else if (d.mark) notify(parseRowIdxs(d.mark.getAttribute?.(ROW_IDX_ATTR)), d.source || "chart", d.mark, true);
3200
3519
  else if (Array.isArray(d.rows)) notify(validRowSet(d.rows), d.source || "chart", null, true, true, d.keepHidden === true ? { keepHidden: true } : void 0);
3201
3520
  };
@@ -3207,6 +3526,7 @@ function createChartHost(container, config) {
3207
3526
  });
3208
3527
  const clearFromGesture = () => {
3209
3528
  if (current && current.length) {
3529
+ const clearingHighlight = !selectionPublished;
3210
3530
  const slot = container[CONTAINER_SLOT_XF_CLEAR];
3211
3531
  if (selectionFromChart && typeof slot === "function") {
3212
3532
  try {
@@ -3214,7 +3534,9 @@ function createChartHost(container, config) {
3214
3534
  } catch {
3215
3535
  }
3216
3536
  }
3217
- if (current && current.length) notify([], "user", void 0, false, selectionPublished);
3537
+ if (clearingHighlight) {
3538
+ if (!selectionPublished) restorePublished("user");
3539
+ } else if (current && current.length) notify([], "user", void 0, false, true);
3218
3540
  }
3219
3541
  };
3220
3542
  let lasso = null;
@@ -3231,6 +3553,9 @@ function createChartHost(container, config) {
3231
3553
  const wasDrag = clickWasDrag(container, e);
3232
3554
  clearPress(container);
3233
3555
  if (wasDrag) return;
3556
+ const inert = marksInert();
3557
+ container.classList?.[inert ? "add" : "remove"](MARKS_INERT_CLASS);
3558
+ if (inert) return;
3234
3559
  if (Date.now() - xfAt < XF_ECHO_MS) return;
3235
3560
  if (isInsideControl(e?.target, container)) return;
3236
3561
  let el = resolver.findMark(e?.target, clickSel, e);
@@ -3244,11 +3569,12 @@ function createChartHost(container, config) {
3244
3569
  if (!rows.length) return;
3245
3570
  const tick = el.classList?.contains?.(AXIS_FILTER_CLASS) ? el : null;
3246
3571
  const multi = !!(e?.ctrlKey || e?.metaKey || e?.shiftKey);
3247
- const next = nextSelection(current, rows, { ctrl: !!(e?.ctrlKey || e?.metaKey), shift: !!e?.shiftKey });
3572
+ const base = selectionPublished ? current : publishedRows;
3573
+ const next = nextSelection(base, rows, { ctrl: !!(e?.ctrlKey || e?.metaKey), shift: !!e?.shiftKey });
3248
3574
  const toggledOff = !multi && next.length === 0;
3249
3575
  setClickedMarks(nextClickedMarks(
3250
3576
  clickedMarks(),
3251
- current,
3577
+ base,
3252
3578
  markKeyOf(el),
3253
3579
  next,
3254
3580
  { ctrl: !!(e?.ctrlKey || e?.metaKey), shift: !!e?.shiftKey }
@@ -3258,6 +3584,27 @@ function createChartHost(container, config) {
3258
3584
  container.addEventListener(XFILTER_REFRESH_EVENT, onXf);
3259
3585
  const untrackPress = trackPress(container);
3260
3586
  container.addEventListener("click", onClick);
3587
+ if (keyboardOn) {
3588
+ const nameOfRow = (m) => {
3589
+ const r = parseRowIdxs(m.getAttribute(ROW_IDX_ATTR))[0];
3590
+ return rowNameOf(data.columns || [], r == null ? null : data.rows?.[r], resolved.cultureCode);
3591
+ };
3592
+ keyboard = installKeyboard({
3593
+ container,
3594
+ markSelector: clickSel,
3595
+ pressSelector: `.${CONTROL_CLASS}`,
3596
+ skipSelector: `.${ANNOTATION_LAYER_CLASS}, .${GLYPH_LAYER_CLASS}, .${LASSO_OUTLINE_CLASS}`,
3597
+ inert: marksInert,
3598
+ select: (m, mods) => {
3599
+ const ME = win?.MouseEvent ?? MouseEvent;
3600
+ m.dispatchEvent(new ME("click", { bubbles: true, cancelable: true, ctrlKey: mods.ctrl, shiftKey: mods.shift }));
3601
+ },
3602
+ clear: clearFromGesture,
3603
+ isSelected: (m) => !!m.classList?.contains(MARK_SELECTED_CLASS),
3604
+ labelOf: nameOfRow,
3605
+ keyOf: (m) => m.getAttribute(ROW_IDX_ATTR) ?? ""
3606
+ });
3607
+ }
3261
3608
  container.classList?.add(HOST_CONTAINER_CLASS);
3262
3609
  ensureAffordanceStyles();
3263
3610
  const fitOpts = config.fit ?? true;
@@ -3405,6 +3752,15 @@ function createChartHost(container, config) {
3405
3752
  } catch {
3406
3753
  }
3407
3754
  paintGlyphLayer();
3755
+ installLassoHere();
3756
+ keyboard?.refresh();
3757
+ const fw = focusWas;
3758
+ focusWas = null;
3759
+ restoreFocus(container, fw);
3760
+ selfRedraw?.takeRecords();
3761
+ watchSelfRedraw();
3762
+ };
3763
+ function installLassoHere() {
3408
3764
  try {
3409
3765
  lasso = installLasso({
3410
3766
  container,
@@ -3415,7 +3771,38 @@ function createChartHost(container, config) {
3415
3771
  });
3416
3772
  } catch {
3417
3773
  }
3774
+ }
3775
+ let selfRedraw = null;
3776
+ let focusWas = null;
3777
+ let notesHeld = (config.annotations ?? []).length > 0;
3778
+ const takesMarks = (n) => n.nodeType === 1 && (!!n.classList?.contains(MARK_CLASS) || !!n.querySelector?.(`.${MARK_CLASS}`));
3779
+ const lassoLost = () => !!lasso?.surface && !lasso.surface.isConnected;
3780
+ const repaintAfterChart = () => {
3781
+ if (destroyed) return;
3782
+ try {
3783
+ ensureCrossfilterHitTargets(container, doc);
3784
+ } catch {
3785
+ }
3786
+ paintSelection();
3787
+ try {
3788
+ drawAnnotations();
3789
+ } catch {
3790
+ }
3791
+ paintGlyphLayer(false);
3792
+ if (lassoLost()) installLassoHere();
3793
+ keyboard?.refresh();
3794
+ selfRedraw?.takeRecords();
3418
3795
  };
3796
+ function watchSelfRedraw() {
3797
+ const MO = win?.MutationObserver;
3798
+ if (selfRedraw || !MO || destroyed) return;
3799
+ selfRedraw = new MO((records) => {
3800
+ if (destroyed || !records.some((r) => Array.from(r.removedNodes).some(takesMarks))) return;
3801
+ if (!(current && current.length) && !notesHeld && !lassoLost()) return;
3802
+ repaintAfterChart();
3803
+ });
3804
+ selfRedraw.observe(container, { childList: true, subtree: true });
3805
+ }
3419
3806
  const renderFailure = (err) => {
3420
3807
  const message = String(err?.message ?? err ?? "");
3421
3808
  if (config.onInvalidSentinel && isInvalidSentinelError(message)) {
@@ -3435,7 +3822,9 @@ function createChartHost(container, config) {
3435
3822
  disposeLasso();
3436
3823
  flushZoom();
3437
3824
  if (hasRendered && keepScroll) rememberScroll(container, scrollKey());
3825
+ focusWas = rememberFocus(container);
3438
3826
  container.innerHTML = "";
3827
+ selfRedraw?.takeRecords();
3439
3828
  hasRendered = true;
3440
3829
  if (!renderFn) {
3441
3830
  if (!config.code) throw new Error("createChartHost: provide code or renderFn");
@@ -3461,6 +3850,7 @@ function createChartHost(container, config) {
3461
3850
  if (resolved.dateCellsAreUtcDays === void 0 && data.dateCellsAreUtcDays) resolved = { ...resolved, dateCellsAreUtcDays: true };
3462
3851
  if (resolved.suppressSelectionGlyph === void 0 && data.meta?.suppressSelectionGlyph === true)
3463
3852
  resolved = { ...resolved, suppressSelectionGlyph: true };
3853
+ if (keyboardOn) resolved = { ...resolved, keyboardMarks: true };
3464
3854
  let outcome;
3465
3855
  zoomCap = null;
3466
3856
  if (zoomMemory && config.code && !config.renderFn) {
@@ -3536,7 +3926,7 @@ function createChartHost(container, config) {
3536
3926
  // callback) must not clear, stop or annotate the chart that is on screen now.
3537
3927
  setOptions(partial) {
3538
3928
  if (destroyed) return;
3539
- raw = { ...raw, ...partial };
3929
+ raw = { ...raw, ...takeSeed(partial ?? {}) };
3540
3930
  resolved = resolveForHost(raw);
3541
3931
  host.render();
3542
3932
  },
@@ -3551,6 +3941,7 @@ function createChartHost(container, config) {
3551
3941
  },
3552
3942
  setAnnotations(list) {
3553
3943
  if (destroyed) return;
3944
+ notesHeld = (list ?? []).length > 0;
3554
3945
  annotationLayer.set(list);
3555
3946
  try {
3556
3947
  drawAnnotations();
@@ -3612,6 +4003,12 @@ function createChartHost(container, config) {
3612
4003
  if (destroyed) return;
3613
4004
  destroyed = true;
3614
4005
  stopAnim();
4006
+ if (selfRedraw) {
4007
+ selfRedraw.disconnect();
4008
+ selfRedraw = null;
4009
+ }
4010
+ keyboard?.destroy();
4011
+ keyboard = null;
3615
4012
  container.removeEventListener(XFILTER_REFRESH_EVENT, onXf);
3616
4013
  container.removeEventListener("click", onClick);
3617
4014
  untrackPress();
@@ -3836,6 +4233,7 @@ export {
3836
4233
  exportTableService,
3837
4234
  browserSaveFile,
3838
4235
  cleanHostSelection,
4236
+ cleanFilterScope,
3839
4237
  resolveOptions,
3840
4238
  buildRenderPayload,
3841
4239
  stripJsComments,
@@ -3858,6 +4256,14 @@ export {
3858
4256
  invalidSentinelReason,
3859
4257
  ANNOTATION_LAYER_CLASS,
3860
4258
  ANNOTATION_CLASS,
4259
+ rememberFocus,
4260
+ restoreFocus,
4261
+ KEYBOARD_STAMP,
4262
+ codeHasKeyboardStamp,
4263
+ KEYBOARD_CLASS,
4264
+ WIRED_ATTR,
4265
+ rowNameOf,
4266
+ installKeyboard,
3861
4267
  sessionViewStateProvider,
3862
4268
  noopViewStateProvider,
3863
4269
  assembleD3,