@tmagic/stage 1.2.0-beta.9 → 1.2.0

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.
Files changed (39) hide show
  1. package/README.md +62 -1
  2. package/dist/{tmagic-stage.mjs → tmagic-stage.js} +1395 -1062
  3. package/dist/tmagic-stage.js.map +1 -0
  4. package/dist/{tmagic-stage.umd.js → tmagic-stage.umd.cjs} +1393 -1060
  5. package/dist/tmagic-stage.umd.cjs.map +1 -0
  6. package/package.json +9 -8
  7. package/src/ActionManager.ts +535 -0
  8. package/src/DragResizeHelper.ts +338 -0
  9. package/src/MoveableOptionsManager.ts +249 -0
  10. package/src/MoveableSelectParentAble.ts +3 -5
  11. package/src/Rule.ts +4 -4
  12. package/src/StageCore.ts +203 -275
  13. package/src/StageDragResize.ts +86 -348
  14. package/src/StageHighlight.ts +17 -16
  15. package/src/StageMask.ts +19 -102
  16. package/src/StageMultiDragResize.ts +97 -198
  17. package/src/StageRender.ts +85 -10
  18. package/src/TargetShadow.ts +120 -0
  19. package/src/const.ts +1 -1
  20. package/src/types.ts +99 -19
  21. package/src/util.ts +18 -11
  22. package/types/ActionManager.d.ts +141 -0
  23. package/types/DragResizeHelper.d.ts +70 -0
  24. package/types/MoveableOptionsManager.d.ts +86 -0
  25. package/types/MoveableSelectParentAble.d.ts +1 -2
  26. package/types/StageCore.d.ts +58 -41
  27. package/types/StageDragResize.d.ts +12 -40
  28. package/types/StageHighlight.d.ts +3 -4
  29. package/types/StageMask.d.ts +0 -21
  30. package/types/StageMultiDragResize.d.ts +8 -24
  31. package/types/StageRender.d.ts +23 -4
  32. package/types/TargetShadow.d.ts +22 -0
  33. package/types/const.d.ts +1 -1
  34. package/types/types.d.ts +87 -18
  35. package/types/util.d.ts +8 -8
  36. package/dist/tmagic-stage.mjs.map +0 -1
  37. package/dist/tmagic-stage.umd.js.map +0 -1
  38. package/src/TargetCalibrate.ts +0 -119
  39. package/types/TargetCalibrate.d.ts +0 -20
@@ -18,28 +18,30 @@
18
18
 
19
19
  import { EventEmitter } from 'events';
20
20
 
21
+ import { Id } from '@tmagic/schema';
21
22
  import { getHost, injectStyle, isSameDomain } from '@tmagic/utils';
22
23
 
24
+ import { DEFAULT_ZOOM } from './const';
23
25
  import style from './style.css?raw';
24
- import type { Runtime, RuntimeWindow, StageRenderConfig } from './types';
26
+ import type { Point, RemoveData, Runtime, RuntimeWindow, StageRenderConfig, UpdateData } from './types';
27
+ import { addSelectedClassName, removeSelectedClassName } from './util';
25
28
 
26
29
  export default class StageRender extends EventEmitter {
27
30
  /** 组件的js、css执行的环境,直接渲染为当前window,iframe渲染则为iframe.contentWindow */
28
31
  public contentWindow: RuntimeWindow | null = null;
29
-
30
32
  public runtime: Runtime | null = null;
31
-
32
33
  public iframe?: HTMLIFrameElement;
33
34
 
34
- public runtimeUrl?: string;
35
+ private runtimeUrl?: string;
36
+ private zoom = DEFAULT_ZOOM;
37
+ private customizedRender?: () => Promise<HTMLElement | null>;
35
38
 
36
- private render?: () => Promise<HTMLElement | null>;
37
-
38
- constructor({ runtimeUrl, render }: StageRenderConfig) {
39
+ constructor({ runtimeUrl, zoom, customizedRender }: StageRenderConfig) {
39
40
  super();
40
41
 
41
42
  this.runtimeUrl = runtimeUrl || '';
42
- this.render = render;
43
+ this.customizedRender = customizedRender;
44
+ this.setZoom(zoom);
43
45
 
44
46
  this.iframe = globalThis.document.createElement('iframe');
45
47
  // 同源,直接加载
@@ -63,6 +65,38 @@ export default class StageRender extends EventEmitter {
63
65
  },
64
66
  });
65
67
 
68
+ public async add(data: UpdateData): Promise<void> {
69
+ const runtime = await this.getRuntime();
70
+ return runtime?.add?.(data);
71
+ }
72
+
73
+ public async remove(data: RemoveData): Promise<void> {
74
+ const runtime = await this.getRuntime();
75
+ return runtime?.remove?.(data);
76
+ }
77
+
78
+ public async update(data: UpdateData): Promise<void> {
79
+ const runtime = await this.getRuntime();
80
+ // 更新画布中的组件
81
+ runtime?.update?.(data);
82
+ }
83
+
84
+ public async select(els: HTMLElement[]): Promise<void> {
85
+ const runtime = await this.getRuntime();
86
+
87
+ for (const el of els) {
88
+ await runtime?.select?.(el.id);
89
+ if (runtime?.beforeSelect) {
90
+ await runtime.beforeSelect(el);
91
+ }
92
+ this.flagSelectedEl(el);
93
+ }
94
+ }
95
+
96
+ public setZoom(zoom: number = DEFAULT_ZOOM): void {
97
+ this.zoom = zoom;
98
+ }
99
+
66
100
  /**
67
101
  * 挂载Dom节点
68
102
  * @param el 将页面挂载到该Dom节点上
@@ -101,6 +135,35 @@ export default class StageRender extends EventEmitter {
101
135
  return this.contentWindow?.document;
102
136
  }
103
137
 
138
+ /**
139
+ * 通过坐标获得坐标下所有HTML元素数组
140
+ * @param point 坐标
141
+ * @returns 坐标下方所有HTML元素数组,会包含父元素直至html,元素层叠时返回顺序是从上到下
142
+ */
143
+ public getElementsFromPoint(point: Point): HTMLElement[] {
144
+ let x = point.clientX;
145
+ let y = point.clientY;
146
+
147
+ if (this.iframe) {
148
+ const rect = this.iframe.getClientRects()[0];
149
+ if (rect) {
150
+ x = x - rect.left;
151
+ y = y - rect.top;
152
+ }
153
+ }
154
+
155
+ return this.getDocument()?.elementsFromPoint(x / this.zoom, y / this.zoom) as HTMLElement[];
156
+ }
157
+
158
+ public getTargetElement(idOrEl: Id | HTMLElement): HTMLElement {
159
+ if (typeof idOrEl === 'string' || typeof idOrEl === 'number') {
160
+ const el = this.getDocument()?.getElementById(`${idOrEl}`);
161
+ if (!el) throw new Error(`不存在ID为${idOrEl}的元素`);
162
+ return el;
163
+ }
164
+ return idOrEl;
165
+ }
166
+
104
167
  /**
105
168
  * 销毁实例
106
169
  */
@@ -112,6 +175,18 @@ export default class StageRender extends EventEmitter {
112
175
  this.removeAllListeners();
113
176
  }
114
177
 
178
+ /**
179
+ * 在runtime中对被选中的元素进行标记,部分组件有对选中态进行特殊显示的需求
180
+ * @param el 被选中的元素
181
+ */
182
+ private flagSelectedEl(el: HTMLElement): void {
183
+ const doc = this.getDocument();
184
+ if (doc) {
185
+ removeSelectedClassName(doc);
186
+ addSelectedClassName(el, doc);
187
+ }
188
+ }
189
+
115
190
  private loadHandler = async () => {
116
191
  if (!this.contentWindow?.magic) {
117
192
  this.postTmagicRuntimeReady();
@@ -119,8 +194,8 @@ export default class StageRender extends EventEmitter {
119
194
 
120
195
  if (!this.contentWindow) return;
121
196
 
122
- if (this.render) {
123
- const el = await this.render();
197
+ if (this.customizedRender) {
198
+ const el = await this.customizedRender();
124
199
  if (el) {
125
200
  this.contentWindow.document?.body?.appendChild(el);
126
201
  }
@@ -0,0 +1,120 @@
1
+ /*
2
+ * Tencent is pleased to support the open source community by making TMagicEditor available.
3
+ *
4
+ * Copyright (C) 2021 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 { Mode, ZIndex } from './const';
19
+ import type { TargetElement as ShadowElement, TargetShadowConfig, UpdateDragEl } from './types';
20
+ import { getTargetElStyle, isFixedParent } from './util';
21
+
22
+ /**
23
+ * 将选中的节点修正定位后,添加一个操作节点到蒙层上
24
+ */
25
+ export default class TargetShadow {
26
+ public el?: ShadowElement;
27
+ public els: ShadowElement[] = [];
28
+
29
+ private idPrefix = 'target_calibrate_';
30
+ private container: HTMLElement;
31
+ private scrollLeft = 0;
32
+ private scrollTop = 0;
33
+ private zIndex?: ZIndex;
34
+
35
+ private updateDragEl?: UpdateDragEl;
36
+
37
+ constructor(config: TargetShadowConfig) {
38
+ this.container = config.container;
39
+
40
+ if (config.updateDragEl) {
41
+ this.updateDragEl = config.updateDragEl;
42
+ }
43
+
44
+ if (typeof config.zIndex !== 'undefined') {
45
+ this.zIndex = config.zIndex;
46
+ }
47
+
48
+ if (config.idPrefix) {
49
+ this.idPrefix = config.idPrefix;
50
+ }
51
+
52
+ this.container.addEventListener('customScroll', this.scrollHandler);
53
+ }
54
+
55
+ public update(target: ShadowElement): ShadowElement {
56
+ this.el = this.updateEl(target, this.el);
57
+
58
+ return this.el;
59
+ }
60
+
61
+ public updateGroup(targetGroup: ShadowElement[]): ShadowElement[] {
62
+ if (this.els.length > targetGroup.length) {
63
+ this.els.slice(targetGroup.length - 1).forEach((el) => {
64
+ el.remove();
65
+ });
66
+ }
67
+
68
+ this.els = targetGroup.map((target, index) => this.updateEl(target, this.els[index]));
69
+
70
+ return this.els;
71
+ }
72
+
73
+ public destroyEl(): void {
74
+ this.el?.remove();
75
+ this.el = undefined;
76
+ }
77
+
78
+ public destroyEls(): void {
79
+ this.els.forEach((el) => {
80
+ el.remove();
81
+ });
82
+ this.els = [];
83
+ }
84
+
85
+ public destroy(): void {
86
+ this.container.removeEventListener('customScroll', this.scrollHandler);
87
+ this.destroyEl();
88
+ this.destroyEls();
89
+ }
90
+
91
+ private updateEl(target: ShadowElement, src?: ShadowElement): ShadowElement {
92
+ const el = src || globalThis.document.createElement('div');
93
+
94
+ el.id = `${this.idPrefix}${target.id}`;
95
+
96
+ el.style.cssText = getTargetElStyle(target, this.zIndex);
97
+
98
+ if (typeof this.updateDragEl === 'function') {
99
+ this.updateDragEl(el, target);
100
+ }
101
+ const isFixed = isFixedParent(target);
102
+ const mode = this.container.dataset.mode || Mode.ABSOLUTE;
103
+ if (isFixed && mode !== Mode.FIXED) {
104
+ el.style.transform = `translate3d(${this.scrollLeft}px, ${this.scrollTop}px, 0)`;
105
+ } else if (!isFixed && mode === Mode.FIXED) {
106
+ el.style.transform = `translate3d(${-this.scrollLeft}px, ${-this.scrollTop}px, 0)`;
107
+ }
108
+
109
+ if (!globalThis.document.getElementById(el.id)) {
110
+ this.container.append(el);
111
+ }
112
+
113
+ return el;
114
+ }
115
+
116
+ private scrollHandler = (e: any) => {
117
+ this.scrollLeft = e.detail.scrollLeft;
118
+ this.scrollTop = e.detail.scrollTop;
119
+ };
120
+ }
package/src/const.ts CHANGED
@@ -25,7 +25,7 @@ export const DRAG_EL_ID_PREFIX = 'drag_el_';
25
25
  /** 高亮时需要在蒙层中创建一个占位节点,该节点的id前缀 */
26
26
  export const HIGHLIGHT_EL_ID_PREFIX = 'highlight_el_';
27
27
 
28
- export const CONTAINER_HIGHLIGHT_CLASS = 'tmagic-stage-container-highlight';
28
+ export const CONTAINER_HIGHLIGHT_CLASS_NAME = 'tmagic-stage-container-highlight';
29
29
 
30
30
  export const PAGE_CLASS = 'magic-ui-page';
31
31
 
package/src/types.ts CHANGED
@@ -16,25 +16,44 @@
16
16
  * limitations under the License.
17
17
  */
18
18
 
19
- import { MoveableOptions } from 'moveable';
19
+ import type { MoveableOptions } from 'moveable';
20
20
 
21
21
  import Core from '@tmagic/core';
22
22
  import type { Id, MApp, MContainer, MNode } from '@tmagic/schema';
23
23
 
24
- import { GuidesType } from './const';
24
+ import { GuidesType, ZIndex } from './const';
25
25
  import StageCore from './StageCore';
26
- import StageDragResize from './StageDragResize';
27
- import StageMask from './StageMask';
26
+
27
+ export type TargetElement = HTMLElement | SVGElement;
28
28
 
29
29
  export type CanSelect = (el: HTMLElement, event: MouseEvent, stop: () => boolean) => boolean | Promise<boolean>;
30
30
  export type IsContainer = (el: HTMLElement) => boolean | Promise<boolean>;
31
-
31
+ export type CustomizeRender = (renderer: StageCore) => Promise<HTMLElement> | HTMLElement;
32
+ /** 业务方自定义的moveableOptions,可以是配置,也可以是回调函数 */
33
+ export type CustomizeMoveableOptions =
34
+ | ((config?: CustomizeMoveableOptionsCallbackConfig) => MoveableOptions)
35
+ | MoveableOptions
36
+ | undefined;
37
+ /** render提供给的接口,如果是id则转成el,如果是el则直接返回 */
38
+ export type GetTargetElement = (idOrEl: Id | HTMLElement) => HTMLElement;
39
+ /** render提供的接口,通过坐标获得坐标下所有HTML元素数组 */
40
+ export type GetElementsFromPoint = (point: Point) => HTMLElement[];
41
+ export type GetRenderDocument = () => Document | undefined;
42
+ export type DelayedMarkContainer = (event: MouseEvent, exclude: Element[]) => NodeJS.Timeout | undefined;
43
+ export type MarkContainerEnd = () => HTMLElement | null;
44
+ export type GetRootContainer = () => HTMLDivElement | undefined;
45
+
46
+ /** 将组件添加到容器的方式 */
32
47
  export enum ContainerHighlightType {
48
+ /** 默认方式:组件在容器上方悬停一段时间后加入 */
33
49
  DEFAULT = 'default',
50
+ /** 按住alt键,并在容器上方悬停一段时间后加入 */
34
51
  ALT = 'alt',
35
52
  }
36
53
 
37
- export type StageCoreConfig = {
54
+ export type UpdateDragEl = (el: TargetElement, target: TargetElement) => void;
55
+
56
+ export interface StageCoreConfig {
38
57
  /** 需要对齐的dom节点的CSS选择器字符串 */
39
58
  snapElementQuerySelector?: string;
40
59
  /** 放大倍数,默认1倍 */
@@ -44,18 +63,47 @@ export type StageCoreConfig = {
44
63
  containerHighlightClassName?: string;
45
64
  containerHighlightDuration?: number;
46
65
  containerHighlightType?: ContainerHighlightType;
47
- moveableOptions?: ((core?: StageCore) => MoveableOptions) | MoveableOptions;
48
- multiMoveableOptions?: ((core?: StageCore) => MoveableOptions) | MoveableOptions;
66
+ moveableOptions?: CustomizeMoveableOptions;
67
+ multiMoveableOptions?: CustomizeMoveableOptions;
49
68
  /** runtime 的HTML地址,可以是一个HTTP地址,如果和编辑器不同域,需要设置跨域,也可以是一个相对或绝对路径 */
50
69
  runtimeUrl?: string;
51
70
  render?: (renderer: StageCore) => Promise<HTMLElement> | HTMLElement;
52
71
  autoScrollIntoView?: boolean;
53
- updateDragEl?: (el: HTMLDivElement, target: HTMLElement) => void;
54
- };
72
+ updateDragEl?: UpdateDragEl;
73
+ disabledDragStart?: boolean;
74
+ }
75
+
76
+ export interface ActionManagerConfig {
77
+ container: HTMLElement;
78
+ containerHighlightClassName?: string;
79
+ containerHighlightDuration?: number;
80
+ containerHighlightType?: ContainerHighlightType;
81
+ moveableOptions?: CustomizeMoveableOptions;
82
+ multiMoveableOptions?: CustomizeMoveableOptions;
83
+ disabledDragStart?: boolean;
84
+ canSelect?: CanSelect;
85
+ isContainer: IsContainer;
86
+ getRootContainer: GetRootContainer;
87
+ getRenderDocument: GetRenderDocument;
88
+ updateDragEl?: UpdateDragEl;
89
+ getTargetElement: GetTargetElement;
90
+ getElementsFromPoint: GetElementsFromPoint;
91
+ }
92
+
93
+ export interface MoveableOptionsManagerConfig {
94
+ container: HTMLElement;
95
+ moveableOptions?: CustomizeMoveableOptions;
96
+ getRootContainer: GetRootContainer;
97
+ }
98
+
99
+ export interface CustomizeMoveableOptionsCallbackConfig {
100
+ targetElId?: string;
101
+ }
55
102
 
56
103
  export interface StageRenderConfig {
57
104
  runtimeUrl?: string;
58
- render?: () => Promise<HTMLElement | null>;
105
+ zoom: number | undefined;
106
+ customizedRender?: () => Promise<HTMLElement | null>;
59
107
  }
60
108
 
61
109
  export interface StageMaskConfig {
@@ -63,9 +111,35 @@ export interface StageMaskConfig {
63
111
  }
64
112
 
65
113
  export interface StageDragResizeConfig {
66
- core: StageCore;
67
114
  container: HTMLElement;
68
- mask: StageMask;
115
+ moveableOptions?: CustomizeMoveableOptions;
116
+ disabledDragStart?: boolean;
117
+ getRootContainer: GetRootContainer;
118
+ getRenderDocument: GetRenderDocument;
119
+ markContainerEnd: MarkContainerEnd;
120
+ delayedMarkContainer: DelayedMarkContainer;
121
+ updateDragEl?: UpdateDragEl;
122
+ }
123
+
124
+ export interface StageMultiDragResizeConfig {
125
+ container: HTMLElement;
126
+ multiMoveableOptions?: CustomizeMoveableOptions;
127
+ getRootContainer: GetRootContainer;
128
+ getRenderDocument: GetRenderDocument;
129
+ updateDragEl?: UpdateDragEl;
130
+ }
131
+
132
+ export interface DragResizeHelperConfig {
133
+ container: HTMLElement;
134
+ updateDragEl?: UpdateDragEl;
135
+ }
136
+
137
+ /** 选择状态 */
138
+ export enum SelectStatus {
139
+ /** 单选 */
140
+ SELECT = 'select',
141
+ /** 多选 */
142
+ MULTI_SELECT = 'multiSelect',
69
143
  }
70
144
 
71
145
  /** 拖动状态 */
@@ -88,6 +162,11 @@ export interface Offset {
88
162
  top: number;
89
163
  }
90
164
 
165
+ export interface Point {
166
+ clientX: number;
167
+ clientY: number;
168
+ }
169
+
91
170
  export interface GuidesEventData {
92
171
  type: GuidesType;
93
172
  guides: number[];
@@ -154,13 +233,14 @@ export interface RuntimeWindow extends Window {
154
233
  }
155
234
 
156
235
  export interface StageHighlightConfig {
157
- core: StageCore;
158
236
  container: HTMLElement;
237
+ updateDragEl?: UpdateDragEl;
238
+ getRootContainer: GetRootContainer;
159
239
  }
160
240
 
161
- export interface TargetCalibrateConfig {
162
- parent: HTMLElement;
163
- mask: StageMask;
164
- dr: StageDragResize;
165
- core: StageCore;
241
+ export interface TargetShadowConfig {
242
+ container: HTMLElement;
243
+ zIndex?: ZIndex;
244
+ updateDragEl?: UpdateDragEl;
245
+ idPrefix?: string;
166
246
  }
package/src/util.ts CHANGED
@@ -18,7 +18,7 @@
18
18
  import { removeClassName } from '@tmagic/utils';
19
19
 
20
20
  import { GHOST_EL_ID_PREFIX, Mode, SELECTED_CLASS, ZIndex } from './const';
21
- import type { Offset, SortEventData } from './types';
21
+ import type { Offset, SortEventData, TargetElement } from './types';
22
22
 
23
23
  const getParents = (el: Element, relative: Element) => {
24
24
  let cur: Element | null = el.parentElement;
@@ -30,12 +30,16 @@ const getParents = (el: Element, relative: Element) => {
30
30
  return parents;
31
31
  };
32
32
 
33
- export const getOffset = (el: HTMLElement): Offset => {
34
- const { offsetParent } = el;
33
+ export const getOffset = (el: TargetElement): Offset => {
34
+ const htmlEl = el as HTMLElement;
35
+ const { offsetParent } = htmlEl;
35
36
 
36
- const left = el.offsetLeft;
37
- const top = el.offsetTop;
37
+ const left = htmlEl.offsetLeft || 0;
38
+ const top = htmlEl.offsetTop || 0;
38
39
 
40
+ // 在 Webkit 中,如果元素为隐藏的(该元素或其祖先元素的 style.display 为 "none"),或者该元素的 style.position 被设为 "fixed",则该属性返回 null。
41
+ // 在 IE 9 中,如果该元素的 style.position 被设置为 "fixed",则该属性返回 null。(display:none 无影响。)
42
+ // body offsetParent 为 null
39
43
  if (offsetParent) {
40
44
  const parentOffset = getOffset(offsetParent as HTMLElement);
41
45
  return {
@@ -51,7 +55,7 @@ export const getOffset = (el: HTMLElement): Offset => {
51
55
  };
52
56
 
53
57
  // 将蒙层占位节点覆盖在原节点上方
54
- export const getTargetElStyle = (el: HTMLElement) => {
58
+ export const getTargetElStyle = (el: TargetElement, zIndex?: ZIndex) => {
55
59
  const offset = getOffset(el);
56
60
  const { transform } = getComputedStyle(el);
57
61
  return `
@@ -61,13 +65,16 @@ export const getTargetElStyle = (el: HTMLElement) => {
61
65
  top: ${offset.top}px;
62
66
  width: ${el.clientWidth}px;
63
67
  height: ${el.clientHeight}px;
64
- z-index: ${ZIndex.DRAG_EL};
68
+ ${typeof zIndex !== 'undefined' ? `z-index: ${zIndex};` : ''}
65
69
  `;
66
70
  };
67
71
 
68
72
  export const getAbsolutePosition = (el: HTMLElement, { top, left }: Offset) => {
69
73
  const { offsetParent } = el;
70
74
 
75
+ // 在 Webkit 中,如果元素为隐藏的(该元素或其祖先元素的 style.display 为 "none"),或者该元素的 style.position 被设为 "fixed",则该属性返回 null。
76
+ // 在 IE 9 中,如果该元素的 style.position 被设置为 "fixed",则该属性返回 null。(display:none 无影响。)
77
+ // body offsetParent 为 null
71
78
  if (offsetParent) {
72
79
  const parentOffset = getOffset(offsetParent as HTMLElement);
73
80
  return {
@@ -87,7 +94,7 @@ export const isStatic = (style: CSSStyleDeclaration): boolean => style.position
87
94
 
88
95
  export const isFixed = (style: CSSStyleDeclaration): boolean => style.position === 'fixed';
89
96
 
90
- export const isFixedParent = (el: HTMLElement) => {
97
+ export const isFixedParent = (el: Element) => {
91
98
  let fixed = false;
92
99
  let dom = el;
93
100
  while (dom) {
@@ -104,7 +111,7 @@ export const isFixedParent = (el: HTMLElement) => {
104
111
  return fixed;
105
112
  };
106
113
 
107
- export const getMode = (el: HTMLElement): Mode => {
114
+ export const getMode = (el: Element): Mode => {
108
115
  if (isFixedParent(el)) return Mode.FIXED;
109
116
  const style = getComputedStyle(el);
110
117
  if (isStatic(style) || isRelative(style)) return Mode.SORTABLE;
@@ -168,7 +175,7 @@ export const calcValueByFontsize = (doc: Document, value: number) => {
168
175
  * @param {number} deltaTop 偏移量
169
176
  * @param {Object} detail 当前选中的组件配置
170
177
  */
171
- export const down = (deltaTop: number, target: HTMLElement | SVGElement): SortEventData | void => {
178
+ export const down = (deltaTop: number, target: TargetElement): SortEventData | void => {
172
179
  let swapIndex = 0;
173
180
  let addUpH = target.clientHeight;
174
181
  const brothers = Array.from(target.parentNode?.children || []).filter(
@@ -204,7 +211,7 @@ export const down = (deltaTop: number, target: HTMLElement | SVGElement): SortEv
204
211
  * @param {number} deltaTop 偏移量
205
212
  * @param {Object} detail 当前选中的组件配置
206
213
  */
207
- export const up = (deltaTop: number, target: HTMLElement | SVGElement): SortEventData | void => {
214
+ export const up = (deltaTop: number, target: TargetElement): SortEventData | void => {
208
215
  const brothers = Array.from(target.parentNode?.children || []).filter(
209
216
  (node) => !node.id.startsWith(GHOST_EL_ID_PREFIX),
210
217
  );
@@ -0,0 +1,141 @@
1
+ /// <reference types="node" />
2
+ /// <reference types="node" />
3
+ import EventEmitter from 'events';
4
+ import { Id } from '@tmagic/schema';
5
+ import { GuidesType } from './const';
6
+ import { ActionManagerConfig, SelectStatus } from './types';
7
+ /**
8
+ * 管理蒙层mask之上的操作:1、监听键盘鼠标事件,判断形成单选、多选、高亮操作;2、管理单选、多选、高亮三个类协同工作。
9
+ * @extends EventEmitter
10
+ */
11
+ export default class ActionManager extends EventEmitter {
12
+ private dr;
13
+ private multiDr;
14
+ private highlightLayer;
15
+ /** 单选、多选、高亮的容器(蒙层的content) */
16
+ private container;
17
+ /** 当前选中的节点 */
18
+ private selectedEl;
19
+ /** 多选选中的节点组 */
20
+ private selectedElList;
21
+ /** 当前高亮的节点 */
22
+ private highlightedEl;
23
+ /** 当前是否处于多选状态 */
24
+ private isMultiSelectStatus;
25
+ /** 当拖拽组件到容器上方进入可加入容器状态时,给容器添加的一个class名称 */
26
+ private containerHighlightClassName;
27
+ /** 当拖拽组件到容器上方时,需要悬停多久才能将组件加入容器 */
28
+ private containerHighlightDuration;
29
+ /** 将组件加入容器的操作方式 */
30
+ private containerHighlightType?;
31
+ private isAltKeydown;
32
+ private getTargetElement;
33
+ private getElementsFromPoint;
34
+ private canSelect;
35
+ private isContainer;
36
+ private getRenderDocument;
37
+ private mouseMoveHandler;
38
+ constructor(config: ActionManagerConfig);
39
+ /**
40
+ * 设置水平/垂直参考线
41
+ * @param type 参考线类型
42
+ * @param guidelines 参考线坐标数组
43
+ */
44
+ setGuidelines(type: GuidesType, guidelines: number[]): void;
45
+ /**
46
+ * 清空所有参考线
47
+ */
48
+ clearGuides(): void;
49
+ /**
50
+ * 更新moveable,外部主要调用场景是元素配置变更、页面大小变更
51
+ * @param el 变更的元素
52
+ */
53
+ updateMoveable(el?: HTMLElement): void;
54
+ /**
55
+ * 判断是否单选选中的元素
56
+ */
57
+ isSelectedEl(el: HTMLElement): boolean;
58
+ setSelectedEl(el: HTMLElement): void;
59
+ getSelectedEl(): HTMLElement | undefined;
60
+ getSelectedElList(): HTMLElement[];
61
+ /**
62
+ * 获取鼠标下方第一个可选中元素,如果元素层叠,返回到是最上层元素
63
+ * @param event 鼠标事件
64
+ * @returns 鼠标下方第一个可选中元素
65
+ */
66
+ getElementFromPoint(event: MouseEvent): Promise<HTMLElement | undefined>;
67
+ /**
68
+ * 判断一个元素能否在当前场景被选中
69
+ * @param el 被判断的元素
70
+ * @param event 鼠标事件
71
+ * @param stop 通过该元素如果得知剩下的元素都不可被选中,通知调用方终止对剩下元素的判断
72
+ * @returns 能否选中
73
+ */
74
+ isElCanSelect(el: HTMLElement, event: MouseEvent, stop: () => boolean): Promise<boolean>;
75
+ /**
76
+ * 判断一个元素是否可以被多选,如果当前元素是page,则调stop函数告诉调用方不必继续判断其它元素了
77
+ */
78
+ canMultiSelect(el: HTMLElement, stop: () => boolean): boolean;
79
+ select(el: HTMLElement, event: MouseEvent | undefined): void;
80
+ multiSelect(idOrElList: HTMLElement[] | Id[]): void;
81
+ getHighlightEl(): HTMLElement | undefined;
82
+ setHighlightEl(el: HTMLElement | undefined): void;
83
+ highlight(idOrEl: Id | HTMLElement): void;
84
+ clearHighlight(): void;
85
+ /**
86
+ * 用于在切换选择模式时清除上一次的状态
87
+ * @param selectType 需要清理的选择模式
88
+ */
89
+ clearSelectStatus(selectType: SelectStatus): void;
90
+ /**
91
+ * 找到鼠标下方的容器,通过添加className对容器进行标记
92
+ * @param event 鼠标事件
93
+ * @param excludeElList 计算鼠标点所在容器时要排除的元素列表
94
+ */
95
+ addContainerHighlightClassName(event: MouseEvent, excludeElList: Element[]): Promise<void>;
96
+ /**
97
+ * 鼠标拖拽着元素,在容器上方悬停,延迟一段时间后,对容器进行标记,如果悬停时间够长将标记成功,悬停时间短,调用方通过返回的timeoutId取消标记
98
+ * 标记的作用:1、高亮容器,给用户一个加入容器的交互感知;2、释放鼠标后,通过标记的标志找到要加入的容器
99
+ * @param event 鼠标事件
100
+ * @param excludeElList 计算鼠标所在容器时要排除的元素列表
101
+ * @returns timeoutId,调用方在鼠标移走时要取消该timeout,阻止标记
102
+ */
103
+ delayedMarkContainer(event: MouseEvent, excludeElList?: Element[]): NodeJS.Timeout;
104
+ destroy(): void;
105
+ private changeCallback;
106
+ /**
107
+ * 在执行多选逻辑前,先准备好多选选中元素
108
+ * @param el 新选中的元素
109
+ * @returns 多选选中的元素列表
110
+ */
111
+ private beforeMultiSelect;
112
+ /**
113
+ * 当前状态下能否将组件加入容器,默认是鼠标悬停一段时间加入,alt模式则是按住alt+鼠标悬停一段时间加入
114
+ */
115
+ private canAddToContainer;
116
+ /**
117
+ * 结束对container的标记状态
118
+ * @returns 标记的容器元素,没有标记的容器时返回null
119
+ */
120
+ private markContainerEnd;
121
+ private initMouseEvent;
122
+ /**
123
+ * 初始化键盘事件监听
124
+ */
125
+ private initKeyEvent;
126
+ /**
127
+ * 处理单选、多选抛出来的事件
128
+ */
129
+ private initActionEvent;
130
+ /**
131
+ * 在down事件中集中cpu处理画布中选中操作渲染,在up事件中再通知外面的编辑器更新
132
+ */
133
+ private mouseDownHandler;
134
+ private isStopTriggerSelect;
135
+ /**
136
+ * 在up事件中负责对外通知选中事件,通知画布之外的编辑器更新
137
+ */
138
+ private mouseUpHandler;
139
+ private mouseLeaveHandler;
140
+ private mouseWheelHandler;
141
+ }