@pond-ts/charts 0.56.2 → 0.58.0
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/CHANGELOG.md +1218 -1
- package/dist/AreaChart.d.ts +12 -1
- package/dist/AreaChart.js +131 -13
- package/dist/BarChart.d.ts +84 -9
- package/dist/BarChart.js +295 -40
- package/dist/BarList.d.ts +85 -5
- package/dist/BarList.js +25 -4
- package/dist/BoxList.d.ts +70 -3
- package/dist/BoxList.js +21 -7
- package/dist/BoxPlot.d.ts +2 -1
- package/dist/BoxPlot.js +101 -9
- package/dist/Candlestick.d.ts +13 -1
- package/dist/Candlestick.js +89 -3
- package/dist/ChartContainer.d.ts +79 -48
- package/dist/ChartContainer.js +482 -60
- package/dist/ChartRow.d.ts +9 -2
- package/dist/ChartRow.js +86 -12
- package/dist/HeatMap.d.ts +176 -0
- package/dist/HeatMap.js +344 -0
- package/dist/Layers.d.ts +5 -1
- package/dist/Layers.js +1014 -253
- package/dist/Legend.js +8 -4
- package/dist/LineChart.d.ts +18 -1
- package/dist/LineChart.js +165 -4
- package/dist/ListTable.d.ts +30 -3
- package/dist/ListTable.js +381 -23
- package/dist/ScatterChart.d.ts +3 -2
- package/dist/ScatterChart.js +68 -4
- package/dist/XAxis.js +40 -22
- package/dist/YAxis.d.ts +28 -1
- package/dist/YAxis.js +24 -2
- package/dist/annotations.d.ts +74 -0
- package/dist/annotations.js +97 -7
- package/dist/area.d.ts +34 -1
- package/dist/area.js +88 -1
- package/dist/bars.d.ts +178 -5
- package/dist/bars.js +504 -46
- package/dist/box.d.ts +2 -2
- package/dist/box.js +158 -40
- package/dist/brush.d.ts +142 -0
- package/dist/brush.js +179 -0
- package/dist/child-index.d.ts +27 -0
- package/dist/child-index.js +57 -0
- package/dist/context.d.ts +871 -36
- package/dist/cursors.d.ts +161 -0
- package/dist/cursors.js +503 -0
- package/dist/decimate.d.ts +78 -1
- package/dist/decimate.js +157 -0
- package/dist/heat.d.ts +163 -0
- package/dist/heat.js +659 -0
- package/dist/index.d.ts +13 -4
- package/dist/index.js +25 -2
- package/dist/line.d.ts +137 -0
- package/dist/line.js +328 -0
- package/dist/ohlc.d.ts +16 -1
- package/dist/ohlc.js +93 -4
- package/dist/scatter.d.ts +17 -9
- package/dist/scatter.js +221 -33
- package/dist/select.d.ts +13 -5
- package/dist/select.js +14 -6
- package/dist/selection-fixtures.d.ts +174 -0
- package/dist/selection-fixtures.js +569 -0
- package/dist/selection-stories.d.ts +73 -0
- package/dist/selection-stories.js +301 -0
- package/dist/selectors.d.ts +316 -0
- package/dist/selectors.js +391 -0
- package/dist/span.d.ts +122 -0
- package/dist/span.js +203 -0
- package/dist/sweep.d.ts +154 -0
- package/dist/sweep.js +282 -0
- package/dist/theme.d.ts +517 -11
- package/dist/theme.js +220 -39
- package/dist/tracker.d.ts +6 -0
- package/dist/tracker.js +6 -0
- package/dist/tradingAxis.fixture.d.ts +78 -0
- package/dist/tradingAxis.fixture.js +215 -0
- package/dist/useChartLegend.js +18 -3
- package/package.json +3 -3
package/dist/context.d.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
1
2
|
import type { ScaleContinuousNumeric, ScaleLinear, ScaleTime } from 'd3-scale';
|
|
2
3
|
import type { ChartTheme } from './theme.js';
|
|
3
|
-
import type { AxisFormat } from './format.js';
|
|
4
|
+
import type { AxisFormat, CursorFormat } from './format.js';
|
|
4
5
|
import type { LegendItemSpec } from './swatch.js';
|
|
5
|
-
import type { Interval } from 'pond-ts';
|
|
6
|
+
import type { Interval, Sequence, BoundedSequence } from 'pond-ts';
|
|
6
7
|
import type { TradingTimeScale, DiscontinuityProvider } from './tradingTimeScale.js';
|
|
7
8
|
import type { ScaleBand } from './bandScale.js';
|
|
8
9
|
import type { ElapsedScale } from './elapsed.js';
|
|
@@ -74,6 +75,26 @@ export interface ContainerFrame {
|
|
|
74
75
|
readonly regionAnchor: number | null;
|
|
75
76
|
/** Set / clear the region-drag anchor (see {@link regionAnchor}). */
|
|
76
77
|
setRegionAnchor(value: number | null): void;
|
|
78
|
+
/**
|
|
79
|
+
* The **live** spans of a sweep in flight — the preview a span-only layer
|
|
80
|
+
* paints ([PND-TRACESEL]), and empty at rest.
|
|
81
|
+
*
|
|
82
|
+
* A mark layer previews through plural `hovered`: the covered marks light.
|
|
83
|
+
* A **trace has no marks**, so there is nothing for that channel to carry —
|
|
84
|
+
* what wants lighting is the *portion of the trace inside the window*, which
|
|
85
|
+
* is a span, not a set of marks. Hence a second preview channel rather than
|
|
86
|
+
* an abuse of the first.
|
|
87
|
+
*
|
|
88
|
+
* A paint mirror in the same sense as {@link regionAnchor}: `Layers` writes
|
|
89
|
+
* it from the live session and clears it on release, and it never enters the
|
|
90
|
+
* committed selection — {@link selectedSpans} is the consumer's, this is the
|
|
91
|
+
* gesture's. A layer that finds itself named here should draw as though the
|
|
92
|
+
* span were committed, so releasing changes nothing visually and the preview
|
|
93
|
+
* cannot promise a different picture than the commit delivers.
|
|
94
|
+
*/
|
|
95
|
+
readonly previewSpans: readonly SpanSelection[];
|
|
96
|
+
/** Set / clear the live sweep preview (see {@link previewSpans}). */
|
|
97
|
+
setPreviewSpans(spans: readonly SpanSelection[]): void;
|
|
77
98
|
/**
|
|
78
99
|
* One-shot callback fired when a `region`-cursor **drag** is released, with the
|
|
79
100
|
* selected `[lo, hi]` span in **axis units** — epoch ms on a time axis, the axis
|
|
@@ -102,36 +123,93 @@ export interface ContainerFrame {
|
|
|
102
123
|
*/
|
|
103
124
|
readonly regionSelectModifier: 'shift' | undefined;
|
|
104
125
|
/**
|
|
105
|
-
* The selected
|
|
106
|
-
*
|
|
107
|
-
* sample `key` (epoch ms) —
|
|
108
|
-
*
|
|
109
|
-
*
|
|
110
|
-
* (one with an `id`)
|
|
126
|
+
* The selected marks — **empty when nothing is selected**, never `null`.
|
|
127
|
+
* Shared across rows, insertion-ordered. A layer highlights every mark
|
|
128
|
+
* matching a member's series **`id`** and clicked sample `key` (epoch ms) —
|
|
129
|
+
* the `id` picks the series (so two series sharing a timestamp don't both
|
|
130
|
+
* light up), the `key` picks the mark within it. A controlled `selected` prop
|
|
131
|
+
* pins the set; otherwise a click on a selectable layer (one with an `id`)
|
|
132
|
+
* sets it.
|
|
133
|
+
*
|
|
134
|
+
* **A set, not a single mark, since [PND-MULTISEL].** The container prop
|
|
135
|
+
* accepts either shape and normalizes here, so a single-selection consumer is
|
|
136
|
+
* unaffected; this frame field is internal (not exported from `index.ts`), so
|
|
137
|
+
* widening it breaks nobody. A layer that only ever wants one mark can read
|
|
138
|
+
* `selected[0]`, but membership is the honest test — see `isSelected`.
|
|
111
139
|
*/
|
|
112
|
-
readonly selected: SelectInfo
|
|
140
|
+
readonly selected: readonly SelectInfo[];
|
|
113
141
|
/**
|
|
114
|
-
*
|
|
115
|
-
*
|
|
116
|
-
*
|
|
117
|
-
*
|
|
118
|
-
*
|
|
119
|
-
* the
|
|
142
|
+
* The **span** entries of the controlled `selected` prop — **empty when it
|
|
143
|
+
* carries none**, never `null`. The container splits the prop's mixed
|
|
144
|
+
* `SelectionEntry` array into per-mark entries ({@link selected}) and range
|
|
145
|
+
* descriptors (this field) once at the boundary, so every existing reader of
|
|
146
|
+
* {@link selected} keeps its exact shape (and cost) while span-aware layers
|
|
147
|
+
* read this **additionally** — the union of the two fields is the selection
|
|
148
|
+
* (interaction RFC A5.2). Like `selected`, this frame field is internal (not
|
|
149
|
+
* exported from `index.ts`).
|
|
150
|
+
*
|
|
151
|
+
* Only the controlled prop can populate it: clicks produce marks, so the
|
|
152
|
+
* uncontrolled path never holds a span.
|
|
120
153
|
*/
|
|
121
|
-
|
|
154
|
+
readonly selectedSpans: readonly SpanSelection[];
|
|
122
155
|
/**
|
|
123
|
-
*
|
|
124
|
-
*
|
|
125
|
-
*
|
|
126
|
-
*
|
|
156
|
+
* Select a mark, or `null` to clear. Reports the hit to the `<Selector>`s in
|
|
157
|
+
* scope and manages the internal selection only when uncontrolled (no
|
|
158
|
+
* `selected` prop). The split mirrors the tracker's `trackerPosition`
|
|
159
|
+
* (controlled by a *value* prop) + its `onTrackerChanged` notification — not
|
|
160
|
+
* `applyRange`, which is controlled by the presence of a *callback*.
|
|
161
|
+
*
|
|
162
|
+
* **`rowKey` distinguishes the two callers, and it is load-bearing**
|
|
163
|
+
* (interaction RFC §7.1). Pass a row's key for a **plot gesture** — the row's
|
|
164
|
+
* click surface, after hit-testing — and the call is *gated on a mounted
|
|
165
|
+
* `<Selector>`*: with none in scope it does nothing at all (the deliberate
|
|
166
|
+
* break), and dev-warns on the hit that went nowhere. Omit it for a
|
|
167
|
+
* **programmatic** select (a `<Legend>` chip, a consumer's own control),
|
|
168
|
+
* which reports to the container-scoped selectors and commits as it always
|
|
169
|
+
* has — mounting gates the *plot*, not an explicit call.
|
|
170
|
+
*/
|
|
171
|
+
select(hit: SelectInfo | null, modifiers?: SelectModifiers, rowKey?: symbol): void;
|
|
172
|
+
/**
|
|
173
|
+
* The **hovered** marks — **empty when nothing is hovered, never `null`** —
|
|
174
|
+
* the transient hover-highlight, distinct from the committed `selected`. A
|
|
175
|
+
* row's pointer-move surface hit-tests its selectable layers and sets it; a
|
|
176
|
+
* layer that supports hover-highlight (Bar) draws every matching mark lit (a
|
|
177
|
+
* lighter treatment than `selected`'s outline).
|
|
178
|
+
*
|
|
179
|
+
* **A set, not one mark, since RFC `selection.md` A4.2.** A1.4 argued hover
|
|
180
|
+
* "is inherently one mark under the pointer" — true while hover *means*
|
|
181
|
+
* pointer position, and false under a drag sweep, where it means "would be
|
|
182
|
+
* selected if you released now" and several marks are lit at once. Plain
|
|
183
|
+
* pointer-over therefore carries 0 or 1 members; that slightly odd type for
|
|
184
|
+
* the common case is the accepted cost of not minting a third `preview`
|
|
185
|
+
* state a theme would style identically anyway.
|
|
127
186
|
* Set-on-change (deduped by the series `id` + sample `key`) so the data canvas
|
|
128
187
|
* repaints only on a mark transition, not every pointer move.
|
|
129
188
|
*/
|
|
130
|
-
readonly hovered: SelectInfo
|
|
131
|
-
/**
|
|
132
|
-
*
|
|
133
|
-
*
|
|
134
|
-
|
|
189
|
+
readonly hovered: readonly SelectInfo[];
|
|
190
|
+
/**
|
|
191
|
+
* Set the hovered mark (or `null` to clear) from a pointer-move hit-test;
|
|
192
|
+
* deduped by series `id` + sample `key`, so an unchanged mark is a no-op (no
|
|
193
|
+
* repaint). `rowKey` scopes which `<Selector>`s hear about it (a row's own
|
|
194
|
+
* mounts, else the container's) exactly as {@link select} does; omit it for a
|
|
195
|
+
* programmatic hover (a `<Legend>` chip).
|
|
196
|
+
*
|
|
197
|
+
* **Not gated on a mounted `<Selector>`, unlike {@link select}.** With no
|
|
198
|
+
* selector mounted there is simply nobody registered to hear about it (RFC
|
|
199
|
+
* A10.3) — the hover state still moves, it just has nothing to report to,
|
|
200
|
+
* which needs no gate to arrange.
|
|
201
|
+
*
|
|
202
|
+
* `block` — the **resting block preview** under a mounted `<MultiSelector>`
|
|
203
|
+
* (the marks of the snap block the pointer is in — exactly the set a drag
|
|
204
|
+
* begun and released there would select). When present it becomes the
|
|
205
|
+
* hovered *state* (every mark in the block lights) and what
|
|
206
|
+
* `<MultiSelector onHover>` hears, deduped by the block array's identity
|
|
207
|
+
* (the caller keeps it reference-stable per block, so within-block mark
|
|
208
|
+
* transitions re-render nothing). `<Selector onHover>` keeps its per-mark
|
|
209
|
+
* currency — `hit` still reports per mark transition. Omitted ⇒ the
|
|
210
|
+
* plain single-mark hover, exactly as before.
|
|
211
|
+
*/
|
|
212
|
+
setHovered(hit: SelectInfo | null, rowKey?: symbol, block?: readonly SelectInfo[]): void;
|
|
135
213
|
/** The default in-chart cursor presentation for all rows ({@link CursorMode});
|
|
136
214
|
* a row may override it via its own `cursor`. */
|
|
137
215
|
readonly cursor: CursorMode;
|
|
@@ -200,6 +278,45 @@ export interface ContainerFrame {
|
|
|
200
278
|
*/
|
|
201
279
|
registerSelectable(key: symbol): void;
|
|
202
280
|
unregisterSelectable(key: symbol): void;
|
|
281
|
+
/**
|
|
282
|
+
* Register a mounted **selector** ({@link SelectorEntry}) — `<Selector>` /
|
|
283
|
+
* `<MultiSelector>` call this, keyed by the component's per-instance slot
|
|
284
|
+
* key. The same idiom as `registerCursor` / `registerAxis` / `registerLayer`:
|
|
285
|
+
* update in place, unregister on unmount.
|
|
286
|
+
*
|
|
287
|
+
* **The registration is the enablement** (interaction RFC §7.1): a plot click
|
|
288
|
+
* resolves the selectors in scope (`effectiveSelectorEntries`) and does
|
|
289
|
+
* nothing when there are none.
|
|
290
|
+
*
|
|
291
|
+
* The registry itself is deliberately **not** exposed on the frame (unlike
|
|
292
|
+
* `cursors`, which the rows and `<XAxis>` render): nothing outside the
|
|
293
|
+
* container reads it — `select` / `setHovered` resolve the scope internally —
|
|
294
|
+
* and publishing it would re-identify the whole frame on every selector
|
|
295
|
+
* mount. Same shape as `registerSelectable`, which also keeps its set private.
|
|
296
|
+
*/
|
|
297
|
+
registerSelector(key: symbol, entry: SelectorEntry): void;
|
|
298
|
+
unregisterSelector(key: symbol): void;
|
|
299
|
+
/**
|
|
300
|
+
* Resolve a row's press against the mounted `<MultiSelector>`s in scope
|
|
301
|
+
* (interaction RFC §8): the {@link SweepGesture} sinks when at least one is
|
|
302
|
+
* in scope, else `null` — which is how the brush recognizer learns whether a
|
|
303
|
+
* sweep can claim the drag at all (mounting arms the gesture, §7.1's rule
|
|
304
|
+
* extended to the sweep). Scope resolution mirrors {@link select}'s: the
|
|
305
|
+
* row's own mounts when it has any, else the container's. The selector
|
|
306
|
+
* registry itself stays private, for the reason documented on
|
|
307
|
+
* {@link registerSelector}.
|
|
308
|
+
*/
|
|
309
|
+
resolveSweep(rowKey: symbol): SweepGesture | null;
|
|
310
|
+
/**
|
|
311
|
+
* Whether a `<MultiSelector>` is in scope for this row — the *render-time*
|
|
312
|
+
* fact behind {@link resolveSweep} (which builds the per-drag gesture and is
|
|
313
|
+
* for the pointer-down path). Rows read this to decide the **resting block
|
|
314
|
+
* preview**: with one in scope over a sweep-capable row, the shared brush
|
|
315
|
+
* band becomes the resting cursor (replacing the implicit line shim) and
|
|
316
|
+
* hover lights the whole snap block a drag would select. Identity changes
|
|
317
|
+
* when the selector registry does, so it is safe in render memos.
|
|
318
|
+
*/
|
|
319
|
+
hasMultiSelector(rowKey: symbol): boolean;
|
|
203
320
|
/**
|
|
204
321
|
* Register this layer's **legend row** — its display label + resolved
|
|
205
322
|
* {@link SwatchSpec} (and selection `id` when it has one) — keyed by the
|
|
@@ -275,6 +392,34 @@ export interface ContainerFrame {
|
|
|
275
392
|
* called while `panZoom` is on.
|
|
276
393
|
*/
|
|
277
394
|
applyRange(range: readonly [number, number]): void;
|
|
395
|
+
/**
|
|
396
|
+
* The **y view transform** for 2-D pan/zoom (`panZoom="panZoom2D"`), in
|
|
397
|
+
* *pixel* space: a y scale's range becomes `ty + k · basePixel`. Identity is
|
|
398
|
+
* `{ k: 1, ty: 0 }`.
|
|
399
|
+
*
|
|
400
|
+
* Pixel space rather than domain space is what makes this axis-independent.
|
|
401
|
+
* A row may carry several y axes with unrelated domains (left price, right
|
|
402
|
+
* volume), and the question "which one does a vertical gesture own?" has no
|
|
403
|
+
* good answer — but a uniform pixel transform sidesteps it: every axis zooms
|
|
404
|
+
* by the same factor about its own pivot, which is also exactly what keeps
|
|
405
|
+
* the **aspect ratio** fixed. The x half stays in domain space, where
|
|
406
|
+
* `bounds`, `minDuration` and the trading-calendar zoom maths live.
|
|
407
|
+
*/
|
|
408
|
+
/** Which axes the gestures own; pan follows zoom's degrees of freedom. */
|
|
409
|
+
readonly zoomX: boolean;
|
|
410
|
+
readonly zoomY: boolean;
|
|
411
|
+
readonly panX: boolean;
|
|
412
|
+
readonly panY: boolean;
|
|
413
|
+
/** True only when both axes zoom — one factor, so the ratio is fixed. */
|
|
414
|
+
readonly aspectLocked: boolean;
|
|
415
|
+
readonly yTransform: {
|
|
416
|
+
readonly k: number;
|
|
417
|
+
readonly ty: number;
|
|
418
|
+
};
|
|
419
|
+
applyYTransform(next: {
|
|
420
|
+
k: number;
|
|
421
|
+
ty: number;
|
|
422
|
+
}): void;
|
|
278
423
|
/**
|
|
279
424
|
* A row reports its per-slot gutter widths each side; the container reserves
|
|
280
425
|
* each slot's max so every row's plot left-aligns. Returns an unregister fn.
|
|
@@ -289,6 +434,19 @@ export interface ContainerFrame {
|
|
|
289
434
|
registerRow(key: symbol): () => void;
|
|
290
435
|
/** The first (topmost) row's key, or `null` before any row has registered. */
|
|
291
436
|
readonly firstRowKey: symbol | null;
|
|
437
|
+
/**
|
|
438
|
+
* Register a mounted **cursor** ({@link CursorEntry}) — the `<LineCursor>` /
|
|
439
|
+
* `<CrosshairCursor>` / … presets (and the deprecation shim synthesizing them
|
|
440
|
+
* from the legacy string props) call this, keyed by the component's
|
|
441
|
+
* per-instance slot key. The container resolves the per-row effective set
|
|
442
|
+
* (row mounts override container mounts — see `effectiveCursorEntries`) and
|
|
443
|
+
* the rows/`<XAxis>` render the registered slots. Update is in place;
|
|
444
|
+
* unregister on unmount.
|
|
445
|
+
*/
|
|
446
|
+
registerCursor(key: symbol, entry: CursorEntry): void;
|
|
447
|
+
unregisterCursor(key: symbol): void;
|
|
448
|
+
/** Every registered cursor, in mount order (see {@link registerCursor}). */
|
|
449
|
+
readonly cursors: readonly CursorEntry[];
|
|
292
450
|
/**
|
|
293
451
|
* Register an annotation (`<Region>`/`<Marker>`/`<Baseline>`) so the container
|
|
294
452
|
* can coordinate what a mark can't do in isolation: draw each mark's **guide
|
|
@@ -349,6 +507,15 @@ export interface ContainerFrame {
|
|
|
349
507
|
}
|
|
350
508
|
/** The kind of an annotation, and of a creation tool. */
|
|
351
509
|
export type AnnotationKind = 'region' | 'marker' | 'baseline';
|
|
510
|
+
/**
|
|
511
|
+
* The kind of a **registered** mark — wider than {@link AnnotationKind}.
|
|
512
|
+
*
|
|
513
|
+
* A `<Zone>` is a mark you place in JSX but **not** a create *tool*: there is no
|
|
514
|
+
* draw gesture for it and no {@link CreateSpec} variant, so it registers under
|
|
515
|
+
* its own kind without widening the toolbar vocabulary (which would let
|
|
516
|
+
* `creating="zone"` type-check and then silently never fire `onCreate`).
|
|
517
|
+
*/
|
|
518
|
+
export type AnnotationSpecKind = AnnotationKind | 'zone';
|
|
352
519
|
/** What a completed create gesture reports to {@link ContainerFrame.onCreate} —
|
|
353
520
|
* the new mark's kind + position in axis units (+ the y-axis id for a baseline).
|
|
354
521
|
* (Which row a mark lands on is the consumer's call for now; multi-row routing is
|
|
@@ -376,14 +543,14 @@ export interface AnnotationSpec {
|
|
|
376
543
|
* double-click reports via {@link ContainerFrame.onSelectAnnotation}, so the
|
|
377
544
|
* consumer knows which mark to select. */
|
|
378
545
|
readonly id: string | undefined;
|
|
379
|
-
readonly kind:
|
|
546
|
+
readonly kind: AnnotationSpecKind;
|
|
380
547
|
/** The row it lives on (its `<ChartRow>`'s key), so a row skips its own marks
|
|
381
548
|
* when drawing guides. */
|
|
382
549
|
readonly rowKey: symbol;
|
|
383
550
|
/**
|
|
384
551
|
* Its vertical-guide x-position(s) in **axis units** (the shared x): a marker's
|
|
385
|
-
* `[at]`, a region's `[from, to]`. Empty for a baseline
|
|
386
|
-
* casts no vertical guide.
|
|
552
|
+
* `[at]`, a region's `[from, to]`. Empty for a baseline or a zone — a
|
|
553
|
+
* horizontal line (or band) casts no vertical guide.
|
|
387
554
|
*/
|
|
388
555
|
readonly xs: readonly number[];
|
|
389
556
|
/** Whether it's currently selected (controlled by the consumer). */
|
|
@@ -554,13 +721,15 @@ export interface RowLayer {
|
|
|
554
721
|
binCategories?(): readonly string[] | null;
|
|
555
722
|
/**
|
|
556
723
|
* A bar/histogram layer's bar `[begin, end)` spans, as pond `Interval`s — the
|
|
557
|
-
* **
|
|
558
|
-
*
|
|
559
|
-
*
|
|
560
|
-
* **vertical** bar layer
|
|
561
|
-
*
|
|
562
|
-
*
|
|
563
|
-
*
|
|
724
|
+
* **shared snap buckets**. When present (and no `cursorSequence` is set), a
|
|
725
|
+
* region drag snaps bar by bar and a `<MultiSelector>` sweep's band extends
|
|
726
|
+
* bar by bar, so a histogram gets bin-aligned selection for free. Only a
|
|
727
|
+
* **vertical** bar layer publishes them — a horizontal chart puts the value
|
|
728
|
+
* on x (snapping counts is meaningless). On a **category** axis they are
|
|
729
|
+
* the unit slots `[i, i+1)`: the region cursor still ignores them (its band
|
|
730
|
+
* gates on a continuous axis), but the sweep band snaps over them to the
|
|
731
|
+
* slots' outer edges (RFC A7.6's edge rule — the band must agree with the
|
|
732
|
+
* span the release commits). `null` / absent otherwise.
|
|
564
733
|
*/
|
|
565
734
|
binIntervals?(): readonly Interval[] | null;
|
|
566
735
|
/**
|
|
@@ -589,8 +758,87 @@ export interface RowLayer {
|
|
|
589
758
|
* so they render + read out but never select/hover (a click on them resolves
|
|
590
759
|
* to empty space ⇒ deselect). `xScale`/`yScale` map data→pixels (the row
|
|
591
760
|
* resolves the layer's axis scale, as for `draw`).
|
|
761
|
+
*
|
|
762
|
+
* `mode` says which gesture is asking, and a layer whose hover target is
|
|
763
|
+
* generous may narrow the *select* one. The single-series bar path does:
|
|
764
|
+
* **hover** attributes the bar's whole slot (full interval width, full plot
|
|
765
|
+
* height — the continuous model #582 asked for, so the highlight tracks
|
|
766
|
+
* like the readout), while **select** additionally requires the pointer to
|
|
767
|
+
* be within the bar's drawn ink vertically. Without that narrowing, slots
|
|
768
|
+
* tile the entire plot and a click can never resolve to `null` — RFC §7's
|
|
769
|
+
* empty-click deselect path becomes unreachable on any full-range bar
|
|
770
|
+
* chart. Layers whose hit target is already the drawn mark (stacks,
|
|
771
|
+
* scatter, boxes, heat cells) ignore `mode`.
|
|
592
772
|
*/
|
|
593
|
-
hitTest?(px: number, py: number, xScale: (value: number) => number, yScale: (value: number) => number): SelectInfo | null;
|
|
773
|
+
hitTest?(px: number, py: number, xScale: (value: number) => number, yScale: (value: number) => number, mode?: 'hover' | 'select'): SelectInfo | null;
|
|
774
|
+
/**
|
|
775
|
+
* Begin a **sweep session** over this layer's marks — `<MultiSelector>`'s
|
|
776
|
+
* range query (interaction RFC A7.6/A7.7), the range analog of
|
|
777
|
+
* {@link hitTest} and gated exactly like it: only a layer with an `id` (and
|
|
778
|
+
* discrete selectable marks) wires it, so an untagged layer is never swept.
|
|
779
|
+
* Called on the pointer-down that survives `DRAG_SLOP` under a mounted
|
|
780
|
+
* `<MultiSelector>`; the session lives for that one drag and nothing
|
|
781
|
+
* persists outside it. Returns `null` when there is nothing sweepable.
|
|
782
|
+
*
|
|
783
|
+
* `xScale`/`yScale` are the resolved scales (as for `draw`/`hitTest`) —
|
|
784
|
+
* unused by the 1-D layers, but part of the seam so the 2-D sessions
|
|
785
|
+
* (scatter's y-window, the heat map's row cut — [PND-INTERACT2D]) can be
|
|
786
|
+
* added without redesigning the shape (RFC A7.6).
|
|
787
|
+
*/
|
|
788
|
+
beginSweep?(xScale: (value: number) => number, yScale: (value: number) => number): SweepSession | null;
|
|
789
|
+
/**
|
|
790
|
+
* Whether a sweep over this layer cuts a **rect** rather than a band — the
|
|
791
|
+
* same fact {@link SweepSession.twoD} reports, declared on the layer because
|
|
792
|
+
* the RESTING state has to know it and there is no session at rest.
|
|
793
|
+
*
|
|
794
|
+
* It decides what the row's resting cursor is: a band over the snap block
|
|
795
|
+
* for a 1-D layer, a small crosshair at the pointer for a 2-D one (a rect
|
|
796
|
+
* gesture has no resting block to preview — see `Layers`). Building a
|
|
797
|
+
* per-drag session just to ask would be the wrong shape and would snapshot
|
|
798
|
+
* the layer's arrays for nothing.
|
|
799
|
+
*
|
|
800
|
+
* Must agree with the session the layer's own `beginSweep` returns;
|
|
801
|
+
* `sweep-capabilities.test.ts` pins that across every layer.
|
|
802
|
+
*/
|
|
803
|
+
readonly sweepsRect?: boolean;
|
|
804
|
+
/**
|
|
805
|
+
* **Which screen axis a sweep over this layer cuts** — `'x'` (the default,
|
|
806
|
+
* and every layer that shipped before [PND-HSWEEP]) or `'y'` for a
|
|
807
|
+
* transposed layer whose bins run down the screen: a horizontal
|
|
808
|
+
* `<BarChart>` puts the value on the shared x, so a window there says
|
|
809
|
+
* nothing about which bins are covered and the cut has to come from the
|
|
810
|
+
* pointer's y.
|
|
811
|
+
*
|
|
812
|
+
* **Orthogonal to {@link sweepsRect}, and all four pairs are real** — (x,
|
|
813
|
+
* band) a vertical bar, (y, band) a horizontal bar, (x, rect) a scatter or a
|
|
814
|
+
* vertical heat map, (y, rect) a horizontal heat map. That is why this is a
|
|
815
|
+
* second field rather than a third value on `sweepsRect`.
|
|
816
|
+
*
|
|
817
|
+
* The session itself is unaffected: {@link SweepSession.update} takes
|
|
818
|
+
* **key-axis units** and does not care which screen axis produced them, so a
|
|
819
|
+
* transposed layer builds the same `sweep1D` a vertical one does. What the
|
|
820
|
+
* axis changes is the *gesture* — which pointer coordinate is inverted (and
|
|
821
|
+
* through which scale), where the drag slop lives (`|dy|` rather than
|
|
822
|
+
* `|dx|`), and which way the brush band is drawn.
|
|
823
|
+
*
|
|
824
|
+
* A `'y'` cut is measured against **one row's** axis, so its band is
|
|
825
|
+
* row-local exactly as a rect is, and it takes its geometry from
|
|
826
|
+
* {@link SweepSession.extent} rather than from the shared bin channel —
|
|
827
|
+
* `binIntervals` carries the *value* axis on a transposed layer.
|
|
828
|
+
*/
|
|
829
|
+
readonly sweepAxis?: 'x' | 'y';
|
|
830
|
+
/**
|
|
831
|
+
* **Whether a sweep over this layer has a range but no marks** — the same
|
|
832
|
+
* fact {@link SweepSession.spanOnly} reports, declared on the layer for
|
|
833
|
+
* exactly the reason {@link sweepsRect} is: the RESTING state has to know it
|
|
834
|
+
* and there is no session at rest.
|
|
835
|
+
*
|
|
836
|
+
* It suppresses the resting **block** band. That band previews "a drag begun
|
|
837
|
+
* here selects this block", and a trace has no blocks — a drag takes a
|
|
838
|
+
* freeform window — so the band would advertise a set the gesture never
|
|
839
|
+
* selects. Precisely the reason a `twoD` layer gets no resting band either.
|
|
840
|
+
*/
|
|
841
|
+
readonly sweepSpanOnly?: boolean;
|
|
594
842
|
/**
|
|
595
843
|
* Draw into the plot canvas. `xScale`/`yScale` map data→pixels. May return
|
|
596
844
|
* {@link LayerDrawStats} (source/drawn counts + whether decimation engaged) so
|
|
@@ -599,6 +847,142 @@ export interface RowLayer {
|
|
|
599
847
|
*/
|
|
600
848
|
draw(ctx: CanvasRenderingContext2D, xScale: (value: number) => number, yScale: (value: number) => number): LayerDrawStats | void;
|
|
601
849
|
}
|
|
850
|
+
/**
|
|
851
|
+
* A layer's per-drag **sweep session** ({@link RowLayer.beginSweep} — RFC
|
|
852
|
+
* A7.7's `beginSweep(scales) → session` shape, 1-D form). The gesture engine
|
|
853
|
+
* drives it: `update` per coalesced frame with the swept key-window,
|
|
854
|
+
* {@link hits} for the frame-gated live preview, and — at release — the same
|
|
855
|
+
* `hits()` again as the commit payload plus {@link extent} for the span, so
|
|
856
|
+
* the committed hits ARE the materialised preview rather than a fresh range
|
|
857
|
+
* query (RFC A5.2's "the hits are free"). Internal, like `RowLayer` itself.
|
|
858
|
+
*/
|
|
859
|
+
export interface SweepSession {
|
|
860
|
+
/** The layer's `id` — what the committed {@link SpanSelection} carries. */
|
|
861
|
+
readonly id: string;
|
|
862
|
+
/**
|
|
863
|
+
* Re-cut the covered set to the marks intersecting the half-open window
|
|
864
|
+
* `[x0, x1)` (key-axis units, `x0 <= x1`). Returns whether the covered set
|
|
865
|
+
* changed — the delta gate: an unchanged frame re-materialises nothing.
|
|
866
|
+
*
|
|
867
|
+
* `y0`/`y1` are the rect's second dimension in the layer's **y-axis data
|
|
868
|
+
* units** (not pixels — the gesture inverts through the same `yScale` the
|
|
869
|
+
* session was built with), and only a {@link twoD} session reads them; the
|
|
870
|
+
* gesture omits them entirely for a 1-D layer. They are unordered, because
|
|
871
|
+
* a drag upward hands them over inverted and the session is the one place
|
|
872
|
+
* that knows whether its axis descends.
|
|
873
|
+
*
|
|
874
|
+
* **A `twoD` session must include y in its own delta gate.** A purely
|
|
875
|
+
* vertical drag leaves the x run untouched, so a gate that watched x alone
|
|
876
|
+
* would answer `false` and freeze the preview under a moving pointer.
|
|
877
|
+
*/
|
|
878
|
+
update(x0: number, x1: number, y0?: number, y1?: number): boolean;
|
|
879
|
+
/**
|
|
880
|
+
* The covered marks, materialised (and cached until the next change) — the
|
|
881
|
+
* live preview `hovered` lights, and verbatim the release payload.
|
|
882
|
+
*/
|
|
883
|
+
hits(): readonly SelectInfo[];
|
|
884
|
+
/**
|
|
885
|
+
* The covered marks' snapped-outward key extent `[begin(first), end(last))`
|
|
886
|
+
* — {@link SpanSelection.x} per RFC A7.6's edge rule, so the span's
|
|
887
|
+
* half-open containment test reproduces exactly the captured set. `null`
|
|
888
|
+
* when nothing is covered.
|
|
889
|
+
*/
|
|
890
|
+
extent(): readonly [number, number] | null;
|
|
891
|
+
/**
|
|
892
|
+
* **Set by a layer whose marks live in two dimensions** — a scatter (a point
|
|
893
|
+
* is a position, not a column) or a heat map (a grid of cells). The gesture
|
|
894
|
+
* reads this to decide whether to track a y window alongside x and to draw a
|
|
895
|
+
* rect rather than a band: dimensionality is a property of the *layer*, so a
|
|
896
|
+
* consumer mounts the same `<MultiSelector>` either way (RFC Q14).
|
|
897
|
+
*/
|
|
898
|
+
readonly twoD?: boolean;
|
|
899
|
+
/**
|
|
900
|
+
* **Set by a layer that has a range but no marks** — a continuous trace
|
|
901
|
+
* ([PND-TRACESEL]). `hits()` is always empty and the span is the whole
|
|
902
|
+
* selection.
|
|
903
|
+
*
|
|
904
|
+
* The gesture reads this to answer "who else did I sweep?". Topmost-wins
|
|
905
|
+
* exists because a release carried **one** span, and on mark layers it is
|
|
906
|
+
* defensible — you were pointing at marks, so the topmost is the one you
|
|
907
|
+
* meant. A trace sweep points at *nothing*: every trace in the row shares
|
|
908
|
+
* the same x window, so singling one out by z-order is arbitrary to the
|
|
909
|
+
* reader. Every span-only layer in the row is therefore swept together,
|
|
910
|
+
* while mark layers keep topmost-wins unchanged.
|
|
911
|
+
*/
|
|
912
|
+
readonly spanOnly?: boolean;
|
|
913
|
+
/**
|
|
914
|
+
* The captured set's **second-dimension** channels, for a `twoD` session —
|
|
915
|
+
* whichever of {@link SpanSelection.y} (a scatter's continuous window) and
|
|
916
|
+
* {@link SpanSelection.rows} (a heat map's ordinal row set) the layer uses.
|
|
917
|
+
* `null` when nothing is covered. Absent on a 1-D session.
|
|
918
|
+
*/
|
|
919
|
+
extent2D?(): {
|
|
920
|
+
readonly y?: readonly [number, number];
|
|
921
|
+
readonly rows?: readonly string[];
|
|
922
|
+
} | null;
|
|
923
|
+
/**
|
|
924
|
+
* The cut's **snapped rect in axis units** (x in key units, y in the
|
|
925
|
+
* sweeping layer's y-axis units), for the brush to draw — `null` when the
|
|
926
|
+
* layer's cut is free, or when nothing is covered.
|
|
927
|
+
*
|
|
928
|
+
* A snapping layer must draw the rect it is going to *take*, not the one
|
|
929
|
+
* the pointer traced: a heat map snaps to whole cells, so a raw pointer
|
|
930
|
+
* rect promises a different set than the release delivers, and the two
|
|
931
|
+
* disagree most visibly at exactly the moment the user is deciding where
|
|
932
|
+
* to let go.
|
|
933
|
+
*/
|
|
934
|
+
snappedRect?(): {
|
|
935
|
+
readonly x: readonly [number, number];
|
|
936
|
+
readonly y: readonly [number, number];
|
|
937
|
+
} | null;
|
|
938
|
+
}
|
|
939
|
+
/**
|
|
940
|
+
* What the container resolves for a row's press under a mounted
|
|
941
|
+
* `<MultiSelector>` ({@link ContainerFrame.resolveSweep}): the two sinks of
|
|
942
|
+
* the sweep gesture, with the selector registry kept private (the same reason
|
|
943
|
+
* `select` resolves scope internally). Both operate on the entries that were
|
|
944
|
+
* in scope at the press, for the lifetime of that one drag.
|
|
945
|
+
*/
|
|
946
|
+
export interface SweepGesture {
|
|
947
|
+
/**
|
|
948
|
+
* Whether an in-scope `<MultiSelector>` **declared a `sequence`** — i.e. the
|
|
949
|
+
* consumer said selection happens in bucket units rather than in marks.
|
|
950
|
+
*
|
|
951
|
+
* This is what widens a *click* from the mark under the pointer to the whole
|
|
952
|
+
* snap block (RFC §8). It has to be the declaration and not a property of
|
|
953
|
+
* what the block happened to contain: a stack's bin holds one mark per
|
|
954
|
+
* group, so "the block covers more than one mark" is true of an ordinary
|
|
955
|
+
* single-bin click on a stacked chart, and using that as the test made a
|
|
956
|
+
* click there select the whole bin instead of the clicked segment.
|
|
957
|
+
*/
|
|
958
|
+
readonly snapped: boolean;
|
|
959
|
+
/**
|
|
960
|
+
* The frame-coalesced **live preview**: light `hits` through the plural
|
|
961
|
+
* `hovered` (RFC A3.4 — the library owns the state, each layer renders its
|
|
962
|
+
* own hover treatment) and report them to the `<MultiSelector>`s in scope.
|
|
963
|
+
* Nothing else crosses the public boundary until release (RFC A1.4).
|
|
964
|
+
*
|
|
965
|
+
* `light: false` **reports without lighting** — for a layer whose brush
|
|
966
|
+
* already outlines exactly the covered region (a heat map's snapped rect).
|
|
967
|
+
* Its hover treatment is a ring around *the cell under the pointer*, and
|
|
968
|
+
* during a sweep there is no such cell: a ring on every covered cell turns
|
|
969
|
+
* the region into the mostly-border grid the selection outline exists to
|
|
970
|
+
* avoid, inside a rect that is already saying the same thing. The consumer
|
|
971
|
+
* still hears the full set through `onHoverMany`, so a controlled `hovered`
|
|
972
|
+
* can render it however it likes.
|
|
973
|
+
*/
|
|
974
|
+
preview(hits: readonly SelectInfo[], light?: boolean): void;
|
|
975
|
+
/**
|
|
976
|
+
* The release: report `(hits, modifiers, span)` to the `<MultiSelector>`s in
|
|
977
|
+
* scope (RFC A5.2), clear the preview, and — when `selected` is
|
|
978
|
+
* uncontrolled — commit the compact span descriptor as the selection.
|
|
979
|
+
*/
|
|
980
|
+
commit(hits: readonly SelectInfo[], modifiers: SelectModifiers,
|
|
981
|
+
/** Every span the gesture produced, **topmost first** — see
|
|
982
|
+
* `<MultiSelector onSelect>`. Empty for a click, or for a sweep that
|
|
983
|
+
* covered nothing. */
|
|
984
|
+
spans: readonly SpanSelection[]): void;
|
|
985
|
+
}
|
|
602
986
|
/** One tracker readout point — a dot + value the overlay draws at the cursor. */
|
|
603
987
|
export interface TrackerSample {
|
|
604
988
|
/** The sample's time (epoch ms); the dot sits at `xScale(x)`. */
|
|
@@ -676,6 +1060,125 @@ export interface TrackerSource {
|
|
|
676
1060
|
* a selection survives a streaming data update where a sample `key` would go
|
|
677
1061
|
* stale. Only layers that carry an `id` are selectable (see {@link RowLayer.hitTest}).
|
|
678
1062
|
*/
|
|
1063
|
+
/**
|
|
1064
|
+
* The keyboard modifiers held during the click that produced a selection —
|
|
1065
|
+
* handed to `<Selector onSelect>` alongside the hit ([PND-MULTISEL]).
|
|
1066
|
+
*
|
|
1067
|
+
* **Why the library reports these instead of acting on them.** A consumer with
|
|
1068
|
+
* a multi-valued filter needs ⌘/Ctrl-click to mean "add to the selection", and
|
|
1069
|
+
* without the modifier state on the callback it simply cannot: the click has
|
|
1070
|
+
* already been reduced to a hit by the time it arrives, so every consumer is
|
|
1071
|
+
* forced to treat every click as a replace. Reporting the modifiers keeps the
|
|
1072
|
+
* *policy* with the consumer while removing the thing that made the policy
|
|
1073
|
+
* unexpressible.
|
|
1074
|
+
*
|
|
1075
|
+
* The library itself still applies no modifier semantics — a chart click sets
|
|
1076
|
+
* the single hit as it always has. A consumer that wants add/toggle reads
|
|
1077
|
+
* {@link additive} and drives the controlled `selected` set itself.
|
|
1078
|
+
*/
|
|
1079
|
+
export interface SelectModifiers {
|
|
1080
|
+
/**
|
|
1081
|
+
* The platform-idiomatic **"add to selection"** chord — `metaKey` on macOS,
|
|
1082
|
+
* `ctrlKey` elsewhere, surfaced as one boolean so every consumer doesn't
|
|
1083
|
+
* re-derive the same platform rule (and get it wrong on one of the two).
|
|
1084
|
+
* Prefer this over the raw keys unless you specifically want one of them.
|
|
1085
|
+
*/
|
|
1086
|
+
readonly additive: boolean;
|
|
1087
|
+
readonly ctrlKey: boolean;
|
|
1088
|
+
readonly metaKey: boolean;
|
|
1089
|
+
/**
|
|
1090
|
+
* **Note the conflict:** `shift` is already the drag chord for
|
|
1091
|
+
* `<ChartContainer regionSelectModifier="shift">` on a continuous axis, so a
|
|
1092
|
+
* shift-click there may also be the start of a region drag. Reported for
|
|
1093
|
+
* completeness; think before you give it a second meaning. (There is
|
|
1094
|
+
* deliberately no derived `range` flag for this reason — an ordinal range
|
|
1095
|
+
* gesture is [PND-CATRANGE], not a modifier.)
|
|
1096
|
+
*/
|
|
1097
|
+
readonly shiftKey: boolean;
|
|
1098
|
+
readonly altKey: boolean;
|
|
1099
|
+
}
|
|
1100
|
+
/**
|
|
1101
|
+
* A mounted `<Selector>` as the container holds it — the reporting callbacks
|
|
1102
|
+
* plus the mount scope (interaction RFC §7 / A4.2). **A selector both reports
|
|
1103
|
+
* and, optionally, owns controlled state** (`selected` / `hovered` — A10.3):
|
|
1104
|
+
* the gesture and the state it drives are declared on the same mount.
|
|
1105
|
+
*
|
|
1106
|
+
* Registered via {@link ContainerFrame.registerSelector}, the same idiom as
|
|
1107
|
+
* `registerCursor`. Internal (not exported from `index.ts`) — `<Selector>`'s
|
|
1108
|
+
* props are the public surface.
|
|
1109
|
+
*/
|
|
1110
|
+
export interface SelectorEntry {
|
|
1111
|
+
/** Report the mark under the pointer (`null` on leaving every mark). */
|
|
1112
|
+
readonly onHover: ((hit: SelectInfo | null) => void) | undefined;
|
|
1113
|
+
/** Report the clicked mark plus the modifiers held. The library applies no
|
|
1114
|
+
* policy to them and holds no set — see {@link SelectModifiers}. */
|
|
1115
|
+
readonly onSelect: ((hit: SelectInfo | null, modifiers?: SelectModifiers) => void) | undefined;
|
|
1116
|
+
/**
|
|
1117
|
+
* Whether this entry is a mounted `<MultiSelector>` (RFC §8) — the flag the
|
|
1118
|
+
* sweep gesture resolves on: **mounting one is what arms the sweep drag** on
|
|
1119
|
+
* the plot, exactly as mounting any selector is what arms the click (§7.1).
|
|
1120
|
+
* `<Selector>` registers `false`.
|
|
1121
|
+
*/
|
|
1122
|
+
readonly multi: boolean;
|
|
1123
|
+
/** `<MultiSelector onHover>` — the marks a live sweep covers (0/1 outside a
|
|
1124
|
+
* drag, mirroring the single-hit channel). Plural entries only. */
|
|
1125
|
+
readonly onHoverMany?: ((hits: readonly SelectInfo[]) => void) | undefined;
|
|
1126
|
+
/** `<MultiSelector onSelect>` — RFC A5.2's `(hits, modifiers, span)`. A
|
|
1127
|
+
* plain click reports `([hit] | [], modifiers, null)`; a sweep release
|
|
1128
|
+
* reports the covered marks plus the span they demote to. Plural entries
|
|
1129
|
+
* only. */
|
|
1130
|
+
readonly onSelectMany?: ((hits: readonly SelectInfo[], modifiers: SelectModifiers | undefined, spans: readonly SpanSelection[]) => void) | undefined;
|
|
1131
|
+
/** `<MultiSelector sequence>` — the sweep's bucket snap, folded into the
|
|
1132
|
+
* container's shared snap-bucket channel (as `<RangeCursor sequence>` is). */
|
|
1133
|
+
readonly sequence?: Sequence | BoundedSequence | undefined;
|
|
1134
|
+
/** Mount scope: a row's key when mounted inside a `<ChartRow>` (that row's
|
|
1135
|
+
* clicks only), `null` when mounted at the container (every row). */
|
|
1136
|
+
readonly rowKey: symbol | null;
|
|
1137
|
+
/**
|
|
1138
|
+
* Whether the **gesture** is active (`<Selector enabled>`, A10.2). `false`
|
|
1139
|
+
* means the plot behaves as if this entry were unmounted — no hit-testing,
|
|
1140
|
+
* no callbacks fire, no sweep arms — while the entry may still be the
|
|
1141
|
+
* chart's controlled-state owner below. This is what makes
|
|
1142
|
+
* `<Selector enabled={false} selected={sel} />` the "highlight only, no
|
|
1143
|
+
* plot gesture" configuration: one component, gesture off, state on.
|
|
1144
|
+
*/
|
|
1145
|
+
readonly gestureEnabled: boolean;
|
|
1146
|
+
/** Whether `selected` was explicitly passed to this mount — distinguishes
|
|
1147
|
+
* "controlled with nothing selected" (`null`) from "not the state owner"
|
|
1148
|
+
* (`undefined`), the same three-state shape `selected` itself needs. */
|
|
1149
|
+
readonly declaresSelected: boolean;
|
|
1150
|
+
/** `<Selector selected>` / `<MultiSelector selected>` (A10.3) — meaningful
|
|
1151
|
+
* only when {@link declaresSelected} is true. */
|
|
1152
|
+
readonly selected?: SelectInfo | readonly SelectionEntry[] | null | undefined;
|
|
1153
|
+
/** As {@link declaresSelected}, for `hovered`. */
|
|
1154
|
+
readonly declaresHovered: boolean;
|
|
1155
|
+
/** `<Selector hovered>` / `<MultiSelector hovered>` (A10.3) — meaningful
|
|
1156
|
+
* only when {@link declaresHovered} is true. */
|
|
1157
|
+
readonly hovered?: SelectInfo | readonly SelectInfo[] | null | undefined;
|
|
1158
|
+
}
|
|
1159
|
+
/**
|
|
1160
|
+
* The span a completed `<RangeCursor>` drag reports to `onDragRelease` —
|
|
1161
|
+
* **one uniform shape with an optional y** (interaction RFC A3.3), not the
|
|
1162
|
+
* bare pair the legacy `onRegionSelect` used, and not a polymorphic union a
|
|
1163
|
+
* consumer must narrow.
|
|
1164
|
+
*
|
|
1165
|
+
* `x` is `[lo, hi]` in **axis units** — epoch ms on a time axis, the axis
|
|
1166
|
+
* value (strike, distance, …) on a value axis — snapped to the cursor's
|
|
1167
|
+
* buckets when it has a `sequence` (or a histogram's bins), else the raw drag
|
|
1168
|
+
* span. `y` is **absent on today's 1-D layers**; the 2-D drag (scatter / heat
|
|
1169
|
+
* map — [PND-INTERACT2D]) will populate it *without a breaking change*, which
|
|
1170
|
+
* is the whole reason the 1-D payload is already an object.
|
|
1171
|
+
*
|
|
1172
|
+
* The x pair feeds `ChartContainer.range` directly (it accepts a
|
|
1173
|
+
* `readonly [number, number]`), so drag-to-zoom is
|
|
1174
|
+
* `onDragRelease={(s) => setRange(s.x)}`; a time-axis consumer who wants a
|
|
1175
|
+
* `TimeRange` constructs one from the pair.
|
|
1176
|
+
*/
|
|
1177
|
+
export interface RangeSpan {
|
|
1178
|
+
readonly x: readonly [number, number];
|
|
1179
|
+
/** Present only on a 2-D drag (scatter / heat map) — not yet emitted. */
|
|
1180
|
+
readonly y?: readonly [number, number];
|
|
1181
|
+
}
|
|
679
1182
|
export interface SelectInfo {
|
|
680
1183
|
/**
|
|
681
1184
|
* The **series identity** — the layer's `id` prop. The selection / dedup /
|
|
@@ -720,6 +1223,77 @@ export interface SelectInfo {
|
|
|
720
1223
|
*/
|
|
721
1224
|
readonly mark?: string;
|
|
722
1225
|
}
|
|
1226
|
+
/**
|
|
1227
|
+
* A **span selection** — a compact range descriptor over one layer's marks, the
|
|
1228
|
+
* second currency `selected` accepts beside per-mark {@link SelectInfo} entries
|
|
1229
|
+
* (interaction RFC A5.2). Where a mark entry names one mark, a span names
|
|
1230
|
+
* *every* mark of layer {@link id} inside its extent — the shape a sweep
|
|
1231
|
+
* gesture commits, and the shape a consumer with a range-valued filter already
|
|
1232
|
+
* has. Membership is evaluated per draw against the mark's own channels, so a
|
|
1233
|
+
* span costs O(1) per mark however many marks it covers.
|
|
1234
|
+
*
|
|
1235
|
+
* **The containment rule, stated once** (evaluated by `selectionContains` and,
|
|
1236
|
+
* identically, by every layer's draw): a mark is inside a span when the span's
|
|
1237
|
+
* `id` is the mark's layer, the mark's **key** (its `SelectInfo.key` — the bin
|
|
1238
|
+
* axis position in axis units) lies in the **half-open** interval
|
|
1239
|
+
* `x[0] <= key < x[1]`, the mark's **value** (its `SelectInfo.value`) lies in
|
|
1240
|
+
* `y[0] <= value < y[1]` when {@link y} is present, and the mark's **label**
|
|
1241
|
+
* (its `SelectInfo.label` — the row/group name on a 2-D-ordinal layer) is a
|
|
1242
|
+
* member of {@link rows} when present.
|
|
1243
|
+
*
|
|
1244
|
+
* **Half-open, deliberately** — the pond bucket convention (`[begin, end)`),
|
|
1245
|
+
* and the half the edge rule needs: contiguous interval marks share edges
|
|
1246
|
+
* (`end[i] === begin[i+1]`), so a sweep that captures marks `i..j` by
|
|
1247
|
+
* *intersection* stores the snapped-outward edges `[begin(i), end(j)]`, and the
|
|
1248
|
+
* half-open key test then reproduces exactly the captured set — `begin(j+1) ===
|
|
1249
|
+
* end(j)` falls out on the open side (RFC A7.6). A closed test would light the
|
|
1250
|
+
* first mark *past* the sweep on every shared edge.
|
|
1251
|
+
*
|
|
1252
|
+
* **Two optional second dimensions, not one** (RFC A5.3): {@link y} is a
|
|
1253
|
+
* numeric interval for a continuous × continuous layer (scatter), where the
|
|
1254
|
+
* second coordinate is the mark's plotted value. {@link rows} is a label set
|
|
1255
|
+
* for a continuous × ordinal layer (heat map), whose second coordinate is an
|
|
1256
|
+
* ordinal row **name** — a numeric y-interval there would be untestable from a
|
|
1257
|
+
* hit (the hit carries no slot number) and unstable under a row reorder. Do not
|
|
1258
|
+
* conflate them.
|
|
1259
|
+
*/
|
|
1260
|
+
export interface SpanSelection {
|
|
1261
|
+
/** Discriminant against {@link SelectInfo} (which has no `kind`). */
|
|
1262
|
+
readonly kind: 'span';
|
|
1263
|
+
/** The **layer** whose marks the span covers — a layer `id`, exactly as
|
|
1264
|
+
* {@link SelectInfo.id}. A span never matches marks of another layer. */
|
|
1265
|
+
readonly id: string;
|
|
1266
|
+
/**
|
|
1267
|
+
* The key-axis extent in **axis units** (epoch ms on a time axis, the axis
|
|
1268
|
+
* value on a value axis), half-open `[lo, hi)` against each mark's
|
|
1269
|
+
* {@link SelectInfo.key}. This is the layer's **bin/key axis** whatever the
|
|
1270
|
+
* orientation — a horizontal heat map's bins run down the screen, but their
|
|
1271
|
+
* keys (and so this interval) stay in bin-axis units. Must be ordered
|
|
1272
|
+
* `lo <= hi`; a reversed or empty pair matches nothing.
|
|
1273
|
+
*/
|
|
1274
|
+
readonly x: readonly [number, number];
|
|
1275
|
+
/**
|
|
1276
|
+
* Optional **value-axis** interval, half-open against each mark's
|
|
1277
|
+
* {@link SelectInfo.value} — the continuous second dimension of a 2-D sweep
|
|
1278
|
+
* on a scatter (RFC A3.3/A5.3). Omit for 1-D layers.
|
|
1279
|
+
*/
|
|
1280
|
+
readonly y?: readonly [number, number];
|
|
1281
|
+
/**
|
|
1282
|
+
* Optional **row label set**, matched against each mark's
|
|
1283
|
+
* {@link SelectInfo.label} — the ordinal second dimension of a 2-D sweep on
|
|
1284
|
+
* a heat map (RFC A5.3), stable under a row reorder because it names rows
|
|
1285
|
+
* rather than numbering slots. Omit for 1-D layers.
|
|
1286
|
+
*/
|
|
1287
|
+
readonly rows?: readonly string[];
|
|
1288
|
+
}
|
|
1289
|
+
/**
|
|
1290
|
+
* One entry of a plural `selected` — a single mark ({@link SelectInfo}) or a
|
|
1291
|
+
* whole range of one layer's marks ({@link SpanSelection}). The two currencies
|
|
1292
|
+
* of interaction RFC A5.2: clicks produce marks, sweeps produce a span (plus
|
|
1293
|
+
* the marks it covered, for demote-on-edit). Discriminate with
|
|
1294
|
+
* `isSpanSelection` / the `kind` field.
|
|
1295
|
+
*/
|
|
1296
|
+
export type SelectionEntry = SelectInfo | SpanSelection;
|
|
723
1297
|
/** The hover snapshot handed to `onTrackerChanged` — the cursor time + every
|
|
724
1298
|
* series' value there, so a consumer can render the readout outside the chart. */
|
|
725
1299
|
export interface TrackerInfo {
|
|
@@ -743,6 +1317,267 @@ export interface TrackerInfo {
|
|
|
743
1317
|
* snap to the series (the axis pills read like ticks), not the raw mouse Y.
|
|
744
1318
|
*/
|
|
745
1319
|
export type CursorMode = 'none' | 'line' | 'point' | 'inline' | 'flag' | 'crosshair' | 'region';
|
|
1320
|
+
/**
|
|
1321
|
+
* How a cursor wants the shared `cursorX` snapped, **declared, resolved by the
|
|
1322
|
+
* container** (interaction RFC A2.3). The x-snap consults each layer's
|
|
1323
|
+
* `sampleAt` in the hovered row and writes the result into the shared
|
|
1324
|
+
* {@link CursorFrame.cursorX} every other row reads — a cursor component has
|
|
1325
|
+
* neither the layers nor the right to write that value, so it declares the
|
|
1326
|
+
* policy and the container resolves it. Container resolves; slots draw.
|
|
1327
|
+
*
|
|
1328
|
+
* - `'none'` — the raw pointer x.
|
|
1329
|
+
* - `'sample'` — snap to the nearest data sample's x (the crosshair).
|
|
1330
|
+
* - `'sequence'` — bucket-shaped: the *rendering* snaps to the realized
|
|
1331
|
+
* sequence buckets (the range cursor's band); `cursorX` itself stays raw.
|
|
1332
|
+
*/
|
|
1333
|
+
export type CursorSnapX = 'none' | 'sample' | 'sequence';
|
|
1334
|
+
/**
|
|
1335
|
+
* One resolved per-series measurement at the cursor — **finished numbers, not
|
|
1336
|
+
* raw materials** (interaction RFC A2.3): the sample's plot pixels, the axis it
|
|
1337
|
+
* scales against (id + side, so a pill can hug the right gutter), and its value
|
|
1338
|
+
* already formatted by that axis's formatter. A cursor slot draws these; it
|
|
1339
|
+
* never sees a scale, a format map, or an axis-side map.
|
|
1340
|
+
*/
|
|
1341
|
+
export interface ResolvedCursorSample {
|
|
1342
|
+
readonly px: number;
|
|
1343
|
+
readonly py: number;
|
|
1344
|
+
readonly axisId: string;
|
|
1345
|
+
readonly side: 'left' | 'right';
|
|
1346
|
+
readonly formatted: string;
|
|
1347
|
+
readonly color: string;
|
|
1348
|
+
readonly label: string;
|
|
1349
|
+
}
|
|
1350
|
+
/** A resolved consolidated multi-value flag (a {@link RowLayer.cursorFlag}
|
|
1351
|
+
* layer — the BoxPlot): its anchor pixels + the formatted, coloured lines. */
|
|
1352
|
+
export interface ResolvedCursorFlag {
|
|
1353
|
+
readonly px: number;
|
|
1354
|
+
readonly topPy: number;
|
|
1355
|
+
readonly lines: readonly {
|
|
1356
|
+
readonly text: string;
|
|
1357
|
+
readonly color: string;
|
|
1358
|
+
}[];
|
|
1359
|
+
}
|
|
1360
|
+
/**
|
|
1361
|
+
* The frame handed to a {@link CursorSpec}'s render slots — resolved geometry
|
|
1362
|
+
* and finished measurements (RFC A2.3), never scales or format maps.
|
|
1363
|
+
*
|
|
1364
|
+
* **Internal for now** (deliberately not exported from `index.ts`): RFC Q3
|
|
1365
|
+
* publishes the cursor contract only after every built-in preset — and the SR
|
|
1366
|
+
* gapped crosshair — is written against it. The presets in `cursors.tsx` are
|
|
1367
|
+
* that litmus; until it passes, this shape may still move.
|
|
1368
|
+
*/
|
|
1369
|
+
export interface ResolvedCursorFrame {
|
|
1370
|
+
/** The shared plot-pixel x ({@link CursorFrame.cursorX}) — may be out of
|
|
1371
|
+
* `[0, plotWidth]` (a controlled tracker extrapolated); slots gate. */
|
|
1372
|
+
readonly cursorX: number | null;
|
|
1373
|
+
/** The hovered plot-pixel y (row-local; see {@link CursorFrame.cursorY}). */
|
|
1374
|
+
readonly cursorY: number | null;
|
|
1375
|
+
/** The **renderer's own** row, `null` in the x-axis slot… */
|
|
1376
|
+
readonly rowKey: symbol | null;
|
|
1377
|
+
/** …alongside the **hovered** row (A1.3: a slot needs both to know whether
|
|
1378
|
+
* it is drawing in the row the pointer is in). */
|
|
1379
|
+
readonly hoveredRowKey: symbol | null;
|
|
1380
|
+
/** Per-series resolved measurements at the cursor time (empty when the
|
|
1381
|
+
* effective cursors declared no need for them, or nothing is hovered). */
|
|
1382
|
+
readonly samples: readonly ResolvedCursorSample[];
|
|
1383
|
+
/** Resolved consolidated flags (BoxPlot) — the flag cursor's one-chip form. */
|
|
1384
|
+
readonly flags: readonly ResolvedCursorFlag[];
|
|
1385
|
+
/**
|
|
1386
|
+
* The **raw pointer**'s y resolved against the row's default axis — position,
|
|
1387
|
+
* formatted value, and axis side — or `null` when this row isn't hovered.
|
|
1388
|
+
* The free (non-snapping) crosshair reads this; it is resolved here because a
|
|
1389
|
+
* slot has no `yScale.invert` to do it itself.
|
|
1390
|
+
*/
|
|
1391
|
+
readonly pointer: {
|
|
1392
|
+
readonly py: number;
|
|
1393
|
+
readonly formatted: string;
|
|
1394
|
+
readonly side: 'left' | 'right';
|
|
1395
|
+
} | null;
|
|
1396
|
+
/** The range cursor's **band** under the pointer (bucket-snapped via the
|
|
1397
|
+
* declared sequence, else the drag span), as clamped plot pixels; `null`
|
|
1398
|
+
* when nothing to shade. Resolved by the container from `regionSpan`. */
|
|
1399
|
+
readonly band: {
|
|
1400
|
+
readonly x0: number;
|
|
1401
|
+
readonly x1: number;
|
|
1402
|
+
} | null;
|
|
1403
|
+
/**
|
|
1404
|
+
* The **transposed band** — the same brush over a layer that declares
|
|
1405
|
+
* `sweepAxis: 'y'` (a horizontal `<BarChart>`, whose bins run down the
|
|
1406
|
+
* screen), as plot pixels on the row's own y axis. `null` in every other
|
|
1407
|
+
* state, including an x sweep, which paints {@link band}.
|
|
1408
|
+
*
|
|
1409
|
+
* Row-local for {@link rect}'s reason: a y interval only means anything
|
|
1410
|
+
* against the axis that measured it, so the row owning the drag resolves it
|
|
1411
|
+
* and the others stay `null`. It is drawn by the same `renderBrushBand` with
|
|
1412
|
+
* the geometry transposed — one renderer, so the two orientations of one
|
|
1413
|
+
* gesture cannot drift (§8.1).
|
|
1414
|
+
*/
|
|
1415
|
+
readonly bandY: {
|
|
1416
|
+
readonly y0: number;
|
|
1417
|
+
readonly y1: number;
|
|
1418
|
+
} | null;
|
|
1419
|
+
/** Degenerate range cursor (no buckets, not mid-drag): draw a plain line. */
|
|
1420
|
+
readonly bandLine: boolean;
|
|
1421
|
+
/**
|
|
1422
|
+
* Whether the band's extent is being **dragged** right now — a range drag
|
|
1423
|
+
* anchored, or a `<MultiSelector>` sweep past its slop — rather than merely
|
|
1424
|
+
* previewing the block under the pointer.
|
|
1425
|
+
*
|
|
1426
|
+
* The band renders in both states; this is what separates them. The edges
|
|
1427
|
+
* are the gesture's grabbed boundary, so they draw only while there is a
|
|
1428
|
+
* gesture: at rest the band is a *preview*, and edging a preview reads as a
|
|
1429
|
+
* committed range the user hasn't made yet.
|
|
1430
|
+
*/
|
|
1431
|
+
readonly bandDragging: boolean;
|
|
1432
|
+
/**
|
|
1433
|
+
* The **2-D brush rect** — a live `<MultiSelector>` sweep over a `twoD`
|
|
1434
|
+
* layer ({@link SweepSession.twoD}: a scatter, a heat map), as clamped plot
|
|
1435
|
+
* pixels. `null` in every other state, including a 1-D sweep, which paints
|
|
1436
|
+
* {@link band} instead.
|
|
1437
|
+
*
|
|
1438
|
+
* It is deliberately **not** a band with a y range bolted on. The band is
|
|
1439
|
+
* container state, so every row paints it from one anchor — right for an
|
|
1440
|
+
* x-range, which means the same thing in every row. A rect's y is only
|
|
1441
|
+
* meaningful in the row whose layer's axis it was measured against, so this
|
|
1442
|
+
* is resolved per row by the row that owns the drag, and stays `null` in
|
|
1443
|
+
* the others.
|
|
1444
|
+
*
|
|
1445
|
+
* **Unsorted, on purpose:** `(x0, y0)` is the corner the drag was anchored
|
|
1446
|
+
* at and `(x1, y1)` the corner under the pointer, so either may be the
|
|
1447
|
+
* larger. The renderer sorts for the box, but it needs the diagonal intact
|
|
1448
|
+
* to put the two crosshairs on the corners the user is actually holding.
|
|
1449
|
+
* (`x` is still the *snapped* band edge where the layer snaps — assigned to
|
|
1450
|
+
* whichever end the anchor is on.)
|
|
1451
|
+
*/
|
|
1452
|
+
readonly rect: {
|
|
1453
|
+
readonly x0: number;
|
|
1454
|
+
readonly x1: number;
|
|
1455
|
+
readonly y0: number;
|
|
1456
|
+
readonly y1: number;
|
|
1457
|
+
} | null;
|
|
1458
|
+
/**
|
|
1459
|
+
* Draw the **resting** 2-D brush — a small grey `+` at the pointer, the
|
|
1460
|
+
* rect gesture's answer to {@link band}'s resting block preview.
|
|
1461
|
+
*
|
|
1462
|
+
* Small on purpose, and not a crosshair in the usual sense. A full-plot
|
|
1463
|
+
* crosshair is a value-reading instrument: it exists to project the pointer
|
|
1464
|
+
* onto both axes. This one marks *a corner a rect would start from*, and
|
|
1465
|
+
* the rect draws its own edges out to those axes the moment a drag begins —
|
|
1466
|
+
* so plot-spanning rules would add two more lines to a picture that is
|
|
1467
|
+
* about to have them anyway. The compact `+` says "here", which is all a
|
|
1468
|
+
* corner needs to say.
|
|
1469
|
+
*/
|
|
1470
|
+
readonly restingCross: boolean;
|
|
1471
|
+
/** The cursor time, formatted by the container's readout channel
|
|
1472
|
+
* (`formatReadout ?? formatTime` in a row; the axis's own resolved readout
|
|
1473
|
+
* formatter in the x-axis slot). `null` when out of bounds / not wanted. */
|
|
1474
|
+
readonly formattedTime: string | null;
|
|
1475
|
+
readonly plotWidth: number;
|
|
1476
|
+
/** The renderer's row height (`0` in the x-axis slot). */
|
|
1477
|
+
readonly rowHeight: number;
|
|
1478
|
+
/** Whether this row is the topmost — the shared time chip shows once, here. */
|
|
1479
|
+
readonly isFirstRow: boolean;
|
|
1480
|
+
readonly theme: ChartTheme;
|
|
1481
|
+
/** X-axis slot placement (set only when invoking {@link CursorSpec.renderXAxis}):
|
|
1482
|
+
* which side the axis strip is on and the pill's tick-label offset. */
|
|
1483
|
+
readonly xAxis: {
|
|
1484
|
+
readonly onTop: boolean;
|
|
1485
|
+
readonly pillOffset: number;
|
|
1486
|
+
} | null;
|
|
1487
|
+
}
|
|
1488
|
+
/**
|
|
1489
|
+
* A mounted cursor's contract with the container (interaction RFC A2.3):
|
|
1490
|
+
* **declared** snap plus up to three render slots taking resolved geometry.
|
|
1491
|
+
* The container resolves (`cursorX` snapping, per-sample measurements, the
|
|
1492
|
+
* band); the slots draw. Registered via {@link ContainerFrame.registerCursor}
|
|
1493
|
+
* — the same idiom as `registerAxis` / `registerLayer`.
|
|
1494
|
+
*
|
|
1495
|
+
* Internal for now, like {@link ResolvedCursorFrame} (RFC Q3).
|
|
1496
|
+
*/
|
|
1497
|
+
export interface CursorSpec {
|
|
1498
|
+
/** How the shared `cursorX` snaps while this cursor owns the hovered row. */
|
|
1499
|
+
readonly snapX?: CursorSnapX;
|
|
1500
|
+
/** SVG into the row's cursor overlay (above the data canvas — hovering never
|
|
1501
|
+
* repaints the canvas). */
|
|
1502
|
+
renderPlot?(f: ResolvedCursorFrame): ReactNode;
|
|
1503
|
+
/**
|
|
1504
|
+
* DOM into the row's overlay, above the SVG — the value chips (inline /
|
|
1505
|
+
* flag) and the in-plot time readout. NOT in RFC A2.3's three-slot shape:
|
|
1506
|
+
* the production flag/inline cursors are DOM chips positioned in plot space,
|
|
1507
|
+
* which "SVG into the overlay" cannot express — the same gap A1.3 called on
|
|
1508
|
+
* §5. Kept internal; the published contract must resolve this (see the
|
|
1509
|
+
* step-2 notes in the charts plan).
|
|
1510
|
+
*/
|
|
1511
|
+
renderPlotHtml?(f: ResolvedCursorFrame): ReactNode;
|
|
1512
|
+
/** DOM, per row: the axis-edge **value pill** (positioned via `axisPillX`,
|
|
1513
|
+
* overflowing the plot into its axis gutter). */
|
|
1514
|
+
renderYGutter?(f: ResolvedCursorFrame): ReactNode;
|
|
1515
|
+
/** DOM, on the shared x axis: the **time pill** (+ its connector). `<XAxis>`
|
|
1516
|
+
* shows it whenever the hovered row's effective cursor registers this slot
|
|
1517
|
+
* — the mount is the gate, not a mode string. */
|
|
1518
|
+
renderXAxis?(f: ResolvedCursorFrame): ReactNode;
|
|
1519
|
+
}
|
|
1520
|
+
/** What a cursor needs the container to resolve per pointer move — declared at
|
|
1521
|
+
* registration so a line-only cursor never pays for per-sample measurement. */
|
|
1522
|
+
export interface CursorWants {
|
|
1523
|
+
/** Per-series {@link ResolvedCursorSample}s (dots, chips, the reticle pick). */
|
|
1524
|
+
readonly samples: boolean;
|
|
1525
|
+
/** Consolidated {@link ResolvedCursorFlag}s (the flag cursor only). */
|
|
1526
|
+
readonly flags: boolean;
|
|
1527
|
+
/** The range band (+ the degenerate band line). */
|
|
1528
|
+
readonly band: boolean;
|
|
1529
|
+
/** The raw-pointer readout (the free crosshair). */
|
|
1530
|
+
readonly pointer: boolean;
|
|
1531
|
+
/** The formatted in-plot cursor time (`showTime` presets). */
|
|
1532
|
+
readonly time: boolean;
|
|
1533
|
+
}
|
|
1534
|
+
/**
|
|
1535
|
+
* A registered cursor as the container holds it: the {@link CursorSpec} plus
|
|
1536
|
+
* the resolution inputs that must live on the registration rather than the
|
|
1537
|
+
* spec — scope, gesture ownership, and the declared needs.
|
|
1538
|
+
*/
|
|
1539
|
+
export interface CursorEntry {
|
|
1540
|
+
readonly spec: CursorSpec;
|
|
1541
|
+
/** Mount scope: a row's key when mounted inside a `<ChartRow>` (the per-row
|
|
1542
|
+
* override), `null` when mounted at the container (the default for all rows). */
|
|
1543
|
+
readonly rowKey: symbol | null;
|
|
1544
|
+
/** Synthesized by the deprecation shim from the legacy string props. A scope
|
|
1545
|
+
* with any non-legacy (component-mounted) cursor drops its legacy entries —
|
|
1546
|
+
* mounting a component overrides the string prop during the window. */
|
|
1547
|
+
readonly legacy: boolean;
|
|
1548
|
+
/**
|
|
1549
|
+
* The shim entry nobody asked for: the container's `'line'` **default**,
|
|
1550
|
+
* synthesized with no `cursor` prop set. A mounted `<MultiSelector>`'s
|
|
1551
|
+
* resting block preview (the brush band as the resting cursor) replaces
|
|
1552
|
+
* only these — an explicitly chosen cursor, component-mounted or via the
|
|
1553
|
+
* legacy string prop, still wins.
|
|
1554
|
+
*/
|
|
1555
|
+
readonly implicit?: boolean;
|
|
1556
|
+
/**
|
|
1557
|
+
* Whether this cursor owns **snap and gesture** (RFC A2.5): at most one per
|
|
1558
|
+
* scope (dev-warned otherwise), resolved to the hovered row's innermost
|
|
1559
|
+
* mount. Render-only presets (line/point/inline/flag) stack freely.
|
|
1560
|
+
*/
|
|
1561
|
+
readonly ownsGesture: boolean;
|
|
1562
|
+
readonly wants: CursorWants;
|
|
1563
|
+
/** The range cursor's bucket sequence (realized by the container into the
|
|
1564
|
+
* shared snap buckets — the `cursorSequence` successor). */
|
|
1565
|
+
readonly sequence?: Sequence | BoundedSequence | undefined;
|
|
1566
|
+
/** The range cursor's drag-release callback (the `onRegionSelect`
|
|
1567
|
+
* successor) — read by the brush recognizer (`resolveRangeDrag`), which
|
|
1568
|
+
* wraps it into the range-drag session. Only a `<RangeCursor>` sets it. */
|
|
1569
|
+
readonly onDragRelease?: ((span: RangeSpan) => void) | undefined;
|
|
1570
|
+
/** Whether the range drag is live — **resolved** at build time
|
|
1571
|
+
* (`enableDrag ?? !!onDragRelease`), so `false` here means frozen: the
|
|
1572
|
+
* gesture is off even though the callback is wired (§6's OFF switch). */
|
|
1573
|
+
readonly enableDrag?: boolean | undefined;
|
|
1574
|
+
/** The modifier the range drag needs (the `regionSelectModifier`
|
|
1575
|
+
* successor) — only enforced while pan is enabled. */
|
|
1576
|
+
readonly dragModifier?: 'shift' | undefined;
|
|
1577
|
+
/** The cursor's readout format (the `cursorFormat` successor) — resolved by
|
|
1578
|
+
* the container into the shared readout channel. */
|
|
1579
|
+
readonly format?: CursorFormat | undefined;
|
|
1580
|
+
}
|
|
746
1581
|
/** A registered layer plus the axis id it draws against. */
|
|
747
1582
|
export interface LayerEntry {
|
|
748
1583
|
readonly layer: RowLayer;
|