squarified 0.6.2 → 1.1.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/plugin.mjs CHANGED
@@ -1,49 +1,5 @@
1
- import { h as definePlugin, B as smoothFrame, r as createRoundBlock, z as stackMatrixTransform, S as Schedule, H as DEFAULT_MATRIX_LOC, F as isScrollWheelOrRightButtonOnMouseupAndDown, A as stackMatrixTransformWithGraphAndLayer, j as isContextMenuEvent, o as hashCode, P as PI_2, n as isWheelEvent, m as mixin } from './dom-event-DrYYfglv.mjs';
1
+ import { h as definePlugin, u as asserts, I as createRoundBlock, W as DEFAULT_MATRIX_LOC, U as isScrollWheelOrRightButtonOnMouseupAndDown, Q as smoothFrame, p as isBox, t as traverse, r as isText, q as isRoundRect, O as stackMatrixTransform, j as isContextMenuEvent, F as hashCode, X as PI_2, n as isWheelEvent, P as stackMatrixTransformWithGraphAndLayer, m as mixin, w as easing } from './dom-event-CBrOg7CX.mjs';
2
2
 
3
- // Currently, etoile is an internal module, so we won't need too much easing functions.
4
- // And the animation logic is implemented by user code.
5
- const easing = {
6
- linear: (k)=>k,
7
- quadraticIn: (k)=>k * k,
8
- quadraticOut: (k)=>k * (2 - k),
9
- quadraticInOut: (k)=>{
10
- if ((k *= 2) < 1) {
11
- return 0.5 * k * k;
12
- }
13
- return -0.5 * (--k * (k - 2) - 1);
14
- },
15
- cubicIn: (k)=>k * k * k,
16
- cubicOut: (k)=>{
17
- if ((k *= 2) < 1) {
18
- return 0.5 * k * k * k;
19
- }
20
- return 0.5 * ((k -= 2) * k * k + 2);
21
- },
22
- cubicInOut: (k)=>{
23
- if ((k *= 2) < 1) {
24
- return 0.5 * k * k * k;
25
- }
26
- return 0.5 * ((k -= 2) * k * k + 2);
27
- }
28
- };
29
-
30
- class Highlight extends Schedule {
31
- reset() {
32
- this.destory();
33
- this.update();
34
- }
35
- get canvas() {
36
- return this.render.canvas;
37
- }
38
- setZIndexForHighlight(zIndex = '-1') {
39
- this.canvas.style.zIndex = zIndex;
40
- }
41
- init() {
42
- this.setZIndexForHighlight();
43
- this.canvas.style.position = 'absolute';
44
- this.canvas.style.pointerEvents = 'none';
45
- }
46
- }
47
3
  const ANIMATION_DURATION = 300;
48
4
  const HIGH_LIGHT_OPACITY = 0.3;
49
5
  const fill = {
@@ -56,43 +12,80 @@ const fill = {
56
12
  };
57
13
  const presetHighlightPlugin = definePlugin({
58
14
  name: 'treemap:preset-highlight',
59
- onLoad () {
60
- const meta = this.getPluginMetadata('treemap:preset-highlight');
61
- if (!meta) {
15
+ onDOMEventTriggered (name, _, graphic, { stateManager: state, matrix, component }) {
16
+ // Any interaction that isn't a pure hover must reset the overlay so we never
17
+ // show a stale highlight after zoom / drag / pan transitions.
18
+ if (name !== 'mousemove') {
19
+ const meta = this.getPluginMetadata('treemap:preset-highlight');
20
+ if (meta && meta.lastDirtyRect) {
21
+ component.clearOverlay();
22
+ meta.overlayGraphic = null;
23
+ meta.lastDirtyRect = null;
24
+ }
62
25
  return;
63
26
  }
64
- if (!meta.highlight) {
65
- meta.highlight = new Highlight(this.instance.to);
66
- }
67
- },
68
- onDOMEventTriggered (name, _, module, { stateManager: state, matrix }) {
69
27
  if (name === 'mousemove') {
70
28
  if (state.canTransition('MOVE')) {
71
29
  const meta = this.getPluginMetadata('treemap:preset-highlight');
72
- if (!module) {
73
- meta.highlight?.reset();
74
- meta.highlight?.update();
75
- meta.highlight?.setZIndexForHighlight();
30
+ if (!meta) {
76
31
  return;
77
32
  }
33
+ const oldDirtyRect = meta.lastDirtyRect;
34
+ if (!graphic) {
35
+ if (oldDirtyRect) {
36
+ component.clearOverlay();
37
+ component.updateDirty([
38
+ oldDirtyRect
39
+ ]);
40
+ meta.overlayGraphic = null;
41
+ meta.lastDirtyRect = null;
42
+ }
43
+ return;
44
+ }
45
+ const module = graphic.__widget__;
78
46
  const [x, y, w, h] = module.layout;
79
- const effectiveRadius = Math.min(module.config.rectRadius, w / 4, h / 4);
80
- smoothFrame((_, cleanup)=>{
81
- cleanup();
82
- meta.highlight?.reset();
83
- const mask = createRoundBlock(x, y, w, h, {
84
- fill,
85
- opacity: HIGH_LIGHT_OPACITY,
86
- radius: effectiveRadius,
87
- padding: 0
88
- });
89
- meta.highlight?.add(mask);
90
- meta.highlight?.setZIndexForHighlight('1');
91
- stackMatrixTransform(mask, matrix.e, matrix.f, 1);
92
- meta.highlight?.update();
93
- }, {
94
- duration: ANIMATION_DURATION
47
+ const rect = graphic.elements[0];
48
+ if (!rect || !asserts.isRoundRect(rect)) {
49
+ return;
50
+ }
51
+ const effectiveRadius = rect.style.radius;
52
+ // Layout coordinates are already in visual (zoomed) space; matrix.e/f
53
+ // is the pan translation that gets added to every element position.
54
+ const visualX = x + matrix.e;
55
+ const visualY = y + matrix.f;
56
+ // Expand dirty rect by 1 CSS px on each side to cover anti-aliased edges.
57
+ const pad = 1;
58
+ const newDirtyRect = {
59
+ x: visualX - pad,
60
+ y: visualY - pad,
61
+ width: w + pad * 2,
62
+ height: h + pad * 2
63
+ };
64
+ const mask = createRoundBlock(visualX, visualY, w, h, {
65
+ fill,
66
+ opacity: HIGH_LIGHT_OPACITY,
67
+ radius: effectiveRadius,
68
+ padding: 0
95
69
  });
70
+ component.clearOverlay();
71
+ component.addOverlay(mask);
72
+ meta.overlayGraphic = mask;
73
+ meta.lastDirtyRect = newDirtyRect;
74
+ const dirtyRects = oldDirtyRect ? [
75
+ newDirtyRect,
76
+ oldDirtyRect
77
+ ] : [
78
+ newDirtyRect
79
+ ];
80
+ component.updateDirty(dirtyRects);
81
+ } else {
82
+ // State changed away from hoverable (e.g. dragging / zooming) — clear overlay.
83
+ const meta = this.getPluginMetadata('treemap:preset-highlight');
84
+ if (meta && meta.lastDirtyRect) {
85
+ component.clearOverlay();
86
+ meta.overlayGraphic = null;
87
+ meta.lastDirtyRect = null;
88
+ }
96
89
  }
97
90
  }
98
91
  },
@@ -101,21 +94,21 @@ const presetHighlightPlugin = definePlugin({
101
94
  if (!meta) {
102
95
  return;
103
96
  }
104
- meta.highlight?.render.initOptions({
105
- ...this.instance.render.options
106
- });
107
- meta.highlight?.reset();
108
- meta.highlight?.init();
97
+ this.instance.clearOverlay();
98
+ meta.overlayGraphic = null;
99
+ meta.lastDirtyRect = null;
109
100
  },
110
101
  onDispose () {
111
102
  const meta = this.getPluginMetadata('treemap:preset-highlight');
112
- if (meta && meta.highlight) {
113
- meta.highlight.destory();
114
- meta.highlight = null;
103
+ if (meta) {
104
+ this.instance.clearOverlay();
105
+ meta.overlayGraphic = null;
106
+ meta.lastDirtyRect = null;
115
107
  }
116
108
  },
117
109
  meta: {
118
- highlight: null
110
+ overlayGraphic: null,
111
+ lastDirtyRect: null
119
112
  }
120
113
  });
121
114
 
@@ -141,7 +134,6 @@ const presetDragElementPlugin = definePlugin({
141
134
  }
142
135
  state.transition('DRAGGING');
143
136
  if (state.isInState('DRAGGING')) {
144
- const highlight = getHighlightInstance.call(this);
145
137
  smoothFrame((_, cleanup)=>{
146
138
  cleanup();
147
139
  const { offsetX, offsetY } = event.native;
@@ -149,18 +141,30 @@ const presetDragElementPlugin = definePlugin({
149
141
  const drawY = offsetY - meta.dragOptions.y;
150
142
  const lastX = meta.dragOptions.x;
151
143
  const lastY = meta.dragOptions.y;
152
- if (highlight?.highlight) {
153
- highlight.highlight.reset();
154
- highlight.highlight.setZIndexForHighlight();
155
- }
144
+ component.clearOverlay();
156
145
  matrix.translation(drawX, drawY);
157
146
  meta.dragOptions.x = offsetX;
158
147
  meta.dragOptions.y = offsetY;
159
148
  meta.dragOptions.lastX = lastX;
160
149
  meta.dragOptions.lastY = lastY;
150
+ const cloned = component.elements.map((el)=>isBox(el) ? el.clone() : el);
161
151
  component.cleanup();
162
- component.draw(false, false);
163
- stackMatrixTransformWithGraphAndLayer(component.elements, matrix.e, matrix.f, 1);
152
+ component.add(...cloned);
153
+ traverse(component.elements, (graph)=>{
154
+ if (isText(graph)) {
155
+ const { textX, textY } = graph.__widget__;
156
+ graph.x = textX;
157
+ graph.y = textY;
158
+ }
159
+ if (isRoundRect(graph)) {
160
+ const { x, y, w, h } = graph.__widget__;
161
+ graph.x = x;
162
+ graph.y = y;
163
+ graph.width = w;
164
+ graph.height = h;
165
+ }
166
+ stackMatrixTransform(graph, matrix.e, matrix.f, 1);
167
+ });
164
168
  component.update();
165
169
  return true;
166
170
  }, {
@@ -184,11 +188,7 @@ const presetDragElementPlugin = definePlugin({
184
188
  }
185
189
  }
186
190
  if (state.isInState('DRAGGING') && state.canTransition('IDLE')) {
187
- const highlight = getHighlightInstance.call(this);
188
- if (highlight && highlight.highlight) {
189
- highlight.highlight.reset();
190
- highlight.highlight.setZIndexForHighlight();
191
- }
191
+ component.clearOverlay();
192
192
  const meta = getDragOptions.call(this);
193
193
  if (meta && meta.dragOptions) {
194
194
  meta.dragOptions.x = 0;
@@ -231,9 +231,6 @@ const presetDragElementPlugin = definePlugin({
231
231
  state.reset();
232
232
  }
233
233
  });
234
- function getHighlightInstance() {
235
- return this.getPluginMetadata('treemap:preset-highlight');
236
- }
237
234
  function getDragOptions() {
238
235
  const meta = this.getPluginMetadata('treemap:preset-drag-element');
239
236
  return meta;
@@ -257,9 +254,9 @@ function presetMenuPlugin(options) {
257
254
  return;
258
255
  }
259
256
  if (options?.onClick) {
260
- options.onClick(action, domEvent.findRelativeNode({
261
- native: e,
262
- kind: undefined
257
+ options.onClick(action, domEvent.findRelativeGraphicNode({
258
+ kind: 'click',
259
+ native: e
263
260
  }));
264
261
  }
265
262
  }
@@ -361,6 +358,15 @@ function adjustColorToComfortableForHumanEye(hue, saturation, lightness) {
361
358
  };
362
359
  }
363
360
 
361
+ // refer https://developer.mozilla.org/en-US/docs/Web/API/Element/mousewheel_event
362
+ // we shouldn't use wheelDelta property anymore.
363
+ function getScaleOptions() {
364
+ const meta = this.getPluginMetadata('treemap:preset-scale');
365
+ if (!meta) {
366
+ throw new Error('treemap:preset-scale metadata missing; ensure presetScalePlugin is registered');
367
+ }
368
+ return meta;
369
+ }
364
370
  function presetScalePlugin(options) {
365
371
  return definePlugin({
366
372
  name: 'treemap:preset-scale',
@@ -372,9 +378,19 @@ function presetScalePlugin(options) {
372
378
  meta: {
373
379
  scaleOptions: {
374
380
  scale: 1,
375
- minScale: options?.min || 0.1,
376
- maxScale: options?.max || Infinity,
377
- scaleFactor: 0.05
381
+ virtualScale: 1,
382
+ minScale: options?.min ?? 0.1,
383
+ maxScale: options?.max ?? Infinity,
384
+ scaleFactor: 0.05,
385
+ springStiffness: options?.springStiffness ?? 300,
386
+ springDamping: options?.springDamping ?? 35,
387
+ overshootResistance: options?.overshootResistance ?? 0.35,
388
+ overshootLimitFactor: options?.overshootLimitFactor ?? 0.05,
389
+ lastAnchorX: undefined,
390
+ lastAnchorY: undefined,
391
+ springRafId: null,
392
+ animationsEnabled: options?.animationsEnabled ?? true,
393
+ wheelDebounce: options?.wheelDebounce ?? 200
378
394
  },
379
395
  gestureState: {
380
396
  isTrackingGesture: false,
@@ -384,23 +400,19 @@ function presetScalePlugin(options) {
384
400
  totalDeltaX: 0,
385
401
  consecutivePinchEvents: 0,
386
402
  gestureType: 'unknown',
387
- lockGestureType: false
403
+ lockGestureType: false,
404
+ wheelEndTimeoutId: null
388
405
  }
389
406
  },
390
407
  onResize ({ matrix, stateManager: state }) {
391
408
  const meta = getScaleOptions.call(this);
392
- if (meta) {
393
- meta.scaleOptions.scale = 1;
394
- }
409
+ meta.scaleOptions.scale = 1;
410
+ meta.scaleOptions.virtualScale = 1;
395
411
  matrix.create(DEFAULT_MATRIX_LOC);
396
412
  state.reset();
397
413
  }
398
414
  });
399
415
  }
400
- function getScaleOptions() {
401
- const meta = this.getPluginMetadata('treemap:preset-scale');
402
- return meta;
403
- }
404
416
  function determineGestureType(event, gestureState) {
405
417
  const now = Date.now();
406
418
  const timeDiff = now - gestureState.lastEventTime;
@@ -427,7 +439,7 @@ function determineGestureType(event, gestureState) {
427
439
  return 'zoom';
428
440
  }
429
441
  // windows/macos mouse wheel
430
- // Usually the dettaY is large and deltaX maybe 0 or small number.
442
+ // Usually the deltaY is large and deltaX maybe 0 or small number.
431
443
  const isMouseWheel = Math.abs(event.deltaX) >= 100 && Math.abs(event.deltaX) <= 10 || Math.abs(event.deltaY) > 50 && Math.abs(event.deltaX) < Math.abs(event.deltaY) * 0.1;
432
444
  if (isMouseWheel) {
433
445
  gestureState.gestureType = 'zoom';
@@ -458,9 +470,6 @@ function determineGestureType(event, gestureState) {
458
470
  function onWheel(pluginContext, event, domEvent) {
459
471
  event.native.preventDefault();
460
472
  const meta = getScaleOptions.call(pluginContext);
461
- if (!meta) {
462
- return;
463
- }
464
473
  const gestureType = determineGestureType(event.native, meta.gestureState);
465
474
  if (gestureType === 'zoom') {
466
475
  handleZoom(pluginContext, event, domEvent);
@@ -469,12 +478,8 @@ function onWheel(pluginContext, event, domEvent) {
469
478
  }
470
479
  }
471
480
  function updateViewport(pluginContext, { stateManager: state, component, matrix }, useAnimation = false) {
472
- const highlight = getHighlightInstance.apply(pluginContext);
473
481
  const doUpdate = ()=>{
474
- if (highlight && highlight.highlight) {
475
- highlight.highlight.reset();
476
- highlight.highlight.setZIndexForHighlight();
477
- }
482
+ component.clearOverlay();
478
483
  component.cleanup();
479
484
  const { width, height } = component.render.options;
480
485
  component.layoutNodes = component.calculateLayoutNodes(component.data, {
@@ -502,43 +507,197 @@ function updateViewport(pluginContext, { stateManager: state, component, matrix
502
507
  doUpdate();
503
508
  }
504
509
  }
505
- function handleZoom(pluginContext, event, domEvent) {
506
- const { stateManager: state, matrix, component } = domEvent;
510
+ function cancelSpringAnimationIfAny(meta) {
511
+ const rafId = meta.scaleOptions.springRafId;
512
+ if (typeof rafId === 'number' && rafId !== null) {
513
+ cancelAnimationFrame(rafId);
514
+ meta.scaleOptions.springRafId = null;
515
+ }
516
+ }
517
+ function springAnimateToScale(pluginContext, domEvent, targetScale, anchorX, anchorY) {
507
518
  const meta = getScaleOptions.call(pluginContext);
508
- if (!meta) {
519
+ const { matrix, component } = domEvent;
520
+ // if animations disabled, snap immediately and return
521
+ if (!meta.scaleOptions.animationsEnabled) {
522
+ const oldMatrix = {
523
+ e: matrix.e,
524
+ f: matrix.f
525
+ };
526
+ const finalScaleDiff = targetScale / meta.scaleOptions.scale;
527
+ if (isFinite(finalScaleDiff) && finalScaleDiff > 0 && Math.abs(finalScaleDiff - 1) > 1e-12) {
528
+ matrix.scale(finalScaleDiff, finalScaleDiff);
529
+ matrix.e = anchorX - (anchorX - matrix.e) * finalScaleDiff;
530
+ matrix.f = anchorY - (anchorY - matrix.f) * finalScaleDiff;
531
+ }
532
+ meta.scaleOptions.scale = targetScale;
533
+ meta.scaleOptions.virtualScale = targetScale;
534
+ try {
535
+ component.handleTransformCacheInvalidation(oldMatrix, {
536
+ e: matrix.e,
537
+ f: matrix.f
538
+ });
539
+ } catch {}
540
+ updateViewport(pluginContext, domEvent, false);
509
541
  return;
510
542
  }
511
- const { scale, minScale, maxScale, scaleFactor } = meta.scaleOptions;
543
+ cancelSpringAnimationIfAny(meta);
544
+ const stiffness = meta.scaleOptions.springStiffness ?? 300;
545
+ const damping = meta.scaleOptions.springDamping ?? 35;
546
+ let position = meta.scaleOptions.scale;
547
+ let velocity = 0;
548
+ let lastTime = performance.now();
549
+ const thresholdPos = Math.max(1e-4, Math.abs(targetScale) * 1e-3);
550
+ const thresholdVel = 1e-3;
551
+ const oldMatrix = {
552
+ e: matrix.e,
553
+ f: matrix.f
554
+ };
555
+ function step(now) {
556
+ const dt = Math.min((now - lastTime) / 1000, 0.033);
557
+ lastTime = now;
558
+ const force = stiffness * (targetScale - position);
559
+ const accel = force - damping * velocity;
560
+ velocity += accel * dt;
561
+ const prev = position;
562
+ position += velocity * dt;
563
+ const scaleDiff = position / prev;
564
+ if (isFinite(scaleDiff) && scaleDiff > 0) {
565
+ matrix.scale(scaleDiff, scaleDiff);
566
+ matrix.e = anchorX - (anchorX - matrix.e) * scaleDiff;
567
+ matrix.f = anchorY - (anchorY - matrix.f) * scaleDiff;
568
+ meta.scaleOptions.scale = position;
569
+ meta.scaleOptions.virtualScale = position;
570
+ updateViewport(pluginContext, domEvent, false);
571
+ }
572
+ const isSettled = Math.abs(targetScale - position) <= thresholdPos && Math.abs(velocity) <= thresholdVel;
573
+ if (isSettled) {
574
+ const finalScaleDiff = targetScale / meta.scaleOptions.scale;
575
+ if (isFinite(finalScaleDiff) && finalScaleDiff > 0 && Math.abs(finalScaleDiff - 1) > 1e-12) {
576
+ matrix.scale(finalScaleDiff, finalScaleDiff);
577
+ matrix.e = anchorX - (anchorX - matrix.e) * finalScaleDiff;
578
+ matrix.f = anchorY - (anchorY - matrix.f) * finalScaleDiff;
579
+ }
580
+ meta.scaleOptions.scale = targetScale;
581
+ meta.scaleOptions.virtualScale = targetScale;
582
+ try {
583
+ component.handleTransformCacheInvalidation(oldMatrix, {
584
+ e: matrix.e,
585
+ f: matrix.f
586
+ });
587
+ } catch {}
588
+ updateViewport(pluginContext, domEvent, false);
589
+ meta.scaleOptions.springRafId = null;
590
+ return;
591
+ }
592
+ meta.scaleOptions.springRafId = requestAnimationFrame(step);
593
+ }
594
+ meta.scaleOptions.springRafId = requestAnimationFrame(step);
595
+ }
596
+ function handleWheelEnd(pluginContext, domEvent) {
597
+ const meta = getScaleOptions.call(pluginContext);
598
+ const { scale, minScale, maxScale } = meta.scaleOptions;
599
+ const eps = 1e-6;
600
+ if (scale + eps < minScale) {
601
+ const target = minScale;
602
+ const anchorX = meta.scaleOptions.lastAnchorX ?? domEvent.component.render.options.width / 2;
603
+ const anchorY = meta.scaleOptions.lastAnchorY ?? domEvent.component.render.options.height / 2;
604
+ springAnimateToScale(pluginContext, domEvent, target, anchorX, anchorY);
605
+ } else if (scale - eps > maxScale) {
606
+ const target = maxScale;
607
+ const anchorX = meta.scaleOptions.lastAnchorX ?? domEvent.component.render.options.width / 2;
608
+ const anchorY = meta.scaleOptions.lastAnchorY ?? domEvent.component.render.options.height / 2;
609
+ springAnimateToScale(pluginContext, domEvent, target, anchorX, anchorY);
610
+ } else {
611
+ // inside bounds: sync virtualScale to visible scale to avoid sudden jumps later
612
+ meta.scaleOptions.virtualScale = meta.scaleOptions.scale;
613
+ if (Math.abs(scale - minScale) < 1e-8) {
614
+ meta.scaleOptions.scale = minScale;
615
+ }
616
+ if (Math.abs(scale - maxScale) < 1e-8) {
617
+ meta.scaleOptions.scale = maxScale;
618
+ }
619
+ }
620
+ }
621
+ function handleZoom(pluginContext, event, domEvent) {
622
+ const { stateManager: state, matrix, component } = domEvent;
623
+ const meta = getScaleOptions.call(pluginContext);
624
+ // read currentVisible and currentVirtual separately to avoid destructuring-default warnings
625
+ const currentVisible = meta.scaleOptions.scale;
626
+ const prevVirtualRaw = meta.scaleOptions.virtualScale ?? currentVisible;
627
+ const minScale = meta.scaleOptions.minScale;
628
+ const maxScale = meta.scaleOptions.maxScale;
629
+ const scaleFactor = meta.scaleOptions.scaleFactor;
630
+ const overshootResistance = meta.scaleOptions.overshootResistance ?? 0.35;
631
+ const overshootLimitFactor = meta.scaleOptions.overshootLimitFactor ?? 0.05;
632
+ cancelSpringAnimationIfAny(meta);
512
633
  const oldMatrix = {
513
634
  e: matrix.e,
514
635
  f: matrix.f
515
636
  };
516
- const dynamicScaleFactor = Math.max(scaleFactor, scale * 0.1);
637
+ const dynamicScaleFactor = Math.max(scaleFactor, currentVisible * 0.1);
517
638
  const delta = event.native.deltaY < 0 ? dynamicScaleFactor : -dynamicScaleFactor;
518
- const newScale = Math.max(minScale, Math.min(maxScale, scale + delta));
519
- if (newScale === scale) {
639
+ let newVirtual = prevVirtualRaw + delta;
640
+ let newVisible;
641
+ if (newVirtual >= minScale && newVirtual <= maxScale) {
642
+ newVisible = newVirtual;
643
+ } else if (newVirtual < minScale) {
644
+ newVisible = minScale + (newVirtual - minScale) * overshootResistance;
645
+ const lowerBound = Math.max(0, minScale * overshootLimitFactor);
646
+ if (newVisible < lowerBound) {
647
+ newVisible = lowerBound;
648
+ // sync virtual so further moves are consistent with clamped visible value
649
+ newVirtual = minScale + (newVisible - minScale) / Math.max(1e-6, overshootResistance);
650
+ }
651
+ } else {
652
+ newVisible = maxScale + (newVirtual - maxScale) * overshootResistance;
653
+ const upperBound = maxScale * (1 + Math.max(overshootLimitFactor, 0.05));
654
+ if (newVisible > upperBound) {
655
+ newVisible = upperBound;
656
+ newVirtual = maxScale + (newVisible - maxScale) / Math.max(1e-6, overshootResistance);
657
+ }
658
+ }
659
+ const prevVisible = currentVisible;
660
+ if (newVisible === prevVisible) {
661
+ meta.scaleOptions.virtualScale = newVirtual;
520
662
  return;
521
663
  }
522
664
  state.transition('SCALING');
523
665
  const mouseX = event.native.offsetX;
524
666
  const mouseY = event.native.offsetY;
525
- const scaleDiff = newScale / scale;
526
- meta.scaleOptions.scale = newScale;
667
+ // remember anchor for later spring animation
668
+ meta.scaleOptions.lastAnchorX = mouseX;
669
+ meta.scaleOptions.lastAnchorY = mouseY;
670
+ const scaleDiff = newVisible / prevVisible;
671
+ meta.scaleOptions.virtualScale = newVirtual;
672
+ meta.scaleOptions.scale = newVisible;
527
673
  matrix.scale(scaleDiff, scaleDiff);
528
674
  matrix.e = mouseX - (mouseX - matrix.e) * scaleDiff;
529
675
  matrix.f = mouseY - (mouseY - matrix.f) * scaleDiff;
530
- const newMatrix = {
531
- e: matrix.e,
532
- f: matrix.f
533
- };
534
- component.handleTransformCacheInvalidation(oldMatrix, newMatrix);
676
+ try {
677
+ component.handleTransformCacheInvalidation(oldMatrix, {
678
+ e: matrix.e,
679
+ f: matrix.f
680
+ });
681
+ } catch {}
535
682
  updateViewport(pluginContext, domEvent, false);
683
+ const g = meta.gestureState;
684
+ if (g.wheelEndTimeoutId) {
685
+ clearTimeout(g.wheelEndTimeoutId);
686
+ g.wheelEndTimeoutId = null;
687
+ }
688
+ const debounceMs = meta.scaleOptions.wheelDebounce ?? 200;
689
+ g.wheelEndTimeoutId = window.setTimeout(()=>{
690
+ g.wheelEndTimeoutId = null;
691
+ handleWheelEnd(pluginContext, domEvent);
692
+ }, debounceMs);
536
693
  }
537
694
  function handlePan(pluginContext, event, domEvent) {
538
695
  const { stateManager: state, matrix } = domEvent;
539
696
  const panSpeed = 0.8;
540
697
  const deltaX = event.native.deltaX * panSpeed;
541
698
  const deltaY = event.native.deltaY * panSpeed;
699
+ const meta = getScaleOptions.call(pluginContext);
700
+ cancelSpringAnimationIfAny(meta);
542
701
  state.transition('PANNING');
543
702
  matrix.e -= deltaX;
544
703
  matrix.f -= deltaY;
@@ -595,7 +754,6 @@ const presetZoomablePlugin = definePlugin({
595
754
  if (scaleMeta) {
596
755
  scaleMeta.scaleOptions.scale = targetScale;
597
756
  }
598
- const highlight = getHighlightInstance.call(this);
599
757
  const dragMeta = getDragOptions.call(this);
600
758
  if (dragMeta) {
601
759
  Object.assign(dragMeta.dragOptions, {
@@ -623,10 +781,7 @@ const presetZoomablePlugin = definePlugin({
623
781
  matrix.f = startMatrix.f + (targetF - startMatrix.f) * easedProgress;
624
782
  matrix.a = startMatrix.a + (targetScale - startMatrix.a) * easedProgress;
625
783
  matrix.d = startMatrix.d + (targetScale - startMatrix.d) * easedProgress;
626
- if (highlight?.highlight) {
627
- highlight.highlight.reset();
628
- highlight.highlight.setZIndexForHighlight();
629
- }
784
+ component.clearOverlay();
630
785
  component.cleanup();
631
786
  component.layoutNodes = component.calculateLayoutNodes(component.data, {
632
787
  w: width * matrix.a,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "squarified",
3
- "version": "0.6.2",
3
+ "version": "1.1.0",
4
4
  "description": "squarified tree map",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
@@ -56,7 +56,7 @@
56
56
  "tinyglobby": "^0.2.13",
57
57
  "tsx": "^4.19.2",
58
58
  "typescript": "^5.7.3",
59
- "vite-bundle-analyzer": "^0.22.0"
59
+ "vite-bundle-analyzer": "^1.3.6"
60
60
  },
61
61
  "pnpm": {
62
62
  "overrides": {
@@ -70,5 +70,5 @@
70
70
  "esbuild"
71
71
  ]
72
72
  },
73
- "packageManager": "pnpm@10.10.0+sha512.d615db246fe70f25dcfea6d8d73dee782ce23e2245e3c4f6f888249fb568149318637dca73c2c5c8ef2a4ca0d5657fb9567188bfab47f566d1ee6ce987815c39"
73
+ "packageManager": "pnpm@10.32.1+sha512.a706938f0e89ac1456b6563eab4edf1d1faf3368d1191fc5c59790e96dc918e4456ab2e67d613de1043d2e8c81f87303e6b40d4ffeca9df15ef1ad567348f2be"
74
74
  }