@tmagic/stage 1.0.0-beta.7 → 1.0.0-beta.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/LICENSE +5432 -0
- package/dist/tmagic-stage.es.js +462 -17577
- package/dist/tmagic-stage.es.js.map +1 -1
- package/dist/tmagic-stage.umd.js +466 -17582
- package/dist/tmagic-stage.umd.js.map +1 -1
- package/dist/types/src/Rule.d.ts +32 -0
- package/dist/types/src/StageCore.d.ts +7 -6
- package/dist/types/src/StageDragResize.d.ts +12 -9
- package/dist/types/src/StageMask.d.ts +18 -37
- package/dist/types/src/StageRender.d.ts +1 -0
- package/dist/types/src/const.d.ts +17 -0
- package/dist/types/src/types.d.ts +5 -9
- package/dist/types/src/util.d.ts +7 -5
- package/package.json +6 -5
- package/src/Rule.ts +150 -0
- package/src/StageCore.ts +76 -88
- package/src/StageDragResize.ts +147 -162
- package/src/StageMask.ts +122 -184
- package/src/StageRender.ts +12 -9
- package/src/const.ts +21 -0
- package/src/types.ts +23 -28
- package/src/util.ts +43 -7
package/src/StageDragResize.ts
CHANGED
|
@@ -22,10 +22,10 @@ import { EventEmitter } from 'events';
|
|
|
22
22
|
import type { MoveableOptions } from 'moveable';
|
|
23
23
|
import Moveable from 'moveable';
|
|
24
24
|
|
|
25
|
-
import { GHOST_EL_ID_PREFIX } from './const';
|
|
25
|
+
import { GHOST_EL_ID_PREFIX, GuidesType, Mode } from './const';
|
|
26
26
|
import StageCore from './StageCore';
|
|
27
27
|
import type { SortEventData, StageDragResizeConfig } from './types';
|
|
28
|
-
import { getAbsolutePosition, getMode, getOffset
|
|
28
|
+
import { getAbsolutePosition, getMode, getOffset } from './util';
|
|
29
29
|
|
|
30
30
|
enum ActionStatus {
|
|
31
31
|
START = 'start',
|
|
@@ -40,12 +40,13 @@ export default class StageDragResize extends EventEmitter {
|
|
|
40
40
|
public core: StageCore;
|
|
41
41
|
public container: HTMLElement;
|
|
42
42
|
public target?: HTMLElement;
|
|
43
|
+
public dragEl?: HTMLElement;
|
|
43
44
|
public moveable?: Moveable;
|
|
44
45
|
public horizontalGuidelines: number[] = [];
|
|
45
46
|
public verticalGuidelines: number[] = [];
|
|
46
47
|
|
|
48
|
+
private moveableOptions: MoveableOptions = {};
|
|
47
49
|
private dragStatus: ActionStatus = ActionStatus.END;
|
|
48
|
-
private elObserver?: ResizeObserver;
|
|
49
50
|
private ghostEl: HTMLElement | undefined;
|
|
50
51
|
private mode: Mode = Mode.ABSOLUTE;
|
|
51
52
|
|
|
@@ -54,81 +55,88 @@ export default class StageDragResize extends EventEmitter {
|
|
|
54
55
|
|
|
55
56
|
this.core = config.core;
|
|
56
57
|
this.container = config.container;
|
|
57
|
-
this.initObserver();
|
|
58
58
|
}
|
|
59
59
|
|
|
60
60
|
/**
|
|
61
61
|
* 将选中框渲染并覆盖到选中的组件Dom节点上方
|
|
62
|
+
* 当选中的节点是不是absolute时,会创建一个新的节点出来作为拖拽目标
|
|
62
63
|
* @param el 选中组件的Dom节点元素
|
|
64
|
+
* @param event 鼠标事件
|
|
63
65
|
*/
|
|
64
|
-
public async select(el: HTMLElement): Promise<void> {
|
|
65
|
-
if (this.target === el) {
|
|
66
|
-
this.refresh();
|
|
67
|
-
return;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
this.moveable?.destroy();
|
|
71
|
-
|
|
66
|
+
public async select(el: HTMLElement, event?: MouseEvent): Promise<void> {
|
|
72
67
|
this.target = el;
|
|
68
|
+
// 如果有滚动条会导致resize时获取到width,height不准确
|
|
69
|
+
if (/(auto|scroll)/.test(this.target.style.overflow)) {
|
|
70
|
+
this.target.style.overflow = 'hidden';
|
|
71
|
+
}
|
|
73
72
|
this.mode = getMode(el);
|
|
73
|
+
this.destroyDragEl();
|
|
74
|
+
this.destroyGhostEl();
|
|
74
75
|
|
|
75
|
-
|
|
76
|
+
this.dragEl = this.generateDragEl(el);
|
|
76
77
|
|
|
77
|
-
this.
|
|
78
|
-
|
|
79
|
-
|
|
78
|
+
this.moveableOptions = await this.getOptions({
|
|
79
|
+
target: this.dragEl || this.target,
|
|
80
|
+
});
|
|
80
81
|
|
|
81
|
-
this.
|
|
82
|
+
this.initMoveable();
|
|
83
|
+
|
|
84
|
+
if (event) {
|
|
85
|
+
this.moveable?.dragStart(event);
|
|
86
|
+
}
|
|
82
87
|
}
|
|
83
88
|
|
|
84
89
|
/**
|
|
85
90
|
* 初始化选中框并渲染出来
|
|
86
|
-
* @param param0
|
|
87
91
|
*/
|
|
88
|
-
|
|
89
92
|
public async refresh() {
|
|
93
|
+
if (!this.moveable) throw new Error('未初始化moveable');
|
|
94
|
+
|
|
90
95
|
const options = await this.getOptions();
|
|
91
96
|
Object.entries(options).forEach(([key, value]) => {
|
|
92
97
|
(this.moveable as any)[key] = value;
|
|
93
98
|
});
|
|
94
|
-
this.
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
public async setVGuidelines(verticalGuidelines: number[]): Promise<void> {
|
|
98
|
-
this.verticalGuidelines = verticalGuidelines;
|
|
99
|
-
this.target && (await this.select(this.target));
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
public async setHGuidelines(horizontalGuidelines: number[]): Promise<void> {
|
|
103
|
-
this.horizontalGuidelines = horizontalGuidelines;
|
|
104
|
-
this.target && (await this.select(this.target));
|
|
99
|
+
this.moveable.updateTarget();
|
|
105
100
|
}
|
|
106
101
|
|
|
107
|
-
public
|
|
108
|
-
if (
|
|
109
|
-
|
|
110
|
-
if (
|
|
111
|
-
this.
|
|
102
|
+
public setGuidelines(type: GuidesType, guidelines: number[]): void {
|
|
103
|
+
if (type === GuidesType.HORIZONTAL) {
|
|
104
|
+
this.horizontalGuidelines = guidelines;
|
|
105
|
+
} else if (type === GuidesType.VERTICAL) {
|
|
106
|
+
this.verticalGuidelines = guidelines;
|
|
112
107
|
}
|
|
113
108
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
}
|
|
109
|
+
this.refresh();
|
|
110
|
+
}
|
|
117
111
|
|
|
118
|
-
|
|
112
|
+
public clearGuides() {
|
|
113
|
+
this.verticalGuidelines = [];
|
|
114
|
+
this.horizontalGuidelines = [];
|
|
115
|
+
this.refresh();
|
|
119
116
|
}
|
|
120
117
|
|
|
121
118
|
/**
|
|
122
119
|
* 销毁实例
|
|
123
120
|
*/
|
|
124
121
|
public destroy(): void {
|
|
125
|
-
this.destroyGhostEl();
|
|
126
122
|
this.moveable?.destroy();
|
|
123
|
+
this.destroyGhostEl();
|
|
124
|
+
this.destroyDragEl();
|
|
127
125
|
this.dragStatus = ActionStatus.END;
|
|
128
|
-
this.elObserver?.disconnect();
|
|
129
126
|
this.removeAllListeners();
|
|
130
127
|
}
|
|
131
128
|
|
|
129
|
+
private initMoveable() {
|
|
130
|
+
this.moveable?.destroy();
|
|
131
|
+
|
|
132
|
+
this.moveable = new Moveable(this.container, {
|
|
133
|
+
...this.moveableOptions,
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
this.bindResizeEvent();
|
|
137
|
+
this.bindDragEvent();
|
|
138
|
+
}
|
|
139
|
+
|
|
132
140
|
private bindResizeEvent(): void {
|
|
133
141
|
if (!this.moveable) throw new Error('moveable 为初始化');
|
|
134
142
|
|
|
@@ -142,50 +150,38 @@ export default class StageDragResize extends EventEmitter {
|
|
|
142
150
|
const rect = this.moveable!.getRect();
|
|
143
151
|
const offset = getAbsolutePosition(e.target as HTMLElement, rect);
|
|
144
152
|
e.dragStart.set([offset.left, offset.top]);
|
|
145
|
-
|
|
146
|
-
if (this.ghostEl) {
|
|
147
|
-
this.destroyGhostEl();
|
|
148
|
-
this.updateMoveableTarget(this.target);
|
|
149
|
-
}
|
|
150
153
|
}
|
|
151
154
|
})
|
|
152
|
-
.on('resize', ({
|
|
153
|
-
if (!this.moveable) return;
|
|
154
|
-
|
|
155
|
+
.on('resize', ({ width, height, drag }) => {
|
|
156
|
+
if (!this.moveable || !this.target || !this.dragEl) return;
|
|
157
|
+
|
|
155
158
|
const { beforeTranslate } = drag;
|
|
156
159
|
frame.translate = beforeTranslate;
|
|
157
160
|
this.dragStatus = ActionStatus.ING;
|
|
158
161
|
|
|
159
|
-
target.style.width = `${width}px`;
|
|
160
|
-
target.style.height = `${height}px`;
|
|
162
|
+
this.target.style.width = `${width}px`;
|
|
163
|
+
this.target.style.height = `${height}px`;
|
|
164
|
+
this.dragEl.style.width = `${width}px`;
|
|
165
|
+
this.dragEl.style.height = `${height}px`;
|
|
161
166
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
167
|
+
// 流式布局
|
|
168
|
+
if (this.mode === Mode.SORTABLE) {
|
|
169
|
+
this.dragEl.style.top = `${beforeTranslate[1]}px`;
|
|
170
|
+
this.target.style.top = `0px`;
|
|
171
|
+
return;
|
|
165
172
|
}
|
|
173
|
+
|
|
174
|
+
this.dragEl.style.left = `${beforeTranslate[0]}px`;
|
|
175
|
+
this.dragEl.style.top = `${beforeTranslate[1]}px`;
|
|
176
|
+
|
|
177
|
+
const offset = getAbsolutePosition(this.target, { left: beforeTranslate[0], top: beforeTranslate[1] });
|
|
178
|
+
|
|
179
|
+
this.target.style.left = `${offset.left}px`;
|
|
180
|
+
this.target.style.top = `${offset.top}px`;
|
|
166
181
|
})
|
|
167
|
-
.on('resizeEnd', (
|
|
182
|
+
.on('resizeEnd', () => {
|
|
168
183
|
this.dragStatus = ActionStatus.END;
|
|
169
|
-
|
|
170
|
-
const rect = this.moveable!.getRect();
|
|
171
|
-
const offset = getAbsolutePosition(target as HTMLElement, rect);
|
|
172
|
-
|
|
173
|
-
this.updateMoveableTarget(this.target);
|
|
174
|
-
|
|
175
|
-
this.emit('update', {
|
|
176
|
-
el: this.target,
|
|
177
|
-
style: {
|
|
178
|
-
width: this.calcValueByFontsize(rect.width),
|
|
179
|
-
height: this.calcValueByFontsize(rect.height),
|
|
180
|
-
position: this.target?.style.position,
|
|
181
|
-
...(this.mode === Mode.SORTABLE
|
|
182
|
-
? {}
|
|
183
|
-
: {
|
|
184
|
-
left: this.calcValueByFontsize(offset.left),
|
|
185
|
-
top: this.calcValueByFontsize(offset.top),
|
|
186
|
-
}),
|
|
187
|
-
},
|
|
188
|
-
});
|
|
184
|
+
this.update(true);
|
|
189
185
|
});
|
|
190
186
|
}
|
|
191
187
|
|
|
@@ -200,42 +196,40 @@ export default class StageDragResize extends EventEmitter {
|
|
|
200
196
|
|
|
201
197
|
if (this.mode === Mode.SORTABLE) {
|
|
202
198
|
this.ghostEl = this.generateGhostEl(this.target);
|
|
203
|
-
this.updateMoveableTarget(this.ghostEl);
|
|
204
199
|
}
|
|
205
200
|
})
|
|
206
|
-
.on('drag', ({
|
|
201
|
+
.on('drag', ({ left, top }) => {
|
|
202
|
+
if (!this.target || !this.dragEl) return;
|
|
207
203
|
this.dragStatus = ActionStatus.ING;
|
|
208
|
-
|
|
204
|
+
|
|
205
|
+
const offset = getAbsolutePosition(this.target, { left, top });
|
|
206
|
+
|
|
207
|
+
// 流式布局
|
|
208
|
+
if (this.ghostEl) {
|
|
209
|
+
this.dragEl.style.top = `${top}px`;
|
|
210
|
+
this.ghostEl.style.top = `${offset.top}px`;
|
|
209
211
|
return;
|
|
210
212
|
}
|
|
211
213
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
target.style.top = `${offset.top + top}px`;
|
|
218
|
-
}
|
|
214
|
+
this.dragEl.style.left = `${left}px`;
|
|
215
|
+
this.dragEl.style.top = `${top}px`;
|
|
216
|
+
|
|
217
|
+
this.target.style.left = `${offset.left}px`;
|
|
218
|
+
this.target.style.top = `${offset.top}px`;
|
|
219
219
|
})
|
|
220
220
|
.on('dragEnd', () => {
|
|
221
221
|
// 点击不拖动时会触发dragStart和dragEnd,但是不会有drag事件
|
|
222
|
-
if (this.dragStatus
|
|
223
|
-
|
|
222
|
+
if (this.dragStatus === ActionStatus.ING) {
|
|
223
|
+
switch (this.mode) {
|
|
224
|
+
case Mode.SORTABLE:
|
|
225
|
+
this.sort();
|
|
226
|
+
break;
|
|
227
|
+
default:
|
|
228
|
+
this.update();
|
|
229
|
+
}
|
|
224
230
|
}
|
|
225
231
|
|
|
226
|
-
if (!this.target) return;
|
|
227
|
-
|
|
228
232
|
this.dragStatus = ActionStatus.END;
|
|
229
|
-
this.updateMoveableTarget(this.target);
|
|
230
|
-
|
|
231
|
-
switch (this.mode) {
|
|
232
|
-
case Mode.SORTABLE:
|
|
233
|
-
this.sort();
|
|
234
|
-
break;
|
|
235
|
-
default:
|
|
236
|
-
this.drag();
|
|
237
|
-
}
|
|
238
|
-
|
|
239
233
|
this.destroyGhostEl();
|
|
240
234
|
});
|
|
241
235
|
}
|
|
@@ -246,12 +240,13 @@ export default class StageDragResize extends EventEmitter {
|
|
|
246
240
|
(await renderer.getRuntime())?.getSnapElements ||
|
|
247
241
|
(() => {
|
|
248
242
|
const doc = renderer.contentWindow?.document;
|
|
249
|
-
|
|
250
|
-
// 排除掉当前组件本身
|
|
251
|
-
.filter((element) => element !== this.target && !this.target?.contains(element));
|
|
252
|
-
return elementGuidelines as HTMLElement[];
|
|
243
|
+
return doc ? Array.from(doc.querySelectorAll('[id]')) : [];
|
|
253
244
|
});
|
|
254
|
-
return
|
|
245
|
+
return (
|
|
246
|
+
getSnapElements(el)
|
|
247
|
+
// 排除掉当前组件本身
|
|
248
|
+
.filter((element) => element !== this.target && !this.target?.contains(element))
|
|
249
|
+
);
|
|
255
250
|
}
|
|
256
251
|
|
|
257
252
|
private sort(): void {
|
|
@@ -273,18 +268,19 @@ export default class StageDragResize extends EventEmitter {
|
|
|
273
268
|
}
|
|
274
269
|
}
|
|
275
270
|
|
|
276
|
-
private
|
|
271
|
+
private update(isResize = false): void {
|
|
277
272
|
const rect = this.moveable!.getRect();
|
|
278
|
-
const offset =
|
|
273
|
+
const offset =
|
|
274
|
+
this.mode === Mode.SORTABLE ? { left: 0, top: 0 } : getAbsolutePosition(this.target as HTMLElement, rect);
|
|
275
|
+
|
|
276
|
+
const left = this.calcValueByFontsize(offset.left);
|
|
277
|
+
const top = this.calcValueByFontsize(offset.top);
|
|
278
|
+
const width = this.calcValueByFontsize(rect.width);
|
|
279
|
+
const height = this.calcValueByFontsize(rect.height);
|
|
279
280
|
|
|
280
281
|
this.emit('update', {
|
|
281
282
|
el: this.target,
|
|
282
|
-
style: {
|
|
283
|
-
left: this.calcValueByFontsize(this.mode === Mode.FIXED ? rect.left : offset.left),
|
|
284
|
-
top: this.calcValueByFontsize(this.mode === Mode.FIXED ? rect.top : offset.top),
|
|
285
|
-
width: this.calcValueByFontsize(rect.width),
|
|
286
|
-
height: this.calcValueByFontsize(rect.height),
|
|
287
|
-
},
|
|
283
|
+
style: isResize ? { left, top, width, height } : { left, top },
|
|
288
284
|
});
|
|
289
285
|
}
|
|
290
286
|
|
|
@@ -310,87 +306,76 @@ export default class StageDragResize extends EventEmitter {
|
|
|
310
306
|
this.ghostEl = undefined;
|
|
311
307
|
}
|
|
312
308
|
|
|
309
|
+
private generateDragEl(el: HTMLElement): HTMLElement {
|
|
310
|
+
if (this.dragEl) {
|
|
311
|
+
this.destroyDragEl();
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
const { width, height } = el.getBoundingClientRect();
|
|
315
|
+
const offset = getOffset(el);
|
|
316
|
+
const dragEl = globalThis.document.createElement('div');
|
|
317
|
+
dragEl.style.cssText = `
|
|
318
|
+
position: absolute;
|
|
319
|
+
left: ${offset.left}px;
|
|
320
|
+
top: ${offset.top}px;
|
|
321
|
+
width: ${width}px;
|
|
322
|
+
height: ${height}px;
|
|
323
|
+
`;
|
|
324
|
+
this.container.append(dragEl);
|
|
325
|
+
return dragEl;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
private destroyDragEl(): void {
|
|
329
|
+
this.dragEl?.remove();
|
|
330
|
+
this.dragEl = undefined;
|
|
331
|
+
}
|
|
332
|
+
|
|
313
333
|
private async getOptions(options: MoveableOptions = {}): Promise<MoveableOptions> {
|
|
314
334
|
if (!this.target) return {};
|
|
315
335
|
|
|
316
|
-
const
|
|
317
|
-
const { config, renderer, mask } = this.core;
|
|
318
|
-
const { iframe } = renderer;
|
|
336
|
+
const isAbsolute = this.mode === Mode.ABSOLUTE;
|
|
319
337
|
|
|
320
|
-
let { moveableOptions = {} } = config;
|
|
338
|
+
let { moveableOptions = {} } = this.core.config;
|
|
321
339
|
|
|
322
340
|
if (typeof moveableOptions === 'function') {
|
|
323
341
|
moveableOptions = moveableOptions(this.core);
|
|
324
342
|
}
|
|
325
343
|
|
|
326
|
-
const boundsOptions = {
|
|
327
|
-
top: 0,
|
|
328
|
-
left: 0,
|
|
329
|
-
right: iframe?.clientWidth,
|
|
330
|
-
bottom: this.mode === Mode.FIXED ? iframe?.clientHeight : mask.page?.clientHeight,
|
|
331
|
-
...(moveableOptions.bounds || {}),
|
|
332
|
-
};
|
|
333
|
-
|
|
334
344
|
return {
|
|
335
|
-
target: this.target,
|
|
336
345
|
scrollable: true,
|
|
337
346
|
origin: true,
|
|
338
347
|
zoom: 1,
|
|
339
348
|
dragArea: true,
|
|
340
349
|
draggable: true,
|
|
341
350
|
resizable: true,
|
|
342
|
-
snappable:
|
|
343
|
-
snapGap:
|
|
344
|
-
|
|
345
|
-
|
|
351
|
+
snappable: isAbsolute,
|
|
352
|
+
snapGap: isAbsolute,
|
|
353
|
+
snapDirections: { center: isAbsolute, middle: isAbsolute },
|
|
354
|
+
elementSnapDirections: { center: isAbsolute, middle: isAbsolute },
|
|
346
355
|
|
|
347
|
-
elementGuidelines:
|
|
356
|
+
elementGuidelines: isAbsolute ? await this.getSnapElements(this.target) : [],
|
|
348
357
|
horizontalGuidelines: this.horizontalGuidelines,
|
|
349
358
|
verticalGuidelines: this.verticalGuidelines,
|
|
350
359
|
|
|
351
|
-
bounds:
|
|
360
|
+
bounds: {
|
|
361
|
+
top: 0,
|
|
362
|
+
left: 0,
|
|
363
|
+
right: this.container.clientWidth,
|
|
364
|
+
bottom: this.container.clientHeight,
|
|
365
|
+
...(moveableOptions.bounds || {}),
|
|
366
|
+
},
|
|
352
367
|
...options,
|
|
353
368
|
...moveableOptions,
|
|
354
369
|
};
|
|
355
370
|
}
|
|
356
371
|
|
|
357
|
-
private initObserver(): void {
|
|
358
|
-
if (typeof ResizeObserver === 'undefined') {
|
|
359
|
-
return;
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
this.elObserver = new ResizeObserver(() => {
|
|
363
|
-
const doc = this.core.renderer.contentWindow?.document;
|
|
364
|
-
if (!doc || !this.target || !this.moveable) return;
|
|
365
|
-
|
|
366
|
-
/** 组件可能已经重新渲染了,所以需要重新获取新的dom */
|
|
367
|
-
const target = doc.getElementById(this.target.id);
|
|
368
|
-
|
|
369
|
-
if (this.ghostEl) {
|
|
370
|
-
this.destroyGhostEl();
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
if (target && target !== this.target) {
|
|
374
|
-
this.syncRect(target);
|
|
375
|
-
this.target = target;
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
this.updateMoveableTarget(this.target);
|
|
379
|
-
});
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
private syncRect(el: HTMLElement): void {
|
|
383
|
-
this.elObserver?.disconnect();
|
|
384
|
-
this.elObserver?.observe(el);
|
|
385
|
-
}
|
|
386
|
-
|
|
387
372
|
private calcValueByFontsize(value: number) {
|
|
388
373
|
const { contentWindow } = this.core.renderer;
|
|
389
374
|
const fontSize = contentWindow?.document.documentElement.style.fontSize;
|
|
390
375
|
|
|
391
376
|
if (fontSize) {
|
|
392
377
|
const times = globalThis.parseFloat(fontSize) / 100;
|
|
393
|
-
return value / times;
|
|
378
|
+
return (value / times).toFixed(2);
|
|
394
379
|
}
|
|
395
380
|
|
|
396
381
|
return value;
|