@bicharts/chart-host 0.6.83 → 0.6.85

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/page.mjs CHANGED
@@ -14,7 +14,10 @@ import {
14
14
  readControls,
15
15
  viewFromToken,
16
16
  viewToken
17
- } from "./chunk-XIAL6EEC.mjs";
17
+ } from "./chunk-DTOGTKAJ.mjs";
18
+ import "./chunk-QIRLWJBI.mjs";
19
+ import "./chunk-IURROJT7.mjs";
20
+ import "./chunk-MR6TL7QZ.mjs";
18
21
  import "./chunk-GHODWOAL.mjs";
19
22
  export {
20
23
  LINKED_HOVER_CLASS,
package/dist/react.mjs CHANGED
@@ -13,7 +13,7 @@ import {
13
13
  payloadRowReader,
14
14
  viewFromToken,
15
15
  viewToken
16
- } from "./chunk-XIAL6EEC.mjs";
16
+ } from "./chunk-DTOGTKAJ.mjs";
17
17
  import {
18
18
  assembleD3,
19
19
  createChartGroup,
@@ -22,11 +22,14 @@ import {
22
22
  loadGeo,
23
23
  syncMemberSelection,
24
24
  toSourceRows
25
- } from "./chunk-CJNONH6V.mjs";
26
- import "./chunk-2XWE6YFO.mjs";
27
- import "./chunk-MGAO2DR7.mjs";
25
+ } from "./chunk-CDWNZHIN.mjs";
26
+ import "./chunk-QIRLWJBI.mjs";
27
+ import "./chunk-2ZUUIZWL.mjs";
28
+ import "./chunk-7HKVV5T5.mjs";
29
+ import "./chunk-IURROJT7.mjs";
30
+ import "./chunk-BQFG5HWZ.mjs";
28
31
  import "./chunk-A2GMXZP7.mjs";
29
- import "./chunk-7JWL6LYE.mjs";
32
+ import "./chunk-MR6TL7QZ.mjs";
30
33
  import "./chunk-GHODWOAL.mjs";
31
34
 
32
35
  // src/react.tsx
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-7JWL6LYE.mjs";
7
+ } from "./chunk-MR6TL7QZ.mjs";
8
8
  import "./chunk-GHODWOAL.mjs";
9
9
 
10
10
  // src/testing/interact.ts
@@ -1,4 +1,4 @@
1
- export declare const HOST_CONTRACT_VERSION = "1.14.0";
1
+ export declare const HOST_CONTRACT_VERSION = "1.16.0";
2
2
  export type GeoPointPrecision = "latlon" | "city" | "zip3" | "state" | "country";
3
3
  /** Every valid tier, ordered most precise → coarsest. Runtime form of GeoPointPrecision. */
4
4
  export declare const GEO_POINT_PRECISIONS: readonly GeoPointPrecision[];
@@ -19,6 +19,18 @@ export declare const MARK_KEY_ATTR = "data-mark-key";
19
19
  export declare const NO_ROW_MARKS_ATTR = "data-lch-no-row-marks";
20
20
  export declare const SNAPSHOT_SVG_ATTR = "data-lch-snapshot";
21
21
  export declare const XFILTER_REFRESH_EVENT = "llm-xfilter-refresh";
22
+ /** What the event carries. `source` names who selected ("user" when a reader's gesture did); the host
23
+ * passes it on to its subscribers, and "chart" stands in when it is absent. */
24
+ export type XfilterRefreshDetail = {
25
+ clear: true;
26
+ source?: string;
27
+ } | {
28
+ mark: Element;
29
+ source?: string;
30
+ } | {
31
+ rows: readonly number[];
32
+ source?: string;
33
+ };
22
34
  export declare const CONTAINER_SLOT_ANIM_STOP = "__llmAnimStop";
23
35
  export declare const CONTAINER_SLOT_XF_CLEAR = "__llmXfClear";
24
36
  export declare const CONTAINER_SLOT_INITIAL_XF_MARK = "__llmInitialXfMark";
@@ -109,6 +121,9 @@ export interface RenderOptions {
109
121
  rolesRefused?: string[];
110
122
  } | null;
111
123
  dateCellsAreUtcDays?: boolean;
124
+ dataComplete?: boolean;
125
+ rowsWithheld?: number;
126
+ rowsFilteredOut?: number;
112
127
  geoPointDest?: {
113
128
  precision: GeoPointPrecision | null;
114
129
  precisionCounts: Record<GeoPointPrecision, number>;
@@ -178,6 +193,25 @@ export interface RenderOptions {
178
193
  * take the reader's gesture with it: the helper swallows anything this raises.
179
194
  */
180
195
  onControlChange?: (e: ChartControlChange) => void;
196
+ /**
197
+ * THE HOST LASSO (contract 1.16.0): whether this chart gets the gesture that draws around marks and selects the
198
+ * ones inside, and what the host does when it ends. A chart never draws the gesture; it hears it as an
199
+ * `llm-lasso` event on its container.
200
+ * capable - the capability the SERVER sent with this chart (the chart's type declares that it has no drag
201
+ * gesture of its own and that a lasso means something for it). ABSENT MEANS NOT CAPABLE: no host
202
+ * holds a list of chart types, so a chart saved before the capability existed has none.
203
+ * enabled - the reader's setting; on unless it is exactly false. Both must hold for a surface to be drawn.
204
+ * rowCap - the most rows in view that still get a lasso; above it the whole lasso is off and a caption says
205
+ * so. Absent is LASSO_ROW_CAP (provisional).
206
+ * actions - what the host runs on release, in order, by name. Absent is ["select"]; a name the host does
207
+ * not know is skipped, so a list can be sent ahead of the build that understands it.
208
+ */
209
+ lasso?: {
210
+ enabled?: boolean;
211
+ rowCap?: number;
212
+ actions?: string[];
213
+ capable?: boolean;
214
+ };
181
215
  }
182
216
  /** One reader gesture on a chart's own control. */
183
217
  export interface ChartControlChange {
@@ -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, 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, VIEW_ONLY_UI_STATE_KEYS, withoutViewOnlyKeys, HOST_CONTAINER_CLASS, SELECTION_ACTIVE_CLASS, MARK_SELECTED_CLASS, ACTIVE_TICK_CLASS, DIM_OPACITY_VAR, DIM_OPACITY_DEFAULT, 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, } 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, 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, VIEW_ONLY_UI_STATE_KEYS, withoutViewOnlyKeys, HOST_CONTAINER_CLASS, SELECTION_ACTIVE_CLASS, MARK_SELECTED_CLASS, ACTIVE_TICK_CLASS, DIM_OPACITY_VAR, DIM_OPACITY_DEFAULT, 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, } from "./contract";
2
2
  export { resolveOptions, type ResolveOptionsInput } from "./defaults";
3
3
  export { OPTIONS_VOCABULARY, LIVE_OPTION_KEYS, MAX_MAP_POINTS_MIN, MAX_MAP_POINTS_MAX, LIMIT_COUNT_MAX, wireLimitCount, codeReadsOption, type OptionVocabularyEntry, type OptionTiming, type OptionWire, type OptionKey, } from "./optionsVocabulary";
4
4
  export { loadGeo, geoFromCache, registerGeo, registerGeoAsset, geoAssetFor, clearGeoCache, type GeoAssetName, } from "./geoLazy";
@@ -13,14 +13,15 @@ export type { MarkNoteLike, NoteBadgeOptions } from "./markNotes";
13
13
  export type { Controls, ControlInfo, ControlsAttachment } from "./controls";
14
14
  export { createChartGroup, toSourceRows, syncMemberSelection, type ChartGroup, type ChartGroupMember, type ChartGroupSelection, type ChartGroupChange, type ChartGroupMemberOptions, type ChartGroupSourceOptions, type ChartGroupPayload } from "./group";
15
15
  export type { Filter, FilterScope, FilterOptions, FilterKey, FilterChangeReason, FilterBinding, BindFilterOptions, VegaInteractionEvent, PageView } from "./filterScope";
16
- export { createMarkResolver, isInsideControl, rowIdxsFromMark, nextSelection, selectionRuleOf, markKeyOf, planSelectionPaint, nextClickedMarks, sameRowSet, type SelectionRule, type ClickedMarks, type SelectionPaintMark, type SelectionPaintMode, type SelectionPaintPlan, collectD3GlyphCenters, clearSelectionGlyphs, paintSelectionGlyphs, SELECTION_GLYPH_CLASS, type MarkResolver, type MarkResolverEnv, type GlyphCenter, type SelectionGlyphStyle, type SelectionModifiers, } from "./selection";
16
+ export { createMarkResolver, isInsideControl, rowIdxsFromMark, nextSelection, DRAG_NOT_CLICK_PX, gestureWasDrag, trackPress, pressOf, clearPress, clickWasDrag, CONTAINER_SLOT_PRESS_AT, type PressPoint, dispatchRowSet, validRowSet, ROW_SET_MAX, selectionRuleOf, markKeyOf, planSelectionPaint, nextClickedMarks, sameRowSet, type SelectionRule, type ClickedMarks, type SelectionPaintMark, type SelectionPaintMode, type SelectionPaintPlan, collectD3GlyphCenters, clearSelectionGlyphs, paintSelectionGlyphs, SELECTION_GLYPH_CLASS, type MarkResolver, type MarkResolverEnv, type GlyphCenter, type SelectionGlyphStyle, type SelectionModifiers, } from "./selection";
17
+ export { installLasso, clearLasso, lassoVerdict, lassoRowCap, lassoActions, registerLassoAction, pointInShape, LASSO_ROW_CAP, LASSO_DRAG_PX, LASSO_SAMPLE_PX, LASSO_MAX_VERTICES, LASSO_RESTORE_MAX_VERTICES, LASSO_RESTORE_MAX_SHAPES, LASSO_EVENT, LASSO_SURFACE_CLASS, LASSO_OUTLINE_CLASS, LASSO_COUNT_CLASS, LASSO_NOTE_CLASS, LASSO_PLOT_CLASS, LASSO_MODE_ATTR, LASSO_KNOB_KEY, LASSO_AXES_SLOT, LASSO_DEFAULT_ACTIONS, type LassoSetting, type LassoVerdict, type LassoMode, type LassoSpace, type LassoShape, type LassoStored, type LassoEventDetail, type LassoAxes, type LassoAction, type LassoActionContext, type LassoEnv, type LassoHandle, } from "./lasso";
17
18
  export type { AdapterEvent, AdapterHit, AdapterId, AdapterScene, MarkHandle, MarkInfo, MarkRole, PaintRequest, RendererAdapter, RowTable, } from "./adapters/types";
18
19
  export { planTrivialChart, compileTrivialSource, type TrivialPlan, type TrivialShapeKind } from "./trivial";
19
20
  export { captureSvgSnapshot, svgToDataUrl, svgNaturalSize, rasterizeSvgToPngDataUrl, tableHostOf, chartSvgOf, htmlToSvgDataUrl, htmlNaturalSize, type SnapshotOptions } from "./snapshot";
20
21
  export { shouldReview, buildReviewWire, bareBase64, actionFor, type ReviewGate, type ReviewWire, type ReviewVerdict, type ReviewAction } from "./review";
21
22
  export { askApplyImprovements, type ReviewDialogOptions, type ReviewDialogText } from "./reviewDialog";
22
23
  export { mountProductFeedbackBox, normaliseProductFeedbackText, productFeedbackSendable, buildProductFeedbackPayload, PRODUCT_FEEDBACK_MAX_CHARS, PRODUCT_FEEDBACK_MIN_CHARS, type ProductFeedbackBox, type ProductFeedbackBoxOptions, type ProductFeedbackText, type ProductFeedbackSendResult, type ProductFeedbackPayload, type ProductFeedbackIdentity, type ProductFeedbackHost, } from "./productFeedback";
23
- export { qualifyGroupHeadingFor, newQualifyGroupState, type QualifyGroupRow, type QualifyGroupState, type QualifyGroupHeading, orderRefusalsForDisplay, refusalIsSelectable, hasRefusalsToShow, qualifyRefusalHeadingFor, newQualifyRefusalGroupState, qualifyRefusalReason, QUALIFY_REFUSAL_UNSPECIFIED, type QualifyRefusalRow, type QualifyRefusalGroupState, type QualifyRefusalHeading, } from "./qualifyGroups";
24
+ export { qualifyGroupHeadingFor, newQualifyGroupState, type QualifyGroupRow, type QualifyGroupState, type QualifyGroupHeading, orderRefusalsForDisplay, refusalIsSelectable, hasRefusalsToShow, qualifyRefusalHeadingFor, newQualifyRefusalGroupState, qualifyRefusalReason, QUALIFY_REFUSAL_UNSPECIFIED, refusalIsTileBound, QUALIFY_TILE_REFUSAL_CODES, QUALIFY_TOO_SMALL_HEADING, type QualifyRefusalRow, type QualifyRefusalGroupState, type QualifyRefusalHeading, } from "./qualifyGroups";
24
25
  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";
25
26
  export { normalizeFilterTerm, filterQualifyRows, readQualifyChartRow, readQualifyRefusalRow, DEFAULT_FILTER_MATCH_STYLE, filterMatchStyleOf, type FilterMatchStyle, filterFitsChooser, listNeedsFilter, qualifyFilterGate, inlineFilterGate, FILTER_MIN_TERM_CHARS, FILTER_ROW_PX, FILTER_MIN_WIDTH_PX, FILTER_MIN_HEIGHT_PX, INLINE_FILTER_MIN_ROWS, computeQualifyFilterView, qualifyFilterCountText, type QualifyFilterTier, type QualifyFilterResult, type QualifyFilterRead, type QualifyFilterGateReason, type QualifyFilterGateInput, type QualifyFilterRow, type QualifyFilterGroup, type QualifyFilterView, type QualifyFilterNoteKind, } from "./qualifyFilter";
26
27
  export { qualifyTableRows, type QualifyTableRow, type QualifyTableSource } from "./qualifyTable";
@@ -0,0 +1,143 @@
1
+ import { type RenderOptions } from "./contract";
2
+ /**
3
+ * THE MOST ROWS A CHART MAY HAVE IN VIEW AND STILL OFFER A LASSO.
4
+ *
5
+ * PROVISIONAL. Unmeasured: 5,000 is a stand-in until the time Power BI Desktop takes to apply a selection
6
+ * of that many ids is measured on a release-test page (1,000, 5,000 and 13,000 ids), and that number
7
+ * replaces this one. Do not read it as a finding. It is the worst case that is allowed for, not the
8
+ * typical one: a reader can lasso everything.
9
+ *
10
+ * Above it the WHOLE lasso is off: no surface is installed, and a one-line caption says why. It is one
11
+ * number for every host; a host may pass its own through RenderOptions.lasso.rowCap.
12
+ */
13
+ export declare const LASSO_ROW_CAP = 5000;
14
+ /** Travel, in px on either axis, at which a press stops being a click and becomes a lasso. */
15
+ export declare const LASSO_DRAG_PX = 5;
16
+ /** A freeform point is kept only when it is this far from the last one kept. */
17
+ export declare const LASSO_SAMPLE_PX = 3;
18
+ /** A freeform shape longer than this is thinned before it is used, so what is published, stored and
19
+ * restored is one and the same shape. */
20
+ export declare const LASSO_MAX_VERTICES = 400;
21
+ /** What a restore will read, per shape and in all: a bag is untrusted, and a poisoned one must not be able
22
+ * to ask for unbounded work. */
23
+ export declare const LASSO_RESTORE_MAX_VERTICES = 2000;
24
+ export declare const LASSO_RESTORE_MAX_SHAPES = 32;
25
+ /** The DOM event a chart listens for on its container. detail is a LassoEventDetail. */
26
+ export declare const LASSO_EVENT = "llm-lasso";
27
+ export declare const LASSO_SURFACE_CLASS = "lch-lasso-surface";
28
+ export declare const LASSO_OUTLINE_CLASS = "lch-lasso-outline";
29
+ export declare const LASSO_COUNT_CLASS = "lch-lasso-count";
30
+ export declare const LASSO_NOTE_CLASS = "lch-lasso-note";
31
+ /** The group a chart draws its plot in; the surface goes over it. Without one the chart's svg is the plot. */
32
+ export declare const LASSO_PLOT_CLASS = "lch-plot";
33
+ /** Set on the container to switch to a rectangle ("rect"); anything else is freeform. */
34
+ export declare const LASSO_MODE_ATTR = "data-lch-lasso-mode";
35
+ /** The key in the shared knob bag. */
36
+ export declare const LASSO_KNOB_KEY = "lasso";
37
+ /** A chart registers { toData([x, y]), toScreen([dx, dy]) } here so the shape is stored in data coordinates.
38
+ * x and y are in the user space of the element the surface sits in (the plot group). */
39
+ export declare const LASSO_AXES_SLOT = "__lchLassoAxes";
40
+ /** The action list a host runs when none is configured. */
41
+ export declare const LASSO_DEFAULT_ACTIONS: readonly string[];
42
+ /** RenderOptions.lasso. `capable` is the capability the server sent with this chart (absent means not
43
+ * capable); `enabled` is the reader's setting (default on); both must hold. */
44
+ export type LassoSetting = NonNullable<RenderOptions["lasso"]>;
45
+ /** "on" installs a surface. The others say why not; only "over-cap" shows a caption. */
46
+ export type LassoVerdict = "on" | "not-capable" | "disabled" | "over-cap";
47
+ /** The row cap in force: the setting's when it is a positive number, else LASSO_ROW_CAP. */
48
+ export declare function lassoRowCap(setting: LassoSetting | null | undefined): number;
49
+ /** The action names to run on release, in order. Anything that is not a list of names is the default. */
50
+ export declare function lassoActions(setting: LassoSetting | null | undefined): string[];
51
+ /** Whether a chart with `rows` rows in view gets a lasso under `setting`. */
52
+ export declare function lassoVerdict(setting: LassoSetting | null | undefined, rows: number): LassoVerdict;
53
+ type Pt = [number, number];
54
+ export type LassoMode = "free" | "rect";
55
+ export type LassoSpace = "data" | "unit";
56
+ /** One drawn shape as stored: a freeform polygon, or the four corners of a rectangle. */
57
+ export interface LassoShape {
58
+ mode: LassoMode;
59
+ pts: Pt[];
60
+ }
61
+ /** What the knob bag holds under LASSO_KNOB_KEY. */
62
+ export interface LassoStored {
63
+ v: 1;
64
+ space: LassoSpace;
65
+ shapes: LassoShape[];
66
+ }
67
+ /** detail of the `llm-lasso` DOM event. `polygon` is the last shape drawn, `polygons` every shape in the
68
+ * lasso, both in `space` (data coordinates, or fractions of the plot box); `rows` is empty when the lasso
69
+ * was cleared. source: "user" a gesture or a clear, "restore" a lasso re-applied on draw, "host" a clear the
70
+ * host made because the selection was replaced from outside. */
71
+ export interface LassoEventDetail {
72
+ rows: number[];
73
+ polygon: Pt[];
74
+ polygons: Pt[][];
75
+ mode: LassoMode;
76
+ space: LassoSpace;
77
+ source: "user" | "restore" | "host";
78
+ }
79
+ export interface LassoAxes {
80
+ toData(p: [number, number]): ArrayLike<unknown>;
81
+ toScreen(p: [number, number]): ArrayLike<unknown>;
82
+ }
83
+ /** Point in a shape: the bounding box rejects first; a rectangle is then done, and a freeform polygon is
84
+ * an even-odd ray cast. Implemented here so the host takes no dependency on a geometry library. */
85
+ export declare function pointInShape(shape: {
86
+ mode: LassoMode;
87
+ pts: ReadonlyArray<{
88
+ x: number;
89
+ y: number;
90
+ }>;
91
+ }, x: number, y: number): boolean;
92
+ /** What an action is given on release. */
93
+ export interface LassoActionContext {
94
+ /** The rows inside the lasso (every shape of it), ascending. */
95
+ rows: number[];
96
+ mode: LassoMode;
97
+ source: "user";
98
+ env: LassoEnv;
99
+ }
100
+ export type LassoAction = (ctx: LassoActionContext) => void;
101
+ /**
102
+ * Make `name` available to the action list (RenderOptions.lasso.actions). A name in the list that nothing
103
+ * registered is skipped, so a host can carry a list ahead of the build that understands it.
104
+ */
105
+ export declare function registerLassoAction(name: string, action: LassoAction): void;
106
+ /** What the host gives the lasso. The lasso never reaches into the host's selection itself. */
107
+ export interface LassoEnv {
108
+ container: HTMLElement;
109
+ /** The options of the render now on screen (a function, because setOptions replaces them). */
110
+ options: () => RenderOptions;
111
+ /** How many rows the chart was handed: the number the cap is measured against. */
112
+ rows: number;
113
+ /** Publish this row set as the selection (what the "select" action does). */
114
+ select(rows: number[]): void;
115
+ /** Clear the host's selection the way an empty click does: the chart's own clear slot first when the
116
+ * chart owns the selection, then a published clear. A no-op when nothing is selected. */
117
+ clearSelection(): void;
118
+ }
119
+ export interface LassoHandle {
120
+ /** The surface the gesture is read on; null when the lasso is off at this row count. */
121
+ readonly surface: Element | null;
122
+ /** The rows inside the lasso now drawn, ascending; empty when none. */
123
+ rows(): number[];
124
+ /** Erase the lasso. By default it also clears the host's selection and says so with `llm-lasso`. */
125
+ clear(opts?: {
126
+ publish?: boolean;
127
+ source?: "user" | "host";
128
+ }): void;
129
+ /** Remove the listeners and everything the lasso drew. Nothing is persisted. */
130
+ destroy(): void;
131
+ }
132
+ /**
133
+ * Put the lasso on the chart in `env.container`, which has just drawn. Returns null when the chart gets none
134
+ * (not capable, switched off, nothing to draw on); a handle for the caption when the rows exceed the cap;
135
+ * otherwise the live handle. A lasso already installed on the container is destroyed first.
136
+ */
137
+ export declare function installLasso(env: LassoEnv): LassoHandle | null;
138
+ /** Erase the lasso on `container`, if it has one, and clear the selection with it. */
139
+ export declare function clearLasso(container: Element | null | undefined, opts?: {
140
+ publish?: boolean;
141
+ source?: "user" | "host";
142
+ }): void;
143
+ export {};
@@ -0,0 +1,89 @@
1
+ /**
2
+ * Frame fit — pure geometry.
3
+ *
4
+ * The third measured sibling of the label-contrast and legend-reconcile passes.
5
+ * Generated code routinely parks a footer/source caption against the
6
+ * RAW svg edge — `svg.append('text').attr('y', height - 2)` — instead of reserving
7
+ * a band for it inside `margin.bottom`. That looks right in the code and is wrong
8
+ * on screen: a baseline is not an ink box. A glyph descends roughly 0.27 * fontSize
9
+ * BELOW its baseline, so a 2px gap at font-size 7.5 puts the tails of "g", "p", "y"
10
+ * exactly on (or past) the frame, and `.lch-d3ctr` is `overflow:hidden` — the host
11
+ * silently slices them. Measured live on a Marimekko (690x340): the
12
+ * caption's ink box ran 331->340 in a 340-tall frame, i.e. flush, tails gone.
13
+ *
14
+ * Corpus: 41 D3 generations across ~20 chart types park text within 5 user units of
15
+ * the frame, so this is a RENDERER-level habit, not one chart's bug.
16
+ *
17
+ * WHY EXTEND THE VIEWBOX RATHER THAN MOVE THE TEXT: nudging a glyph back inside
18
+ * can shove it into whatever sits above it — in that Marimekko the axis title's ink
19
+ * ends 12 units above the caption, so a blind 6-unit lift would collide the two
20
+ * and trade a clipped line for an overlapping one. Growing the viewBox instead
21
+ * makes the SVG scale to fit its container: nothing moves relative to anything
22
+ * else, nothing is clipped, and a 2-unit grow on a 340-unit frame is a 0.6%
23
+ * rescale nobody can see. This is exactly the remedy the legend-reconcile pass already
24
+ * uses for a legend that runs off the right edge.
25
+ *
26
+ * Coordinates: EVERYTHING here is in the SVG's USER space (== viewBox units). The
27
+ * caller maps measured client-px boxes through `svg.getScreenCTM().inverse()`
28
+ * first, which captures viewBox, preserveAspectRatio, container scaling AND host
29
+ * page zoom in one matrix — so this module needs no px<->unit factor. Same
30
+ * contract as legendReconcile; the box and viewBox types are shared with it.
31
+ *
32
+ * DOM-free so it can be unit-tested on plain numbers; the DOM I/O lives in
33
+ * frameFitDom.fitTextToFrame.
34
+ */
35
+ import type { Rect, ViewBox } from "./legendReconcile";
36
+ export type { Rect, ViewBox };
37
+ export interface FrameFitPlan {
38
+ action: "none" | "extend" | "defer";
39
+ top: number;
40
+ right: number;
41
+ bottom: number;
42
+ left: number;
43
+ newViewBox: ViewBox;
44
+ considered: number;
45
+ rescued: number;
46
+ deferred: number;
47
+ deferredIdx: number[];
48
+ reason: string;
49
+ }
50
+ export declare const FRAME_PAD = 1;
51
+ export declare const MAX_EXTEND_FRACTION = 0.08;
52
+ /**
53
+ * Work out how much the viewBox must grow so every measured ink box sits inside
54
+ * the frame with FRAME_PAD to spare.
55
+ *
56
+ * @param boxes ink boxes in USER space (see module note on the CTM mapping)
57
+ * @param vb the SVG's current viewBox in user units
58
+ * @param maxFraction per-axis extension ceiling, as a fraction of the frame
59
+ */
60
+ export declare function planFrameFit(boxes: Rect[], vb: ViewBox, maxFraction?: number): FrameFitPlan;
61
+ /** Serialize a viewBox the way the SVG attribute wants it. */
62
+ export declare function viewBoxAttr(vb: ViewBox): string;
63
+ export type TextAnchor = "start" | "middle" | "end";
64
+ /** The minimum readable type, px. */
65
+ export declare const FIT_TYPE_FLOOR = 10;
66
+ /** A shrink never goes below this fraction of the chart's own size: past it, cut instead. */
67
+ export declare const FIT_SHRINK_FLOOR = 0.8;
68
+ /**
69
+ * The width, in user units, a label may keep so that its box sits inside `frame` (with
70
+ * FRAME_PAD), given the edge its anchor holds - or null when no shortening can fit it
71
+ * (it overflows vertically, or its held edge is itself outside the frame).
72
+ */
73
+ export declare function labelRoom(box: Rect, frame: ViewBox, anchor: TextAnchor): number | null;
74
+ export interface LabelFitDecision {
75
+ action: "none" | "shrink" | "cut";
76
+ fontSize: number;
77
+ }
78
+ /**
79
+ * Shrink or cut: a label `width` units wide at `fontSize` px that must fit `room` units.
80
+ * Width scales with the font size, so the first guess is exact to within hinting; the caller
81
+ * re-measures after a shrink and falls through to the cut if hinting disagreed.
82
+ */
83
+ export declare function labelFitDecision(width: number, room: number, fontSize: number): LabelFitDecision;
84
+ /**
85
+ * The widest prefix of `full` (plus the ellipsis) that `fits`, never cut inside a number:
86
+ * a cut between two digits moves back to before the number ("largest 12..." of
87
+ * "largest 128 of 400" states a different count). Empty when nothing fits.
88
+ */
89
+ export declare function cutToFit(full: string, fits: (s: string) => boolean, ell?: string): string;
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Frame fit - the labels the viewBox could not rescue.
3
+ *
4
+ * fitTextToFrame grows the viewBox for text that pokes a few units past the frame and
5
+ * DEFERS text that is further out, because swallowing it would shrink the whole chart. A
6
+ * deferred label used to stay exactly where the chart put it, sliced by the frame, and
7
+ * a log line only counted it: a Bullet of 21 KPIs at 1230x626 reported
8
+ * `{"rescued":8,"deferred":6,"grew":"left+68.06"}` while six KPI names still read
9
+ * "...f Service Effort - Reduce effort".
10
+ *
11
+ * A label that overflows on the side its anchor does NOT hold is fitted in place: the type
12
+ * shrinks first (see labelFitDecision - never below the 10px floor or 80% of the chart's size),
13
+ * then the string is cut to the widest prefix that fits, never inside a number, and the full
14
+ * text rides a <title>. A label that overflows vertically, or on the side its anchor holds, is
15
+ * counted as unfixable and left alone. The geometry is in frameFit.ts; this is the DOM I/O,
16
+ * with the measurement handed in so the caller's CTM mapping (viewBox, zoom) is the only one.
17
+ */
18
+ import { Rect, ViewBox, FrameFitPlan } from "./frameFit";
19
+ export interface DeferredLabelFitReport {
20
+ /** Deferred text elements looked at. */
21
+ deferred: number;
22
+ /** Fitted by a smaller font size alone. */
23
+ shrunk: number;
24
+ /** Fitted by cutting the string (after any shrink). */
25
+ cut: number;
26
+ /** Could not be fitted by shortening: vertical overflow, the held edge outside, rotated, tspans. */
27
+ unfixable: number;
28
+ /** The first few full labels that were fitted, for the log line. */
29
+ labels: string[];
30
+ }
31
+ /**
32
+ * Fit ONE label, `width` wide, into `room` (both in whatever units `widthNow` measures): shrink the
33
+ * type first (labelFitDecision - never below the 10px floor or 80% of the label's size), then cut
34
+ * the string to the widest prefix that fits, never inside a number, with the full text riding a
35
+ * <title>. `minChars` keeps at least that many characters before the ellipsis even when they do not
36
+ * fit - a caller that must never REMOVE a label (the nominal axis) passes 1. Shared by the
37
+ * frame fit and the nominal-axis cut.
38
+ */
39
+ export declare function fitLabelToRoom(el: Element, width: number, room: number, widthNow: () => number, minChars?: number): "none" | "shrunk" | "cut";
40
+ /**
41
+ * Fit each deferred label to `frame`. `measure` returns the element's box in the SAME user
42
+ * space as `frame` (null when it cannot). Never throws; a label it cannot judge is unfixable.
43
+ */
44
+ export declare function fitDeferredLabels(els: Element[], frame: ViewBox, measure: (el: Element) => Rect | null): DeferredLabelFitReport;
45
+ /** Perf ceiling: every text costs a getBoundingClientRect, and a Tabular can paint thousands of
46
+ * cells; above this the sweep costs more than the defect it fixes (and such charts scroll their
47
+ * own body anyway, so their overflow is intentional). */
48
+ export declare const FRAME_FIT_TEXT_CAP = 600;
49
+ export interface FrameFitOutcome {
50
+ /** Set when the pass declined: too many texts to measure. */
51
+ skipped?: {
52
+ texts: number;
53
+ cap: number;
54
+ };
55
+ /** The viewBox plan (planFrameFit). */
56
+ plan?: FrameFitPlan;
57
+ /** What happened to the labels the plan deferred, when there were any. */
58
+ labels?: DeferredLabelFitReport;
59
+ }
60
+ /**
61
+ * THE WHOLE PASS, host-free: grow the viewBox for text a few units past the frame (planFrameFit),
62
+ * then fit the labels it deferred (fitDeferredLabels). Null when there is nothing to measure (no
63
+ * svg, no text, no CTM, an unreadable viewBox). A host adds its own logging around this call; it
64
+ * is kept DOM-only so a headless replay can run exactly what a host runs.
65
+ */
66
+ export declare function fitTextToFrame(host: HTMLElement): FrameFitOutcome | null;
67
+ /** True when the element sits under a clip-path/mask, inside <defs>, or inside a scrollable
68
+ * subtree - all cases where being outside the frame is the chart's intent rather than a
69
+ * text-metric miss. */
70
+ export declare function isClippedOrScrolled(el: Element, stopAt: Element): boolean;
@@ -0,0 +1,4 @@
1
+ export { MIN_OVERLAP, MAX_GUTTER_FRACTION, COLORBAR_ON_PLOT_COVER, MAX_FIT_FRACTION, FIT_PAD, LEGIBILITY_GRACE_PX, MIN_LEGIBLE_PX, BOTTOM_TEXT_MIN_TICK_HITS, SWATCH_MAX_PX, SWATCH_LABEL_GAP_PX, SIGNATURE_MIN_PAIRS, AXIS_THIN_MIN_TICKS, AXIS_THIN_GAP, LABEL_ROW_MIN_TEXTS, LABEL_ROW_Y_TOLERANCE, LABEL_ROW_TRACK_COVER, LABEL_ROW_TRACK_REACH, rightmostOverlappingPlotEdge, bottommostOverlappingPlotEdge, planLegendReconcile, colorbarProbePoints, colorbarOnPlot, colorbarSitsOnMarks, colorbarReconcileSide, planContentFit, planBottomTextPush, swatchPairs, isLegendSignature, planAxisTickThin, isNumericLabelText, isDateLikeLabelText, isOrderedAxisLabelText, axisLabelsAreNominal, nominalLabelRooms, groupLabelRows, labelRowHasTrack, type Rect, type ViewBox, type ReconcileSide, type ReconcilePlan, type ReconcileOptions, type ContentFitPlan, type ContentFitOptions, type SwatchPairs, type AxisThinPlan, } from "./legendReconcile";
2
+ export { FRAME_PAD, MAX_EXTEND_FRACTION, FIT_TYPE_FLOOR, FIT_SHRINK_FLOOR, planFrameFit, viewBoxAttr, labelRoom, labelFitDecision, cutToFit, type FrameFitPlan, type TextAnchor, type LabelFitDecision, } from "./frameFit";
3
+ export { reconcileLegendInSvg, findSignatureLegendGroups, findColorbarFurniture, smallestRenderedTextPx, styleDeclaresScroll, type LegendReconcileResult, type AxisThinReport, } from "./legendReconcileDom";
4
+ export { FRAME_FIT_TEXT_CAP, fitTextToFrame, fitDeferredLabels, fitLabelToRoom, isClippedOrScrolled, type FrameFitOutcome, type DeferredLabelFitReport, } from "./frameFitDom";