inkplot 0.0.0-stage → 1.0.0-beta.1
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/LICENSE +21 -0
- package/README.md +46 -2
- package/dist/adapt.d.ts +10 -0
- package/dist/adapt.js +34 -0
- package/dist/annotation.d.ts +120 -0
- package/dist/annotation.js +267 -0
- package/dist/aria.d.ts +24 -0
- package/dist/aria.js +31 -0
- package/dist/attrs.d.ts +81 -0
- package/dist/attrs.js +84 -0
- package/dist/axis.d.ts +137 -0
- package/dist/axis.js +185 -0
- package/dist/bars.d.ts +80 -0
- package/dist/bars.js +255 -0
- package/dist/boxplot.d.ts +57 -0
- package/dist/boxplot.js +177 -0
- package/dist/boxzoom.d.ts +39 -0
- package/dist/boxzoom.js +138 -0
- package/dist/brush.d.ts +60 -0
- package/dist/brush.js +185 -0
- package/dist/bucket.d.ts +22 -0
- package/dist/bucket.js +62 -0
- package/dist/callout.d.ts +30 -0
- package/dist/callout.js +98 -0
- package/dist/chart.d.ts +126 -0
- package/dist/chart.js +257 -0
- package/dist/crosshair.d.ts +28 -0
- package/dist/crosshair.js +78 -0
- package/dist/cursor.d.ts +76 -0
- package/dist/cursor.js +344 -0
- package/dist/curve.d.ts +50 -0
- package/dist/curve.js +87 -0
- package/dist/curves.d.ts +104 -0
- package/dist/curves.js +375 -0
- package/dist/data.d.ts +209 -0
- package/dist/data.js +276 -0
- package/dist/decimation.d.ts +86 -0
- package/dist/decimation.js +410 -0
- package/dist/density.d.ts +55 -0
- package/dist/density.js +252 -0
- package/dist/dimension.d.ts +103 -0
- package/dist/dimension.js +205 -0
- package/dist/dom-platform.d.ts +53 -0
- package/dist/dom-platform.js +190 -0
- package/dist/element.d.ts +232 -0
- package/dist/element.js +414 -0
- package/dist/errors.d.ts +85 -0
- package/dist/errors.js +237 -0
- package/dist/events.d.ts +144 -0
- package/dist/events.js +19 -0
- package/dist/focus.d.ts +22 -0
- package/dist/focus.js +51 -0
- package/dist/furniture.d.ts +70 -0
- package/dist/furniture.js +166 -0
- package/dist/geometry.d.ts +40 -0
- package/dist/geometry.js +7 -0
- package/dist/heatmap.d.ts +57 -0
- package/dist/heatmap.js +207 -0
- package/dist/hit.d.ts +103 -0
- package/dist/hit.js +428 -0
- package/dist/index.d.ts +73 -0
- package/dist/index.js +73 -0
- package/dist/input.d.ts +42 -0
- package/dist/input.js +166 -0
- package/dist/jsx-dev-runtime.d.ts +9 -0
- package/dist/jsx-dev-runtime.js +13 -0
- package/dist/jsx-runtime.d.ts +59 -0
- package/dist/jsx-runtime.js +81 -0
- package/dist/key.d.ts +118 -0
- package/dist/key.js +199 -0
- package/dist/legend.d.ts +36 -0
- package/dist/legend.js +220 -0
- package/dist/mapped.d.ts +80 -0
- package/dist/mapped.js +106 -0
- package/dist/marker.d.ts +143 -0
- package/dist/marker.js +213 -0
- package/dist/panzoom.d.ts +119 -0
- package/dist/panzoom.js +467 -0
- package/dist/plane.d.ts +173 -0
- package/dist/plane.js +370 -0
- package/dist/platform.d.ts +113 -0
- package/dist/platform.js +80 -0
- package/dist/png.d.ts +44 -0
- package/dist/png.js +71 -0
- package/dist/pointer.d.ts +54 -0
- package/dist/pointer.js +132 -0
- package/dist/quantile.d.ts +3 -0
- package/dist/quantile.js +11 -0
- package/dist/radial.d.ts +116 -0
- package/dist/radial.js +336 -0
- package/dist/react.d.ts +441 -0
- package/dist/react.js +252 -0
- package/dist/registry.d.ts +2 -0
- package/dist/registry.js +51 -0
- package/dist/renderer.d.ts +191 -0
- package/dist/renderer.js +164 -0
- package/dist/scale.d.ts +37 -0
- package/dist/scale.js +19 -0
- package/dist/scales.d.ts +69 -0
- package/dist/scales.js +134 -0
- package/dist/serialize.d.ts +77 -0
- package/dist/serialize.js +321 -0
- package/dist/series.d.ts +304 -0
- package/dist/series.js +513 -0
- package/dist/simple.d.ts +35 -0
- package/dist/simple.js +20 -0
- package/dist/sources.d.ts +116 -0
- package/dist/sources.js +237 -0
- package/dist/stack.d.ts +61 -0
- package/dist/stack.js +172 -0
- package/dist/stats.d.ts +38 -0
- package/dist/stats.js +159 -0
- package/dist/steps.d.ts +50 -0
- package/dist/steps.js +154 -0
- package/dist/stub.d.ts +3 -0
- package/dist/stub.js +5 -0
- package/dist/style-names.d.ts +1 -0
- package/dist/style-names.js +6 -0
- package/dist/summary.d.ts +44 -0
- package/dist/summary.js +58 -0
- package/dist/surface.d.ts +94 -0
- package/dist/surface.js +145 -0
- package/dist/table.d.ts +39 -0
- package/dist/table.js +37 -0
- package/dist/text.d.ts +23 -0
- package/dist/text.js +44 -0
- package/dist/theme.d.ts +174 -0
- package/dist/theme.js +73 -0
- package/dist/themes.d.ts +9 -0
- package/dist/themes.js +56 -0
- package/dist/time-intl.d.ts +194 -0
- package/dist/time-intl.js +144 -0
- package/dist/time-temporal.d.ts +236 -0
- package/dist/time-temporal.js +58 -0
- package/dist/time.d.ts +216 -0
- package/dist/time.js +154 -0
- package/dist/trend.d.ts +151 -0
- package/dist/trend.js +500 -0
- package/dist/web-components.d.ts +62 -0
- package/dist/web-components.js +264 -0
- package/package.json +53 -3
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import { SVG_NS, estimateTextWidth } from './platform.js';
|
|
2
|
+
/* oxlint-disable typescript/consistent-type-assertions -- the opaque `PlatformNode` handle is a DOM `Element` here, and DOM event targets are loosely typed: the casts sit at this one boundary */
|
|
3
|
+
/**
|
|
4
|
+
* A platform node is the library's opaque handle; in this platform it is always a DOM element. These views are the only places the
|
|
5
|
+
* handle is converted (the library's types do not mention DOM types, so they cannot say so themselves).
|
|
6
|
+
*/
|
|
7
|
+
const element = (node) => node;
|
|
8
|
+
const fromElement = (node) => node;
|
|
9
|
+
/**
|
|
10
|
+
* Draws into a live `<svg>` appended to a container, and reports the container's size and pointer input on it.
|
|
11
|
+
* The container needs a height of its own; without one the platform assumes 600 wide by 300 high.
|
|
12
|
+
* Pointer events are captured while a button is down. Once something in the chart listens for the pointer (the chart then
|
|
13
|
+
* subscribes through `onPointer`) the svg sets `touch-action: none`, so a drag or a wheel turn over the chart belongs to the
|
|
14
|
+
* chart (`setTouchMode('two-finger')` lets one finger scroll the page instead); until then it sets none, and the chart is a picture.
|
|
15
|
+
*/
|
|
16
|
+
export class DOMPlatform {
|
|
17
|
+
name = 'dom';
|
|
18
|
+
root;
|
|
19
|
+
schedule;
|
|
20
|
+
measureText;
|
|
21
|
+
#container;
|
|
22
|
+
#svg;
|
|
23
|
+
#fixed;
|
|
24
|
+
/** The container's document, seen with DOM types (`DOMContainer` keeps them out of the public signature). */
|
|
25
|
+
get #document() { return this.#container.ownerDocument; }
|
|
26
|
+
#cleanups = new Set();
|
|
27
|
+
constructor(container, options = {}) {
|
|
28
|
+
this.#container = container;
|
|
29
|
+
this.#fixed = options.size ?? null;
|
|
30
|
+
this.#svg = container.ownerDocument.createElementNS(SVG_NS, 'svg');
|
|
31
|
+
this.root = fromElement(this.#svg);
|
|
32
|
+
this.#svg.style.display = 'block';
|
|
33
|
+
this.#svg.style.userSelect = 'none';
|
|
34
|
+
container.appendChild(this.#svg);
|
|
35
|
+
switch (options.schedule ?? 'frame') {
|
|
36
|
+
case 'frame':
|
|
37
|
+
this.schedule = (cb) => { const id = requestAnimationFrame(() => cb()); return () => cancelAnimationFrame(id); };
|
|
38
|
+
break;
|
|
39
|
+
case 'microtask':
|
|
40
|
+
this.schedule = (cb) => { let live = true; queueMicrotask(() => live && cb()); return () => { live = false; }; };
|
|
41
|
+
break;
|
|
42
|
+
case 'manual':
|
|
43
|
+
break;
|
|
44
|
+
default:
|
|
45
|
+
this.schedule = options.schedule;
|
|
46
|
+
}
|
|
47
|
+
if (options.preciseText !== false) {
|
|
48
|
+
let context;
|
|
49
|
+
this.measureText = (text, font) => {
|
|
50
|
+
// One shared canvas context: measuring is the hot path of tick layout, and creating contexts is slow. A page with no
|
|
51
|
+
// canvas to measure on (jsdom, a locked-down browser) gets the estimate rather than a chart that cannot be drawn.
|
|
52
|
+
if (context === undefined)
|
|
53
|
+
context = this.#document.createElement('canvas').getContext('2d') ?? null;
|
|
54
|
+
if (!context)
|
|
55
|
+
return estimateTextWidth(text, font);
|
|
56
|
+
context.font = `${font.weight ?? 400} ${font.size}px ${font.family}`;
|
|
57
|
+
return { width: context.measureText(text).width, height: font.size * 1.2 };
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
/** `|| 600` / `|| 300`: a container that has not been laid out yet reports 0, which would give an unusable chart. */
|
|
62
|
+
size() {
|
|
63
|
+
return this.#fixed ?? { width: this.#container.clientWidth || 600, height: this.#container.clientHeight || 300 };
|
|
64
|
+
}
|
|
65
|
+
createNode(tag) { return fromElement(this.#svg.ownerDocument.createElementNS(SVG_NS, tag)); }
|
|
66
|
+
setAttribute(node, name, value) { element(node).setAttribute(name, String(value)); }
|
|
67
|
+
setText(node, text) { element(node).textContent = text; }
|
|
68
|
+
insertBefore(parent, node, before) { element(parent).insertBefore(element(node), before && element(before)); }
|
|
69
|
+
remove(node) { element(node).remove(); }
|
|
70
|
+
setCursor(cursor) { this.#svg.style.cursor = cursor; }
|
|
71
|
+
#touchMode = 'always';
|
|
72
|
+
#listening = false;
|
|
73
|
+
setTouchMode(mode) {
|
|
74
|
+
this.#touchMode = mode;
|
|
75
|
+
this.#applyTouchAction();
|
|
76
|
+
}
|
|
77
|
+
/** A chart that listens for pointer input takes the touches on it; one that does not is a picture, and the page keeps its scrolling. */
|
|
78
|
+
#applyTouchAction() {
|
|
79
|
+
this.#svg.style.touchAction = !this.#listening ? '' : this.#touchMode === 'always' ? 'none' : 'pan-x pan-y';
|
|
80
|
+
}
|
|
81
|
+
onResize(callback) {
|
|
82
|
+
if (this.#fixed)
|
|
83
|
+
return () => { };
|
|
84
|
+
const observer = new ResizeObserver(() => callback(this.size()));
|
|
85
|
+
observer.observe(this.#container);
|
|
86
|
+
return this.#track(() => observer.disconnect());
|
|
87
|
+
}
|
|
88
|
+
onPointer(callback) {
|
|
89
|
+
const svg = this.#svg;
|
|
90
|
+
// Coordinates are made relative to the svg here so no element needs to know where the chart sits on the page.
|
|
91
|
+
const send = (e, kind, extra = {}) => {
|
|
92
|
+
const box = svg.getBoundingClientRect();
|
|
93
|
+
return callback({
|
|
94
|
+
kind, x: e.clientX - box.left, y: e.clientY - box.top, buttons: e.buttons, deltaY: 0,
|
|
95
|
+
id: e.pointerId ?? 0, pointerType: e.pointerType === 'pen' || e.pointerType === 'touch' ? e.pointerType : 'mouse',
|
|
96
|
+
shift: e.shiftKey, ctrl: e.ctrlKey || e.metaKey, ...extra,
|
|
97
|
+
});
|
|
98
|
+
};
|
|
99
|
+
// Safari reports a trackpad pinch as gesture events (not as wheel events with ctrlKey, as other browsers do): each carries the
|
|
100
|
+
// total scale since the gesture began, which is turned here into the factor since the last event.
|
|
101
|
+
let gestureScale = 1;
|
|
102
|
+
// Handlers are typed by the event they are registered for; the table is widened to `Event` at its one point of use, below.
|
|
103
|
+
const listeners = [
|
|
104
|
+
['pointermove', (e) => send(e, 'move')],
|
|
105
|
+
['pointerdown', (e) => { svg.setPointerCapture(e.pointerId); send(e, 'down'); }],
|
|
106
|
+
['pointerup', (e) => send(e, 'up')],
|
|
107
|
+
['pointercancel', (e) => send(e, 'up')],
|
|
108
|
+
['pointerleave', (e) => send(e, 'leave')],
|
|
109
|
+
['wheel', (e) => {
|
|
110
|
+
// Wheel deltas arrive in lines or pages on some devices; normalise to pixels so zoom speed is consistent.
|
|
111
|
+
const unit = e.deltaMode === 1 ? 16 : e.deltaMode === 2 ? this.size().height : 1;
|
|
112
|
+
if (send(e, 'wheel', { deltaY: e.deltaY * unit, deltaX: e.deltaX * unit }))
|
|
113
|
+
e.preventDefault();
|
|
114
|
+
}, { passive: false }],
|
|
115
|
+
['gesturestart', (e) => { gestureScale = 1; e.preventDefault(); }],
|
|
116
|
+
['gesturechange', (e) => {
|
|
117
|
+
const scale = e.scale / gestureScale;
|
|
118
|
+
gestureScale = e.scale;
|
|
119
|
+
send(e, 'pinch', { scale });
|
|
120
|
+
e.preventDefault();
|
|
121
|
+
}, { passive: false }],
|
|
122
|
+
// With one finger the page scrolls (touch-action allows it); a second finger down means the chart has taken the gesture.
|
|
123
|
+
['touchmove', (e) => { if (this.#touchMode === 'two-finger' && e.touches.length > 1 && e.cancelable)
|
|
124
|
+
e.preventDefault(); }, { passive: false }],
|
|
125
|
+
];
|
|
126
|
+
for (const [type, handler, options] of listeners)
|
|
127
|
+
svg.addEventListener(type, handler, options);
|
|
128
|
+
this.#listening = true;
|
|
129
|
+
this.#applyTouchAction();
|
|
130
|
+
return this.#track(() => {
|
|
131
|
+
for (const [type, handler, options] of listeners)
|
|
132
|
+
svg.removeEventListener(type, handler, options);
|
|
133
|
+
this.#listening = false;
|
|
134
|
+
this.#applyTouchAction();
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
onKey(callback) {
|
|
138
|
+
const svg = this.#svg;
|
|
139
|
+
const handler = (kind) => (e) => {
|
|
140
|
+
// The browser's own shortcuts (copy, reload, switch tab) are never the chart's to take.
|
|
141
|
+
if (e.metaKey || (e.ctrlKey && e.key.length === 1))
|
|
142
|
+
return;
|
|
143
|
+
const used = callback({ kind, key: e.key, shift: e.shiftKey, ctrl: e.ctrlKey, alt: e.altKey, repeat: e.repeat }, fromElement(e.target));
|
|
144
|
+
if (used)
|
|
145
|
+
e.preventDefault();
|
|
146
|
+
};
|
|
147
|
+
const down = handler('down'), up = handler('up');
|
|
148
|
+
svg.addEventListener('keydown', down);
|
|
149
|
+
svg.addEventListener('keyup', up);
|
|
150
|
+
return this.#track(() => { svg.removeEventListener('keydown', down); svg.removeEventListener('keyup', up); });
|
|
151
|
+
}
|
|
152
|
+
onFocus(callback) {
|
|
153
|
+
const svg = this.#svg;
|
|
154
|
+
const into = (e) => callback({ node: fromElement(e.target), focused: true, visible: !!e.target?.matches?.(':focus-visible') });
|
|
155
|
+
const out = (e) => callback({ node: fromElement(e.target), focused: false, visible: false });
|
|
156
|
+
svg.addEventListener('focusin', into);
|
|
157
|
+
svg.addEventListener('focusout', out);
|
|
158
|
+
return this.#track(() => { svg.removeEventListener('focusin', into); svg.removeEventListener('focusout', out); });
|
|
159
|
+
}
|
|
160
|
+
/** A visually hidden live region beside the chart: SVG text is not reliably announced when it changes, an HTML status is. */
|
|
161
|
+
announce(text) {
|
|
162
|
+
if (!this.#live) {
|
|
163
|
+
const live = this.#document.createElement('div');
|
|
164
|
+
live.setAttribute('role', 'status');
|
|
165
|
+
live.setAttribute('aria-live', 'polite');
|
|
166
|
+
live.setAttribute('aria-atomic', 'true');
|
|
167
|
+
live.style.cssText = 'position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0';
|
|
168
|
+
this.#container.appendChild(live);
|
|
169
|
+
this.#live = live;
|
|
170
|
+
}
|
|
171
|
+
this.#live.textContent = text;
|
|
172
|
+
}
|
|
173
|
+
#live = null;
|
|
174
|
+
focus(node) { element(node).focus({ focusVisible: true }); }
|
|
175
|
+
dispose() {
|
|
176
|
+
// oxlint-disable-next-line unicorn/no-useless-spread -- a snapshot: each cleanup removes itself from the set while we walk it
|
|
177
|
+
for (const cleanup of [...this.#cleanups])
|
|
178
|
+
cleanup();
|
|
179
|
+
this.#svg.remove();
|
|
180
|
+
this.#live?.remove();
|
|
181
|
+
this.#live = null;
|
|
182
|
+
}
|
|
183
|
+
/** Registers a cleanup so `dispose()` releases every listener even if the caller never unsubscribed. */
|
|
184
|
+
#track(cleanup) {
|
|
185
|
+
const once = () => { if (this.#cleanups.delete(once))
|
|
186
|
+
cleanup(); };
|
|
187
|
+
this.#cleanups.add(once);
|
|
188
|
+
return once;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The element base class: a retained tree with three links per node, typed children, events and tree utilities.
|
|
3
|
+
*
|
|
4
|
+
* How the pieces fit together
|
|
5
|
+
* ───────────────────────────
|
|
6
|
+
* Elements form one tree (Chart → Plane → Axis/Series → data/renderers). Three systems live on top of it:
|
|
7
|
+
*
|
|
8
|
+
* 1. Events. A parent never calls a child's methods to lay it out or draw it; it sends an event (`measureChild`,
|
|
9
|
+
* `arrangeChild`, or `broadcast` for ATTACH/PAINT/POINTER/HOVER) and the child's `onEvent` answers. One extension
|
|
10
|
+
* point means subclasses stay small, and the same tree can be driven by tests, by an adapter, or by the real chart.
|
|
11
|
+
* 2. Drawing. Each element draws through its own `Surface` (created lazily, so non-drawing elements cost nothing),
|
|
12
|
+
* which keeps its platform nodes keyed and skips attribute writes that did not change. Elements do not know where
|
|
13
|
+
* their nodes sit among the others' — see "Placement" below.
|
|
14
|
+
* 3. Invalidation. Any change calls `invalidate()`, which walks to the root; the chart there schedules a flush that
|
|
15
|
+
* re-runs layout and paint over the whole tree. Re-running everything is cheap because Surface caches the output.
|
|
16
|
+
*
|
|
17
|
+
* Placement: the document order of SVG nodes must follow tree order (so a later sibling paints over an earlier one),
|
|
18
|
+
* yet elements are mounted, drawn and removed independently. Rather than store positions, each Surface asks the live
|
|
19
|
+
* tree on demand: `container()` (the nearest ancestor wrapper `<g>`, else the platform root) and `end()` (the first
|
|
20
|
+
* node of whatever element draws next). Because the answer is derived from the tree, inserting or removing an element
|
|
21
|
+
* anywhere cannot leave a stale position behind.
|
|
22
|
+
*/
|
|
23
|
+
import type { Chart } from './chart.ts';
|
|
24
|
+
import { type ChartEvent, type ChartEventInit } from './events.ts';
|
|
25
|
+
import type { LayoutRequest, Rect, Size } from './geometry.ts';
|
|
26
|
+
import type { Platform } from './platform.ts';
|
|
27
|
+
import type { Roots, SelectorName, Theme } from './theme.ts';
|
|
28
|
+
import { type Counters, Surface } from './surface.ts';
|
|
29
|
+
import { type JsxChild } from './attrs.ts';
|
|
30
|
+
/**
|
|
31
|
+
* Naming for an element, for people who cannot see it. `title` is its accessible name and `description` a longer account
|
|
32
|
+
* of it. `role` overrides the ARIA role the element would have, and `aria` sets any other ARIA attributes by name
|
|
33
|
+
* (`{ 'aria-roledescription': 'sparkline' }`, or `{ roledescription: 'sparkline' }`). See aria.ts.
|
|
34
|
+
*/
|
|
35
|
+
export interface Meta {
|
|
36
|
+
title?: string;
|
|
37
|
+
description?: string;
|
|
38
|
+
role?: string;
|
|
39
|
+
aria?: Record<string, string | number | boolean>;
|
|
40
|
+
}
|
|
41
|
+
/** What every element accepts. */
|
|
42
|
+
export interface BaseAttributes {
|
|
43
|
+
/** Lets other elements (and JSON/adapter configs) refer to this one by name; see `resolveRef`. */
|
|
44
|
+
id?: string;
|
|
45
|
+
meta?: Meta;
|
|
46
|
+
/** False hides the element and everything inside it without removing it, so toggling is cheap and keeps state. */
|
|
47
|
+
visible?: boolean;
|
|
48
|
+
/**
|
|
49
|
+
* Looks for this element and everything below it, by selector (see theme.ts). Nearer blocks beat farther ones, and every
|
|
50
|
+
* block beats the chart's base theme. The element's own attributes that share a style property's name (`stroke`, `fill`…)
|
|
51
|
+
* are the same thing set directly, and beat any block.
|
|
52
|
+
*/
|
|
53
|
+
style?: Theme;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Any element, whatever its children and attributes. `any` is required here: element types are invariant in their children
|
|
57
|
+
* (they appear in both the constructor and `children`) and in their attributes, so no concrete type, not even `unknown`, accepts every element.
|
|
58
|
+
*/
|
|
59
|
+
export type AnyElement = ChartElement<any, any, any>;
|
|
60
|
+
/**
|
|
61
|
+
* The parameter list of "any constructor". Constructor parameters are contravariant, so `unknown[]` would reject every class with a typed
|
|
62
|
+
* parameter; `never[]` would make the class uncallable. `any[]` is the only list every constructor accepts.
|
|
63
|
+
*/
|
|
64
|
+
export type AnyArgs = any[];
|
|
65
|
+
/**
|
|
66
|
+
* Attributes first, then the children as declared by the parent. A parent declares them as a tuple type:
|
|
67
|
+
* `[Scale]` is exactly one scale, `[DataNode, ...SeriesRenderer[]]` is one source then any renderers,
|
|
68
|
+
* `Axis[]` is any number. Attributes may be left out when none are required.
|
|
69
|
+
*/
|
|
70
|
+
export type ElementArgs<A, Children extends readonly AnyElement[]> = [attrs: A, ...children: Children] | ({} extends A ? [...children: Children] : never);
|
|
71
|
+
export type ElementClass<T extends AnyElement = AnyElement> = abstract new (...args: AnyArgs) => T;
|
|
72
|
+
/** A class whose instances are (some of) `T`: for a union, a class of any one member. */
|
|
73
|
+
export type ClassFor<T> = T extends unknown ? abstract new (...args: AnyArgs) => T : never;
|
|
74
|
+
/** An element, or the id of one. Vanilla code passes the variable; adapters and JSON pass the id. */
|
|
75
|
+
export type ElementRef<T extends AnyElement = AnyElement> = T | string;
|
|
76
|
+
/** What an attached element needs from the chart: where to draw, and the counters Surface reports into. */
|
|
77
|
+
interface MountContext {
|
|
78
|
+
platform: Platform;
|
|
79
|
+
counters: Counters;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* The element type of a parent's variadic tail: the children that may come and go.
|
|
83
|
+
* `[Scale]` has none, `[DataNode, ...SeriesRenderer[]]` has SeriesRenderer, `Axis[]` has Axis.
|
|
84
|
+
*/
|
|
85
|
+
export type VariadicChild<C extends readonly unknown[]> = C extends readonly [unknown, ...infer Rest] ? VariadicChild<Rest> : C extends readonly (infer E)[] ? E : never;
|
|
86
|
+
/** The positions of a parent's fixed children, which are always present and can only be replaced. */
|
|
87
|
+
export type FixedIndex<C extends readonly unknown[], Seen extends unknown[] = []> = C extends readonly [unknown, ...infer Rest] ? Seen['length'] | FixedIndex<Rest, [...Seen, unknown]> : never;
|
|
88
|
+
/**
|
|
89
|
+
* A node with three links: its parent, its first child, and its next sibling. Nothing else is stored, so
|
|
90
|
+
* finding a predecessor or the tail walks the siblings, which is cheap at the sizes charts have.
|
|
91
|
+
*/
|
|
92
|
+
export declare abstract class ChartElement<Parent extends AnyElement | null = AnyElement | null, Children extends readonly AnyElement[] = [], Attrs extends BaseAttributes = BaseAttributes> {
|
|
93
|
+
#private;
|
|
94
|
+
/** How HTML attributes of this element become values; see attrs.ts. Subclasses repeat these by spreading and add their own. */
|
|
95
|
+
/**
|
|
96
|
+
* For an element that takes pointer, key or focus input: set this to `inputOf` (from input.ts), and the chart is made to listen for
|
|
97
|
+
* input as the element joins it, before the element's own ATTACH handler runs. It is the function and not a flag so that
|
|
98
|
+
* declaring it brings the input code into a bundle, which a chart with no such element never carries.
|
|
99
|
+
*/
|
|
100
|
+
static takesInput: ((chart: Chart) => unknown) | null;
|
|
101
|
+
static attrTypes: {
|
|
102
|
+
id: 1;
|
|
103
|
+
meta: {
|
|
104
|
+
title: 1;
|
|
105
|
+
description: 1;
|
|
106
|
+
role: 1;
|
|
107
|
+
};
|
|
108
|
+
};
|
|
109
|
+
/**
|
|
110
|
+
* Types only, nothing at run time: what a JSX tag for this class accepts (see jsx-runtime.ts): the attributes and children if
|
|
111
|
+
* there are any (hyphenated names such as `meta-title` are not checked: TypeScript lets any through). Which children, and in what
|
|
112
|
+
* order, cannot be checked, because TypeScript gives every JSX tag the same type; the class' documentation says.
|
|
113
|
+
*/
|
|
114
|
+
readonly $props: Attrs & {
|
|
115
|
+
children?: [Children[number]] extends [never] ? JsxChild<never> : JsxChild<AnyElement>;
|
|
116
|
+
};
|
|
117
|
+
/**
|
|
118
|
+
* `new X(attrs?, ...children)`. The attrs object is recognised by not being an element, and is copied so that
|
|
119
|
+
* the caller's object is never mutated by later `setAttribute` calls.
|
|
120
|
+
*/
|
|
121
|
+
constructor(...args: ElementArgs<Attrs, Children>);
|
|
122
|
+
/** Read-only view; change attributes through `setAttribute(s)` so the chart learns it is stale. */
|
|
123
|
+
get attrs(): Readonly<Attrs>;
|
|
124
|
+
get parent(): Parent | null;
|
|
125
|
+
get firstChild(): AnyElement | null;
|
|
126
|
+
get nextSibling(): AnyElement | null;
|
|
127
|
+
/** For elements that only make sense inside a parent (an axis in a plane): fails loudly rather than returning null. */
|
|
128
|
+
requireParent(): Parent & AnyElement;
|
|
129
|
+
/** The root chart, if this element is in a tree that has one. */
|
|
130
|
+
get chart(): Chart | null;
|
|
131
|
+
/** Like `chart`, for code that only runs inside a chart (painting, input): fails loudly rather than returning null. */
|
|
132
|
+
requireChart(): Chart;
|
|
133
|
+
/** True for an element that takes a pointer press (PanZoom): one that must see the press first (a BoxZoom, a Brush) is placed before it. */
|
|
134
|
+
readonly claimsPress?: boolean;
|
|
135
|
+
get isAttached(): boolean;
|
|
136
|
+
/** The children in order. Do not remove the element you are on and the one after it while iterating. */
|
|
137
|
+
[Symbol.iterator](): Generator<Children[number]>;
|
|
138
|
+
/**
|
|
139
|
+
* A fixed child, by position, checked where it is used: it has to be one of `classes` (an instance of any, subclasses
|
|
140
|
+
* included). The classes must be ones the declared child type allows, so a wrong one is a compile error; a wrong or missing child
|
|
141
|
+
* is a run-time error that says what was expected and what was found, wherever the tree was built (`new`, JSX, React, markup).
|
|
142
|
+
*/
|
|
143
|
+
child<I extends FixedIndex<Children> & keyof Children>(index: I, ...classes: [ClassFor<Children[I]>, ...ClassFor<Children[I]>[]]): Children[I];
|
|
144
|
+
private nth;
|
|
145
|
+
/** What this element draws with. Created on first use, so an element that draws nothing costs nothing. */
|
|
146
|
+
protected get surface(): Surface;
|
|
147
|
+
/** The flush counters if attached, else null: for code that also runs when a node is read outside a chart. */
|
|
148
|
+
protected get countersOrNull(): Counters | null;
|
|
149
|
+
/** The chart's flush counters. Only valid between attach and detach. */
|
|
150
|
+
protected get counters(): Counters;
|
|
151
|
+
/** Setting `undefined` removes the attribute, so defaults apply again. Unchanged values do not invalidate. */
|
|
152
|
+
/**
|
|
153
|
+
* This element's look for a chain of selectors, most specific first, ending in a root: all of that root's properties,
|
|
154
|
+
* settled (see theme.ts for the order). `local` is the element's own attributes that are style properties; `accent` is
|
|
155
|
+
* a default colour from the palette that anything named in a theme beats.
|
|
156
|
+
*/
|
|
157
|
+
protected styleFor<R extends keyof Roots>(chain: readonly [...SelectorName[], R], local?: Partial<Roots[R]>, accent?: Partial<Roots[R]>): Roots[R];
|
|
158
|
+
getAttribute<K extends keyof Attrs>(name: K): Attrs[K];
|
|
159
|
+
setAttribute<K extends keyof Attrs>(name: K, value: Attrs[K]): this;
|
|
160
|
+
setAttributes(patch: Partial<Attrs>): this;
|
|
161
|
+
private put;
|
|
162
|
+
/** Put `child` in front of `before` (null: at the end). Finding the place walks the siblings. */
|
|
163
|
+
private link;
|
|
164
|
+
private unlink;
|
|
165
|
+
/**
|
|
166
|
+
* Add children that can come and go. Fixed children (an axis' scale, a series' data) are given to the
|
|
167
|
+
* constructor and replaced with `setChild`; the types do not allow adding or removing them.
|
|
168
|
+
*/
|
|
169
|
+
append(...children: VariadicChild<Children>[]): this;
|
|
170
|
+
/** Exactly `Node.insertBefore`: `before` is one of this element's children, or null to add at the end. */
|
|
171
|
+
insertBefore(child: VariadicChild<Children>, before: Children[number] | null): this;
|
|
172
|
+
remove(child: VariadicChild<Children>): this;
|
|
173
|
+
/** Swap one of the fixed children for another of the same kind. */
|
|
174
|
+
setChild<I extends FixedIndex<Children> & keyof Children>(index: I, child: Children[I]): this;
|
|
175
|
+
/** Convenience for `parent.remove(this)` when the caller holds only the child. */
|
|
176
|
+
detachFromParent(): this;
|
|
177
|
+
/**
|
|
178
|
+
* Something about this element is stale. The request travels to the root, where a chart on a
|
|
179
|
+
* scheduling platform arranges a flush. Each flush re-runs layout; fills are cached and unchanged
|
|
180
|
+
* attribute writes skipped, which is where the saving is.
|
|
181
|
+
*/
|
|
182
|
+
invalidate(): void;
|
|
183
|
+
/** The single extension point. Switch on `event.type`; ignore what you don't handle. */
|
|
184
|
+
onEvent(_event: ChartEvent): void;
|
|
185
|
+
/** Deliver an event to `root` (default: this element) and everything beneath it, parents first. */
|
|
186
|
+
protected broadcast(init: ChartEventInit, root?: AnyElement): ChartEvent;
|
|
187
|
+
/** Parents are visited before their children, so a parent can set up state its children read in the same event. */
|
|
188
|
+
private cascade;
|
|
189
|
+
/** Ask a child how much space it needs. */
|
|
190
|
+
protected measureChild(child: AnyElement, available: Size): LayoutRequest;
|
|
191
|
+
/** Tell a child where it is. */
|
|
192
|
+
protected arrangeChild(child: AnyElement, rect: Rect): void;
|
|
193
|
+
/** The one entry point for events: the base class handles what every element shares, then the subclass sees it. */
|
|
194
|
+
private receive;
|
|
195
|
+
/** Put this tree on the platform. The root calls it; elements added later are mounted by their parent. */
|
|
196
|
+
protected mount(ctx: MountContext): void;
|
|
197
|
+
/**
|
|
198
|
+
* `removeNodes` is false once an ancestor's wrapper is being removed: everything inside goes with it.
|
|
199
|
+
* Without a wrapper, an element's nodes sit in a shared container and have to be taken out one by one.
|
|
200
|
+
*/
|
|
201
|
+
protected unmount(removeNodes: boolean): void;
|
|
202
|
+
/** The first node of this element's content, or null if it and everything below it has drawn nothing. */
|
|
203
|
+
private first;
|
|
204
|
+
private firstOfChildren;
|
|
205
|
+
/** The node this element's content ends before: whatever the next element that has drawn starts with. */
|
|
206
|
+
private end;
|
|
207
|
+
/** The node this element's own nodes go into when it has no wrapper. */
|
|
208
|
+
private container;
|
|
209
|
+
/** Hidden if it, or an ancestor without a wrapper to hide for it, says `visible: false`. */
|
|
210
|
+
private isHidden;
|
|
211
|
+
}
|
|
212
|
+
/** Depth-first, parents before children. Returning `false` from `visit` skips that element's subtree. */
|
|
213
|
+
export declare function walk(root: AnyElement, visit: (element: AnyElement, depth: number) => boolean | void, depth?: number): void;
|
|
214
|
+
/** The first element under `root` (or `root` itself) with this id. */
|
|
215
|
+
export declare function findById<T extends AnyElement = AnyElement>(root: AnyElement, id: string): T | undefined;
|
|
216
|
+
/** Every element below `root` of a class, in tree order. */
|
|
217
|
+
export declare function descendantsOfType<T extends AnyElement>(root: AnyElement, type: ElementClass<T>): T[];
|
|
218
|
+
/** The first element below `root` of a class, stopping the walk as soon as it is found. */
|
|
219
|
+
export declare function firstDescendantOfType<T extends AnyElement>(root: AnyElement, type: ElementClass<T>): T | undefined;
|
|
220
|
+
/** The direct children of a class; use it when the parent's children are mixed (an axis next to series). */
|
|
221
|
+
export declare function childrenOfType<T extends AnyElement>(parent: AnyElement, type: ElementClass<T>): T[];
|
|
222
|
+
/** The nearest enclosing element of a class: how a child finds "its" plane or chart without being handed one. */
|
|
223
|
+
export declare function closestAncestorOfType<T extends AnyElement>(from: AnyElement, type: ElementClass<T>): T | undefined;
|
|
224
|
+
/**
|
|
225
|
+
* Turns an `ElementRef` into the element: ids are looked up from the root of `from`'s tree, elements pass through.
|
|
226
|
+
* With `type`, a mismatch is reported by name, which is the useful error when a config points at the wrong thing.
|
|
227
|
+
*/
|
|
228
|
+
export declare function resolveRef<T extends AnyElement>(ref: ElementRef<T>, from: AnyElement, type?: ElementClass<T>): T;
|
|
229
|
+
export declare function resolveRef<T extends AnyElement>(ref: ElementRef<T> | undefined, from: AnyElement, type?: ElementClass<T>): T | undefined;
|
|
230
|
+
/** `throw` as an expression, so a missing value can be reported inline: `x ?? fail('no x')`. */
|
|
231
|
+
export declare function fail(message: string): never;
|
|
232
|
+
export {};
|