@antv/infographic 0.2.13 → 0.2.15
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 +12 -5
- package/README.zh-CN.md +12 -5
- package/dist/infographic.min.js +180 -171
- package/dist/infographic.min.js.map +1 -1
- package/esm/designs/structures/index.d.ts +1 -0
- package/esm/designs/structures/index.js +1 -0
- package/esm/designs/structures/relation-dagre-flow.js +4 -139
- package/esm/designs/structures/sequence-interaction.d.ts +54 -0
- package/esm/designs/structures/sequence-interaction.js +440 -0
- package/esm/designs/utils/geometry.d.ts +44 -0
- package/esm/designs/utils/geometry.js +244 -0
- package/esm/designs/utils/index.d.ts +1 -0
- package/esm/designs/utils/index.js +1 -0
- package/esm/editor/commands/UpdateOptions.d.ts +4 -4
- package/esm/editor/commands/UpdateOptions.js +6 -3
- package/esm/editor/editor.d.ts +5 -1
- package/esm/editor/editor.js +16 -5
- package/esm/editor/index.d.ts +1 -0
- package/esm/editor/index.js +1 -0
- package/esm/editor/interactions/brush-select.d.ts +0 -1
- package/esm/editor/interactions/brush-select.js +2 -10
- package/esm/editor/interactions/drag-canvas.d.ts +35 -0
- package/esm/editor/interactions/drag-canvas.js +161 -0
- package/esm/editor/interactions/drag-element.js +4 -3
- package/esm/editor/interactions/index.d.ts +1 -0
- package/esm/editor/interactions/index.js +1 -0
- package/esm/editor/interactions/zoom-wheel.d.ts +9 -0
- package/esm/editor/interactions/zoom-wheel.js +32 -15
- package/esm/editor/managers/index.d.ts +1 -0
- package/esm/editor/managers/index.js +1 -0
- package/esm/editor/managers/state.d.ts +4 -2
- package/esm/editor/managers/state.js +14 -13
- package/esm/editor/managers/sync-registry.d.ts +16 -0
- package/esm/editor/managers/sync-registry.js +51 -0
- package/esm/editor/plugins/{edit-bar/components → components}/button.js +1 -1
- package/esm/editor/plugins/{edit-bar/components → components}/color-picker.js +1 -1
- package/esm/editor/plugins/{edit-bar/components → components}/icons.d.ts +1 -0
- package/esm/editor/plugins/{edit-bar/components → components}/icons.js +1 -0
- package/esm/editor/plugins/{edit-bar/components → components}/popover.js +2 -2
- package/esm/editor/plugins/{edit-bar/components → components}/select.js +1 -1
- package/esm/editor/plugins/core-sync.d.ts +8 -0
- package/esm/editor/plugins/core-sync.js +30 -0
- package/esm/editor/plugins/edit-bar/edit-items/align-elements.js +1 -1
- package/esm/editor/plugins/edit-bar/edit-items/font-align.js +1 -1
- package/esm/editor/plugins/edit-bar/edit-items/font-color.js +1 -1
- package/esm/editor/plugins/edit-bar/edit-items/font-family.js +1 -1
- package/esm/editor/plugins/edit-bar/edit-items/font-size.js +1 -1
- package/esm/editor/plugins/edit-bar/edit-items/icon-color.js +1 -1
- package/esm/editor/plugins/edit-bar/index.d.ts +2 -2
- package/esm/editor/plugins/edit-bar/index.js +1 -1
- package/esm/editor/plugins/index.d.ts +2 -0
- package/esm/editor/plugins/index.js +2 -0
- package/esm/editor/plugins/reset-viewbox.d.ts +33 -0
- package/esm/editor/plugins/reset-viewbox.js +186 -0
- package/esm/editor/types/editor.d.ts +14 -0
- package/esm/editor/types/index.d.ts +1 -0
- package/esm/editor/types/interaction.d.ts +9 -0
- package/esm/editor/types/state.d.ts +4 -2
- package/esm/editor/types/sync.d.ts +27 -0
- package/esm/editor/types/sync.js +1 -0
- package/esm/editor/utils/data.js +3 -1
- package/esm/editor/utils/event.d.ts +1 -0
- package/esm/editor/utils/event.js +8 -0
- package/esm/editor/utils/index.d.ts +1 -0
- package/esm/editor/utils/index.js +1 -0
- package/esm/editor/utils/object.d.ts +15 -0
- package/esm/editor/utils/object.js +77 -0
- package/esm/index.d.ts +4 -3
- package/esm/index.js +3 -2
- package/esm/options/types.d.ts +7 -0
- package/esm/runtime/Infographic.js +20 -7
- package/esm/runtime/options.js +7 -2
- package/esm/syntax/index.js +40 -20
- package/esm/syntax/relations.js +26 -2
- package/esm/syntax/schema.js +1 -0
- package/esm/templates/built-in.js +27 -2
- package/esm/templates/sequence-interaction.d.ts +2 -0
- package/esm/templates/sequence-interaction.js +76 -0
- package/esm/types/data.d.ts +1 -0
- package/esm/utils/index.d.ts +1 -0
- package/esm/utils/index.js +1 -0
- package/esm/utils/measure-text.js +40 -9
- package/esm/utils/padding.d.ts +1 -0
- package/esm/utils/padding.js +6 -2
- package/esm/utils/types.d.ts +16 -0
- package/esm/utils/types.js +12 -0
- package/esm/version.d.ts +1 -1
- package/esm/version.js +1 -1
- package/lib/designs/structures/index.d.ts +1 -0
- package/lib/designs/structures/index.js +1 -0
- package/lib/designs/structures/relation-dagre-flow.js +5 -140
- package/lib/designs/structures/sequence-interaction.d.ts +54 -0
- package/lib/designs/structures/sequence-interaction.js +444 -0
- package/lib/designs/utils/geometry.d.ts +44 -0
- package/lib/designs/utils/geometry.js +256 -0
- package/lib/designs/utils/index.d.ts +1 -0
- package/lib/designs/utils/index.js +1 -0
- package/lib/editor/commands/UpdateOptions.d.ts +4 -4
- package/lib/editor/commands/UpdateOptions.js +6 -3
- package/lib/editor/editor.d.ts +5 -1
- package/lib/editor/editor.js +16 -5
- package/lib/editor/index.d.ts +1 -0
- package/lib/editor/index.js +1 -0
- package/lib/editor/interactions/brush-select.d.ts +0 -1
- package/lib/editor/interactions/brush-select.js +1 -9
- package/lib/editor/interactions/drag-canvas.d.ts +35 -0
- package/lib/editor/interactions/drag-canvas.js +165 -0
- package/lib/editor/interactions/drag-element.js +4 -3
- package/lib/editor/interactions/index.d.ts +1 -0
- package/lib/editor/interactions/index.js +3 -1
- package/lib/editor/interactions/zoom-wheel.d.ts +9 -0
- package/lib/editor/interactions/zoom-wheel.js +32 -15
- package/lib/editor/managers/index.d.ts +1 -0
- package/lib/editor/managers/index.js +1 -0
- package/lib/editor/managers/state.d.ts +4 -2
- package/lib/editor/managers/state.js +12 -11
- package/lib/editor/managers/sync-registry.d.ts +16 -0
- package/lib/editor/managers/sync-registry.js +55 -0
- package/lib/editor/plugins/{edit-bar/components → components}/button.js +1 -1
- package/lib/editor/plugins/{edit-bar/components → components}/color-picker.js +1 -1
- package/lib/editor/plugins/{edit-bar/components → components}/icons.d.ts +1 -0
- package/lib/editor/plugins/{edit-bar/components → components}/icons.js +2 -1
- package/lib/editor/plugins/{edit-bar/components → components}/popover.js +2 -2
- package/lib/editor/plugins/{edit-bar/components → components}/select.js +1 -1
- package/lib/editor/plugins/core-sync.d.ts +8 -0
- package/lib/editor/plugins/core-sync.js +34 -0
- package/lib/editor/plugins/edit-bar/edit-items/align-elements.js +1 -1
- package/lib/editor/plugins/edit-bar/edit-items/font-align.js +1 -1
- package/lib/editor/plugins/edit-bar/edit-items/font-color.js +1 -1
- package/lib/editor/plugins/edit-bar/edit-items/font-family.js +1 -1
- package/lib/editor/plugins/edit-bar/edit-items/font-size.js +1 -1
- package/lib/editor/plugins/edit-bar/edit-items/icon-color.js +1 -1
- package/lib/editor/plugins/edit-bar/index.d.ts +2 -2
- package/lib/editor/plugins/edit-bar/index.js +1 -1
- package/lib/editor/plugins/index.d.ts +2 -0
- package/lib/editor/plugins/index.js +5 -1
- package/lib/editor/plugins/reset-viewbox.d.ts +33 -0
- package/lib/editor/plugins/reset-viewbox.js +190 -0
- package/lib/editor/types/editor.d.ts +14 -0
- package/lib/editor/types/index.d.ts +1 -0
- package/lib/editor/types/interaction.d.ts +9 -0
- package/lib/editor/types/state.d.ts +4 -2
- package/lib/editor/types/sync.d.ts +27 -0
- package/lib/editor/types/sync.js +2 -0
- package/lib/editor/utils/data.js +3 -1
- package/lib/editor/utils/event.d.ts +1 -0
- package/lib/editor/utils/event.js +9 -0
- package/lib/editor/utils/index.d.ts +1 -0
- package/lib/editor/utils/index.js +1 -0
- package/lib/editor/utils/object.d.ts +15 -0
- package/lib/editor/utils/object.js +80 -0
- package/lib/index.d.ts +4 -3
- package/lib/index.js +9 -2
- package/lib/options/types.d.ts +7 -0
- package/lib/runtime/Infographic.js +19 -6
- package/lib/runtime/options.js +6 -1
- package/lib/syntax/index.js +40 -20
- package/lib/syntax/relations.js +26 -2
- package/lib/syntax/schema.js +1 -0
- package/lib/templates/built-in.js +27 -2
- package/lib/templates/sequence-interaction.d.ts +2 -0
- package/lib/templates/sequence-interaction.js +79 -0
- package/lib/types/data.d.ts +1 -0
- package/lib/utils/index.d.ts +1 -0
- package/lib/utils/index.js +1 -0
- package/lib/utils/measure-text.js +39 -8
- package/lib/utils/padding.d.ts +1 -0
- package/lib/utils/padding.js +7 -2
- package/lib/utils/types.d.ts +16 -0
- package/lib/utils/types.js +13 -0
- package/lib/version.d.ts +1 -1
- package/lib/version.js +1 -1
- package/package.json +1 -1
- package/src/designs/structures/index.ts +1 -0
- package/src/designs/structures/relation-dagre-flow.tsx +14 -178
- package/src/designs/structures/sequence-interaction.tsx +885 -0
- package/src/designs/utils/geometry.tsx +315 -0
- package/src/designs/utils/index.ts +1 -0
- package/src/editor/commands/UpdateOptions.ts +11 -6
- package/src/editor/editor.ts +26 -5
- package/src/editor/index.ts +1 -0
- package/src/editor/interactions/brush-select.ts +2 -8
- package/src/editor/interactions/drag-canvas.ts +203 -0
- package/src/editor/interactions/drag-element.ts +5 -4
- package/src/editor/interactions/index.ts +1 -0
- package/src/editor/interactions/zoom-wheel.ts +49 -13
- package/src/editor/managers/index.ts +1 -0
- package/src/editor/managers/state.ts +21 -21
- package/src/editor/managers/sync-registry.ts +66 -0
- package/src/editor/plugins/{edit-bar/components → components}/button.ts +1 -1
- package/src/editor/plugins/{edit-bar/components → components}/color-picker.ts +1 -1
- package/src/editor/plugins/{edit-bar/components → components}/icons.ts +4 -0
- package/src/editor/plugins/{edit-bar/components → components}/popover.ts +2 -2
- package/src/editor/plugins/{edit-bar/components → components}/select.ts +1 -1
- package/src/editor/plugins/core-sync.ts +44 -0
- package/src/editor/plugins/edit-bar/edit-items/align-elements.ts +2 -2
- package/src/editor/plugins/edit-bar/edit-items/font-align.ts +1 -1
- package/src/editor/plugins/edit-bar/edit-items/font-color.ts +1 -1
- package/src/editor/plugins/edit-bar/edit-items/font-family.ts +1 -1
- package/src/editor/plugins/edit-bar/edit-items/font-size.ts +3 -3
- package/src/editor/plugins/edit-bar/edit-items/icon-color.ts +1 -1
- package/src/editor/plugins/edit-bar/index.ts +2 -2
- package/src/editor/plugins/index.ts +2 -0
- package/src/editor/plugins/reset-viewbox.ts +258 -0
- package/src/editor/types/editor.ts +18 -0
- package/src/editor/types/index.ts +1 -0
- package/src/editor/types/interaction.ts +31 -0
- package/src/editor/types/state.ts +14 -2
- package/src/editor/types/sync.ts +33 -0
- package/src/editor/utils/data.ts +2 -1
- package/src/editor/utils/event.ts +7 -0
- package/src/editor/utils/index.ts +1 -0
- package/src/editor/utils/object.ts +116 -0
- package/src/index.ts +26 -2
- package/src/options/types.ts +11 -0
- package/src/runtime/Infographic.tsx +27 -17
- package/src/runtime/options.ts +8 -1
- package/src/syntax/index.ts +51 -18
- package/src/syntax/relations.ts +29 -2
- package/src/syntax/schema.ts +1 -0
- package/src/templates/built-in.ts +30 -0
- package/src/templates/sequence-interaction.ts +101 -0
- package/src/types/data.ts +1 -0
- package/src/utils/index.ts +1 -0
- package/src/utils/measure-text.ts +41 -9
- package/src/utils/padding.ts +10 -2
- package/src/utils/types.ts +61 -0
- package/src/version.ts +1 -1
- /package/esm/editor/plugins/{edit-bar/components → components}/button.d.ts +0 -0
- /package/esm/editor/plugins/{edit-bar/components → components}/color-picker.d.ts +0 -0
- /package/esm/editor/plugins/{edit-bar/components → components}/index.d.ts +0 -0
- /package/esm/editor/plugins/{edit-bar/components → components}/index.js +0 -0
- /package/esm/editor/plugins/{edit-bar/components → components}/popover.d.ts +0 -0
- /package/esm/editor/plugins/{edit-bar/components → components}/select.d.ts +0 -0
- /package/lib/editor/plugins/{edit-bar/components → components}/button.d.ts +0 -0
- /package/lib/editor/plugins/{edit-bar/components → components}/color-picker.d.ts +0 -0
- /package/lib/editor/plugins/{edit-bar/components → components}/index.d.ts +0 -0
- /package/lib/editor/plugins/{edit-bar/components → components}/index.js +0 -0
- /package/lib/editor/plugins/{edit-bar/components → components}/popover.d.ts +0 -0
- /package/lib/editor/plugins/{edit-bar/components → components}/select.d.ts +0 -0
- /package/src/editor/plugins/{edit-bar/components → components}/index.ts +0 -0
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
import { COMPONENT_ROLE } from '../../constants';
|
|
2
|
+
import {
|
|
3
|
+
getBoundViewBox,
|
|
4
|
+
getViewBox,
|
|
5
|
+
injectStyleOnce,
|
|
6
|
+
parsePadding,
|
|
7
|
+
setElementRole,
|
|
8
|
+
viewBoxToString,
|
|
9
|
+
} from '../../utils';
|
|
10
|
+
import { UpdateOptionsCommand } from '../commands';
|
|
11
|
+
import type { IPlugin, PluginInitOptions } from '../types';
|
|
12
|
+
import { Plugin } from './base';
|
|
13
|
+
import { IconButton } from './components';
|
|
14
|
+
import { RESET_ICON } from './components/icons';
|
|
15
|
+
|
|
16
|
+
const MARGIN_X = 25;
|
|
17
|
+
const MARGIN_Y = 25;
|
|
18
|
+
const BUTTON_SIZE = 40;
|
|
19
|
+
export interface ResetViewBoxOptions {
|
|
20
|
+
style?: Partial<CSSStyleDeclaration>;
|
|
21
|
+
className?: string;
|
|
22
|
+
getContainer?: HTMLElement | (() => HTMLElement);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export class ResetViewBox extends Plugin implements IPlugin {
|
|
26
|
+
name = 'reset-viewBox';
|
|
27
|
+
|
|
28
|
+
private originViewBox!: string;
|
|
29
|
+
private resetButton?: HTMLButtonElement;
|
|
30
|
+
private viewBoxChanged = false;
|
|
31
|
+
private unregisterSync?: () => void;
|
|
32
|
+
|
|
33
|
+
constructor(private options?: ResetViewBoxOptions) {
|
|
34
|
+
super();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
init(options: PluginInitOptions) {
|
|
38
|
+
super.init(options);
|
|
39
|
+
|
|
40
|
+
// Initialize originViewBox
|
|
41
|
+
this.ensureButtonStyle();
|
|
42
|
+
this.updateOriginViewBox();
|
|
43
|
+
|
|
44
|
+
this.unregisterSync = this.editor.registerSync(
|
|
45
|
+
'viewBox',
|
|
46
|
+
this.handleViewBoxChange,
|
|
47
|
+
);
|
|
48
|
+
window.addEventListener('resize', this.handleResize);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
destroy(): void {
|
|
52
|
+
this.unregisterSync?.();
|
|
53
|
+
window.removeEventListener('resize', this.handleResize);
|
|
54
|
+
this.removeButton();
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
private handleResize = () => {
|
|
58
|
+
this.updateOriginViewBox();
|
|
59
|
+
this.updatePosition();
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
private updateOriginViewBox() {
|
|
63
|
+
const svg = this.editor.getDocument();
|
|
64
|
+
// In Node env or before render, fallback to current viewBox attribute
|
|
65
|
+
if (!svg.getBBox) {
|
|
66
|
+
this.originViewBox = viewBoxToString(getViewBox(svg));
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// In Browser, calculate fit
|
|
71
|
+
const { padding } = this.state.getOptions();
|
|
72
|
+
this.originViewBox = getBoundViewBox(svg, parsePadding(padding));
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
private handleViewBoxChange = (viewBox?: string) => {
|
|
76
|
+
const svg = this.editor.getDocument();
|
|
77
|
+
|
|
78
|
+
this.viewBoxChanged = viewBox !== this.originViewBox;
|
|
79
|
+
|
|
80
|
+
if (!this.viewBoxChanged) {
|
|
81
|
+
if (this.resetButton) this.hideButton(this.resetButton);
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
const button = this.getOrCreateResetButton();
|
|
85
|
+
|
|
86
|
+
this.placeButton(button, svg);
|
|
87
|
+
this.showButton(button);
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
protected getOrCreateResetButton = () => {
|
|
91
|
+
if (this.resetButton) return this.resetButton;
|
|
92
|
+
|
|
93
|
+
const { style, className } = this.options || {};
|
|
94
|
+
|
|
95
|
+
const button = IconButton({
|
|
96
|
+
icon: RESET_ICON,
|
|
97
|
+
onClick: this.resetViewBox,
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
button.classList.add(RESET_BUTTON_CLASS);
|
|
101
|
+
|
|
102
|
+
if (className) {
|
|
103
|
+
button.classList.add(className);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (style) {
|
|
107
|
+
Object.assign(button.style, style);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
setElementRole(button, COMPONENT_ROLE);
|
|
111
|
+
|
|
112
|
+
this.resetButton = button;
|
|
113
|
+
|
|
114
|
+
const { getContainer } = this.options || {};
|
|
115
|
+
const resolvedContainer =
|
|
116
|
+
typeof getContainer === 'function' ? getContainer() : getContainer;
|
|
117
|
+
const containerParent = resolvedContainer ?? document.body;
|
|
118
|
+
|
|
119
|
+
containerParent?.appendChild(button);
|
|
120
|
+
|
|
121
|
+
return button;
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
/** Get CSS transform scale of an element */
|
|
125
|
+
private getTransformScale = (
|
|
126
|
+
element: HTMLElement,
|
|
127
|
+
): { scaleX: number; scaleY: number } => {
|
|
128
|
+
const rect = element.getBoundingClientRect();
|
|
129
|
+
const scaleX =
|
|
130
|
+
element.offsetWidth > 0 ? rect.width / element.offsetWidth : 1;
|
|
131
|
+
const scaleY =
|
|
132
|
+
element.offsetHeight > 0 ? rect.height / element.offsetHeight : 1;
|
|
133
|
+
return { scaleX, scaleY };
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
/** Find the nearest stable overflow container */
|
|
137
|
+
private findStableContainer = (svg: SVGSVGElement): HTMLElement | null => {
|
|
138
|
+
let current: Element | null = svg.parentElement;
|
|
139
|
+
while (current) {
|
|
140
|
+
if (current instanceof HTMLElement) {
|
|
141
|
+
const style = window.getComputedStyle(current);
|
|
142
|
+
// Look for overflow container or positioned element as stable reference
|
|
143
|
+
if (
|
|
144
|
+
(style.overflow && style.overflow !== 'visible') ||
|
|
145
|
+
(style.overflowX && style.overflowX !== 'visible') ||
|
|
146
|
+
(style.overflowY && style.overflowY !== 'visible')
|
|
147
|
+
) {
|
|
148
|
+
return current;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
current = current.parentElement;
|
|
152
|
+
}
|
|
153
|
+
return null;
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
private placeButton = (button: HTMLButtonElement, svg: SVGSVGElement) => {
|
|
157
|
+
if (!svg.isConnected) return;
|
|
158
|
+
const svgRect = svg.getBoundingClientRect();
|
|
159
|
+
const offsetParent =
|
|
160
|
+
(button.offsetParent as HTMLElement | null) ??
|
|
161
|
+
(document.documentElement as HTMLElement);
|
|
162
|
+
|
|
163
|
+
// Use stable container bounds to clamp button position when SVG overflows
|
|
164
|
+
const stableContainer = this.findStableContainer(svg);
|
|
165
|
+
const containerRect = stableContainer?.getBoundingClientRect();
|
|
166
|
+
|
|
167
|
+
// Prefer SVG bounds, but clamp to container if SVG overflows
|
|
168
|
+
const effectiveRect = containerRect
|
|
169
|
+
? {
|
|
170
|
+
right: Math.min(svgRect.right, containerRect.right),
|
|
171
|
+
bottom: Math.min(svgRect.bottom, containerRect.bottom),
|
|
172
|
+
}
|
|
173
|
+
: svgRect;
|
|
174
|
+
|
|
175
|
+
if (
|
|
176
|
+
offsetParent === document.body ||
|
|
177
|
+
offsetParent === document.documentElement
|
|
178
|
+
) {
|
|
179
|
+
const scrollX = window.scrollX || document.documentElement.scrollLeft;
|
|
180
|
+
const scrollY = window.scrollY || document.documentElement.scrollTop;
|
|
181
|
+
|
|
182
|
+
const left = scrollX + effectiveRect.right - MARGIN_X - BUTTON_SIZE;
|
|
183
|
+
const top = scrollY + effectiveRect.bottom - MARGIN_Y - BUTTON_SIZE;
|
|
184
|
+
|
|
185
|
+
button.style.left = `${left}px`;
|
|
186
|
+
button.style.top = `${top}px`;
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const parentRect = offsetParent.getBoundingClientRect();
|
|
191
|
+
|
|
192
|
+
// Compensate for parent transform scale
|
|
193
|
+
const { scaleX, scaleY } = this.getTransformScale(offsetParent);
|
|
194
|
+
|
|
195
|
+
// Convert to offsetParent local coordinates
|
|
196
|
+
const left =
|
|
197
|
+
(effectiveRect.right - parentRect.left) / scaleX - MARGIN_X - BUTTON_SIZE;
|
|
198
|
+
const top =
|
|
199
|
+
(effectiveRect.bottom - parentRect.top) / scaleY - MARGIN_Y - BUTTON_SIZE;
|
|
200
|
+
|
|
201
|
+
button.style.left = `${left}px`;
|
|
202
|
+
button.style.top = `${top}px`;
|
|
203
|
+
};
|
|
204
|
+
private updatePosition = () => {
|
|
205
|
+
if (this.resetButton && this.viewBoxChanged) {
|
|
206
|
+
this.placeButton(this.resetButton, this.editor.getDocument());
|
|
207
|
+
}
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
private resetViewBox = () => {
|
|
211
|
+
const command = new UpdateOptionsCommand({
|
|
212
|
+
viewBox: this.originViewBox,
|
|
213
|
+
});
|
|
214
|
+
void this.commander.execute(command);
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
private showButton = (button: HTMLButtonElement) => {
|
|
218
|
+
button.style.display = 'flex';
|
|
219
|
+
button.style.visibility = 'visible';
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
private hideButton = (button: HTMLButtonElement) => {
|
|
223
|
+
button.style.display = 'none';
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
private removeButton = () => {
|
|
227
|
+
this.viewBoxChanged = false;
|
|
228
|
+
this.resetButton?.remove();
|
|
229
|
+
this.resetButton = undefined;
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
private ensureButtonStyle() {
|
|
233
|
+
injectStyleOnce(
|
|
234
|
+
RESET_BUTTON_STYLE_ID,
|
|
235
|
+
`
|
|
236
|
+
button.${RESET_BUTTON_CLASS} {
|
|
237
|
+
visibility: hidden;
|
|
238
|
+
position: absolute;
|
|
239
|
+
display: flex;
|
|
240
|
+
justify-content: center;
|
|
241
|
+
align-items: center;
|
|
242
|
+
width: ${BUTTON_SIZE}px;
|
|
243
|
+
height: ${BUTTON_SIZE}px;
|
|
244
|
+
border-radius: 50%;
|
|
245
|
+
padding: 4px;
|
|
246
|
+
background-color: #fff;
|
|
247
|
+
border: 1px solid #e5e7eb;
|
|
248
|
+
z-index: 1000;
|
|
249
|
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
|
250
|
+
cursor: pointer;
|
|
251
|
+
}
|
|
252
|
+
`,
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
const RESET_BUTTON_CLASS = 'infographic-reset-viewbox-btn';
|
|
258
|
+
const RESET_BUTTON_STYLE_ID = 'infographic-reset-viewbox-btn-style';
|
|
@@ -1,4 +1,22 @@
|
|
|
1
|
+
import type { InfographicOptionPath } from '../../options';
|
|
2
|
+
import type { ICommandManager } from './command';
|
|
3
|
+
import type { IInteractionManager } from './interaction';
|
|
4
|
+
import type { IPluginManager } from './plugin';
|
|
5
|
+
import type { IStateManager } from './state';
|
|
6
|
+
import type { ISyncRegistry, SyncHandler } from './sync';
|
|
7
|
+
|
|
1
8
|
export interface IEditor {
|
|
9
|
+
commander: ICommandManager;
|
|
10
|
+
interaction: IInteractionManager;
|
|
11
|
+
plugin: IPluginManager;
|
|
12
|
+
state: IStateManager;
|
|
13
|
+
syncRegistry: ISyncRegistry;
|
|
14
|
+
|
|
2
15
|
getDocument(): SVGSVGElement;
|
|
16
|
+
registerSync(
|
|
17
|
+
path: InfographicOptionPath | (string & {}),
|
|
18
|
+
handler: SyncHandler,
|
|
19
|
+
options?: { immediate?: boolean },
|
|
20
|
+
): () => void;
|
|
3
21
|
destroy(): void;
|
|
4
22
|
}
|
|
@@ -4,6 +4,32 @@ import type { IEditor } from './editor';
|
|
|
4
4
|
import type { Selection } from './selection';
|
|
5
5
|
import type { IStateManager } from './state';
|
|
6
6
|
|
|
7
|
+
/**
|
|
8
|
+
* Union type of common key codes, using (string & {}) trick to provide completion suggestions without limiting specific strings
|
|
9
|
+
* Reference: https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/code/code_values
|
|
10
|
+
*/
|
|
11
|
+
export type KeyCode =
|
|
12
|
+
| 'Space'
|
|
13
|
+
| 'ShiftLeft'
|
|
14
|
+
| 'ShiftRight'
|
|
15
|
+
| 'ControlLeft'
|
|
16
|
+
| 'ControlRight'
|
|
17
|
+
| 'AltLeft'
|
|
18
|
+
| 'AltRight'
|
|
19
|
+
| 'MetaLeft'
|
|
20
|
+
| 'MetaRight'
|
|
21
|
+
| 'CapsLock'
|
|
22
|
+
| 'Tab'
|
|
23
|
+
| 'Enter'
|
|
24
|
+
| 'Escape'
|
|
25
|
+
| 'ArrowUp'
|
|
26
|
+
| 'ArrowDown'
|
|
27
|
+
| 'ArrowLeft'
|
|
28
|
+
| 'ArrowRight'
|
|
29
|
+
| `Key${string}`
|
|
30
|
+
| `Digit${number}`
|
|
31
|
+
| (string & {});
|
|
32
|
+
|
|
7
33
|
export interface IInteraction {
|
|
8
34
|
name: string;
|
|
9
35
|
init(options: InteractionInitOptions): void;
|
|
@@ -21,6 +47,11 @@ export interface SelectionChangePayload {
|
|
|
21
47
|
mode: SelectMode;
|
|
22
48
|
}
|
|
23
49
|
|
|
50
|
+
export interface viewBoxChangePayload {
|
|
51
|
+
type: 'viewBox:change';
|
|
52
|
+
viewBox: string | undefined;
|
|
53
|
+
}
|
|
54
|
+
|
|
24
55
|
export interface IInteractionManager {
|
|
25
56
|
isActive(): boolean;
|
|
26
57
|
select(items: Selection, mode: SelectMode): void;
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type {
|
|
2
|
+
ParsedInfographicOptions,
|
|
3
|
+
UpdatableInfographicOptions,
|
|
4
|
+
} from '../../options';
|
|
2
5
|
import type { Element, IEventEmitter, ItemDatum } from '../../types';
|
|
3
6
|
import type { ICommandManager } from './command';
|
|
4
7
|
import type { IEditor } from './editor';
|
|
@@ -24,7 +27,16 @@ export interface IStateManager {
|
|
|
24
27
|
removeItemDatum(indexes: number[], count?: number): void;
|
|
25
28
|
updateData(key: string, value: any): void;
|
|
26
29
|
updateElement(element: Element, props: Partial<ElementProps>): void;
|
|
27
|
-
updateOptions(
|
|
30
|
+
updateOptions(
|
|
31
|
+
options: UpdatableInfographicOptions,
|
|
32
|
+
// Configuration for this update execution
|
|
33
|
+
execOptions?: {
|
|
34
|
+
// Whether to bubble up notifications to parent paths.
|
|
35
|
+
// Enabling this might duplicate objects and impact performance.
|
|
36
|
+
// Default is false.
|
|
37
|
+
bubbleUp?: boolean;
|
|
38
|
+
},
|
|
39
|
+
): void;
|
|
28
40
|
getOptions(): ParsedInfographicOptions;
|
|
29
41
|
destroy(): void;
|
|
30
42
|
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { InfographicOptionPath } from '../../options';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Sync callback
|
|
5
|
+
* @param newValue The new value after modification
|
|
6
|
+
* @param oldValue The old value before modification. Note: This value is undefined when triggered by parent path bubbling notification (i.e., listening to a non-leaf node).
|
|
7
|
+
*/
|
|
8
|
+
export type SyncHandler = (newValue: any, oldValue: any) => void;
|
|
9
|
+
|
|
10
|
+
export interface ISyncRegistry {
|
|
11
|
+
/**
|
|
12
|
+
* Register synchronization logic
|
|
13
|
+
* @param path Configuration path, such as 'design.background'
|
|
14
|
+
* @param handler Sync callback
|
|
15
|
+
* @param options.immediate Whether to execute immediately (used for view initialization)
|
|
16
|
+
* @returns unregister function
|
|
17
|
+
*/
|
|
18
|
+
register(
|
|
19
|
+
path: InfographicOptionPath | (string & {}),
|
|
20
|
+
handler: SyncHandler,
|
|
21
|
+
options?: { immediate?: boolean },
|
|
22
|
+
): () => void;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Trigger synchronization (usually called by StateManager)
|
|
26
|
+
* @param path Configuration path
|
|
27
|
+
* @param newVal New value
|
|
28
|
+
* @param oldVal Old value
|
|
29
|
+
*/
|
|
30
|
+
trigger(path: string, newVal: any, oldVal: any): void;
|
|
31
|
+
|
|
32
|
+
destroy(): void;
|
|
33
|
+
}
|
package/src/editor/utils/data.ts
CHANGED
|
@@ -8,8 +8,9 @@ export function getChildrenDataByIndexes(
|
|
|
8
8
|
data: ParsedData,
|
|
9
9
|
indexes: number[],
|
|
10
10
|
): ItemDatum[] {
|
|
11
|
-
if (indexes.length === 0) return data.
|
|
11
|
+
if (indexes.length === 0) return data.items;
|
|
12
12
|
const datum = getDatumByIndexes(data, indexes);
|
|
13
|
+
if (datum == null) return [];
|
|
13
14
|
datum.children ||= [];
|
|
14
15
|
return datum.children;
|
|
15
16
|
}
|
|
@@ -90,3 +90,10 @@ const getIconEventTarget = (element: SVGElement): IconElement | null => {
|
|
|
90
90
|
|
|
91
91
|
return null;
|
|
92
92
|
};
|
|
93
|
+
|
|
94
|
+
export function isTextSelectionTarget(target: EventTarget | null) {
|
|
95
|
+
if (!(target instanceof HTMLElement)) return false;
|
|
96
|
+
if (target.isContentEditable) return true;
|
|
97
|
+
const tag = target.tagName.toLowerCase();
|
|
98
|
+
return tag === 'input' || tag === 'textarea';
|
|
99
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { cloneDeep, isEqual, isPlainObject } from 'lodash-es';
|
|
2
|
+
|
|
3
|
+
export interface ApplyOptionUpdatesOptions {
|
|
4
|
+
/** Whether to notify parent paths of changes (bubbling) */
|
|
5
|
+
bubbleUp?: boolean;
|
|
6
|
+
/** Callback triggered whenever a property value changes */
|
|
7
|
+
collector?: (path: string, newVal: any, oldVal: any) => void;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Recursively applies properties from 'source' to 'target' and collects changes.
|
|
12
|
+
*
|
|
13
|
+
* @param target - The object to be updated
|
|
14
|
+
* @param source - The source object containing partial updates
|
|
15
|
+
* @param basePath - Current path prefix for nested properties
|
|
16
|
+
* @param options - Configuration options
|
|
17
|
+
*/
|
|
18
|
+
export function applyOptionUpdates(
|
|
19
|
+
target: any,
|
|
20
|
+
source: any,
|
|
21
|
+
basePath: string = '',
|
|
22
|
+
options?: ApplyOptionUpdatesOptions,
|
|
23
|
+
): void {
|
|
24
|
+
const { bubbleUp = false, collector } = options ?? {};
|
|
25
|
+
|
|
26
|
+
const hasChange = applyOptionUpdatesInternal(
|
|
27
|
+
target,
|
|
28
|
+
source,
|
|
29
|
+
basePath,
|
|
30
|
+
collector,
|
|
31
|
+
bubbleUp,
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
if (basePath === '' && hasChange && bubbleUp && collector) {
|
|
35
|
+
collector('', cloneDeep(target), undefined);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Internal recursive function.
|
|
41
|
+
* Returns true if any change occurred within this branch (or its children).
|
|
42
|
+
*/
|
|
43
|
+
function applyOptionUpdatesInternal(
|
|
44
|
+
target: any,
|
|
45
|
+
source: any,
|
|
46
|
+
basePath: string,
|
|
47
|
+
collector: ((path: string, newVal: any, oldVal: any) => void) | undefined,
|
|
48
|
+
bubbleUp: boolean,
|
|
49
|
+
): boolean {
|
|
50
|
+
let hasChange = false;
|
|
51
|
+
|
|
52
|
+
Object.keys(source).forEach((key) => {
|
|
53
|
+
if (key === '__proto__' || key === 'constructor' || key === 'prototype') {
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const fullPath = basePath ? `${basePath}.${key}` : key;
|
|
58
|
+
const updateValue = source[key];
|
|
59
|
+
const oldValue = target[key];
|
|
60
|
+
let childChanged = false;
|
|
61
|
+
|
|
62
|
+
if (updateValue === undefined) {
|
|
63
|
+
// Handle deletion: Only delete and notify if the key actually exists
|
|
64
|
+
if (key in target) {
|
|
65
|
+
delete target[key];
|
|
66
|
+
collector?.(fullPath, undefined, oldValue);
|
|
67
|
+
childChanged = true;
|
|
68
|
+
}
|
|
69
|
+
} else if (isPlainObject(updateValue)) {
|
|
70
|
+
// Handle nested object
|
|
71
|
+
const oldValueIsObject = isPlainObject(target[key]);
|
|
72
|
+
if (!oldValueIsObject) {
|
|
73
|
+
target[key] = {};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const grandChildChanged = applyOptionUpdatesInternal(
|
|
77
|
+
target[key],
|
|
78
|
+
updateValue,
|
|
79
|
+
fullPath,
|
|
80
|
+
collector,
|
|
81
|
+
bubbleUp,
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
if (!oldValueIsObject) {
|
|
85
|
+
// Overwriting a primitive with an object is always a change.
|
|
86
|
+
childChanged = true;
|
|
87
|
+
// If the object was empty (grandChildChanged is false), we still need to report it.
|
|
88
|
+
if (!grandChildChanged) {
|
|
89
|
+
collector?.(fullPath, target[key], oldValue);
|
|
90
|
+
}
|
|
91
|
+
} else {
|
|
92
|
+
childChanged = grandChildChanged;
|
|
93
|
+
}
|
|
94
|
+
} else {
|
|
95
|
+
// Handle primitive update
|
|
96
|
+
target[key] = updateValue;
|
|
97
|
+
if (!isEqual(updateValue, oldValue)) {
|
|
98
|
+
collector?.(fullPath, updateValue, oldValue);
|
|
99
|
+
childChanged = true;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (childChanged) {
|
|
104
|
+
hasChange = true;
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
// Bubbling: Notify if any child changed in this branch
|
|
109
|
+
// The recursion naturally ensures this happens in "deepest-first" (post-order) sequence.
|
|
110
|
+
if (hasChange && bubbleUp && basePath !== '') {
|
|
111
|
+
// Current target is now fully updated for this scope.
|
|
112
|
+
collector?.(basePath, cloneDeep(target), undefined);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return hasChange;
|
|
116
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,16 +1,23 @@
|
|
|
1
1
|
export * from './designs';
|
|
2
2
|
export { getItemProps, getThemeColors } from './designs/utils';
|
|
3
|
+
export {
|
|
4
|
+
BatchCommand,
|
|
5
|
+
UpdateElementCommand,
|
|
6
|
+
UpdateOptionsCommand,
|
|
7
|
+
UpdateTextCommand,
|
|
8
|
+
} from './editor/commands';
|
|
3
9
|
export {
|
|
4
10
|
BrushSelect,
|
|
5
11
|
ClickSelect,
|
|
6
12
|
DblClickEditText,
|
|
13
|
+
DragCanvas,
|
|
7
14
|
DragElement,
|
|
8
15
|
HotkeyHistory,
|
|
9
16
|
Interaction,
|
|
10
17
|
SelectHighlight,
|
|
11
18
|
ZoomWheel,
|
|
12
19
|
} from './editor/interactions';
|
|
13
|
-
export { EditBar, Plugin, ResizeElement } from './editor/plugins';
|
|
20
|
+
export { EditBar, Plugin, ResetViewBox, ResizeElement } from './editor/plugins';
|
|
14
21
|
export { exportToSVG } from './exporter';
|
|
15
22
|
export {
|
|
16
23
|
Defs,
|
|
@@ -51,7 +58,24 @@ export { getTheme, getThemes, registerTheme } from './themes';
|
|
|
51
58
|
export { parseSVG, setFontExtendFactor } from './utils';
|
|
52
59
|
export { VERSION } from './version';
|
|
53
60
|
|
|
54
|
-
export type {
|
|
61
|
+
export type {
|
|
62
|
+
EditBarOptions,
|
|
63
|
+
ICommand,
|
|
64
|
+
ICommandManager,
|
|
65
|
+
IEditor,
|
|
66
|
+
IInteraction,
|
|
67
|
+
IInteractionManager,
|
|
68
|
+
IPlugin,
|
|
69
|
+
IPluginManager,
|
|
70
|
+
IStateManager,
|
|
71
|
+
ISyncRegistry,
|
|
72
|
+
InteractionInitOptions,
|
|
73
|
+
KeyCode,
|
|
74
|
+
PluginInitOptions,
|
|
75
|
+
SelectMode,
|
|
76
|
+
Selection,
|
|
77
|
+
SyncHandler,
|
|
78
|
+
} from './editor';
|
|
55
79
|
export type {
|
|
56
80
|
ExportOptions,
|
|
57
81
|
PNGExportOptions,
|
package/src/options/types.ts
CHANGED
|
@@ -2,6 +2,7 @@ import type { DesignOptions, ParsedDesignsOptions } from '../designs';
|
|
|
2
2
|
import type { ElementProps, IInteraction, IPlugin } from '../editor';
|
|
3
3
|
import type { ThemeConfig } from '../themes';
|
|
4
4
|
import type { Data, Padding, ParsedData } from '../types';
|
|
5
|
+
import type { Path } from '../utils';
|
|
5
6
|
|
|
6
7
|
export interface InfographicOptions {
|
|
7
8
|
/** 容器,可以是选择器或者 HTMLElement */
|
|
@@ -54,6 +55,10 @@ export interface ParsedInfographicOptions {
|
|
|
54
55
|
shapes?: ElementProps[];
|
|
55
56
|
}
|
|
56
57
|
|
|
58
|
+
export type UpdatableInfographicOptions = Partial<
|
|
59
|
+
Omit<ParsedInfographicOptions, 'container'>
|
|
60
|
+
>;
|
|
61
|
+
|
|
57
62
|
interface SVGOptions {
|
|
58
63
|
style?: Record<string, string | number>;
|
|
59
64
|
attributes?: Record<string, string | number | boolean>;
|
|
@@ -62,3 +67,9 @@ interface SVGOptions {
|
|
|
62
67
|
/** 是否启用背景 */
|
|
63
68
|
background?: boolean;
|
|
64
69
|
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* All valid property paths for Infographic Options.
|
|
73
|
+
* Use this to validate paths in SyncRegistry and other places.
|
|
74
|
+
*/
|
|
75
|
+
export type InfographicOptionPath = Path<UpdatableInfographicOptions>;
|
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
ParsedInfographicOptions,
|
|
12
12
|
parseOptions,
|
|
13
13
|
} from '../options';
|
|
14
|
-
import { Renderer } from '../renderer';
|
|
14
|
+
import { DEFAULT_FONT, Renderer, setDefaultFont } from '../renderer';
|
|
15
15
|
import { waitForSvgLoads } from '../resource';
|
|
16
16
|
import { parseSyntax, type SyntaxError } from '../syntax';
|
|
17
17
|
import { IEventEmitter } from '../types';
|
|
@@ -133,29 +133,39 @@ export class Infographic {
|
|
|
133
133
|
* Compose the SVG template
|
|
134
134
|
*/
|
|
135
135
|
compose(parsedOptions: ParsedInfographicOptions): SVGSVGElement {
|
|
136
|
-
const { design, data } = parsedOptions;
|
|
136
|
+
const { design, data, themeConfig } = parsedOptions;
|
|
137
137
|
const { title, item, items, structure } = design;
|
|
138
138
|
const { component: Structure, props: structureProps } = structure;
|
|
139
139
|
const Title = title.component;
|
|
140
140
|
const Item = item.component;
|
|
141
141
|
const Items = items.map((it) => it.component);
|
|
142
142
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
143
|
+
// Apply theme font-family before measurement so measureText uses the correct font
|
|
144
|
+
const themeFontFamily = themeConfig?.base?.text?.['font-family'];
|
|
145
|
+
const previousDefaultFont = DEFAULT_FONT;
|
|
146
|
+
if (themeFontFamily) setDefaultFont(themeFontFamily);
|
|
147
|
+
|
|
148
|
+
try {
|
|
149
|
+
const svg = renderSVG(
|
|
150
|
+
<Structure
|
|
151
|
+
data={data}
|
|
152
|
+
Title={Title}
|
|
153
|
+
Item={Item}
|
|
154
|
+
Items={Items}
|
|
155
|
+
options={parsedOptions}
|
|
156
|
+
{...structureProps}
|
|
157
|
+
/>,
|
|
158
|
+
);
|
|
159
|
+
|
|
160
|
+
const template = parseSVG(svg);
|
|
161
|
+
if (!template) {
|
|
162
|
+
throw new Error('Failed to parse SVG template');
|
|
163
|
+
}
|
|
164
|
+
return template;
|
|
165
|
+
} finally {
|
|
166
|
+
// Restore previous default font
|
|
167
|
+
if (themeFontFamily) setDefaultFont(previousDefaultFont);
|
|
157
168
|
}
|
|
158
|
-
return template;
|
|
159
169
|
}
|
|
160
170
|
|
|
161
171
|
getTypes() {
|