@tmagic/stage 1.0.0-beta.9 → 1.0.0-rc.3

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,7 +1,9 @@
1
1
  {
2
- "version": "1.0.0-beta.9",
2
+ "version": "1.0.0-rc.3",
3
3
  "name": "@tmagic/stage",
4
- "sideEffects": false,
4
+ "sideEffects": [
5
+ "dist/*"
6
+ ],
5
7
  "main": "dist/tmagic-stage.umd.js",
6
8
  "module": "dist/tmagic-stage.es.js",
7
9
  "types": "dist/types/src/index.d.ts",
@@ -9,8 +11,10 @@
9
11
  ".": {
10
12
  "import": "./dist/tmagic-stage.es.js",
11
13
  "require": "./dist/tmagic-stage.umd.js"
12
- }
14
+ },
15
+ "./*": "./*"
13
16
  },
17
+ "license": "Apache-2.0",
14
18
  "scripts": {
15
19
  "build": "vite build"
16
20
  },
@@ -23,11 +27,11 @@
23
27
  },
24
28
  "dependencies": {
25
29
  "@scena/guides": "^0.17.0",
26
- "@tmagic/schema": "^1.0.0-beta.9",
27
- "@tmagic/utils": "^1.0.0-beta.8",
30
+ "@tmagic/schema": "^1.0.0-rc.3",
31
+ "@tmagic/utils": "^1.0.0-rc.3",
28
32
  "events": "^3.3.0",
29
33
  "lodash-es": "^4.17.21",
30
- "moveable": "^0.28.0",
34
+ "moveable": "^0.29.4",
31
35
  "moveable-helper": "^0.4.0"
32
36
  },
33
37
  "devDependencies": {
@@ -39,5 +43,5 @@
39
43
  "vite": "^2.3.7",
40
44
  "vite-plugin-dts": "^0.9.6"
41
45
  },
42
- "gitHead": "6314078c100ddcf513eb5581b38e2292f9b93eb9"
46
+ "gitHead": "d9f6ca25a0a6efba35d183e643a306e164b74af7"
43
47
  }
package/src/StageCore.ts CHANGED
@@ -61,10 +61,14 @@ export default class StageCore extends EventEmitter {
61
61
  this.renderer = new StageRender({ core: this });
62
62
  this.mask = new StageMask({ core: this });
63
63
  this.dr = new StageDragResize({ core: this, container: this.mask.content });
64
- this.highlightLayer = new StageHighlight({ core: this, container: this.mask.content });
64
+ this.highlightLayer = new StageHighlight({ core: this, container: this.mask.wrapper });
65
65
 
66
- this.renderer.on('runtime-ready', (runtime: Runtime) => this.emit('runtime-ready', runtime));
67
- this.renderer.on('page-el-update', (el: HTMLElement) => this.mask?.observe(el));
66
+ this.renderer.on('runtime-ready', (runtime: Runtime) => {
67
+ this.emit('runtime-ready', runtime);
68
+ });
69
+ this.renderer.on('page-el-update', (el: HTMLElement) => {
70
+ this.mask?.observe(el);
71
+ });
68
72
 
69
73
  this.mask
70
74
  .on('beforeSelect', (event: MouseEvent) => {
@@ -79,6 +83,10 @@ export default class StageCore extends EventEmitter {
79
83
  })
80
84
  .on('highlight', async (event: MouseEvent) => {
81
85
  await this.setElementFromPoint(event);
86
+ if (this.highlightedDom === this.selectedDom) {
87
+ this.highlightLayer.clearHighlight();
88
+ return;
89
+ }
82
90
  this.highlightLayer.highlight(this.highlightedDom as HTMLElement);
83
91
  this.emit('highlight', this.highlightedDom);
84
92
  })
@@ -116,7 +124,7 @@ export default class StageCore extends EventEmitter {
116
124
  let stopped = false;
117
125
  const stop = () => (stopped = true);
118
126
  for (const el of els) {
119
- if (!el.id.startsWith(GHOST_EL_ID_PREFIX) && (await this.canSelect(el, stop))) {
127
+ if (!el.id.startsWith(GHOST_EL_ID_PREFIX) && (await this.canSelect(el, event, stop))) {
120
128
  if (stopped) break;
121
129
  if (event.type === 'mousemove') {
122
130
  this.highlight(el);
@@ -139,18 +147,25 @@ export default class StageCore extends EventEmitter {
139
147
 
140
148
  const runtime = await this.renderer.getRuntime();
141
149
 
150
+ await runtime?.select?.(el.id);
151
+
142
152
  if (runtime?.beforeSelect) {
143
153
  await runtime.beforeSelect(el);
144
154
  }
145
155
 
146
156
  this.mask.setLayout(el);
147
- this.dr?.select(el, event);
157
+ this.dr.select(el, event);
158
+
159
+ if (this.config.autoScrollIntoView || el.dataset.autoScrollIntoView) {
160
+ this.mask.intersectionObserver?.observe(el);
161
+ }
162
+
148
163
  this.selectedDom = el;
149
164
 
150
165
  if (this.renderer.contentWindow) {
151
166
  removeSelectedClassName(this.renderer.contentWindow.document);
152
167
  if (this.selectedDom) {
153
- addSelectedClassName(this.selectedDom);
168
+ addSelectedClassName(this.selectedDom, this.renderer.contentWindow.document);
154
169
  }
155
170
  }
156
171
  }
@@ -159,18 +174,20 @@ export default class StageCore extends EventEmitter {
159
174
  * 更新选中的节点
160
175
  * @param data 更新的数据
161
176
  */
162
- public update(data: UpdateData): void {
177
+ public update(data: UpdateData): Promise<void> {
163
178
  const { config } = data;
164
179
 
165
- this.renderer?.getRuntime().then((runtime) => {
180
+ return this.renderer?.getRuntime().then((runtime) => {
166
181
  runtime?.update?.(data);
167
182
  // 更新配置后,需要等组件渲染更新
168
183
  setTimeout(() => {
169
184
  const el = this.renderer.contentWindow?.document.getElementById(`${config.id}`);
170
- if (el) {
185
+ // 有可能dom已经重新渲染,不再是原来的dom了,所以这里判断id,而不是判断el === this.selectedDom
186
+ if (el && el.id === this.selectedDom?.id) {
187
+ this.selectedDom = el;
171
188
  // 更新了组件的布局,需要重新设置mask是否可以滚动
172
189
  this.mask.setLayout(el);
173
- this.dr?.select(el);
190
+ this.dr.updateMoveable(el);
174
191
  }
175
192
  }, 0);
176
193
  });
@@ -181,22 +198,28 @@ export default class StageCore extends EventEmitter {
181
198
  * @param el 页面Dom节点
182
199
  */
183
200
  public async highlight(idOrEl: HTMLElement | Id): Promise<void> {
184
- const el = await this.getTargetElement(idOrEl);
201
+ let el;
202
+ try {
203
+ el = await this.getTargetElement(idOrEl);
204
+ } catch (error) {
205
+ this.highlightLayer.clearHighlight();
206
+ return;
207
+ }
185
208
  if (el === this.highlightedDom) return;
186
209
  this.highlightLayer.highlight(el);
187
210
  this.highlightedDom = el;
188
211
  }
189
212
 
190
- public sortNode(data: SortEventData): void {
191
- this.renderer?.getRuntime().then((runtime) => runtime?.sortNode?.(data));
213
+ public sortNode(data: SortEventData): Promise<void> {
214
+ return this.renderer?.getRuntime().then((runtime) => runtime?.sortNode?.(data));
192
215
  }
193
216
 
194
- public add(data: UpdateData): void {
195
- this.renderer?.getRuntime().then((runtime) => runtime?.add?.(data));
217
+ public add(data: UpdateData): Promise<void> {
218
+ return this.renderer?.getRuntime().then((runtime) => runtime?.add?.(data));
196
219
  }
197
220
 
198
- public remove(data: RemoveData): void {
199
- this.renderer?.getRuntime().then((runtime) => runtime?.remove?.(data));
221
+ public remove(data: RemoveData): Promise<void> {
222
+ return this.renderer?.getRuntime().then((runtime) => runtime?.remove?.(data));
200
223
  }
201
224
 
202
225
  public setZoom(zoom: number = DEFAULT_ZOOM): void {
@@ -242,14 +265,11 @@ export default class StageCore extends EventEmitter {
242
265
  }
243
266
 
244
267
  private async getTargetElement(idOrEl: Id | HTMLElement): Promise<HTMLElement> {
245
- let el;
246
268
  if (typeof idOrEl === 'string' || typeof idOrEl === 'number') {
247
- const runtime = await this.renderer?.getRuntime();
248
- el = await runtime?.select?.(`${idOrEl}`);
269
+ const el = this.renderer.contentWindow?.document.getElementById(`${idOrEl}`);
249
270
  if (!el) throw new Error(`不存在ID为${idOrEl}的元素`);
250
- } else {
251
- el = idOrEl;
271
+ return el;
252
272
  }
253
- return el;
273
+ return idOrEl;
254
274
  }
255
275
  }
@@ -23,9 +23,9 @@ import type { MoveableOptions } from 'moveable';
23
23
  import Moveable from 'moveable';
24
24
  import MoveableHelper from 'moveable-helper';
25
25
 
26
- import { GHOST_EL_ID_PREFIX, GuidesType, Mode } from './const';
26
+ import { DRAG_EL_ID_PREFIX, GHOST_EL_ID_PREFIX, GuidesType, Mode } from './const';
27
27
  import StageCore from './StageCore';
28
- import type { SortEventData, StageDragResizeConfig } from './types';
28
+ import type { Offset, Runtime, SortEventData, StageDragResizeConfig } from './types';
29
29
  import { getAbsolutePosition, getMode, getOffset } from './util';
30
30
 
31
31
  enum ActionStatus {
@@ -41,15 +41,16 @@ export default class StageDragResize extends EventEmitter {
41
41
  public core: StageCore;
42
42
  public container: HTMLElement;
43
43
  public target?: HTMLElement;
44
- public dragEl?: HTMLElement;
44
+ public dragEl: HTMLElement;
45
45
  public moveable?: Moveable;
46
46
  public horizontalGuidelines: number[] = [];
47
47
  public verticalGuidelines: number[] = [];
48
+ public elementGuidelines: HTMLElement[] = [];
49
+ public mode: Mode = Mode.ABSOLUTE;
48
50
 
49
51
  private moveableOptions: MoveableOptions = {};
50
52
  private dragStatus: ActionStatus = ActionStatus.END;
51
53
  private ghostEl: HTMLElement | undefined;
52
- private mode: Mode = Mode.ABSOLUTE;
53
54
  private moveableHelper?: MoveableHelper;
54
55
 
55
56
  constructor(config: StageDragResizeConfig) {
@@ -57,6 +58,9 @@ export default class StageDragResize extends EventEmitter {
57
58
 
58
59
  this.core = config.core;
59
60
  this.container = config.container;
61
+
62
+ this.dragEl = globalThis.document.createElement('div');
63
+ this.container.append(this.dragEl);
60
64
  }
61
65
 
62
66
  /**
@@ -65,29 +69,24 @@ export default class StageDragResize extends EventEmitter {
65
69
  * @param el 选中组件的Dom节点元素
66
70
  * @param event 鼠标事件
67
71
  */
68
- public async select(el: HTMLElement, event?: MouseEvent): Promise<void> {
72
+ public select(el: HTMLElement, event?: MouseEvent): void {
73
+ const oldTarget = this.target;
69
74
  this.target = el;
70
- // 如果有滚动条会导致resize时获取到width,height不准确
71
- if (/(auto|scroll)/.test(this.target.style.overflow)) {
72
- this.target.style.overflow = 'hidden';
73
- }
74
- this.mode = getMode(el);
75
- this.destroyDragEl();
76
- this.destroyGhostEl();
77
75
 
78
- this.dragEl = this.generateDragEl(el);
76
+ this.init(el);
79
77
 
80
- this.moveableOptions = await this.getOptions({
81
- target: this.dragEl || this.target,
82
- });
83
-
84
- this.moveableHelper = MoveableHelper.create({
85
- useBeforeRender: true,
86
- useRender: false,
87
- createAuto: true,
88
- });
78
+ // 从不能拖动到能拖动的节点之间切换,要重新创建moveable,不然dragStart不生效
79
+ if (!this.moveable || this.target !== oldTarget) {
80
+ this.moveableHelper = MoveableHelper.create({
81
+ useBeforeRender: true,
82
+ useRender: false,
83
+ createAuto: true,
84
+ });
89
85
 
90
- this.initMoveable();
86
+ this.initMoveable();
87
+ } else {
88
+ this.updateMoveable();
89
+ }
91
90
 
92
91
  if (event) {
93
92
  this.moveable?.dragStart(event);
@@ -97,11 +96,15 @@ export default class StageDragResize extends EventEmitter {
97
96
  /**
98
97
  * 初始化选中框并渲染出来
99
98
  */
100
- public async refresh() {
99
+ public updateMoveable(el = this.target): void {
101
100
  if (!this.moveable) throw new Error('未初始化moveable');
101
+ if (!el) throw new Error('为选中任何节点');
102
+
103
+ this.target = el;
102
104
 
103
- const options = await this.getOptions();
104
- Object.entries(options).forEach(([key, value]) => {
105
+ this.init(el);
106
+
107
+ Object.entries(this.moveableOptions).forEach(([key, value]) => {
105
108
  (this.moveable as any)[key] = value;
106
109
  });
107
110
  this.moveable.updateTarget();
@@ -110,17 +113,21 @@ export default class StageDragResize extends EventEmitter {
110
113
  public setGuidelines(type: GuidesType, guidelines: number[]): void {
111
114
  if (type === GuidesType.HORIZONTAL) {
112
115
  this.horizontalGuidelines = guidelines;
116
+ this.moveableOptions.horizontalGuidelines = guidelines;
113
117
  } else if (type === GuidesType.VERTICAL) {
114
118
  this.verticalGuidelines = guidelines;
119
+ this.moveableOptions.verticalGuidelines = guidelines;
115
120
  }
116
121
 
117
- this.refresh();
122
+ this.updateMoveable();
118
123
  }
119
124
 
120
125
  public clearGuides() {
121
- this.verticalGuidelines = [];
122
126
  this.horizontalGuidelines = [];
123
- this.refresh();
127
+ this.verticalGuidelines = [];
128
+ this.moveableOptions.horizontalGuidelines = [];
129
+ this.moveableOptions.verticalGuidelines = [];
130
+ this.updateMoveable();
124
131
  }
125
132
 
126
133
  /**
@@ -134,6 +141,22 @@ export default class StageDragResize extends EventEmitter {
134
141
  this.removeAllListeners();
135
142
  }
136
143
 
144
+ private init(el: HTMLElement): void {
145
+ // 如果有滚动条会导致resize时获取到width,height不准确
146
+ if (/(auto|scroll)/.test(el.style.overflow)) {
147
+ el.style.overflow = 'hidden';
148
+ }
149
+ this.mode = getMode(el);
150
+
151
+ this.destroyGhostEl();
152
+
153
+ this.updateDragEl(el);
154
+
155
+ this.moveableOptions = this.getOptions({
156
+ target: this.dragEl,
157
+ });
158
+ }
159
+
137
160
  private initMoveable() {
138
161
  this.moveable?.destroy();
139
162
 
@@ -196,10 +219,7 @@ export default class StageDragResize extends EventEmitter {
196
219
  private bindDragEvent(): void {
197
220
  if (!this.moveable) throw new Error('moveable 为初始化');
198
221
 
199
- let offset = {
200
- left: 0,
201
- top: 0,
202
- };
222
+ let offset: Offset | null = null;
203
223
 
204
224
  this.moveable
205
225
  .on('dragStart', (e) => {
@@ -209,14 +229,17 @@ export default class StageDragResize extends EventEmitter {
209
229
 
210
230
  this.moveableHelper?.onDragStart(e);
211
231
 
212
- offset = getAbsolutePosition(this.target, { left: 0, top: 0 });
213
-
214
232
  if (this.mode === Mode.SORTABLE) {
215
233
  this.ghostEl = this.generateGhostEl(this.target);
216
234
  }
217
235
  })
218
236
  .on('drag', (e) => {
219
237
  if (!this.target || !this.dragEl) return;
238
+
239
+ if (!offset) {
240
+ offset = getAbsolutePosition(this.target, { left: 0, top: 0 });
241
+ }
242
+
220
243
  this.dragStatus = ActionStatus.ING;
221
244
 
222
245
  const { left, top } = e;
@@ -244,24 +267,23 @@ export default class StageDragResize extends EventEmitter {
244
267
  this.update();
245
268
  }
246
269
  }
270
+ offset = null;
247
271
 
248
272
  this.dragStatus = ActionStatus.END;
249
273
  this.destroyGhostEl();
250
274
  });
251
275
  }
252
276
 
253
- private async getSnapElements(el: HTMLElement): Promise<HTMLElement[]> {
254
- const { renderer } = this.core;
277
+ private getSnapElements(runtime: Runtime, el?: HTMLElement): HTMLElement[] {
278
+ const { renderer, mask } = this.core;
255
279
  const getSnapElements =
256
- (await renderer.getRuntime())?.getSnapElements ||
280
+ runtime?.getSnapElements ||
257
281
  (() => {
258
282
  const doc = renderer.contentWindow?.document;
259
283
  return doc ? Array.from(doc.querySelectorAll('[id]')) : [];
260
284
  });
261
- return (
262
- getSnapElements(el)
263
- // 排除掉当前组件本身
264
- .filter((element) => element !== this.target && !this.target?.contains(element))
285
+ return getSnapElements(el).filter(
286
+ (element) => element !== this.target && !this.target?.contains(element) && element !== mask.page,
265
287
  );
266
288
  }
267
289
 
@@ -307,7 +329,7 @@ export default class StageDragResize extends EventEmitter {
307
329
 
308
330
  const ghostEl = el.cloneNode(true) as HTMLElement;
309
331
  const { top, left } = getAbsolutePosition(el, getOffset(el));
310
- ghostEl.id = `${GHOST_EL_ID_PREFIX}${ghostEl.id}`;
332
+ ghostEl.id = `${GHOST_EL_ID_PREFIX}${el.id}`;
311
333
  ghostEl.style.zIndex = '5';
312
334
  ghostEl.style.opacity = '.5';
313
335
  ghostEl.style.position = 'absolute';
@@ -322,34 +344,31 @@ export default class StageDragResize extends EventEmitter {
322
344
  this.ghostEl = undefined;
323
345
  }
324
346
 
325
- private generateDragEl(el: HTMLElement): HTMLElement {
326
- if (this.dragEl) {
327
- this.destroyDragEl();
328
- }
329
-
347
+ private updateDragEl(el: HTMLElement) {
330
348
  const { width, height } = el.getBoundingClientRect();
331
349
  const offset = getOffset(el);
332
- const dragEl = globalThis.document.createElement('div');
333
- dragEl.style.cssText = `
350
+
351
+ this.dragEl.style.cssText = `
334
352
  position: absolute;
335
353
  left: ${offset.left}px;
336
354
  top: ${offset.top}px;
337
355
  width: ${width}px;
338
356
  height: ${height}px;
357
+ z-index: 9;
339
358
  `;
340
- this.container.append(dragEl);
341
- return dragEl;
359
+
360
+ this.dragEl.id = `${DRAG_EL_ID_PREFIX}${el.id}`;
342
361
  }
343
362
 
344
363
  private destroyDragEl(): void {
345
364
  this.dragEl?.remove();
346
- this.dragEl = undefined;
347
365
  }
348
366
 
349
- private async getOptions(options: MoveableOptions = {}): Promise<MoveableOptions> {
367
+ private getOptions(options: MoveableOptions = {}): MoveableOptions {
350
368
  if (!this.target) return {};
351
369
 
352
370
  const isAbsolute = this.mode === Mode.ABSOLUTE;
371
+ const isFixed = this.mode === Mode.FIXED;
353
372
 
354
373
  let { moveableOptions = {} } = this.core.config;
355
374
 
@@ -358,14 +377,14 @@ export default class StageDragResize extends EventEmitter {
358
377
  }
359
378
 
360
379
  return {
361
- origin: true,
380
+ origin: false,
362
381
  rootContainer: this.core.container,
363
382
  zoom: 1,
364
383
  dragArea: false,
365
384
  draggable: true,
366
385
  resizable: true,
367
- snappable: isAbsolute,
368
- snapGap: isAbsolute,
386
+ snappable: isAbsolute || isFixed,
387
+ snapGap: isAbsolute || isFixed,
369
388
  snapThreshold: 5,
370
389
  snapDigit: 0,
371
390
  throttleDrag: 0,
@@ -378,8 +397,16 @@ export default class StageDragResize extends EventEmitter {
378
397
  center: isAbsolute,
379
398
  middle: isAbsolute,
380
399
  },
400
+ elementSnapDirections: {
401
+ top: isAbsolute,
402
+ right: isAbsolute,
403
+ bottom: isAbsolute,
404
+ left: isAbsolute,
405
+ },
406
+ isDisplayInnerSnapDigit: true,
381
407
  horizontalGuidelines: this.horizontalGuidelines,
382
408
  verticalGuidelines: this.verticalGuidelines,
409
+ elementGuidelines: this.elementGuidelines,
383
410
 
384
411
  bounds: {
385
412
  top: 0,
@@ -21,19 +21,27 @@ import { EventEmitter } from 'events';
21
21
 
22
22
  import Moveable from 'moveable';
23
23
 
24
+ import { HIGHLIGHT_EL_ID_PREFIX } from './const';
24
25
  import StageCore from './StageCore';
26
+ import TargetCalibrate from './TargetCalibrate';
25
27
  import type { StageHighlightConfig } from './types';
26
28
  export default class StageHighlight extends EventEmitter {
27
29
  public core: StageCore;
28
30
  public container: HTMLElement;
29
31
  public target?: HTMLElement;
30
32
  public moveable?: Moveable;
33
+ public calibrationTarget: TargetCalibrate;
31
34
 
32
35
  constructor(config: StageHighlightConfig) {
33
36
  super();
34
37
 
35
38
  this.core = config.core;
36
39
  this.container = config.container;
40
+ this.calibrationTarget = new TargetCalibrate({
41
+ parent: this.core.mask.content,
42
+ mask: this.core.mask,
43
+ dr: this.core.dr,
44
+ });
37
45
  }
38
46
 
39
47
  /**
@@ -44,8 +52,12 @@ export default class StageHighlight extends EventEmitter {
44
52
  if (!el || el === this.target) return;
45
53
  this.target = el;
46
54
  this.moveable?.destroy();
55
+
47
56
  this.moveable = new Moveable(this.container, {
48
- target: this.target,
57
+ target: this.calibrationTarget.update(el, HIGHLIGHT_EL_ID_PREFIX),
58
+ origin: false,
59
+ rootContainer: this.core.container,
60
+ zoom: 1,
49
61
  });
50
62
  }
51
63
 
@@ -54,6 +66,7 @@ export default class StageHighlight extends EventEmitter {
54
66
  */
55
67
  public clearHighlight(): void {
56
68
  if (!this.moveable) return;
69
+ this.target = undefined;
57
70
  this.moveable.target = null;
58
71
  this.moveable.updateTarget();
59
72
  }
@@ -63,5 +76,6 @@ export default class StageHighlight extends EventEmitter {
63
76
  */
64
77
  public destroy(): void {
65
78
  this.moveable?.destroy();
79
+ this.calibrationTarget.destroy();
66
80
  }
67
81
  }