@bicharts/chart-host 0.5.40 → 0.5.42

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.
@@ -112,7 +112,7 @@ function resolveOptions(p2) {
112
112
  // synced; only an explicit false switches it.
113
113
  flipSync: p2.flipSync === void 0 || p2.flipSync === null ? true : !!p2.flipSync,
114
114
  // WHERE the controls live, which is a different question from whether the cards move
115
- // together (Joel 2026-08-13: "the ability to flip individual cards shouldn't necessarily
115
+ // together (2026-08-13: "the ability to flip individual cards shouldn't necessarily
116
116
  // stop being able to flip all together too"). Resolution order matters and is the whole
117
117
  // back-compat story:
118
118
  // 1. an explicit, recognised flipMode wins;
package/dist/index.mjs CHANGED
@@ -47,7 +47,7 @@ import {
47
47
  requiredD3Plugins,
48
48
  resolveOptions,
49
49
  stripEsmExports
50
- } from "./chunk-XEVWH2RK.mjs";
50
+ } from "./chunk-BMYEYUEN.mjs";
51
51
  import "./chunk-A2GMXZP7.mjs";
52
52
 
53
53
  // src/trivial.ts
@@ -574,6 +574,37 @@ function qualifyGroupHeadingFor(row, state) {
574
574
  return null;
575
575
  }
576
576
 
577
+ // src/qualifyLaunch.ts
578
+ var qualifyPick = (chartType) => ({ kind: "pick", chartType });
579
+ var qualifyAuto = () => ({ kind: "auto" });
580
+ var qualifyCancel = () => ({ kind: "cancel" });
581
+ function launchGenerates(outcome) {
582
+ return outcome.kind === "pick" || outcome.kind === "auto";
583
+ }
584
+ function launchFavorStyle(outcome, autoSentinel) {
585
+ return outcome.kind === "pick" ? outcome.chartType : autoSentinel;
586
+ }
587
+ var CHOOSER_MIN_WIDTH_PX = 400;
588
+ var CHOOSER_MIN_HEIGHT_PX = 240;
589
+ function chooserFitsViewport(width, height) {
590
+ if (!isFinite(width) || !isFinite(height)) return false;
591
+ return width >= CHOOSER_MIN_WIDTH_PX && height >= CHOOSER_MIN_HEIGHT_PX;
592
+ }
593
+ function shouldOpenChooserOnGenerate(i) {
594
+ if (!i.enabled) return false;
595
+ if (!i.hasData) return false;
596
+ return chooserFitsViewport(i.width, i.height);
597
+ }
598
+ function canConfirmLaunch(selected) {
599
+ return typeof selected === "string" && selected.trim() !== "";
600
+ }
601
+ function confirmLaunch(selected) {
602
+ return canConfirmLaunch(selected) ? qualifyPick(String(selected).trim()) : qualifyCancel();
603
+ }
604
+ function qualifyFailureFallsOpen(viaGenerate) {
605
+ return viaGenerate === true;
606
+ }
607
+
577
608
  // src/selectionCard.ts
578
609
  var SYNTHETIC_PREFIX = "__";
579
610
  var NUMERIC_DATATYPE = /int|double|decimal|single|float|number|currency|money/i;
@@ -753,6 +784,8 @@ export {
753
784
  ANIM_PLAY_SPEED_MAX,
754
785
  ANIM_PLAY_SPEED_MIN,
755
786
  AXIS_FILTER_CLASS,
787
+ CHOOSER_MIN_HEIGHT_PX,
788
+ CHOOSER_MIN_WIDTH_PX,
756
789
  COLOR_SCALE_SELF_CLAMP_PCT_DEFAULT,
757
790
  COLOR_SCALE_SELF_CLAMP_PCT_MAX,
758
791
  COLOR_SCALE_SELF_CLAMP_PCT_MIN,
@@ -777,30 +810,40 @@ export {
777
810
  bareBase64,
778
811
  buildRenderPayload,
779
812
  buildReviewWire,
813
+ canConfirmLaunch,
780
814
  captureSvgSnapshot,
781
815
  chartOwnsTimeline,
816
+ chooserFitsViewport,
782
817
  clearGeoCache,
783
818
  compileRenderFn,
784
819
  compileTrivialSource,
785
820
  computeSelectionCard,
821
+ confirmLaunch,
786
822
  createChartHost,
787
823
  createMarkResolver,
788
824
  ensureCrossfilterHitTargets,
789
825
  explainRenderFailure,
790
826
  geoAssetFor,
791
827
  geoFromCache,
828
+ launchFavorStyle,
829
+ launchGenerates,
792
830
  loadGeo,
793
831
  newQualifyGroupState,
794
832
  normaliseAggregation,
795
833
  periodTickSuppressesFeedback,
796
834
  planTrivialChart,
835
+ qualifyAuto,
836
+ qualifyCancel,
837
+ qualifyFailureFallsOpen,
797
838
  qualifyGroupHeadingFor,
839
+ qualifyPick,
798
840
  rasterizeSvgToPngDataUrl,
799
841
  registerCityTable,
800
842
  registerGeo,
801
843
  registerGeoAsset,
802
844
  requiredD3Plugins,
803
845
  resolveOptions,
846
+ shouldOpenChooserOnGenerate,
804
847
  shouldReview,
805
848
  stripEsmExports,
806
849
  svgNaturalSize,
package/dist/react.mjs CHANGED
@@ -4,7 +4,7 @@ import {
4
4
  createChartHost,
5
5
  geoFromCache,
6
6
  loadGeo
7
- } from "./chunk-XEVWH2RK.mjs";
7
+ } from "./chunk-BMYEYUEN.mjs";
8
8
  import "./chunk-A2GMXZP7.mjs";
9
9
 
10
10
  // src/react.tsx
@@ -12,5 +12,6 @@ export { captureSvgSnapshot, svgToDataUrl, svgNaturalSize, rasterizeSvgToPngData
12
12
  export { shouldReview, buildReviewWire, bareBase64, actionFor, type ReviewGate, type ReviewWire, type ReviewVerdict, type ReviewAction } from "./review";
13
13
  export { askApplyImprovements, type ReviewDialogOptions, type ReviewDialogText } from "./reviewDialog";
14
14
  export { qualifyGroupHeadingFor, newQualifyGroupState, type QualifyGroupRow, type QualifyGroupState, type QualifyGroupHeading, } from "./qualifyGroups";
15
+ export { qualifyPick, qualifyAuto, qualifyCancel, launchGenerates, launchFavorStyle, chooserFitsViewport, shouldOpenChooserOnGenerate, canConfirmLaunch, confirmLaunch, qualifyFailureFallsOpen, CHOOSER_MIN_WIDTH_PX, CHOOSER_MIN_HEIGHT_PX, type QualifyLaunchOutcome, type ChooserGateInput, } from "./qualifyLaunch";
15
16
  export { computeSelectionCard, normaliseAggregation, type SelectionCardModel, type SelectionCardLine, type SelectionCardOptions, } from "./selectionCard";
16
17
  export { ensureCrossfilterHitTargets, type HitTargetReport } from "./hitTargets";
@@ -44,7 +44,7 @@ export interface RenderPayload {
44
44
  * reason ("only 95.5% of values are country identifiers") and no way to say it.
45
45
  *
46
46
  * Present whenever a point binding was attempted and something was refused, whether or not
47
- * a map came out of it. Joel 2026-08-02: "be sure to provide feedback in the output that
47
+ * a map came out of it. 2026-08-02: "be sure to provide feedback in the output that
48
48
  * helps call out exceptions / decisions like this, if possible."
49
49
  */
50
50
  geoPointRefused?: string[];
@@ -0,0 +1,116 @@
1
+ /** How the reader left the chooser. Three outcomes, never two - see the note above. */
2
+ export type QualifyLaunchOutcome =
3
+ /** A named type. Rides the host's explicit-pick lane for exactly one generation. */
4
+ {
5
+ kind: "pick";
6
+ chartType: string;
7
+ }
8
+ /** "Choose for me." Generates with NO preference expressed. */
9
+ | {
10
+ kind: "auto";
11
+ }
12
+ /** Backed out. Nothing is generated and no preference is recorded. */
13
+ | {
14
+ kind: "cancel";
15
+ };
16
+ export declare const qualifyPick: (chartType: string) => QualifyLaunchOutcome;
17
+ export declare const qualifyAuto: () => QualifyLaunchOutcome;
18
+ export declare const qualifyCancel: () => QualifyLaunchOutcome;
19
+ /** Does this outcome start a generation? False for cancel ALONE. */
20
+ export declare function launchGenerates(outcome: QualifyLaunchOutcome): boolean;
21
+ /**
22
+ * The value a host should put in its explicit-pick field for this outcome.
23
+ *
24
+ * GENERIC OVER THE SENTINEL BECAUSE THE HOSTS DISAGREE ABOUT WHAT "AUTO" IS SPELLED AS, and
25
+ * that disagreement is real rather than sloppy: one host's field is a nullable string whose
26
+ * null means "the picker decides", another's is a plain string whose empty value is the wire
27
+ * form. Neither can adopt the other's without changing a wire contract. So the sentinel is
28
+ * passed IN and this function only decides WHICH of the two values applies - which is the part
29
+ * that must not differ.
30
+ *
31
+ * Cancel maps to the sentinel too, defensively: a caller that ignores `launchGenerates` and
32
+ * reads this anyway gets "no preference" rather than a stale pick. It should still not generate.
33
+ */
34
+ export declare function launchFavorStyle<T>(outcome: QualifyLaunchOutcome, autoSentinel: T): string | T;
35
+ /**
36
+ * THE SMALLEST TILE THE CHOOSER IS USABLE IN, measured rather than guessed (2026-09-01).
37
+ *
38
+ * A sweep of 168 tile sizes in headless Chromium, rendering the real card DOM and CSS, asking
39
+ * three questions of each: do the three launch buttons sit on one line inside the card, are at
40
+ * least two list rows visible, and is nothing in the pinned chrome clipped. The measured floor
41
+ * is 320 x 220 - but the two dimensions are COUPLED, because under 400 px the card's title
42
+ * wraps to a second line and eats the list's height: at 320-340 wide the height floor rises to
43
+ * 280, at 360-380 to 260, and only at 400+ does 220 hold.
44
+ *
45
+ * 400 x 240 is that envelope with one sweep step of margin on the height. The margin is not
46
+ * decoration: the measurement used one font stack and one string, and every host localizes the
47
+ * title, so a longer translation wraps sooner than the sample did.
48
+ *
49
+ * ERRING HIGH IS THE CHEAP DIRECTION. Below the floor the host generates immediately, which is
50
+ * the behaviour it had before the chooser existed - so a tile wrongly judged too small costs a
51
+ * feature, while one wrongly judged big enough costs a dialog whose buttons are off the card.
52
+ */
53
+ export declare const CHOOSER_MIN_WIDTH_PX = 400;
54
+ export declare const CHOOSER_MIN_HEIGHT_PX = 240;
55
+ /**
56
+ * Is there room to DRAW the chooser here?
57
+ *
58
+ * MEASURE THE REAL SURFACE, never a size the host reports outward. Where a host lets an author
59
+ * state a viewport for generation purposes, that stated size is a claim about a tile that does
60
+ * not exist yet; the dialog has to fit the pixels actually on screen.
61
+ */
62
+ export declare function chooserFitsViewport(width: number, height: number): boolean;
63
+ export interface ChooserGateInput {
64
+ /** The host's opt-out setting. False = the reader wants one-click Generate back. */
65
+ enabled: boolean;
66
+ /** Real drawable width, in CSS pixels. */
67
+ width: number;
68
+ /** Real drawable height, in CSS pixels. */
69
+ height: number;
70
+ /**
71
+ * Is there anything to qualify? A chooser over an empty binding can only say "bind
72
+ * something first", which the host's own Generate path already says better and without
73
+ * a round-trip.
74
+ */
75
+ hasData: boolean;
76
+ }
77
+ /**
78
+ * Should pressing Generate open the chooser instead of generating?
79
+ *
80
+ * FALSE IS ALWAYS SAFE and that is the design: every false answer here lands on the behaviour
81
+ * the host had before this feature existed - press Generate, get a chart. So the gate can be
82
+ * conservative without stranding anybody, and a new reason to decline can be added later
83
+ * without auditing what it breaks.
84
+ */
85
+ export declare function shouldOpenChooserOnGenerate(i: ChooserGateInput): boolean;
86
+ /**
87
+ * Is the confirm button live?
88
+ *
89
+ * A SEPARATE QUESTION FROM "is a type selected", because it is the answer to a UI state and the
90
+ * host asks it on every selection change. Kept here so both hosts agree that selecting a row is
91
+ * what arms confirm - the alternative, arming it always and validating on click, produces a
92
+ * button that looks ready and then refuses, which is the interaction this flow replaced.
93
+ */
94
+ export declare function canConfirmLaunch(selected: string | null | undefined): boolean;
95
+ /**
96
+ * The outcome of pressing confirm with `selected` highlighted.
97
+ *
98
+ * Returns CANCEL rather than auto when nothing is selected, and the choice matters: confirm
99
+ * with an empty selection is a state the UI should not allow, so if it happens the honest
100
+ * reading is "this click means nothing", not "this click means spend a credit". The host's
101
+ * disabled state is the first guard; this is the second.
102
+ */
103
+ export declare function confirmLaunch(selected: string | null | undefined): QualifyLaunchOutcome;
104
+ /**
105
+ * WHAT A FAILED QUALIFY MEANS depends on which door the reader came through, and this is the
106
+ * one place that says so.
107
+ *
108
+ * Opened from Generate, the reader asked for a CHART: the chooser is an offer along the way, so
109
+ * when qualify errors or times out the flow proceeds to generate and the degradation is logged
110
+ * rather than shown. Opened from a "what fits?" affordance, the reader asked a QUESTION: failing
111
+ * open there would spend a credit nobody requested, so the error is shown and nothing runs.
112
+ *
113
+ * This is the whole reason the two doors are distinguishable at all. Everything else about them
114
+ * - the list, the buttons, the outcomes - is deliberately identical.
115
+ */
116
+ export declare function qualifyFailureFallsOpen(viaGenerate: boolean): boolean;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bicharts/chart-host",
3
- "version": "0.5.40",
3
+ "version": "0.5.42",
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",