@tmagic/stage 1.1.0-beta.0 → 1.1.0-beta.11

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.
@@ -19,38 +19,32 @@
19
19
  /* eslint-disable no-param-reassign */
20
20
  import { EventEmitter } from 'events';
21
21
 
22
+ import KeyController from 'keycon';
22
23
  import type { MoveableOptions } from 'moveable';
23
24
  import Moveable from 'moveable';
24
25
  import MoveableHelper from 'moveable-helper';
25
26
 
26
- import { addClassName, removeClassNameByClassName } from '@tmagic/utils';
27
+ import { removeClassNameByClassName } from '@tmagic/utils';
27
28
 
28
29
  import { DRAG_EL_ID_PREFIX, GHOST_EL_ID_PREFIX, GuidesType, Mode, ZIndex } from './const';
29
30
  import StageCore from './StageCore';
30
- import type { SortEventData, StageDragResizeConfig } from './types';
31
- import { calcValueByFontsize, getAbsolutePosition, getMode, getOffset } from './util';
32
-
33
- /** 拖动状态 */
34
- enum ActionStatus {
35
- /** 开始拖动 */
36
- START = 'start',
37
- /** 拖动中 */
38
- ING = 'ing',
39
- /** 拖动结束 */
40
- END = 'end',
41
- }
31
+ import StageMask from './StageMask';
32
+ import type { StageDragResizeConfig } from './types';
33
+ import { ContainerHighlightType, StageDragStatus } from './types';
34
+ import { calcValueByFontsize, down, getAbsolutePosition, getMode, getOffset, getTargetElStyle, up } from './util';
42
35
 
43
36
  /**
44
37
  * 选中框
45
38
  */
46
39
  export default class StageDragResize extends EventEmitter {
47
40
  public core: StageCore;
41
+ public mask: StageMask;
48
42
  /** 画布容器 */
49
43
  public container: HTMLElement;
50
44
  /** 目标节点 */
51
45
  public target?: HTMLElement;
52
46
  /** 目标节点在蒙层中的占位节点 */
53
- public dragEl: HTMLDivElement;
47
+ public dragEl?: HTMLDivElement;
54
48
  /** Moveable拖拽类实例 */
55
49
  public moveable?: Moveable;
56
50
  /** 水平参考线 */
@@ -64,19 +58,32 @@ export default class StageDragResize extends EventEmitter {
64
58
 
65
59
  private moveableOptions: MoveableOptions = {};
66
60
  /** 拖动状态 */
67
- private dragStatus: ActionStatus = ActionStatus.END;
61
+ private dragStatus: StageDragStatus = StageDragStatus.END;
68
62
  /** 流式布局下,目标节点的镜像节点 */
69
63
  private ghostEl: HTMLElement | undefined;
70
64
  private moveableHelper?: MoveableHelper;
65
+ private isContainerHighlight: Boolean = false;
71
66
 
72
67
  constructor(config: StageDragResizeConfig) {
73
68
  super();
74
69
 
75
70
  this.core = config.core;
76
71
  this.container = config.container;
72
+ this.mask = config.mask;
77
73
 
78
- this.dragEl = globalThis.document.createElement('div');
79
- this.container.append(this.dragEl);
74
+ KeyController.global.keydown('alt', (e) => {
75
+ e.inputEvent.preventDefault();
76
+ this.isContainerHighlight = true;
77
+ });
78
+ KeyController.global.keyup('alt', (e) => {
79
+ e.inputEvent.preventDefault();
80
+
81
+ const doc = this.core.renderer.contentWindow?.document;
82
+ if (doc && this.canContainerHighlight()) {
83
+ removeClassNameByClassName(doc, this.core.containerHighlightClassName);
84
+ }
85
+ this.isContainerHighlight = false;
86
+ });
80
87
  }
81
88
 
82
89
  /**
@@ -147,6 +154,17 @@ export default class StageDragResize extends EventEmitter {
147
154
  this.updateMoveable();
148
155
  }
149
156
 
157
+ public clearSelectStatus(): void {
158
+ if (!this.moveable) return;
159
+ this.destroyDragEl();
160
+ this.moveable.target = null;
161
+ this.moveable.updateTarget();
162
+ }
163
+
164
+ public destroyDragEl(): void {
165
+ this.dragEl?.remove();
166
+ }
167
+
150
168
  /**
151
169
  * 销毁实例
152
170
  */
@@ -154,7 +172,7 @@ export default class StageDragResize extends EventEmitter {
154
172
  this.moveable?.destroy();
155
173
  this.destroyGhostEl();
156
174
  this.destroyDragEl();
157
- this.dragStatus = ActionStatus.END;
175
+ this.dragStatus = StageDragStatus.END;
158
176
  this.removeAllListeners();
159
177
  }
160
178
 
@@ -166,9 +184,15 @@ export default class StageDragResize extends EventEmitter {
166
184
  this.mode = getMode(el);
167
185
 
168
186
  this.destroyGhostEl();
187
+ this.destroyDragEl();
188
+ this.dragEl = globalThis.document.createElement('div');
189
+ this.container.append(this.dragEl);
190
+ this.dragEl.style.cssText = getTargetElStyle(el);
191
+ this.dragEl.id = `${DRAG_EL_ID_PREFIX}${el.id}`;
169
192
 
170
- this.updateDragEl(el);
171
-
193
+ if (typeof this.core.config.updateDragEl === 'function') {
194
+ this.core.config.updateDragEl(this.dragEl, el);
195
+ }
172
196
  this.moveableOptions = this.getOptions({
173
197
  target: this.dragEl,
174
198
  });
@@ -226,7 +250,7 @@ export default class StageDragResize extends EventEmitter {
226
250
  .on('resizeStart', (e) => {
227
251
  if (!this.target) return;
228
252
 
229
- this.dragStatus = ActionStatus.START;
253
+ this.dragStatus = StageDragStatus.START;
230
254
  this.moveableHelper?.onResizeStart(e);
231
255
 
232
256
  frame.top = this.target.offsetTop;
@@ -237,7 +261,7 @@ export default class StageDragResize extends EventEmitter {
237
261
  if (!this.moveable || !this.target || !this.dragEl) return;
238
262
 
239
263
  const { beforeTranslate } = drag;
240
- this.dragStatus = ActionStatus.ING;
264
+ this.dragStatus = StageDragStatus.ING;
241
265
 
242
266
  this.moveableHelper?.onResize(e);
243
267
 
@@ -253,7 +277,7 @@ export default class StageDragResize extends EventEmitter {
253
277
  this.target.style.height = `${height}px`;
254
278
  })
255
279
  .on('resizeEnd', () => {
256
- this.dragStatus = ActionStatus.END;
280
+ this.dragStatus = StageDragStatus.END;
257
281
  this.update(true);
258
282
  });
259
283
  }
@@ -275,7 +299,7 @@ export default class StageDragResize extends EventEmitter {
275
299
  .on('dragStart', (e) => {
276
300
  if (!this.target) throw new Error('未选中组件');
277
301
 
278
- this.dragStatus = ActionStatus.START;
302
+ this.dragStatus = StageDragStatus.START;
279
303
 
280
304
  this.moveableHelper?.onDragStart(e);
281
305
 
@@ -294,22 +318,11 @@ export default class StageDragResize extends EventEmitter {
294
318
  timeout = undefined;
295
319
  }
296
320
 
297
- timeout = globalThis.setTimeout(async () => {
298
- const els = this.core.getElementsFromPoint(e.inputEvent);
299
- for (const el of els) {
300
- if (
301
- doc &&
302
- !el.id.startsWith(GHOST_EL_ID_PREFIX) &&
303
- el !== this.target &&
304
- (await this.core.isContainer(el))
305
- ) {
306
- addClassName(el, doc, this.core.containerHighlightClassName);
307
- break;
308
- }
309
- }
310
- }, this.core.containerHighlightDuration);
321
+ if (this.canContainerHighlight()) {
322
+ timeout = this.core.getAddContainerHighlightClassNameTimeout(e.inputEvent, [this.target]);
323
+ }
311
324
 
312
- this.dragStatus = ActionStatus.ING;
325
+ this.dragStatus = StageDragStatus.ING;
313
326
 
314
327
  // 流式布局
315
328
  if (this.ghostEl) {
@@ -330,12 +343,12 @@ export default class StageDragResize extends EventEmitter {
330
343
 
331
344
  let parentEl: HTMLElement | null = null;
332
345
 
333
- if (doc) {
346
+ if (doc && this.canContainerHighlight()) {
334
347
  parentEl = removeClassNameByClassName(doc, this.core.containerHighlightClassName);
335
348
  }
336
349
 
337
350
  // 点击不拖动时会触发dragStart和dragEnd,但是不会有drag事件
338
- if (this.dragStatus === ActionStatus.ING) {
351
+ if (this.dragStatus === StageDragStatus.ING) {
339
352
  if (parentEl) {
340
353
  this.update(false, parentEl);
341
354
  } else {
@@ -349,7 +362,7 @@ export default class StageDragResize extends EventEmitter {
349
362
  }
350
363
  }
351
364
 
352
- this.dragStatus = ActionStatus.END;
365
+ this.dragStatus = StageDragStatus.END;
353
366
  this.destroyGhostEl();
354
367
  });
355
368
  }
@@ -359,18 +372,18 @@ export default class StageDragResize extends EventEmitter {
359
372
 
360
373
  this.moveable
361
374
  .on('rotateStart', (e) => {
362
- this.dragStatus = ActionStatus.START;
375
+ this.dragStatus = StageDragStatus.START;
363
376
  this.moveableHelper?.onRotateStart(e);
364
377
  })
365
378
  .on('rotate', (e) => {
366
379
  if (!this.target || !this.dragEl) return;
367
- this.dragStatus = ActionStatus.ING;
380
+ this.dragStatus = StageDragStatus.ING;
368
381
  this.moveableHelper?.onRotate(e);
369
382
  const frame = this.moveableHelper?.getFrame(e.target);
370
383
  this.target.style.transform = frame?.toCSSObject().transform || '';
371
384
  })
372
385
  .on('rotateEnd', (e) => {
373
- this.dragStatus = ActionStatus.END;
386
+ this.dragStatus = StageDragStatus.END;
374
387
  const frame = this.moveableHelper?.getFrame(e.target);
375
388
  this.emit('update', {
376
389
  el: this.target,
@@ -386,18 +399,18 @@ export default class StageDragResize extends EventEmitter {
386
399
 
387
400
  this.moveable
388
401
  .on('scaleStart', (e) => {
389
- this.dragStatus = ActionStatus.START;
402
+ this.dragStatus = StageDragStatus.START;
390
403
  this.moveableHelper?.onScaleStart(e);
391
404
  })
392
405
  .on('scale', (e) => {
393
406
  if (!this.target || !this.dragEl) return;
394
- this.dragStatus = ActionStatus.ING;
407
+ this.dragStatus = StageDragStatus.ING;
395
408
  this.moveableHelper?.onScale(e);
396
409
  const frame = this.moveableHelper?.getFrame(e.target);
397
410
  this.target.style.transform = frame?.toCSSObject().transform || '';
398
411
  })
399
412
  .on('scaleEnd', (e) => {
400
- this.dragStatus = ActionStatus.END;
413
+ this.dragStatus = StageDragStatus.END;
401
414
  const frame = this.moveableHelper?.getFrame(e.target);
402
415
  this.emit('update', {
403
416
  el: this.target,
@@ -455,9 +468,13 @@ export default class StageDragResize extends EventEmitter {
455
468
  }
456
469
 
457
470
  this.emit('update', {
458
- el: this.target,
471
+ data: [
472
+ {
473
+ el: this.target,
474
+ style: isResize ? { left, top, width, height } : { left, top },
475
+ },
476
+ ],
459
477
  parentEl,
460
- style: isResize ? { left, top, width, height } : { left, top },
461
478
  });
462
479
  }
463
480
 
@@ -467,6 +484,7 @@ export default class StageDragResize extends EventEmitter {
467
484
  }
468
485
 
469
486
  const ghostEl = el.cloneNode(true) as HTMLElement;
487
+ this.setGhostElChildrenId(ghostEl);
470
488
  const { top, left } = getAbsolutePosition(el, getOffset(el));
471
489
  ghostEl.id = `${GHOST_EL_ID_PREFIX}${el.id}`;
472
490
  ghostEl.style.zIndex = ZIndex.GHOST_EL;
@@ -478,34 +496,21 @@ export default class StageDragResize extends EventEmitter {
478
496
  return ghostEl;
479
497
  }
480
498
 
481
- private destroyGhostEl(): void {
482
- this.ghostEl?.remove();
483
- this.ghostEl = undefined;
484
- }
485
-
486
- private updateDragEl(el: HTMLElement) {
487
- const offset = getOffset(el);
488
- const { transform } = getComputedStyle(el);
489
-
490
- this.dragEl.style.cssText = `
491
- position: absolute;
492
- transform: ${transform};
493
- left: ${offset.left}px;
494
- top: ${offset.top}px;
495
- width: ${el.clientWidth}px;
496
- height: ${el.clientHeight}px;
497
- z-index: ${ZIndex.DRAG_EL};
498
- `;
499
-
500
- this.dragEl.id = `${DRAG_EL_ID_PREFIX}${el.id}`;
499
+ private setGhostElChildrenId(el: Element) {
500
+ for (const child of Array.from(el.children)) {
501
+ if (child.id) {
502
+ child.id = `${GHOST_EL_ID_PREFIX}${child.id}`;
503
+ }
501
504
 
502
- if (typeof this.core.config.updateDragEl === 'function') {
503
- this.core.config.updateDragEl(this.dragEl, el);
505
+ if (child.children.length) {
506
+ this.setGhostElChildrenId(child);
507
+ }
504
508
  }
505
509
  }
506
510
 
507
- private destroyDragEl(): void {
508
- this.dragEl?.remove();
511
+ private destroyGhostEl(): void {
512
+ this.ghostEl?.remove();
513
+ this.ghostEl = undefined;
509
514
  }
510
515
 
511
516
  private getOptions(options: MoveableOptions = {}): MoveableOptions {
@@ -566,7 +571,7 @@ export default class StageDragResize extends EventEmitter {
566
571
  top: 0,
567
572
  // 设置0的话无法移动到left为0,所以只能设置为-1
568
573
  left: -1,
569
- right: this.container.clientWidth,
574
+ right: this.container.clientWidth - 1,
570
575
  bottom: this.container.clientHeight,
571
576
  ...(moveableOptions.bounds || {}),
572
577
  },
@@ -574,74 +579,11 @@ export default class StageDragResize extends EventEmitter {
574
579
  ...moveableOptions,
575
580
  };
576
581
  }
577
- }
578
582
 
579
- /**
580
- * 下移组件位置
581
- * @param {number} deltaTop 偏移量
582
- * @param {Object} detail 当前选中的组件配置
583
- */
584
- export const down = (deltaTop: number, target: HTMLElement | SVGElement): SortEventData | void => {
585
- let swapIndex = 0;
586
- let addUpH = target.clientHeight;
587
- const brothers = Array.from(target.parentNode?.children || []).filter(
588
- (node) => !node.id.startsWith(GHOST_EL_ID_PREFIX),
589
- );
590
- const index = brothers.indexOf(target);
591
- // 往下移动
592
- const downEls = brothers.slice(index + 1) as HTMLElement[];
593
-
594
- for (let i = 0; i < downEls.length; i++) {
595
- const ele = downEls[i];
596
- // 是 fixed 不做处理
597
- if (ele.style?.position === 'fixed') {
598
- continue;
599
- }
600
- addUpH += ele.clientHeight / 2;
601
- if (deltaTop <= addUpH) {
602
- break;
603
- }
604
- addUpH += ele.clientHeight / 2;
605
- swapIndex = i;
606
- }
607
- return {
608
- src: target.id,
609
- dist: downEls.length && swapIndex > -1 ? downEls[swapIndex].id : target.id,
610
- };
611
- };
612
-
613
- /**
614
- * 上移组件位置
615
- * @param {Array} brothers 处于同一容器下的所有子组件配置
616
- * @param {number} index 当前组件所处的位置
617
- * @param {number} deltaTop 偏移量
618
- * @param {Object} detail 当前选中的组件配置
619
- */
620
- export const up = (deltaTop: number, target: HTMLElement | SVGElement): SortEventData | void => {
621
- const brothers = Array.from(target.parentNode?.children || []).filter(
622
- (node) => !node.id.startsWith(GHOST_EL_ID_PREFIX),
623
- );
624
- const index = brothers.indexOf(target);
625
- // 往上移动
626
- const upEls = brothers.slice(0, index) as HTMLElement[];
627
-
628
- let addUpH = target.clientHeight;
629
- let swapIndex = upEls.length - 1;
630
-
631
- for (let i = upEls.length - 1; i >= 0; i--) {
632
- const ele = upEls[i];
633
- if (!ele) continue;
634
- // 是 fixed 不做处理
635
- if (ele.style.position === 'fixed') continue;
636
-
637
- addUpH += ele.clientHeight / 2;
638
- if (-deltaTop <= addUpH) break;
639
- addUpH += ele.clientHeight / 2;
640
-
641
- swapIndex = i;
583
+ private canContainerHighlight() {
584
+ return (
585
+ this.core.containerHighlightType === ContainerHighlightType.DEFAULT ||
586
+ (this.core.containerHighlightType === ContainerHighlightType.ALT && this.isContainerHighlight)
587
+ );
642
588
  }
643
- return {
644
- src: target.id,
645
- dist: upEls.length && swapIndex > -1 ? upEls[swapIndex].id : target.id,
646
- };
647
- };
589
+ }
@@ -57,7 +57,7 @@ export default class StageHighlight extends EventEmitter {
57
57
  target: this.calibrationTarget.update(el, HIGHLIGHT_EL_ID_PREFIX),
58
58
  origin: false,
59
59
  rootContainer: this.core.container,
60
- zoom: 1,
60
+ zoom: 2,
61
61
  });
62
62
  }
63
63
 
package/src/StageMask.ts CHANGED
@@ -16,6 +16,7 @@
16
16
  * limitations under the License.
17
17
  */
18
18
 
19
+ import KeyController from 'keycon';
19
20
  import { throttle } from 'lodash-es';
20
21
 
21
22
  import { createDiv, injectStyle } from '@tmagic/utils';
@@ -82,6 +83,7 @@ export default class StageMask extends Rule {
82
83
  public maxScrollTop = 0;
83
84
  public maxScrollLeft = 0;
84
85
  public intersectionObserver: IntersectionObserver | null = null;
86
+ public isMultiSelectStatus: Boolean = false;
85
87
 
86
88
  private mode: Mode = Mode.ABSOLUTE;
87
89
  private pageResizeObserver: ResizeObserver | null = null;
@@ -106,6 +108,19 @@ export default class StageMask extends Rule {
106
108
  this.content.addEventListener('wheel', this.mouseWheelHandler);
107
109
  this.content.addEventListener('mousemove', this.highlightHandler);
108
110
  this.content.addEventListener('mouseleave', this.mouseLeaveHandler);
111
+
112
+ const isMac = /mac os x/.test(navigator.userAgent.toLowerCase());
113
+
114
+ const ctrl = isMac ? 'meta' : 'ctrl';
115
+
116
+ KeyController.global.keydown(ctrl, (e) => {
117
+ e.inputEvent.preventDefault();
118
+ this.isMultiSelectStatus = true;
119
+ });
120
+ KeyController.global.keyup(ctrl, (e) => {
121
+ e.inputEvent.preventDefault();
122
+ this.isMultiSelectStatus = false;
123
+ });
109
124
  }
110
125
 
111
126
  public setMode(mode: Mode) {
@@ -292,21 +307,28 @@ export default class StageMask extends Rule {
292
307
  */
293
308
  private mouseDownHandler = (event: MouseEvent): void => {
294
309
  this.emit('clearHighlight');
295
-
296
310
  event.stopImmediatePropagation();
297
311
  event.stopPropagation();
298
312
 
299
313
  if (event.button !== MouseButton.LEFT && event.button !== MouseButton.RIGHT) return;
300
314
 
301
- // 点击的对象如果是选中框,则不需要再触发选中了,而可能是拖动行为
315
+ // 如果单击多选选中区域,则不需要再触发选中了,而可能是拖动行为
316
+ if (!this.isMultiSelectStatus && (event.target as HTMLDivElement).className.indexOf('moveable-area') !== -1) {
317
+ return;
318
+ }
319
+ // 点击对象如果是边框锚点,则可能是resize
302
320
  if ((event.target as HTMLDivElement).className.indexOf('moveable-control') !== -1) {
303
321
  return;
304
322
  }
305
323
 
306
324
  this.content.removeEventListener('mousemove', this.highlightHandler);
307
325
 
308
- this.emit('beforeSelect', event);
309
-
326
+ // 判断触发多选还是单选
327
+ if (this.isMultiSelectStatus) {
328
+ this.emit('beforeMultiSelect', event);
329
+ } else {
330
+ this.emit('beforeSelect', event);
331
+ }
310
332
  // 如果是右键点击,这里的mouseup事件监听没有效果
311
333
  globalThis.document.addEventListener('mouseup', this.mouseUpHandler);
312
334
  };
@@ -314,7 +336,9 @@ export default class StageMask extends Rule {
314
336
  private mouseUpHandler = (): void => {
315
337
  globalThis.document.removeEventListener('mouseup', this.mouseUpHandler);
316
338
  this.content.addEventListener('mousemove', this.highlightHandler);
317
- this.emit('select');
339
+ if (!this.isMultiSelectStatus) {
340
+ this.emit('select');
341
+ }
318
342
  };
319
343
 
320
344
  private mouseWheelHandler = (event: WheelEvent) => {