@tmagic/stage 1.4.6 → 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.
- package/dist/tmagic-stage.js +69 -138
- package/dist/tmagic-stage.umd.cjs +69 -138
- package/package.json +9 -5
- package/src/ActionManager.ts +0 -661
- package/src/DragResizeHelper.ts +0 -396
- package/src/MoveableActionsAble.ts +0 -91
- package/src/MoveableOptionsManager.ts +0 -274
- package/src/Rule.ts +0 -173
- package/src/StageCore.ts +0 -417
- package/src/StageDragResize.ts +0 -357
- package/src/StageHighlight.ts +0 -89
- package/src/StageMask.ts +0 -337
- package/src/StageMultiDragResize.ts +0 -229
- package/src/StageRender.ts +0 -252
- package/src/TargetShadow.ts +0 -122
- package/src/const.ts +0 -111
- package/src/index.ts +0 -29
- package/src/logger.ts +0 -37
- package/src/moveable-able.css +0 -108
- package/src/style.css +0 -15
- package/src/types.ts +0 -309
- package/src/util.ts +0 -272
- package/tsconfig.build.json +0 -13
package/src/ActionManager.ts
DELETED
|
@@ -1,661 +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
|
-
import EventEmitter from 'events';
|
|
19
|
-
|
|
20
|
-
import KeyController from 'keycon';
|
|
21
|
-
import { throttle } from 'lodash-es';
|
|
22
|
-
import type { MoveableOptions, OnDragStart } from 'moveable';
|
|
23
|
-
|
|
24
|
-
import { Env } from '@tmagic/core';
|
|
25
|
-
import type { Id } from '@tmagic/schema';
|
|
26
|
-
import { addClassName, getDocument, removeClassNameByClassName } from '@tmagic/utils';
|
|
27
|
-
|
|
28
|
-
import {
|
|
29
|
-
AbleActionEventType,
|
|
30
|
-
CONTAINER_HIGHLIGHT_CLASS_NAME,
|
|
31
|
-
ContainerHighlightType,
|
|
32
|
-
GHOST_EL_ID_PREFIX,
|
|
33
|
-
GuidesType,
|
|
34
|
-
MouseButton,
|
|
35
|
-
PAGE_CLASS,
|
|
36
|
-
SelectStatus,
|
|
37
|
-
StageDragStatus,
|
|
38
|
-
} from './const';
|
|
39
|
-
import DragResizeHelper from './DragResizeHelper';
|
|
40
|
-
import StageDragResize from './StageDragResize';
|
|
41
|
-
import StageHighlight from './StageHighlight';
|
|
42
|
-
import StageMultiDragResize from './StageMultiDragResize';
|
|
43
|
-
import type {
|
|
44
|
-
ActionManagerConfig,
|
|
45
|
-
ActionManagerEvents,
|
|
46
|
-
CanSelect,
|
|
47
|
-
CustomizeMoveableOptions,
|
|
48
|
-
CustomizeMoveableOptionsCallbackConfig,
|
|
49
|
-
GetElementsFromPoint,
|
|
50
|
-
GetRenderDocument,
|
|
51
|
-
GetTargetElement,
|
|
52
|
-
IsContainer,
|
|
53
|
-
Point,
|
|
54
|
-
RemoveEventData,
|
|
55
|
-
SortEventData,
|
|
56
|
-
UpdateEventData,
|
|
57
|
-
} from './types';
|
|
58
|
-
import { isMoveableButton } from './util';
|
|
59
|
-
|
|
60
|
-
const throttleTime = 100;
|
|
61
|
-
const defaultContainerHighlightDuration = 800;
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* 管理蒙层mask之上的操作:1、监听键盘鼠标事件,判断形成单选、多选、高亮操作;2、管理单选、多选、高亮三个类协同工作。
|
|
65
|
-
* @extends EventEmitter
|
|
66
|
-
*/
|
|
67
|
-
export default class ActionManager extends EventEmitter {
|
|
68
|
-
private dr: StageDragResize;
|
|
69
|
-
private multiDr?: StageMultiDragResize;
|
|
70
|
-
private highlightLayer: StageHighlight;
|
|
71
|
-
/** 单选、多选、高亮的容器(蒙层的content) */
|
|
72
|
-
private container: HTMLElement;
|
|
73
|
-
/** 当前选中的节点 */
|
|
74
|
-
private selectedEl: HTMLElement | null = null;
|
|
75
|
-
/** 多选选中的节点组 */
|
|
76
|
-
private selectedElList: HTMLElement[] = [];
|
|
77
|
-
/** 当前高亮的节点 */
|
|
78
|
-
private highlightedEl: HTMLElement | undefined;
|
|
79
|
-
/** 当前是否处于多选状态 */
|
|
80
|
-
private isMultiSelectStatus = false;
|
|
81
|
-
/** 当拖拽组件到容器上方进入可加入容器状态时,给容器添加的一个class名称 */
|
|
82
|
-
private containerHighlightClassName: string;
|
|
83
|
-
/** 当拖拽组件到容器上方时,需要悬停多久才能将组件加入容器 */
|
|
84
|
-
private containerHighlightDuration: number;
|
|
85
|
-
/** 将组件加入容器的操作方式 */
|
|
86
|
-
private containerHighlightType?: ContainerHighlightType;
|
|
87
|
-
private isAltKeydown = false;
|
|
88
|
-
private getTargetElement: GetTargetElement;
|
|
89
|
-
private getElementsFromPoint: GetElementsFromPoint;
|
|
90
|
-
private canSelect: CanSelect;
|
|
91
|
-
private isContainer?: IsContainer;
|
|
92
|
-
private getRenderDocument: GetRenderDocument;
|
|
93
|
-
private disabledMultiSelect = false;
|
|
94
|
-
private config: ActionManagerConfig;
|
|
95
|
-
|
|
96
|
-
private mouseMoveHandler = throttle(async (event: MouseEvent): Promise<void> => {
|
|
97
|
-
if ((event.target as HTMLDivElement)?.classList?.contains('moveable-direction')) {
|
|
98
|
-
return;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
const el = await this.getElementFromPoint(event);
|
|
102
|
-
if (!el) {
|
|
103
|
-
this.clearHighlight();
|
|
104
|
-
return;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
this.emit('mousemove', event);
|
|
108
|
-
this.highlight(el.id);
|
|
109
|
-
}, throttleTime);
|
|
110
|
-
|
|
111
|
-
constructor(config: ActionManagerConfig) {
|
|
112
|
-
super();
|
|
113
|
-
this.config = config;
|
|
114
|
-
this.container = config.container;
|
|
115
|
-
this.containerHighlightClassName = config.containerHighlightClassName || CONTAINER_HIGHLIGHT_CLASS_NAME;
|
|
116
|
-
this.containerHighlightDuration = config.containerHighlightDuration || defaultContainerHighlightDuration;
|
|
117
|
-
this.containerHighlightType = config.containerHighlightType;
|
|
118
|
-
this.disabledMultiSelect = config.disabledMultiSelect ?? false;
|
|
119
|
-
this.getTargetElement = config.getTargetElement;
|
|
120
|
-
this.getElementsFromPoint = config.getElementsFromPoint;
|
|
121
|
-
this.canSelect = config.canSelect || ((el: HTMLElement) => !!el.id);
|
|
122
|
-
this.getRenderDocument = config.getRenderDocument;
|
|
123
|
-
this.isContainer = config.isContainer;
|
|
124
|
-
|
|
125
|
-
this.dr = this.createDr(config);
|
|
126
|
-
|
|
127
|
-
if (!this.disabledMultiSelect) {
|
|
128
|
-
this.multiDr = this.createMultiDr(config);
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
this.highlightLayer = new StageHighlight({
|
|
132
|
-
container: config.container,
|
|
133
|
-
updateDragEl: config.updateDragEl,
|
|
134
|
-
getRootContainer: config.getRootContainer,
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
this.initMouseEvent();
|
|
138
|
-
this.initKeyEvent();
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
public disableMultiSelect() {
|
|
142
|
-
this.disabledMultiSelect = true;
|
|
143
|
-
if (this.multiDr) {
|
|
144
|
-
this.multiDr.destroy();
|
|
145
|
-
this.multiDr = undefined;
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
public enableMultiSelect() {
|
|
150
|
-
this.disabledMultiSelect = false;
|
|
151
|
-
|
|
152
|
-
if (!this.multiDr) {
|
|
153
|
-
this.multiDr = this.createMultiDr(this.config);
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
/**
|
|
158
|
-
* 设置水平/垂直参考线
|
|
159
|
-
* @param type 参考线类型
|
|
160
|
-
* @param guidelines 参考线坐标数组
|
|
161
|
-
*/
|
|
162
|
-
public setGuidelines(type: GuidesType, guidelines: number[]): void {
|
|
163
|
-
this.dr.setGuidelines(type, guidelines);
|
|
164
|
-
this.multiDr?.setGuidelines(type, guidelines);
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
/**
|
|
168
|
-
* 清空所有参考线
|
|
169
|
-
*/
|
|
170
|
-
public clearGuides(): void {
|
|
171
|
-
this.dr.clearGuides();
|
|
172
|
-
this.multiDr?.clearGuides();
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
/**
|
|
176
|
-
* 更新moveable,外部主要调用场景是元素配置变更、页面大小变更
|
|
177
|
-
* @param el 变更的元素
|
|
178
|
-
*/
|
|
179
|
-
public updateMoveable(el?: HTMLElement): void {
|
|
180
|
-
this.dr.updateMoveable(el);
|
|
181
|
-
// 多选时不可配置元素,因此不存在多选元素变更,不需要传el
|
|
182
|
-
this.multiDr?.updateMoveable();
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
/**
|
|
186
|
-
* 判断是否单选选中的元素
|
|
187
|
-
*/
|
|
188
|
-
public isSelectedEl(el: HTMLElement): boolean {
|
|
189
|
-
// 有可能dom已经重新渲染,不再是原来的dom了,所以这里判断id,而不是判断el === this.selectedDom
|
|
190
|
-
return el.id === this.selectedEl?.id;
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
public setSelectedEl(el: HTMLElement | null): void {
|
|
194
|
-
this.selectedEl = el;
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
public getSelectedEl(): HTMLElement | null {
|
|
198
|
-
return this.selectedEl;
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
public getSelectedElList(): HTMLElement[] {
|
|
202
|
-
return this.selectedElList;
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
public getMoveableOption<K extends keyof MoveableOptions>(key: K): MoveableOptions[K] | undefined {
|
|
206
|
-
if (this.dr.getTarget()) {
|
|
207
|
-
return this.dr.getOption(key);
|
|
208
|
-
}
|
|
209
|
-
if (this.multiDr?.targetList.length) {
|
|
210
|
-
return this.multiDr.getOption(key);
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
/**
|
|
215
|
-
* 获取鼠标下方第一个可选中元素,如果元素层叠,返回到是最上层元素
|
|
216
|
-
* @param event 鼠标事件
|
|
217
|
-
* @returns 鼠标下方第一个可选中元素
|
|
218
|
-
*/
|
|
219
|
-
public async getElementFromPoint(event: MouseEvent): Promise<HTMLElement | null> {
|
|
220
|
-
const els = this.getElementsFromPoint(event as Point);
|
|
221
|
-
|
|
222
|
-
this.emit('get-elements-from-point', els);
|
|
223
|
-
|
|
224
|
-
let stopped = false;
|
|
225
|
-
const stop = () => (stopped = true);
|
|
226
|
-
for (const el of els) {
|
|
227
|
-
if (!el.id.startsWith(GHOST_EL_ID_PREFIX) && (await this.isElCanSelect(el, event, stop))) {
|
|
228
|
-
if (stopped) break;
|
|
229
|
-
return el;
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
return null;
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
/**
|
|
236
|
-
* 判断一个元素能否在当前场景被选中
|
|
237
|
-
* @param el 被判断的元素
|
|
238
|
-
* @param event 鼠标事件
|
|
239
|
-
* @param stop 通过该元素如果得知剩下的元素都不可被选中,通知调用方终止对剩下元素的判断
|
|
240
|
-
* @returns 能否选中
|
|
241
|
-
*/
|
|
242
|
-
public async isElCanSelect(el: HTMLElement, event: MouseEvent, stop: () => boolean): Promise<boolean> {
|
|
243
|
-
// 执行业务方传入的判断逻辑
|
|
244
|
-
const canSelectByProp = await this.canSelect(el, event, stop);
|
|
245
|
-
if (!canSelectByProp) return false;
|
|
246
|
-
// 多选规则
|
|
247
|
-
if (this.isMultiSelectStatus) {
|
|
248
|
-
return this.canMultiSelect(el, stop);
|
|
249
|
-
}
|
|
250
|
-
return true;
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
/**
|
|
254
|
-
* 判断一个元素是否可以被多选,如果当前元素是page,则调stop函数告诉调用方不必继续判断其它元素了
|
|
255
|
-
*/
|
|
256
|
-
public canMultiSelect(el: HTMLElement, stop: () => boolean): boolean {
|
|
257
|
-
// 多选状态下不可以选中magic-ui-page,并停止继续向上层选中
|
|
258
|
-
if (el.className.includes(PAGE_CLASS)) {
|
|
259
|
-
stop();
|
|
260
|
-
return false;
|
|
261
|
-
}
|
|
262
|
-
const selectedEl = this.getSelectedEl();
|
|
263
|
-
// 先单击选中了页面(magic-ui-page),再按住多选键多选时,任一元素均可选中
|
|
264
|
-
if (selectedEl?.className.includes(PAGE_CLASS)) {
|
|
265
|
-
return true;
|
|
266
|
-
}
|
|
267
|
-
return this.multiDr?.canSelect(el, selectedEl) || false;
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
public select(el: HTMLElement | null, event?: MouseEvent): void {
|
|
271
|
-
this.setSelectedEl(el);
|
|
272
|
-
this.clearSelectStatus(SelectStatus.MULTI_SELECT);
|
|
273
|
-
this.dr.select(el, event);
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
public multiSelect(ids: Id[]): void {
|
|
277
|
-
this.selectedElList = [];
|
|
278
|
-
ids.forEach((id) => {
|
|
279
|
-
const el = this.getTargetElement(id);
|
|
280
|
-
if (el) {
|
|
281
|
-
this.selectedElList.push(el);
|
|
282
|
-
}
|
|
283
|
-
});
|
|
284
|
-
this.clearSelectStatus(SelectStatus.SELECT);
|
|
285
|
-
this.multiDr?.multiSelect(this.selectedElList);
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
public getHighlightEl(): HTMLElement | undefined {
|
|
289
|
-
return this.highlightedEl;
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
public setHighlightEl(el: HTMLElement | undefined): void {
|
|
293
|
-
this.highlightedEl = el;
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
public highlight(id: Id): void {
|
|
297
|
-
let el;
|
|
298
|
-
try {
|
|
299
|
-
el = this.getTargetElement(id);
|
|
300
|
-
} catch (error) {
|
|
301
|
-
this.clearHighlight();
|
|
302
|
-
return;
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
// 选中组件不高亮、多选拖拽状态不高亮
|
|
306
|
-
if (el === this.getSelectedEl() || this.multiDr?.dragStatus === StageDragStatus.ING) {
|
|
307
|
-
this.clearHighlight();
|
|
308
|
-
return;
|
|
309
|
-
}
|
|
310
|
-
if (el === this.highlightedEl || !el) return;
|
|
311
|
-
|
|
312
|
-
this.highlightLayer.highlight(el);
|
|
313
|
-
this.highlightedEl = el;
|
|
314
|
-
this.emit('highlight', el);
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
public clearHighlight(): void {
|
|
318
|
-
this.setHighlightEl(undefined);
|
|
319
|
-
this.highlightLayer.clearHighlight();
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
/**
|
|
323
|
-
* 用于在切换选择模式时清除上一次的状态
|
|
324
|
-
* @param selectType 需要清理的选择模式
|
|
325
|
-
*/
|
|
326
|
-
public clearSelectStatus(selectType: SelectStatus): void {
|
|
327
|
-
if (selectType === SelectStatus.MULTI_SELECT) {
|
|
328
|
-
this.multiDr?.clearSelectStatus();
|
|
329
|
-
this.selectedElList = [];
|
|
330
|
-
} else {
|
|
331
|
-
this.dr.clearSelectStatus();
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
/**
|
|
336
|
-
* 找到鼠标下方的容器,通过添加className对容器进行标记
|
|
337
|
-
* @param event 鼠标事件
|
|
338
|
-
* @param excludeElList 计算鼠标点所在容器时要排除的元素列表
|
|
339
|
-
*/
|
|
340
|
-
public async addContainerHighlightClassName(event: MouseEvent, excludeElList: Element[]): Promise<void> {
|
|
341
|
-
const doc = this.getRenderDocument();
|
|
342
|
-
if (!doc) return;
|
|
343
|
-
|
|
344
|
-
const els = this.getElementsFromPoint(event);
|
|
345
|
-
|
|
346
|
-
for (const el of els) {
|
|
347
|
-
if (!el.id.startsWith(GHOST_EL_ID_PREFIX) && (await this.isContainer?.(el)) && !excludeElList.includes(el)) {
|
|
348
|
-
addClassName(el, doc, this.containerHighlightClassName);
|
|
349
|
-
break;
|
|
350
|
-
}
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
/**
|
|
355
|
-
* 鼠标拖拽着元素,在容器上方悬停,延迟一段时间后,对容器进行标记,如果悬停时间够长将标记成功,悬停时间短,调用方通过返回的timeoutId取消标记
|
|
356
|
-
* 标记的作用:1、高亮容器,给用户一个加入容器的交互感知;2、释放鼠标后,通过标记的标志找到要加入的容器
|
|
357
|
-
* @param event 鼠标事件
|
|
358
|
-
* @param excludeElList 计算鼠标所在容器时要排除的元素列表
|
|
359
|
-
* @returns timeoutId,调用方在鼠标移走时要取消该timeout,阻止标记
|
|
360
|
-
*/
|
|
361
|
-
public delayedMarkContainer(event: MouseEvent, excludeElList: Element[] = []): NodeJS.Timeout | undefined {
|
|
362
|
-
if (this.canAddToContainer()) {
|
|
363
|
-
return globalThis.setTimeout(() => {
|
|
364
|
-
this.addContainerHighlightClassName(event, excludeElList);
|
|
365
|
-
}, this.containerHighlightDuration);
|
|
366
|
-
}
|
|
367
|
-
return undefined;
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
public getDragStatus() {
|
|
371
|
-
return this.dr.getDragStatus();
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
public destroy(): void {
|
|
375
|
-
this.container.removeEventListener('mousedown', this.mouseDownHandler);
|
|
376
|
-
this.container.removeEventListener('mousemove', this.mouseMoveHandler);
|
|
377
|
-
this.container.removeEventListener('mouseleave', this.mouseLeaveHandler);
|
|
378
|
-
this.container.removeEventListener('wheel', this.mouseWheelHandler);
|
|
379
|
-
this.container.removeEventListener('dblclick', this.dblclickHandler);
|
|
380
|
-
this.dr.destroy();
|
|
381
|
-
this.multiDr?.destroy();
|
|
382
|
-
this.highlightLayer.destroy();
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
public on<Name extends keyof ActionManagerEvents, Param extends ActionManagerEvents[Name]>(
|
|
386
|
-
eventName: Name,
|
|
387
|
-
listener: (...args: Param) => void,
|
|
388
|
-
) {
|
|
389
|
-
return super.on(eventName, listener as any);
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
public emit<Name extends keyof ActionManagerEvents, Param extends ActionManagerEvents[Name]>(
|
|
393
|
-
eventName: Name,
|
|
394
|
-
...args: Param
|
|
395
|
-
) {
|
|
396
|
-
return super.emit(eventName, ...args);
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
private createDr(config: ActionManagerConfig) {
|
|
400
|
-
const createDrHelper = () =>
|
|
401
|
-
new DragResizeHelper({
|
|
402
|
-
container: config.container,
|
|
403
|
-
updateDragEl: config.updateDragEl,
|
|
404
|
-
});
|
|
405
|
-
|
|
406
|
-
const dr = new StageDragResize({
|
|
407
|
-
container: config.container,
|
|
408
|
-
disabledDragStart: config.disabledDragStart,
|
|
409
|
-
moveableOptions: this.changeCallback(config.moveableOptions, false),
|
|
410
|
-
dragResizeHelper: createDrHelper(),
|
|
411
|
-
getRootContainer: config.getRootContainer,
|
|
412
|
-
getRenderDocument: config.getRenderDocument,
|
|
413
|
-
markContainerEnd: this.markContainerEnd.bind(this),
|
|
414
|
-
delayedMarkContainer: this.delayedMarkContainer.bind(this),
|
|
415
|
-
});
|
|
416
|
-
|
|
417
|
-
dr.on('update', (data: UpdateEventData) => {
|
|
418
|
-
// 点击组件并立即拖动的场景,要保证select先被触发,延迟update通知
|
|
419
|
-
setTimeout(() => this.emit('update', data));
|
|
420
|
-
})
|
|
421
|
-
.on('sort', (data: SortEventData) => {
|
|
422
|
-
// 点击组件并立即拖动的场景,要保证select先被触发,延迟update通知
|
|
423
|
-
setTimeout(() => this.emit('sort', data));
|
|
424
|
-
})
|
|
425
|
-
.on(AbleActionEventType.SELECT_PARENT, () => {
|
|
426
|
-
this.emit('select-parent');
|
|
427
|
-
})
|
|
428
|
-
.on(AbleActionEventType.REMOVE, () => {
|
|
429
|
-
const drTarget = this.dr.getTarget();
|
|
430
|
-
if (!drTarget) return;
|
|
431
|
-
const data: RemoveEventData = {
|
|
432
|
-
data: [{ el: drTarget }],
|
|
433
|
-
};
|
|
434
|
-
this.emit('remove', data);
|
|
435
|
-
})
|
|
436
|
-
.on('drag-start', (e: OnDragStart) => {
|
|
437
|
-
this.emit('drag-start', e);
|
|
438
|
-
});
|
|
439
|
-
|
|
440
|
-
return dr;
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
private createMultiDr(config: ActionManagerConfig) {
|
|
444
|
-
const createDrHelper = () =>
|
|
445
|
-
new DragResizeHelper({
|
|
446
|
-
container: config.container,
|
|
447
|
-
updateDragEl: config.updateDragEl,
|
|
448
|
-
});
|
|
449
|
-
const multiDr = new StageMultiDragResize({
|
|
450
|
-
container: config.container,
|
|
451
|
-
moveableOptions: this.changeCallback(config.moveableOptions, true),
|
|
452
|
-
dragResizeHelper: createDrHelper(),
|
|
453
|
-
getRootContainer: config.getRootContainer,
|
|
454
|
-
getRenderDocument: config.getRenderDocument,
|
|
455
|
-
markContainerEnd: this.markContainerEnd.bind(this),
|
|
456
|
-
delayedMarkContainer: this.delayedMarkContainer.bind(this),
|
|
457
|
-
});
|
|
458
|
-
|
|
459
|
-
multiDr
|
|
460
|
-
?.on('update', (data: UpdateEventData) => {
|
|
461
|
-
this.emit('multi-update', data);
|
|
462
|
-
})
|
|
463
|
-
.on('change-to-select', (id: Id, e: MouseEvent) => {
|
|
464
|
-
// 如果还在多选状态,不触发切换到单选
|
|
465
|
-
if (this.isMultiSelectStatus) return;
|
|
466
|
-
this.emit('change-to-select', id, e);
|
|
467
|
-
});
|
|
468
|
-
|
|
469
|
-
return multiDr;
|
|
470
|
-
}
|
|
471
|
-
|
|
472
|
-
private changeCallback(options: CustomizeMoveableOptions, isMulti: boolean): CustomizeMoveableOptions {
|
|
473
|
-
// 在actionManager才能获取到各种参数,在这里传好参数有比较好的扩展性
|
|
474
|
-
if (typeof options === 'function') {
|
|
475
|
-
return () => {
|
|
476
|
-
// 要再判断一次,不然过不了ts检查
|
|
477
|
-
if (typeof options === 'function') {
|
|
478
|
-
const cfg: CustomizeMoveableOptionsCallbackConfig = {
|
|
479
|
-
targetEl: this.selectedEl,
|
|
480
|
-
targetElId: this.selectedEl?.id,
|
|
481
|
-
targetEls: this.selectedElList,
|
|
482
|
-
targetElIds: this.selectedElList?.map((item) => item.id),
|
|
483
|
-
isMulti,
|
|
484
|
-
document: this.getRenderDocument(),
|
|
485
|
-
};
|
|
486
|
-
return options(cfg);
|
|
487
|
-
}
|
|
488
|
-
return options;
|
|
489
|
-
};
|
|
490
|
-
}
|
|
491
|
-
return options;
|
|
492
|
-
}
|
|
493
|
-
|
|
494
|
-
/**
|
|
495
|
-
* 在执行多选逻辑前,先准备好多选选中元素
|
|
496
|
-
* @param el 新选中的元素
|
|
497
|
-
* @returns 多选选中的元素列表
|
|
498
|
-
*/
|
|
499
|
-
private async beforeMultiSelect(event: MouseEvent): Promise<void> {
|
|
500
|
-
const el = await this.getElementFromPoint(event);
|
|
501
|
-
if (!el) return;
|
|
502
|
-
|
|
503
|
-
// 如果已有单选选中元素,不是magic-ui-page就可以加入多选列表
|
|
504
|
-
if (this.selectedEl && !this.selectedEl.className.includes(PAGE_CLASS)) {
|
|
505
|
-
this.selectedElList.push(this.selectedEl as HTMLElement);
|
|
506
|
-
this.setSelectedEl(null);
|
|
507
|
-
}
|
|
508
|
-
|
|
509
|
-
// 判断元素是否已在多选列表
|
|
510
|
-
const existIndex = this.selectedElList.findIndex((selectedDom) => selectedDom.id === el.id);
|
|
511
|
-
if (existIndex !== -1) {
|
|
512
|
-
// 再次点击取消选中
|
|
513
|
-
if (this.selectedElList.length > 1) {
|
|
514
|
-
this.selectedElList.splice(existIndex, 1);
|
|
515
|
-
}
|
|
516
|
-
} else {
|
|
517
|
-
this.selectedElList.push(el);
|
|
518
|
-
}
|
|
519
|
-
}
|
|
520
|
-
|
|
521
|
-
/**
|
|
522
|
-
* 当前状态下能否将组件加入容器,默认是鼠标悬停一段时间加入,alt模式则是按住alt+鼠标悬停一段时间加入
|
|
523
|
-
*/
|
|
524
|
-
private canAddToContainer(): boolean {
|
|
525
|
-
return (
|
|
526
|
-
this.containerHighlightType === ContainerHighlightType.DEFAULT ||
|
|
527
|
-
(this.containerHighlightType === ContainerHighlightType.ALT && this.isAltKeydown)
|
|
528
|
-
);
|
|
529
|
-
}
|
|
530
|
-
|
|
531
|
-
/**
|
|
532
|
-
* 结束对container的标记状态
|
|
533
|
-
* @returns 标记的容器元素,没有标记的容器时返回null
|
|
534
|
-
*/
|
|
535
|
-
private markContainerEnd(): HTMLElement | null {
|
|
536
|
-
const doc = this.getRenderDocument();
|
|
537
|
-
if (doc && this.canAddToContainer()) {
|
|
538
|
-
return removeClassNameByClassName(doc, this.containerHighlightClassName);
|
|
539
|
-
}
|
|
540
|
-
return null;
|
|
541
|
-
}
|
|
542
|
-
|
|
543
|
-
private initMouseEvent(): void {
|
|
544
|
-
this.container.addEventListener('mousedown', this.mouseDownHandler);
|
|
545
|
-
this.container.addEventListener('mousemove', this.mouseMoveHandler);
|
|
546
|
-
this.container.addEventListener('mouseleave', this.mouseLeaveHandler);
|
|
547
|
-
this.container.addEventListener('wheel', this.mouseWheelHandler);
|
|
548
|
-
this.container.addEventListener('dblclick', this.dblclickHandler);
|
|
549
|
-
}
|
|
550
|
-
|
|
551
|
-
/**
|
|
552
|
-
* 初始化键盘事件监听
|
|
553
|
-
*/
|
|
554
|
-
private initKeyEvent(): void {
|
|
555
|
-
const { isMac } = new Env();
|
|
556
|
-
const ctrl = isMac ? 'meta' : 'ctrl';
|
|
557
|
-
|
|
558
|
-
// 多选启用状态监听
|
|
559
|
-
KeyController.global.keydown(ctrl, (e) => {
|
|
560
|
-
e.inputEvent.preventDefault();
|
|
561
|
-
if (!this.disabledMultiSelect) {
|
|
562
|
-
this.isMultiSelectStatus = true;
|
|
563
|
-
}
|
|
564
|
-
});
|
|
565
|
-
// ctrl+tab切到其他窗口,需要将多选状态置为false
|
|
566
|
-
KeyController.global.on('blur', () => {
|
|
567
|
-
if (!this.disabledMultiSelect) {
|
|
568
|
-
this.isMultiSelectStatus = false;
|
|
569
|
-
}
|
|
570
|
-
});
|
|
571
|
-
KeyController.global.keyup(ctrl, (e) => {
|
|
572
|
-
e.inputEvent.preventDefault();
|
|
573
|
-
if (!this.disabledMultiSelect) {
|
|
574
|
-
this.isMultiSelectStatus = false;
|
|
575
|
-
}
|
|
576
|
-
});
|
|
577
|
-
|
|
578
|
-
// alt健监听,用于启用拖拽组件加入容器状态
|
|
579
|
-
KeyController.global.keydown('alt', (e) => {
|
|
580
|
-
e.inputEvent.preventDefault();
|
|
581
|
-
this.isAltKeydown = true;
|
|
582
|
-
});
|
|
583
|
-
|
|
584
|
-
KeyController.global.keyup('alt', (e) => {
|
|
585
|
-
e.inputEvent.preventDefault();
|
|
586
|
-
this.markContainerEnd();
|
|
587
|
-
this.isAltKeydown = false;
|
|
588
|
-
});
|
|
589
|
-
}
|
|
590
|
-
|
|
591
|
-
/**
|
|
592
|
-
* 在down事件中集中cpu处理画布中选中操作渲染,在up事件中再通知外面的编辑器更新
|
|
593
|
-
*/
|
|
594
|
-
private mouseDownHandler = async (event: MouseEvent): Promise<void> => {
|
|
595
|
-
this.clearHighlight();
|
|
596
|
-
event.stopImmediatePropagation();
|
|
597
|
-
event.stopPropagation();
|
|
598
|
-
|
|
599
|
-
if (this.isStopTriggerSelect(event)) return;
|
|
600
|
-
|
|
601
|
-
// 点击状态下不触发高亮事件
|
|
602
|
-
this.container.removeEventListener('mousemove', this.mouseMoveHandler);
|
|
603
|
-
|
|
604
|
-
// 判断触发多选还是单选
|
|
605
|
-
if (this.isMultiSelectStatus) {
|
|
606
|
-
await this.beforeMultiSelect(event);
|
|
607
|
-
if (this.selectedElList.length > 0) {
|
|
608
|
-
this.emit('before-multi-select', this.selectedElList);
|
|
609
|
-
}
|
|
610
|
-
} else {
|
|
611
|
-
const el = await this.getElementFromPoint(event);
|
|
612
|
-
if (!el) return;
|
|
613
|
-
this.emit('before-select', el, event);
|
|
614
|
-
}
|
|
615
|
-
|
|
616
|
-
getDocument().addEventListener('mouseup', this.mouseUpHandler);
|
|
617
|
-
};
|
|
618
|
-
|
|
619
|
-
private isStopTriggerSelect(event: MouseEvent): boolean {
|
|
620
|
-
if (event.button !== MouseButton.LEFT && event.button !== MouseButton.RIGHT) return true;
|
|
621
|
-
if (!event.target) return true;
|
|
622
|
-
|
|
623
|
-
const targetClassList = (event.target as HTMLDivElement).classList;
|
|
624
|
-
|
|
625
|
-
// 如果单击多选选中区域,则不需要再触发选中了,要支持此处单击后进行拖动
|
|
626
|
-
if (!this.isMultiSelectStatus && targetClassList.contains('moveable-area')) {
|
|
627
|
-
return true;
|
|
628
|
-
}
|
|
629
|
-
// 点击对象如果是边框锚点,则可能是resize; 点击对象是功能按钮
|
|
630
|
-
if (targetClassList.contains('moveable-control') || isMoveableButton(event.target as Element)) {
|
|
631
|
-
return true;
|
|
632
|
-
}
|
|
633
|
-
return false;
|
|
634
|
-
}
|
|
635
|
-
|
|
636
|
-
/**
|
|
637
|
-
* 在up事件中负责对外通知选中事件,通知画布之外的编辑器更新
|
|
638
|
-
*/
|
|
639
|
-
private mouseUpHandler = (event: MouseEvent): void => {
|
|
640
|
-
getDocument().removeEventListener('mouseup', this.mouseUpHandler);
|
|
641
|
-
this.container.addEventListener('mousemove', this.mouseMoveHandler);
|
|
642
|
-
if (this.isMultiSelectStatus) {
|
|
643
|
-
this.emit('multi-select', this.selectedElList, event);
|
|
644
|
-
} else {
|
|
645
|
-
this.emit('select', this.selectedEl, event);
|
|
646
|
-
}
|
|
647
|
-
};
|
|
648
|
-
|
|
649
|
-
private mouseLeaveHandler = (event: MouseEvent) => {
|
|
650
|
-
setTimeout(() => this.clearHighlight(), throttleTime);
|
|
651
|
-
this.emit('mouseleave', event);
|
|
652
|
-
};
|
|
653
|
-
|
|
654
|
-
private mouseWheelHandler = () => {
|
|
655
|
-
this.clearHighlight();
|
|
656
|
-
};
|
|
657
|
-
|
|
658
|
-
private dblclickHandler = (event: MouseEvent) => {
|
|
659
|
-
this.emit('dblclick', event);
|
|
660
|
-
};
|
|
661
|
-
}
|