@opendata-ai/openchart-vanilla 8.7.0 → 8.8.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/dist/{chunk-5O624XON.js → chunk-QBTRAQAX.js} +580 -23
- package/dist/chunk-QBTRAQAX.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +1 -1
- package/dist/story/index.js +1 -1
- package/dist/styles.css +1 -1
- package/dist/styles.css.map +1 -1
- package/package.json +3 -3
- package/dist/chunk-5O624XON.js.map +0 -1
|
@@ -4269,7 +4269,7 @@ function wireAnnotationDrag(svg, specAnnotations, onAnnotationEdit, onEdit, setD
|
|
|
4269
4269
|
};
|
|
4270
4270
|
}
|
|
4271
4271
|
function wireConnectorEndpointDrag(svg, specAnnotations, onEdit, setDragging) {
|
|
4272
|
-
const
|
|
4272
|
+
const SVG_NS5 = "http://www.w3.org/2000/svg";
|
|
4273
4273
|
const cleanups = [];
|
|
4274
4274
|
const annotationGroups = svg.querySelectorAll(".oc-annotation-text");
|
|
4275
4275
|
for (const el of annotationGroups) {
|
|
@@ -4307,7 +4307,7 @@ function wireConnectorEndpointDrag(svg, specAnnotations, onEdit, setDragging) {
|
|
|
4307
4307
|
const createdHandles = [];
|
|
4308
4308
|
for (const ep of endpoints) {
|
|
4309
4309
|
if (!Number.isFinite(ep.cx) || !Number.isFinite(ep.cy)) continue;
|
|
4310
|
-
const handleEl = document.createElementNS(
|
|
4310
|
+
const handleEl = document.createElementNS(SVG_NS5, "circle");
|
|
4311
4311
|
handleEl.setAttribute("class", "oc-connector-handle");
|
|
4312
4312
|
handleEl.setAttribute("data-endpoint", ep.name);
|
|
4313
4313
|
handleEl.setAttribute("cx", String(ep.cx));
|
|
@@ -4584,7 +4584,7 @@ function wireSeriesLabelDrag(svg, spec, onEdit, setDragging) {
|
|
|
4584
4584
|
function wireAnchorDrag(svg, specAnnotations, layout, onEdit, setDragging) {
|
|
4585
4585
|
if (!onEdit || !layout.xInvert || !layout.yInvert) return () => {
|
|
4586
4586
|
};
|
|
4587
|
-
const
|
|
4587
|
+
const SVG_NS5 = "http://www.w3.org/2000/svg";
|
|
4588
4588
|
const cleanups = [];
|
|
4589
4589
|
const annotationGroups = svg.querySelectorAll(".oc-annotation-text");
|
|
4590
4590
|
for (const el of annotationGroups) {
|
|
@@ -4599,7 +4599,7 @@ function wireAnchorDrag(svg, specAnnotations, layout, onEdit, setDragging) {
|
|
|
4599
4599
|
if (!resolved?.dot) continue;
|
|
4600
4600
|
const anchorX = resolved.dot.x;
|
|
4601
4601
|
const anchorY = resolved.dot.y;
|
|
4602
|
-
const handleEl = document.createElementNS(
|
|
4602
|
+
const handleEl = document.createElementNS(SVG_NS5, "circle");
|
|
4603
4603
|
handleEl.setAttribute("class", "oc-anchor-handle");
|
|
4604
4604
|
handleEl.setAttribute("cx", String(anchorX));
|
|
4605
4605
|
handleEl.setAttribute("cy", String(anchorY));
|
|
@@ -7003,6 +7003,42 @@ function cameraForTarget(layout, target) {
|
|
|
7003
7003
|
const cam = fitTarget(target, vb);
|
|
7004
7004
|
return { ...cam, k: Math.max(1, Math.min(40, cam.k)) };
|
|
7005
7005
|
}
|
|
7006
|
+
var DEFAULT_MAX_ZOOM = 12;
|
|
7007
|
+
function resolveMapZoom(zoom) {
|
|
7008
|
+
if (!zoom) return null;
|
|
7009
|
+
const cfg = zoom === true ? {} : zoom;
|
|
7010
|
+
const max = cfg.maxZoom;
|
|
7011
|
+
return {
|
|
7012
|
+
maxZoom: typeof max === "number" && Number.isFinite(max) ? Math.max(1, max) : DEFAULT_MAX_ZOOM,
|
|
7013
|
+
controls: cfg.controls !== false
|
|
7014
|
+
};
|
|
7015
|
+
}
|
|
7016
|
+
function clampMapCamera(camera, vb, maxZoom) {
|
|
7017
|
+
const k = Math.min(Math.max(camera.k, 1), Math.max(1, maxZoom));
|
|
7018
|
+
const halfW = vb.width / (2 * k);
|
|
7019
|
+
const halfH = vb.height / (2 * k);
|
|
7020
|
+
return {
|
|
7021
|
+
cx: Math.min(Math.max(camera.cx, halfW), vb.width - halfW),
|
|
7022
|
+
cy: Math.min(Math.max(camera.cy, halfH), vb.height - halfH),
|
|
7023
|
+
k
|
|
7024
|
+
};
|
|
7025
|
+
}
|
|
7026
|
+
function zoomCameraAt(camera, vb, k, pivotX, pivotY) {
|
|
7027
|
+
const ox = pivotX - vb.width / 2;
|
|
7028
|
+
const oy = pivotY - vb.height / 2;
|
|
7029
|
+
const px = camera.cx + ox / camera.k;
|
|
7030
|
+
const py = camera.cy + oy / camera.k;
|
|
7031
|
+
return { cx: px - ox / k, cy: py - oy / k, k };
|
|
7032
|
+
}
|
|
7033
|
+
function panCamera(camera, dx, dy) {
|
|
7034
|
+
return { cx: camera.cx - dx / camera.k, cy: camera.cy - dy / camera.k, k: camera.k };
|
|
7035
|
+
}
|
|
7036
|
+
function wheelZoomFactor(e) {
|
|
7037
|
+
const perUnit = e.deltaMode === 1 ? 0.05 : e.deltaMode === 2 ? 1 : 2e-3;
|
|
7038
|
+
let exp = -e.deltaY * perUnit;
|
|
7039
|
+
if (e.ctrlKey && e.deltaMode === 0 && Math.abs(e.deltaY) < 50) exp *= 5;
|
|
7040
|
+
return 2 ** Math.max(-1, Math.min(1, exp));
|
|
7041
|
+
}
|
|
7006
7042
|
function applyMapCamera(svg, camera, layout) {
|
|
7007
7043
|
const cameraGroup = svg.querySelector("[data-oc-map-camera]");
|
|
7008
7044
|
if (!cameraGroup) return;
|
|
@@ -7524,6 +7560,468 @@ function runMapFillTransition(svg, prev, opts) {
|
|
|
7524
7560
|
};
|
|
7525
7561
|
}
|
|
7526
7562
|
|
|
7563
|
+
// src/map-zoom.ts
|
|
7564
|
+
var ZOOM_STEP = 2;
|
|
7565
|
+
var KEY_PAN_FRACTION = 0.1;
|
|
7566
|
+
var DRAG_THRESHOLD = 3;
|
|
7567
|
+
var HINT_MS = 1500;
|
|
7568
|
+
var EPS = 1e-3;
|
|
7569
|
+
var SVG_NS4 = "http://www.w3.org/2000/svg";
|
|
7570
|
+
var clipIdCounter = 0;
|
|
7571
|
+
var ICONS = {
|
|
7572
|
+
in: '<path d="M8 3v10M3 8h10"/>',
|
|
7573
|
+
out: '<path d="M3 8h10"/>',
|
|
7574
|
+
reset: '<path d="M3 8a5 5 0 1 0 1.6-3.7"/><path d="M3 2.5v2.8h2.8"/>'
|
|
7575
|
+
};
|
|
7576
|
+
function isMacLike() {
|
|
7577
|
+
if (typeof navigator === "undefined") return false;
|
|
7578
|
+
return /Mac|iPhone|iPad|iPod/.test(navigator.platform || navigator.userAgent || "");
|
|
7579
|
+
}
|
|
7580
|
+
function createMapZoomController(host) {
|
|
7581
|
+
const { container } = host;
|
|
7582
|
+
const clipId = `oc-map-zoom-clip-${++clipIdCounter}`;
|
|
7583
|
+
let overlay = null;
|
|
7584
|
+
let buttons = null;
|
|
7585
|
+
let hint = null;
|
|
7586
|
+
let hintTimer = null;
|
|
7587
|
+
let drag = null;
|
|
7588
|
+
let suppressClick = false;
|
|
7589
|
+
let pinch = null;
|
|
7590
|
+
let touchPan = null;
|
|
7591
|
+
let gestureStartK = null;
|
|
7592
|
+
function vb() {
|
|
7593
|
+
return host.getLayout().mapSize;
|
|
7594
|
+
}
|
|
7595
|
+
function clamp(camera) {
|
|
7596
|
+
const cfg = host.getConfig();
|
|
7597
|
+
return clampMapCamera(camera, vb(), cfg?.maxZoom ?? 1);
|
|
7598
|
+
}
|
|
7599
|
+
function clampK(k) {
|
|
7600
|
+
const cfg = host.getConfig();
|
|
7601
|
+
return Math.min(Math.max(k, 1), cfg?.maxZoom ?? 1);
|
|
7602
|
+
}
|
|
7603
|
+
function svgRect() {
|
|
7604
|
+
const svg = host.getSvg();
|
|
7605
|
+
let rect = svg?.getBoundingClientRect();
|
|
7606
|
+
if (!rect || !rect.width || !rect.height) rect = container.getBoundingClientRect();
|
|
7607
|
+
if (!rect.width || !rect.height) return null;
|
|
7608
|
+
return rect;
|
|
7609
|
+
}
|
|
7610
|
+
function toMapFrame(clientX, clientY) {
|
|
7611
|
+
const rect = svgRect();
|
|
7612
|
+
if (!rect) return null;
|
|
7613
|
+
const layout = host.getLayout();
|
|
7614
|
+
const x = (clientX - rect.left) * (layout.width / rect.width) - layout.area.x;
|
|
7615
|
+
const y = (clientY - rect.top) * (layout.height / rect.height) - layout.area.y;
|
|
7616
|
+
const inside = x >= 0 && y >= 0 && x <= layout.area.width && y <= layout.area.height;
|
|
7617
|
+
return { x, y, inside };
|
|
7618
|
+
}
|
|
7619
|
+
function unitsPerPx() {
|
|
7620
|
+
const rect = svgRect();
|
|
7621
|
+
return rect ? host.getLayout().width / rect.width : 1;
|
|
7622
|
+
}
|
|
7623
|
+
function isZoomed() {
|
|
7624
|
+
return host.getCamera().k > 1 + EPS;
|
|
7625
|
+
}
|
|
7626
|
+
function fromControls(e) {
|
|
7627
|
+
const t = e.target;
|
|
7628
|
+
return !!t?.closest?.(".oc-map-zoom");
|
|
7629
|
+
}
|
|
7630
|
+
function zoomAround(factor, x, y, animate) {
|
|
7631
|
+
const cam = host.getCamera();
|
|
7632
|
+
const next = clamp(zoomCameraAt(cam, vb(), clampK(cam.k * factor), x, y));
|
|
7633
|
+
if (animate) host.animateCamera(next);
|
|
7634
|
+
else host.setCamera(next);
|
|
7635
|
+
}
|
|
7636
|
+
function zoomAtCenter(factor) {
|
|
7637
|
+
const { width, height } = vb();
|
|
7638
|
+
zoomAround(factor, width / 2, height / 2, true);
|
|
7639
|
+
}
|
|
7640
|
+
function showHint() {
|
|
7641
|
+
if (!hint) return;
|
|
7642
|
+
hint.setAttribute("data-visible", "");
|
|
7643
|
+
if (hintTimer) clearTimeout(hintTimer);
|
|
7644
|
+
hintTimer = setTimeout(() => {
|
|
7645
|
+
hint?.removeAttribute("data-visible");
|
|
7646
|
+
hintTimer = null;
|
|
7647
|
+
}, HINT_MS);
|
|
7648
|
+
}
|
|
7649
|
+
function onWheel(e) {
|
|
7650
|
+
if (!host.getConfig() || fromControls(e)) return;
|
|
7651
|
+
const p = toMapFrame(e.clientX, e.clientY);
|
|
7652
|
+
if (!p?.inside) return;
|
|
7653
|
+
if (!e.ctrlKey && !e.metaKey) {
|
|
7654
|
+
if (Math.abs(e.deltaY) > Math.abs(e.deltaX)) showHint();
|
|
7655
|
+
return;
|
|
7656
|
+
}
|
|
7657
|
+
e.preventDefault();
|
|
7658
|
+
hint?.removeAttribute("data-visible");
|
|
7659
|
+
zoomAround(wheelZoomFactor(e), p.x, p.y, false);
|
|
7660
|
+
}
|
|
7661
|
+
function onGestureStart(e) {
|
|
7662
|
+
if (!host.getConfig()) return;
|
|
7663
|
+
e.preventDefault();
|
|
7664
|
+
gestureStartK = host.getCamera().k;
|
|
7665
|
+
}
|
|
7666
|
+
function onGestureChange(e) {
|
|
7667
|
+
if (!host.getConfig() || gestureStartK === null) return;
|
|
7668
|
+
e.preventDefault();
|
|
7669
|
+
if (pinch) return;
|
|
7670
|
+
const g = e;
|
|
7671
|
+
const p = toMapFrame(g.clientX, g.clientY);
|
|
7672
|
+
if (!p) return;
|
|
7673
|
+
const cam = host.getCamera();
|
|
7674
|
+
host.setCamera(clamp(zoomCameraAt(cam, vb(), clampK(gestureStartK * g.scale), p.x, p.y)));
|
|
7675
|
+
}
|
|
7676
|
+
function onGestureEnd(e) {
|
|
7677
|
+
if (gestureStartK === null) return;
|
|
7678
|
+
e.preventDefault();
|
|
7679
|
+
gestureStartK = null;
|
|
7680
|
+
}
|
|
7681
|
+
function onPointerDown(e) {
|
|
7682
|
+
if (e.pointerType === "touch" || e.button !== 0) return;
|
|
7683
|
+
if (!host.getConfig() || fromControls(e) || !isZoomed()) return;
|
|
7684
|
+
const p = toMapFrame(e.clientX, e.clientY);
|
|
7685
|
+
if (!p?.inside) return;
|
|
7686
|
+
drag = {
|
|
7687
|
+
id: e.pointerId,
|
|
7688
|
+
startX: e.clientX,
|
|
7689
|
+
startY: e.clientY,
|
|
7690
|
+
lastX: e.clientX,
|
|
7691
|
+
lastY: e.clientY,
|
|
7692
|
+
moved: false
|
|
7693
|
+
};
|
|
7694
|
+
}
|
|
7695
|
+
function onMouseDown(e) {
|
|
7696
|
+
if (e.button !== 0 || !host.getConfig() || fromControls(e)) return;
|
|
7697
|
+
if (!isZoomed() && e.detail < 2) return;
|
|
7698
|
+
const p = toMapFrame(e.clientX, e.clientY);
|
|
7699
|
+
if (!p?.inside) return;
|
|
7700
|
+
e.preventDefault();
|
|
7701
|
+
host.getSvg()?.focus({ preventScroll: true });
|
|
7702
|
+
}
|
|
7703
|
+
function onPointerMove(e) {
|
|
7704
|
+
if (!drag || e.pointerId !== drag.id) return;
|
|
7705
|
+
if (!drag.moved) {
|
|
7706
|
+
if (Math.hypot(e.clientX - drag.startX, e.clientY - drag.startY) < DRAG_THRESHOLD) return;
|
|
7707
|
+
drag.moved = true;
|
|
7708
|
+
const svg = host.getSvg();
|
|
7709
|
+
try {
|
|
7710
|
+
svg?.setPointerCapture(e.pointerId);
|
|
7711
|
+
} catch {
|
|
7712
|
+
}
|
|
7713
|
+
svg?.classList.add("oc-map--panning");
|
|
7714
|
+
host.hideTooltip();
|
|
7715
|
+
}
|
|
7716
|
+
e.preventDefault();
|
|
7717
|
+
const s = unitsPerPx();
|
|
7718
|
+
const dx = (e.clientX - drag.lastX) * s;
|
|
7719
|
+
const dy = (e.clientY - drag.lastY) * s;
|
|
7720
|
+
drag.lastX = e.clientX;
|
|
7721
|
+
drag.lastY = e.clientY;
|
|
7722
|
+
host.setCamera(clamp(panCamera(host.getCamera(), dx, dy)));
|
|
7723
|
+
}
|
|
7724
|
+
function endDrag(e) {
|
|
7725
|
+
if (!drag || e.pointerId !== drag.id) return;
|
|
7726
|
+
const svg = host.getSvg();
|
|
7727
|
+
if (drag.moved) {
|
|
7728
|
+
suppressClick = true;
|
|
7729
|
+
setTimeout(() => {
|
|
7730
|
+
suppressClick = false;
|
|
7731
|
+
}, 0);
|
|
7732
|
+
}
|
|
7733
|
+
try {
|
|
7734
|
+
if (svg?.hasPointerCapture?.(e.pointerId)) svg.releasePointerCapture(e.pointerId);
|
|
7735
|
+
} catch {
|
|
7736
|
+
}
|
|
7737
|
+
svg?.classList.remove("oc-map--panning");
|
|
7738
|
+
drag = null;
|
|
7739
|
+
}
|
|
7740
|
+
function onClickCapture(e) {
|
|
7741
|
+
if (!suppressClick) return;
|
|
7742
|
+
suppressClick = false;
|
|
7743
|
+
e.stopPropagation();
|
|
7744
|
+
e.preventDefault();
|
|
7745
|
+
}
|
|
7746
|
+
function onDblClick(e) {
|
|
7747
|
+
if (!host.getConfig() || fromControls(e)) return;
|
|
7748
|
+
const p = toMapFrame(e.clientX, e.clientY);
|
|
7749
|
+
if (!p?.inside) return;
|
|
7750
|
+
e.preventDefault();
|
|
7751
|
+
zoomAround(e.shiftKey ? 1 / ZOOM_STEP : ZOOM_STEP, p.x, p.y, true);
|
|
7752
|
+
}
|
|
7753
|
+
function touchPoint(t) {
|
|
7754
|
+
return toMapFrame(t.clientX, t.clientY);
|
|
7755
|
+
}
|
|
7756
|
+
function startPinch(e) {
|
|
7757
|
+
const a = touchPoint(e.touches[0]);
|
|
7758
|
+
const b = touchPoint(e.touches[1]);
|
|
7759
|
+
if (!a || !b) return;
|
|
7760
|
+
pinch = {
|
|
7761
|
+
dist: Math.hypot(b.x - a.x, b.y - a.y) || 1,
|
|
7762
|
+
x: (a.x + b.x) / 2,
|
|
7763
|
+
y: (a.y + b.y) / 2
|
|
7764
|
+
};
|
|
7765
|
+
touchPan = null;
|
|
7766
|
+
}
|
|
7767
|
+
function startTouchPan(t) {
|
|
7768
|
+
touchPan = isZoomed() ? { x: t.clientX, y: t.clientY, moved: false } : null;
|
|
7769
|
+
}
|
|
7770
|
+
function onTouchStart(e) {
|
|
7771
|
+
if (!host.getConfig() || fromControls(e)) return;
|
|
7772
|
+
if (e.touches.length >= 2) {
|
|
7773
|
+
const a = touchPoint(e.touches[0]);
|
|
7774
|
+
if (!a?.inside) return;
|
|
7775
|
+
e.preventDefault();
|
|
7776
|
+
host.hideTooltip();
|
|
7777
|
+
startPinch(e);
|
|
7778
|
+
} else if (e.touches.length === 1) {
|
|
7779
|
+
const p = touchPoint(e.touches[0]);
|
|
7780
|
+
if (!p?.inside) return;
|
|
7781
|
+
startTouchPan(e.touches[0]);
|
|
7782
|
+
}
|
|
7783
|
+
}
|
|
7784
|
+
function onTouchMove(e) {
|
|
7785
|
+
if (!host.getConfig()) return;
|
|
7786
|
+
if (pinch && e.touches.length >= 2) {
|
|
7787
|
+
e.preventDefault();
|
|
7788
|
+
const a = touchPoint(e.touches[0]);
|
|
7789
|
+
const b = touchPoint(e.touches[1]);
|
|
7790
|
+
if (!a || !b) return;
|
|
7791
|
+
const dist = Math.hypot(b.x - a.x, b.y - a.y) || 1;
|
|
7792
|
+
const x = (a.x + b.x) / 2;
|
|
7793
|
+
const y = (a.y + b.y) / 2;
|
|
7794
|
+
let cam = host.getCamera();
|
|
7795
|
+
cam = zoomCameraAt(cam, vb(), clampK(cam.k * (dist / pinch.dist)), x, y);
|
|
7796
|
+
cam = panCamera(cam, x - pinch.x, y - pinch.y);
|
|
7797
|
+
host.setCamera(clamp(cam));
|
|
7798
|
+
pinch = { dist, x, y };
|
|
7799
|
+
return;
|
|
7800
|
+
}
|
|
7801
|
+
if (touchPan && e.touches.length === 1) {
|
|
7802
|
+
const t = e.touches[0];
|
|
7803
|
+
if (!touchPan.moved) {
|
|
7804
|
+
if (Math.hypot(t.clientX - touchPan.x, t.clientY - touchPan.y) < DRAG_THRESHOLD) return;
|
|
7805
|
+
touchPan.moved = true;
|
|
7806
|
+
host.hideTooltip();
|
|
7807
|
+
}
|
|
7808
|
+
if (e.cancelable) e.preventDefault();
|
|
7809
|
+
const s = unitsPerPx();
|
|
7810
|
+
const dx = (t.clientX - touchPan.x) * s;
|
|
7811
|
+
const dy = (t.clientY - touchPan.y) * s;
|
|
7812
|
+
touchPan.x = t.clientX;
|
|
7813
|
+
touchPan.y = t.clientY;
|
|
7814
|
+
host.setCamera(clamp(panCamera(host.getCamera(), dx, dy)));
|
|
7815
|
+
}
|
|
7816
|
+
}
|
|
7817
|
+
function onTouchEnd(e) {
|
|
7818
|
+
if (e.touches.length >= 2) {
|
|
7819
|
+
startPinch(e);
|
|
7820
|
+
return;
|
|
7821
|
+
}
|
|
7822
|
+
pinch = null;
|
|
7823
|
+
if (e.touches.length === 1) {
|
|
7824
|
+
startTouchPan(e.touches[0]);
|
|
7825
|
+
if (touchPan) touchPan.moved = true;
|
|
7826
|
+
} else {
|
|
7827
|
+
touchPan = null;
|
|
7828
|
+
}
|
|
7829
|
+
}
|
|
7830
|
+
function onKeyDown(e) {
|
|
7831
|
+
if (!host.getConfig() || e.target !== host.getSvg()) return;
|
|
7832
|
+
if (e.ctrlKey || e.metaKey || e.altKey) return;
|
|
7833
|
+
const { width, height } = vb();
|
|
7834
|
+
switch (e.key) {
|
|
7835
|
+
case "+":
|
|
7836
|
+
case "=":
|
|
7837
|
+
zoomAtCenter(ZOOM_STEP);
|
|
7838
|
+
break;
|
|
7839
|
+
case "-":
|
|
7840
|
+
case "_":
|
|
7841
|
+
zoomAtCenter(1 / ZOOM_STEP);
|
|
7842
|
+
break;
|
|
7843
|
+
case "0":
|
|
7844
|
+
host.reset();
|
|
7845
|
+
break;
|
|
7846
|
+
case "ArrowLeft":
|
|
7847
|
+
case "ArrowRight":
|
|
7848
|
+
case "ArrowUp":
|
|
7849
|
+
case "ArrowDown": {
|
|
7850
|
+
if (!isZoomed()) return;
|
|
7851
|
+
const dx = e.key === "ArrowLeft" ? width * KEY_PAN_FRACTION : e.key === "ArrowRight" ? -width * KEY_PAN_FRACTION : 0;
|
|
7852
|
+
const dy = e.key === "ArrowUp" ? height * KEY_PAN_FRACTION : e.key === "ArrowDown" ? -height * KEY_PAN_FRACTION : 0;
|
|
7853
|
+
host.setCamera(clamp(panCamera(host.getCamera(), dx, dy)));
|
|
7854
|
+
break;
|
|
7855
|
+
}
|
|
7856
|
+
default:
|
|
7857
|
+
return;
|
|
7858
|
+
}
|
|
7859
|
+
e.preventDefault();
|
|
7860
|
+
}
|
|
7861
|
+
function makeButton(kind, label) {
|
|
7862
|
+
const btn = document.createElement("button");
|
|
7863
|
+
btn.type = "button";
|
|
7864
|
+
btn.className = "oc-map-zoom-btn";
|
|
7865
|
+
btn.setAttribute("data-zoom", kind);
|
|
7866
|
+
btn.setAttribute("aria-label", label);
|
|
7867
|
+
btn.title = label;
|
|
7868
|
+
btn.innerHTML = `<svg viewBox="0 0 16 16" width="16" height="16" aria-hidden="true" focusable="false" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">${ICONS[kind]}</svg>`;
|
|
7869
|
+
btn.addEventListener("click", (e) => {
|
|
7870
|
+
e.stopPropagation();
|
|
7871
|
+
if (kind === "in") zoomAtCenter(ZOOM_STEP);
|
|
7872
|
+
else if (kind === "out") zoomAtCenter(1 / ZOOM_STEP);
|
|
7873
|
+
else host.reset();
|
|
7874
|
+
});
|
|
7875
|
+
return btn;
|
|
7876
|
+
}
|
|
7877
|
+
function ensureOverlay(showControls) {
|
|
7878
|
+
if (!overlay) {
|
|
7879
|
+
overlay = document.createElement("div");
|
|
7880
|
+
overlay.className = "oc-map-zoom";
|
|
7881
|
+
hint = document.createElement("div");
|
|
7882
|
+
hint.className = "oc-map-zoom-hint";
|
|
7883
|
+
hint.setAttribute("aria-hidden", "true");
|
|
7884
|
+
hint.textContent = isMacLike() ? "Use \u2318 + scroll to zoom the map" : "Use Ctrl + scroll to zoom the map";
|
|
7885
|
+
overlay.appendChild(hint);
|
|
7886
|
+
if (getComputedStyle(container).position === "static") {
|
|
7887
|
+
container.style.position = "relative";
|
|
7888
|
+
}
|
|
7889
|
+
container.appendChild(overlay);
|
|
7890
|
+
}
|
|
7891
|
+
if (showControls && !buttons) {
|
|
7892
|
+
const group = document.createElement("div");
|
|
7893
|
+
group.className = "oc-map-zoom-controls";
|
|
7894
|
+
group.setAttribute("role", "group");
|
|
7895
|
+
group.setAttribute("aria-label", "Map zoom");
|
|
7896
|
+
buttons = {
|
|
7897
|
+
in: makeButton("in", "Zoom in"),
|
|
7898
|
+
out: makeButton("out", "Zoom out"),
|
|
7899
|
+
reset: makeButton("reset", "Reset zoom")
|
|
7900
|
+
};
|
|
7901
|
+
group.append(buttons.in, buttons.out, buttons.reset);
|
|
7902
|
+
overlay.appendChild(group);
|
|
7903
|
+
} else if (!showControls && buttons) {
|
|
7904
|
+
buttons.in.parentElement?.remove();
|
|
7905
|
+
buttons = null;
|
|
7906
|
+
}
|
|
7907
|
+
}
|
|
7908
|
+
function removeOverlay() {
|
|
7909
|
+
if (hintTimer) clearTimeout(hintTimer);
|
|
7910
|
+
hintTimer = null;
|
|
7911
|
+
overlay?.remove();
|
|
7912
|
+
overlay = null;
|
|
7913
|
+
buttons = null;
|
|
7914
|
+
hint = null;
|
|
7915
|
+
}
|
|
7916
|
+
function placeOverlay(layout) {
|
|
7917
|
+
if (!overlay || !layout.width || !layout.height) return;
|
|
7918
|
+
const { area, width, height } = layout;
|
|
7919
|
+
overlay.style.left = `${area.x / width * 100}%`;
|
|
7920
|
+
overlay.style.top = `${area.y / height * 100}%`;
|
|
7921
|
+
overlay.style.width = `${area.width / width * 100}%`;
|
|
7922
|
+
overlay.style.height = `${area.height / height * 100}%`;
|
|
7923
|
+
}
|
|
7924
|
+
function ensureClip(svg, layout) {
|
|
7925
|
+
if (svg.querySelector(`#${clipId}`)) return;
|
|
7926
|
+
let defs = svg.querySelector("defs");
|
|
7927
|
+
if (!defs) {
|
|
7928
|
+
defs = document.createElementNS(SVG_NS4, "defs");
|
|
7929
|
+
svg.insertBefore(defs, svg.firstChild);
|
|
7930
|
+
}
|
|
7931
|
+
const clip = document.createElementNS(SVG_NS4, "clipPath");
|
|
7932
|
+
clip.setAttribute("id", clipId);
|
|
7933
|
+
const rect = document.createElementNS(SVG_NS4, "rect");
|
|
7934
|
+
rect.setAttribute("width", String(layout.area.width));
|
|
7935
|
+
rect.setAttribute("height", String(layout.area.height));
|
|
7936
|
+
clip.appendChild(rect);
|
|
7937
|
+
defs.appendChild(clip);
|
|
7938
|
+
}
|
|
7939
|
+
function onRender(restoreFocus) {
|
|
7940
|
+
const cfg = host.getConfig();
|
|
7941
|
+
const svg = host.getSvg();
|
|
7942
|
+
if (!cfg || !svg) {
|
|
7943
|
+
removeOverlay();
|
|
7944
|
+
return;
|
|
7945
|
+
}
|
|
7946
|
+
const layout = host.getLayout();
|
|
7947
|
+
svg.setAttribute("tabindex", "0");
|
|
7948
|
+
svg.setAttribute("aria-keyshortcuts", "+ - 0 ArrowUp ArrowDown ArrowLeft ArrowRight");
|
|
7949
|
+
ensureClip(svg, layout);
|
|
7950
|
+
ensureOverlay(cfg.controls);
|
|
7951
|
+
placeOverlay(layout);
|
|
7952
|
+
if (restoreFocus) svg.focus({ preventScroll: true });
|
|
7953
|
+
onCamera();
|
|
7954
|
+
}
|
|
7955
|
+
function onCamera() {
|
|
7956
|
+
const cfg = host.getConfig();
|
|
7957
|
+
const svg = host.getSvg();
|
|
7958
|
+
if (!cfg || !svg) return;
|
|
7959
|
+
const cam = host.getCamera();
|
|
7960
|
+
const zoomed = cam.k > 1 + EPS;
|
|
7961
|
+
const { width, height } = vb();
|
|
7962
|
+
const atFullView = !zoomed && Math.abs(cam.cx - width / 2) < EPS && Math.abs(cam.cy - height / 2) < EPS;
|
|
7963
|
+
const group = svg.querySelector(".oc-map-group");
|
|
7964
|
+
if (zoomed) group?.setAttribute("clip-path", `url(#${clipId})`);
|
|
7965
|
+
else group?.removeAttribute("clip-path");
|
|
7966
|
+
svg.style.touchAction = zoomed ? "none" : "pan-x pan-y";
|
|
7967
|
+
if (zoomed) svg.setAttribute("data-oc-zoomed", "");
|
|
7968
|
+
else svg.removeAttribute("data-oc-zoomed");
|
|
7969
|
+
if (buttons) {
|
|
7970
|
+
setDisabled(buttons.in, cam.k >= cfg.maxZoom - EPS, svg);
|
|
7971
|
+
setDisabled(buttons.out, !zoomed, svg);
|
|
7972
|
+
setDisabled(buttons.reset, atFullView, svg);
|
|
7973
|
+
}
|
|
7974
|
+
}
|
|
7975
|
+
function setDisabled(btn, disabled, svg) {
|
|
7976
|
+
if (btn.disabled === disabled) return;
|
|
7977
|
+
const hadFocus = disabled && document.activeElement === btn;
|
|
7978
|
+
btn.disabled = disabled;
|
|
7979
|
+
if (hadFocus) svg.focus({ preventScroll: true });
|
|
7980
|
+
}
|
|
7981
|
+
const touchOpts = { passive: false };
|
|
7982
|
+
container.addEventListener("wheel", onWheel, { passive: false });
|
|
7983
|
+
container.addEventListener("pointerdown", onPointerDown);
|
|
7984
|
+
container.addEventListener("mousedown", onMouseDown);
|
|
7985
|
+
container.addEventListener("pointermove", onPointerMove);
|
|
7986
|
+
container.addEventListener("pointerup", endDrag);
|
|
7987
|
+
container.addEventListener("pointercancel", endDrag);
|
|
7988
|
+
container.addEventListener("click", onClickCapture, true);
|
|
7989
|
+
container.addEventListener("dblclick", onDblClick);
|
|
7990
|
+
container.addEventListener("touchstart", onTouchStart, touchOpts);
|
|
7991
|
+
container.addEventListener("touchmove", onTouchMove, touchOpts);
|
|
7992
|
+
container.addEventListener("touchend", onTouchEnd);
|
|
7993
|
+
container.addEventListener("touchcancel", onTouchEnd);
|
|
7994
|
+
container.addEventListener("gesturestart", onGestureStart);
|
|
7995
|
+
container.addEventListener("gesturechange", onGestureChange);
|
|
7996
|
+
container.addEventListener("gestureend", onGestureEnd);
|
|
7997
|
+
container.addEventListener("keydown", onKeyDown);
|
|
7998
|
+
return {
|
|
7999
|
+
onRender,
|
|
8000
|
+
onCamera,
|
|
8001
|
+
destroy() {
|
|
8002
|
+
container.removeEventListener("wheel", onWheel);
|
|
8003
|
+
container.removeEventListener("pointerdown", onPointerDown);
|
|
8004
|
+
container.removeEventListener("mousedown", onMouseDown);
|
|
8005
|
+
container.removeEventListener("pointermove", onPointerMove);
|
|
8006
|
+
container.removeEventListener("pointerup", endDrag);
|
|
8007
|
+
container.removeEventListener("pointercancel", endDrag);
|
|
8008
|
+
container.removeEventListener("click", onClickCapture, true);
|
|
8009
|
+
container.removeEventListener("dblclick", onDblClick);
|
|
8010
|
+
container.removeEventListener("touchstart", onTouchStart);
|
|
8011
|
+
container.removeEventListener("touchmove", onTouchMove);
|
|
8012
|
+
container.removeEventListener("touchend", onTouchEnd);
|
|
8013
|
+
container.removeEventListener("touchcancel", onTouchEnd);
|
|
8014
|
+
container.removeEventListener("gesturestart", onGestureStart);
|
|
8015
|
+
container.removeEventListener("gesturechange", onGestureChange);
|
|
8016
|
+
container.removeEventListener("gestureend", onGestureEnd);
|
|
8017
|
+
container.removeEventListener("keydown", onKeyDown);
|
|
8018
|
+
const svg = host.getSvg();
|
|
8019
|
+
svg?.removeAttribute("tabindex");
|
|
8020
|
+
removeOverlay();
|
|
8021
|
+
}
|
|
8022
|
+
};
|
|
8023
|
+
}
|
|
8024
|
+
|
|
7527
8025
|
// src/map-mount.ts
|
|
7528
8026
|
function focusSignature(focus) {
|
|
7529
8027
|
if (!focus) return null;
|
|
@@ -7552,6 +8050,7 @@ function restoreFeatureOrder(el) {
|
|
|
7552
8050
|
parent.appendChild(el);
|
|
7553
8051
|
}
|
|
7554
8052
|
}
|
|
8053
|
+
var USER_ZOOM_MS = 250;
|
|
7555
8054
|
function prefersReducedMotion2() {
|
|
7556
8055
|
if (typeof window === "undefined" || typeof window.matchMedia !== "function") return false;
|
|
7557
8056
|
try {
|
|
@@ -7574,6 +8073,7 @@ function createGeoMap(container, spec, options) {
|
|
|
7574
8073
|
let currentCamera = { cx: 0, cy: 0, k: 1 };
|
|
7575
8074
|
let currentFocusIds = null;
|
|
7576
8075
|
let cameraTween = null;
|
|
8076
|
+
let zoomController = null;
|
|
7577
8077
|
let isFirstRender = true;
|
|
7578
8078
|
let fontsReloadPending = false;
|
|
7579
8079
|
container.classList.add("oc-map-root");
|
|
@@ -7774,6 +8274,18 @@ function createGeoMap(container, spec, options) {
|
|
|
7774
8274
|
}
|
|
7775
8275
|
return cameraForTarget(currentLayout, null);
|
|
7776
8276
|
}
|
|
8277
|
+
function commitCamera(cam) {
|
|
8278
|
+
currentCamera = cam;
|
|
8279
|
+
if (svgElement) applyMapCamera(svgElement, cam, currentLayout);
|
|
8280
|
+
zoomController?.onCamera();
|
|
8281
|
+
options?.onCameraChange?.({ ...cam });
|
|
8282
|
+
}
|
|
8283
|
+
function cancelCameraTween() {
|
|
8284
|
+
if (cameraTween) {
|
|
8285
|
+
cameraTween.cancel();
|
|
8286
|
+
cameraTween = null;
|
|
8287
|
+
}
|
|
8288
|
+
}
|
|
7777
8289
|
function animateCamera(target, duration) {
|
|
7778
8290
|
if (cameraTween) cameraTween.cancel();
|
|
7779
8291
|
cameraTween = createTween2({
|
|
@@ -7782,20 +8294,64 @@ function createGeoMap(container, spec, options) {
|
|
|
7782
8294
|
duration,
|
|
7783
8295
|
ease: easingFns.easeInOutCubic,
|
|
7784
8296
|
onFrame(cam) {
|
|
7785
|
-
|
|
7786
|
-
if (svgElement) applyMapCamera(svgElement, cam, currentLayout);
|
|
8297
|
+
commitCamera(cam);
|
|
7787
8298
|
}
|
|
7788
8299
|
});
|
|
7789
8300
|
cameraTween.to(target);
|
|
7790
8301
|
}
|
|
7791
8302
|
function driveCamera(cam, duration) {
|
|
7792
8303
|
if (duration === 0 || prefersReducedMotion2()) {
|
|
7793
|
-
|
|
7794
|
-
|
|
8304
|
+
cancelCameraTween();
|
|
8305
|
+
commitCamera(cam);
|
|
7795
8306
|
} else {
|
|
7796
8307
|
animateCamera(cam, duration);
|
|
7797
8308
|
}
|
|
7798
8309
|
}
|
|
8310
|
+
function resetCamera(duration) {
|
|
8311
|
+
currentFocusIds = null;
|
|
8312
|
+
const cam = cameraForTarget(currentLayout, null);
|
|
8313
|
+
driveCamera(cam, duration);
|
|
8314
|
+
applyFocusDim(null);
|
|
8315
|
+
updateA11yLive(null);
|
|
8316
|
+
}
|
|
8317
|
+
function rescaleFreeCamera(prev) {
|
|
8318
|
+
const next = currentLayout.mapSize;
|
|
8319
|
+
if (!prev.width || !prev.height || prev.width === next.width && prev.height === next.height) {
|
|
8320
|
+
return;
|
|
8321
|
+
}
|
|
8322
|
+
const scaled = {
|
|
8323
|
+
cx: currentCamera.cx * next.width / prev.width,
|
|
8324
|
+
cy: currentCamera.cy * next.height / prev.height,
|
|
8325
|
+
k: currentCamera.k
|
|
8326
|
+
};
|
|
8327
|
+
const zoom = resolveMapZoom(currentSpec.geo?.zoom);
|
|
8328
|
+
currentCamera = zoom ? clampMapCamera(scaled, next, zoom.maxZoom) : scaled;
|
|
8329
|
+
}
|
|
8330
|
+
function syncZoomController(restoreFocus) {
|
|
8331
|
+
const zoom = resolveMapZoom(currentSpec.geo?.zoom);
|
|
8332
|
+
if (!zoom && !zoomController) return;
|
|
8333
|
+
if (!zoomController) {
|
|
8334
|
+
zoomController = createMapZoomController({
|
|
8335
|
+
container,
|
|
8336
|
+
getSvg: () => svgElement,
|
|
8337
|
+
getLayout: () => currentLayout,
|
|
8338
|
+
getConfig: () => resolveMapZoom(currentSpec.geo?.zoom),
|
|
8339
|
+
getCamera: () => ({ ...currentCamera }),
|
|
8340
|
+
setCamera(cam) {
|
|
8341
|
+
cancelCameraTween();
|
|
8342
|
+
currentFocusIds = null;
|
|
8343
|
+
commitCamera(cam);
|
|
8344
|
+
},
|
|
8345
|
+
animateCamera(cam) {
|
|
8346
|
+
currentFocusIds = null;
|
|
8347
|
+
driveCamera(cam, USER_ZOOM_MS);
|
|
8348
|
+
},
|
|
8349
|
+
reset: () => resetCamera(USER_ZOOM_MS),
|
|
8350
|
+
hideTooltip: () => tooltipManager?.hide()
|
|
8351
|
+
});
|
|
8352
|
+
}
|
|
8353
|
+
zoomController.onRender(restoreFocus);
|
|
8354
|
+
}
|
|
7799
8355
|
function updateA11yLive(ids) {
|
|
7800
8356
|
let liveEl = container.querySelector(".oc-map-live");
|
|
7801
8357
|
if (!liveEl) {
|
|
@@ -7842,6 +8398,7 @@ function createGeoMap(container, spec, options) {
|
|
|
7842
8398
|
fillTransitionHandle = null;
|
|
7843
8399
|
}
|
|
7844
8400
|
const oldSvg = svgElement;
|
|
8401
|
+
const oldSvgHadFocus = !!oldSvg && document.activeElement === oldSvg;
|
|
7845
8402
|
const newSvg = renderMapSVG(currentLayout, { animate });
|
|
7846
8403
|
if (oldSvg) {
|
|
7847
8404
|
if (cleanupTooltipEvents) {
|
|
@@ -7893,6 +8450,7 @@ function createGeoMap(container, spec, options) {
|
|
|
7893
8450
|
}
|
|
7894
8451
|
}
|
|
7895
8452
|
isFirstRender = false;
|
|
8453
|
+
syncZoomController(oldSvgHadFocus);
|
|
7896
8454
|
}
|
|
7897
8455
|
function update(newSpec) {
|
|
7898
8456
|
currentSpec = newSpec;
|
|
@@ -7905,7 +8463,9 @@ function createGeoMap(container, spec, options) {
|
|
|
7905
8463
|
const prevPaint = canAnimate ? captureMapSnapshot(svgElement) : null;
|
|
7906
8464
|
const prevFocusIds = currentFocusIds ? currentFocusIds.map(String).sort().join(",") : null;
|
|
7907
8465
|
const prevFocusSig = focusSignature(currentLayout.focus);
|
|
8466
|
+
const prevMapSize = currentLayout.mapSize;
|
|
7908
8467
|
currentLayout = compile();
|
|
8468
|
+
if (!currentFocusIds) rescaleFreeCamera(prevMapSize);
|
|
7909
8469
|
render();
|
|
7910
8470
|
if (canAnimate && prevPaint && (prevPaint.fills.size > 0 || prevPaint.points.size > 0) && svgElement && currentLayout.animation?.update) {
|
|
7911
8471
|
fillTransitionHandle = runMapFillTransition(svgElement, prevPaint, {
|
|
@@ -7934,11 +8494,10 @@ function createGeoMap(container, spec, options) {
|
|
|
7934
8494
|
pendingResize = true;
|
|
7935
8495
|
return;
|
|
7936
8496
|
}
|
|
7937
|
-
|
|
7938
|
-
|
|
7939
|
-
cameraTween = null;
|
|
7940
|
-
}
|
|
8497
|
+
cancelCameraTween();
|
|
8498
|
+
const prevMapSize = currentLayout.mapSize;
|
|
7941
8499
|
currentLayout = compile();
|
|
8500
|
+
if (!currentFocusIds) rescaleFreeCamera(prevMapSize);
|
|
7942
8501
|
render();
|
|
7943
8502
|
if (currentFocusIds && currentFocusIds.length > 0) {
|
|
7944
8503
|
currentCamera = resolveCamera();
|
|
@@ -7949,6 +8508,7 @@ function createGeoMap(container, spec, options) {
|
|
|
7949
8508
|
if (svgElement) applyMapCamera(svgElement, currentCamera, currentLayout);
|
|
7950
8509
|
}
|
|
7951
8510
|
applyFocusDim(currentFocusIds);
|
|
8511
|
+
zoomController?.onCamera();
|
|
7952
8512
|
}
|
|
7953
8513
|
function exportChart(format, options_) {
|
|
7954
8514
|
if (!svgElement) return "";
|
|
@@ -7977,9 +8537,10 @@ function createGeoMap(container, spec, options) {
|
|
|
7977
8537
|
fillTransitionHandle.cancel();
|
|
7978
8538
|
fillTransitionHandle = null;
|
|
7979
8539
|
}
|
|
7980
|
-
|
|
7981
|
-
|
|
7982
|
-
|
|
8540
|
+
cancelCameraTween();
|
|
8541
|
+
if (zoomController) {
|
|
8542
|
+
zoomController.destroy();
|
|
8543
|
+
zoomController = null;
|
|
7983
8544
|
}
|
|
7984
8545
|
if (cleanupTooltipEvents) {
|
|
7985
8546
|
cleanupTooltipEvents();
|
|
@@ -8062,19 +8623,15 @@ function createGeoMap(container, spec, options) {
|
|
|
8062
8623
|
applyFocusDim(null);
|
|
8063
8624
|
},
|
|
8064
8625
|
resetView(opts) {
|
|
8065
|
-
|
|
8066
|
-
const cam = cameraForTarget(currentLayout, null);
|
|
8067
|
-
driveCamera(cam, opts?.duration ?? storyMotion.camera);
|
|
8068
|
-
applyFocusDim(null);
|
|
8069
|
-
updateA11yLive(null);
|
|
8626
|
+
resetCamera(opts?.duration ?? storyMotion.camera);
|
|
8070
8627
|
},
|
|
8071
8628
|
getCamera() {
|
|
8072
8629
|
return { ...currentCamera };
|
|
8073
8630
|
},
|
|
8074
8631
|
setCamera(camera) {
|
|
8075
|
-
|
|
8632
|
+
cancelCameraTween();
|
|
8076
8633
|
currentFocusIds = null;
|
|
8077
|
-
|
|
8634
|
+
commitCamera(camera);
|
|
8078
8635
|
}
|
|
8079
8636
|
};
|
|
8080
8637
|
}
|
|
@@ -8108,4 +8665,4 @@ export {
|
|
|
8108
8665
|
scrubCamera,
|
|
8109
8666
|
createGeoMap
|
|
8110
8667
|
};
|
|
8111
|
-
//# sourceMappingURL=chunk-
|
|
8668
|
+
//# sourceMappingURL=chunk-QBTRAQAX.js.map
|