@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.
- package/dist/infographic.min.js +123 -116
- package/dist/infographic.min.js.map +1 -1
- 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 +15 -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 +13 -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 +26 -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 +70 -0
- package/esm/index.d.ts +4 -3
- package/esm/index.js +3 -2
- package/esm/options/types.d.ts +1 -0
- package/esm/runtime/options.js +7 -2
- package/esm/templates/built-in.js +24 -0
- package/esm/utils/measure-text.js +9 -6
- package/esm/utils/padding.d.ts +1 -0
- package/esm/utils/padding.js +6 -2
- package/esm/version.d.ts +1 -1
- package/esm/version.js +1 -1
- 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 +15 -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 +13 -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 +26 -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 +73 -0
- package/lib/index.d.ts +4 -3
- package/lib/index.js +9 -2
- package/lib/options/types.d.ts +1 -0
- package/lib/runtime/options.js +6 -1
- package/lib/templates/built-in.js +24 -0
- package/lib/utils/measure-text.js +9 -6
- package/lib/utils/padding.d.ts +1 -0
- package/lib/utils/padding.js +7 -2
- package/lib/version.d.ts +1 -1
- package/lib/version.js +1 -1
- package/package.json +1 -1
- 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 +65 -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 +17 -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 +31 -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 +106 -0
- package/src/index.ts +26 -2
- package/src/options/types.ts +4 -0
- package/src/runtime/options.ts +8 -1
- package/src/templates/built-in.ts +28 -0
- package/src/utils/measure-text.ts +6 -6
- package/src/utils/padding.ts +10 -2
- 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,44 @@
|
|
|
1
|
+
import { parsePadding, setSVGPadding } from '../../utils';
|
|
2
|
+
import type { IPlugin, PluginInitOptions } from '../types';
|
|
3
|
+
import { Plugin } from './base';
|
|
4
|
+
|
|
5
|
+
export class CoreSyncPlugin extends Plugin implements IPlugin {
|
|
6
|
+
name = 'core-sync';
|
|
7
|
+
|
|
8
|
+
private unregisters: (() => void)[] = [];
|
|
9
|
+
|
|
10
|
+
init(options: PluginInitOptions) {
|
|
11
|
+
super.init(options);
|
|
12
|
+
const svg = this.editor.getDocument();
|
|
13
|
+
|
|
14
|
+
// viewBox Sync
|
|
15
|
+
this.unregisters.push(
|
|
16
|
+
this.editor.registerSync(
|
|
17
|
+
'viewBox',
|
|
18
|
+
(val) => {
|
|
19
|
+
if (val) {
|
|
20
|
+
svg.setAttribute('viewBox', val);
|
|
21
|
+
} else {
|
|
22
|
+
svg.removeAttribute('viewBox');
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
{ immediate: true },
|
|
26
|
+
),
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
// padding Sync
|
|
30
|
+
this.unregisters.push(
|
|
31
|
+
this.editor.registerSync(
|
|
32
|
+
'padding',
|
|
33
|
+
(val) => {
|
|
34
|
+
if (val !== undefined) setSVGPadding(svg, parsePadding(val));
|
|
35
|
+
},
|
|
36
|
+
{ immediate: true },
|
|
37
|
+
),
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
destroy() {
|
|
42
|
+
this.unregisters.forEach((fn) => fn());
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -8,8 +8,8 @@ import {
|
|
|
8
8
|
import { UpdateElementCommand } from '../../../commands';
|
|
9
9
|
import type { ICommandManager, Selection } from '../../../types';
|
|
10
10
|
import { getElementViewportBounds } from '../../../utils';
|
|
11
|
-
import { ELEMENT_ICONS, IconButton, Popover } from '
|
|
12
|
-
import type { Icon } from '
|
|
11
|
+
import { ELEMENT_ICONS, IconButton, Popover } from '../../components';
|
|
12
|
+
import type { Icon } from '../../components/icons';
|
|
13
13
|
import type { EditItem } from './types';
|
|
14
14
|
|
|
15
15
|
const GRID_CLASS = 'infographic-align-grid';
|
|
@@ -5,7 +5,7 @@ import type {
|
|
|
5
5
|
} from '../../../../types';
|
|
6
6
|
import { injectStyleOnce } from '../../../../utils';
|
|
7
7
|
import { UpdateElementCommand } from '../../../commands';
|
|
8
|
-
import { Button, IconButton, Popover, TEXT_ICONS } from '
|
|
8
|
+
import { Button, IconButton, Popover, TEXT_ICONS } from '../../components';
|
|
9
9
|
import type { EditItem } from './types';
|
|
10
10
|
|
|
11
11
|
// 常量定义
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { TextAttributes } from '../../../../types';
|
|
2
2
|
import { hasColor, injectStyleOnce } from '../../../../utils';
|
|
3
3
|
import { UpdateElementCommand } from '../../../commands';
|
|
4
|
-
import { ColorPicker, Popover } from '
|
|
4
|
+
import { ColorPicker, Popover } from '../../components';
|
|
5
5
|
import type { EditItem } from './types';
|
|
6
6
|
|
|
7
7
|
const FONT_COLOR_BUTTON_CLASS = 'infographic-font-color-btn';
|
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
injectStyleOnce,
|
|
7
7
|
} from '../../../../utils';
|
|
8
8
|
import { UpdateElementCommand } from '../../../commands';
|
|
9
|
-
import { IconButton, Popover, TEXT_ICONS } from '
|
|
9
|
+
import { IconButton, Popover, TEXT_ICONS } from '../../components';
|
|
10
10
|
import type { EditItem } from './types';
|
|
11
11
|
|
|
12
12
|
const FONT_LIST_CLASS = 'infographic-font-family-list';
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import type { TextAttributes } from '../../../../types';
|
|
2
2
|
import { injectStyleOnce } from '../../../../utils';
|
|
3
3
|
import { UpdateElementCommand } from '../../../commands';
|
|
4
|
-
import { IconButton, Popover, TEXT_ICONS } from '
|
|
5
|
-
import type { Button } from '
|
|
6
|
-
import type { Icon } from '
|
|
4
|
+
import { IconButton, Popover, TEXT_ICONS } from '../../components';
|
|
5
|
+
import type { Button } from '../../components/button';
|
|
6
|
+
import type { Icon } from '../../components/icons';
|
|
7
7
|
import type { EditItem } from './types';
|
|
8
8
|
|
|
9
9
|
const FONT_SIZE_CLASS = 'infographic-font-size-grid';
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { IconAttributes } from '../../../../types';
|
|
2
2
|
import { hasColor, injectStyleOnce } from '../../../../utils';
|
|
3
3
|
import { UpdateElementCommand } from '../../../commands';
|
|
4
|
-
import { ColorPicker, Popover } from '
|
|
4
|
+
import { ColorPicker, Popover } from '../../components';
|
|
5
5
|
import type { EditItem } from './types';
|
|
6
6
|
|
|
7
7
|
const ICON_COLOR_BUTTON_CLASS = 'infographic-icon-color-btn';
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { ColorPicker, Popover, Select } from '
|
|
1
|
+
export { ColorPicker, Popover, Select } from '../components';
|
|
2
2
|
export { EditBar } from './edit-bar';
|
|
3
3
|
|
|
4
4
|
export type {
|
|
@@ -14,5 +14,5 @@ export type {
|
|
|
14
14
|
SelectOption,
|
|
15
15
|
SelectProps,
|
|
16
16
|
SelectValue,
|
|
17
|
-
} from '
|
|
17
|
+
} from '../components';
|
|
18
18
|
export type { EditBarOptions } from './edit-bar';
|
|
@@ -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,21 @@
|
|
|
1
|
+
import type { ICommandManager } from './command';
|
|
2
|
+
import type { IInteractionManager } from './interaction';
|
|
3
|
+
import type { IPluginManager } from './plugin';
|
|
4
|
+
import type { IStateManager } from './state';
|
|
5
|
+
import type { ISyncRegistry, SyncHandler } from './sync';
|
|
6
|
+
|
|
1
7
|
export interface IEditor {
|
|
8
|
+
commander: ICommandManager;
|
|
9
|
+
interaction: IInteractionManager;
|
|
10
|
+
plugin: IPluginManager;
|
|
11
|
+
state: IStateManager;
|
|
12
|
+
syncRegistry: ISyncRegistry;
|
|
13
|
+
|
|
2
14
|
getDocument(): SVGSVGElement;
|
|
15
|
+
registerSync(
|
|
16
|
+
path: string,
|
|
17
|
+
handler: SyncHandler,
|
|
18
|
+
options?: { immediate?: boolean },
|
|
19
|
+
): () => void;
|
|
3
20
|
destroy(): void;
|
|
4
21
|
}
|
|
@@ -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,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sync callback
|
|
3
|
+
* @param newValue The new value after modification
|
|
4
|
+
* @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).
|
|
5
|
+
*/
|
|
6
|
+
export type SyncHandler = (newValue: any, oldValue: any) => void;
|
|
7
|
+
|
|
8
|
+
export interface ISyncRegistry {
|
|
9
|
+
/**
|
|
10
|
+
* Register synchronization logic
|
|
11
|
+
* @param path Configuration path, such as 'design.background'
|
|
12
|
+
* @param handler Sync callback
|
|
13
|
+
* @param options.immediate Whether to execute immediately (used for view initialization)
|
|
14
|
+
* @returns unregister function
|
|
15
|
+
*/
|
|
16
|
+
register(
|
|
17
|
+
path: string,
|
|
18
|
+
handler: SyncHandler,
|
|
19
|
+
options?: { immediate?: boolean },
|
|
20
|
+
): () => void;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Trigger synchronization (usually called by StateManager)
|
|
24
|
+
* @param path Configuration path
|
|
25
|
+
* @param newVal New value
|
|
26
|
+
* @param oldVal Old value
|
|
27
|
+
*/
|
|
28
|
+
trigger(path: string, newVal: any, oldVal: any): void;
|
|
29
|
+
|
|
30
|
+
destroy(): void;
|
|
31
|
+
}
|
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,106 @@
|
|
|
1
|
+
import { cloneDeep, get, 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
|
+
// Set to store unique parent paths that need notification
|
|
26
|
+
const parentPathsToNotify = new Set<string>();
|
|
27
|
+
|
|
28
|
+
applyOptionUpdatesInternal(
|
|
29
|
+
target,
|
|
30
|
+
source,
|
|
31
|
+
basePath,
|
|
32
|
+
collector,
|
|
33
|
+
bubbleUp,
|
|
34
|
+
parentPathsToNotify,
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
// Bubbling notification: from the deepest parent path to the shallowest
|
|
38
|
+
if (bubbleUp && collector && parentPathsToNotify.size > 0) {
|
|
39
|
+
// Sort by path depth in descending order (deepest first)
|
|
40
|
+
const sortedPaths = Array.from(parentPathsToNotify).sort((a, b) => {
|
|
41
|
+
const depthA = a === '' ? 0 : a.split('.').length;
|
|
42
|
+
const depthB = b === '' ? 0 : b.split('.').length;
|
|
43
|
+
return depthB - depthA;
|
|
44
|
+
});
|
|
45
|
+
for (const parentPath of sortedPaths) {
|
|
46
|
+
const newVal = parentPath ? get(target, parentPath) : target;
|
|
47
|
+
// For parent paths, we provide the cloned new value.
|
|
48
|
+
// oldVal is passed as undefined as tracking branch node state is complex.
|
|
49
|
+
collector(parentPath, cloneDeep(newVal), undefined);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function applyOptionUpdatesInternal(
|
|
55
|
+
target: any,
|
|
56
|
+
source: any,
|
|
57
|
+
basePath: string,
|
|
58
|
+
collector: ((path: string, newVal: any, oldVal: any) => void) | undefined,
|
|
59
|
+
bubbleUp: boolean,
|
|
60
|
+
parentPathsToNotify: Set<string>,
|
|
61
|
+
): void {
|
|
62
|
+
Object.keys(source).forEach((key) => {
|
|
63
|
+
const fullPath = basePath ? `${basePath}.${key}` : key;
|
|
64
|
+
const updateValue = source[key];
|
|
65
|
+
const oldValue = target[key];
|
|
66
|
+
|
|
67
|
+
if (updateValue === undefined) {
|
|
68
|
+
delete target[key];
|
|
69
|
+
collector?.(fullPath, undefined, oldValue);
|
|
70
|
+
if (bubbleUp) collectParentPaths(basePath, parentPathsToNotify);
|
|
71
|
+
} else if (isPlainObject(updateValue)) {
|
|
72
|
+
if (!isPlainObject(target[key])) {
|
|
73
|
+
target[key] = {};
|
|
74
|
+
}
|
|
75
|
+
applyOptionUpdatesInternal(
|
|
76
|
+
target[key],
|
|
77
|
+
updateValue,
|
|
78
|
+
fullPath,
|
|
79
|
+
collector,
|
|
80
|
+
bubbleUp,
|
|
81
|
+
parentPathsToNotify,
|
|
82
|
+
);
|
|
83
|
+
} else {
|
|
84
|
+
target[key] = updateValue;
|
|
85
|
+
if (!isEqual(updateValue, oldValue)) {
|
|
86
|
+
collector?.(fullPath, updateValue, oldValue);
|
|
87
|
+
if (bubbleUp) collectParentPaths(basePath, parentPathsToNotify);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Collects all parent paths of a given path and adds them to the provided set.
|
|
95
|
+
*/
|
|
96
|
+
function collectParentPaths(path: string, set: Set<string>): void {
|
|
97
|
+
if (!path) {
|
|
98
|
+
set.add(''); // Root path
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const parts = path.split('.');
|
|
103
|
+
for (let i = parts.length; i >= 0; i--) {
|
|
104
|
+
set.add(parts.slice(0, i).join('.'));
|
|
105
|
+
}
|
|
106
|
+
}
|