@bicharts/chart-host 0.6.90 → 0.6.91
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/README.md +20 -6
- package/dist/{chunk-FHHT7DBM.mjs → chunk-RDUEXM2D.mjs} +61 -19
- package/dist/index.mjs +1 -1
- package/dist/react.mjs +9 -4
- package/dist/types/host.d.ts +4 -3
- package/dist/types/react.d.ts +5 -2
- package/dist/types/zoomMemory/zoomMemoryDom.d.ts +15 -6
- package/dist/zoom-memory.mjs +38 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -269,9 +269,11 @@ cue, a hierarchy level) is never flattened by a fix meant for another chart.
|
|
|
269
269
|
|
|
270
270
|
**How far the rest dims, per chart.** `options.dimOpacity` (a share of full opacity, `0.01`-`1`;
|
|
271
271
|
`DIM_OPACITY_MIN` is the floor) sets the level for one host without writing CSS, and it is live
|
|
272
|
-
through `setOptions`.
|
|
273
|
-
|
|
274
|
-
|
|
272
|
+
through `setOptions`. It takes a number or a numeric string; a value above 1 is clamped to 1 (no
|
|
273
|
+
dim), never read as a percentage, and anything else is ignored. The host writes it as
|
|
274
|
+
`--lch-dim-opacity` on its own container and, when the option goes away or the host is destroyed,
|
|
275
|
+
puts back the value your page had set inline there, so with the option absent nothing is written
|
|
276
|
+
and the CSS above decides: the stylesheet's `0.25`, or whatever your page sets on the variable.
|
|
275
277
|
|
|
276
278
|
```js
|
|
277
279
|
createChartHost(el, { code, data, options: { dimOpacity: 0.15 } });
|
|
@@ -410,15 +412,27 @@ const zoomHost: ZoomMemoryHost = {
|
|
|
410
412
|
persistZoom: json => localStorage.setItem("chart-zoom", json), // "" forgets
|
|
411
413
|
log: () => {},
|
|
412
414
|
};
|
|
413
|
-
const zoomMemory = createZoomMemoryHook(zoomHost); // once per
|
|
415
|
+
const zoomMemory = createZoomMemoryHook(zoomHost); // once per chart, reused by every host built for it
|
|
414
416
|
const host = createChartHost(el, { code, data, d3, zoomMemory });
|
|
415
417
|
```
|
|
416
418
|
|
|
417
419
|
The view survives every redraw in the session whatever the preference says, and a close and reopen
|
|
418
420
|
only while `rememberAcrossSessions()` is true. It is keyed by the chart's code and the viewport size
|
|
419
421
|
(to the nearest 32 px), so a regenerated chart or a real resize opens at full extent instead of
|
|
420
|
-
somewhere arbitrary. A chart with several zoomable panels is left alone.
|
|
421
|
-
the
|
|
422
|
+
somewhere arbitrary. A chart with several zoomable panels is left alone. The `ZoomMemoryHost` is the
|
|
423
|
+
memory: keep one per chart for as long as the chart is on the page, and hand its hook to every host
|
|
424
|
+
built for that chart, on the same element or, after a remount, a new one (a destroyed host releases
|
|
425
|
+
the hook's watcher, so the next one can take it). In React, pass the hook as the `zoomMemory` prop of
|
|
426
|
+
`<BicChart>`, built once (`zoomHost` as above, kept outside the render):
|
|
427
|
+
|
|
428
|
+
```tsx
|
|
429
|
+
const [zoomMemory] = useState(() => createZoomMemoryHook(zoomHost)); // once, however often this renders
|
|
430
|
+
return <BicChart code={code} data={data} d3={d3} zoomMemory={zoomMemory} />;
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
A hook that arrives after mount, or goes away, rebuilds the chart's host; swapping one hook for another
|
|
434
|
+
on a mounted `<BicChart>` rebuilds nothing, and applies at the next rebuild (a new `code`, say) or at
|
|
435
|
+
once if you remount it with a `key`, so a hook built inline in the props costs no rebuild.
|
|
422
436
|
|
|
423
437
|
**A scrolled chart** keeps its place with no setup. A chart taller than its frame scrolls, and every
|
|
424
438
|
redraw empties the container, which used to send the reader back to the top. The host now parks the
|
|
@@ -614,10 +614,14 @@ function resolveOptions(p) {
|
|
|
614
614
|
cultureCode: p.cultureCode,
|
|
615
615
|
allowTooltips: p.allowTooltips,
|
|
616
616
|
noDataText: p.noDataText,
|
|
617
|
-
// A finite number
|
|
618
|
-
//
|
|
617
|
+
// A finite number, or a string that is numeric once trimmed, is clamped to DIM_OPACITY_MIN-1; anything else (absent,
|
|
618
|
+
// blank, a boolean, an array, an object) stays undefined so the stylesheet - and a page's own CSS override - still
|
|
619
|
+
// decides. Not numberOr: Number() reads true as 1 and false, [] and whitespace as 0, which would dim to the floor.
|
|
620
|
+
// Above 1 is clamped to 1, which dims nothing; it is never reinterpreted as a percentage, so the option keeps one
|
|
621
|
+
// unit (a share of full opacity) for every value. Never defaulted here: that would override every page's CSS.
|
|
619
622
|
dimOpacity: (() => {
|
|
620
|
-
const
|
|
623
|
+
const v = p.dimOpacity;
|
|
624
|
+
const n = typeof v === "number" ? v : typeof v === "string" && v.trim() !== "" ? Number(v) : NaN;
|
|
621
625
|
return Number.isFinite(n) ? clamp(n, DIM_OPACITY_MIN, 1) : void 0;
|
|
622
626
|
})(),
|
|
623
627
|
// A recognised glyph or none: an unknown string must never reach the painter as a live mode, and "none" is
|
|
@@ -2031,6 +2035,7 @@ function compileRenderFn(code, win, doc, d3) {
|
|
|
2031
2035
|
}
|
|
2032
2036
|
var ASYNC_RENDER_TIMEOUT_MS = 15e3;
|
|
2033
2037
|
var isThenable = (v) => !!v && (typeof v === "object" || typeof v === "function") && typeof v.then === "function";
|
|
2038
|
+
var dimVarPage = /* @__PURE__ */ new WeakMap();
|
|
2034
2039
|
function createChartHost(container, config) {
|
|
2035
2040
|
const doc = container.ownerDocument;
|
|
2036
2041
|
const win = doc?.defaultView ?? globalThis;
|
|
@@ -2098,6 +2103,19 @@ function createChartHost(container, config) {
|
|
|
2098
2103
|
});
|
|
2099
2104
|
};
|
|
2100
2105
|
let dimVarSet = false;
|
|
2106
|
+
const releaseDimVar = () => {
|
|
2107
|
+
if (!dimVarSet) return;
|
|
2108
|
+
dimVarSet = false;
|
|
2109
|
+
const page = dimVarPage.get(container);
|
|
2110
|
+
if (!page || --page.holders > 0) return;
|
|
2111
|
+
dimVarPage.delete(container);
|
|
2112
|
+
try {
|
|
2113
|
+
if (container.style?.getPropertyValue?.(DIM_OPACITY_VAR) !== page.wrote) return;
|
|
2114
|
+
if (page.was) container.style?.setProperty(DIM_OPACITY_VAR, page.was.value, page.was.priority);
|
|
2115
|
+
else container.style?.removeProperty(DIM_OPACITY_VAR);
|
|
2116
|
+
} catch {
|
|
2117
|
+
}
|
|
2118
|
+
};
|
|
2101
2119
|
let current = null;
|
|
2102
2120
|
let activeTickEl = null;
|
|
2103
2121
|
let selectionIsPeriod = false;
|
|
@@ -2169,6 +2187,7 @@ function createChartHost(container, config) {
|
|
|
2169
2187
|
}
|
|
2170
2188
|
};
|
|
2171
2189
|
const paintSelection = () => {
|
|
2190
|
+
if (destroyed) return;
|
|
2172
2191
|
try {
|
|
2173
2192
|
const sel = current;
|
|
2174
2193
|
const active = !!sel && sel.length > 0;
|
|
@@ -2176,12 +2195,20 @@ function createChartHost(container, config) {
|
|
|
2176
2195
|
container.classList?.[dim ? "add" : "remove"](SELECTION_ACTIVE_CLASS);
|
|
2177
2196
|
const want = resolved.dimOpacity;
|
|
2178
2197
|
if (typeof want === "number") {
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2198
|
+
const level = String(want);
|
|
2199
|
+
let page = dimVarPage.get(container);
|
|
2200
|
+
if (!dimVarSet) {
|
|
2201
|
+
if (!page) {
|
|
2202
|
+
const value = container.style?.getPropertyValue?.(DIM_OPACITY_VAR) ?? "";
|
|
2203
|
+
page = { was: value ? { value, priority: container.style?.getPropertyPriority?.(DIM_OPACITY_VAR) ?? "" } : null, holders: 0, wrote: level };
|
|
2204
|
+
dimVarPage.set(container, page);
|
|
2205
|
+
}
|
|
2206
|
+
page.holders++;
|
|
2207
|
+
dimVarSet = true;
|
|
2208
|
+
}
|
|
2209
|
+
if (page) page.wrote = level;
|
|
2210
|
+
container.style?.setProperty(DIM_OPACITY_VAR, level);
|
|
2211
|
+
} else releaseDimVar();
|
|
2185
2212
|
const lit = container.querySelectorAll(`.${ACTIVE_TICK_CLASS}`);
|
|
2186
2213
|
for (const t of Array.from(lit)) t.classList?.remove(ACTIVE_TICK_CLASS);
|
|
2187
2214
|
const tickStillDrawn = !!activeTickEl && (typeof container.contains === "function" ? container.contains(activeTickEl) : activeTickEl.isConnected !== false);
|
|
@@ -2418,6 +2445,7 @@ function createChartHost(container, config) {
|
|
|
2418
2445
|
}
|
|
2419
2446
|
};
|
|
2420
2447
|
const notify = (rowIdxs, source, tick, fromChart = false) => {
|
|
2448
|
+
if (destroyed) return;
|
|
2421
2449
|
current = rowIdxs;
|
|
2422
2450
|
selectionFromChart = rowIdxs.length > 0 && fromChart;
|
|
2423
2451
|
activeTickEl = rowIdxs.length ? tick ?? null : null;
|
|
@@ -2523,6 +2551,14 @@ function createChartHost(container, config) {
|
|
|
2523
2551
|
}
|
|
2524
2552
|
}
|
|
2525
2553
|
};
|
|
2554
|
+
const releaseZoom = () => {
|
|
2555
|
+
if (zoomMemory) {
|
|
2556
|
+
try {
|
|
2557
|
+
zoomMemory.release?.(container);
|
|
2558
|
+
} catch {
|
|
2559
|
+
}
|
|
2560
|
+
}
|
|
2561
|
+
};
|
|
2526
2562
|
const keepScroll = config.restoreScroll !== false;
|
|
2527
2563
|
let chartSource;
|
|
2528
2564
|
const scrollKey = () => {
|
|
@@ -2761,12 +2797,18 @@ function createChartHost(container, config) {
|
|
|
2761
2797
|
);
|
|
2762
2798
|
}));
|
|
2763
2799
|
},
|
|
2800
|
+
// A DESTROYED HOST OWNS NOTHING ON THE CONTAINER any more. A host rebuilt on the same element has taken over
|
|
2801
|
+
// the chart's slots (its clear, its animation stop, its initial mark) and the clicked-marks record, so every
|
|
2802
|
+
// method below that reaches the container returns first: a stale reference to the old host (a closure, a late
|
|
2803
|
+
// callback) must not clear, stop or annotate the chart that is on screen now.
|
|
2764
2804
|
setOptions(partial) {
|
|
2805
|
+
if (destroyed) return;
|
|
2765
2806
|
raw = { ...raw, ...partial };
|
|
2766
2807
|
resolved = resolveForHost(raw);
|
|
2767
2808
|
host.render();
|
|
2768
2809
|
},
|
|
2769
2810
|
setData(next) {
|
|
2811
|
+
if (destroyed) return;
|
|
2770
2812
|
data = next;
|
|
2771
2813
|
host.render();
|
|
2772
2814
|
},
|
|
@@ -2774,8 +2816,9 @@ function createChartHost(container, config) {
|
|
|
2774
2816
|
return resolved;
|
|
2775
2817
|
},
|
|
2776
2818
|
setAnnotations(list) {
|
|
2819
|
+
if (destroyed) return;
|
|
2777
2820
|
annotationLayer.set(list);
|
|
2778
|
-
|
|
2821
|
+
try {
|
|
2779
2822
|
drawAnnotations();
|
|
2780
2823
|
} catch {
|
|
2781
2824
|
}
|
|
@@ -2794,6 +2837,7 @@ function createChartHost(container, config) {
|
|
|
2794
2837
|
return () => subs.delete(cb);
|
|
2795
2838
|
},
|
|
2796
2839
|
clear() {
|
|
2840
|
+
if (destroyed) return;
|
|
2797
2841
|
const s = container[CONTAINER_SLOT_XF_CLEAR];
|
|
2798
2842
|
if (typeof s === "function") {
|
|
2799
2843
|
try {
|
|
@@ -2806,6 +2850,7 @@ function createChartHost(container, config) {
|
|
|
2806
2850
|
notify([], "host");
|
|
2807
2851
|
},
|
|
2808
2852
|
highlight(rowIdxs) {
|
|
2853
|
+
if (destroyed) return;
|
|
2809
2854
|
const rows = Array.isArray(rowIdxs) ? rowIdxs.slice() : [];
|
|
2810
2855
|
if (lasso && lasso.rows().length && !sameRowSet(rows, lasso.rows())) lasso.clear({ publish: false, source: "host" });
|
|
2811
2856
|
notify(rows, "host");
|
|
@@ -2814,9 +2859,11 @@ function createChartHost(container, config) {
|
|
|
2814
2859
|
return current;
|
|
2815
2860
|
}
|
|
2816
2861
|
},
|
|
2817
|
-
animation: { stop
|
|
2862
|
+
animation: { stop() {
|
|
2863
|
+
if (!destroyed) stopAnim();
|
|
2864
|
+
} },
|
|
2818
2865
|
initialMark() {
|
|
2819
|
-
return container[CONTAINER_SLOT_INITIAL_XF_MARK] ?? null;
|
|
2866
|
+
return destroyed ? null : container[CONTAINER_SLOT_INITIAL_XF_MARK] ?? null;
|
|
2820
2867
|
},
|
|
2821
2868
|
destroy() {
|
|
2822
2869
|
if (destroyed) return;
|
|
@@ -2828,17 +2875,12 @@ function createChartHost(container, config) {
|
|
|
2828
2875
|
clearPress(container);
|
|
2829
2876
|
disposeLasso();
|
|
2830
2877
|
subs.clear();
|
|
2831
|
-
|
|
2832
|
-
try {
|
|
2833
|
-
container.style?.removeProperty(DIM_OPACITY_VAR);
|
|
2834
|
-
} catch {
|
|
2835
|
-
}
|
|
2836
|
-
dimVarSet = false;
|
|
2837
|
-
}
|
|
2878
|
+
releaseDimVar();
|
|
2838
2879
|
annotationLayer.destroy();
|
|
2839
2880
|
if (hasRendered && keepScroll) rememberScroll(container, scrollKey());
|
|
2840
2881
|
unpinScrolledAxis(container);
|
|
2841
2882
|
flushZoom();
|
|
2883
|
+
releaseZoom();
|
|
2842
2884
|
followGlyphs(false);
|
|
2843
2885
|
container.innerHTML = "";
|
|
2844
2886
|
if (glyphPositionWas !== null) {
|
package/dist/index.mjs
CHANGED
package/dist/react.mjs
CHANGED
|
@@ -22,7 +22,7 @@ import {
|
|
|
22
22
|
loadGeo,
|
|
23
23
|
syncMemberSelection,
|
|
24
24
|
toSourceRows
|
|
25
|
-
} from "./chunk-
|
|
25
|
+
} from "./chunk-RDUEXM2D.mjs";
|
|
26
26
|
import "./chunk-QHMLMF4L.mjs";
|
|
27
27
|
import "./chunk-PDVEIW42.mjs";
|
|
28
28
|
import "./chunk-VEVBXK44.mjs";
|
|
@@ -113,6 +113,11 @@ function BicChart(props) {
|
|
|
113
113
|
};
|
|
114
114
|
const userSelectedRef = useRef(false);
|
|
115
115
|
const optKey = useMemo(() => JSON.stringify(options ?? {}), [options]);
|
|
116
|
+
const zoomOn = zoomMemory != null;
|
|
117
|
+
const zoomRef = useRef(zoomMemory ?? null);
|
|
118
|
+
useLayoutEffect(() => {
|
|
119
|
+
zoomRef.current = zoomMemory ?? null;
|
|
120
|
+
});
|
|
116
121
|
const builtWithRef = useRef(null);
|
|
117
122
|
const onSelectRef = useRef(onSelect);
|
|
118
123
|
onSelectRef.current = onSelect;
|
|
@@ -155,7 +160,7 @@ function BicChart(props) {
|
|
|
155
160
|
d3,
|
|
156
161
|
geoKind,
|
|
157
162
|
viewState,
|
|
158
|
-
zoomMemory,
|
|
163
|
+
zoomMemory: zoomRef.current,
|
|
159
164
|
labelContrast,
|
|
160
165
|
layoutPass,
|
|
161
166
|
restoreScroll,
|
|
@@ -191,7 +196,7 @@ function BicChart(props) {
|
|
|
191
196
|
host.destroy();
|
|
192
197
|
hostRef.current = null;
|
|
193
198
|
};
|
|
194
|
-
}, [code, renderFn, d3, geoKind, labelContrast, layoutPass, restoreScroll, controls]);
|
|
199
|
+
}, [code, renderFn, d3, geoKind, labelContrast, layoutPass, restoreScroll, zoomOn, controls]);
|
|
195
200
|
useLayoutEffect(() => {
|
|
196
201
|
const host = hostRef.current;
|
|
197
202
|
if (!host || !selects) return;
|
|
@@ -217,7 +222,7 @@ function BicChart(props) {
|
|
|
217
222
|
b.detach();
|
|
218
223
|
if (bindingRef.current === b) bindingRef.current = null;
|
|
219
224
|
};
|
|
220
|
-
}, [selects, code, renderFn, d3, geoKind, labelContrast, layoutPass, restoreScroll]);
|
|
225
|
+
}, [selects, code, renderFn, d3, geoKind, labelContrast, layoutPass, restoreScroll, zoomOn]);
|
|
221
226
|
useEffect(() => {
|
|
222
227
|
if (!hostRef.current || !options) return;
|
|
223
228
|
if (builtWithRef.current?.optKey === optKey) return;
|
package/dist/types/host.d.ts
CHANGED
|
@@ -80,9 +80,10 @@ export interface ChartHostConfig {
|
|
|
80
80
|
/**
|
|
81
81
|
* REMEMBER THE READER'S ZOOM AND PAN on a chart that calls d3.zoom(), across every re-render (and across a
|
|
82
82
|
* close and reopen, if the host's store says so). Build it with createZoomMemoryHook() from
|
|
83
|
-
* "@bicharts/chart-host/zoom-memory", ONCE per
|
|
84
|
-
*
|
|
85
|
-
*
|
|
83
|
+
* "@bicharts/chart-host/zoom-memory", ONCE per chart, and hand the same hook to every host built for that chart
|
|
84
|
+
* (on the same element or, after a remount, a new one): a host that is destroyed releases the hook's watcher, so
|
|
85
|
+
* the hook can go to the next. Absent (the default), d3.zoom is never wrapped and nothing is listened for. It
|
|
86
|
+
* applies to a chart compiled from `code` only: a precompiled `renderFn` closed over its own d3.
|
|
86
87
|
*/
|
|
87
88
|
zoomMemory?: ZoomMemoryHook | null;
|
|
88
89
|
/** D3 v7 instance handed to the compiled code. Default: (globalThis as any).d3. */
|
package/dist/types/react.d.ts
CHANGED
|
@@ -35,8 +35,11 @@ export interface BicChartProps {
|
|
|
35
35
|
/**
|
|
36
36
|
* REMEMBER THE READER'S ZOOM AND PAN on a chart that calls d3.zoom(), across re-renders and, if
|
|
37
37
|
* the hook's store keeps it, across a reload. Build it with createZoomMemoryHook() from
|
|
38
|
-
* "@bicharts/chart-host/zoom-memory"
|
|
39
|
-
* keep the view. Absent, nothing is remembered.
|
|
38
|
+
* "@bicharts/chart-host/zoom-memory" over one ZoomMemoryHost per chart (module scope, useRef or
|
|
39
|
+
* useMemo) with somewhere to keep the view. Absent (null or undefined), nothing is remembered. Host config:
|
|
40
|
+
* a hook that arrives after mount, or goes away, rebuilds the host; a change from one hook to another does
|
|
41
|
+
* not, and applies at the next rebuild (a new `code`, say), or at once if the component is remounted with a
|
|
42
|
+
* `key`. So a hook built inline in the props is harmless. See createChartHost's `zoomMemory`.
|
|
40
43
|
*/
|
|
41
44
|
zoomMemory?: ChartHostConfig["zoomMemory"];
|
|
42
45
|
/**
|
|
@@ -1,20 +1,23 @@
|
|
|
1
1
|
import { type ZoomState } from "./zoomMemory";
|
|
2
2
|
/**
|
|
3
3
|
* WHAT A HOST SUPPLIES: where the remembered view lives, and whether it may outlive the session.
|
|
4
|
-
* One per chart
|
|
5
|
-
* and re-created on the same element
|
|
4
|
+
* One per chart, kept for as long as the chart is on the page, so it outlives a host that is destroyed
|
|
5
|
+
* and re-created (on the same element for a redraw on every resize, say, or on a new one after a remount).
|
|
6
6
|
*/
|
|
7
7
|
export interface ZoomMemoryHost {
|
|
8
8
|
/** The in-session tier, always on: survives every re-render, never written anywhere. */
|
|
9
9
|
liveZoomState: ZoomState | null;
|
|
10
10
|
/** The key the watcher stamps on what it records (the latest render's). */
|
|
11
11
|
zoomWatchKey: string;
|
|
12
|
-
/** Whether the watcher's listeners are on zoomContainer (they attach once
|
|
12
|
+
/** Whether the watcher's listeners are on zoomContainer (they attach once per element, and come off when the
|
|
13
|
+
* host that drew into it is destroyed). */
|
|
13
14
|
zoomWatchAttached: boolean;
|
|
14
15
|
/** The pending settle timer, 0 when none. */
|
|
15
16
|
zoomSettleTimer: number;
|
|
16
17
|
/** The element the chart draws into; it outlives each render, so listeners attach to it once. Null until
|
|
17
|
-
* known: createZoomMemoryHook's restore adopts the render's container then
|
|
18
|
+
* known: createZoomMemoryHook's restore adopts the render's container then, moves to a new one when a
|
|
19
|
+
* render arrives on a different element (a remount), and gives it back when the host is destroyed. An element
|
|
20
|
+
* set here beforehand is the page's choice: the hook watches it and neither replaces nor gives it back. */
|
|
18
21
|
zoomContainer: Element | null;
|
|
19
22
|
/** Is the persisted tier on NOW? Read on every restore and every write, so a change applies to the next. */
|
|
20
23
|
rememberAcrossSessions(): boolean;
|
|
@@ -68,7 +71,13 @@ export interface ZoomMemoryHook {
|
|
|
68
71
|
flush(): void;
|
|
69
72
|
/** forgetZoom: drop the session view and a pending gesture. */
|
|
70
73
|
forget(): void;
|
|
74
|
+
/** Let go of the watcher on `container`, the element a destroyed host drew into (after flush, which records a
|
|
75
|
+
* gesture still settling), so the next host handed this hook attaches afresh and a hook that is replaced leaves
|
|
76
|
+
* nothing on the element. Without `container`, wherever the watcher is. A watcher that has since moved to another
|
|
77
|
+
* element is left alone. Optional: a hook written by hand without it works as before. */
|
|
78
|
+
release?(container?: Element): void;
|
|
71
79
|
}
|
|
72
|
-
/** Bind the zoom memory to one host object. Build it once per
|
|
73
|
-
* host created
|
|
80
|
+
/** Bind the zoom memory to one host object. Build it once per chart and hand the same hook to every
|
|
81
|
+
* host created for it, so the session tier outlives each one - on the same element, or on a new one
|
|
82
|
+
* after a remount, where the watcher moves with it. A host that is destroyed releases the watcher. */
|
|
74
83
|
export declare function createZoomMemoryHook(host: ZoomMemoryHost): ZoomMemoryHook;
|
package/dist/zoom-memory.mjs
CHANGED
|
@@ -192,15 +192,34 @@ function attachZoomWatcher(host, key) {
|
|
|
192
192
|
captureCurrentZoom(host);
|
|
193
193
|
}, SETTLE_MS);
|
|
194
194
|
};
|
|
195
|
-
for (const evt of
|
|
195
|
+
for (const evt of WATCHED_EVENTS) {
|
|
196
196
|
try {
|
|
197
197
|
ctr.addEventListener(evt, settle, true);
|
|
198
198
|
} catch {
|
|
199
199
|
}
|
|
200
200
|
}
|
|
201
|
+
attachedWatchers.set(host, { ctr, settle });
|
|
201
202
|
host.zoomWatchAttached = true;
|
|
202
203
|
host.log("zoom-watcher-attached", {});
|
|
203
204
|
}
|
|
205
|
+
var WATCHED_EVENTS = ["wheel", "pointerup", "pointercancel", "click", "dblclick", "keyup"];
|
|
206
|
+
var attachedWatchers = /* @__PURE__ */ new WeakMap();
|
|
207
|
+
var adoptedContainers = /* @__PURE__ */ new WeakMap();
|
|
208
|
+
function detachZoomWatcher(host) {
|
|
209
|
+
const w = attachedWatchers.get(host);
|
|
210
|
+
if (w) {
|
|
211
|
+
for (const evt of WATCHED_EVENTS) {
|
|
212
|
+
try {
|
|
213
|
+
w.ctr.removeEventListener(evt, w.settle, true);
|
|
214
|
+
} catch {
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
attachedWatchers.delete(host);
|
|
218
|
+
}
|
|
219
|
+
if (host.zoomSettleTimer) clearTimeout(host.zoomSettleTimer);
|
|
220
|
+
host.zoomSettleTimer = 0;
|
|
221
|
+
host.zoomWatchAttached = false;
|
|
222
|
+
}
|
|
204
223
|
function captureCurrentZoom(host) {
|
|
205
224
|
try {
|
|
206
225
|
const key = host.zoomWatchKey;
|
|
@@ -242,17 +261,32 @@ function forgetZoom(host) {
|
|
|
242
261
|
host.liveZoomState = null;
|
|
243
262
|
}
|
|
244
263
|
function createZoomMemoryHook(host) {
|
|
245
|
-
|
|
264
|
+
const hook = {
|
|
246
265
|
keyFor: (code, width, height) => zoomStateKey(B(String(code ?? "")), width, height),
|
|
247
266
|
capture: (d3) => captureZoomBehaviors(d3, host),
|
|
248
267
|
restore: (container, capture, key, d3) => {
|
|
249
|
-
|
|
268
|
+
const mine = adoptedContainers.get(host);
|
|
269
|
+
if (host.zoomContainer && host.zoomContainer === mine && mine !== container) {
|
|
270
|
+
detachZoomWatcher(host);
|
|
271
|
+
host.zoomContainer = null;
|
|
272
|
+
}
|
|
273
|
+
if (!host.zoomContainer) {
|
|
274
|
+
host.zoomContainer = container;
|
|
275
|
+
adoptedContainers.set(host, container);
|
|
276
|
+
}
|
|
250
277
|
restoreZoomForRender(d3, host, capture, key);
|
|
251
278
|
attachZoomWatcher(host, key);
|
|
252
279
|
},
|
|
253
280
|
flush: () => flushPendingZoom(host),
|
|
254
|
-
forget: () => forgetZoom(host)
|
|
281
|
+
forget: () => forgetZoom(host),
|
|
282
|
+
release: (container) => {
|
|
283
|
+
if (container && attachedWatchers.get(host)?.ctr !== container) return;
|
|
284
|
+
detachZoomWatcher(host);
|
|
285
|
+
if (host.zoomContainer && host.zoomContainer === adoptedContainers.get(host)) host.zoomContainer = null;
|
|
286
|
+
adoptedContainers.delete(host);
|
|
287
|
+
}
|
|
255
288
|
};
|
|
289
|
+
return hook;
|
|
256
290
|
}
|
|
257
291
|
export {
|
|
258
292
|
attachZoomWatcher,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bicharts/chart-host",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.91",
|
|
4
4
|
"description": "Run a BIC-generated D3 chart in any web host: compiles the generated render() function, applies the shared option defaults, resolves mark clicks (through tooltip overlays), owns the selection affordance, and translates row indices between cross-filtered charts. The same contract the BIC Power BI visual implements, minus Power BI. React bindings at @bicharts/chart-host/react.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|