@tmagic/stage 1.4.7 → 1.4.8

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.
@@ -1,274 +0,0 @@
1
- /*
2
- * Tencent is pleased to support the open source community by making TMagicEditor available.
3
- *
4
- * Copyright (C) 2023 THL A29 Limited, a Tencent company. All rights reserved.
5
- *
6
- * Licensed under the Apache License, Version 2.0 (the "License");
7
- * you may not use this file except in compliance with the License.
8
- * You may obtain a copy of the License at
9
- *
10
- * http://www.apache.org/licenses/LICENSE-2.0
11
- *
12
- * Unless required by applicable law or agreed to in writing, software
13
- * distributed under the License is distributed on an "AS IS" BASIS,
14
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
- * See the License for the specific language governing permissions and
16
- * limitations under the License.
17
- */
18
-
19
- import EventEmitter from 'events';
20
-
21
- import { merge } from 'lodash-es';
22
- import type { ElementGuidelineValueOption, MoveableOptions, MoveableRefType } from 'moveable';
23
-
24
- import { AbleActionEventType, GuidesType, Mode } from './const';
25
- import MoveableActionsAble from './MoveableActionsAble';
26
- import type { GetRootContainer, MoveableOptionsManagerConfig } from './types';
27
- import { getOffset } from './util';
28
-
29
- /**
30
- * 单选和多选的父类,用于管理moveableOptions
31
- * @extends EventEmitter
32
- */
33
- export default class MoveableOptionsManager extends EventEmitter {
34
- /** 布局方式:流式布局、绝对定位、固定定位 */
35
- public mode: Mode = Mode.ABSOLUTE;
36
-
37
- /** 画布容器 */
38
- protected container: HTMLElement;
39
- protected options: MoveableOptions = {};
40
-
41
- /** 水平参考线 */
42
- private horizontalGuidelines: number[] = [];
43
- /** 垂直参考线 */
44
- private verticalGuidelines: number[] = [];
45
- /** 对齐元素集合 */
46
- private elementGuidelines: HTMLElement[] = [];
47
- /** 由外部调用方(编辑器)传入进来的moveable默认参数,可以为空,也可以是一个回调函数 */
48
- private customizedOptions?: (() => MoveableOptions) | MoveableOptions;
49
- /** 获取整个画布的根元素(在StageCore的mount函数中挂载的container) */
50
- private getRootContainer: GetRootContainer;
51
-
52
- constructor(config: MoveableOptionsManagerConfig) {
53
- super();
54
- this.customizedOptions = config.moveableOptions;
55
- this.container = config.container;
56
- this.getRootContainer = config.getRootContainer;
57
- }
58
-
59
- public getOption<K extends keyof MoveableOptions>(key: K): MoveableOptions[K] {
60
- return this.options[key];
61
- }
62
-
63
- /**
64
- * 设置水平/垂直参考线
65
- * @param type 参考线类型
66
- * @param guidelines 参考线坐标数组
67
- */
68
- public setGuidelines(type: GuidesType, guidelines: number[]): void {
69
- if (type === GuidesType.HORIZONTAL) {
70
- this.horizontalGuidelines = guidelines;
71
- } else if (type === GuidesType.VERTICAL) {
72
- this.verticalGuidelines = guidelines;
73
- }
74
-
75
- this.emit('update-moveable');
76
- }
77
-
78
- /**
79
- * 清除横向和纵向的参考线
80
- */
81
- public clearGuides(): void {
82
- this.horizontalGuidelines = [];
83
- this.verticalGuidelines = [];
84
-
85
- this.emit('update-moveable');
86
- }
87
-
88
- /**
89
- * 设置有哪些元素要辅助对齐
90
- * @param selectedElList 选中的元素列表,需要排除在对齐元素之外
91
- */
92
- protected setElementGuidelines(selectedElList: HTMLElement[]): void {
93
- this.elementGuidelines.forEach((node) => {
94
- node.remove();
95
- });
96
- this.elementGuidelines = [];
97
-
98
- // 设置选中元素的周围元素,用于选中元素跟周围元素对齐辅助
99
- const elementGuidelines: Array<ElementGuidelineValueOption | MoveableRefType<Element>> =
100
- this.getCustomizeOptions()?.elementGuidelines || Array.from(selectedElList[0]?.parentElement?.children || []);
101
-
102
- if (this.mode === Mode.ABSOLUTE) {
103
- this.container.append(this.createGuidelineElements(selectedElList, elementGuidelines));
104
- }
105
- }
106
-
107
- /**
108
- * 获取moveable参数
109
- * @param isMultiSelect 是否多选模式
110
- * @param runtimeOptions 调用时实时传进来的的moveable参数
111
- * @returns moveable所需参数
112
- */
113
- protected getOptions(isMultiSelect: boolean, runtimeOptions: MoveableOptions = {}): MoveableOptions {
114
- const defaultOptions = this.getDefaultOptions(isMultiSelect);
115
- const customizedOptions = this.getCustomizeOptions();
116
-
117
- this.options = merge(defaultOptions, customizedOptions, runtimeOptions);
118
- return this.options;
119
- }
120
-
121
- /**
122
- * 获取单选和多选的moveable公共参数
123
- * @returns moveable公共参数
124
- */
125
- private getDefaultOptions(isMultiSelect: boolean): MoveableOptions {
126
- const isSortable = this.mode === Mode.SORTABLE;
127
-
128
- const commonOptions = {
129
- draggable: true,
130
- resizable: true,
131
- rootContainer: this.getRootContainer(),
132
- zoom: 1,
133
- throttleDrag: 0,
134
- snappable: true,
135
- horizontalGuidelines: this.horizontalGuidelines,
136
- verticalGuidelines: this.verticalGuidelines,
137
- elementGuidelines: this.elementGuidelines,
138
- bounds: {
139
- top: 0,
140
- left: 0,
141
- right: this.container.clientWidth,
142
- bottom: isSortable ? undefined : this.container.clientHeight,
143
- },
144
- };
145
- const differenceOptions = isMultiSelect ? this.getMultiOptions() : this.getSingleOptions();
146
-
147
- return merge(commonOptions, differenceOptions);
148
- }
149
-
150
- /**
151
- * 获取单选下的差异化参数
152
- * @returns {MoveableOptions} moveable options参数
153
- */
154
- private getSingleOptions(): MoveableOptions {
155
- const isAbsolute = this.mode === Mode.ABSOLUTE;
156
- const isFixed = this.mode === Mode.FIXED;
157
-
158
- return {
159
- origin: false,
160
- dragArea: false,
161
- scalable: false,
162
- rotatable: false,
163
- snapGap: isAbsolute || isFixed,
164
- snapThreshold: 5,
165
- snapDigit: 0,
166
- isDisplaySnapDigit: isAbsolute,
167
- snapDirections: {
168
- top: isAbsolute,
169
- right: isAbsolute,
170
- bottom: isAbsolute,
171
- left: isAbsolute,
172
- center: isAbsolute,
173
- middle: isAbsolute,
174
- },
175
- elementSnapDirections: {
176
- top: isAbsolute,
177
- right: isAbsolute,
178
- bottom: isAbsolute,
179
- left: isAbsolute,
180
- },
181
- isDisplayInnerSnapDigit: true,
182
- dragTarget: '.moveable-drag-area-button',
183
- dragTargetSelf: true,
184
- props: {
185
- actions: true,
186
- },
187
-
188
- ables: [MoveableActionsAble(this.actionHandler.bind(this))],
189
- };
190
- }
191
-
192
- /**
193
- * 获取多选下的差异化参数
194
- * @returns {MoveableOptions} moveable options参数
195
- */
196
- private getMultiOptions(): MoveableOptions {
197
- return {
198
- defaultGroupRotate: 0,
199
- defaultGroupOrigin: '50% 50%',
200
- startDragRotate: 0,
201
- throttleDragRotate: 0,
202
- origin: true,
203
- padding: { left: 0, top: 0, right: 0, bottom: 0 },
204
- };
205
- }
206
-
207
- /**
208
- * 获取业务方自定义的moveable参数
209
- */
210
- private getCustomizeOptions(): MoveableOptions | undefined {
211
- if (typeof this.customizedOptions === 'function') {
212
- return this.customizedOptions();
213
- }
214
- return this.customizedOptions;
215
- }
216
-
217
- /**
218
- * 这是给selectParentAbles的回调函数,用于触发选中父元素事件
219
- */
220
- private actionHandler(type: AbleActionEventType): void {
221
- this.emit(type);
222
- }
223
-
224
- /**
225
- * 为需要辅助对齐的元素创建div
226
- * @param selectedElList 选中的元素列表,需要排除在对齐元素之外
227
- * @param allElList 全部元素列表
228
- * @returns frame 辅助对齐元素集合的页面片
229
- */
230
- private createGuidelineElements(
231
- selectedElList: HTMLElement[],
232
- allElList: Array<ElementGuidelineValueOption | MoveableRefType<Element>>,
233
- ): DocumentFragment {
234
- const frame = globalThis.document.createDocumentFragment();
235
-
236
- for (const element of allElList) {
237
- let node: MoveableRefType<Element> =
238
- (element as ElementGuidelineValueOption).element || (element as MoveableRefType<Element>);
239
-
240
- if (!node || typeof node === 'string') continue;
241
-
242
- if (typeof node === 'function') {
243
- node = node();
244
- }
245
-
246
- if (this.isInElementList(node as Element, selectedElList)) continue;
247
-
248
- const { width, height } = (node as Element).getBoundingClientRect();
249
-
250
- if (!width || !height) continue;
251
-
252
- const { left, top } = getOffset(node as Element);
253
- const elementGuideline = globalThis.document.createElement('div');
254
- elementGuideline.style.cssText = `position: absolute;width: ${width}px;height: ${height}px;top: ${top}px;left: ${left}px`;
255
- this.elementGuidelines.push(elementGuideline);
256
- frame.append(elementGuideline);
257
- }
258
-
259
- return frame;
260
- }
261
-
262
- /**
263
- * 判断一个元素是否在元素列表里面
264
- * @param ele 元素
265
- * @param eleList 元素列表
266
- * @returns 是否在元素列表里面
267
- */
268
- private isInElementList(ele: Element, eleList: Element[]): boolean {
269
- for (const eleItem of eleList) {
270
- if (ele === eleItem) return true;
271
- }
272
- return false;
273
- }
274
- }
package/src/Rule.ts DELETED
@@ -1,173 +0,0 @@
1
- import EventEmitter from 'events';
2
-
3
- import Guides, { type GuidesEvents, type GuidesOptions } from '@scena/guides';
4
-
5
- import { GuidesType } from './const';
6
- import type { RuleOptions } from './types';
7
-
8
- export default class Rule extends EventEmitter {
9
- public hGuides: Guides;
10
- public vGuides: Guides;
11
- public horizontalGuidelines: number[] = [];
12
- public verticalGuidelines: number[] = [];
13
-
14
- private container: HTMLDivElement;
15
- private containerResizeObserver: ResizeObserver;
16
- private isShowGuides = true;
17
- private guidesOptions?: Partial<GuidesOptions>;
18
-
19
- constructor(container: HTMLDivElement, options?: RuleOptions) {
20
- super();
21
-
22
- this.guidesOptions = options?.guidesOptions || {};
23
-
24
- this.container = container;
25
- this.hGuides = this.createGuides(GuidesType.HORIZONTAL, this.horizontalGuidelines);
26
- this.vGuides = this.createGuides(GuidesType.VERTICAL, this.verticalGuidelines);
27
-
28
- this.containerResizeObserver = new ResizeObserver(() => {
29
- this.vGuides.resize();
30
- this.hGuides.resize();
31
- });
32
-
33
- this.containerResizeObserver.observe(this.container);
34
- }
35
-
36
- /**
37
- * 是否显示辅助线
38
- * @param isShowGuides 是否显示
39
- */
40
- public showGuides(isShowGuides = true) {
41
- this.isShowGuides = isShowGuides;
42
-
43
- this.hGuides.setState({
44
- showGuides: isShowGuides,
45
- });
46
-
47
- this.vGuides.setState({
48
- showGuides: isShowGuides,
49
- });
50
- }
51
-
52
- public setGuides([hLines, vLines]: [number[], number[]]) {
53
- this.horizontalGuidelines = hLines;
54
- this.verticalGuidelines = vLines;
55
-
56
- this.hGuides.setState({
57
- defaultGuides: hLines,
58
- });
59
-
60
- this.vGuides.setState({
61
- defaultGuides: vLines,
62
- });
63
-
64
- this.emit('change-guides', {
65
- type: GuidesType.HORIZONTAL,
66
- guides: hLines,
67
- });
68
-
69
- this.emit('change-guides', {
70
- type: GuidesType.VERTICAL,
71
- guides: vLines,
72
- });
73
- }
74
-
75
- /**
76
- * 清空所有参考线
77
- */
78
- public clearGuides() {
79
- this.setGuides([[], []]);
80
- }
81
-
82
- /**
83
- * 是否显示标尺
84
- * @param show 是否显示
85
- */
86
- public showRule(show = true) {
87
- // 当尺子隐藏时发现大小变化,显示后会变形,所以这里做重新初始化处理
88
- if (show) {
89
- this.hGuides.destroy();
90
- this.hGuides = this.createGuides(GuidesType.HORIZONTAL, this.horizontalGuidelines);
91
-
92
- this.vGuides.destroy();
93
- this.vGuides = this.createGuides(GuidesType.VERTICAL, this.verticalGuidelines);
94
- } else {
95
- this.hGuides.setState({
96
- rulerStyle: {
97
- visibility: 'hidden',
98
- },
99
- });
100
-
101
- this.vGuides.setState({
102
- rulerStyle: {
103
- visibility: 'hidden',
104
- },
105
- });
106
- }
107
- }
108
-
109
- public scrollRule(scrollTop: number) {
110
- this.hGuides.scrollGuides(scrollTop);
111
- this.hGuides.scroll(0);
112
-
113
- this.vGuides.scrollGuides(0);
114
- this.vGuides.scroll(scrollTop);
115
- }
116
-
117
- public destroy(): void {
118
- this.hGuides.off('changeGuides', this.hGuidesChangeGuidesHandler);
119
- this.vGuides.off('changeGuides', this.vGuidesChangeGuidesHandler);
120
- this.containerResizeObserver.disconnect();
121
- this.removeAllListeners();
122
- }
123
-
124
- private getGuidesStyle = (type: GuidesType) => ({
125
- position: 'fixed',
126
- zIndex: 1,
127
- left: type === GuidesType.HORIZONTAL ? 0 : '-30px',
128
- top: type === GuidesType.HORIZONTAL ? '-30px' : 0,
129
- width: type === GuidesType.HORIZONTAL ? '100%' : '30px',
130
- height: type === GuidesType.HORIZONTAL ? '30px' : '100%',
131
- });
132
-
133
- private createGuides = (type: GuidesType, defaultGuides: number[] = []): Guides => {
134
- const guides = new Guides(this.container, {
135
- type,
136
- defaultGuides,
137
- displayDragPos: true,
138
- backgroundColor: '#fff',
139
- lineColor: '#000',
140
- textColor: '#000',
141
- style: this.getGuidesStyle(type),
142
- showGuides: this.isShowGuides,
143
- ...this.guidesOptions,
144
- });
145
-
146
- const changEventHandler = {
147
- [GuidesType.HORIZONTAL]: this.hGuidesChangeGuidesHandler,
148
- [GuidesType.VERTICAL]: this.vGuidesChangeGuidesHandler,
149
- }[type];
150
-
151
- if (changEventHandler) {
152
- guides.on('changeGuides', changEventHandler);
153
- }
154
-
155
- return guides;
156
- };
157
-
158
- private hGuidesChangeGuidesHandler = (e: GuidesEvents['changeGuides']) => {
159
- this.horizontalGuidelines = e.guides;
160
- this.emit('change-guides', {
161
- type: GuidesType.HORIZONTAL,
162
- guides: this.horizontalGuidelines,
163
- });
164
- };
165
-
166
- private vGuidesChangeGuidesHandler = (e: GuidesEvents['changeGuides']) => {
167
- this.verticalGuidelines = e.guides;
168
- this.emit('change-guides', {
169
- type: GuidesType.VERTICAL,
170
- guides: this.verticalGuidelines,
171
- });
172
- };
173
- }