@bicharts/chart-host 0.6.56 → 0.6.59
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-CMHGGW3Y.mjs → chunk-5NSU3MYW.mjs} +41 -110
- package/dist/chunk-WNGJUPEI.mjs +114 -0
- package/dist/chunk-XIAL6EEC.mjs +598 -0
- package/dist/dax.mjs +60 -0
- package/dist/geo.mjs +3 -3
- package/dist/index.mjs +49 -47
- package/dist/page.mjs +34 -0
- package/dist/react.mjs +299 -9
- package/dist/testing.mjs +46 -1
- package/dist/types/controls.d.ts +50 -0
- package/dist/types/dax.d.ts +22 -0
- package/dist/types/filterScope.d.ts +173 -0
- package/dist/types/index.d.ts +3 -0
- package/dist/types/markNotes.d.ts +30 -0
- package/dist/types/page.d.ts +4 -0
- package/dist/types/react.d.ts +100 -2
- package/dist/types/testing/index.d.ts +1 -0
- package/dist/types/testing/interact.d.ts +17 -0
- package/package.json +11 -1
package/dist/index.mjs
CHANGED
|
@@ -1,63 +1,28 @@
|
|
|
1
1
|
/*! @bicharts/chart-host — Apache-2.0. Bundled reference data: GeoNames (https://www.geonames.org/) CC BY 4.0; Natural Earth and US Census/TIGER (public domain). Full text: NOTICE in this package. */
|
|
2
2
|
import {
|
|
3
|
-
ACTIVE_TICK_CLASS,
|
|
4
|
-
ANIMATION_OPTION_KEYS,
|
|
5
|
-
ANIM_LOOP_DELAY_DEFAULT,
|
|
6
|
-
ANIM_LOOP_DELAY_MIN,
|
|
7
|
-
ANIM_MAX_IDEAL_FRAMES_DEFAULT,
|
|
8
|
-
ANIM_MAX_IDEAL_FRAMES_MAX,
|
|
9
|
-
ANIM_MAX_IDEAL_FRAMES_MIN,
|
|
10
|
-
ANIM_PLAY_SPEED_DEFAULT,
|
|
11
|
-
ANIM_PLAY_SPEED_MAX,
|
|
12
|
-
ANIM_PLAY_SPEED_MIN,
|
|
13
3
|
ANNOTATION_CLASS,
|
|
14
4
|
ANNOTATION_LAYER_CLASS,
|
|
15
|
-
APPROXIMATE_POSITIONS_DEFAULT,
|
|
16
5
|
AUTO_RAMP_CLASSES,
|
|
17
6
|
AUTO_RAMP_DRAWN_OPACITY,
|
|
18
7
|
AUTO_RAMP_MIN_STEP_DELTA_E,
|
|
19
|
-
AXIS_FILTER_CLASS,
|
|
20
8
|
AXIS_PIN_BAND_PAD_PX,
|
|
21
9
|
AXIS_PIN_MAX_BAND_FRACTION,
|
|
22
10
|
AXIS_PIN_MIN_LABELS,
|
|
23
11
|
AXIS_PIN_TRACK_REACH_PX,
|
|
24
12
|
BACKING_MAJORITY,
|
|
25
|
-
COLOR_SCALE_SELF_CLAMP_PCT_DEFAULT,
|
|
26
|
-
COLOR_SCALE_SELF_CLAMP_PCT_MAX,
|
|
27
|
-
COLOR_SCALE_SELF_CLAMP_PCT_MIN,
|
|
28
|
-
CONTAINER_SLOT_ANIM_STOP,
|
|
29
|
-
CONTAINER_SLOT_INITIAL_XF_MARK,
|
|
30
|
-
CONTAINER_SLOT_UI_STATE,
|
|
31
|
-
CONTAINER_SLOT_XF_CLEAR,
|
|
32
|
-
CONTROL_CLASS,
|
|
33
13
|
D3_PLUGIN_PACKAGES,
|
|
34
14
|
DARK_TEXT,
|
|
35
|
-
DIM_OPACITY_DEFAULT,
|
|
36
|
-
DIM_OPACITY_VAR,
|
|
37
15
|
FIT_CONTENT_SELECTOR,
|
|
38
|
-
FLIP_DIRECTION_DEFAULT,
|
|
39
|
-
FLIP_INTERVAL_MAX_MS,
|
|
40
|
-
FLIP_INTERVAL_MIN_MS,
|
|
41
|
-
FLIP_MODE_DEFAULT,
|
|
42
|
-
GEO_POINT_PRECISIONS,
|
|
43
16
|
GLYPH_SAMPLE_N,
|
|
44
|
-
HOST_CONTAINER_CLASS,
|
|
45
|
-
HOST_CONTRACT_VERSION,
|
|
46
17
|
L,
|
|
47
18
|
LABEL_CONTRAST_CAP,
|
|
48
19
|
LABEL_CONTRAST_DONE_ATTR,
|
|
49
20
|
LABEL_PILL_CLASS,
|
|
50
|
-
LEGEND_MARK_CLASS,
|
|
51
|
-
LIFT_SELECTED_CLASS,
|
|
52
21
|
LIGHT_TEXT,
|
|
53
|
-
MARK_CLASS,
|
|
54
|
-
MARK_SELECTED_CLASS,
|
|
55
22
|
MAX_FRAME_GROW_FACTOR,
|
|
56
|
-
MAX_MAP_POINTS_DEFAULT,
|
|
57
23
|
MIN_CONTRAST,
|
|
58
24
|
MIN_HIT_BAND_PX,
|
|
59
25
|
MIN_RAMP_FILLS,
|
|
60
|
-
NO_ROW_MARKS_ATTR,
|
|
61
26
|
PAGE_MATCH_TOLERANCE,
|
|
62
27
|
PHANTOM_FRACTION,
|
|
63
28
|
PILL_MIN_ALPHA,
|
|
@@ -65,23 +30,16 @@ import {
|
|
|
65
30
|
PILL_OPAQUE_ALPHA,
|
|
66
31
|
Qe,
|
|
67
32
|
Qn,
|
|
68
|
-
ROW_IDX_ATTR,
|
|
69
33
|
SAME_SHADE_DELTA_E,
|
|
70
34
|
SCROLL_SLACK_PX,
|
|
71
|
-
SEASONAL_MARKERS_DEFAULT,
|
|
72
|
-
SELECTION_ACTIVE_CLASS,
|
|
73
35
|
SELECTION_GLYPH_CLASS,
|
|
74
|
-
SNAPSHOT_SVG_ATTR,
|
|
75
36
|
STRADDLE_COLS,
|
|
76
37
|
STRADDLE_MIN_SHARE,
|
|
77
38
|
STRADDLE_PILL_ALPHA,
|
|
78
39
|
STRADDLE_ROWS,
|
|
79
40
|
STRADDLE_TARGET_CONTRAST,
|
|
80
|
-
VALUE_AXIS_BASELINE_DEFAULT,
|
|
81
41
|
VALUE_AXIS_MIN_FIT,
|
|
82
|
-
VIEW_ONLY_UI_STATE_KEYS,
|
|
83
42
|
WHITE_TEXT_BG_LUM,
|
|
84
|
-
XFILTER_REFRESH_EVENT,
|
|
85
43
|
Zn,
|
|
86
44
|
_3,
|
|
87
45
|
applyLabelContrast,
|
|
@@ -97,7 +55,6 @@ import {
|
|
|
97
55
|
censusHitBands,
|
|
98
56
|
censusMarks,
|
|
99
57
|
censusValuePlacement,
|
|
100
|
-
chartOwnsTimeline,
|
|
101
58
|
clearGeoCache,
|
|
102
59
|
clearSelectionGlyphs,
|
|
103
60
|
collectD3GlyphCenters,
|
|
@@ -138,7 +95,6 @@ import {
|
|
|
138
95
|
noopViewStateProvider,
|
|
139
96
|
o,
|
|
140
97
|
paintSelectionGlyphs,
|
|
141
|
-
periodTickSuppressesFeedback,
|
|
142
98
|
pickColorOver,
|
|
143
99
|
pillBacksGlyph,
|
|
144
100
|
pinScrolledAxis,
|
|
@@ -163,10 +119,56 @@ import {
|
|
|
163
119
|
toRGBA,
|
|
164
120
|
toSourceRows,
|
|
165
121
|
unpinScrolledAxis,
|
|
166
|
-
valuePlacementFlag
|
|
167
|
-
|
|
168
|
-
} from "./chunk-CMHGGW3Y.mjs";
|
|
122
|
+
valuePlacementFlag
|
|
123
|
+
} from "./chunk-5NSU3MYW.mjs";
|
|
169
124
|
import "./chunk-A2GMXZP7.mjs";
|
|
125
|
+
import {
|
|
126
|
+
ACTIVE_TICK_CLASS,
|
|
127
|
+
ANIMATION_OPTION_KEYS,
|
|
128
|
+
ANIM_LOOP_DELAY_DEFAULT,
|
|
129
|
+
ANIM_LOOP_DELAY_MIN,
|
|
130
|
+
ANIM_MAX_IDEAL_FRAMES_DEFAULT,
|
|
131
|
+
ANIM_MAX_IDEAL_FRAMES_MAX,
|
|
132
|
+
ANIM_MAX_IDEAL_FRAMES_MIN,
|
|
133
|
+
ANIM_PLAY_SPEED_DEFAULT,
|
|
134
|
+
ANIM_PLAY_SPEED_MAX,
|
|
135
|
+
ANIM_PLAY_SPEED_MIN,
|
|
136
|
+
APPROXIMATE_POSITIONS_DEFAULT,
|
|
137
|
+
AXIS_FILTER_CLASS,
|
|
138
|
+
COLOR_SCALE_SELF_CLAMP_PCT_DEFAULT,
|
|
139
|
+
COLOR_SCALE_SELF_CLAMP_PCT_MAX,
|
|
140
|
+
COLOR_SCALE_SELF_CLAMP_PCT_MIN,
|
|
141
|
+
CONTAINER_SLOT_ANIM_STOP,
|
|
142
|
+
CONTAINER_SLOT_INITIAL_XF_MARK,
|
|
143
|
+
CONTAINER_SLOT_UI_STATE,
|
|
144
|
+
CONTAINER_SLOT_XF_CLEAR,
|
|
145
|
+
CONTROL_CLASS,
|
|
146
|
+
DIM_OPACITY_DEFAULT,
|
|
147
|
+
DIM_OPACITY_VAR,
|
|
148
|
+
FLIP_DIRECTION_DEFAULT,
|
|
149
|
+
FLIP_INTERVAL_MAX_MS,
|
|
150
|
+
FLIP_INTERVAL_MIN_MS,
|
|
151
|
+
FLIP_MODE_DEFAULT,
|
|
152
|
+
GEO_POINT_PRECISIONS,
|
|
153
|
+
HOST_CONTAINER_CLASS,
|
|
154
|
+
HOST_CONTRACT_VERSION,
|
|
155
|
+
LEGEND_MARK_CLASS,
|
|
156
|
+
LIFT_SELECTED_CLASS,
|
|
157
|
+
MARK_CLASS,
|
|
158
|
+
MARK_SELECTED_CLASS,
|
|
159
|
+
MAX_MAP_POINTS_DEFAULT,
|
|
160
|
+
NO_ROW_MARKS_ATTR,
|
|
161
|
+
ROW_IDX_ATTR,
|
|
162
|
+
SEASONAL_MARKERS_DEFAULT,
|
|
163
|
+
SELECTION_ACTIVE_CLASS,
|
|
164
|
+
SNAPSHOT_SVG_ATTR,
|
|
165
|
+
VALUE_AXIS_BASELINE_DEFAULT,
|
|
166
|
+
VIEW_ONLY_UI_STATE_KEYS,
|
|
167
|
+
XFILTER_REFRESH_EVENT,
|
|
168
|
+
chartOwnsTimeline,
|
|
169
|
+
periodTickSuppressesFeedback,
|
|
170
|
+
withoutViewOnlyKeys
|
|
171
|
+
} from "./chunk-WNGJUPEI.mjs";
|
|
170
172
|
import "./chunk-GHODWOAL.mjs";
|
|
171
173
|
|
|
172
174
|
// src/optionsVocabulary.ts
|
package/dist/page.mjs
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/*! @bicharts/chart-host — Apache-2.0. Bundled reference data: GeoNames (https://www.geonames.org/) CC BY 4.0; Natural Earth and US Census/TIGER (public domain). Full text: NOTICE in this package. */
|
|
2
|
+
import {
|
|
3
|
+
LINKED_HOVER_CLASS,
|
|
4
|
+
attachControls,
|
|
5
|
+
badgeKey,
|
|
6
|
+
bindFilter,
|
|
7
|
+
createControls,
|
|
8
|
+
createFilter,
|
|
9
|
+
createFilterScope,
|
|
10
|
+
fromVegaInteraction,
|
|
11
|
+
noteBadges,
|
|
12
|
+
notesFor,
|
|
13
|
+
payloadRowReader,
|
|
14
|
+
readControls,
|
|
15
|
+
viewFromToken,
|
|
16
|
+
viewToken
|
|
17
|
+
} from "./chunk-XIAL6EEC.mjs";
|
|
18
|
+
import "./chunk-GHODWOAL.mjs";
|
|
19
|
+
export {
|
|
20
|
+
LINKED_HOVER_CLASS,
|
|
21
|
+
attachControls,
|
|
22
|
+
badgeKey,
|
|
23
|
+
bindFilter,
|
|
24
|
+
createControls,
|
|
25
|
+
createFilter,
|
|
26
|
+
createFilterScope,
|
|
27
|
+
fromVegaInteraction,
|
|
28
|
+
noteBadges,
|
|
29
|
+
notesFor,
|
|
30
|
+
payloadRowReader,
|
|
31
|
+
readControls,
|
|
32
|
+
viewFromToken,
|
|
33
|
+
viewToken
|
|
34
|
+
};
|
package/dist/react.mjs
CHANGED
|
@@ -1,4 +1,19 @@
|
|
|
1
1
|
/*! @bicharts/chart-host — Apache-2.0. Bundled reference data: GeoNames (https://www.geonames.org/) CC BY 4.0; Natural Earth and US Census/TIGER (public domain). Full text: NOTICE in this package. */
|
|
2
|
+
import {
|
|
3
|
+
LINKED_HOVER_CLASS,
|
|
4
|
+
attachControls,
|
|
5
|
+
badgeKey,
|
|
6
|
+
bindFilter,
|
|
7
|
+
createControls,
|
|
8
|
+
createFilter,
|
|
9
|
+
createFilterScope,
|
|
10
|
+
fromVegaInteraction,
|
|
11
|
+
noteBadges,
|
|
12
|
+
notesFor,
|
|
13
|
+
payloadRowReader,
|
|
14
|
+
viewFromToken,
|
|
15
|
+
viewToken
|
|
16
|
+
} from "./chunk-XIAL6EEC.mjs";
|
|
2
17
|
import {
|
|
3
18
|
assembleD3,
|
|
4
19
|
createChartGroup,
|
|
@@ -7,13 +22,14 @@ import {
|
|
|
7
22
|
loadGeo,
|
|
8
23
|
syncMemberSelection,
|
|
9
24
|
toSourceRows
|
|
10
|
-
} from "./chunk-
|
|
25
|
+
} from "./chunk-5NSU3MYW.mjs";
|
|
11
26
|
import "./chunk-A2GMXZP7.mjs";
|
|
27
|
+
import "./chunk-WNGJUPEI.mjs";
|
|
12
28
|
import "./chunk-GHODWOAL.mjs";
|
|
13
29
|
|
|
14
30
|
// src/react.tsx
|
|
15
|
-
import { createContext, useContext, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
|
|
16
|
-
import { jsx } from "react/jsx-runtime";
|
|
31
|
+
import { createContext, useContext, useEffect, useLayoutEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
|
|
32
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
17
33
|
var Ctx = createContext(null);
|
|
18
34
|
function useBicSelection() {
|
|
19
35
|
const g = useContext(Ctx);
|
|
@@ -56,7 +72,10 @@ function BicChart(props) {
|
|
|
56
72
|
annotations,
|
|
57
73
|
onAnnotationClick,
|
|
58
74
|
className,
|
|
59
|
-
style
|
|
75
|
+
style,
|
|
76
|
+
selects,
|
|
77
|
+
controls,
|
|
78
|
+
ariaLabel
|
|
60
79
|
} = props;
|
|
61
80
|
const ref = useRef(null);
|
|
62
81
|
const hostRef = useRef(null);
|
|
@@ -72,6 +91,18 @@ function BicChart(props) {
|
|
|
72
91
|
}, [group, ctx?.source, filterSel && filterSel.join(",")]);
|
|
73
92
|
const data = built ? built.payload : props.data;
|
|
74
93
|
rowMapRef.current = built ? built.rowMap : null;
|
|
94
|
+
const dataRef = useRef(data);
|
|
95
|
+
dataRef.current = data;
|
|
96
|
+
const groupRef = useRef(group);
|
|
97
|
+
groupRef.current = group;
|
|
98
|
+
const bindingRef = useRef(null);
|
|
99
|
+
const controlsRef = useRef(null);
|
|
100
|
+
const readControlsSoon = () => {
|
|
101
|
+
const h = hostRef.current, c = controlsRef.current;
|
|
102
|
+
if (h && c) h.rendered.then(c.read, () => {
|
|
103
|
+
});
|
|
104
|
+
};
|
|
105
|
+
const userSelectedRef = useRef(false);
|
|
75
106
|
const optKey = useMemo(() => JSON.stringify(options ?? {}), [options]);
|
|
76
107
|
const builtWithRef = useRef(null);
|
|
77
108
|
const onSelectRef = useRef(onSelect);
|
|
@@ -102,11 +133,14 @@ function BicChart(props) {
|
|
|
102
133
|
useLayoutEffect(() => {
|
|
103
134
|
const el = ref.current;
|
|
104
135
|
if (!el || !code && !renderFn || !data) return;
|
|
136
|
+
const ctl = controls ? attachControls(() => hostRef.current, el, controls) : null;
|
|
137
|
+
controlsRef.current = ctl;
|
|
138
|
+
const hostOptions = ctl ? { ...options ?? {}, ...ctl.options } : options;
|
|
105
139
|
const host = createChartHost(el, {
|
|
106
140
|
code,
|
|
107
141
|
renderFn,
|
|
108
142
|
data,
|
|
109
|
-
options,
|
|
143
|
+
options: hostOptions,
|
|
110
144
|
d3,
|
|
111
145
|
geoKind,
|
|
112
146
|
viewState,
|
|
@@ -127,23 +161,54 @@ function BicChart(props) {
|
|
|
127
161
|
builtAnnKeyRef.current = annKey;
|
|
128
162
|
const off = host.selection.onChange((payloadIdxs, source) => {
|
|
129
163
|
if (source === "host") return;
|
|
164
|
+
userSelectedRef.current = payloadIdxs.length > 0;
|
|
130
165
|
const sourceIdxs = toSourceRows(rowMapRef.current, payloadIdxs);
|
|
131
166
|
if (group && id) group.publish(id, sourceIdxs);
|
|
132
167
|
onSelectRef.current?.(sourceIdxs);
|
|
133
168
|
});
|
|
134
169
|
host.render();
|
|
170
|
+
readControlsSoon();
|
|
135
171
|
builtWithRef.current = { data, optKey };
|
|
136
172
|
return () => {
|
|
137
173
|
off();
|
|
174
|
+
ctl?.detach();
|
|
175
|
+
if (controlsRef.current === ctl) controlsRef.current = null;
|
|
138
176
|
host.destroy();
|
|
139
177
|
hostRef.current = null;
|
|
140
178
|
};
|
|
141
|
-
}, [code, renderFn, d3, geoKind, labelContrast]);
|
|
179
|
+
}, [code, renderFn, d3, geoKind, labelContrast, controls]);
|
|
180
|
+
useLayoutEffect(() => {
|
|
181
|
+
const host = hostRef.current;
|
|
182
|
+
if (!host || !selects) return;
|
|
183
|
+
const payloadRows = payloadRowReader(() => dataRef.current);
|
|
184
|
+
const b = bindFilter(host, selects, {
|
|
185
|
+
container: ref.current,
|
|
186
|
+
rowAt(i) {
|
|
187
|
+
const g = groupRef.current, map = rowMapRef.current;
|
|
188
|
+
if (g && map) {
|
|
189
|
+
const s = map[i];
|
|
190
|
+
return s === void 0 ? null : g.rows[s] ?? null;
|
|
191
|
+
}
|
|
192
|
+
return payloadRows.rowAt(i);
|
|
193
|
+
},
|
|
194
|
+
rowCount() {
|
|
195
|
+
const map = rowMapRef.current;
|
|
196
|
+
return groupRef.current && map ? map.length : payloadRows.rowCount();
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
bindingRef.current = b;
|
|
200
|
+
b.reconcile();
|
|
201
|
+
return () => {
|
|
202
|
+
b.detach();
|
|
203
|
+
if (bindingRef.current === b) bindingRef.current = null;
|
|
204
|
+
};
|
|
205
|
+
}, [selects, code, renderFn, d3, geoKind, labelContrast]);
|
|
142
206
|
useEffect(() => {
|
|
143
207
|
if (!hostRef.current || !options) return;
|
|
144
208
|
if (builtWithRef.current?.optKey === optKey) return;
|
|
145
209
|
builtWithRef.current = { ...builtWithRef.current, optKey };
|
|
146
210
|
hostRef.current.setOptions(options);
|
|
211
|
+
readControlsSoon();
|
|
147
212
|
}, [optKey]);
|
|
148
213
|
const builtAnnKeyRef = useRef(null);
|
|
149
214
|
useEffect(() => {
|
|
@@ -156,13 +221,220 @@ function BicChart(props) {
|
|
|
156
221
|
if (builtWithRef.current?.data === data) return;
|
|
157
222
|
builtWithRef.current = { ...builtWithRef.current, data };
|
|
158
223
|
hostRef.current.setData(data);
|
|
224
|
+
bindingRef.current?.reconcile();
|
|
225
|
+
readControlsSoon();
|
|
159
226
|
}, [data]);
|
|
227
|
+
const lastSourceRef = useRef(ctx?.source);
|
|
160
228
|
useEffect(() => {
|
|
161
229
|
const host = hostRef.current;
|
|
162
230
|
if (!host || !ctx) return;
|
|
231
|
+
const sourceChanged = lastSourceRef.current !== ctx.source;
|
|
232
|
+
lastSourceRef.current = ctx.source;
|
|
233
|
+
if (selects) return;
|
|
234
|
+
const before = (host.selection.current ?? []).length;
|
|
163
235
|
syncMemberSelection(host, ctx.selection, id, highlightMode, incoming, rowMapRef.current);
|
|
164
|
-
|
|
165
|
-
|
|
236
|
+
if (sourceChanged && before && !(host.selection.current ?? []).length && userSelectedRef.current) {
|
|
237
|
+
userSelectedRef.current = false;
|
|
238
|
+
onSelectRef.current?.([]);
|
|
239
|
+
}
|
|
240
|
+
}, [ctx?.selection, id, ctx, highlightMode, incoming && incoming.join(","), selects]);
|
|
241
|
+
return /* @__PURE__ */ jsx(
|
|
242
|
+
"div",
|
|
243
|
+
{
|
|
244
|
+
ref,
|
|
245
|
+
className,
|
|
246
|
+
style,
|
|
247
|
+
role: ariaLabel ? "figure" : void 0,
|
|
248
|
+
"aria-label": ariaLabel || void 0
|
|
249
|
+
}
|
|
250
|
+
);
|
|
251
|
+
}
|
|
252
|
+
var defaultScope = createFilterScope();
|
|
253
|
+
var PageCtx = createContext(defaultScope);
|
|
254
|
+
function BicPage({ children }) {
|
|
255
|
+
const ref = useRef(null);
|
|
256
|
+
if (!ref.current) ref.current = createFilterScope();
|
|
257
|
+
return /* @__PURE__ */ jsx(PageCtx.Provider, { value: ref.current, children });
|
|
258
|
+
}
|
|
259
|
+
function useBicFilter(columns, opts) {
|
|
260
|
+
const scope = useContext(PageCtx);
|
|
261
|
+
const ref = useRef(null);
|
|
262
|
+
if (!ref.current) ref.current = createFilter(columns, opts);
|
|
263
|
+
const f = ref.current;
|
|
264
|
+
useEffect(() => scope.add(f), [scope, f]);
|
|
265
|
+
useSyncExternalStore((cb) => f.onChange(() => cb()), () => f.version, () => f.version);
|
|
266
|
+
return f;
|
|
267
|
+
}
|
|
268
|
+
function useBicControls(initial, opts) {
|
|
269
|
+
const scope = useContext(PageCtx);
|
|
270
|
+
const ref = useRef(null);
|
|
271
|
+
if (!ref.current) ref.current = createControls(initial);
|
|
272
|
+
const c = ref.current;
|
|
273
|
+
useEffect(() => scope.addControls(opts?.id ?? "controls", c), [scope, c, opts?.id]);
|
|
274
|
+
useSyncExternalStore((cb) => c.onChange(() => cb()), () => c.version, () => c.version);
|
|
275
|
+
return c;
|
|
276
|
+
}
|
|
277
|
+
function useBicView() {
|
|
278
|
+
const scope = useContext(PageCtx);
|
|
279
|
+
return useMemo(() => ({
|
|
280
|
+
save: () => scope.save(),
|
|
281
|
+
restore: (v) => scope.restore(v),
|
|
282
|
+
token: () => viewToken(scope.save()),
|
|
283
|
+
restoreToken: (t) => scope.restore(viewFromToken(t))
|
|
284
|
+
}), [scope]);
|
|
285
|
+
}
|
|
286
|
+
function useBicUrlState(param = "view") {
|
|
287
|
+
const scope = useContext(PageCtx);
|
|
288
|
+
useEffect(() => {
|
|
289
|
+
if (typeof window === "undefined") return;
|
|
290
|
+
const url = new URL(window.location.href);
|
|
291
|
+
scope.restore(viewFromToken(url.searchParams.get(param)));
|
|
292
|
+
return scope.onChange(() => {
|
|
293
|
+
const u = new URL(window.location.href);
|
|
294
|
+
const view = scope.save();
|
|
295
|
+
const empty = !view.filters && !view.controls;
|
|
296
|
+
if (empty) u.searchParams.delete(param);
|
|
297
|
+
else u.searchParams.set(param, viewToken(view));
|
|
298
|
+
if (u.href !== window.location.href) window.history.replaceState(window.history.state, "", u.href);
|
|
299
|
+
});
|
|
300
|
+
}, [scope, param]);
|
|
301
|
+
}
|
|
302
|
+
function BicDevtools({ max = 20 }) {
|
|
303
|
+
const scope = useContext(PageCtx);
|
|
304
|
+
const [, tick] = useState(0);
|
|
305
|
+
const log = useRef([]);
|
|
306
|
+
useEffect(() => scope.onChange(() => {
|
|
307
|
+
const at = (/* @__PURE__ */ new Date()).toLocaleTimeString();
|
|
308
|
+
const changed = scope.filters.map((f) => `${f.label}: ${f.active ? f.text : "(none)"}${f.reason ? ` [${f.reason}]` : ""}`);
|
|
309
|
+
log.current = [`${at} ${changed.join(" | ")}`, ...log.current].slice(0, max);
|
|
310
|
+
tick((t) => t + 1);
|
|
311
|
+
}), [scope, max]);
|
|
312
|
+
const box = {
|
|
313
|
+
position: "fixed",
|
|
314
|
+
right: 8,
|
|
315
|
+
bottom: 8,
|
|
316
|
+
zIndex: 2147483e3,
|
|
317
|
+
maxWidth: 420,
|
|
318
|
+
maxHeight: "50vh",
|
|
319
|
+
overflow: "auto",
|
|
320
|
+
font: "12px/1.4 ui-monospace, monospace",
|
|
321
|
+
background: "Canvas",
|
|
322
|
+
color: "CanvasText",
|
|
323
|
+
border: "1px solid GrayText",
|
|
324
|
+
borderRadius: 6,
|
|
325
|
+
padding: 6,
|
|
326
|
+
opacity: 0.95
|
|
327
|
+
};
|
|
328
|
+
return /* @__PURE__ */ jsxs("details", { className: "bic-devtools", style: box, children: [
|
|
329
|
+
/* @__PURE__ */ jsxs("summary", { style: { cursor: "pointer" }, children: [
|
|
330
|
+
"chart-host: ",
|
|
331
|
+
scope.active.length,
|
|
332
|
+
" filter(s) active"
|
|
333
|
+
] }),
|
|
334
|
+
scope.filters.map((f) => /* @__PURE__ */ jsxs("div", { children: [
|
|
335
|
+
/* @__PURE__ */ jsx("b", { children: f.label }),
|
|
336
|
+
" [",
|
|
337
|
+
f.key,
|
|
338
|
+
"] ",
|
|
339
|
+
f.active ? JSON.stringify(f.keys) : "(none)",
|
|
340
|
+
" ",
|
|
341
|
+
f.text && `"${f.text}"`,
|
|
342
|
+
f.reason ? ` - last: ${f.reason}` : "",
|
|
343
|
+
" v",
|
|
344
|
+
f.version
|
|
345
|
+
] }, f.id)),
|
|
346
|
+
[...scope.controls].map(([k, c]) => /* @__PURE__ */ jsxs("div", { children: [
|
|
347
|
+
/* @__PURE__ */ jsx("b", { children: "controls" }),
|
|
348
|
+
" [",
|
|
349
|
+
k,
|
|
350
|
+
"] ",
|
|
351
|
+
c.summary || JSON.stringify(c.values)
|
|
352
|
+
] }, k)),
|
|
353
|
+
/* @__PURE__ */ jsx("div", { style: { marginTop: 4, opacity: 0.8 }, children: log.current.map((l, i) => /* @__PURE__ */ jsx("div", { children: l }, i)) })
|
|
354
|
+
] });
|
|
355
|
+
}
|
|
356
|
+
function useBicFilters() {
|
|
357
|
+
const scope = useContext(PageCtx);
|
|
358
|
+
const [, tick] = useState(0);
|
|
359
|
+
useEffect(() => scope.onChange(() => tick((t) => t + 1)), [scope]);
|
|
360
|
+
return { filters: scope.filters, active: scope.active, clearAll: () => scope.clearAll() };
|
|
361
|
+
}
|
|
362
|
+
function BicFilterChips({ className, chipClassName, clearAllText = "Clear all", style }) {
|
|
363
|
+
const { active, clearAll } = useBicFilters();
|
|
364
|
+
if (!active.length) return null;
|
|
365
|
+
const chip = {
|
|
366
|
+
display: "inline-flex",
|
|
367
|
+
alignItems: "center",
|
|
368
|
+
gap: 6,
|
|
369
|
+
padding: "2px 4px 2px 10px",
|
|
370
|
+
border: "1px solid currentColor",
|
|
371
|
+
borderRadius: 999,
|
|
372
|
+
font: "inherit",
|
|
373
|
+
lineHeight: 1.6
|
|
374
|
+
};
|
|
375
|
+
const x = {
|
|
376
|
+
border: 0,
|
|
377
|
+
background: "transparent",
|
|
378
|
+
color: "inherit",
|
|
379
|
+
cursor: "pointer",
|
|
380
|
+
font: "inherit",
|
|
381
|
+
padding: "0 6px",
|
|
382
|
+
lineHeight: 1
|
|
383
|
+
};
|
|
384
|
+
return /* @__PURE__ */ jsxs(
|
|
385
|
+
"div",
|
|
386
|
+
{
|
|
387
|
+
className: ["bic-filter-chips", className].filter(Boolean).join(" "),
|
|
388
|
+
role: "group",
|
|
389
|
+
"aria-label": "Filters",
|
|
390
|
+
style: { display: "flex", flexWrap: "wrap", gap: 8, alignItems: "center", ...style },
|
|
391
|
+
children: [
|
|
392
|
+
active.map((f) => /* @__PURE__ */ jsxs(
|
|
393
|
+
"span",
|
|
394
|
+
{
|
|
395
|
+
className: ["bic-filter-chip", chipClassName].filter(Boolean).join(" "),
|
|
396
|
+
style: chipClassName ? void 0 : chip,
|
|
397
|
+
children: [
|
|
398
|
+
/* @__PURE__ */ jsxs("span", { className: "bic-filter-chip-label", children: [
|
|
399
|
+
f.label,
|
|
400
|
+
": "
|
|
401
|
+
] }),
|
|
402
|
+
/* @__PURE__ */ jsx("span", { className: "bic-filter-chip-value", children: f.text }),
|
|
403
|
+
/* @__PURE__ */ jsx(
|
|
404
|
+
"button",
|
|
405
|
+
{
|
|
406
|
+
type: "button",
|
|
407
|
+
className: "bic-filter-chip-clear",
|
|
408
|
+
"aria-label": `Clear ${f.label}`,
|
|
409
|
+
style: chipClassName ? void 0 : x,
|
|
410
|
+
onClick: () => f.clear(),
|
|
411
|
+
children: "\xD7"
|
|
412
|
+
}
|
|
413
|
+
)
|
|
414
|
+
]
|
|
415
|
+
},
|
|
416
|
+
f.id
|
|
417
|
+
)),
|
|
418
|
+
active.length > 1 && /* @__PURE__ */ jsx(
|
|
419
|
+
"button",
|
|
420
|
+
{
|
|
421
|
+
type: "button",
|
|
422
|
+
className: "bic-filter-chips-clear-all",
|
|
423
|
+
onClick: clearAll,
|
|
424
|
+
style: {
|
|
425
|
+
font: "inherit",
|
|
426
|
+
background: "transparent",
|
|
427
|
+
border: 0,
|
|
428
|
+
color: "inherit",
|
|
429
|
+
textDecoration: "underline",
|
|
430
|
+
cursor: "pointer"
|
|
431
|
+
},
|
|
432
|
+
children: clearAllText
|
|
433
|
+
}
|
|
434
|
+
)
|
|
435
|
+
]
|
|
436
|
+
}
|
|
437
|
+
);
|
|
166
438
|
}
|
|
167
439
|
function useMeasuredSize(ref) {
|
|
168
440
|
const [size, setSize] = useState({ width: 0, height: 0 });
|
|
@@ -185,7 +457,25 @@ function useMeasuredSize(ref) {
|
|
|
185
457
|
export {
|
|
186
458
|
BicChart,
|
|
187
459
|
BicChartGroup,
|
|
460
|
+
BicDevtools,
|
|
461
|
+
BicFilterChips,
|
|
462
|
+
BicPage,
|
|
463
|
+
LINKED_HOVER_CLASS,
|
|
188
464
|
assembleD3,
|
|
465
|
+
badgeKey,
|
|
466
|
+
createControls,
|
|
467
|
+
createFilter,
|
|
468
|
+
createFilterScope,
|
|
469
|
+
fromVegaInteraction,
|
|
470
|
+
noteBadges,
|
|
471
|
+
notesFor,
|
|
472
|
+
useBicControls,
|
|
473
|
+
useBicFilter,
|
|
474
|
+
useBicFilters,
|
|
189
475
|
useBicSelection,
|
|
190
|
-
|
|
476
|
+
useBicUrlState,
|
|
477
|
+
useBicView,
|
|
478
|
+
useMeasuredSize,
|
|
479
|
+
viewFromToken,
|
|
480
|
+
viewToken
|
|
191
481
|
};
|
package/dist/testing.mjs
CHANGED
|
@@ -1,6 +1,45 @@
|
|
|
1
1
|
/*! @bicharts/chart-host — Apache-2.0. Bundled reference data: GeoNames (https://www.geonames.org/) CC BY 4.0; Natural Earth and US Census/TIGER (public domain). Full text: NOTICE in this package. */
|
|
2
|
+
import {
|
|
3
|
+
HOST_CONTAINER_CLASS,
|
|
4
|
+
MARK_CLASS,
|
|
5
|
+
MARK_SELECTED_CLASS,
|
|
6
|
+
ROW_IDX_ATTR
|
|
7
|
+
} from "./chunk-WNGJUPEI.mjs";
|
|
2
8
|
import "./chunk-GHODWOAL.mjs";
|
|
3
9
|
|
|
10
|
+
// src/testing/interact.ts
|
|
11
|
+
function chartContainers(root) {
|
|
12
|
+
return Array.from(root.querySelectorAll(`.${HOST_CONTAINER_CLASS}`));
|
|
13
|
+
}
|
|
14
|
+
function marksOf(chart) {
|
|
15
|
+
return Array.from(chart.querySelectorAll(`.${MARK_CLASS}[${ROW_IDX_ATTR}]`)).filter((m) => (m.getAttribute(ROW_IDX_ATTR) ?? "") !== "");
|
|
16
|
+
}
|
|
17
|
+
function findMark(chart, which) {
|
|
18
|
+
const marks = marksOf(chart);
|
|
19
|
+
if (typeof which === "function") return marks.find(which) ?? null;
|
|
20
|
+
return marks.find((m) => (m.getAttribute(ROW_IDX_ATTR) ?? "").split(",").map(Number).includes(which)) ?? null;
|
|
21
|
+
}
|
|
22
|
+
function clickMark(mark, opts = {}) {
|
|
23
|
+
const win = mark.ownerDocument?.defaultView;
|
|
24
|
+
const Ev = win?.MouseEvent ?? MouseEvent;
|
|
25
|
+
mark.dispatchEvent(new Ev("click", { bubbles: true, cancelable: true, ctrlKey: !!opts.add }));
|
|
26
|
+
}
|
|
27
|
+
function clickEmpty(chart) {
|
|
28
|
+
const win = chart.ownerDocument?.defaultView;
|
|
29
|
+
const Ev = win?.MouseEvent ?? MouseEvent;
|
|
30
|
+
chart.dispatchEvent(new Ev("click", { bubbles: true, cancelable: true }));
|
|
31
|
+
}
|
|
32
|
+
function selectedRows(chart) {
|
|
33
|
+
const out = /* @__PURE__ */ new Set();
|
|
34
|
+
for (const m of Array.from(chart.querySelectorAll(`.${MARK_CLASS}.${MARK_SELECTED_CLASS}`))) {
|
|
35
|
+
for (const p of (m.getAttribute(ROW_IDX_ATTR) ?? "").split(",")) {
|
|
36
|
+
const n = parseInt(p, 10);
|
|
37
|
+
if (Number.isFinite(n)) out.add(n);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return [...out].sort((a, b) => a - b);
|
|
41
|
+
}
|
|
42
|
+
|
|
4
43
|
// src/testing/index.ts
|
|
5
44
|
var ConformanceError = class extends Error {
|
|
6
45
|
contract;
|
|
@@ -63,5 +102,11 @@ async function assertMarkerStoreConformance(store) {
|
|
|
63
102
|
}
|
|
64
103
|
export {
|
|
65
104
|
ConformanceError,
|
|
66
|
-
assertMarkerStoreConformance
|
|
105
|
+
assertMarkerStoreConformance,
|
|
106
|
+
chartContainers,
|
|
107
|
+
clickEmpty,
|
|
108
|
+
clickMark,
|
|
109
|
+
findMark,
|
|
110
|
+
marksOf,
|
|
111
|
+
selectedRows
|
|
67
112
|
};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { ChartHost } from "./host";
|
|
2
|
+
export interface ControlInfo {
|
|
3
|
+
/** The knob's name in the chart's bag ("rate", "horizon"). */
|
|
4
|
+
key: string;
|
|
5
|
+
/** Its value as the chart stores it (a number, or an ISO date string for a date knob). */
|
|
6
|
+
value: unknown;
|
|
7
|
+
/** The label the chart draws beside it ("growth per year"), or "" if none. */
|
|
8
|
+
label: string;
|
|
9
|
+
/** The readout the chart draws ("+5.5%", "7 years") - the value in the chart's own words. */
|
|
10
|
+
text: string;
|
|
11
|
+
}
|
|
12
|
+
export interface Controls {
|
|
13
|
+
/** Every knob's current value, by key - the opening values as soon as the chart has drawn. */
|
|
14
|
+
readonly values: Readonly<Record<string, unknown>>;
|
|
15
|
+
/** Each knob in the order the chart draws them, with its label and readout. */
|
|
16
|
+
readonly info: readonly ControlInfo[];
|
|
17
|
+
/** "growth per year +5.5% · horizon 7 years": what to show beside a saved scenario. */
|
|
18
|
+
readonly summary: string;
|
|
19
|
+
/** True once a chart has drawn and reported its controls. */
|
|
20
|
+
readonly ready: boolean;
|
|
21
|
+
/** Bumped on every change. */
|
|
22
|
+
readonly version: number;
|
|
23
|
+
/** Apply values (a saved scenario): the chart redraws with them. Unknown keys are ignored by the chart. */
|
|
24
|
+
set(values: Readonly<Record<string, unknown>>): void;
|
|
25
|
+
/** Back to the chart's own defaults. */
|
|
26
|
+
reset(): void;
|
|
27
|
+
onChange(cb: (c: Controls) => void): () => void;
|
|
28
|
+
}
|
|
29
|
+
/** What attachControls hands the host: the uiState pair the chart reads and writes. */
|
|
30
|
+
export interface ControlsAttachment {
|
|
31
|
+
/** Merge into the host's options at creation (and they stay: setOptions merges). */
|
|
32
|
+
readonly options: {
|
|
33
|
+
uiState: Record<string, unknown>;
|
|
34
|
+
setUiState: (s: unknown) => void;
|
|
35
|
+
};
|
|
36
|
+
/** Read the chart's controls now (call once a render has settled). */
|
|
37
|
+
read(): void;
|
|
38
|
+
detach(): void;
|
|
39
|
+
}
|
|
40
|
+
/** Make a controls handle: give it to one chart (a BicChart's `controls`, or attachControls). */
|
|
41
|
+
export declare function createControls(initial?: Readonly<Record<string, unknown>>): Controls;
|
|
42
|
+
/** Read each knob off a rendered chart: values from the bag, label and readout from what it drew. */
|
|
43
|
+
export declare function readControls(container: HTMLElement): ControlInfo[];
|
|
44
|
+
/**
|
|
45
|
+
* Wire a controls handle to one chart. Call it before creating the host and pass
|
|
46
|
+
* `attachment.options` into createChartHost's options (a raw uiState / setUiState pair takes
|
|
47
|
+
* precedence over any viewState provider); `getHost` returns the host once it exists. Call
|
|
48
|
+
* `read()` after each render settles.
|
|
49
|
+
*/
|
|
50
|
+
export declare function attachControls(getHost: () => ChartHost | null, container: HTMLElement, controls: Controls): ControlsAttachment;
|