@tmagic/stage 1.1.0-beta.3 → 1.1.0-beta.6

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/package.json CHANGED
@@ -1,22 +1,24 @@
1
1
  {
2
- "version": "1.1.0-beta.3",
2
+ "version": "1.1.0-beta.6",
3
3
  "name": "@tmagic/stage",
4
4
  "sideEffects": [
5
5
  "dist/*"
6
6
  ],
7
7
  "main": "dist/tmagic-stage.umd.js",
8
- "module": "dist/tmagic-stage.es.js",
9
- "types": "dist/types/src/index.d.ts",
8
+ "module": "dist/tmagic-stage.mjs",
9
+ "types": "types/index.d.ts",
10
10
  "exports": {
11
11
  ".": {
12
- "import": "./dist/tmagic-stage.es.js",
12
+ "import": "./dist/tmagic-stage.mjs",
13
13
  "require": "./dist/tmagic-stage.umd.js"
14
14
  },
15
15
  "./*": "./*"
16
16
  },
17
17
  "license": "Apache-2.0",
18
18
  "scripts": {
19
- "build": "vite build"
19
+ "build": "npm run build:type && vite build",
20
+ "build:type": "npm run clear:type && tsc --declaration --emitDeclarationOnly --project tsconfig.build.json",
21
+ "clear:type": "rimraf ./types"
20
22
  },
21
23
  "engines": {
22
24
  "node": ">=14"
@@ -27,9 +29,9 @@
27
29
  },
28
30
  "dependencies": {
29
31
  "@scena/guides": "^0.17.0",
30
- "@tmagic/core": "1.1.0-beta.3",
31
- "@tmagic/schema": "1.1.0-beta.3",
32
- "@tmagic/utils": "1.1.0-beta.3",
32
+ "@tmagic/core": "1.1.0-beta.6",
33
+ "@tmagic/schema": "1.1.0-beta.6",
34
+ "@tmagic/utils": "1.1.0-beta.6",
33
35
  "events": "^3.3.0",
34
36
  "keycon": "^1.1.2",
35
37
  "lodash-es": "^4.17.21",
@@ -40,9 +42,9 @@
40
42
  "@types/events": "^3.0.0",
41
43
  "@types/lodash-es": "^4.17.4",
42
44
  "@types/node": "^15.12.4",
45
+ "rimraf": "^3.0.2",
43
46
  "sass": "^1.35.1",
44
- "typescript": "^4.3.4",
45
- "vite": "^2.9.13",
46
- "vite-plugin-dts": "^0.9.6"
47
+ "typescript": "^4.7.4",
48
+ "vite": "^3.0.4"
47
49
  }
48
50
  }
package/src/StageCore.ts CHANGED
@@ -99,7 +99,7 @@ export default class StageCore extends EventEmitter {
99
99
  this.emit('changeGuides', data);
100
100
  })
101
101
  .on('highlight', async (event: MouseEvent) => {
102
- const el = await this.getElementFromPoint(event, 'mousemove');
102
+ const el = await this.getElementFromPoint(event);
103
103
  if (!el) return;
104
104
  await this.highlight(el);
105
105
  if (this.highlightedDom === this.selectedDom) {
@@ -114,9 +114,6 @@ export default class StageCore extends EventEmitter {
114
114
  .on('beforeMultiSelect', async (event: MouseEvent) => {
115
115
  const el = await this.getElementFromPoint(event);
116
116
  if (!el) return;
117
- // 多选不可以选中magic-ui-page
118
- if (el.className.includes(PAGE_CLASS)) return;
119
- this.clearSelectStatus('select');
120
117
  // 如果已有单选选中元素,不是magic-ui-page就可以加入多选列表
121
118
  if (this.selectedDom && !this.selectedDom.className.includes(PAGE_CLASS)) {
122
119
  this.selectedDomList.push(this.selectedDom as HTMLElement);
@@ -130,8 +127,7 @@ export default class StageCore extends EventEmitter {
130
127
  } else {
131
128
  this.selectedDomList.push(el);
132
129
  }
133
- this.multiDr.multiSelect(this.selectedDomList);
134
- this.emit('multiSelect', this.selectedDomList);
130
+ this.multiSelect(this.selectedDomList);
135
131
  });
136
132
 
137
133
  // 要先触发select,在触发update
@@ -166,26 +162,35 @@ export default class StageCore extends EventEmitter {
166
162
  return doc?.elementsFromPoint(x / zoom, y / zoom) as HTMLElement[];
167
163
  }
168
164
 
169
- public async getElementFromPoint(event: MouseEvent, type?: String) {
165
+ public async getElementFromPoint(event: MouseEvent) {
170
166
  const els = this.getElementsFromPoint(event);
171
167
  let stopped = false;
172
168
  const stop = () => (stopped = true);
173
169
  for (const el of els) {
174
- if (!el.id.startsWith(GHOST_EL_ID_PREFIX) && (await this.canSelect(el, event, stop))) {
170
+ if (!el.id.startsWith(GHOST_EL_ID_PREFIX) && (await this.isElCanSelect(el, event, stop))) {
175
171
  if (stopped) break;
176
- if (event.type === type) {
177
- return el;
178
- }
179
172
  return el;
180
173
  }
181
174
  }
182
175
  }
183
176
 
177
+ public async isElCanSelect(el: HTMLElement, event: MouseEvent, stop: () => boolean): Promise<Boolean> {
178
+ // 执行业务方传入的判断逻辑
179
+ const canSelectByProp = await this.canSelect(el, event, stop);
180
+ if (!canSelectByProp) return false;
181
+ // 多选规则
182
+ if (this.mask.isMultiSelectStatus) {
183
+ return this.multiDr.canSelect(el);
184
+ }
185
+ return true;
186
+ }
187
+
184
188
  /**
185
189
  * 选中组件
186
190
  * @param idOrEl 组件Dom节点的id属性,或者Dom节点
187
191
  */
188
192
  public async select(idOrEl: Id | HTMLElement, event?: MouseEvent): Promise<void> {
193
+ this.clearSelectStatus('multiSelect');
189
194
  const el = await this.getTargetElement(idOrEl);
190
195
 
191
196
  if (el === this.selectedDom) return;
@@ -199,7 +204,6 @@ export default class StageCore extends EventEmitter {
199
204
  }
200
205
 
201
206
  this.mask.setLayout(el);
202
- this.multiDr.destroyDragElList();
203
207
  this.dr.select(el, event);
204
208
 
205
209
  if (this.config.autoScrollIntoView || el.dataset.autoScrollIntoView) {
@@ -216,6 +220,17 @@ export default class StageCore extends EventEmitter {
216
220
  }
217
221
  }
218
222
 
223
+ /**
224
+ * 多选
225
+ * @param domList 多选节点
226
+ */
227
+ public async multiSelect(idOrElList: HTMLElement[] | Id[]): Promise<void> {
228
+ this.clearSelectStatus('select');
229
+ const elList = await Promise.all(idOrElList.map(async (idOrEl) => await this.getTargetElement(idOrEl)));
230
+ this.multiDr.multiSelect(elList);
231
+ this.emit('multiSelect', elList);
232
+ }
233
+
219
234
  /**
220
235
  * 更新选中的节点
221
236
  * @param data 更新的数据
@@ -28,8 +28,8 @@ import { removeClassNameByClassName } from '@tmagic/utils';
28
28
  import { DRAG_EL_ID_PREFIX, GHOST_EL_ID_PREFIX, GuidesType, Mode, ZIndex } from './const';
29
29
  import StageCore from './StageCore';
30
30
  import StageMask from './StageMask';
31
- import type { SortEventData, StageDragResizeConfig } from './types';
32
- import { calcValueByFontsize, getAbsolutePosition, getMode, getOffset, getTargetElStyle } from './util';
31
+ import type { StageDragResizeConfig } from './types';
32
+ import { calcValueByFontsize, down, getAbsolutePosition, getMode, getOffset, getTargetElStyle, up } from './util';
33
33
 
34
34
  /** 拖动状态 */
35
35
  enum ActionStatus {
@@ -484,7 +484,7 @@ export default class StageDragResize extends EventEmitter {
484
484
  }
485
485
 
486
486
  private setGhostElChildrenId(el: Element) {
487
- for (const child of el.children) {
487
+ for (const child of Array.from(el.children)) {
488
488
  if (child.id) {
489
489
  child.id = `${GHOST_EL_ID_PREFIX}${child.id}`;
490
490
  }
@@ -558,7 +558,7 @@ export default class StageDragResize extends EventEmitter {
558
558
  top: 0,
559
559
  // 设置0的话无法移动到left为0,所以只能设置为-1
560
560
  left: -1,
561
- right: this.container.clientWidth,
561
+ right: this.container.clientWidth - 1,
562
562
  bottom: this.container.clientHeight,
563
563
  ...(moveableOptions.bounds || {}),
564
564
  },
@@ -567,73 +567,3 @@ export default class StageDragResize extends EventEmitter {
567
567
  };
568
568
  }
569
569
  }
570
-
571
- /**
572
- * 下移组件位置
573
- * @param {number} deltaTop 偏移量
574
- * @param {Object} detail 当前选中的组件配置
575
- */
576
- export const down = (deltaTop: number, target: HTMLElement | SVGElement): SortEventData | void => {
577
- let swapIndex = 0;
578
- let addUpH = target.clientHeight;
579
- const brothers = Array.from(target.parentNode?.children || []).filter(
580
- (node) => !node.id.startsWith(GHOST_EL_ID_PREFIX),
581
- );
582
- const index = brothers.indexOf(target);
583
- // 往下移动
584
- const downEls = brothers.slice(index + 1) as HTMLElement[];
585
-
586
- for (let i = 0; i < downEls.length; i++) {
587
- const ele = downEls[i];
588
- // 是 fixed 不做处理
589
- if (ele.style?.position === 'fixed') {
590
- continue;
591
- }
592
- addUpH += ele.clientHeight / 2;
593
- if (deltaTop <= addUpH) {
594
- break;
595
- }
596
- addUpH += ele.clientHeight / 2;
597
- swapIndex = i;
598
- }
599
- return {
600
- src: target.id,
601
- dist: downEls.length && swapIndex > -1 ? downEls[swapIndex].id : target.id,
602
- };
603
- };
604
-
605
- /**
606
- * 上移组件位置
607
- * @param {Array} brothers 处于同一容器下的所有子组件配置
608
- * @param {number} index 当前组件所处的位置
609
- * @param {number} deltaTop 偏移量
610
- * @param {Object} detail 当前选中的组件配置
611
- */
612
- export const up = (deltaTop: number, target: HTMLElement | SVGElement): SortEventData | void => {
613
- const brothers = Array.from(target.parentNode?.children || []).filter(
614
- (node) => !node.id.startsWith(GHOST_EL_ID_PREFIX),
615
- );
616
- const index = brothers.indexOf(target);
617
- // 往上移动
618
- const upEls = brothers.slice(0, index) as HTMLElement[];
619
-
620
- let addUpH = target.clientHeight;
621
- let swapIndex = upEls.length - 1;
622
-
623
- for (let i = upEls.length - 1; i >= 0; i--) {
624
- const ele = upEls[i];
625
- if (!ele) continue;
626
- // 是 fixed 不做处理
627
- if (ele.style.position === 'fixed') continue;
628
-
629
- addUpH += ele.clientHeight / 2;
630
- if (-deltaTop <= addUpH) break;
631
- addUpH += ele.clientHeight / 2;
632
-
633
- swapIndex = i;
634
- }
635
- return {
636
- src: target.id,
637
- dist: upEls.length && swapIndex > -1 ? upEls[swapIndex].id : target.id,
638
- };
639
- };
@@ -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
@@ -83,7 +83,7 @@ export default class StageMask extends Rule {
83
83
  public maxScrollTop = 0;
84
84
  public maxScrollLeft = 0;
85
85
  public intersectionObserver: IntersectionObserver | null = null;
86
- public shiftKeyDown: Boolean = false;
86
+ public isMultiSelectStatus: Boolean = false;
87
87
 
88
88
  private mode: Mode = Mode.ABSOLUTE;
89
89
  private pageResizeObserver: ResizeObserver | null = null;
@@ -110,11 +110,11 @@ export default class StageMask extends Rule {
110
110
  this.content.addEventListener('mouseleave', this.mouseLeaveHandler);
111
111
  KeyController.global.keydown('shift', (e) => {
112
112
  e.inputEvent.preventDefault();
113
- this.shiftKeyDown = true;
113
+ this.isMultiSelectStatus = true;
114
114
  });
115
115
  KeyController.global.keyup('shift', (e) => {
116
116
  e.inputEvent.preventDefault();
117
- this.shiftKeyDown = false;
117
+ this.isMultiSelectStatus = false;
118
118
  });
119
119
  }
120
120
 
@@ -308,7 +308,7 @@ export default class StageMask extends Rule {
308
308
  if (event.button !== MouseButton.LEFT && event.button !== MouseButton.RIGHT) return;
309
309
 
310
310
  // 如果单击多选选中区域,则不需要再触发选中了,而可能是拖动行为
311
- if (!this.shiftKeyDown && (event.target as HTMLDivElement).className.indexOf('moveable-area') !== -1) {
311
+ if (!this.isMultiSelectStatus && (event.target as HTMLDivElement).className.indexOf('moveable-area') !== -1) {
312
312
  return;
313
313
  }
314
314
  // 点击对象如果是边框锚点,则可能是resize
@@ -319,19 +319,21 @@ export default class StageMask extends Rule {
319
319
  this.content.removeEventListener('mousemove', this.highlightHandler);
320
320
 
321
321
  // 判断触发多选还是单选
322
- if (this.shiftKeyDown) {
322
+ if (this.isMultiSelectStatus) {
323
323
  this.emit('beforeMultiSelect', event);
324
324
  } else {
325
325
  this.emit('beforeSelect', event);
326
- // 如果是右键点击,这里的mouseup事件监听没有效果
327
- globalThis.document.addEventListener('mouseup', this.mouseUpHandler);
328
326
  }
327
+ // 如果是右键点击,这里的mouseup事件监听没有效果
328
+ globalThis.document.addEventListener('mouseup', this.mouseUpHandler);
329
329
  };
330
330
 
331
331
  private mouseUpHandler = (): void => {
332
332
  globalThis.document.removeEventListener('mouseup', this.mouseUpHandler);
333
333
  this.content.addEventListener('mousemove', this.highlightHandler);
334
- this.emit('select');
334
+ if (!this.isMultiSelectStatus) {
335
+ this.emit('select');
336
+ }
335
337
  };
336
338
 
337
339
  private mouseWheelHandler = (event: WheelEvent) => {
@@ -18,14 +18,15 @@
18
18
 
19
19
  import { EventEmitter } from 'events';
20
20
 
21
+ import type { MoveableOptions } from 'moveable';
21
22
  import Moveable from 'moveable';
22
23
  import MoveableHelper from 'moveable-helper';
23
24
 
24
- import { DRAG_EL_ID_PREFIX } from './const';
25
+ import { DRAG_EL_ID_PREFIX, PAGE_CLASS } from './const';
25
26
  import StageCore from './StageCore';
26
27
  import StageMask from './StageMask';
27
28
  import { StageDragResizeConfig } from './types';
28
- import { calcValueByFontsize, getTargetElStyle } from './util';
29
+ import { calcValueByFontsize, getMode, getTargetElStyle } from './util';
29
30
  export default class StageMultiDragResize extends EventEmitter {
30
31
  public core: StageCore;
31
32
  public mask: StageMask;
@@ -69,20 +70,12 @@ export default class StageMultiDragResize extends EventEmitter {
69
70
  });
70
71
  this.moveableForMulti?.destroy();
71
72
  this.multiMoveableHelper?.clear();
72
-
73
- this.moveableForMulti = new Moveable(this.container, {
74
- target: this.dragElList,
75
- defaultGroupRotate: 0,
76
- defaultGroupOrigin: '50% 50%',
77
- draggable: true,
78
- resizable: true,
79
- throttleDrag: 0,
80
- startDragRotate: 0,
81
- throttleDragRotate: 0,
82
- zoom: 1,
83
- origin: true,
84
- padding: { left: 0, top: 0, right: 0, bottom: 0 },
85
- });
73
+ this.moveableForMulti = new Moveable(
74
+ this.container,
75
+ this.getOptions({
76
+ target: this.dragElList,
77
+ }),
78
+ );
86
79
  this.multiMoveableHelper = MoveableHelper.create({
87
80
  useBeforeRender: true,
88
81
  useRender: false,
@@ -134,6 +127,30 @@ export default class StageMultiDragResize extends EventEmitter {
134
127
  });
135
128
  }
136
129
 
130
+ public canSelect(el: HTMLElement): Boolean {
131
+ // 多选不可以选中magic-ui-page
132
+ if (el.className.includes(PAGE_CLASS)) {
133
+ this.core.highlightedDom = undefined;
134
+ this.core.highlightLayer.clearHighlight();
135
+ return false;
136
+ }
137
+ const currentTargetMode = getMode(el);
138
+ let selectedDomMode = '';
139
+
140
+ if (this.targetList.length === 0 && this.core.selectedDom) {
141
+ // 单选后添加到多选的情况
142
+ selectedDomMode = getMode(this.core.selectedDom);
143
+ } else if (this.targetList.length > 0) {
144
+ // 已加入多选列表的布局模式是一样的,取第一个判断
145
+ selectedDomMode = getMode(this.targetList[0]);
146
+ }
147
+ // 定位模式不同,不可混选
148
+ if (currentTargetMode !== selectedDomMode) {
149
+ return false;
150
+ }
151
+ return true;
152
+ }
153
+
137
154
  /**
138
155
  * 清除多选状态
139
156
  */
@@ -142,6 +159,7 @@ export default class StageMultiDragResize extends EventEmitter {
142
159
  this.destroyDragElList();
143
160
  this.moveableForMulti.target = null;
144
161
  this.moveableForMulti.updateTarget();
162
+ this.targetList = [];
145
163
  }
146
164
 
147
165
  /**
@@ -182,4 +200,32 @@ export default class StageMultiDragResize extends EventEmitter {
182
200
  });
183
201
  });
184
202
  }
203
+
204
+ /**
205
+ * 获取moveable options参数
206
+ * @param {MoveableOptions} options
207
+ * @return {MoveableOptions} moveable options参数
208
+ */
209
+ private getOptions(options: MoveableOptions = {}): MoveableOptions {
210
+ let { multiMoveableOptions = {} } = this.core.config;
211
+
212
+ if (typeof multiMoveableOptions === 'function') {
213
+ multiMoveableOptions = multiMoveableOptions(this.core);
214
+ }
215
+
216
+ return {
217
+ defaultGroupRotate: 0,
218
+ defaultGroupOrigin: '50% 50%',
219
+ draggable: true,
220
+ resizable: true,
221
+ throttleDrag: 0,
222
+ startDragRotate: 0,
223
+ throttleDragRotate: 0,
224
+ zoom: 1,
225
+ origin: true,
226
+ padding: { left: 0, top: 0, right: 0, bottom: 0 },
227
+ ...options,
228
+ ...multiMoveableOptions,
229
+ };
230
+ }
185
231
  }
@@ -72,20 +72,22 @@ export default class StageRender extends EventEmitter {
72
72
  * @param el 将页面挂载到该Dom节点上
73
73
  */
74
74
  public async mount(el: HTMLDivElement) {
75
- if (this.iframe) {
76
- if (!isSameDomain(this.runtimeUrl) && this.runtimeUrl) {
77
- // 不同域,使用srcdoc发起异步请求,需要目标地址支持跨域
78
- let html = await fetch(this.runtimeUrl).then((res) => res.text());
79
- // 使用base, 解决相对路径或绝对路径的问题
80
- const base = `${location.protocol}//${getHost(this.runtimeUrl)}`;
81
- html = html.replace('<head>', `<head>\n<base href="${base}">`);
82
- this.iframe.srcdoc = html;
83
- }
84
-
85
- el.appendChild<HTMLIFrameElement>(this.iframe);
86
- } else {
75
+ if (!this.iframe) {
87
76
  throw Error('mount 失败');
88
77
  }
78
+
79
+ if (!isSameDomain(this.runtimeUrl) && this.runtimeUrl) {
80
+ // 不同域,使用srcdoc发起异步请求,需要目标地址支持跨域
81
+ let html = await fetch(this.runtimeUrl).then((res) => res.text());
82
+ // 使用base, 解决相对路径或绝对路径的问题
83
+ const base = `${location.protocol}//${getHost(this.runtimeUrl)}`;
84
+ html = html.replace('<head>', `<head>\n<base href="${base}">`);
85
+ this.iframe.srcdoc = html;
86
+ }
87
+
88
+ el.appendChild<HTMLIFrameElement>(this.iframe);
89
+
90
+ this.postTmagicRuntimeReady();
89
91
  }
90
92
 
91
93
  public getRuntime = (): Promise<Runtime> => {
@@ -111,26 +113,34 @@ export default class StageRender extends EventEmitter {
111
113
  }
112
114
 
113
115
  private loadHandler = async () => {
114
- this.contentWindow = this.iframe?.contentWindow as RuntimeWindow;
116
+ if (!this.contentWindow?.magic) {
117
+ this.postTmagicRuntimeReady();
118
+ }
115
119
 
116
- this.contentWindow.magic = this.getMagicApi();
120
+ if (!this.contentWindow) return;
117
121
 
118
122
  if (this.render) {
119
123
  const el = await this.render(this.core);
120
124
  if (el) {
121
- this.iframe?.contentDocument?.body?.appendChild(el);
125
+ this.contentWindow.document?.body?.appendChild(el);
122
126
  }
123
127
  }
124
128
 
125
129
  this.emit('onload');
126
130
 
131
+ injectStyle(this.contentWindow.document, style);
132
+ };
133
+
134
+ private postTmagicRuntimeReady() {
135
+ this.contentWindow = this.iframe?.contentWindow as RuntimeWindow;
136
+
137
+ this.contentWindow.magic = this.getMagicApi();
138
+
127
139
  this.contentWindow.postMessage(
128
140
  {
129
141
  tmagicRuntimeReady: true,
130
142
  },
131
143
  '*',
132
144
  );
133
-
134
- injectStyle(this.contentWindow.document, style);
135
- };
145
+ }
136
146
  }
@@ -18,7 +18,7 @@
18
18
 
19
19
  import { EventEmitter } from 'events';
20
20
 
21
- import { Mode } from './const';
21
+ import { Mode, ZIndex } from './const';
22
22
  import StageCore from './StageCore';
23
23
  import StageDragResize from './StageDragResize';
24
24
  import StageMask from './StageMask';
@@ -57,6 +57,7 @@ export default class TargetCalibrate extends EventEmitter {
57
57
  top: ${top}px;
58
58
  width: ${el.clientWidth}px;
59
59
  height: ${el.clientHeight}px;
60
+ z-index: ${ZIndex.DRAG_EL};
60
61
  `;
61
62
 
62
63
  this.operationEl.id = `${prefix}${el.id}`;
package/src/style.css CHANGED
@@ -7,4 +7,5 @@
7
7
  left: 0;
8
8
  background-color: #000;
9
9
  opacity: .1;
10
+ pointer-events: none;
10
11
  }
package/src/types.ts CHANGED
@@ -39,6 +39,7 @@ export type StageCoreConfig = {
39
39
  containerHighlightClassName: string;
40
40
  containerHighlightDuration: number;
41
41
  moveableOptions?: ((core?: StageCore) => MoveableOptions) | MoveableOptions;
42
+ multiMoveableOptions?: ((core?: StageCore) => MoveableOptions) | MoveableOptions;
42
43
  /** runtime 的HTML地址,可以是一个HTTP地址,如果和编辑器不同域,需要设置跨域,也可以是一个相对或绝对路径 */
43
44
  runtimeUrl?: string;
44
45
  render?: (renderer: StageCore) => Promise<HTMLElement> | HTMLElement;
@@ -111,7 +112,6 @@ export interface RemoveData {
111
112
  export interface Runtime {
112
113
  getApp?: () => Core;
113
114
  beforeSelect?: (el: HTMLElement) => Promise<boolean> | boolean;
114
- getSnapElements?: (el?: HTMLElement) => HTMLElement[];
115
115
  updateRootConfig?: (config: MApp) => void;
116
116
  updatePageId?: (id: Id) => void;
117
117
  select?: (id: Id) => Promise<HTMLElement> | HTMLElement;
package/src/util.ts CHANGED
@@ -17,8 +17,8 @@
17
17
  */
18
18
  import { removeClassName } from '@tmagic/utils';
19
19
 
20
- import { Mode, SELECTED_CLASS, ZIndex } from './const';
21
- import type { Offset } from './types';
20
+ import { GHOST_EL_ID_PREFIX, Mode, SELECTED_CLASS, ZIndex } from './const';
21
+ import type { Offset, SortEventData } from './types';
22
22
 
23
23
  const getParents = (el: Element, relative: Element) => {
24
24
  let cur: Element | null = el.parentElement;
@@ -159,3 +159,73 @@ export const calcValueByFontsize = (doc: Document, value: number) => {
159
159
 
160
160
  return value;
161
161
  };
162
+
163
+ /**
164
+ * 下移组件位置
165
+ * @param {number} deltaTop 偏移量
166
+ * @param {Object} detail 当前选中的组件配置
167
+ */
168
+ export const down = (deltaTop: number, target: HTMLElement | SVGElement): SortEventData | void => {
169
+ let swapIndex = 0;
170
+ let addUpH = target.clientHeight;
171
+ const brothers = Array.from(target.parentNode?.children || []).filter(
172
+ (node) => !node.id.startsWith(GHOST_EL_ID_PREFIX),
173
+ );
174
+ const index = brothers.indexOf(target);
175
+ // 往下移动
176
+ const downEls = brothers.slice(index + 1) as HTMLElement[];
177
+
178
+ for (let i = 0; i < downEls.length; i++) {
179
+ const ele = downEls[i];
180
+ // 是 fixed 不做处理
181
+ if (ele.style?.position === 'fixed') {
182
+ continue;
183
+ }
184
+ addUpH += ele.clientHeight / 2;
185
+ if (deltaTop <= addUpH) {
186
+ break;
187
+ }
188
+ addUpH += ele.clientHeight / 2;
189
+ swapIndex = i;
190
+ }
191
+ return {
192
+ src: target.id,
193
+ dist: downEls.length && swapIndex > -1 ? downEls[swapIndex].id : target.id,
194
+ };
195
+ };
196
+
197
+ /**
198
+ * 上移组件位置
199
+ * @param {Array} brothers 处于同一容器下的所有子组件配置
200
+ * @param {number} index 当前组件所处的位置
201
+ * @param {number} deltaTop 偏移量
202
+ * @param {Object} detail 当前选中的组件配置
203
+ */
204
+ export const up = (deltaTop: number, target: HTMLElement | SVGElement): SortEventData | void => {
205
+ const brothers = Array.from(target.parentNode?.children || []).filter(
206
+ (node) => !node.id.startsWith(GHOST_EL_ID_PREFIX),
207
+ );
208
+ const index = brothers.indexOf(target);
209
+ // 往上移动
210
+ const upEls = brothers.slice(0, index) as HTMLElement[];
211
+
212
+ let addUpH = target.clientHeight;
213
+ let swapIndex = upEls.length - 1;
214
+
215
+ for (let i = upEls.length - 1; i >= 0; i--) {
216
+ const ele = upEls[i];
217
+ if (!ele) continue;
218
+ // 是 fixed 不做处理
219
+ if (ele.style.position === 'fixed') continue;
220
+
221
+ addUpH += ele.clientHeight / 2;
222
+ if (-deltaTop <= addUpH) break;
223
+ addUpH += ele.clientHeight / 2;
224
+
225
+ swapIndex = i;
226
+ }
227
+ return {
228
+ src: target.id,
229
+ dist: upEls.length && swapIndex > -1 ? upEls[swapIndex].id : target.id,
230
+ };
231
+ };
@@ -0,0 +1,13 @@
1
+ {
2
+ "extends": "../../tsconfig.json",
3
+ "compilerOptions": {
4
+ "baseUrl": ".",
5
+ "declaration": true,
6
+ "declarationDir": "types",
7
+ "forceConsistentCasingInFileNames": true,
8
+ "paths": {},
9
+ },
10
+ "include": [
11
+ "src"
12
+ ],
13
+ }
File without changes
@@ -1,4 +1,5 @@
1
1
  /// <reference types="node" />
2
+ /// <reference types="node" />
2
3
  import { EventEmitter } from 'events';
3
4
  import type { Id } from '@tmagic/schema';
4
5
  import StageDragResize from './StageDragResize';
@@ -25,12 +26,18 @@ export default class StageCore extends EventEmitter {
25
26
  private canSelect;
26
27
  constructor(config: StageCoreConfig);
27
28
  getElementsFromPoint(event: MouseEvent): HTMLElement[];
28
- getElementFromPoint(event: MouseEvent, type?: String): Promise<HTMLElement | undefined>;
29
+ getElementFromPoint(event: MouseEvent): Promise<HTMLElement | undefined>;
30
+ isElCanSelect(el: HTMLElement, event: MouseEvent, stop: () => boolean): Promise<Boolean>;
29
31
  /**
30
32
  * 选中组件
31
33
  * @param idOrEl 组件Dom节点的id属性,或者Dom节点
32
34
  */
33
35
  select(idOrEl: Id | HTMLElement, event?: MouseEvent): Promise<void>;
36
+ /**
37
+ * 多选
38
+ * @param domList 多选节点
39
+ */
40
+ multiSelect(idOrElList: HTMLElement[] | Id[]): Promise<void>;
34
41
  /**
35
42
  * 更新选中的节点
36
43
  * @param data 更新的数据