squarified 0.4.2 → 0.5.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/README.md CHANGED
@@ -2,8 +2,6 @@
2
2
 
3
3
  `squarified` is a minimal and powerful treemap component.
4
4
 
5
- https://github.com/user-attachments/assets/caf30d9d-0a5a-446a-b767-7927bf4387c9
6
-
7
5
  ## Install
8
6
 
9
7
  ```shell
@@ -18,6 +16,10 @@ nonzzz.github.io/squarified/
18
16
 
19
17
  Kanno
20
18
 
19
+ ### Credits
20
+
21
+ Algorithm is ported from [esbuild Bundle Size Analyzer](https://esbuild.github.io/analyze/) by [Evan Wallace](https://github.com/evanw). Refactor algorithm to adjusted the layout.
22
+
21
23
  ### LICENSE
22
24
 
23
25
  [MIT](./LICENSE)
@@ -1529,7 +1529,7 @@ class StateManager {
1529
1529
  canTransition(to) {
1530
1530
  switch(this.current){
1531
1531
  case 'IDLE':
1532
- return to === 'PRESSED' || to === 'MOVE' || to === 'SCALING' || to === 'ZOOMING';
1532
+ return to === 'PRESSED' || to === 'MOVE' || to === 'SCALING' || to === 'ZOOMING' || to === 'PANNING';
1533
1533
  case 'PRESSED':
1534
1534
  return to === 'DRAGGING' || to === 'IDLE';
1535
1535
  case 'DRAGGING':
@@ -1540,6 +1540,8 @@ class StateManager {
1540
1540
  return to === 'IDLE';
1541
1541
  case 'ZOOMING':
1542
1542
  return to === 'IDLE';
1543
+ case 'PANNING':
1544
+ return to === 'IDLE';
1543
1545
  default:
1544
1546
  return false;
1545
1547
  }
@@ -1531,7 +1531,7 @@ class StateManager {
1531
1531
  canTransition(to) {
1532
1532
  switch(this.current){
1533
1533
  case 'IDLE':
1534
- return to === 'PRESSED' || to === 'MOVE' || to === 'SCALING' || to === 'ZOOMING';
1534
+ return to === 'PRESSED' || to === 'MOVE' || to === 'SCALING' || to === 'ZOOMING' || to === 'PANNING';
1535
1535
  case 'PRESSED':
1536
1536
  return to === 'DRAGGING' || to === 'IDLE';
1537
1537
  case 'DRAGGING':
@@ -1542,6 +1542,8 @@ class StateManager {
1542
1542
  return to === 'IDLE';
1543
1543
  case 'ZOOMING':
1544
1544
  return to === 'IDLE';
1545
+ case 'PANNING':
1546
+ return to === 'IDLE';
1545
1547
  default:
1546
1548
  return false;
1547
1549
  }
@@ -454,6 +454,7 @@ declare const STATE_TRANSITION: {
454
454
  readonly ZOOMING: "ZOOMING";
455
455
  readonly MOVE: "MOVE";
456
456
  readonly SCALING: "SCALING";
457
+ readonly PANNING: "PANNING";
457
458
  };
458
459
  type StateTransition = typeof STATE_TRANSITION[keyof typeof STATE_TRANSITION];
459
460
  declare class StateManager {
package/dist/index.d.mts CHANGED
@@ -1 +1 @@
1
- export { B as BasicTreemapInstance, c as CreateTreemapOptions, D as DOMEventType, a1 as DefaultMap, E as ExposedEventCallback, n as ExposedEventDefinition, o as ExposedEventMethods, x as GraphicConfig, w as GraphicFont, G as GraphicLayout, Q as InheritedCollections, U as InheritedCollectionsWithParamter, L as LayoutModule, M as Module, N as NativeModule, k as Plugin, P as PluginContext, l as PluginHooks, p as PrimitiveEventMetadata, S as Series, e as TreemapInstance, y as TreemapInstanceAPI, T as TreemapOptions, O as applyCanvasTransform, f as c2m, K as createCanvasElement, H as createRoundBlock, I as createTitleText, d as createTreemap, m as definePlugin, g as findRelativeNode, h as findRelativeNodeById, i as flattenModule, j as getNodeDepth, z as hashCode, q as isClickEvent, r as isContextMenuEvent, X as isMacOS, t as isMouseEvent, a0 as isScrollWheelOrRightButtonOnMouseupAndDown, u as isWheelEvent, R as mixin, V as mixinWithParams, F as noop, A as perferNumeric, W as prettyStrJoin, J as raf, $ as smoothFrame, s as sortChildrenByKey, Z as stackMatrixTransform, _ as stackMatrixTransformWithGraphAndLayer, Y as typedForIn, v as visit } from './index-BoUEaWVv.js';
1
+ export { B as BasicTreemapInstance, c as CreateTreemapOptions, D as DOMEventType, a1 as DefaultMap, E as ExposedEventCallback, n as ExposedEventDefinition, o as ExposedEventMethods, x as GraphicConfig, w as GraphicFont, G as GraphicLayout, Q as InheritedCollections, U as InheritedCollectionsWithParamter, L as LayoutModule, M as Module, N as NativeModule, k as Plugin, P as PluginContext, l as PluginHooks, p as PrimitiveEventMetadata, S as Series, e as TreemapInstance, y as TreemapInstanceAPI, T as TreemapOptions, O as applyCanvasTransform, f as c2m, K as createCanvasElement, H as createRoundBlock, I as createTitleText, d as createTreemap, m as definePlugin, g as findRelativeNode, h as findRelativeNodeById, i as flattenModule, j as getNodeDepth, z as hashCode, q as isClickEvent, r as isContextMenuEvent, X as isMacOS, t as isMouseEvent, a0 as isScrollWheelOrRightButtonOnMouseupAndDown, u as isWheelEvent, R as mixin, V as mixinWithParams, F as noop, A as perferNumeric, W as prettyStrJoin, J as raf, $ as smoothFrame, s as sortChildrenByKey, Z as stackMatrixTransform, _ as stackMatrixTransformWithGraphAndLayer, Y as typedForIn, v as visit } from './index-Dskgz6nc.js';
package/dist/index.d.ts CHANGED
@@ -1 +1 @@
1
- export { B as BasicTreemapInstance, c as CreateTreemapOptions, D as DOMEventType, a1 as DefaultMap, E as ExposedEventCallback, n as ExposedEventDefinition, o as ExposedEventMethods, x as GraphicConfig, w as GraphicFont, G as GraphicLayout, Q as InheritedCollections, U as InheritedCollectionsWithParamter, L as LayoutModule, M as Module, N as NativeModule, k as Plugin, P as PluginContext, l as PluginHooks, p as PrimitiveEventMetadata, S as Series, e as TreemapInstance, y as TreemapInstanceAPI, T as TreemapOptions, O as applyCanvasTransform, f as c2m, K as createCanvasElement, H as createRoundBlock, I as createTitleText, d as createTreemap, m as definePlugin, g as findRelativeNode, h as findRelativeNodeById, i as flattenModule, j as getNodeDepth, z as hashCode, q as isClickEvent, r as isContextMenuEvent, X as isMacOS, t as isMouseEvent, a0 as isScrollWheelOrRightButtonOnMouseupAndDown, u as isWheelEvent, R as mixin, V as mixinWithParams, F as noop, A as perferNumeric, W as prettyStrJoin, J as raf, $ as smoothFrame, s as sortChildrenByKey, Z as stackMatrixTransform, _ as stackMatrixTransformWithGraphAndLayer, Y as typedForIn, v as visit } from './index-BoUEaWVv.js';
1
+ export { B as BasicTreemapInstance, c as CreateTreemapOptions, D as DOMEventType, a1 as DefaultMap, E as ExposedEventCallback, n as ExposedEventDefinition, o as ExposedEventMethods, x as GraphicConfig, w as GraphicFont, G as GraphicLayout, Q as InheritedCollections, U as InheritedCollectionsWithParamter, L as LayoutModule, M as Module, N as NativeModule, k as Plugin, P as PluginContext, l as PluginHooks, p as PrimitiveEventMetadata, S as Series, e as TreemapInstance, y as TreemapInstanceAPI, T as TreemapOptions, O as applyCanvasTransform, f as c2m, K as createCanvasElement, H as createRoundBlock, I as createTitleText, d as createTreemap, m as definePlugin, g as findRelativeNode, h as findRelativeNodeById, i as flattenModule, j as getNodeDepth, z as hashCode, q as isClickEvent, r as isContextMenuEvent, X as isMacOS, t as isMouseEvent, a0 as isScrollWheelOrRightButtonOnMouseupAndDown, u as isWheelEvent, R as mixin, V as mixinWithParams, F as noop, A as perferNumeric, W as prettyStrJoin, J as raf, $ as smoothFrame, s as sortChildrenByKey, Z as stackMatrixTransform, _ as stackMatrixTransformWithGraphAndLayer, Y as typedForIn, v as visit } from './index-Dskgz6nc.js';
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var domEvent = require('./dom-event-CeVZ44nB.js');
3
+ var domEvent = require('./dom-event-Dz0I7Z12.js');
4
4
 
5
5
  function createTreemap(// @ts-expect-error todo fix
6
6
  options) {
package/dist/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
- import { l as logger, m as mixin, E as Event, n as noop, a as assertExists, b as bindParentForModule, C as Component, D as DOMEvent } from './dom-event-BLJt9knO.mjs';
2
- export { J as DefaultMap, x as applyCanvasTransform, c as c2m, w as createCanvasElement, r as createRoundBlock, t as createTitleText, h as definePlugin, f as findRelativeNode, d as findRelativeNodeById, e as flattenModule, g as getNodeDepth, p as hashCode, i as isClickEvent, j as isContextMenuEvent, A as isMacOS, k as isMouseEvent, I as isScrollWheelOrRightButtonOnMouseupAndDown, o as isWheelEvent, y as mixinWithParams, q as perferNumeric, z as prettyStrJoin, u as raf, H as smoothFrame, s as sortChildrenByKey, F as stackMatrixTransform, G as stackMatrixTransformWithGraphAndLayer, B as typedForIn, v as visit } from './dom-event-BLJt9knO.mjs';
1
+ import { l as logger, m as mixin, E as Event, n as noop, a as assertExists, b as bindParentForModule, C as Component, D as DOMEvent } from './dom-event-Bkw3ecGf.mjs';
2
+ export { J as DefaultMap, x as applyCanvasTransform, c as c2m, w as createCanvasElement, r as createRoundBlock, t as createTitleText, h as definePlugin, f as findRelativeNode, d as findRelativeNodeById, e as flattenModule, g as getNodeDepth, p as hashCode, i as isClickEvent, j as isContextMenuEvent, A as isMacOS, k as isMouseEvent, I as isScrollWheelOrRightButtonOnMouseupAndDown, o as isWheelEvent, y as mixinWithParams, q as perferNumeric, z as prettyStrJoin, u as raf, H as smoothFrame, s as sortChildrenByKey, F as stackMatrixTransform, G as stackMatrixTransformWithGraphAndLayer, B as typedForIn, v as visit } from './dom-event-Bkw3ecGf.mjs';
3
3
 
4
4
  function createTreemap(// @ts-expect-error todo fix
5
5
  options) {
package/dist/plugin.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { P as PluginContext, D as DOMEventType, a as DOMEventMetadata, L as LayoutModule, b as DOMEvent, C as ColorMappings, B as BasicTreemapInstance } from './index-BoUEaWVv.js';
1
+ import { P as PluginContext, D as DOMEventType, a as DOMEventMetadata, L as LayoutModule, b as DOMEvent, C as ColorMappings, B as BasicTreemapInstance } from './index-Dskgz6nc.js';
2
2
 
3
3
  declare const presetHighlightPlugin: {
4
4
  name: "treemap:preset-highlight";
@@ -25,6 +25,21 @@ declare const presetDragElementPlugin: {
25
25
  onResize(this: PluginContext, { matrix, stateManager: state }: DOMEvent): void;
26
26
  };
27
27
 
28
+ interface MenuRenderConfig {
29
+ html: string;
30
+ action: string;
31
+ }
32
+ interface MenuPluginOptions {
33
+ style?: Partial<CSSStyleDeclaration>;
34
+ render?: (menu: HTMLDivElement) => MenuRenderConfig[];
35
+ onClick?: (action: string, module: LayoutModule | null) => void;
36
+ }
37
+ declare function presetMenuPlugin(options?: MenuPluginOptions): {
38
+ name: "treemap:preset-menu";
39
+ onDOMEventTriggered<N extends DOMEventType>(this: PluginContext, _: N, event: DOMEventMetadata<N>, __: LayoutModule | null, DOMEvent: DOMEvent): void;
40
+ onDispose(this: PluginContext): void;
41
+ };
42
+
28
43
  declare const presetColorPlugin: {
29
44
  name: "treemap:preset-color";
30
45
  onModuleInit(this: PluginContext, modules: LayoutModule[]): {
@@ -54,6 +69,16 @@ declare function presetScalePlugin(options?: ScalePluginOptions): {
54
69
  maxScale: number;
55
70
  scaleFactor: number;
56
71
  };
72
+ gestureState: {
73
+ isTrackingGesture: false;
74
+ lastEventTime: number;
75
+ eventCount: number;
76
+ totalDeltaY: number;
77
+ totalDeltaX: number;
78
+ consecutivePinchEvents: number;
79
+ gestureType: "unknown";
80
+ lockGestureType: false;
81
+ };
57
82
  };
58
83
  onResize(this: PluginContext, { matrix, stateManager: state }: DOMEvent): void;
59
84
  };
@@ -75,4 +100,4 @@ declare const presetZoomablePlugin: {
75
100
  meta: ZoomableMetadata;
76
101
  };
77
102
 
78
- export { presetColorPlugin, presetDragElementPlugin, presetHighlightPlugin, presetScalePlugin, presetZoomablePlugin };
103
+ export { presetColorPlugin, presetDragElementPlugin, presetHighlightPlugin, presetMenuPlugin, presetScalePlugin, presetZoomablePlugin };
package/dist/plugin.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { P as PluginContext, D as DOMEventType, a as DOMEventMetadata, L as LayoutModule, b as DOMEvent, C as ColorMappings, B as BasicTreemapInstance } from './index-BoUEaWVv.js';
1
+ import { P as PluginContext, D as DOMEventType, a as DOMEventMetadata, L as LayoutModule, b as DOMEvent, C as ColorMappings, B as BasicTreemapInstance } from './index-Dskgz6nc.js';
2
2
 
3
3
  declare const presetHighlightPlugin: {
4
4
  name: "treemap:preset-highlight";
@@ -25,6 +25,21 @@ declare const presetDragElementPlugin: {
25
25
  onResize(this: PluginContext, { matrix, stateManager: state }: DOMEvent): void;
26
26
  };
27
27
 
28
+ interface MenuRenderConfig {
29
+ html: string;
30
+ action: string;
31
+ }
32
+ interface MenuPluginOptions {
33
+ style?: Partial<CSSStyleDeclaration>;
34
+ render?: (menu: HTMLDivElement) => MenuRenderConfig[];
35
+ onClick?: (action: string, module: LayoutModule | null) => void;
36
+ }
37
+ declare function presetMenuPlugin(options?: MenuPluginOptions): {
38
+ name: "treemap:preset-menu";
39
+ onDOMEventTriggered<N extends DOMEventType>(this: PluginContext, _: N, event: DOMEventMetadata<N>, __: LayoutModule | null, DOMEvent: DOMEvent): void;
40
+ onDispose(this: PluginContext): void;
41
+ };
42
+
28
43
  declare const presetColorPlugin: {
29
44
  name: "treemap:preset-color";
30
45
  onModuleInit(this: PluginContext, modules: LayoutModule[]): {
@@ -54,6 +69,16 @@ declare function presetScalePlugin(options?: ScalePluginOptions): {
54
69
  maxScale: number;
55
70
  scaleFactor: number;
56
71
  };
72
+ gestureState: {
73
+ isTrackingGesture: false;
74
+ lastEventTime: number;
75
+ eventCount: number;
76
+ totalDeltaY: number;
77
+ totalDeltaX: number;
78
+ consecutivePinchEvents: number;
79
+ gestureType: "unknown";
80
+ lockGestureType: false;
81
+ };
57
82
  };
58
83
  onResize(this: PluginContext, { matrix, stateManager: state }: DOMEvent): void;
59
84
  };
@@ -75,4 +100,4 @@ declare const presetZoomablePlugin: {
75
100
  meta: ZoomableMetadata;
76
101
  };
77
102
 
78
- export { presetColorPlugin, presetDragElementPlugin, presetHighlightPlugin, presetScalePlugin, presetZoomablePlugin };
103
+ export { presetColorPlugin, presetDragElementPlugin, presetHighlightPlugin, presetMenuPlugin, presetScalePlugin, presetZoomablePlugin };
package/dist/plugin.js CHANGED
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var domEvent = require('./dom-event-CeVZ44nB.js');
3
+ var domEvent = require('./dom-event-Dz0I7Z12.js');
4
4
 
5
5
  // Currently, etoile is an internal module, so we won't need too much easing functions.
6
6
  // And the animation logic is implemented by user code.
@@ -241,6 +241,72 @@ function getDragOptions() {
241
241
  return meta;
242
242
  }
243
243
 
244
+ function presetMenuPlugin(options) {
245
+ let menu = null;
246
+ let domEvent$1 = null;
247
+ const handleMenuClick = (e)=>{
248
+ if (!domEvent$1) {
249
+ return;
250
+ }
251
+ if (!menu) {
252
+ return;
253
+ }
254
+ const target = e.target;
255
+ if (target.parentNode) {
256
+ const parent = target.parentNode;
257
+ const action = parent.getAttribute('data-action');
258
+ if (!action) {
259
+ return;
260
+ }
261
+ if (options?.onClick) {
262
+ options.onClick(action, domEvent$1.findRelativeNode({
263
+ native: e,
264
+ kind: undefined
265
+ }));
266
+ }
267
+ }
268
+ menu.style.display = 'none';
269
+ };
270
+ return domEvent.definePlugin({
271
+ name: 'treemap:preset-menu',
272
+ onDOMEventTriggered (_, event, __, DOMEvent) {
273
+ if (domEvent.isContextMenuEvent(event)) {
274
+ event.native.stopPropagation();
275
+ event.native.preventDefault();
276
+ if (!menu) {
277
+ menu = document.createElement('div');
278
+ domEvent$1 = DOMEvent;
279
+ Object.assign(menu.style, {
280
+ backgroundColor: '#fff',
281
+ ...options?.style,
282
+ position: 'absolute',
283
+ zIndex: '9999'
284
+ });
285
+ menu.addEventListener('click', handleMenuClick);
286
+ if (menu && options?.render) {
287
+ const result = options.render(menu);
288
+ menu.innerHTML = result.map((item)=>{
289
+ return `<div data-action='${item.action}'>${item.html}</div>`;
290
+ }).join('');
291
+ }
292
+ document.body.append(menu);
293
+ }
294
+ menu.style.left = event.native.clientX + 'px';
295
+ menu.style.top = event.native.clientY + 'px';
296
+ menu.style.display = 'initial';
297
+ }
298
+ },
299
+ onDispose () {
300
+ if (!menu) {
301
+ return;
302
+ }
303
+ menu.removeEventListener('click', handleMenuClick);
304
+ menu = null;
305
+ domEvent$1 = null;
306
+ }
307
+ });
308
+ }
309
+
244
310
  const presetColorPlugin = domEvent.definePlugin({
245
311
  name: 'treemap:preset-color',
246
312
  onModuleInit (modules) {
@@ -311,6 +377,16 @@ function presetScalePlugin(options) {
311
377
  minScale: options?.min || 0.1,
312
378
  maxScale: options?.max || Infinity,
313
379
  scaleFactor: 0.05
380
+ },
381
+ gestureState: {
382
+ isTrackingGesture: false,
383
+ lastEventTime: 0,
384
+ eventCount: 0,
385
+ totalDeltaY: 0,
386
+ totalDeltaX: 0,
387
+ consecutivePinchEvents: 0,
388
+ gestureType: 'unknown',
389
+ lockGestureType: false
314
390
  }
315
391
  },
316
392
  onResize ({ matrix, stateManager: state }) {
@@ -327,33 +403,80 @@ function getScaleOptions() {
327
403
  const meta = this.getPluginMetadata('treemap:preset-scale');
328
404
  return meta;
329
405
  }
330
- function onWheel(pluginContext, event, { stateManager: state, component, matrix }) {
406
+ function determineGestureType(event, gestureState) {
407
+ const now = Date.now();
408
+ const timeDiff = now - gestureState.lastEventTime;
409
+ if (timeDiff > 150) {
410
+ Object.assign(gestureState, {
411
+ isTrackingGesture: false,
412
+ lastEventTime: now,
413
+ eventCount: 1,
414
+ totalDeltaY: Math.abs(event.deltaY),
415
+ totalDeltaX: Math.abs(event.deltaX),
416
+ consecutivePinchEvents: 0,
417
+ gestureType: 'unknown',
418
+ lockGestureType: false
419
+ });
420
+ } else {
421
+ gestureState.eventCount++;
422
+ gestureState.totalDeltaY += Math.abs(event.deltaY);
423
+ gestureState.totalDeltaX += Math.abs(event.deltaX);
424
+ gestureState.lastEventTime = now;
425
+ }
426
+ if (event.ctrlKey) {
427
+ gestureState.gestureType = 'zoom';
428
+ gestureState.lockGestureType = true;
429
+ return 'zoom';
430
+ }
431
+ // windows/macos mouse wheel
432
+ // Usually the dettaY is large and deltaX maybe 0 or small number.
433
+ 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;
434
+ if (isMouseWheel) {
435
+ gestureState.gestureType = 'zoom';
436
+ gestureState.lockGestureType = true;
437
+ return 'zoom';
438
+ }
439
+ if (gestureState.lockGestureType && gestureState.gestureType !== 'unknown') {
440
+ return gestureState.gestureType;
441
+ }
442
+ // Magic trackpad
443
+ if (gestureState.eventCount >= 3) {
444
+ const avgDeltaY = gestureState.totalDeltaY / gestureState.eventCount;
445
+ const avgDeltaX = gestureState.totalDeltaX / gestureState.eventCount;
446
+ const ratio = avgDeltaX / (avgDeltaY + 0.1);
447
+ const isZoomGesture = avgDeltaY > 8 && ratio < 0.3 && Math.abs(event.deltaY) > 5;
448
+ if (isZoomGesture) {
449
+ gestureState.gestureType = 'zoom';
450
+ gestureState.lockGestureType = true;
451
+ return 'zoom';
452
+ } else {
453
+ gestureState.gestureType = 'pan';
454
+ gestureState.lockGestureType = true;
455
+ return 'pan';
456
+ }
457
+ }
458
+ return 'pan';
459
+ }
460
+ function onWheel(pluginContext, event, domEvent) {
331
461
  event.native.preventDefault();
332
462
  const meta = getScaleOptions.call(pluginContext);
333
463
  if (!meta) {
334
464
  return;
335
465
  }
336
- const { scale, minScale, maxScale, scaleFactor } = meta.scaleOptions;
337
- const delta = event.native.deltaY < 0 ? scaleFactor : -scaleFactor;
338
- const newScale = Math.max(minScale, Math.min(maxScale, scale + delta));
339
- if (newScale === scale) {
340
- return;
466
+ const gestureType = determineGestureType(event.native, meta.gestureState);
467
+ if (gestureType === 'zoom') {
468
+ handleZoom(pluginContext, event, domEvent);
469
+ } else {
470
+ handlePan(pluginContext, event, domEvent);
341
471
  }
342
- state.transition('SCALING');
343
- const mouseX = event.native.offsetX;
344
- const mouseY = event.native.offsetY;
345
- const scaleDiff = newScale / scale;
346
- meta.scaleOptions.scale = newScale;
472
+ }
473
+ function updateViewport(pluginContext, { stateManager: state, component, matrix }, useAnimation = false) {
347
474
  const highlight = getHighlightInstance.apply(pluginContext);
348
- domEvent.smoothFrame((_, cleanup)=>{
349
- cleanup();
475
+ const doUpdate = ()=>{
350
476
  if (highlight && highlight.highlight) {
351
477
  highlight.highlight.reset();
352
478
  highlight.highlight.setZIndexForHighlight();
353
479
  }
354
- matrix.scale(scaleDiff, scaleDiff);
355
- matrix.e = mouseX - (mouseX - matrix.e) * scaleDiff;
356
- matrix.f = mouseY - (mouseY - matrix.f) * scaleDiff;
357
480
  component.cleanup();
358
481
  const { width, height } = component.render.options;
359
482
  component.layoutNodes = component.calculateLayoutNodes(component.data, {
@@ -363,15 +486,56 @@ function onWheel(pluginContext, event, { stateManager: state, component, matrix
363
486
  y: 0
364
487
  }, matrix.a);
365
488
  component.draw(true, false);
366
- domEvent.stackMatrixTransformWithGraphAndLayer(component.elements, matrix.e, matrix.f, newScale);
489
+ domEvent.stackMatrixTransformWithGraphAndLayer(component.elements, matrix.e, matrix.f, matrix.a);
367
490
  component.update();
368
491
  if (state.canTransition('IDLE')) {
369
492
  state.transition('IDLE');
370
493
  }
371
- return true;
372
- }, {
373
- duration: ANIMATION_DURATION
374
- });
494
+ };
495
+ if (useAnimation) {
496
+ domEvent.smoothFrame((_, cleanup)=>{
497
+ cleanup();
498
+ doUpdate();
499
+ return true;
500
+ }, {
501
+ duration: ANIMATION_DURATION
502
+ });
503
+ } else {
504
+ doUpdate();
505
+ }
506
+ }
507
+ function handleZoom(pluginContext, event, domEvent) {
508
+ const { stateManager: state, matrix } = domEvent;
509
+ const meta = getScaleOptions.call(pluginContext);
510
+ if (!meta) {
511
+ return;
512
+ }
513
+ const { scale, minScale, maxScale, scaleFactor } = meta.scaleOptions;
514
+ const dynamicScaleFactor = Math.max(scaleFactor, scale * 0.1);
515
+ const delta = event.native.deltaY < 0 ? dynamicScaleFactor : -dynamicScaleFactor;
516
+ const newScale = Math.max(minScale, Math.min(maxScale, scale + delta));
517
+ if (newScale === scale) {
518
+ return;
519
+ }
520
+ state.transition('SCALING');
521
+ const mouseX = event.native.offsetX;
522
+ const mouseY = event.native.offsetY;
523
+ const scaleDiff = newScale / scale;
524
+ meta.scaleOptions.scale = newScale;
525
+ matrix.scale(scaleDiff, scaleDiff);
526
+ matrix.e = mouseX - (mouseX - matrix.e) * scaleDiff;
527
+ matrix.f = mouseY - (mouseY - matrix.f) * scaleDiff;
528
+ updateViewport(pluginContext, domEvent, false);
529
+ }
530
+ function handlePan(pluginContext, event, domEvent) {
531
+ const { stateManager: state, matrix } = domEvent;
532
+ const panSpeed = 0.8;
533
+ const deltaX = event.native.deltaX * panSpeed;
534
+ const deltaY = event.native.deltaY * panSpeed;
535
+ state.transition('PANNING');
536
+ matrix.e -= deltaX;
537
+ matrix.f -= deltaY;
538
+ updateViewport(pluginContext, domEvent, true);
375
539
  }
376
540
 
377
541
  const MAX_SCALE_MULTIPLIER = 2.0;
@@ -474,5 +638,6 @@ const presetZoomablePlugin = domEvent.definePlugin({
474
638
  exports.presetColorPlugin = presetColorPlugin;
475
639
  exports.presetDragElementPlugin = presetDragElementPlugin;
476
640
  exports.presetHighlightPlugin = presetHighlightPlugin;
641
+ exports.presetMenuPlugin = presetMenuPlugin;
477
642
  exports.presetScalePlugin = presetScalePlugin;
478
643
  exports.presetZoomablePlugin = presetZoomablePlugin;
package/dist/plugin.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { h as definePlugin, H as smoothFrame, r as createRoundBlock, F as stackMatrixTransform, S as Schedule, K as DEFAULT_MATRIX_LOC, I as isScrollWheelOrRightButtonOnMouseupAndDown, G as stackMatrixTransformWithGraphAndLayer, p as hashCode, P as PI_2, o as isWheelEvent, y as mixinWithParams } from './dom-event-BLJt9knO.mjs';
1
+ import { h as definePlugin, H as smoothFrame, r as createRoundBlock, F as stackMatrixTransform, S as Schedule, K as DEFAULT_MATRIX_LOC, I as isScrollWheelOrRightButtonOnMouseupAndDown, G as stackMatrixTransformWithGraphAndLayer, j as isContextMenuEvent, p as hashCode, P as PI_2, o as isWheelEvent, y as mixinWithParams } from './dom-event-Bkw3ecGf.mjs';
2
2
 
3
3
  // Currently, etoile is an internal module, so we won't need too much easing functions.
4
4
  // And the animation logic is implemented by user code.
@@ -239,6 +239,72 @@ function getDragOptions() {
239
239
  return meta;
240
240
  }
241
241
 
242
+ function presetMenuPlugin(options) {
243
+ let menu = null;
244
+ let domEvent = null;
245
+ const handleMenuClick = (e)=>{
246
+ if (!domEvent) {
247
+ return;
248
+ }
249
+ if (!menu) {
250
+ return;
251
+ }
252
+ const target = e.target;
253
+ if (target.parentNode) {
254
+ const parent = target.parentNode;
255
+ const action = parent.getAttribute('data-action');
256
+ if (!action) {
257
+ return;
258
+ }
259
+ if (options?.onClick) {
260
+ options.onClick(action, domEvent.findRelativeNode({
261
+ native: e,
262
+ kind: undefined
263
+ }));
264
+ }
265
+ }
266
+ menu.style.display = 'none';
267
+ };
268
+ return definePlugin({
269
+ name: 'treemap:preset-menu',
270
+ onDOMEventTriggered (_, event, __, DOMEvent) {
271
+ if (isContextMenuEvent(event)) {
272
+ event.native.stopPropagation();
273
+ event.native.preventDefault();
274
+ if (!menu) {
275
+ menu = document.createElement('div');
276
+ domEvent = DOMEvent;
277
+ Object.assign(menu.style, {
278
+ backgroundColor: '#fff',
279
+ ...options?.style,
280
+ position: 'absolute',
281
+ zIndex: '9999'
282
+ });
283
+ menu.addEventListener('click', handleMenuClick);
284
+ if (menu && options?.render) {
285
+ const result = options.render(menu);
286
+ menu.innerHTML = result.map((item)=>{
287
+ return `<div data-action='${item.action}'>${item.html}</div>`;
288
+ }).join('');
289
+ }
290
+ document.body.append(menu);
291
+ }
292
+ menu.style.left = event.native.clientX + 'px';
293
+ menu.style.top = event.native.clientY + 'px';
294
+ menu.style.display = 'initial';
295
+ }
296
+ },
297
+ onDispose () {
298
+ if (!menu) {
299
+ return;
300
+ }
301
+ menu.removeEventListener('click', handleMenuClick);
302
+ menu = null;
303
+ domEvent = null;
304
+ }
305
+ });
306
+ }
307
+
242
308
  const presetColorPlugin = definePlugin({
243
309
  name: 'treemap:preset-color',
244
310
  onModuleInit (modules) {
@@ -309,6 +375,16 @@ function presetScalePlugin(options) {
309
375
  minScale: options?.min || 0.1,
310
376
  maxScale: options?.max || Infinity,
311
377
  scaleFactor: 0.05
378
+ },
379
+ gestureState: {
380
+ isTrackingGesture: false,
381
+ lastEventTime: 0,
382
+ eventCount: 0,
383
+ totalDeltaY: 0,
384
+ totalDeltaX: 0,
385
+ consecutivePinchEvents: 0,
386
+ gestureType: 'unknown',
387
+ lockGestureType: false
312
388
  }
313
389
  },
314
390
  onResize ({ matrix, stateManager: state }) {
@@ -325,33 +401,80 @@ function getScaleOptions() {
325
401
  const meta = this.getPluginMetadata('treemap:preset-scale');
326
402
  return meta;
327
403
  }
328
- function onWheel(pluginContext, event, { stateManager: state, component, matrix }) {
404
+ function determineGestureType(event, gestureState) {
405
+ const now = Date.now();
406
+ const timeDiff = now - gestureState.lastEventTime;
407
+ if (timeDiff > 150) {
408
+ Object.assign(gestureState, {
409
+ isTrackingGesture: false,
410
+ lastEventTime: now,
411
+ eventCount: 1,
412
+ totalDeltaY: Math.abs(event.deltaY),
413
+ totalDeltaX: Math.abs(event.deltaX),
414
+ consecutivePinchEvents: 0,
415
+ gestureType: 'unknown',
416
+ lockGestureType: false
417
+ });
418
+ } else {
419
+ gestureState.eventCount++;
420
+ gestureState.totalDeltaY += Math.abs(event.deltaY);
421
+ gestureState.totalDeltaX += Math.abs(event.deltaX);
422
+ gestureState.lastEventTime = now;
423
+ }
424
+ if (event.ctrlKey) {
425
+ gestureState.gestureType = 'zoom';
426
+ gestureState.lockGestureType = true;
427
+ return 'zoom';
428
+ }
429
+ // windows/macos mouse wheel
430
+ // Usually the dettaY is large and deltaX maybe 0 or small number.
431
+ 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
+ if (isMouseWheel) {
433
+ gestureState.gestureType = 'zoom';
434
+ gestureState.lockGestureType = true;
435
+ return 'zoom';
436
+ }
437
+ if (gestureState.lockGestureType && gestureState.gestureType !== 'unknown') {
438
+ return gestureState.gestureType;
439
+ }
440
+ // Magic trackpad
441
+ if (gestureState.eventCount >= 3) {
442
+ const avgDeltaY = gestureState.totalDeltaY / gestureState.eventCount;
443
+ const avgDeltaX = gestureState.totalDeltaX / gestureState.eventCount;
444
+ const ratio = avgDeltaX / (avgDeltaY + 0.1);
445
+ const isZoomGesture = avgDeltaY > 8 && ratio < 0.3 && Math.abs(event.deltaY) > 5;
446
+ if (isZoomGesture) {
447
+ gestureState.gestureType = 'zoom';
448
+ gestureState.lockGestureType = true;
449
+ return 'zoom';
450
+ } else {
451
+ gestureState.gestureType = 'pan';
452
+ gestureState.lockGestureType = true;
453
+ return 'pan';
454
+ }
455
+ }
456
+ return 'pan';
457
+ }
458
+ function onWheel(pluginContext, event, domEvent) {
329
459
  event.native.preventDefault();
330
460
  const meta = getScaleOptions.call(pluginContext);
331
461
  if (!meta) {
332
462
  return;
333
463
  }
334
- const { scale, minScale, maxScale, scaleFactor } = meta.scaleOptions;
335
- const delta = event.native.deltaY < 0 ? scaleFactor : -scaleFactor;
336
- const newScale = Math.max(minScale, Math.min(maxScale, scale + delta));
337
- if (newScale === scale) {
338
- return;
464
+ const gestureType = determineGestureType(event.native, meta.gestureState);
465
+ if (gestureType === 'zoom') {
466
+ handleZoom(pluginContext, event, domEvent);
467
+ } else {
468
+ handlePan(pluginContext, event, domEvent);
339
469
  }
340
- state.transition('SCALING');
341
- const mouseX = event.native.offsetX;
342
- const mouseY = event.native.offsetY;
343
- const scaleDiff = newScale / scale;
344
- meta.scaleOptions.scale = newScale;
470
+ }
471
+ function updateViewport(pluginContext, { stateManager: state, component, matrix }, useAnimation = false) {
345
472
  const highlight = getHighlightInstance.apply(pluginContext);
346
- smoothFrame((_, cleanup)=>{
347
- cleanup();
473
+ const doUpdate = ()=>{
348
474
  if (highlight && highlight.highlight) {
349
475
  highlight.highlight.reset();
350
476
  highlight.highlight.setZIndexForHighlight();
351
477
  }
352
- matrix.scale(scaleDiff, scaleDiff);
353
- matrix.e = mouseX - (mouseX - matrix.e) * scaleDiff;
354
- matrix.f = mouseY - (mouseY - matrix.f) * scaleDiff;
355
478
  component.cleanup();
356
479
  const { width, height } = component.render.options;
357
480
  component.layoutNodes = component.calculateLayoutNodes(component.data, {
@@ -361,15 +484,56 @@ function onWheel(pluginContext, event, { stateManager: state, component, matrix
361
484
  y: 0
362
485
  }, matrix.a);
363
486
  component.draw(true, false);
364
- stackMatrixTransformWithGraphAndLayer(component.elements, matrix.e, matrix.f, newScale);
487
+ stackMatrixTransformWithGraphAndLayer(component.elements, matrix.e, matrix.f, matrix.a);
365
488
  component.update();
366
489
  if (state.canTransition('IDLE')) {
367
490
  state.transition('IDLE');
368
491
  }
369
- return true;
370
- }, {
371
- duration: ANIMATION_DURATION
372
- });
492
+ };
493
+ if (useAnimation) {
494
+ smoothFrame((_, cleanup)=>{
495
+ cleanup();
496
+ doUpdate();
497
+ return true;
498
+ }, {
499
+ duration: ANIMATION_DURATION
500
+ });
501
+ } else {
502
+ doUpdate();
503
+ }
504
+ }
505
+ function handleZoom(pluginContext, event, domEvent) {
506
+ const { stateManager: state, matrix } = domEvent;
507
+ const meta = getScaleOptions.call(pluginContext);
508
+ if (!meta) {
509
+ return;
510
+ }
511
+ const { scale, minScale, maxScale, scaleFactor } = meta.scaleOptions;
512
+ const dynamicScaleFactor = Math.max(scaleFactor, scale * 0.1);
513
+ const delta = event.native.deltaY < 0 ? dynamicScaleFactor : -dynamicScaleFactor;
514
+ const newScale = Math.max(minScale, Math.min(maxScale, scale + delta));
515
+ if (newScale === scale) {
516
+ return;
517
+ }
518
+ state.transition('SCALING');
519
+ const mouseX = event.native.offsetX;
520
+ const mouseY = event.native.offsetY;
521
+ const scaleDiff = newScale / scale;
522
+ meta.scaleOptions.scale = newScale;
523
+ matrix.scale(scaleDiff, scaleDiff);
524
+ matrix.e = mouseX - (mouseX - matrix.e) * scaleDiff;
525
+ matrix.f = mouseY - (mouseY - matrix.f) * scaleDiff;
526
+ updateViewport(pluginContext, domEvent, false);
527
+ }
528
+ function handlePan(pluginContext, event, domEvent) {
529
+ const { stateManager: state, matrix } = domEvent;
530
+ const panSpeed = 0.8;
531
+ const deltaX = event.native.deltaX * panSpeed;
532
+ const deltaY = event.native.deltaY * panSpeed;
533
+ state.transition('PANNING');
534
+ matrix.e -= deltaX;
535
+ matrix.f -= deltaY;
536
+ updateViewport(pluginContext, domEvent, true);
373
537
  }
374
538
 
375
539
  const MAX_SCALE_MULTIPLIER = 2.0;
@@ -469,4 +633,4 @@ const presetZoomablePlugin = definePlugin({
469
633
  }
470
634
  });
471
635
 
472
- export { presetColorPlugin, presetDragElementPlugin, presetHighlightPlugin, presetScalePlugin, presetZoomablePlugin };
636
+ export { presetColorPlugin, presetDragElementPlugin, presetHighlightPlugin, presetMenuPlugin, presetScalePlugin, presetZoomablePlugin };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "squarified",
3
- "version": "0.4.2",
3
+ "version": "0.5.0",
4
4
  "description": "squarified tree map",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
@@ -34,7 +34,6 @@
34
34
  "author": "Kanno",
35
35
  "license": "MIT",
36
36
  "devDependencies": {
37
- "@microsoft/api-extractor": "^7.52.3",
38
37
  "@swc/core": "^1.11.29",
39
38
  "@types/markdown-it": "^14.1.2",
40
39
  "@types/node": "^22.7.4",
@@ -57,7 +56,7 @@
57
56
  "tinyglobby": "^0.2.13",
58
57
  "tsx": "^4.19.2",
59
58
  "typescript": "^5.7.3",
60
- "vite-bundle-analyzer": "^0.21.0"
59
+ "vite-bundle-analyzer": "^0.22.0"
61
60
  },
62
61
  "pnpm": {
63
62
  "overrides": {