@bicharts/chart-host 0.6.108 → 0.6.109

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/adapters.mjs CHANGED
@@ -3,7 +3,7 @@ import {
3
3
  censusHitBands,
4
4
  ensureCrossfilterHitTargets,
5
5
  hitBandFlag
6
- } from "./chunk-UKXGOQMS.mjs";
6
+ } from "./chunk-GQLEU3VC.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-LGWTV5PB.mjs";
15
+ } from "./chunk-3ER4C7TG.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-PHUUFCJY.mjs";
21
+ } from "./chunk-USZSHKIB.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-MU4BWWO6.mjs";
5
- import "./chunk-SYUJ4TPX.mjs";
4
+ } from "./chunk-G4CKILIL.mjs";
5
+ import "./chunk-TH577JHI.mjs";
6
6
  import "./chunk-GHODWOAL.mjs";
7
7
 
8
8
  // src/selectionCardChrome.ts
@@ -1,7 +1,7 @@
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
  SNAPSHOT_SVG_ATTR
4
- } from "./chunk-PHUUFCJY.mjs";
4
+ } from "./chunk-USZSHKIB.mjs";
5
5
 
6
6
  // src/snapshot.ts
7
7
  var DEFAULT_TIMEOUT_MS = 2e3;
@@ -8,7 +8,7 @@ import {
8
8
  SELECTION_RULE_ATTR,
9
9
  SELECTION_RULE_CLICKED_MARKS,
10
10
  XFILTER_REFRESH_EVENT
11
- } from "./chunk-PHUUFCJY.mjs";
11
+ } from "./chunk-USZSHKIB.mjs";
12
12
 
13
13
  // src/selection.ts
14
14
  function rowIdxsFromMark(el) {
@@ -4,7 +4,7 @@ import {
4
4
  gestureWasDrag,
5
5
  isInsideControl,
6
6
  rowIdxsFromMark
7
- } from "./chunk-LGWTV5PB.mjs";
7
+ } from "./chunk-3ER4C7TG.mjs";
8
8
  import {
9
9
  AXIS_FILTER_CLASS,
10
10
  CONTROL_CLASS,
@@ -12,7 +12,7 @@ import {
12
12
  MARK_CLASS,
13
13
  ROW_IDX_ATTR,
14
14
  SNAPSHOT_SVG_ATTR
15
- } from "./chunk-PHUUFCJY.mjs";
15
+ } from "./chunk-USZSHKIB.mjs";
16
16
 
17
17
  // src/lasso.ts
18
18
  var LASSO_ROW_CAP = 5e3;
@@ -6,7 +6,7 @@ import {
6
6
  Ti,
7
7
  j,
8
8
  u
9
- } from "./chunk-SYUJ4TPX.mjs";
9
+ } from "./chunk-TH577JHI.mjs";
10
10
 
11
11
  // src/sourceDateFormat.ts
12
12
  function formatSourceDate(raw, opts = {}) {
@@ -4,7 +4,7 @@ import {
4
4
  LEGEND_MARK_CLASS,
5
5
  MARK_CLASS,
6
6
  ROW_IDX_ATTR
7
- } from "./chunk-PHUUFCJY.mjs";
7
+ } from "./chunk-USZSHKIB.mjs";
8
8
 
9
9
  // src/hitTargets.ts
10
10
  var SVG_NS = "http://www.w3.org/2000/svg";
@@ -4,11 +4,11 @@ import {
4
4
  claimParkedLasso,
5
5
  installLasso,
6
6
  lassoEffect
7
- } from "./chunk-ESBH2L6E.mjs";
7
+ } from "./chunk-4DOKJ3R3.mjs";
8
8
  import {
9
9
  censusHitBands,
10
10
  ensureCrossfilterHitTargets
11
- } from "./chunk-UKXGOQMS.mjs";
11
+ } from "./chunk-GQLEU3VC.mjs";
12
12
  import {
13
13
  clearPress,
14
14
  clearSelectionGlyphs,
@@ -25,14 +25,14 @@ import {
25
25
  selectionRuleOf,
26
26
  trackPress,
27
27
  validRowSet
28
- } from "./chunk-LGWTV5PB.mjs";
28
+ } from "./chunk-3ER4C7TG.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-TN5JRULV.mjs";
36
36
  import {
37
37
  geoFromCache
38
38
  } from "./chunk-5WTQN5DL.mjs";
@@ -43,7 +43,7 @@ import {
43
43
  Rr,
44
44
  Yn,
45
45
  tt
46
- } from "./chunk-SYUJ4TPX.mjs";
46
+ } from "./chunk-TH577JHI.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-USZSHKIB.mjs";
91
92
 
92
93
  // src/labelContrast.ts
93
94
  function parseRGBA(c) {
@@ -1159,6 +1160,8 @@ function resolveOptions(p) {
1159
1160
  })(),
1160
1161
  // A host FACT from the generate answer, true only for a literal true: silence never suppresses anything.
1161
1162
  suppressSelectionGlyph: p.suppressSelectionGlyph === true ? true : void 0,
1163
+ // A host FACT (1.22.0): the chart works from the keyboard. True only for a literal true, like the one above.
1164
+ keyboardMarks: p.keyboardMarks === true ? true : void 0,
1162
1165
  onSelect: p.onSelect,
1163
1166
  // Control gestures (1.10.0). Passed straight through like onSelect: the host owns what a gesture MEANS,
1164
1167
  // and a host that supplies nothing leaves every control behaving exactly as before.
@@ -2322,7 +2325,7 @@ var DOT = 8;
2322
2325
  var RING = 1.5;
2323
2326
  var DOT_HALF = DOT / 2 + RING;
2324
2327
  var DOT_IN = 8;
2325
- function createAnnotationLayer(container, onClick) {
2328
+ function createAnnotationLayer(container, onClick, opts) {
2326
2329
  let list = [];
2327
2330
  let report = { shown: 0, notShown: 0 };
2328
2331
  let last = null;
@@ -2446,7 +2449,7 @@ function createAnnotationLayer(container, onClick) {
2446
2449
  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
2450
  return { r: Math.min(run.r, seen), y: (run.t + run.b) / 2, next: after.length ? Math.min(...after) : v.r };
2448
2451
  };
2449
- const clear = (v, len, mark) => {
2452
+ const clear = (v, len, mark, whole = v) => {
2450
2453
  const hw = Math.max(16, len * 6 + 8) / 2, hh = 8, g = 2;
2451
2454
  const cx = (v.l + v.r) / 2, cy = (v.t + v.b) / 2;
2452
2455
  const at = (x, y) => ({ l: x - hw, t: y - hh, r: x + hw, b: y + hh });
@@ -2459,6 +2462,7 @@ function createAnnotationLayer(container, onClick) {
2459
2462
  const tries = [];
2460
2463
  const add = (x, y, px = x, py = y) => tries.push({ x: clampX(x, hw), y: clampY(y, hh), px, py });
2461
2464
  const label = rowLabel(v, mark);
2465
+ if (!label && whole !== v && rowLabel(whole, mark)) return null;
2462
2466
  if (label) {
2463
2467
  const x = label.r + g + hw;
2464
2468
  if (x + hw <= label.next - g || label.next >= v.r) add(x, label.y, Math.min(x, v.r - 1), label.y);
@@ -2523,7 +2527,7 @@ function createAnnotationLayer(container, onClick) {
2523
2527
  }
2524
2528
  }
2525
2529
  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);
2530
+ const pt = !best ? null : dot ? dotSpot(best, bestWhole) : clear(best, (a.label ?? "\u2022").length, bestMark, bestWhole ?? best);
2527
2531
  if (laidOut && !pt) continue;
2528
2532
  shown++;
2529
2533
  const badge = doc.createElement("div");
@@ -2564,6 +2568,15 @@ function createAnnotationLayer(container, onClick) {
2564
2568
  e.stopPropagation();
2565
2569
  onClick?.(a);
2566
2570
  });
2571
+ if (opts?.keyboard?.()) {
2572
+ badge.setAttribute("tabindex", "0");
2573
+ badge.addEventListener("keydown", (e) => {
2574
+ if (e.key !== "Enter" && e.key !== " " && e.key !== "Spacebar") return;
2575
+ e.preventDefault();
2576
+ e.stopPropagation();
2577
+ onClick?.(a);
2578
+ });
2579
+ }
2567
2580
  layer.appendChild(badge);
2568
2581
  }
2569
2582
  report = { shown, notShown: list.length - shown };
@@ -2590,6 +2603,222 @@ function createAnnotationLayer(container, onClick) {
2590
2603
  };
2591
2604
  }
2592
2605
 
2606
+ // src/focusKeep.ts
2607
+ function signatureOf(el) {
2608
+ const a = (n) => el.getAttribute(n) ?? "";
2609
+ const cls = Array.from(el.classList ?? []).filter((c) => !/^lch-|^bic-/.test(c)).sort().join(" ");
2610
+ return [el.localName, a("type"), cls, a("name"), a("placeholder"), a("aria-label"), a("role")].join("|");
2611
+ }
2612
+ function alike(container, sig) {
2613
+ const tag = sig.split("|", 1)[0];
2614
+ if (!tag) return [];
2615
+ return Array.from(container.querySelectorAll(tag)).filter((e) => signatureOf(e) === sig);
2616
+ }
2617
+ function rememberFocus(container) {
2618
+ try {
2619
+ const active = container.ownerDocument?.activeElement;
2620
+ if (!active || active === container || !container.contains(active)) return null;
2621
+ const sig = signatureOf(active);
2622
+ const nth = alike(container, sig).indexOf(active);
2623
+ const t = active;
2624
+ let start = null, end = null, dir = null;
2625
+ try {
2626
+ if (typeof t.selectionStart === "number") {
2627
+ start = t.selectionStart;
2628
+ end = t.selectionEnd;
2629
+ dir = t.selectionDirection ?? null;
2630
+ }
2631
+ } catch {
2632
+ }
2633
+ return { sig, nth: Math.max(0, nth), start, end, dir };
2634
+ } catch {
2635
+ return null;
2636
+ }
2637
+ }
2638
+ function restoreFocus(container, memory) {
2639
+ if (!memory) return false;
2640
+ try {
2641
+ const active = container.ownerDocument?.activeElement;
2642
+ if (active && active !== container && container.contains(active)) return false;
2643
+ const list = alike(container, memory.sig);
2644
+ const el = list[memory.nth] ?? (list.length === 1 ? list[0] : null);
2645
+ if (!el || typeof el.focus !== "function") return false;
2646
+ el.focus({ preventScroll: true });
2647
+ const t = el;
2648
+ if (memory.start != null && typeof t.setSelectionRange === "function") {
2649
+ const len = typeof t.value === "string" ? t.value.length : 0;
2650
+ try {
2651
+ t.setSelectionRange(Math.min(memory.start, len), Math.min(memory.end ?? memory.start, len), memory.dir ?? void 0);
2652
+ } catch {
2653
+ }
2654
+ }
2655
+ return container.ownerDocument?.activeElement === el;
2656
+ } catch {
2657
+ return false;
2658
+ }
2659
+ }
2660
+
2661
+ // src/keyboardMarks.ts
2662
+ var KEYBOARD_STAMP = "// bic:keyboard-marks";
2663
+ var STAMP_RE = /^[ \t]*\/\/[ \t]*bic:keyboard-marks\b/;
2664
+ var STAMP_HEAD_LINES = 12;
2665
+ function codeHasKeyboardStamp(code) {
2666
+ if (typeof code !== "string" || !code) return false;
2667
+ let start = 0;
2668
+ for (let n = 0; n < STAMP_HEAD_LINES && start <= code.length; n++) {
2669
+ const end = code.indexOf("\n", start);
2670
+ const line = code.slice(start, end < 0 ? code.length : end);
2671
+ if (STAMP_RE.test(line)) return true;
2672
+ if (end < 0) break;
2673
+ start = end + 1;
2674
+ }
2675
+ return false;
2676
+ }
2677
+ var KEYBOARD_CLASS = "lch-keyboard";
2678
+ var WIRED_ATTR = "data-lch-kb";
2679
+ function rowNameOf(columns, row, culture) {
2680
+ if (!row) return null;
2681
+ const parts = [];
2682
+ (columns || []).forEach((c, i) => {
2683
+ const name = typeof c === "string" ? c : c?.name;
2684
+ const v = row[i];
2685
+ if (parts.length >= 4 || v == null || v === "" || typeof name === "string" && name.startsWith("__")) return;
2686
+ parts.push(typeof v === "number" ? v.toLocaleString(culture || void 0, { maximumFractionDigits: 2 }) : String(v));
2687
+ });
2688
+ return parts.length ? parts.join(", ") : null;
2689
+ }
2690
+ var isClearChord = (e) => !!e.ctrlKey && !!e.shiftKey && (e.key === "C" || e.key === "c");
2691
+ var NATIVE = /* @__PURE__ */ new Set(["input", "select", "textarea", "button", "a"]);
2692
+ var isNative = (el) => NATIVE.has((el.localName || "").toLowerCase()) || el.isContentEditable === true;
2693
+ function installKeyboard(env) {
2694
+ const { container } = env;
2695
+ let rovingKey = null;
2696
+ let destroyed = false;
2697
+ const marksNow = () => {
2698
+ if (env.inert()) return [];
2699
+ return Array.from(container.querySelectorAll(env.markSelector)).filter((m) => !m.closest(env.skipSelector) && (m.getAttribute("data-row-idx") ?? "") !== "");
2700
+ };
2701
+ const wired = (el) => el.hasAttribute(WIRED_ATTR);
2702
+ const wire = (el, tabindex, role) => {
2703
+ if (el.hasAttribute("tabindex") && !wired(el)) return false;
2704
+ el.setAttribute(WIRED_ATTR, "");
2705
+ el.setAttribute("tabindex", tabindex);
2706
+ if (!el.hasAttribute("role")) el.setAttribute("role", role);
2707
+ return true;
2708
+ };
2709
+ const setRoving = (list, to) => {
2710
+ for (const m of list) if (wired(m)) m.setAttribute("tabindex", m === to ? "0" : "-1");
2711
+ rovingKey = env.keyOf(to);
2712
+ };
2713
+ const refresh = () => {
2714
+ if (destroyed) return;
2715
+ try {
2716
+ container.classList?.add(KEYBOARD_CLASS);
2717
+ const list = marksNow();
2718
+ for (const m of list) {
2719
+ if (!wire(m, "-1", "button")) continue;
2720
+ m.setAttribute("aria-pressed", env.isSelected(m) ? "true" : "false");
2721
+ if (!m.hasAttribute("aria-label") && !m.querySelector(":scope > title")) {
2722
+ const name = env.labelOf(m);
2723
+ if (name) m.setAttribute("aria-label", name);
2724
+ }
2725
+ }
2726
+ if (!list.length) {
2727
+ for (const m of Array.from(container.querySelectorAll(`[${WIRED_ATTR}]`)))
2728
+ if (m.matches(env.markSelector)) {
2729
+ m.removeAttribute("tabindex");
2730
+ m.removeAttribute(WIRED_ATTR);
2731
+ }
2732
+ }
2733
+ const own = list.filter(wired);
2734
+ if (own.length) {
2735
+ const keep = own.find((m) => env.keyOf(m) === rovingKey) ?? own.find((m) => env.isSelected(m)) ?? own[0];
2736
+ setRoving(own, keep);
2737
+ }
2738
+ for (const c of Array.from(container.querySelectorAll(env.pressSelector))) {
2739
+ if (isNative(c)) continue;
2740
+ wire(c, "0", "button");
2741
+ }
2742
+ } catch {
2743
+ }
2744
+ };
2745
+ const onKey = (e) => {
2746
+ if (destroyed || e.defaultPrevented) return;
2747
+ const t = e.target;
2748
+ if (!t || !container.contains(t)) return;
2749
+ const k = e.key;
2750
+ if (isClearChord(e)) {
2751
+ e.preventDefault();
2752
+ env.clear();
2753
+ return;
2754
+ }
2755
+ const press = t.closest?.(env.pressSelector);
2756
+ if (press && wired(press) && container.contains(press)) {
2757
+ if (k === "Enter" || k === " " || k === "Spacebar") {
2758
+ e.preventDefault();
2759
+ const ME = container.ownerDocument.defaultView?.MouseEvent ?? MouseEvent;
2760
+ press.dispatchEvent(new ME("click", { bubbles: true, cancelable: true }));
2761
+ } else if (k === "Escape" && env.escapeClears !== false) env.clear();
2762
+ return;
2763
+ }
2764
+ const mark = t.closest?.(env.markSelector);
2765
+ if (!mark || !wired(mark) || !container.contains(mark)) return;
2766
+ const list = marksNow().filter(wired);
2767
+ const at = list.indexOf(mark);
2768
+ const go = (i) => {
2769
+ const to = list[Math.max(0, Math.min(list.length - 1, i))];
2770
+ if (!to) return;
2771
+ setRoving(list, to);
2772
+ try {
2773
+ to.focus({ preventScroll: false });
2774
+ } catch {
2775
+ }
2776
+ };
2777
+ switch (k) {
2778
+ case "ArrowRight":
2779
+ case "ArrowDown":
2780
+ e.preventDefault();
2781
+ go(at + 1);
2782
+ break;
2783
+ case "ArrowLeft":
2784
+ case "ArrowUp":
2785
+ e.preventDefault();
2786
+ go(at - 1);
2787
+ break;
2788
+ case "Home":
2789
+ e.preventDefault();
2790
+ go(0);
2791
+ break;
2792
+ case "End":
2793
+ e.preventDefault();
2794
+ go(list.length - 1);
2795
+ break;
2796
+ case "Enter":
2797
+ case " ":
2798
+ case "Spacebar":
2799
+ e.preventDefault();
2800
+ rovingKey = env.keyOf(mark);
2801
+ env.select(mark, { ctrl: !!(e.ctrlKey || e.metaKey), shift: !!e.shiftKey });
2802
+ break;
2803
+ case "Escape":
2804
+ if (env.escapeClears !== false) env.clear();
2805
+ break;
2806
+ }
2807
+ };
2808
+ container.addEventListener("keydown", onKey);
2809
+ return {
2810
+ refresh,
2811
+ destroy() {
2812
+ destroyed = true;
2813
+ container.removeEventListener("keydown", onKey);
2814
+ try {
2815
+ container.classList?.remove(KEYBOARD_CLASS);
2816
+ } catch {
2817
+ }
2818
+ }
2819
+ };
2820
+ }
2821
+
2593
2822
  // src/host.ts
2594
2823
  function sessionViewStateProvider(container) {
2595
2824
  const holder = container;
@@ -2738,6 +2967,9 @@ function createChartHost(container, config) {
2738
2967
  }
2739
2968
  }
2740
2969
  let lassoClaimed = false;
2970
+ let ownBag = null;
2971
+ const seedKey = (s) => s && typeof s === "object" ? JSON.stringify(s) : void 0;
2972
+ let lastSeed = seedKey(raw.uiState);
2741
2973
  if (typeof raw.setUiState !== "function") {
2742
2974
  const provider = config.viewState ?? sessionViewStateProvider(container);
2743
2975
  let bag;
@@ -2751,6 +2983,7 @@ function createChartHost(container, config) {
2751
2983
  if (raw.uiState && typeof raw.uiState === "object" && Object.keys(bag).length === 0)
2752
2984
  Object.assign(bag, raw.uiState);
2753
2985
  raw.uiState = bag;
2986
+ ownBag = { bag, provider };
2754
2987
  raw.setUiState = (s) => {
2755
2988
  if (!s || typeof s !== "object") return;
2756
2989
  try {
@@ -2759,6 +2992,26 @@ function createChartHost(container, config) {
2759
2992
  }
2760
2993
  };
2761
2994
  }
2995
+ const takeSeed = (partial) => {
2996
+ if (typeof partial.setUiState === "function") {
2997
+ ownBag = null;
2998
+ return partial;
2999
+ }
3000
+ if (!ownBag || !("uiState" in partial)) return partial;
3001
+ const { uiState: seed, ...rest } = partial;
3002
+ const key = seedKey(seed);
3003
+ if (key !== void 0 && key !== lastSeed) {
3004
+ lastSeed = key;
3005
+ const { bag, provider } = ownBag;
3006
+ for (const k of Object.keys(bag)) delete bag[k];
3007
+ Object.assign(bag, seed);
3008
+ try {
3009
+ provider.save({ ...bag });
3010
+ } catch {
3011
+ }
3012
+ }
3013
+ return rest;
3014
+ };
2762
3015
  const resolveForHost = (input) => {
2763
3016
  const out = resolveOptions(input);
2764
3017
  return out.allowTooltips == null ? { ...out, allowTooltips: true } : out;
@@ -2773,7 +3026,9 @@ function createChartHost(container, config) {
2773
3026
  let warnedLassoNoFilter = false;
2774
3027
  const idScope = newIdScope();
2775
3028
  let renderSeq = 0;
2776
- const annotationLayer = createAnnotationLayer(container, (a) => config.onAnnotationClick?.(a));
3029
+ const keyboardOn = codeHasKeyboardStamp(config.code);
3030
+ let keyboard = null;
3031
+ const annotationLayer = createAnnotationLayer(container, (a) => config.onAnnotationClick?.(a), { keyboard: () => keyboardOn });
2777
3032
  const drawAnnotations = () => annotationLayer.draw(
2778
3033
  { columns: data.columns, rows: data.rows },
2779
3034
  {
@@ -2857,7 +3112,18 @@ function createChartHost(container, config) {
2857
3112
  anomaly badge, a highlight stroke) and must keep dimming with its datum. */
2858
3113
  .${HOST_CONTAINER_CLASS} .${MARK_CLASS} { cursor: pointer; transition: opacity 120ms ease-in-out, filter 100ms ease-in-out; }
2859
3114
  .${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; }
3115
+ .${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; }
3116
+ /* A MARK THAT SELECTS NOTHING DOESN'T LOOK CLICKABLE: no pointer, over the inline cursor generated code
3117
+ sets on its marks, and no dimming of the others on hover (the rule above). A control the chart draws keeps its own. */
3118
+ .${HOST_CONTAINER_CLASS}.${MARKS_INERT_CLASS} .${MARK_CLASS} { cursor: default !important; }
3119
+ /* THE KEYBOARD'S FOCUS: a ring on the mark, control or badge that holds keyboard focus, in the colour the
3120
+ chart's text is drawn in, so it reads in either theme. A pointer's focus shows none. */
3121
+ .${HOST_CONTAINER_CLASS}.${KEYBOARD_CLASS} [${WIRED_ATTR}]:focus:not(:focus-visible) { outline: none; }
3122
+ .${HOST_CONTAINER_CLASS}.${KEYBOARD_CLASS} [${WIRED_ATTR}]:focus-visible,
3123
+ .${HOST_CONTAINER_CLASS} .bic-annotation:focus-visible { outline: 2px solid var(--lch-focus-ring, currentColor) !important; outline-offset: 2px; }
3124
+ /* A dimmed mark that takes keyboard focus is drawn at full strength while it has it, ring included: the dim would
3125
+ otherwise fade the ring with the mark. */
3126
+ .${HOST_CONTAINER_CLASS}.${KEYBOARD_CLASS}.${SELECTION_ACTIVE_CLASS} .${MARK_CLASS}[${WIRED_ATTR}]:focus-visible:not(.${MARK_SELECTED_CLASS}) { opacity: 1 !important; }
2861
3127
  .${HOST_CONTAINER_CLASS}.${SELECTION_ACTIVE_CLASS} .${MARK_CLASS}:not(.${MARK_SELECTED_CLASS}):not([${ROW_IDX_ATTR}=""]) {
2862
3128
  opacity: var(${DIM_OPACITY_VAR}, ${DIM_OPACITY_DEFAULT}) !important;
2863
3129
  }
@@ -2890,9 +3156,18 @@ function createChartHost(container, config) {
2890
3156
  } catch {
2891
3157
  }
2892
3158
  };
3159
+ const marksInert = () => {
3160
+ if (typeof config.marksSelect !== "function") return false;
3161
+ try {
3162
+ return config.marksSelect() === false;
3163
+ } catch {
3164
+ return false;
3165
+ }
3166
+ };
2893
3167
  const paintSelection = () => {
2894
3168
  if (destroyed) return;
2895
3169
  try {
3170
+ container.classList?.[marksInert() ? "add" : "remove"](MARKS_INERT_CLASS);
2896
3171
  const sel = current;
2897
3172
  const active = !!sel && sel.length > 0;
2898
3173
  const dim = active && !selectionIsPeriod;
@@ -3161,6 +3436,7 @@ function createChartHost(container, config) {
3161
3436
  factFresh = true;
3162
3437
  paintSelection();
3163
3438
  paintGlyphLayer();
3439
+ keyboard?.refresh();
3164
3440
  if (publish) for (const cb of subs) {
3165
3441
  try {
3166
3442
  cb(rowIdxs, source, meta);
@@ -3196,6 +3472,7 @@ function createChartHost(container, config) {
3196
3472
  const d = e?.detail || {};
3197
3473
  xfAt = Date.now();
3198
3474
  if (d.clear) notify([], d.source || "chart", void 0, false, selectionPublished || !(current && current.length));
3475
+ else if (marksInert()) return;
3199
3476
  else if (d.mark) notify(parseRowIdxs(d.mark.getAttribute?.(ROW_IDX_ATTR)), d.source || "chart", d.mark, true);
3200
3477
  else if (Array.isArray(d.rows)) notify(validRowSet(d.rows), d.source || "chart", null, true, true, d.keepHidden === true ? { keepHidden: true } : void 0);
3201
3478
  };
@@ -3231,6 +3508,9 @@ function createChartHost(container, config) {
3231
3508
  const wasDrag = clickWasDrag(container, e);
3232
3509
  clearPress(container);
3233
3510
  if (wasDrag) return;
3511
+ const inert = marksInert();
3512
+ container.classList?.[inert ? "add" : "remove"](MARKS_INERT_CLASS);
3513
+ if (inert) return;
3234
3514
  if (Date.now() - xfAt < XF_ECHO_MS) return;
3235
3515
  if (isInsideControl(e?.target, container)) return;
3236
3516
  let el = resolver.findMark(e?.target, clickSel, e);
@@ -3258,6 +3538,27 @@ function createChartHost(container, config) {
3258
3538
  container.addEventListener(XFILTER_REFRESH_EVENT, onXf);
3259
3539
  const untrackPress = trackPress(container);
3260
3540
  container.addEventListener("click", onClick);
3541
+ if (keyboardOn) {
3542
+ const nameOfRow = (m) => {
3543
+ const r = parseRowIdxs(m.getAttribute(ROW_IDX_ATTR))[0];
3544
+ return rowNameOf(data.columns || [], r == null ? null : data.rows?.[r], resolved.cultureCode);
3545
+ };
3546
+ keyboard = installKeyboard({
3547
+ container,
3548
+ markSelector: clickSel,
3549
+ pressSelector: `.${CONTROL_CLASS}`,
3550
+ skipSelector: `.${ANNOTATION_LAYER_CLASS}, .${GLYPH_LAYER_CLASS}, .${LASSO_OUTLINE_CLASS}`,
3551
+ inert: marksInert,
3552
+ select: (m, mods) => {
3553
+ const ME = win?.MouseEvent ?? MouseEvent;
3554
+ m.dispatchEvent(new ME("click", { bubbles: true, cancelable: true, ctrlKey: mods.ctrl, shiftKey: mods.shift }));
3555
+ },
3556
+ clear: clearFromGesture,
3557
+ isSelected: (m) => !!m.classList?.contains(MARK_SELECTED_CLASS),
3558
+ labelOf: nameOfRow,
3559
+ keyOf: (m) => m.getAttribute(ROW_IDX_ATTR) ?? ""
3560
+ });
3561
+ }
3261
3562
  container.classList?.add(HOST_CONTAINER_CLASS);
3262
3563
  ensureAffordanceStyles();
3263
3564
  const fitOpts = config.fit ?? true;
@@ -3405,6 +3706,15 @@ function createChartHost(container, config) {
3405
3706
  } catch {
3406
3707
  }
3407
3708
  paintGlyphLayer();
3709
+ installLassoHere();
3710
+ keyboard?.refresh();
3711
+ const fw = focusWas;
3712
+ focusWas = null;
3713
+ restoreFocus(container, fw);
3714
+ selfRedraw?.takeRecords();
3715
+ watchSelfRedraw();
3716
+ };
3717
+ function installLassoHere() {
3408
3718
  try {
3409
3719
  lasso = installLasso({
3410
3720
  container,
@@ -3415,7 +3725,38 @@ function createChartHost(container, config) {
3415
3725
  });
3416
3726
  } catch {
3417
3727
  }
3728
+ }
3729
+ let selfRedraw = null;
3730
+ let focusWas = null;
3731
+ let notesHeld = (config.annotations ?? []).length > 0;
3732
+ const takesMarks = (n) => n.nodeType === 1 && (!!n.classList?.contains(MARK_CLASS) || !!n.querySelector?.(`.${MARK_CLASS}`));
3733
+ const lassoLost = () => !!lasso?.surface && !lasso.surface.isConnected;
3734
+ const repaintAfterChart = () => {
3735
+ if (destroyed) return;
3736
+ try {
3737
+ ensureCrossfilterHitTargets(container, doc);
3738
+ } catch {
3739
+ }
3740
+ paintSelection();
3741
+ try {
3742
+ drawAnnotations();
3743
+ } catch {
3744
+ }
3745
+ paintGlyphLayer(false);
3746
+ if (lassoLost()) installLassoHere();
3747
+ keyboard?.refresh();
3748
+ selfRedraw?.takeRecords();
3418
3749
  };
3750
+ function watchSelfRedraw() {
3751
+ const MO = win?.MutationObserver;
3752
+ if (selfRedraw || !MO || destroyed) return;
3753
+ selfRedraw = new MO((records) => {
3754
+ if (destroyed || !records.some((r) => Array.from(r.removedNodes).some(takesMarks))) return;
3755
+ if (!(current && current.length) && !notesHeld && !lassoLost()) return;
3756
+ repaintAfterChart();
3757
+ });
3758
+ selfRedraw.observe(container, { childList: true, subtree: true });
3759
+ }
3419
3760
  const renderFailure = (err) => {
3420
3761
  const message = String(err?.message ?? err ?? "");
3421
3762
  if (config.onInvalidSentinel && isInvalidSentinelError(message)) {
@@ -3435,7 +3776,9 @@ function createChartHost(container, config) {
3435
3776
  disposeLasso();
3436
3777
  flushZoom();
3437
3778
  if (hasRendered && keepScroll) rememberScroll(container, scrollKey());
3779
+ focusWas = rememberFocus(container);
3438
3780
  container.innerHTML = "";
3781
+ selfRedraw?.takeRecords();
3439
3782
  hasRendered = true;
3440
3783
  if (!renderFn) {
3441
3784
  if (!config.code) throw new Error("createChartHost: provide code or renderFn");
@@ -3461,6 +3804,7 @@ function createChartHost(container, config) {
3461
3804
  if (resolved.dateCellsAreUtcDays === void 0 && data.dateCellsAreUtcDays) resolved = { ...resolved, dateCellsAreUtcDays: true };
3462
3805
  if (resolved.suppressSelectionGlyph === void 0 && data.meta?.suppressSelectionGlyph === true)
3463
3806
  resolved = { ...resolved, suppressSelectionGlyph: true };
3807
+ if (keyboardOn) resolved = { ...resolved, keyboardMarks: true };
3464
3808
  let outcome;
3465
3809
  zoomCap = null;
3466
3810
  if (zoomMemory && config.code && !config.renderFn) {
@@ -3536,7 +3880,7 @@ function createChartHost(container, config) {
3536
3880
  // callback) must not clear, stop or annotate the chart that is on screen now.
3537
3881
  setOptions(partial) {
3538
3882
  if (destroyed) return;
3539
- raw = { ...raw, ...partial };
3883
+ raw = { ...raw, ...takeSeed(partial ?? {}) };
3540
3884
  resolved = resolveForHost(raw);
3541
3885
  host.render();
3542
3886
  },
@@ -3551,6 +3895,7 @@ function createChartHost(container, config) {
3551
3895
  },
3552
3896
  setAnnotations(list) {
3553
3897
  if (destroyed) return;
3898
+ notesHeld = (list ?? []).length > 0;
3554
3899
  annotationLayer.set(list);
3555
3900
  try {
3556
3901
  drawAnnotations();
@@ -3612,6 +3957,12 @@ function createChartHost(container, config) {
3612
3957
  if (destroyed) return;
3613
3958
  destroyed = true;
3614
3959
  stopAnim();
3960
+ if (selfRedraw) {
3961
+ selfRedraw.disconnect();
3962
+ selfRedraw = null;
3963
+ }
3964
+ keyboard?.destroy();
3965
+ keyboard = null;
3615
3966
  container.removeEventListener(XFILTER_REFRESH_EVENT, onXf);
3616
3967
  container.removeEventListener("click", onClick);
3617
3968
  untrackPress();
@@ -3858,6 +4209,14 @@ export {
3858
4209
  invalidSentinelReason,
3859
4210
  ANNOTATION_LAYER_CLASS,
3860
4211
  ANNOTATION_CLASS,
4212
+ rememberFocus,
4213
+ restoreFocus,
4214
+ KEYBOARD_STAMP,
4215
+ codeHasKeyboardStamp,
4216
+ KEYBOARD_CLASS,
4217
+ WIRED_ATTR,
4218
+ rowNameOf,
4219
+ installKeyboard,
3861
4220
  sessionViewStateProvider,
3862
4221
  noopViewStateProvider,
3863
4222
  assembleD3,
@@ -1,7 +1,7 @@
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
  LASSO_KNOB_KEY
4
- } from "./chunk-ESBH2L6E.mjs";
4
+ } from "./chunk-4DOKJ3R3.mjs";
5
5
 
6
6
  // src/filterScope.ts
7
7
  function viewToken(view) {
@@ -1421,10 +1421,10 @@ function Sr2(e, t) {
1421
1421
  return n;
1422
1422
  }
1423
1423
  function Rr(e, t, n, o2, a3) {
1424
- let u3 = [], l2 = [], i = {}, C3 = {}, b2 = new Set(e), h3 = (s2) => t.map((d2) => d2[s2]);
1424
+ let u3 = [], l2 = [], i = {}, y3 = {}, h3 = new Set(e), b2 = (s2) => t.map((d2) => d2[s2]);
1425
1425
  for (let s2 of ["city", "country"]) {
1426
1426
  let d2 = n?.[s2];
1427
- d2 && b2.has(d2) && (i[s2] = d2);
1427
+ d2 && h3.has(d2) && (i[s2] = d2);
1428
1428
  }
1429
1429
  let g5 = new Set(a3 ?? e);
1430
1430
  for (let s2 of ["lat", "lon"]) {
@@ -1432,20 +1432,20 @@ function Rr(e, t, n, o2, a3) {
1432
1432
  d2 && g5.has(d2) && (i[s2] = d2);
1433
1433
  }
1434
1434
  let f2 = n?.zip;
1435
- if (f2 && b2.has(f2)) {
1436
- let s2 = h3(f2), d2 = ie(s2);
1435
+ if (f2 && h3.has(f2)) {
1436
+ let s2 = b2(f2), d2 = ie(s2);
1437
1437
  d2 < 95 ? l2.push(`zip=${f2} (only ${d2}% of values read as ZIPs)`) : pe(s2, f2) ? i.zip = f2 : l2.push(`zip=${f2} (all values are 3-4 digits with nothing to say they are postal)`);
1438
1438
  }
1439
1439
  let c = n?.state;
1440
- if (c && b2.has(c)) {
1441
- let s2 = h3(c), d2 = F3(s2);
1440
+ if (c && h3.has(c)) {
1441
+ let s2 = b2(c), d2 = F3(s2);
1442
1442
  d2.length > 0 && d2.every((S) => fe(S, o2)) ? l2.push(`state=${c} (every value is "CA", which is both Canada and California)`) : N(s2) ? (l2.push(`state=${c} (every value is a country, not a state)`), i.country || (i.country = c)) : q4(s2) < 95 ? l2.push(`state=${c} (only ${q4(s2)}% of values are states/provinces)`) : i.state = c;
1443
1443
  }
1444
- let p3 = new Set(Object.values(i).filter(Boolean)), y3 = e.filter((s2) => !p3.has(s2) && !s2.startsWith("__"));
1444
+ let p3 = new Set(Object.values(i).filter(Boolean)), C3 = e.filter((s2) => !p3.has(s2) && !s2.startsWith("__"));
1445
1445
  if (!i.state) {
1446
1446
  let s2 = null;
1447
- for (let d2 of y3) {
1448
- let S = h3(d2);
1447
+ for (let d2 of C3) {
1448
+ let S = b2(d2);
1449
1449
  if (N(S)) continue;
1450
1450
  let O2 = q4(S);
1451
1451
  if (O2 < 95) continue;
@@ -1454,46 +1454,46 @@ function Rr(e, t, n, o2, a3) {
1454
1454
  }
1455
1455
  s2 && (i.state = s2.name, p3.add(s2.name), u3.push(`state=${s2.name} (${s2.pct}% states/provinces)`));
1456
1456
  }
1457
- if (!i.zip) for (let s2 of y3) {
1457
+ if (!i.zip) for (let s2 of C3) {
1458
1458
  if (p3.has(s2)) continue;
1459
- let d2 = h3(s2), S = ie(d2);
1459
+ let d2 = b2(s2), S = ie(d2);
1460
1460
  if (!(S < 95) && pe(d2, s2) && !(Sr2(d2, (O2) => Gn(O2).length > 0) < ee)) {
1461
1461
  i.zip = s2, p3.add(s2), u3.push(`zip=${s2} (${S}% ZIP codes)`);
1462
1462
  break;
1463
1463
  }
1464
1464
  }
1465
1465
  let T3 = i.country;
1466
- if (!T3 || !N(h3(T3))) {
1466
+ if (!T3 || !N(b2(T3))) {
1467
1467
  let s2 = null;
1468
- for (let d2 of y3) {
1469
- if (p3.has(d2) || !N(h3(d2))) continue;
1470
- let S = re(h3(d2));
1468
+ for (let d2 of C3) {
1469
+ if (p3.has(d2) || !N(b2(d2))) continue;
1470
+ let S = re(b2(d2));
1471
1471
  (!s2 || S > s2.pct) && (s2 = { name: d2, pct: S });
1472
1472
  }
1473
- s2 && (T3 && (l2.push(`country=${T3} (only ${re(h3(T3))}% of values are country identifiers; using ${s2.name} instead)`), C3.country = T3), i.country = s2.name, p3.add(s2.name), u3.push(`country=${s2.name}`));
1473
+ s2 && (T3 && (l2.push(`country=${T3} (only ${re(b2(T3))}% of values are country identifiers; using ${s2.name} instead)`), y3.country = T3), i.country = s2.name, p3.add(s2.name), u3.push(`country=${s2.name}`));
1474
1474
  }
1475
1475
  if (!i.city) {
1476
1476
  let s2 = null;
1477
- for (let d2 of y3) {
1477
+ for (let d2 of C3) {
1478
1478
  if (p3.has(d2)) continue;
1479
- let S = h3(d2), O2 = Tr2(S, o2);
1479
+ let S = b2(d2), O2 = Tr2(S, o2);
1480
1480
  O2 < ct || te(S, (H3) => Ht(H3, o2)) < ee || (!s2 || O2 > s2.pct) && (s2 = { name: d2, pct: O2 });
1481
1481
  }
1482
1482
  s2 && (i.city = s2.name, u3.push(`city=${s2.name} (${s2.pct}% known cities)`));
1483
1483
  }
1484
- let v2 = i.country ? h3(i.country) : [], R3 = !!i.country && N(v2), L3 = R3 ? new Set(v2.map((s2) => I(s2 == null ? null : String(s2))).filter(Boolean)).size : 0, x3 = R3 && L3 >= 2, w4 = !!(i.lat && i.lon) && t.some((s2) => {
1484
+ let A2 = i.country ? b2(i.country) : [], R3 = !!i.country && N(A2), v2 = R3 ? new Set(A2.map((s2) => I(s2 == null ? null : String(s2))).filter(Boolean)).size : 0, x3 = R3 && v2 >= 2, w4 = !!(i.lat && i.lon) && t.some((s2) => {
1485
1485
  let d2 = +String(s2[i.lat] ?? "").trim(), S = +String(s2[i.lon] ?? "").trim();
1486
1486
  return isFinite(d2) && isFinite(S) && d2 >= -90 && d2 <= 90 && S >= -180 && S <= 180 && String(s2[i.lat] ?? "").trim() !== "" && String(s2[i.lon] ?? "").trim() !== "";
1487
1487
  });
1488
- return i.lat && i.lon && !w4 && l2.push(`lat/lon=${i.lat}/${i.lon} (no row holds a usable coordinate pair)`), i.country && !x3 && !(i.city || i.state || i.zip || w4) && l2.push(R3 ? `country=${i.country} (every row resolves to the same country - nothing to place apart)` : `country=${i.country} (values are not country identifiers)`), { bind: !!(i.city || i.state || i.zip || x3 || w4) ? i : null, backfilled: u3, refused: l2, ...Object.keys(C3).length ? { labelFallback: C3 } : {} };
1488
+ return i.lat && i.lon && !w4 && l2.push(`lat/lon=${i.lat}/${i.lon} (no row holds a usable coordinate pair)`), i.country && !x3 && !(i.city || i.state || i.zip || w4) && l2.push(R3 ? `country=${i.country} (every row resolves to the same country - nothing to place apart)` : `country=${i.country} (values are not country identifiers)`), { bind: !!(i.city || i.state || i.zip || x3 || w4) ? i : null, backfilled: u3, refused: l2, ...Object.keys(y3).length ? { labelFallback: y3 } : {} };
1489
1489
  }
1490
1490
  var r = (e, ...t) => ({ part: e, langs: Object.freeze(t) });
1491
1491
  var ae = Object.freeze({ jaar: r("year", "nl"), kwartaal: r("quarter", "nl"), maand: r("month", "nl"), dag: r("day", "nl", "sv", "da", "nb"), jahr: r("year", "de"), quartal: r("quarter", "de"), monat: r("month", "de"), tag: r("day", "de"), ann\u00E9e: r("year", "fr"), mois: r("month", "fr"), jour: r("day", "fr"), trimestre: r("quarter", "fr", "es", "pt", "it"), a\u00F1o: r("year", "es"), mes: r("month", "es"), d\u00EDa: r("day", "es"), ano: r("year", "pt"), m\u00EAs: r("month", "pt"), dia: r("day", "pt"), anno: r("year", "it"), mese: r("month", "it"), giorno: r("day", "it"), \u0433\u043E\u0434: r("year", "ru"), \u043C\u0435\u0441\u044F\u0446: r("month", "ru"), \u0440\u0456\u043A: r("year", "uk"), \u043C\u0456\u0441\u044F\u0446\u044C: r("month", "uk"), \u043A\u0432\u0430\u0440\u0442\u0430\u043B: r("quarter", "ru", "uk"), \u0434\u0435\u043D\u044C: r("day", "ru", "uk"), rok: r("year", "pl", "cs", "sk"), kwarta\u0142: r("quarter", "pl"), miesi\u0105c: r("month", "pl"), dzie\u0144: r("day", "pl"), \u010Dtvrtlet\u00ED: r("quarter", "cs"), m\u011Bs\u00EDc: r("month", "cs"), den: r("day", "cs"), \u0161tvr\u0165rok: r("quarter", "sk"), mesiac: r("month", "sk"), de\u0148: r("day", "sk"), \u00E5r: r("year", "sv", "da", "nb"), kvartal: r("quarter", "sv", "da", "nb"), m\u00E5nad: r("month", "sv"), m\u00E5ned: r("month", "da", "nb"), vuosi: r("year", "fi"), vuosinelj\u00E4nnes: r("quarter", "fi"), kuukausi: r("month", "fi"), p\u00E4iv\u00E4: r("day", "fi"), \u00E9v: r("year", "hu"), negyed\u00E9v: r("quarter", "hu"), h\u00F3nap: r("month", "hu"), nap: r("day", "hu"), y\u0131l: r("year", "tr"), \u00E7eyrek: r("quarter", "tr"), ay: r("month", "tr"), g\u00FCn: r("day", "tr"), \u03AD\u03C4\u03BF\u03C2: r("year", "el"), \u03C4\u03C1\u03AF\u03BC\u03B7\u03BD\u03BF: r("quarter", "el"), \u03BC\u03AE\u03BD\u03B1\u03C2: r("month", "el"), \u03B7\u03BC\u03AD\u03C1\u03B1: r("day", "el"), an: r("year", "ro"), trimestru: r("quarter", "ro"), lun\u0103: r("month", "ro"), zi: r("day", "ro"), godina: r("year", "hr"), tromjese\u010Dje: r("quarter", "hr"), mjesec: r("month", "hr"), dan: r("day", "hr"), \u5E74: r("year", "ja", "zh"), \u56DB\u534A\u671F: r("quarter", "ja"), \u5B63\u5EA6: r("quarter", "zh"), \u6708: r("month", "ja", "zh"), \u65E5: r("day", "ja", "zh"), \uC5F0\uB3C4: r("year", "ko"), \uBD84\uAE30: r("quarter", "ko"), \uC6D4: r("month", "ko"), \uC77C: r("day", "ko"), tahun: r("year", "id"), kuartal: r("quarter", "id"), triwulan: r("quarter", "id"), bulan: r("month", "id"), hari: r("day", "id"), n\u0103m: r("year", "vi"), qu\u00FD: r("quarter", "vi"), th\u00E1ng: r("month", "vi"), ng\u00E0y: r("day", "vi"), \u0E1B\u0E35: r("year", "th"), \u0E44\u0E15\u0E23\u0E21\u0E32\u0E2A: r("quarter", "th"), \u0E40\u0E14\u0E37\u0E2D\u0E19: r("month", "th"), \u0E27\u0E31\u0E19: r("day", "th"), \u0627\u0644\u0633\u0646\u0629: r("year", "ar"), \u0633\u0646\u0629: r("year", "ar"), \u0627\u0644\u0639\u0627\u0645: r("year", "ar"), \u0639\u0627\u0645: r("year", "ar"), \u0627\u0644\u0631\u0628\u0639: r("quarter", "ar"), \u0631\u0628\u0639: r("quarter", "ar"), "\u0631\u0628\u0639 \u0627\u0644\u0633\u0646\u0629": r("quarter", "ar"), \u0627\u0644\u0634\u0647\u0631: r("month", "ar"), \u0634\u0647\u0631: r("month", "ar"), \u0627\u0644\u064A\u0648\u0645: r("day", "ar"), \u064A\u0648\u0645: r("day", "ar"), \u05E9\u05E0\u05D4: r("year", "he"), \u05E8\u05D1\u05E2\u05D5\u05DF: r("quarter", "he"), \u05D7\u05D5\u05D3\u05E9: r("month", "he"), \u05D9\u05D5\u05DD: r("day", "he"), \u0935\u0930\u094D\u0937: r("year", "hi"), \u0938\u093E\u0932: r("year", "hi"), \u0924\u093F\u092E\u093E\u0939\u0940: r("quarter", "hi"), \u092E\u0939\u0940\u0928\u093E: r("month", "hi"), \u092E\u093E\u0939: r("month", "hi"), \u0926\u093F\u0928: r("day", "hi"), \u0633\u0627\u0644: r("year", "fa"), \u0645\u0627\u0647: r("month", "fa"), \u0631\u0648\u0632: r("day", "fa") });
1492
1492
  var xr = Object.freeze(Array.from(new Set(Object.values(ae).flatMap((e) => e.langs))));
1493
1493
  var ye = Object.freeze(["fa", "ar", "he", "th"]);
1494
- var no = Object.freeze({ integer: "number", decimal: "number", double: "number", datetime: "date", date: "date", boolean: "boolean", string: "text" });
1495
- var co = Object.freeze({ RENDER_START: "render-start", RENDER_OK: "render-ok", RENDER_ERROR: "render-error", VIEW: "view", VIEW_VERDICT: "view-verdict", REDRAW: "redraw", INTERACT: "interact" });
1496
- var mo = Object.freeze(["open", "generate", "version", "rebind", "data", "sample", "fix", "resize", "other"]);
1494
+ var ro = Object.freeze({ integer: "number", decimal: "number", double: "number", datetime: "date", date: "date", boolean: "boolean", string: "text" });
1495
+ var mo = Object.freeze({ RENDER_START: "render-start", RENDER_OK: "render-ok", RENDER_ERROR: "render-error", VIEW: "view", VIEW_VERDICT: "view-verdict", REDRAW: "redraw", INTERACT: "interact" });
1496
+ var fo = Object.freeze(["open", "generate", "version", "rebind", "data", "sample", "fix", "resize", "other"]);
1497
1497
  var Ne = 2 ** 31 - 1;
1498
1498
 
1499
1499
  export {
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  CONTAINER_SLOT_SCROLL,
4
4
  SNAPSHOT_SVG_ATTR
5
- } from "./chunk-PHUUFCJY.mjs";
5
+ } from "./chunk-USZSHKIB.mjs";
6
6
 
7
7
  // src/fit.ts
8
8
  var SCROLL_SLACK_PX = 8;
@@ -1,7 +1,7 @@
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
 
3
3
  // src/contract.ts
4
- var HOST_CONTRACT_VERSION = "1.21.0";
4
+ var HOST_CONTRACT_VERSION = "1.22.0";
5
5
  var GEO_POINT_PRECISIONS = ["latlon", "city", "zip3", "state", "country"];
6
6
  var MARK_CLASS = "d3-mark";
7
7
  var LEGEND_MARK_CLASS = "d3-legend-mark";
@@ -34,6 +34,7 @@ function withoutViewOnlyKeys(bag) {
34
34
  var HOST_CONTAINER_CLASS = "bic-chart-host";
35
35
  var SELECTION_ACTIVE_CLASS = "lch-has-selection";
36
36
  var MARK_SELECTED_CLASS = "lch-mark-selected";
37
+ var MARKS_INERT_CLASS = "lch-marks-inert";
37
38
  var ACTIVE_TICK_CLASS = "lch-tick-active";
38
39
  var DIM_OPACITY_VAR = "--lch-dim-opacity";
39
40
  var DIM_OPACITY_DEFAULT = 0.25;
@@ -101,6 +102,7 @@ export {
101
102
  HOST_CONTAINER_CLASS,
102
103
  SELECTION_ACTIVE_CLASS,
103
104
  MARK_SELECTED_CLASS,
105
+ MARKS_INERT_CLASS,
104
106
  ACTIVE_TICK_CLASS,
105
107
  DIM_OPACITY_VAR,
106
108
  DIM_OPACITY_DEFAULT,
package/dist/index.mjs CHANGED
@@ -5,7 +5,7 @@ import {
5
5
  formatSourceDateFor,
6
6
  isExcelDateFormat,
7
7
  normaliseAggregation
8
- } from "./chunk-MU4BWWO6.mjs";
8
+ } from "./chunk-G4CKILIL.mjs";
9
9
  import {
10
10
  captureSvgSnapshot,
11
11
  chartSvgOf,
@@ -15,7 +15,7 @@ import {
15
15
  svgNaturalSize,
16
16
  svgToDataUrl,
17
17
  tableHostOf
18
- } from "./chunk-FCLPMC6Q.mjs";
18
+ } from "./chunk-3EQOJHRA.mjs";
19
19
  import {
20
20
  ANNOTATION_CLASS,
21
21
  ANNOTATION_LAYER_CLASS,
@@ -29,6 +29,8 @@ import {
29
29
  DARK_TEXT,
30
30
  EXPORT_FORMATS,
31
31
  GLYPH_SAMPLE_N,
32
+ KEYBOARD_CLASS,
33
+ KEYBOARD_STAMP,
32
34
  LABEL_CONTRAST_CAP,
33
35
  LABEL_CONTRAST_DONE_ATTR,
34
36
  LABEL_PILL_CLASS,
@@ -47,6 +49,7 @@ import {
47
49
  STRADDLE_TARGET_CONTRAST,
48
50
  VALUE_AXIS_MIN_FIT,
49
51
  WHITE_TEXT_BG_LUM,
52
+ WIRED_ATTR,
50
53
  XLSX_MIME,
51
54
  applyLabelContrast,
52
55
  assembleD3,
@@ -65,6 +68,7 @@ import {
65
68
  censusValuePlacement,
66
69
  cleanHostSelection,
67
70
  clippedTextFlag,
71
+ codeHasKeyboardStamp,
68
72
  colourSpreadFlag,
69
73
  compileRenderFn,
70
74
  compositeOver,
@@ -80,6 +84,7 @@ import {
80
84
  exportFormatsFor,
81
85
  exportTableService,
82
86
  glyphSampleGrid,
87
+ installKeyboard,
83
88
  invalidSentinelReason,
84
89
  isBlankRender,
85
90
  isInvalidSentinelError,
@@ -90,9 +95,12 @@ import {
90
95
  pickColorOver,
91
96
  pillBacksGlyph,
92
97
  relativeLuminance,
98
+ rememberFocus,
93
99
  requiredD3Plugins,
94
100
  resolveAutoColorScale,
95
101
  resolveOptions,
102
+ restoreFocus,
103
+ rowNameOf,
96
104
  scopeChartIds,
97
105
  sessionViewStateProvider,
98
106
  sheetName,
@@ -106,7 +114,7 @@ import {
106
114
  toRGBA,
107
115
  toSourceRows,
108
116
  valuePlacementFlag
109
- } from "./chunk-MO2GR56D.mjs";
117
+ } from "./chunk-JQ7FFCOE.mjs";
110
118
  import {
111
119
  LASSO_AXES_SLOT,
112
120
  LASSO_COUNT_CLASS,
@@ -133,13 +141,13 @@ import {
133
141
  lassoVerdict,
134
142
  pointInShape,
135
143
  registerLassoAction
136
- } from "./chunk-ESBH2L6E.mjs";
144
+ } from "./chunk-4DOKJ3R3.mjs";
137
145
  import {
138
146
  MIN_HIT_BAND_PX,
139
147
  censusHitBands,
140
148
  ensureCrossfilterHitTargets,
141
149
  hitBandFlag
142
- } from "./chunk-UKXGOQMS.mjs";
150
+ } from "./chunk-GQLEU3VC.mjs";
143
151
  import {
144
152
  CONTAINER_SLOT_PRESS_AT,
145
153
  DRAG_NOT_CLICK_PX,
@@ -164,7 +172,7 @@ import {
164
172
  selectionRuleOf,
165
173
  trackPress,
166
174
  validRowSet
167
- } from "./chunk-LGWTV5PB.mjs";
175
+ } from "./chunk-3ER4C7TG.mjs";
168
176
  import {
169
177
  AXIS_PIN_BAND_PAD_PX,
170
178
  AXIS_PIN_MAX_BAND_FRACTION,
@@ -193,7 +201,7 @@ import {
193
201
  svgInkReach,
194
202
  takeScroll,
195
203
  unpinScrolledAxis
196
- } from "./chunk-OSREEGBN.mjs";
204
+ } from "./chunk-TN5JRULV.mjs";
197
205
  import {
198
206
  clearGeoCache,
199
207
  geoAssetFor,
@@ -206,7 +214,7 @@ import {
206
214
  Kt,
207
215
  l,
208
216
  o
209
- } from "./chunk-SYUJ4TPX.mjs";
217
+ } from "./chunk-TH577JHI.mjs";
210
218
  import "./chunk-A2GMXZP7.mjs";
211
219
  import {
212
220
  ACTIVE_TICK_CLASS,
@@ -244,6 +252,7 @@ import {
244
252
  HOST_CONTRACT_VERSION,
245
253
  LEGEND_MARK_CLASS,
246
254
  LIFT_SELECTED_CLASS,
255
+ MARKS_INERT_CLASS,
247
256
  MARK_CLASS,
248
257
  MARK_KEY_ATTR,
249
258
  MARK_SELECTED_CLASS,
@@ -262,7 +271,7 @@ import {
262
271
  chartOwnsTimeline,
263
272
  periodTickSuppressesFeedback,
264
273
  withoutViewOnlyKeys
265
- } from "./chunk-PHUUFCJY.mjs";
274
+ } from "./chunk-USZSHKIB.mjs";
266
275
  import "./chunk-GHODWOAL.mjs";
267
276
 
268
277
  // src/hostLinks.ts
@@ -2368,6 +2377,9 @@ export {
2368
2377
  HOST_CONTAINER_CLASS,
2369
2378
  HOST_CONTRACT_VERSION,
2370
2379
  INLINE_FILTER_MIN_ROWS,
2380
+ KEYBOARD_CLASS,
2381
+ KEYBOARD_STAMP,
2382
+ WIRED_ATTR as KEYBOARD_WIRED_ATTR,
2371
2383
  LABEL_CONTRAST_CAP,
2372
2384
  LABEL_CONTRAST_DONE_ATTR,
2373
2385
  LABEL_PILL_CLASS,
@@ -2394,6 +2406,7 @@ export {
2394
2406
  LIGHT_TEXT,
2395
2407
  LIMIT_COUNT_MAX,
2396
2408
  LIVE_OPTION_KEYS,
2409
+ MARKS_INERT_CLASS,
2397
2410
  MARK_CLASS,
2398
2411
  MARK_KEY_ATTR,
2399
2412
  MARK_SELECTED_CLASS,
@@ -2487,6 +2500,7 @@ export {
2487
2500
  clearSelectionGlyphs,
2488
2501
  clickWasDrag,
2489
2502
  clippedTextFlag,
2503
+ codeHasKeyboardStamp,
2490
2504
  codeReadsDatesInLocalTime,
2491
2505
  codeReadsOption,
2492
2506
  collectD3GlyphCenters,
@@ -2537,6 +2551,7 @@ export {
2537
2551
  htmlNaturalSize,
2538
2552
  htmlToSvgDataUrl,
2539
2553
  inlineFilterGate,
2554
+ installKeyboard,
2540
2555
  installLasso,
2541
2556
  invalidSentinelReason,
2542
2557
  isBlankRender,
@@ -2630,11 +2645,14 @@ export {
2630
2645
  registerGeoAsset,
2631
2646
  registerLassoAction,
2632
2647
  relativeLuminance,
2648
+ rememberFocus,
2633
2649
  rememberScroll,
2634
2650
  requiredD3Plugins,
2635
2651
  resolveAutoColorScale,
2636
2652
  resolveOptions,
2653
+ restoreFocus,
2637
2654
  rowIdxsFromMark,
2655
+ rowNameOf,
2638
2656
  sameMarker,
2639
2657
  sameRowSet,
2640
2658
  scopeChartIds,
@@ -2,13 +2,13 @@
2
2
  import {
3
3
  chartSvgOf,
4
4
  tableHostOf
5
- } from "./chunk-FCLPMC6Q.mjs";
5
+ } from "./chunk-3EQOJHRA.mjs";
6
6
  import {
7
7
  FIT_CONTENT_SELECTOR,
8
8
  ctmScaleOf,
9
9
  isPhantomBox
10
- } from "./chunk-OSREEGBN.mjs";
11
- import "./chunk-PHUUFCJY.mjs";
10
+ } from "./chunk-TN5JRULV.mjs";
11
+ import "./chunk-USZSHKIB.mjs";
12
12
  import "./chunk-GHODWOAL.mjs";
13
13
 
14
14
  // src/legendFit/legendReconcile.ts
package/dist/page.mjs CHANGED
@@ -14,10 +14,10 @@ import {
14
14
  readControls,
15
15
  viewFromToken,
16
16
  viewToken
17
- } from "./chunk-JU2PQ22J.mjs";
18
- import "./chunk-ESBH2L6E.mjs";
19
- import "./chunk-LGWTV5PB.mjs";
20
- import "./chunk-PHUUFCJY.mjs";
17
+ } from "./chunk-QAPZIFOB.mjs";
18
+ import "./chunk-4DOKJ3R3.mjs";
19
+ import "./chunk-3ER4C7TG.mjs";
20
+ import "./chunk-USZSHKIB.mjs";
21
21
  import "./chunk-GHODWOAL.mjs";
22
22
  export {
23
23
  LINKED_HOVER_CLASS,
@@ -7,7 +7,7 @@ import {
7
7
  Si,
8
8
  W,
9
9
  bt
10
- } from "./chunk-SYUJ4TPX.mjs";
10
+ } from "./chunk-TH577JHI.mjs";
11
11
  import "./chunk-A2GMXZP7.mjs";
12
12
  import "./chunk-GHODWOAL.mjs";
13
13
 
package/dist/react.mjs CHANGED
@@ -13,25 +13,25 @@ import {
13
13
  payloadRowReader,
14
14
  viewFromToken,
15
15
  viewToken
16
- } from "./chunk-JU2PQ22J.mjs";
16
+ } from "./chunk-QAPZIFOB.mjs";
17
17
  import {
18
18
  assembleD3,
19
19
  createChartGroup,
20
20
  createChartHost,
21
21
  syncMemberSelection,
22
22
  toSourceRows
23
- } from "./chunk-MO2GR56D.mjs";
24
- import "./chunk-ESBH2L6E.mjs";
25
- import "./chunk-UKXGOQMS.mjs";
26
- import "./chunk-LGWTV5PB.mjs";
27
- import "./chunk-OSREEGBN.mjs";
23
+ } from "./chunk-JQ7FFCOE.mjs";
24
+ import "./chunk-4DOKJ3R3.mjs";
25
+ import "./chunk-GQLEU3VC.mjs";
26
+ import "./chunk-3ER4C7TG.mjs";
27
+ import "./chunk-TN5JRULV.mjs";
28
28
  import {
29
29
  geoFromCache,
30
30
  loadGeo
31
31
  } from "./chunk-5WTQN5DL.mjs";
32
- import "./chunk-SYUJ4TPX.mjs";
32
+ import "./chunk-TH577JHI.mjs";
33
33
  import "./chunk-A2GMXZP7.mjs";
34
- import "./chunk-PHUUFCJY.mjs";
34
+ import "./chunk-USZSHKIB.mjs";
35
35
  import "./chunk-GHODWOAL.mjs";
36
36
 
37
37
  // src/react.tsx
@@ -185,7 +185,11 @@ function BicChart(props) {
185
185
  // siblings of a chart that has an id in a group. This binding always
186
186
  // subscribes to the selection (for onSelect), so the host cannot count
187
187
  // subscribers here; an onSelect callback alone is not the page's filter.
188
- selectionReachesPage: () => !!reachRef.current.selects || !!(reachRef.current.group && reachRef.current.id)
188
+ selectionReachesPage: () => !!reachRef.current.selects || !!(reachRef.current.group && reachRef.current.id),
189
+ // Whether a click on a mark does anything at all: the same two,
190
+ // or an onSelect the page passed. A chart with none of them draws its marks
191
+ // inert rather than dimming the others for a click that filters nothing.
192
+ marksSelect: () => !!reachRef.current.selects || !!(reachRef.current.group && reachRef.current.id) || typeof onSelectRef.current === "function"
189
193
  });
190
194
  hostRef.current = host;
191
195
  builtAnnKeyRef.current = annKey;
package/dist/testing.mjs CHANGED
@@ -4,7 +4,7 @@ import {
4
4
  MARK_CLASS,
5
5
  MARK_SELECTED_CLASS,
6
6
  ROW_IDX_ATTR
7
- } from "./chunk-PHUUFCJY.mjs";
7
+ } from "./chunk-USZSHKIB.mjs";
8
8
  import "./chunk-GHODWOAL.mjs";
9
9
 
10
10
  // src/testing/interact.ts
@@ -40,4 +40,6 @@ export interface AnnotationLayer {
40
40
  readonly report: AnnotationReport;
41
41
  destroy(): void;
42
42
  }
43
- export declare function createAnnotationLayer(container: HTMLElement, onClick?: (a: MarkAnnotation) => void): AnnotationLayer;
43
+ export declare function createAnnotationLayer(container: HTMLElement, onClick?: (a: MarkAnnotation) => void, opts?: {
44
+ keyboard?: () => boolean;
45
+ }): AnnotationLayer;
@@ -1,5 +1,5 @@
1
1
  import type { ExportFormat, ExportTableService, SaveFileService } from "./exportTable";
2
- export declare const HOST_CONTRACT_VERSION = "1.21.0";
2
+ export declare const HOST_CONTRACT_VERSION = "1.22.0";
3
3
  export type GeoPointPrecision = "latlon" | "city" | "zip3" | "state" | "country";
4
4
  /** Every valid tier, ordered most precise → coarsest. Runtime form of GeoPointPrecision. */
5
5
  export declare const GEO_POINT_PRECISIONS: readonly GeoPointPrecision[];
@@ -90,6 +90,7 @@ export interface ViewStateProvider {
90
90
  export declare const HOST_CONTAINER_CLASS = "bic-chart-host";
91
91
  export declare const SELECTION_ACTIVE_CLASS = "lch-has-selection";
92
92
  export declare const MARK_SELECTED_CLASS = "lch-mark-selected";
93
+ export declare const MARKS_INERT_CLASS = "lch-marks-inert";
93
94
  export declare const ACTIVE_TICK_CLASS = "lch-tick-active";
94
95
  export declare const DIM_OPACITY_VAR = "--lch-dim-opacity";
95
96
  export declare const DIM_OPACITY_DEFAULT = 0.25;
@@ -196,6 +197,10 @@ export interface RenderOptions {
196
197
  selectionGlyph?: "none" | "dot" | "check";
197
198
  /** A host FACT from the generate answer: this chart's family never takes a selection glyph. */
198
199
  suppressSelectionGlyph?: boolean;
200
+ /** A host FACT: this chart works from the keyboard (its code carries the keyboard stamp), so the host has made its
201
+ * selectable marks focusable. A chart or helper drawing its own controls (a flip arrow, a sort header) gives them
202
+ * a tabindex, a role and an Enter / Space press when this is true. */
203
+ keyboardMarks?: boolean;
199
204
  animAutoPlay?: boolean;
200
205
  animPlaySpeedMs?: number;
201
206
  animLoopDelaySec?: number;
@@ -0,0 +1,12 @@
1
+ export interface FocusMemory {
2
+ sig: string;
3
+ nth: number;
4
+ start: number | null;
5
+ end: number | null;
6
+ dir: "forward" | "backward" | "none" | null;
7
+ }
8
+ /** The focused element inside the container and its caret, or null when focus is elsewhere (or on the container). */
9
+ export declare function rememberFocus(container: HTMLElement): FocusMemory | null;
10
+ /** Give focus back to the same element of the new draw, with its caret. Nothing when the chart already moved focus into
11
+ * itself, or when the new draw has no such element. Returns whether focus was restored. */
12
+ export declare function restoreFocus(container: HTMLElement, memory: FocusMemory | null): boolean;
@@ -280,6 +280,16 @@ export interface ChartHostConfig {
280
280
  * `selects` and the group itself. Never throws through: an answer that throws counts as reached.
281
281
  */
282
282
  selectionReachesPage?: () => boolean;
283
+ /**
284
+ * WHETHER A CLICK ON A MARK SELECTS ANYTHING. False: nothing listens to this chart's selection - no
285
+ * page filter, no group sibling, no callback - so its marks are drawn inert: no pointer cursor (over the chart's own
286
+ * inline one), no hover dimming of the others, and the reader's click, or a selection the chart dispatches itself,
287
+ * lights and dims nothing. A selection the page hands in (selection.highlight) still paints. Read at every click and
288
+ * every paint, so a binding added later counts at once. Absent: true, which every host but React relies on (a Power
289
+ * BI click always reaches its selection manager; an Excel click always opens a selection card). An answer that
290
+ * throws counts as true.
291
+ */
292
+ marksSelect?: () => boolean;
283
293
  }
284
294
  export interface ChartHost {
285
295
  /** Replace the notes drawn on marks (see ChartHostConfig.annotations); redraws them at once. */
@@ -1,4 +1,4 @@
1
- export { HOST_CONTRACT_VERSION, GEO_POINT_PRECISIONS, MARK_CLASS, LEGEND_MARK_CLASS, AXIS_FILTER_CLASS, ROW_IDX_ATTR, LIFT_SELECTED_CLASS, CONTROL_CLASS, NO_ROW_MARKS_ATTR, NOTE_STYLE_ATTR, SELECTION_RULE_ATTR, SELECTION_RULE_CLICKED_MARKS, MARK_KEY_ATTR, CONTAINER_SLOT_CLICKED_MARKS, SNAPSHOT_SVG_ATTR, XFILTER_REFRESH_EVENT, CONTAINER_SLOT_ANIM_STOP, CONTAINER_SLOT_XF_CLEAR, CONTAINER_SLOT_INITIAL_XF_MARK, CONTAINER_SLOT_UI_STATE, CONTAINER_SLOT_SCROLL, CONTAINER_SLOT_HOST_SELECTION, VIEW_ONLY_UI_STATE_KEYS, withoutViewOnlyKeys, HOST_CONTAINER_CLASS, SELECTION_ACTIVE_CLASS, MARK_SELECTED_CLASS, ACTIVE_TICK_CLASS, DIM_OPACITY_VAR, DIM_OPACITY_DEFAULT, DIM_OPACITY_MIN, chartOwnsTimeline, periodTickSuppressesFeedback, ANIM_PLAY_SPEED_DEFAULT, ANIM_PLAY_SPEED_MIN, ANIM_PLAY_SPEED_MAX, ANIM_LOOP_DELAY_DEFAULT, ANIM_LOOP_DELAY_MIN, ANIM_MAX_IDEAL_FRAMES_DEFAULT, ANIM_MAX_IDEAL_FRAMES_MIN, ANIM_MAX_IDEAL_FRAMES_MAX, ANIMATION_OPTION_KEYS, COLOR_SCALE_SELF_CLAMP_PCT_DEFAULT, COLOR_SCALE_SELF_CLAMP_PCT_MIN, COLOR_SCALE_SELF_CLAMP_PCT_MAX, FLIP_MODE_DEFAULT, FLIP_INTERVAL_MIN_MS, FLIP_INTERVAL_MAX_MS, FLIP_DIRECTION_DEFAULT, APPROXIMATE_POSITIONS_DEFAULT, VALUE_AXIS_BASELINE_DEFAULT, SEASONAL_MARKERS_DEFAULT, MAX_MAP_POINTS_DEFAULT, type GeoPointPrecision, type GeoMapKind, type TimelineStyle, type FlipMode, type FlipDirection, type ApproximatePositions, type ValueAxisBaseline, type SeasonalMarkers, type ColorScaleScope, type RenderOptions, type ViewStateProvider, type ChartControlChange, type XfilterRefreshDetail, type HostSelectionFact, } from "./contract";
1
+ export { HOST_CONTRACT_VERSION, GEO_POINT_PRECISIONS, MARK_CLASS, LEGEND_MARK_CLASS, AXIS_FILTER_CLASS, ROW_IDX_ATTR, LIFT_SELECTED_CLASS, CONTROL_CLASS, NO_ROW_MARKS_ATTR, NOTE_STYLE_ATTR, SELECTION_RULE_ATTR, SELECTION_RULE_CLICKED_MARKS, MARK_KEY_ATTR, CONTAINER_SLOT_CLICKED_MARKS, SNAPSHOT_SVG_ATTR, XFILTER_REFRESH_EVENT, CONTAINER_SLOT_ANIM_STOP, CONTAINER_SLOT_XF_CLEAR, CONTAINER_SLOT_INITIAL_XF_MARK, CONTAINER_SLOT_UI_STATE, CONTAINER_SLOT_SCROLL, CONTAINER_SLOT_HOST_SELECTION, VIEW_ONLY_UI_STATE_KEYS, withoutViewOnlyKeys, HOST_CONTAINER_CLASS, SELECTION_ACTIVE_CLASS, MARK_SELECTED_CLASS, MARKS_INERT_CLASS, ACTIVE_TICK_CLASS, DIM_OPACITY_VAR, DIM_OPACITY_DEFAULT, DIM_OPACITY_MIN, chartOwnsTimeline, periodTickSuppressesFeedback, ANIM_PLAY_SPEED_DEFAULT, ANIM_PLAY_SPEED_MIN, ANIM_PLAY_SPEED_MAX, ANIM_LOOP_DELAY_DEFAULT, ANIM_LOOP_DELAY_MIN, ANIM_MAX_IDEAL_FRAMES_DEFAULT, ANIM_MAX_IDEAL_FRAMES_MIN, ANIM_MAX_IDEAL_FRAMES_MAX, ANIMATION_OPTION_KEYS, COLOR_SCALE_SELF_CLAMP_PCT_DEFAULT, COLOR_SCALE_SELF_CLAMP_PCT_MIN, COLOR_SCALE_SELF_CLAMP_PCT_MAX, FLIP_MODE_DEFAULT, FLIP_INTERVAL_MIN_MS, FLIP_INTERVAL_MAX_MS, FLIP_DIRECTION_DEFAULT, APPROXIMATE_POSITIONS_DEFAULT, VALUE_AXIS_BASELINE_DEFAULT, SEASONAL_MARKERS_DEFAULT, MAX_MAP_POINTS_DEFAULT, type GeoPointPrecision, type GeoMapKind, type TimelineStyle, type FlipMode, type FlipDirection, type ApproximatePositions, type ValueAxisBaseline, type SeasonalMarkers, type ColorScaleScope, type RenderOptions, type ViewStateProvider, type ChartControlChange, type XfilterRefreshDetail, type HostSelectionFact, } from "./contract";
2
2
  export { resolveOptions, cleanHostSelection, type ResolveOptionsInput } from "./defaults";
3
3
  export { hostLinkServices, type HostLinkServices, type ClipboardLike } from "./hostLinks";
4
4
  export { tableToCsv, tableToXlsx, buildExportFile, exportFileName, sheetName as exportSheetName, excelNumberFormat, excelSerial, bytesToBase64, exportFormatsFor, exportTableService, browserSaveFile, EXPORT_FORMATS, XLSX_MIME, CSV_MIME, type ExportTable, type ExportColumn, type ExportColumnType, type ExportCell, type ExportFormat, type ExportFile, type SaveFileService, type ExportTableService, } from "./exportTable";
@@ -11,6 +11,8 @@ export { buildRenderPayload, type RenderPayload, type GeoPointBinding, type GeoB
11
11
  export { codeReadsDatesInLocalTime, vegaSpecReadsDatesInLocalTime } from "./dateCells";
12
12
  export { createChartHost, compileRenderFn, stripEsmExports, requiredD3Plugins, explainRenderFailure, sessionViewStateProvider, noopViewStateProvider, assembleD3, D3_PLUGIN_PACKAGES, type ChartHost, type ChartHostConfig, type RenderFn, type SelectionPaintReport, type SelectionChangeMeta } from "./host";
13
13
  export { ANNOTATION_CLASS, ANNOTATION_LAYER_CLASS, type MarkAnnotation, type AnnotationReport } from "./annotations";
14
+ export { KEYBOARD_STAMP, KEYBOARD_CLASS, WIRED_ATTR as KEYBOARD_WIRED_ATTR, codeHasKeyboardStamp, installKeyboard, rowNameOf, type KeyboardEnv, type KeyboardHandle } from "./keyboardMarks";
15
+ export { rememberFocus, restoreFocus, type FocusMemory } from "./focusKeep";
14
16
  export type { MarkNoteLike, NoteBadgeOptions } from "./markNotes";
15
17
  export type { Controls, ControlInfo, ControlsAttachment } from "./controls";
16
18
  export { createChartGroup, toSourceRows, syncMemberSelection, type ChartGroup, type ChartGroupMember, type ChartGroupSelection, type ChartGroupChange, type ChartGroupMemberOptions, type ChartGroupSourceOptions, type ChartGroupPayload } from "./group";
@@ -0,0 +1,43 @@
1
+ export declare const KEYBOARD_STAMP = "// bic:keyboard-marks";
2
+ /** Whether the code carries the keyboard stamp in its header. */
3
+ export declare function codeHasKeyboardStamp(code: unknown): boolean;
4
+ /** On the container while its chart works from the keyboard. */
5
+ export declare const KEYBOARD_CLASS = "lch-keyboard";
6
+ /** On each element the host made focusable, so it never touches one the chart made focusable itself. */
7
+ export declare const WIRED_ATTR = "data-lch-kb";
8
+ export interface KeyboardEnv {
9
+ container: HTMLElement;
10
+ /** The elements a click selects (the host's click selector). */
11
+ markSelector: string;
12
+ /** Elements pressed with Enter: the chart's declared controls and the host's badges. */
13
+ pressSelector: string;
14
+ /** Elements never to treat as marks (the host's own layers). */
15
+ skipSelector: string;
16
+ /** True while a click on a mark selects nothing (the marks are then not made focusable). */
17
+ inert(): boolean;
18
+ /** Select a mark as a click on it would. */
19
+ select(mark: Element, mods: {
20
+ ctrl: boolean;
21
+ shift: boolean;
22
+ }): void;
23
+ /** The reader's clear: Ctrl+Shift+C on every host (Power BI's own clear-selection key), and Escape where the host
24
+ * lets it (escapeClears). */
25
+ clear(): void;
26
+ /** Whether Escape clears. False for a host whose frame uses Escape to move focus out of the chart (Power BI), so
27
+ * leaving the chart never wipes the reader's selection there. Default true. */
28
+ escapeClears?: boolean;
29
+ isSelected(mark: Element): boolean;
30
+ /** What a screen reader calls a mark that names nothing itself. */
31
+ labelOf(mark: Element): string | null;
32
+ /** A stable key for a mark across redraws (its rows). */
33
+ keyOf(mark: Element): string;
34
+ }
35
+ export interface KeyboardHandle {
36
+ /** Re-apply tab stops, roles, names and pressed state after a draw or a selection change. */
37
+ refresh(): void;
38
+ destroy(): void;
39
+ }
40
+ /** What a screen reader calls a row: its first few values, numbers in the reader's culture, internal columns left out.
41
+ * Shared so every host names a mark the same way. */
42
+ export declare function rowNameOf(columns: ReadonlyArray<unknown>, row: ReadonlyArray<unknown> | null | undefined, culture?: string): string | null;
43
+ export declare function installKeyboard(env: KeyboardEnv): KeyboardHandle;
@@ -1,7 +1,7 @@
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
  K
4
- } from "./chunk-SYUJ4TPX.mjs";
4
+ } from "./chunk-TH577JHI.mjs";
5
5
  import "./chunk-GHODWOAL.mjs";
6
6
 
7
7
  // src/zoomMemory/zoomMemory.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bicharts/chart-host",
3
- "version": "0.6.108",
3
+ "version": "0.6.109",
4
4
  "description": "Run a BIC-generated D3 chart in any web host: compiles the generated render() function, applies the shared option defaults, resolves mark clicks (through tooltip overlays), owns the selection affordance, and translates row indices between cross-filtered charts. The same contract the BIC Power BI visual implements, minus Power BI. React bindings at @bicharts/chart-host/react.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",