@tmagic/stage 1.4.8 → 1.4.10
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/tmagic-stage.js +12 -3
- package/dist/tmagic-stage.umd.cjs +12 -3
- package/package.json +6 -8
- package/src/ActionManager.ts +661 -0
- package/src/DragResizeHelper.ts +396 -0
- package/src/MoveableActionsAble.ts +91 -0
- package/src/MoveableOptionsManager.ts +274 -0
- package/src/Rule.ts +185 -0
- package/src/StageCore.ts +417 -0
- package/src/StageDragResize.ts +357 -0
- package/src/StageHighlight.ts +89 -0
- package/src/StageMask.ts +337 -0
- package/src/StageMultiDragResize.ts +229 -0
- package/src/StageRender.ts +252 -0
- package/src/TargetShadow.ts +122 -0
- package/src/const.ts +111 -0
- package/src/index.ts +29 -0
- package/src/logger.ts +37 -0
- package/src/moveable-able.css +108 -0
- package/src/style.css +15 -0
- package/src/types.ts +309 -0
- package/src/util.ts +272 -0
- package/types/Rule.d.ts +1 -0
|
@@ -0,0 +1,357 @@
|
|
|
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
|
+
/* eslint-disable no-param-reassign */
|
|
20
|
+
import Moveable, { MoveableOptions } from 'moveable';
|
|
21
|
+
|
|
22
|
+
import { Mode, StageDragStatus } from './const';
|
|
23
|
+
import DragResizeHelper from './DragResizeHelper';
|
|
24
|
+
import MoveableOptionsManager from './MoveableOptionsManager';
|
|
25
|
+
import type {
|
|
26
|
+
DelayedMarkContainer,
|
|
27
|
+
DrEvents,
|
|
28
|
+
GetRenderDocument,
|
|
29
|
+
MarkContainerEnd,
|
|
30
|
+
StageDragResizeConfig,
|
|
31
|
+
} from './types';
|
|
32
|
+
import { down, getMode, up } from './util';
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* 管理单选操作,响应选中操作,初始化moveableOption参数并初始化moveable,处理moveable回调事件对组件进行更新
|
|
36
|
+
* @extends MoveableOptionsManager
|
|
37
|
+
*/
|
|
38
|
+
export default class StageDragResize extends MoveableOptionsManager {
|
|
39
|
+
/** 目标节点 */
|
|
40
|
+
private target: HTMLElement | null = null;
|
|
41
|
+
/** Moveable拖拽类实例 */
|
|
42
|
+
private moveable?: Moveable;
|
|
43
|
+
/** 拖动状态 */
|
|
44
|
+
private dragStatus: StageDragStatus = StageDragStatus.END;
|
|
45
|
+
private dragResizeHelper: DragResizeHelper;
|
|
46
|
+
private disabledDragStart?: boolean;
|
|
47
|
+
private getRenderDocument: GetRenderDocument;
|
|
48
|
+
private markContainerEnd: MarkContainerEnd;
|
|
49
|
+
private delayedMarkContainer: DelayedMarkContainer;
|
|
50
|
+
|
|
51
|
+
constructor(config: StageDragResizeConfig) {
|
|
52
|
+
super(config);
|
|
53
|
+
|
|
54
|
+
this.getRenderDocument = config.getRenderDocument;
|
|
55
|
+
this.markContainerEnd = config.markContainerEnd;
|
|
56
|
+
this.delayedMarkContainer = config.delayedMarkContainer;
|
|
57
|
+
this.disabledDragStart = config.disabledDragStart;
|
|
58
|
+
|
|
59
|
+
this.dragResizeHelper = config.dragResizeHelper;
|
|
60
|
+
|
|
61
|
+
this.on('update-moveable', () => {
|
|
62
|
+
if (this.moveable) {
|
|
63
|
+
this.updateMoveable();
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
public getTarget() {
|
|
69
|
+
return this.target;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* 将选中框渲染并覆盖到选中的组件Dom节点上方
|
|
74
|
+
* 当选中的节点不是absolute时,会创建一个新的节点出来作为拖拽目标
|
|
75
|
+
* @param el 选中组件的Dom节点元素
|
|
76
|
+
* @param event 鼠标事件
|
|
77
|
+
*/
|
|
78
|
+
public select(el: HTMLElement | null, event?: MouseEvent): void {
|
|
79
|
+
if (!el) {
|
|
80
|
+
this.moveable?.destroy();
|
|
81
|
+
this.moveable = undefined;
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
// 从不能拖动到能拖动的节点之间切换,要重新创建moveable,不然dragStart不生效
|
|
85
|
+
if (!this.moveable || el !== this.target) {
|
|
86
|
+
this.initMoveable(el);
|
|
87
|
+
} else {
|
|
88
|
+
this.updateMoveable(el);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (event && !this.disabledDragStart) {
|
|
92
|
+
this.moveable?.dragStart(event);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* 初始化选中框并渲染出来
|
|
98
|
+
*/
|
|
99
|
+
public updateMoveable(el = this.target): void {
|
|
100
|
+
if (!this.moveable) return;
|
|
101
|
+
if (!el) throw new Error('未选中任何节点');
|
|
102
|
+
|
|
103
|
+
const options: MoveableOptions = this.init(el);
|
|
104
|
+
|
|
105
|
+
Object.entries(options).forEach(([key, value]) => {
|
|
106
|
+
(this.moveable as any)[key] = value;
|
|
107
|
+
});
|
|
108
|
+
this.moveable.updateRect();
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
public clearSelectStatus(): void {
|
|
112
|
+
if (!this.moveable) return;
|
|
113
|
+
this.dragResizeHelper.destroyShadowEl();
|
|
114
|
+
this.moveable.target = null;
|
|
115
|
+
this.moveable.updateRect();
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
public getDragStatus(): StageDragStatus {
|
|
119
|
+
return this.dragStatus;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* 销毁实例
|
|
124
|
+
*/
|
|
125
|
+
public destroy(): void {
|
|
126
|
+
this.moveable?.destroy();
|
|
127
|
+
this.dragResizeHelper.destroy();
|
|
128
|
+
this.dragStatus = StageDragStatus.END;
|
|
129
|
+
this.removeAllListeners();
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
public on<Name extends keyof DrEvents, Param extends DrEvents[Name]>(
|
|
133
|
+
eventName: Name,
|
|
134
|
+
listener: (...args: Param) => void | Promise<void>,
|
|
135
|
+
) {
|
|
136
|
+
return super.on(eventName, listener as any);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
public emit<Name extends keyof DrEvents, Param extends DrEvents[Name]>(eventName: Name, ...args: Param) {
|
|
140
|
+
return super.emit(eventName, ...args);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
private init(el: HTMLElement): MoveableOptions {
|
|
144
|
+
// 如果有滚动条会导致resize时获取到width,height不准确
|
|
145
|
+
if (/(auto|scroll)/.test(el.style.overflow)) {
|
|
146
|
+
el.style.overflow = 'hidden';
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
this.target = el;
|
|
150
|
+
this.mode = getMode(el);
|
|
151
|
+
|
|
152
|
+
this.dragResizeHelper.updateShadowEl(el);
|
|
153
|
+
this.dragResizeHelper.setMode(this.mode);
|
|
154
|
+
|
|
155
|
+
this.setElementGuidelines([this.target as HTMLElement]);
|
|
156
|
+
|
|
157
|
+
return this.getOptions(false, {
|
|
158
|
+
target: this.dragResizeHelper.getShadowEl(),
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
private initMoveable(el: HTMLElement) {
|
|
163
|
+
const options: MoveableOptions = this.init(el);
|
|
164
|
+
this.dragResizeHelper.clear();
|
|
165
|
+
|
|
166
|
+
this.moveable?.destroy();
|
|
167
|
+
|
|
168
|
+
this.moveable = new Moveable(this.container, {
|
|
169
|
+
...options,
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
this.bindResizeEvent();
|
|
173
|
+
this.bindDragEvent();
|
|
174
|
+
this.bindRotateEvent();
|
|
175
|
+
this.bindScaleEvent();
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
private bindResizeEvent(): void {
|
|
179
|
+
if (!this.moveable) throw new Error('moveable 未初始化');
|
|
180
|
+
|
|
181
|
+
this.moveable
|
|
182
|
+
.on('resizeStart', (e) => {
|
|
183
|
+
if (!this.target) return;
|
|
184
|
+
|
|
185
|
+
this.dragStatus = StageDragStatus.START;
|
|
186
|
+
this.dragResizeHelper.onResizeStart(e);
|
|
187
|
+
})
|
|
188
|
+
.on('resize', (e) => {
|
|
189
|
+
if (!this.moveable || !this.target || !this.dragResizeHelper.getShadowEl()) return;
|
|
190
|
+
|
|
191
|
+
this.dragStatus = StageDragStatus.ING;
|
|
192
|
+
|
|
193
|
+
this.dragResizeHelper.onResize(e);
|
|
194
|
+
})
|
|
195
|
+
.on('resizeEnd', () => {
|
|
196
|
+
this.dragStatus = StageDragStatus.END;
|
|
197
|
+
this.update(true);
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
private bindDragEvent(): void {
|
|
202
|
+
if (!this.moveable) throw new Error('moveable 未初始化');
|
|
203
|
+
|
|
204
|
+
let timeout: NodeJS.Timeout | undefined;
|
|
205
|
+
|
|
206
|
+
this.moveable
|
|
207
|
+
.on('dragStart', (e) => {
|
|
208
|
+
if (!this.target) throw new Error('未选中组件');
|
|
209
|
+
|
|
210
|
+
this.dragStatus = StageDragStatus.START;
|
|
211
|
+
|
|
212
|
+
this.dragResizeHelper.onDragStart(e);
|
|
213
|
+
this.emit('drag-start', e);
|
|
214
|
+
})
|
|
215
|
+
.on('drag', (e) => {
|
|
216
|
+
if (!this.target || !this.dragResizeHelper.getShadowEl()) return;
|
|
217
|
+
|
|
218
|
+
if (timeout) {
|
|
219
|
+
globalThis.clearTimeout(timeout);
|
|
220
|
+
timeout = undefined;
|
|
221
|
+
}
|
|
222
|
+
timeout = this.delayedMarkContainer(e.inputEvent, [this.target]);
|
|
223
|
+
|
|
224
|
+
this.dragStatus = StageDragStatus.ING;
|
|
225
|
+
|
|
226
|
+
this.dragResizeHelper.onDrag(e);
|
|
227
|
+
})
|
|
228
|
+
.on('dragEnd', () => {
|
|
229
|
+
if (timeout) {
|
|
230
|
+
globalThis.clearTimeout(timeout);
|
|
231
|
+
timeout = undefined;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const parentEl = this.markContainerEnd();
|
|
235
|
+
// 点击不拖动时会触发dragStart和dragEnd,但是不会有drag事件
|
|
236
|
+
if (this.dragStatus === StageDragStatus.ING) {
|
|
237
|
+
if (parentEl) {
|
|
238
|
+
this.update(false, parentEl);
|
|
239
|
+
} else {
|
|
240
|
+
switch (this.mode) {
|
|
241
|
+
case Mode.SORTABLE:
|
|
242
|
+
this.sort();
|
|
243
|
+
break;
|
|
244
|
+
default:
|
|
245
|
+
this.update();
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
this.dragStatus = StageDragStatus.END;
|
|
251
|
+
this.dragResizeHelper.destroyGhostEl();
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
private bindRotateEvent(): void {
|
|
256
|
+
if (!this.moveable) throw new Error('moveable 未初始化');
|
|
257
|
+
|
|
258
|
+
this.moveable
|
|
259
|
+
.on('rotateStart', (e) => {
|
|
260
|
+
this.dragStatus = StageDragStatus.START;
|
|
261
|
+
this.dragResizeHelper.onRotateStart(e);
|
|
262
|
+
})
|
|
263
|
+
.on('rotate', (e) => {
|
|
264
|
+
if (!this.target || !this.dragResizeHelper.getShadowEl()) return;
|
|
265
|
+
this.dragStatus = StageDragStatus.ING;
|
|
266
|
+
this.dragResizeHelper.onRotate(e);
|
|
267
|
+
})
|
|
268
|
+
.on('rotateEnd', (e) => {
|
|
269
|
+
this.dragStatus = StageDragStatus.END;
|
|
270
|
+
const frame = this.dragResizeHelper?.getFrame(e.target);
|
|
271
|
+
if (this.target && frame) {
|
|
272
|
+
this.emit('update', {
|
|
273
|
+
data: [
|
|
274
|
+
{
|
|
275
|
+
el: this.target,
|
|
276
|
+
style: {
|
|
277
|
+
transform: frame.get('transform'),
|
|
278
|
+
},
|
|
279
|
+
},
|
|
280
|
+
],
|
|
281
|
+
parentEl: null,
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
private bindScaleEvent(): void {
|
|
288
|
+
if (!this.moveable) throw new Error('moveable 未初始化');
|
|
289
|
+
|
|
290
|
+
this.moveable
|
|
291
|
+
.on('scaleStart', (e) => {
|
|
292
|
+
this.dragStatus = StageDragStatus.START;
|
|
293
|
+
this.dragResizeHelper.onScaleStart(e);
|
|
294
|
+
})
|
|
295
|
+
.on('scale', (e) => {
|
|
296
|
+
if (!this.target || !this.dragResizeHelper.getShadowEl()) return;
|
|
297
|
+
this.dragStatus = StageDragStatus.ING;
|
|
298
|
+
this.dragResizeHelper.onScale(e);
|
|
299
|
+
})
|
|
300
|
+
.on('scaleEnd', (e) => {
|
|
301
|
+
this.dragStatus = StageDragStatus.END;
|
|
302
|
+
const frame = this.dragResizeHelper.getFrame(e.target);
|
|
303
|
+
if (this.target && frame) {
|
|
304
|
+
this.emit('update', {
|
|
305
|
+
data: [
|
|
306
|
+
{
|
|
307
|
+
el: this.target,
|
|
308
|
+
style: {
|
|
309
|
+
transform: frame.get('transform'),
|
|
310
|
+
},
|
|
311
|
+
},
|
|
312
|
+
],
|
|
313
|
+
parentEl: null,
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
private sort(): void {
|
|
320
|
+
if (!this.target || !this.dragResizeHelper.getGhostEl()) throw new Error('未知错误');
|
|
321
|
+
const { top } = this.dragResizeHelper.getGhostEl()!.getBoundingClientRect();
|
|
322
|
+
const { top: oriTop } = this.target.getBoundingClientRect();
|
|
323
|
+
const deltaTop = top - oriTop;
|
|
324
|
+
if (Math.abs(deltaTop) >= this.target.clientHeight / 2) {
|
|
325
|
+
if (deltaTop > 0) {
|
|
326
|
+
this.emit('sort', down(deltaTop, this.target));
|
|
327
|
+
} else {
|
|
328
|
+
this.emit('sort', up(deltaTop, this.target));
|
|
329
|
+
}
|
|
330
|
+
} else {
|
|
331
|
+
this.emit('sort', {
|
|
332
|
+
src: this.target.id,
|
|
333
|
+
dist: this.target.id,
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
private update(isResize = false, parentEl: HTMLElement | null = null): void {
|
|
339
|
+
if (!this.target) return;
|
|
340
|
+
|
|
341
|
+
const doc = this.getRenderDocument();
|
|
342
|
+
|
|
343
|
+
if (!doc) return;
|
|
344
|
+
|
|
345
|
+
const rect = this.dragResizeHelper.getUpdatedElRect(this.target, parentEl, doc);
|
|
346
|
+
|
|
347
|
+
this.emit('update', {
|
|
348
|
+
data: [
|
|
349
|
+
{
|
|
350
|
+
el: this.target,
|
|
351
|
+
style: isResize ? rect : { left: rect.left, top: rect.top },
|
|
352
|
+
},
|
|
353
|
+
],
|
|
354
|
+
parentEl,
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
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 Moveable from 'moveable';
|
|
22
|
+
|
|
23
|
+
import { HIGHLIGHT_EL_ID_PREFIX, ZIndex } from './const';
|
|
24
|
+
import TargetShadow from './TargetShadow';
|
|
25
|
+
import type { GetRootContainer, StageHighlightConfig } from './types';
|
|
26
|
+
|
|
27
|
+
export default class StageHighlight extends EventEmitter {
|
|
28
|
+
public container: HTMLElement;
|
|
29
|
+
public target?: HTMLElement;
|
|
30
|
+
public moveable?: Moveable;
|
|
31
|
+
public targetShadow?: TargetShadow;
|
|
32
|
+
private getRootContainer: GetRootContainer;
|
|
33
|
+
|
|
34
|
+
constructor(config: StageHighlightConfig) {
|
|
35
|
+
super();
|
|
36
|
+
|
|
37
|
+
this.container = config.container;
|
|
38
|
+
this.getRootContainer = config.getRootContainer;
|
|
39
|
+
|
|
40
|
+
this.targetShadow = new TargetShadow({
|
|
41
|
+
container: config.container,
|
|
42
|
+
updateDragEl: config.updateDragEl,
|
|
43
|
+
zIndex: ZIndex.HIGHLIGHT_EL,
|
|
44
|
+
idPrefix: HIGHLIGHT_EL_ID_PREFIX,
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* 高亮鼠标悬停的组件
|
|
50
|
+
* @param el 选中组件的Dom节点元素
|
|
51
|
+
*/
|
|
52
|
+
public highlight(el: HTMLElement): void {
|
|
53
|
+
if (!el || el === this.target) return;
|
|
54
|
+
this.target = el;
|
|
55
|
+
|
|
56
|
+
this.targetShadow?.update(el);
|
|
57
|
+
if (this.moveable) {
|
|
58
|
+
this.moveable.zoom = 2;
|
|
59
|
+
this.moveable.updateRect();
|
|
60
|
+
} else {
|
|
61
|
+
this.moveable = new Moveable(this.container, {
|
|
62
|
+
target: this.targetShadow?.el,
|
|
63
|
+
origin: false,
|
|
64
|
+
rootContainer: this.getRootContainer(),
|
|
65
|
+
zoom: 2,
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* 清空高亮
|
|
72
|
+
*/
|
|
73
|
+
public clearHighlight(): void {
|
|
74
|
+
if (!this.moveable || !this.target) return;
|
|
75
|
+
this.moveable.zoom = 0;
|
|
76
|
+
this.moveable.updateRect();
|
|
77
|
+
this.target = undefined;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* 销毁实例
|
|
82
|
+
*/
|
|
83
|
+
public destroy(): void {
|
|
84
|
+
this.moveable?.destroy();
|
|
85
|
+
this.targetShadow?.destroy();
|
|
86
|
+
this.moveable = undefined;
|
|
87
|
+
this.targetShadow = undefined;
|
|
88
|
+
}
|
|
89
|
+
}
|