@antv/infographic 0.2.13 → 0.2.14

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.
Files changed (186) hide show
  1. package/dist/infographic.min.js +123 -116
  2. package/dist/infographic.min.js.map +1 -1
  3. package/esm/editor/commands/UpdateOptions.d.ts +4 -4
  4. package/esm/editor/commands/UpdateOptions.js +6 -3
  5. package/esm/editor/editor.d.ts +5 -1
  6. package/esm/editor/editor.js +16 -5
  7. package/esm/editor/index.d.ts +1 -0
  8. package/esm/editor/index.js +1 -0
  9. package/esm/editor/interactions/brush-select.d.ts +0 -1
  10. package/esm/editor/interactions/brush-select.js +2 -10
  11. package/esm/editor/interactions/drag-canvas.d.ts +35 -0
  12. package/esm/editor/interactions/drag-canvas.js +161 -0
  13. package/esm/editor/interactions/drag-element.js +4 -3
  14. package/esm/editor/interactions/index.d.ts +1 -0
  15. package/esm/editor/interactions/index.js +1 -0
  16. package/esm/editor/interactions/zoom-wheel.d.ts +9 -0
  17. package/esm/editor/interactions/zoom-wheel.js +32 -15
  18. package/esm/editor/managers/index.d.ts +1 -0
  19. package/esm/editor/managers/index.js +1 -0
  20. package/esm/editor/managers/state.d.ts +4 -2
  21. package/esm/editor/managers/state.js +14 -13
  22. package/esm/editor/managers/sync-registry.d.ts +15 -0
  23. package/esm/editor/managers/sync-registry.js +51 -0
  24. package/esm/editor/plugins/{edit-bar/components → components}/button.js +1 -1
  25. package/esm/editor/plugins/{edit-bar/components → components}/color-picker.js +1 -1
  26. package/esm/editor/plugins/{edit-bar/components → components}/icons.d.ts +1 -0
  27. package/esm/editor/plugins/{edit-bar/components → components}/icons.js +1 -0
  28. package/esm/editor/plugins/{edit-bar/components → components}/popover.js +2 -2
  29. package/esm/editor/plugins/{edit-bar/components → components}/select.js +1 -1
  30. package/esm/editor/plugins/core-sync.d.ts +8 -0
  31. package/esm/editor/plugins/core-sync.js +30 -0
  32. package/esm/editor/plugins/edit-bar/edit-items/align-elements.js +1 -1
  33. package/esm/editor/plugins/edit-bar/edit-items/font-align.js +1 -1
  34. package/esm/editor/plugins/edit-bar/edit-items/font-color.js +1 -1
  35. package/esm/editor/plugins/edit-bar/edit-items/font-family.js +1 -1
  36. package/esm/editor/plugins/edit-bar/edit-items/font-size.js +1 -1
  37. package/esm/editor/plugins/edit-bar/edit-items/icon-color.js +1 -1
  38. package/esm/editor/plugins/edit-bar/index.d.ts +2 -2
  39. package/esm/editor/plugins/edit-bar/index.js +1 -1
  40. package/esm/editor/plugins/index.d.ts +2 -0
  41. package/esm/editor/plugins/index.js +2 -0
  42. package/esm/editor/plugins/reset-viewbox.d.ts +33 -0
  43. package/esm/editor/plugins/reset-viewbox.js +186 -0
  44. package/esm/editor/types/editor.d.ts +13 -0
  45. package/esm/editor/types/index.d.ts +1 -0
  46. package/esm/editor/types/interaction.d.ts +9 -0
  47. package/esm/editor/types/state.d.ts +4 -2
  48. package/esm/editor/types/sync.d.ts +26 -0
  49. package/esm/editor/types/sync.js +1 -0
  50. package/esm/editor/utils/data.js +3 -1
  51. package/esm/editor/utils/event.d.ts +1 -0
  52. package/esm/editor/utils/event.js +8 -0
  53. package/esm/editor/utils/index.d.ts +1 -0
  54. package/esm/editor/utils/index.js +1 -0
  55. package/esm/editor/utils/object.d.ts +15 -0
  56. package/esm/editor/utils/object.js +70 -0
  57. package/esm/index.d.ts +4 -3
  58. package/esm/index.js +3 -2
  59. package/esm/options/types.d.ts +1 -0
  60. package/esm/runtime/options.js +7 -2
  61. package/esm/templates/built-in.js +24 -0
  62. package/esm/utils/measure-text.js +9 -6
  63. package/esm/utils/padding.d.ts +1 -0
  64. package/esm/utils/padding.js +6 -2
  65. package/esm/version.d.ts +1 -1
  66. package/esm/version.js +1 -1
  67. package/lib/editor/commands/UpdateOptions.d.ts +4 -4
  68. package/lib/editor/commands/UpdateOptions.js +6 -3
  69. package/lib/editor/editor.d.ts +5 -1
  70. package/lib/editor/editor.js +16 -5
  71. package/lib/editor/index.d.ts +1 -0
  72. package/lib/editor/index.js +1 -0
  73. package/lib/editor/interactions/brush-select.d.ts +0 -1
  74. package/lib/editor/interactions/brush-select.js +1 -9
  75. package/lib/editor/interactions/drag-canvas.d.ts +35 -0
  76. package/lib/editor/interactions/drag-canvas.js +165 -0
  77. package/lib/editor/interactions/drag-element.js +4 -3
  78. package/lib/editor/interactions/index.d.ts +1 -0
  79. package/lib/editor/interactions/index.js +3 -1
  80. package/lib/editor/interactions/zoom-wheel.d.ts +9 -0
  81. package/lib/editor/interactions/zoom-wheel.js +32 -15
  82. package/lib/editor/managers/index.d.ts +1 -0
  83. package/lib/editor/managers/index.js +1 -0
  84. package/lib/editor/managers/state.d.ts +4 -2
  85. package/lib/editor/managers/state.js +12 -11
  86. package/lib/editor/managers/sync-registry.d.ts +15 -0
  87. package/lib/editor/managers/sync-registry.js +55 -0
  88. package/lib/editor/plugins/{edit-bar/components → components}/button.js +1 -1
  89. package/lib/editor/plugins/{edit-bar/components → components}/color-picker.js +1 -1
  90. package/lib/editor/plugins/{edit-bar/components → components}/icons.d.ts +1 -0
  91. package/lib/editor/plugins/{edit-bar/components → components}/icons.js +2 -1
  92. package/lib/editor/plugins/{edit-bar/components → components}/popover.js +2 -2
  93. package/lib/editor/plugins/{edit-bar/components → components}/select.js +1 -1
  94. package/lib/editor/plugins/core-sync.d.ts +8 -0
  95. package/lib/editor/plugins/core-sync.js +34 -0
  96. package/lib/editor/plugins/edit-bar/edit-items/align-elements.js +1 -1
  97. package/lib/editor/plugins/edit-bar/edit-items/font-align.js +1 -1
  98. package/lib/editor/plugins/edit-bar/edit-items/font-color.js +1 -1
  99. package/lib/editor/plugins/edit-bar/edit-items/font-family.js +1 -1
  100. package/lib/editor/plugins/edit-bar/edit-items/font-size.js +1 -1
  101. package/lib/editor/plugins/edit-bar/edit-items/icon-color.js +1 -1
  102. package/lib/editor/plugins/edit-bar/index.d.ts +2 -2
  103. package/lib/editor/plugins/edit-bar/index.js +1 -1
  104. package/lib/editor/plugins/index.d.ts +2 -0
  105. package/lib/editor/plugins/index.js +5 -1
  106. package/lib/editor/plugins/reset-viewbox.d.ts +33 -0
  107. package/lib/editor/plugins/reset-viewbox.js +190 -0
  108. package/lib/editor/types/editor.d.ts +13 -0
  109. package/lib/editor/types/index.d.ts +1 -0
  110. package/lib/editor/types/interaction.d.ts +9 -0
  111. package/lib/editor/types/state.d.ts +4 -2
  112. package/lib/editor/types/sync.d.ts +26 -0
  113. package/lib/editor/types/sync.js +2 -0
  114. package/lib/editor/utils/data.js +3 -1
  115. package/lib/editor/utils/event.d.ts +1 -0
  116. package/lib/editor/utils/event.js +9 -0
  117. package/lib/editor/utils/index.d.ts +1 -0
  118. package/lib/editor/utils/index.js +1 -0
  119. package/lib/editor/utils/object.d.ts +15 -0
  120. package/lib/editor/utils/object.js +73 -0
  121. package/lib/index.d.ts +4 -3
  122. package/lib/index.js +9 -2
  123. package/lib/options/types.d.ts +1 -0
  124. package/lib/runtime/options.js +6 -1
  125. package/lib/templates/built-in.js +24 -0
  126. package/lib/utils/measure-text.js +9 -6
  127. package/lib/utils/padding.d.ts +1 -0
  128. package/lib/utils/padding.js +7 -2
  129. package/lib/version.d.ts +1 -1
  130. package/lib/version.js +1 -1
  131. package/package.json +1 -1
  132. package/src/editor/commands/UpdateOptions.ts +11 -6
  133. package/src/editor/editor.ts +26 -5
  134. package/src/editor/index.ts +1 -0
  135. package/src/editor/interactions/brush-select.ts +2 -8
  136. package/src/editor/interactions/drag-canvas.ts +203 -0
  137. package/src/editor/interactions/drag-element.ts +5 -4
  138. package/src/editor/interactions/index.ts +1 -0
  139. package/src/editor/interactions/zoom-wheel.ts +49 -13
  140. package/src/editor/managers/index.ts +1 -0
  141. package/src/editor/managers/state.ts +21 -21
  142. package/src/editor/managers/sync-registry.ts +65 -0
  143. package/src/editor/plugins/{edit-bar/components → components}/button.ts +1 -1
  144. package/src/editor/plugins/{edit-bar/components → components}/color-picker.ts +1 -1
  145. package/src/editor/plugins/{edit-bar/components → components}/icons.ts +4 -0
  146. package/src/editor/plugins/{edit-bar/components → components}/popover.ts +2 -2
  147. package/src/editor/plugins/{edit-bar/components → components}/select.ts +1 -1
  148. package/src/editor/plugins/core-sync.ts +44 -0
  149. package/src/editor/plugins/edit-bar/edit-items/align-elements.ts +2 -2
  150. package/src/editor/plugins/edit-bar/edit-items/font-align.ts +1 -1
  151. package/src/editor/plugins/edit-bar/edit-items/font-color.ts +1 -1
  152. package/src/editor/plugins/edit-bar/edit-items/font-family.ts +1 -1
  153. package/src/editor/plugins/edit-bar/edit-items/font-size.ts +3 -3
  154. package/src/editor/plugins/edit-bar/edit-items/icon-color.ts +1 -1
  155. package/src/editor/plugins/edit-bar/index.ts +2 -2
  156. package/src/editor/plugins/index.ts +2 -0
  157. package/src/editor/plugins/reset-viewbox.ts +258 -0
  158. package/src/editor/types/editor.ts +17 -0
  159. package/src/editor/types/index.ts +1 -0
  160. package/src/editor/types/interaction.ts +31 -0
  161. package/src/editor/types/state.ts +14 -2
  162. package/src/editor/types/sync.ts +31 -0
  163. package/src/editor/utils/data.ts +2 -1
  164. package/src/editor/utils/event.ts +7 -0
  165. package/src/editor/utils/index.ts +1 -0
  166. package/src/editor/utils/object.ts +106 -0
  167. package/src/index.ts +26 -2
  168. package/src/options/types.ts +4 -0
  169. package/src/runtime/options.ts +8 -1
  170. package/src/templates/built-in.ts +28 -0
  171. package/src/utils/measure-text.ts +6 -6
  172. package/src/utils/padding.ts +10 -2
  173. package/src/version.ts +1 -1
  174. /package/esm/editor/plugins/{edit-bar/components → components}/button.d.ts +0 -0
  175. /package/esm/editor/plugins/{edit-bar/components → components}/color-picker.d.ts +0 -0
  176. /package/esm/editor/plugins/{edit-bar/components → components}/index.d.ts +0 -0
  177. /package/esm/editor/plugins/{edit-bar/components → components}/index.js +0 -0
  178. /package/esm/editor/plugins/{edit-bar/components → components}/popover.d.ts +0 -0
  179. /package/esm/editor/plugins/{edit-bar/components → components}/select.d.ts +0 -0
  180. /package/lib/editor/plugins/{edit-bar/components → components}/button.d.ts +0 -0
  181. /package/lib/editor/plugins/{edit-bar/components → components}/color-picker.d.ts +0 -0
  182. /package/lib/editor/plugins/{edit-bar/components → components}/index.d.ts +0 -0
  183. /package/lib/editor/plugins/{edit-bar/components → components}/index.js +0 -0
  184. /package/lib/editor/plugins/{edit-bar/components → components}/popover.d.ts +0 -0
  185. /package/lib/editor/plugins/{edit-bar/components → components}/select.d.ts +0 -0
  186. /package/src/editor/plugins/{edit-bar/components → components}/index.ts +0 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@antv/infographic",
3
- "version": "0.2.13",
3
+ "version": "0.2.14",
4
4
  "description": "An Infographic Generation and Rendering Framework, bring words to life!",
5
5
  "keywords": [
6
6
  "antv",
@@ -1,18 +1,23 @@
1
- import type { ParsedInfographicOptions } from '../../options';
1
+ import type { UpdatableInfographicOptions } from '../../options';
2
2
  import type { ICommand, IStateManager } from '../types';
3
3
 
4
4
  export class UpdateOptionsCommand implements ICommand {
5
5
  constructor(
6
- private options: Partial<ParsedInfographicOptions>,
7
- private original?: ParsedInfographicOptions,
6
+ private options: UpdatableInfographicOptions,
7
+ private original?: UpdatableInfographicOptions,
8
8
  ) {}
9
9
 
10
10
  async apply(state: IStateManager) {
11
- const prev = state.getOptions();
12
11
  if (!this.original) {
13
- this.original = prev;
12
+ const prev = state.getOptions();
13
+ this.original = {};
14
+ (
15
+ Object.keys(this.options) as Array<keyof UpdatableInfographicOptions>
16
+ ).forEach((key) => {
17
+ (this.original as any)[key] = prev[key];
18
+ });
14
19
  }
15
- state.updateOptions({ ...prev, ...this.options });
20
+ state.updateOptions(this.options);
16
21
  }
17
22
 
18
23
  async undo(state: IStateManager) {
@@ -6,11 +6,15 @@ import {
6
6
  PluginManager,
7
7
  StateManager,
8
8
  } from './managers';
9
+ import { SyncRegistry } from './managers/sync-registry';
10
+ import { CoreSyncPlugin } from './plugins';
9
11
  import type {
10
12
  ICommandManager,
11
13
  IEditor,
12
14
  IPluginManager,
13
15
  IStateManager,
16
+ ISyncRegistry,
17
+ SyncHandler,
14
18
  } from './types';
15
19
 
16
20
  export class Editor implements IEditor {
@@ -18,6 +22,7 @@ export class Editor implements IEditor {
18
22
  commander: ICommandManager;
19
23
  plugin: IPluginManager;
20
24
  interaction: InteractionManager;
25
+ syncRegistry: ISyncRegistry;
21
26
 
22
27
  constructor(
23
28
  private emitter: IEventEmitter,
@@ -34,6 +39,14 @@ export class Editor implements IEditor {
34
39
  const plugin = new PluginManager();
35
40
  const interaction = new InteractionManager();
36
41
 
42
+ const syncRegistry = new SyncRegistry(() => state.getOptions());
43
+
44
+ this.commander = commander;
45
+ this.state = state;
46
+ this.plugin = plugin;
47
+ this.interaction = interaction;
48
+ this.syncRegistry = syncRegistry;
49
+
37
50
  commander.init({ state, emitter });
38
51
  state.init({
39
52
  emitter,
@@ -41,6 +54,10 @@ export class Editor implements IEditor {
41
54
  commander,
42
55
  options,
43
56
  });
57
+ // Load core plugin: CoreSyncPlugin (handles viewBox/padding sync)
58
+ const corePlugin = new CoreSyncPlugin();
59
+ const userPlugins = options.plugins || [];
60
+
44
61
  plugin.init(
45
62
  {
46
63
  emitter,
@@ -48,7 +65,7 @@ export class Editor implements IEditor {
48
65
  commander,
49
66
  state,
50
67
  },
51
- options.plugins,
68
+ [corePlugin, ...userPlugins],
52
69
  );
53
70
  interaction.init({
54
71
  emitter,
@@ -57,11 +74,14 @@ export class Editor implements IEditor {
57
74
  state,
58
75
  interactions: options.interactions,
59
76
  });
77
+ }
60
78
 
61
- this.commander = commander;
62
- this.state = state;
63
- this.plugin = plugin;
64
- this.interaction = interaction;
79
+ registerSync(
80
+ path: string,
81
+ handler: SyncHandler,
82
+ options?: { immediate?: boolean },
83
+ ) {
84
+ return this.syncRegistry.register(path, handler, options);
65
85
  }
66
86
 
67
87
  getDocument() {
@@ -74,5 +94,6 @@ export class Editor implements IEditor {
74
94
  this.plugin.destroy();
75
95
  this.commander.destroy();
76
96
  this.state.destroy();
97
+ this.syncRegistry.destroy();
77
98
  }
78
99
  }
@@ -1,3 +1,4 @@
1
+ export * from './commands';
1
2
  export { Editor } from './editor';
2
3
  export * from './interactions';
3
4
  export * from './plugins';
@@ -5,6 +5,7 @@ import {
5
5
  getElementViewportBounds,
6
6
  getEventTarget,
7
7
  getSelectableTarget,
8
+ isTextSelectionTarget,
8
9
  } from '../utils';
9
10
  import { Interaction } from './base';
10
11
 
@@ -38,7 +39,7 @@ export class BrushSelect extends Interaction implements IInteraction {
38
39
  private handleStart = (event: PointerEvent) => {
39
40
  if (!this.interaction.isActive()) return;
40
41
  if (event.button !== 0) return;
41
- if (this.isTextSelectionTarget(event.target)) return;
42
+ if (isTextSelectionTarget(event.target)) return;
42
43
  if (this.hasElementAtStart(event.target)) return;
43
44
 
44
45
  this.interaction.executeExclusiveInteraction(
@@ -194,11 +195,4 @@ export class BrushSelect extends Interaction implements IInteraction {
194
195
  if (getEventTarget(target as unknown as SVGElement)) return true;
195
196
  return Boolean(target.closest?.('[data-element-type]'));
196
197
  }
197
-
198
- private isTextSelectionTarget(target: EventTarget | null) {
199
- if (!(target instanceof HTMLElement)) return false;
200
- if (target.isContentEditable) return true;
201
- const tag = target.tagName.toLowerCase();
202
- return tag === 'input' || tag === 'textarea';
203
- }
204
198
  }
@@ -0,0 +1,203 @@
1
+ import { getViewBox, viewBoxToString } from '../../utils';
2
+ import { UpdateOptionsCommand } from '../commands';
3
+ import { IInteraction, InteractionInitOptions, KeyCode } from '../types';
4
+ import { clientToViewport, isTextSelectionTarget } from '../utils';
5
+ import { Interaction } from './base';
6
+
7
+ type CursorType = 'grab' | 'grabbing' | 'default';
8
+ export interface DragCanvasOptions {
9
+ trigger?: KeyCode[];
10
+ }
11
+
12
+ export class DragCanvas extends Interaction implements IInteraction {
13
+ name = 'drag-canvas';
14
+
15
+ /**
16
+ * 触发交互的按键代码。
17
+ * 参考标准的 KeyboardEvent.code 值:
18
+ * https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/code/code_values
19
+ * @default ['Space']
20
+ */
21
+ public trigger: KeyCode[] = ['Space'];
22
+
23
+ constructor(options?: DragCanvasOptions) {
24
+ super();
25
+ if (options?.trigger) {
26
+ this.trigger = options.trigger;
27
+ }
28
+ }
29
+
30
+ private isTriggerPressed = false;
31
+
32
+ private pointerId?: number;
33
+ private startPoint?: DOMPoint;
34
+
35
+ private document!: SVGSVGElement;
36
+
37
+ private startViewBoxString?: string;
38
+
39
+ private completeInteraction?: () => void;
40
+
41
+ // 防止组件快捷键侵入性过强
42
+ private isHovering = false;
43
+
44
+ init(options: InteractionInitOptions): void {
45
+ super.init(options);
46
+ this.document = this.editor.getDocument();
47
+
48
+ this.document.addEventListener('mouseenter', this.onMouseEnter);
49
+ this.document.addEventListener('mouseleave', this.onMouseLeave);
50
+ window.addEventListener('keydown', this.handleKeyDown);
51
+ window.addEventListener('blur', this.handleBlur);
52
+ }
53
+
54
+ destroy(): void {
55
+ window.removeEventListener('keydown', this.handleKeyDown);
56
+ window.removeEventListener('keyup', this.handleKeyUp);
57
+
58
+ this.document.removeEventListener('pointerdown', this.handlePointerDown);
59
+ window.removeEventListener('pointermove', this.handlePointerMove);
60
+ window.removeEventListener('pointerup', this.handlePointerUp);
61
+ window.removeEventListener('pointercancel', this.handlePointerUp);
62
+
63
+ window.removeEventListener('blur', this.handleBlur);
64
+ this.document.removeEventListener('mouseenter', this.onMouseEnter);
65
+ this.document.removeEventListener('mouseleave', this.onMouseLeave);
66
+ }
67
+
68
+ private handleKeyDown = (event: KeyboardEvent) => {
69
+ if (!this.interaction.isActive()) return;
70
+ if (isTextSelectionTarget(event.target)) return;
71
+
72
+ // 增加焦点的判断,防止对空格的preventDefault侵入性过强
73
+ const target = event.target as Element;
74
+ const isBody =
75
+ target === document.body || target === document.documentElement;
76
+ const isEditor = target === this.document || this.document.contains(target);
77
+ if (!isBody && !isEditor) return;
78
+
79
+ if (!this.trigger.includes(event.code)) return;
80
+ if (!this.isHovering && !this.isTriggerPressed) return;
81
+ event.preventDefault();
82
+ event.stopPropagation();
83
+ this.interaction.executeExclusiveInteraction(
84
+ this,
85
+ async () =>
86
+ new Promise<void>((resolve) => {
87
+ this.completeInteraction = resolve;
88
+
89
+ this.isTriggerPressed = true;
90
+ const viewBox = getViewBox(this.document);
91
+
92
+ this.startViewBoxString = viewBoxToString(viewBox);
93
+ this.setCursor('grab');
94
+
95
+ this.document.addEventListener('pointerdown', this.handlePointerDown);
96
+ window.addEventListener('keyup', this.handleKeyUp);
97
+ }),
98
+ );
99
+ };
100
+
101
+ private handlePointerDown = (event: PointerEvent) => {
102
+ if (event.button !== 0) return;
103
+ event.preventDefault();
104
+ event.stopPropagation();
105
+
106
+ const svg = this.document;
107
+ this.startPoint = clientToViewport(svg, event.clientX, event.clientY);
108
+ this.pointerId = event.pointerId;
109
+
110
+ this.setCursor('grabbing');
111
+
112
+ window.addEventListener('pointermove', this.handlePointerMove);
113
+ window.addEventListener('pointerup', this.handlePointerUp);
114
+ window.addEventListener('pointercancel', this.handlePointerUp);
115
+ };
116
+
117
+ private handlePointerMove = (event: PointerEvent) => {
118
+ if (event.pointerId !== this.pointerId || !this.startPoint) return;
119
+ event.preventDefault();
120
+ event.stopPropagation();
121
+
122
+ const svg = this.document;
123
+ const current = clientToViewport(svg, event.clientX, event.clientY);
124
+ const dx = current.x - this.startPoint.x;
125
+ const dy = current.y - this.startPoint.y;
126
+
127
+ const viewBox = getViewBox(svg);
128
+
129
+ const { x, y, width, height } = viewBox;
130
+
131
+ const newX = x - dx;
132
+ const newY = y - dy;
133
+
134
+ this.state.updateOptions({
135
+ viewBox: viewBoxToString({ x: newX, y: newY, width, height }),
136
+ });
137
+ };
138
+
139
+ private handlePointerUp = (event: PointerEvent) => {
140
+ if (event.pointerId !== this.pointerId) return;
141
+
142
+ this.startPoint = undefined;
143
+ this.pointerId = undefined;
144
+ this.setCursor('grab');
145
+ window.removeEventListener('pointermove', this.handlePointerMove);
146
+ window.removeEventListener('pointerup', this.handlePointerUp);
147
+ window.removeEventListener('pointercancel', this.handlePointerUp);
148
+ };
149
+
150
+ private handleKeyUp = (event: KeyboardEvent) => {
151
+ if (!this.trigger.includes(event.code)) return;
152
+ this.stopDrag();
153
+ };
154
+
155
+ private stopDrag = () => {
156
+ if (this.startViewBoxString) {
157
+ const svg = this.document;
158
+ const viewBox = getViewBox(svg);
159
+ const currentViewBoxString = viewBoxToString(viewBox);
160
+
161
+ if (this.startViewBoxString !== currentViewBoxString) {
162
+ const command = new UpdateOptionsCommand(
163
+ { viewBox: currentViewBoxString },
164
+ { viewBox: this.startViewBoxString },
165
+ );
166
+ void this.commander.execute(command);
167
+ }
168
+ }
169
+ this.startViewBoxString = undefined;
170
+
171
+ this.isTriggerPressed = false;
172
+
173
+ this.setCursor('default');
174
+
175
+ this.startPoint = undefined;
176
+ this.pointerId = undefined;
177
+
178
+ window.removeEventListener('keyup', this.handleKeyUp);
179
+
180
+ this.document.removeEventListener('pointerdown', this.handlePointerDown);
181
+ window.removeEventListener('pointermove', this.handlePointerMove);
182
+ window.removeEventListener('pointerup', this.handlePointerUp);
183
+ window.removeEventListener('pointercancel', this.handlePointerUp);
184
+
185
+ this.completeInteraction?.();
186
+ this.completeInteraction = undefined;
187
+ };
188
+
189
+ private setCursor = (behavior: CursorType) => {
190
+ document.body.style.cursor = behavior;
191
+ };
192
+
193
+ private handleBlur = () => {
194
+ this.stopDrag();
195
+ };
196
+
197
+ private onMouseEnter = () => {
198
+ this.isHovering = true;
199
+ };
200
+ private onMouseLeave = () => {
201
+ this.isHovering = false;
202
+ };
203
+ }
@@ -157,10 +157,6 @@ export class DragElement extends Interaction implements IInteraction {
157
157
  if (this.exclusiveStarted) return true;
158
158
  if (!this.startTarget) return false;
159
159
 
160
- if (this.willReplaceSelection) {
161
- this.interaction.select([this.startTarget], 'replace');
162
- }
163
-
164
160
  this.dragItems = this.selectionForDrag
165
161
  .filter((element) => isEditableText(element))
166
162
  .map((element) => this.createDragItem(element))
@@ -173,6 +169,11 @@ export class DragElement extends Interaction implements IInteraction {
173
169
  this,
174
170
  async () =>
175
171
  new Promise<void>((resolve) => {
172
+ // 只有拿到锁之后,才真正执行选中逻辑
173
+ if (this.willReplaceSelection && this.startTarget) {
174
+ this.interaction.select([this.startTarget], 'replace');
175
+ }
176
+
176
177
  this.completeInteraction = resolve;
177
178
  started = true;
178
179
  }),
@@ -2,6 +2,7 @@ export { Interaction } from './base';
2
2
  export { BrushSelect } from './brush-select';
3
3
  export { ClickSelect } from './click-select';
4
4
  export { DblClickEditText } from './dblclick-edit-text';
5
+ export { DragCanvas } from './drag-canvas';
5
6
  export { DragElement } from './drag-element';
6
7
  export { HotkeyHistory } from './hotkey-history';
7
8
  export { SelectHighlight } from './select-highlight';
@@ -9,12 +9,50 @@ import type { IInteraction, InteractionInitOptions } from '../types';
9
9
  import { clientToViewport } from '../utils';
10
10
  import { Interaction } from './base';
11
11
 
12
- const MIN_VIEWBOX_SIZE = 20;
13
- const MAX_VIEWBOX_SIZE = 2000;
12
+ export interface ZoomWheelOptions {
13
+ minViewBoxSize?: number;
14
+ maxViewBoxSize?: number;
15
+ }
16
+
14
17
  const ZOOM_FACTOR = 1.1;
15
18
 
16
19
  export class ZoomWheel extends Interaction implements IInteraction {
17
20
  name = 'zoom-wheel';
21
+ private minViewBoxSize = 20;
22
+ private maxViewBoxSize = 20000;
23
+
24
+ constructor(options?: ZoomWheelOptions) {
25
+ super();
26
+ if (options?.minViewBoxSize !== undefined) {
27
+ this.minViewBoxSize = options.minViewBoxSize;
28
+ }
29
+ if (options?.maxViewBoxSize !== undefined) {
30
+ this.maxViewBoxSize = options.maxViewBoxSize;
31
+ }
32
+ }
33
+
34
+ private initialViewBox: string | null = null;
35
+
36
+ private handleKeyUp = (event: KeyboardEvent) => {
37
+ const isZoomModifierHeld = event.ctrlKey || event.metaKey || event.shiftKey;
38
+
39
+ if (!isZoomModifierHeld && this.initialViewBox) {
40
+ const currentViewBox = viewBoxToString(
41
+ getViewBox(this.editor.getDocument()),
42
+ );
43
+
44
+ if (currentViewBox !== this.initialViewBox) {
45
+ const command = new UpdateOptionsCommand(
46
+ { viewBox: currentViewBox },
47
+ { viewBox: this.initialViewBox },
48
+ );
49
+ void this.commander.execute(command);
50
+ }
51
+
52
+ this.initialViewBox = null;
53
+ document.removeEventListener('keyup', this.handleKeyUp);
54
+ }
55
+ };
18
56
 
19
57
  private wheelListener = (event: WheelEvent) => {
20
58
  if (!this.shouldZoom(event)) return;
@@ -32,19 +70,16 @@ export class ZoomWheel extends Interaction implements IInteraction {
32
70
  const newHeight = height * factor;
33
71
 
34
72
  if (
35
- !inRange(newWidth, MIN_VIEWBOX_SIZE, MAX_VIEWBOX_SIZE) ||
36
- !inRange(newHeight, MIN_VIEWBOX_SIZE, MAX_VIEWBOX_SIZE)
73
+ !inRange(newWidth, this.minViewBoxSize, this.maxViewBoxSize) ||
74
+ !inRange(newHeight, this.minViewBoxSize, this.maxViewBoxSize)
37
75
  )
38
76
  return;
39
77
 
40
- // TODO: Remove after implementing the reset UI plugin
41
- if ((event.ctrlKey || event.metaKey) && event.shiftKey) {
42
- const command = new UpdateOptionsCommand({
43
- viewBox: undefined,
44
- });
45
- void this.commander.execute(command);
46
- return;
78
+ if (this.initialViewBox === null) {
79
+ this.initialViewBox = viewBoxToString(viewBox);
80
+ document.addEventListener('keyup', this.handleKeyUp);
47
81
  }
82
+
48
83
  const pivot =
49
84
  (event.ctrlKey || event.metaKey) && !event.shiftKey
50
85
  ? this.getMousePoint(svg, event)
@@ -52,10 +87,9 @@ export class ZoomWheel extends Interaction implements IInteraction {
52
87
 
53
88
  const newViewBox = calculateZoomedViewBox(viewBox, factor, pivot);
54
89
 
55
- const command = new UpdateOptionsCommand({
90
+ this.state.updateOptions({
56
91
  viewBox: viewBoxToString(newViewBox),
57
92
  });
58
- void this.commander.execute(command);
59
93
  };
60
94
 
61
95
  private getMousePoint = (svg: SVGSVGElement, event: WheelEvent) => {
@@ -79,6 +113,7 @@ export class ZoomWheel extends Interaction implements IInteraction {
79
113
 
80
114
  const isMouseZoom = event.ctrlKey || event.metaKey;
81
115
  const isCenterZoom = event.shiftKey;
116
+ if (isMouseZoom && isCenterZoom) return false;
82
117
 
83
118
  return isMouseZoom || isCenterZoom;
84
119
  };
@@ -90,5 +125,6 @@ export class ZoomWheel extends Interaction implements IInteraction {
90
125
 
91
126
  destroy() {
92
127
  document.removeEventListener('wheel', this.wheelListener);
128
+ document.removeEventListener('keyup', this.handleKeyUp);
93
129
  }
94
130
  }
@@ -2,3 +2,4 @@ export * from './command';
2
2
  export * from './interaction';
3
3
  export * from './plugin';
4
4
  export * from './state';
5
+ export * from './sync-registry';
@@ -1,13 +1,10 @@
1
1
  import { ElementTypeEnum } from '../../constants';
2
- import type { ParsedInfographicOptions } from '../../options';
2
+ import type {
3
+ ParsedInfographicOptions,
4
+ UpdatableInfographicOptions,
5
+ } from '../../options';
3
6
  import type { Element, IEventEmitter, ItemDatum } from '../../types';
4
- import {
5
- getDatumByIndexes,
6
- getElementRole,
7
- isIconElement,
8
- parsePadding,
9
- setSVGPadding,
10
- } from '../../utils';
7
+ import { getDatumByIndexes, getElementRole, isIconElement } from '../../utils';
11
8
  import type {
12
9
  ElementProps,
13
10
  ICommandManager,
@@ -17,6 +14,7 @@ import type {
17
14
  StateManagerInitOptions,
18
15
  } from '../types';
19
16
  import {
17
+ applyOptionUpdates,
20
18
  buildItemPath,
21
19
  getChildrenDataByIndexes,
22
20
  getIndexesFromElement,
@@ -56,6 +54,7 @@ export class StateManager implements IStateManager {
56
54
 
57
55
  updateItemDatum(indexes: number[], datum: Partial<ItemDatum>): void {
58
56
  const item = getDatumByIndexes(this.options.data, indexes);
57
+ if (item == null || indexes.length === 0) return;
59
58
  Object.assign(item, datum);
60
59
  this.emitter.emit('options:data:item:update', { indexes, datum });
61
60
  this.emitter.emit('options:change', {
@@ -111,19 +110,19 @@ export class StateManager implements IStateManager {
111
110
  this.updateBuiltInElement(element, props);
112
111
  }
113
112
 
114
- updateOptions(options: ParsedInfographicOptions) {
115
- this.options = { ...this.options, ...options };
116
- if (this.options.viewBox) {
117
- this.editor.getDocument().setAttribute('viewBox', this.options.viewBox);
118
- } else {
119
- this.editor.getDocument().removeAttribute('viewBox');
120
- if (this.options.padding !== undefined) {
121
- setSVGPadding(
122
- this.editor.getDocument(),
123
- parsePadding(this.options.padding),
124
- );
125
- }
126
- }
113
+ updateOptions(
114
+ options: UpdatableInfographicOptions,
115
+ execOptions?: { bubbleUp?: boolean },
116
+ ) {
117
+ const { bubbleUp = false } = execOptions || {};
118
+
119
+ applyOptionUpdates(this.options, options, '', {
120
+ bubbleUp,
121
+ collector: (path, newVal, oldVal) => {
122
+ this.editor.syncRegistry.trigger(path, newVal, oldVal);
123
+ },
124
+ });
125
+
127
126
  this.emitter.emit('options:change', {
128
127
  type: 'options:change',
129
128
  changes: [
@@ -156,6 +155,7 @@ export class StateManager implements IStateManager {
156
155
  const indexes = isItemElement ? getIndexesFromElement(element) : undefined;
157
156
  if (isItemElement) {
158
157
  const datum = getDatumByIndexes(data, indexes!);
158
+ if (datum == null) return;
159
159
  const key = role.replace('item-', '');
160
160
  datum.attributes ||= {};
161
161
  datum.attributes[key] ||= {};
@@ -0,0 +1,65 @@
1
+ import { get } from 'lodash-es';
2
+ import { ISyncRegistry, SyncHandler } from '../types';
3
+
4
+ type OptionsGetter = () => any;
5
+
6
+ export class SyncRegistry implements ISyncRegistry {
7
+ private handlers = new Map<string, Set<SyncHandler>>();
8
+ // lock to prevent recursive updates
9
+ private isDispatching = false;
10
+ private isDestroyed = false;
11
+
12
+ constructor(private getOptions: OptionsGetter) {}
13
+
14
+ register(
15
+ path: string,
16
+ handler: SyncHandler,
17
+ options?: { immediate?: boolean },
18
+ ): () => void {
19
+ if (!this.handlers.has(path)) {
20
+ this.handlers.set(path, new Set());
21
+ }
22
+ this.handlers.get(path)!.add(handler);
23
+
24
+ if (options?.immediate) {
25
+ const currentVal = get(this.getOptions(), path);
26
+ handler(currentVal, undefined);
27
+ }
28
+
29
+ return () => {
30
+ const set = this.handlers.get(path);
31
+ if (set) {
32
+ set.delete(handler);
33
+ if (set.size === 0) {
34
+ this.handlers.delete(path);
35
+ }
36
+ }
37
+ };
38
+ }
39
+ trigger(path: string, newVal: any, oldVal: any): void {
40
+ if (this.isDestroyed || this.isDispatching) {
41
+ if (this.isDispatching) {
42
+ console.warn(
43
+ `[SyncRegistry] Recursive update detected on ${path}. Skipped to prevent loop.`,
44
+ );
45
+ }
46
+ return;
47
+ }
48
+
49
+ const handlers = this.handlers.get(path);
50
+
51
+ if (handlers) {
52
+ this.isDispatching = true;
53
+ try {
54
+ handlers.forEach((fn) => fn(newVal, oldVal));
55
+ } finally {
56
+ this.isDispatching = false;
57
+ }
58
+ }
59
+ }
60
+
61
+ destroy() {
62
+ this.isDestroyed = true;
63
+ this.handlers.clear();
64
+ }
65
+ }
@@ -1,4 +1,4 @@
1
- import { injectStyleOnce } from '../../../../utils';
1
+ import { injectStyleOnce } from '../../../utils';
2
2
  import type { Icon } from './icons';
3
3
 
4
4
  export type Button = HTMLButtonElement & IconButtonHandle;
@@ -1,6 +1,6 @@
1
1
  import { parse } from 'culori';
2
2
 
3
- import { injectStyleOnce } from '../../../../utils';
3
+ import { injectStyleOnce } from '../../../utils';
4
4
 
5
5
  export type ColorPickerProps = {
6
6
  value?: string;
@@ -54,6 +54,10 @@ export const TEXT_ICONS = {
54
54
  ),
55
55
  };
56
56
 
57
+ export const RESET_ICON = createIcon(
58
+ `<path d="M502.714987 58.258904l-126.531056-54.617723a52.797131 52.797131 0 0 0-41.873587 96.855428A447.865322 447.865322 0 0 0 392.02307 946.707184a61.535967 61.535967 0 0 0 13.83649 1.820591 52.797131 52.797131 0 0 0 13.65443-103.773672 342.453118 342.453118 0 0 1-31.678278-651.771485l-8.374718 19.480321a52.615072 52.615072 0 0 0 27.855039 69.182448 51.522718 51.522718 0 0 0 20.572675 4.369418A52.797131 52.797131 0 0 0 476.498481 254.882703L530.205907 127.441352a52.979191 52.979191 0 0 0-27.49092-69.182448zM962.960326 509.765407A448.775617 448.775617 0 0 0 643.992829 68.090094a52.797131 52.797131 0 1 0-30.403866 101.042786A342.635177 342.635177 0 0 1 674.578753 801.059925a52.615072 52.615072 0 0 0-92.30395-50.612422l-71.913335 117.246043a52.433013 52.433013 0 0 0 17.295612 72.82363l117.063985 72.823629a52.797131 52.797131 0 1 0 54.617722-89.755123l-16.021198-10.013249A448.593558 448.593558 0 0 0 962.960326 509.765407z" p-id="1630"/>`,
59
+ );
60
+
57
61
  export const ELEMENT_ICONS = {
58
62
  align: createIcon(
59
63
  `<path d="M555.188 715.059c17.673 0 32 14.327 32 32V875c0 17.673-14.327 32-32 32H171c-17.673 0-32-14.327-32-32V747.059c0-17.673 14.327-32 32-32z m-32.001 63.999H203V843h320.187v-63.942zM854 416.529c17.673 0 32 14.327 32 32v127.942c0 17.673-14.327 32-32 32H171c-17.673 0-32-14.327-32-32V448.529c0-17.673 14.327-32 32-32z m-32 64H203v63.941h619v-63.941zM683.25 118c17.673 0 32 14.327 32 32v127.941c0 17.673-14.327 32-32 32H171c-17.673 0-32-14.327-32-32V150c0-17.673 14.327-32 32-32z m-32 64H203v63.941h448.25V182z"></path>`,
@@ -1,5 +1,5 @@
1
- import { COMPONENT_ROLE } from '../../../../constants';
2
- import { injectStyleOnce, setElementRole } from '../../../../utils';
1
+ import { COMPONENT_ROLE } from '../../../constants';
2
+ import { injectStyleOnce, setElementRole } from '../../../utils';
3
3
 
4
4
  export type PopoverPlacement = 'top' | 'bottom' | 'left' | 'right';
5
5
  export type PopoverPlacementPreference = PopoverPlacement | PopoverPlacement[];