@bicharts/chart-host 0.5.41 → 0.5.43

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -574,6 +574,40 @@ 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 shouldOpenInlineChooserOnGenerate(i) {
599
+ return i.enabled === true && i.hasData === true;
600
+ }
601
+ function canConfirmLaunch(selected) {
602
+ return typeof selected === "string" && selected.trim() !== "";
603
+ }
604
+ function confirmLaunch(selected) {
605
+ return canConfirmLaunch(selected) ? qualifyPick(String(selected).trim()) : qualifyCancel();
606
+ }
607
+ function qualifyFailureFallsOpen(viaGenerate) {
608
+ return viaGenerate === true;
609
+ }
610
+
577
611
  // src/selectionCard.ts
578
612
  var SYNTHETIC_PREFIX = "__";
579
613
  var NUMERIC_DATATYPE = /int|double|decimal|single|float|number|currency|money/i;
@@ -753,6 +787,8 @@ export {
753
787
  ANIM_PLAY_SPEED_MAX,
754
788
  ANIM_PLAY_SPEED_MIN,
755
789
  AXIS_FILTER_CLASS,
790
+ CHOOSER_MIN_HEIGHT_PX,
791
+ CHOOSER_MIN_WIDTH_PX,
756
792
  COLOR_SCALE_SELF_CLAMP_PCT_DEFAULT,
757
793
  COLOR_SCALE_SELF_CLAMP_PCT_MAX,
758
794
  COLOR_SCALE_SELF_CLAMP_PCT_MIN,
@@ -777,30 +813,41 @@ export {
777
813
  bareBase64,
778
814
  buildRenderPayload,
779
815
  buildReviewWire,
816
+ canConfirmLaunch,
780
817
  captureSvgSnapshot,
781
818
  chartOwnsTimeline,
819
+ chooserFitsViewport,
782
820
  clearGeoCache,
783
821
  compileRenderFn,
784
822
  compileTrivialSource,
785
823
  computeSelectionCard,
824
+ confirmLaunch,
786
825
  createChartHost,
787
826
  createMarkResolver,
788
827
  ensureCrossfilterHitTargets,
789
828
  explainRenderFailure,
790
829
  geoAssetFor,
791
830
  geoFromCache,
831
+ launchFavorStyle,
832
+ launchGenerates,
792
833
  loadGeo,
793
834
  newQualifyGroupState,
794
835
  normaliseAggregation,
795
836
  periodTickSuppressesFeedback,
796
837
  planTrivialChart,
838
+ qualifyAuto,
839
+ qualifyCancel,
840
+ qualifyFailureFallsOpen,
797
841
  qualifyGroupHeadingFor,
842
+ qualifyPick,
798
843
  rasterizeSvgToPngDataUrl,
799
844
  registerCityTable,
800
845
  registerGeo,
801
846
  registerGeoAsset,
802
847
  requiredD3Plugins,
803
848
  resolveOptions,
849
+ shouldOpenChooserOnGenerate,
850
+ shouldOpenInlineChooserOnGenerate,
804
851
  shouldReview,
805
852
  stripEsmExports,
806
853
  svgNaturalSize,
@@ -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, shouldOpenInlineChooserOnGenerate, 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";
@@ -0,0 +1,136 @@
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
+ * The same gate for a host whose chooser is an INLINE, SCROLLING PANEL rather than a modal card
88
+ * - and it deliberately has no size clause at all.
89
+ *
90
+ * THE ASYMMETRY IS THE POINT, AND IT IS MEASURED RATHER THAN ASSUMED. The modal above lives in
91
+ * a card that CLIPS, so past a certain smallness its buttons are simply not on screen and the
92
+ * reader is trapped. A panel that flows inside a scrolling pane, with a wrapping button row,
93
+ * cannot reach that state: swept across 63 pane sizes down to 200x120, every one stayed usable
94
+ * - the footer wrapped from one line to three (38px to 86px) and stayed inside the panel, and
95
+ * the list kept four or more rows because it carries its own max-height and scrolls.
96
+ *
97
+ * So a size clause here would not protect anybody; it would only switch the feature off for a
98
+ * default task pane, which is narrower than the modal's floor. Two hosts, two layouts, two
99
+ * honest answers - written next to each other so the difference reads as a decision rather than
100
+ * as one of them having forgotten.
101
+ */
102
+ export declare function shouldOpenInlineChooserOnGenerate(i: {
103
+ enabled: boolean;
104
+ hasData: boolean;
105
+ }): boolean;
106
+ /**
107
+ * Is the confirm button live?
108
+ *
109
+ * A SEPARATE QUESTION FROM "is a type selected", because it is the answer to a UI state and the
110
+ * host asks it on every selection change. Kept here so both hosts agree that selecting a row is
111
+ * what arms confirm - the alternative, arming it always and validating on click, produces a
112
+ * button that looks ready and then refuses, which is the interaction this flow replaced.
113
+ */
114
+ export declare function canConfirmLaunch(selected: string | null | undefined): boolean;
115
+ /**
116
+ * The outcome of pressing confirm with `selected` highlighted.
117
+ *
118
+ * Returns CANCEL rather than auto when nothing is selected, and the choice matters: confirm
119
+ * with an empty selection is a state the UI should not allow, so if it happens the honest
120
+ * reading is "this click means nothing", not "this click means spend a credit". The host's
121
+ * disabled state is the first guard; this is the second.
122
+ */
123
+ export declare function confirmLaunch(selected: string | null | undefined): QualifyLaunchOutcome;
124
+ /**
125
+ * WHAT A FAILED QUALIFY MEANS depends on which door the reader came through, and this is the
126
+ * one place that says so.
127
+ *
128
+ * Opened from Generate, the reader asked for a CHART: the chooser is an offer along the way, so
129
+ * when qualify errors or times out the flow proceeds to generate and the degradation is logged
130
+ * rather than shown. Opened from a "what fits?" affordance, the reader asked a QUESTION: failing
131
+ * open there would spend a credit nobody requested, so the error is shown and nothing runs.
132
+ *
133
+ * This is the whole reason the two doors are distinguishable at all. Everything else about them
134
+ * - the list, the buttons, the outcomes - is deliberately identical.
135
+ */
136
+ 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.41",
3
+ "version": "0.5.43",
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",