@tmagic/stage 1.0.0-beta.1 → 1.0.0-beta.10
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 +636 -17586
- package/dist/tmagic-stage.es.js.map +1 -1
- package/dist/tmagic-stage.umd.js +641 -17593
- 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 +17 -6
- package/dist/types/src/StageDragResize.d.ts +18 -12
- package/dist/types/src/StageHighlight.d.ts +25 -0
- package/dist/types/src/StageMask.d.ts +22 -35
- package/dist/types/src/StageRender.d.ts +1 -0
- package/dist/types/src/const.d.ts +20 -0
- package/dist/types/src/types.d.ts +10 -10
- package/dist/types/src/util.d.ts +9 -5
- package/package.json +12 -9
- package/src/Rule.ts +150 -0
- package/src/StageCore.ts +144 -96
- package/src/StageDragResize.ts +213 -182
- package/src/StageHighlight.ts +69 -0
- package/src/StageMask.ts +142 -166
- package/src/StageRender.ts +25 -9
- package/src/const.ts +26 -0
- package/src/types.ts +28 -28
- package/src/util.ts +57 -7
package/src/StageMask.ts
CHANGED
|
@@ -16,17 +16,16 @@
|
|
|
16
16
|
* limitations under the License.
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
|
-
import {
|
|
19
|
+
import { throttle } from 'lodash-es';
|
|
20
20
|
|
|
21
|
-
import
|
|
22
|
-
import
|
|
23
|
-
|
|
24
|
-
import { GHOST_EL_ID_PREFIX } from './const';
|
|
21
|
+
import { Mode, MouseButton, ZIndex } from './const';
|
|
22
|
+
import Rule from './Rule';
|
|
25
23
|
import type StageCore from './StageCore';
|
|
26
|
-
import type {
|
|
27
|
-
import {
|
|
24
|
+
import type { StageMaskConfig } from './types';
|
|
25
|
+
import { createDiv, getScrollParent, isFixedParent } from './util';
|
|
28
26
|
|
|
29
27
|
const wrapperClassName = 'editor-mask-wrapper';
|
|
28
|
+
const throttleTime = 300;
|
|
30
29
|
|
|
31
30
|
const hideScrollbar = () => {
|
|
32
31
|
const style = globalThis.document.createElement('style');
|
|
@@ -36,30 +35,30 @@ const hideScrollbar = () => {
|
|
|
36
35
|
globalThis.document.head.appendChild(style);
|
|
37
36
|
};
|
|
38
37
|
|
|
39
|
-
const createContent = (): HTMLDivElement =>
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
38
|
+
const createContent = (): HTMLDivElement =>
|
|
39
|
+
createDiv({
|
|
40
|
+
className: 'editor-mask',
|
|
41
|
+
cssText: `
|
|
43
42
|
position: absolute;
|
|
44
43
|
top: 0;
|
|
45
44
|
left: 0;
|
|
46
45
|
transform: translate3d(0, 0, 0);
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
};
|
|
46
|
+
`,
|
|
47
|
+
});
|
|
50
48
|
|
|
51
49
|
const createWrapper = (): HTMLDivElement => {
|
|
52
|
-
const el =
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
50
|
+
const el = createDiv({
|
|
51
|
+
className: wrapperClassName,
|
|
52
|
+
cssText: `
|
|
53
|
+
position: absolute;
|
|
54
|
+
top: 0;
|
|
55
|
+
left: 0;
|
|
56
|
+
height: 100%;
|
|
57
|
+
width: 100%;
|
|
58
|
+
overflow: hidden;
|
|
59
|
+
z-index: ${ZIndex.MASK};
|
|
60
|
+
`,
|
|
61
|
+
});
|
|
63
62
|
|
|
64
63
|
hideScrollbar();
|
|
65
64
|
|
|
@@ -70,54 +69,53 @@ const createWrapper = (): HTMLDivElement => {
|
|
|
70
69
|
* 蒙层
|
|
71
70
|
* @description 用于拦截页面的点击动作,避免点击时触发组件自身动作;在编辑器中点击组件应当是选中组件;
|
|
72
71
|
*/
|
|
73
|
-
export default class StageMask extends
|
|
72
|
+
export default class StageMask extends Rule {
|
|
74
73
|
public content: HTMLDivElement = createContent();
|
|
75
|
-
public wrapper: HTMLDivElement
|
|
74
|
+
public wrapper: HTMLDivElement;
|
|
76
75
|
public core: StageCore;
|
|
77
76
|
public page: HTMLElement | null = null;
|
|
78
|
-
|
|
79
|
-
public
|
|
80
|
-
public
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
77
|
+
public pageScrollParent: HTMLElement | null = null;
|
|
78
|
+
public scrollTop = 0;
|
|
79
|
+
public scrollLeft = 0;
|
|
80
|
+
public width = 0;
|
|
81
|
+
public height = 0;
|
|
82
|
+
public wrapperHeight = 0;
|
|
83
|
+
public wrapperWidth = 0;
|
|
84
|
+
|
|
85
|
+
private mode: Mode = Mode.ABSOLUTE;
|
|
86
|
+
private pageResizeObserver: ResizeObserver | null = null;
|
|
87
|
+
private wrapperResizeObserver: ResizeObserver | null = null;
|
|
88
|
+
/**
|
|
89
|
+
* 高亮事件处理函数
|
|
90
|
+
* @param event 事件对象
|
|
91
|
+
*/
|
|
92
|
+
private highlightHandler = throttle((event: MouseEvent): void => {
|
|
93
|
+
this.emit('highlight', event);
|
|
94
|
+
}, throttleTime);
|
|
86
95
|
|
|
87
96
|
constructor(config: StageMaskConfig) {
|
|
88
|
-
|
|
97
|
+
const wrapper = createWrapper();
|
|
98
|
+
super(wrapper);
|
|
89
99
|
|
|
100
|
+
this.wrapper = wrapper;
|
|
90
101
|
this.core = config.core;
|
|
91
|
-
const { config: coreConfig } = config.core;
|
|
92
102
|
|
|
93
|
-
this.
|
|
94
|
-
this.content.addEventListener('mousedown', this.mouseDownHandler, true);
|
|
95
|
-
this.content.addEventListener('contextmenu', this.contextmenuHandler, true);
|
|
103
|
+
this.content.addEventListener('mousedown', this.mouseDownHandler);
|
|
96
104
|
this.wrapper.appendChild(this.content);
|
|
97
|
-
|
|
98
|
-
this.
|
|
99
|
-
this.vGuides = this.createGuides('vertical');
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
/**
|
|
103
|
-
* 设置成固定定位模式
|
|
104
|
-
*/
|
|
105
|
-
public setFixed(): void {
|
|
106
|
-
this.wrapper.scrollTo({
|
|
107
|
-
top: 0,
|
|
108
|
-
});
|
|
109
|
-
this.wrapper.style.overflow = 'hidden';
|
|
110
|
-
// 要等滚动条滚上去,才刷新选中框
|
|
111
|
-
setTimeout(() => {
|
|
112
|
-
this.core.dr.refresh();
|
|
113
|
-
});
|
|
105
|
+
this.content.addEventListener('wheel', this.mouseWheelHandler);
|
|
106
|
+
this.content.addEventListener('mousemove', this.highlightHandler);
|
|
114
107
|
}
|
|
115
108
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
109
|
+
public setMode(mode: Mode) {
|
|
110
|
+
this.mode = mode;
|
|
111
|
+
this.scroll();
|
|
112
|
+
if (mode === Mode.FIXED) {
|
|
113
|
+
this.content.style.width = `${this.wrapperWidth}px`;
|
|
114
|
+
this.content.style.height = `${this.wrapperHeight}px`;
|
|
115
|
+
} else {
|
|
116
|
+
this.content.style.width = `${this.width}px`;
|
|
117
|
+
this.content.style.height = `${this.height}px`;
|
|
118
|
+
}
|
|
121
119
|
}
|
|
122
120
|
|
|
123
121
|
/**
|
|
@@ -129,17 +127,26 @@ export default class StageMask extends EventEmitter {
|
|
|
129
127
|
if (!page) return;
|
|
130
128
|
|
|
131
129
|
this.page = page;
|
|
132
|
-
this.
|
|
130
|
+
this.pageScrollParent = getScrollParent(page) || this.core.renderer.contentWindow?.document.documentElement || null;
|
|
131
|
+
this.pageResizeObserver?.disconnect();
|
|
133
132
|
|
|
134
133
|
if (typeof ResizeObserver !== 'undefined') {
|
|
135
|
-
this.
|
|
134
|
+
this.pageResizeObserver = new ResizeObserver((entries) => {
|
|
136
135
|
const [entry] = entries;
|
|
137
136
|
const { clientHeight, clientWidth } = entry.target;
|
|
138
137
|
this.setHeight(clientHeight);
|
|
139
138
|
this.setWidth(clientWidth);
|
|
140
139
|
});
|
|
141
140
|
|
|
142
|
-
this.
|
|
141
|
+
this.pageResizeObserver.observe(page);
|
|
142
|
+
|
|
143
|
+
this.wrapperResizeObserver = new ResizeObserver((entries) => {
|
|
144
|
+
const [entry] = entries;
|
|
145
|
+
const { clientHeight, clientWidth } = entry.target;
|
|
146
|
+
this.wrapperHeight = clientHeight;
|
|
147
|
+
this.wrapperWidth = clientWidth;
|
|
148
|
+
});
|
|
149
|
+
this.wrapperResizeObserver.observe(this.wrapper);
|
|
143
150
|
}
|
|
144
151
|
}
|
|
145
152
|
|
|
@@ -151,12 +158,10 @@ export default class StageMask extends EventEmitter {
|
|
|
151
158
|
if (!this.content) throw new Error('content 不存在');
|
|
152
159
|
|
|
153
160
|
el.appendChild(this.wrapper);
|
|
161
|
+
}
|
|
154
162
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
this.hGuides.resize();
|
|
158
|
-
});
|
|
159
|
-
this.parentResizeObserver.observe(el);
|
|
163
|
+
public setLayout(el: HTMLElement): void {
|
|
164
|
+
this.setMode(isFixedParent(el) ? Mode.FIXED : Mode.ABSOLUTE);
|
|
160
165
|
}
|
|
161
166
|
|
|
162
167
|
/**
|
|
@@ -165,144 +170,115 @@ export default class StageMask extends EventEmitter {
|
|
|
165
170
|
public destroy(): void {
|
|
166
171
|
this.content?.remove();
|
|
167
172
|
this.page = null;
|
|
168
|
-
this.
|
|
169
|
-
this.
|
|
170
|
-
this.
|
|
173
|
+
this.pageScrollParent = null;
|
|
174
|
+
this.pageResizeObserver?.disconnect();
|
|
175
|
+
this.wrapperResizeObserver?.disconnect();
|
|
176
|
+
super.destroy();
|
|
171
177
|
}
|
|
172
178
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
* @param show 是否显示
|
|
176
|
-
*/
|
|
177
|
-
public showGuides(show = true) {
|
|
178
|
-
this.hGuides.setState({
|
|
179
|
-
showGuides: show,
|
|
180
|
-
});
|
|
181
|
-
|
|
182
|
-
this.vGuides.setState({
|
|
183
|
-
showGuides: show,
|
|
184
|
-
});
|
|
185
|
-
}
|
|
179
|
+
private scroll() {
|
|
180
|
+
let { scrollLeft, scrollTop } = this;
|
|
186
181
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
this.
|
|
193
|
-
|
|
194
|
-
visibility: show ? '' : 'hidden',
|
|
195
|
-
},
|
|
196
|
-
});
|
|
197
|
-
|
|
198
|
-
this.vGuides.setState({
|
|
199
|
-
rulerStyle: {
|
|
200
|
-
visibility: show ? '' : 'hidden',
|
|
201
|
-
},
|
|
202
|
-
});
|
|
182
|
+
if (this.mode === Mode.FIXED) {
|
|
183
|
+
scrollLeft = 0;
|
|
184
|
+
scrollTop = 0;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
this.scrollRule(scrollTop);
|
|
188
|
+
this.scrollTo(scrollLeft, scrollTop);
|
|
203
189
|
}
|
|
204
190
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
*/
|
|
208
|
-
public clearGuides() {
|
|
209
|
-
this.vGuides.setState({
|
|
210
|
-
defaultGuides: [],
|
|
211
|
-
});
|
|
212
|
-
this.hGuides.setState({
|
|
213
|
-
defaultGuides: [],
|
|
214
|
-
});
|
|
191
|
+
private scrollTo(scrollLeft: number, scrollTop: number): void {
|
|
192
|
+
this.content.style.transform = `translate3d(${-scrollLeft}px, ${-scrollTop}px, 0)`;
|
|
215
193
|
}
|
|
216
194
|
|
|
217
195
|
/**
|
|
218
196
|
* 设置蒙层高度
|
|
219
197
|
* @param height 高度
|
|
220
198
|
*/
|
|
221
|
-
private setHeight(height: number
|
|
222
|
-
this.
|
|
199
|
+
private setHeight(height: number): void {
|
|
200
|
+
this.height = height;
|
|
201
|
+
this.content.style.height = `${height}px`;
|
|
223
202
|
}
|
|
224
203
|
|
|
225
204
|
/**
|
|
226
205
|
* 设置蒙层宽度
|
|
227
206
|
* @param width 宽度
|
|
228
207
|
*/
|
|
229
|
-
private setWidth(width: number
|
|
230
|
-
this.
|
|
208
|
+
private setWidth(width: number): void {
|
|
209
|
+
this.width = width;
|
|
210
|
+
this.content.style.width = `${width}px`;
|
|
231
211
|
}
|
|
232
212
|
|
|
233
213
|
/**
|
|
234
214
|
* 点击事件处理函数
|
|
235
215
|
* @param event 事件对象
|
|
236
216
|
*/
|
|
237
|
-
private mouseDownHandler =
|
|
238
|
-
|
|
217
|
+
private mouseDownHandler = (event: MouseEvent): void => {
|
|
218
|
+
this.emit('clearHighlight');
|
|
219
|
+
|
|
220
|
+
event.stopImmediatePropagation();
|
|
221
|
+
event.stopPropagation();
|
|
222
|
+
|
|
223
|
+
if (event.button !== MouseButton.LEFT && event.button !== MouseButton.RIGHT) return;
|
|
239
224
|
|
|
240
225
|
// 点击的对象如果是选中框,则不需要再触发选中了,而可能是拖动行为
|
|
241
226
|
if ((event.target as HTMLDivElement).className.indexOf('moveable-control') !== -1) {
|
|
242
227
|
return;
|
|
243
228
|
}
|
|
244
229
|
|
|
245
|
-
this.
|
|
230
|
+
this.content.removeEventListener('mousemove', this.highlightHandler);
|
|
231
|
+
|
|
232
|
+
this.emit('beforeSelect', event);
|
|
233
|
+
|
|
234
|
+
// 如果是右键点击,这里的mouseup事件监听没有效果
|
|
235
|
+
globalThis.document.addEventListener('mouseup', this.mouseUpHandler);
|
|
246
236
|
};
|
|
247
237
|
|
|
248
238
|
private mouseUpHandler = (): void => {
|
|
249
|
-
|
|
250
|
-
this.
|
|
251
|
-
this.
|
|
239
|
+
globalThis.document.removeEventListener('mouseup', this.mouseUpHandler);
|
|
240
|
+
this.content.addEventListener('mousemove', this.highlightHandler);
|
|
241
|
+
this.emit('select');
|
|
252
242
|
};
|
|
253
243
|
|
|
254
|
-
private
|
|
255
|
-
|
|
256
|
-
this.
|
|
257
|
-
};
|
|
244
|
+
private mouseWheelHandler = (event: WheelEvent) => {
|
|
245
|
+
this.emit('clearHighlight');
|
|
246
|
+
if (!this.page) throw new Error('page 未初始化');
|
|
258
247
|
|
|
259
|
-
|
|
260
|
-
const {
|
|
248
|
+
const { deltaY, deltaX } = event;
|
|
249
|
+
const { height, wrapperHeight, width, wrapperWidth } = this;
|
|
261
250
|
|
|
262
|
-
const
|
|
263
|
-
|
|
264
|
-
let y = event.clientY;
|
|
251
|
+
const maxScrollTop = height - wrapperHeight;
|
|
252
|
+
const maxScrollLeft = width - wrapperWidth;
|
|
265
253
|
|
|
266
|
-
if (
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
254
|
+
if (maxScrollTop > 0) {
|
|
255
|
+
if (deltaY > 0) {
|
|
256
|
+
this.scrollTop = this.scrollTop + Math.min(maxScrollTop - this.scrollTop, deltaY);
|
|
257
|
+
} else {
|
|
258
|
+
this.scrollTop = Math.max(this.scrollTop + deltaY, 0);
|
|
271
259
|
}
|
|
272
260
|
}
|
|
273
261
|
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
if (!el.id.startsWith(GHOST_EL_ID_PREFIX) && (await this.canSelect(el, stop))) {
|
|
280
|
-
if (stopped) break;
|
|
281
|
-
|
|
282
|
-
this.emit('select', el, event);
|
|
283
|
-
this.target = el;
|
|
284
|
-
// 如果是右键点击,这里的mouseup事件监听没有效果
|
|
285
|
-
this.content?.addEventListener('mouseup', this.mouseUpHandler, true);
|
|
286
|
-
break;
|
|
262
|
+
if (width > wrapperWidth) {
|
|
263
|
+
if (deltaX > 0) {
|
|
264
|
+
this.scrollLeft = this.scrollLeft + Math.min(maxScrollLeft - this.scrollLeft, deltaX);
|
|
265
|
+
} else {
|
|
266
|
+
this.scrollLeft = Math.max(this.scrollLeft + deltaX, 0);
|
|
287
267
|
}
|
|
288
268
|
}
|
|
289
|
-
}
|
|
290
269
|
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
270
|
+
if (this.mode !== Mode.FIXED) {
|
|
271
|
+
this.scrollTo(this.scrollLeft, this.scrollTop);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
if (this.pageScrollParent) {
|
|
275
|
+
this.pageScrollParent.scrollTo({
|
|
276
|
+
top: this.scrollTop,
|
|
277
|
+
left: this.scrollLeft,
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
this.scroll();
|
|
298
281
|
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
type,
|
|
302
|
-
displayDragPos: true,
|
|
303
|
-
backgroundColor: '#fff',
|
|
304
|
-
lineColor: '#000',
|
|
305
|
-
textColor: '#000',
|
|
306
|
-
style: this.getGuidesStyle(type),
|
|
307
|
-
});
|
|
282
|
+
this.emit('scroll', event);
|
|
283
|
+
};
|
|
308
284
|
}
|
package/src/StageRender.ts
CHANGED
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
|
|
19
19
|
import { EventEmitter } from 'events';
|
|
20
20
|
|
|
21
|
+
import { SELECTED_CLASS, ZIndex } from './const';
|
|
21
22
|
import StageCore from './StageCore';
|
|
22
23
|
import type { Runtime, RuntimeWindow, StageRenderConfig } from './types';
|
|
23
24
|
import { getHost, isSameDomain } from './util';
|
|
@@ -52,15 +53,7 @@ export default class StageRender extends EventEmitter {
|
|
|
52
53
|
height: 100%;
|
|
53
54
|
`;
|
|
54
55
|
|
|
55
|
-
this.iframe.
|
|
56
|
-
this.emit('onload');
|
|
57
|
-
if (this.render) {
|
|
58
|
-
const el = await this.render(this.core);
|
|
59
|
-
if (el) {
|
|
60
|
-
this.iframe?.contentDocument?.body?.appendChild(el);
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
};
|
|
56
|
+
this.iframe.addEventListener('load', this.loadHandler);
|
|
64
57
|
}
|
|
65
58
|
|
|
66
59
|
public getMagicApi = () => ({
|
|
@@ -113,9 +106,32 @@ export default class StageRender extends EventEmitter {
|
|
|
113
106
|
* 销毁实例
|
|
114
107
|
*/
|
|
115
108
|
public destroy(): void {
|
|
109
|
+
this.iframe?.removeEventListener('load', this.loadHandler);
|
|
116
110
|
this.contentWindow = null;
|
|
117
111
|
this.iframe?.remove();
|
|
118
112
|
this.iframe = undefined;
|
|
119
113
|
this.removeAllListeners();
|
|
120
114
|
}
|
|
115
|
+
|
|
116
|
+
private loadHandler = async () => {
|
|
117
|
+
this.emit('onload');
|
|
118
|
+
|
|
119
|
+
if (this.render) {
|
|
120
|
+
const el = await this.render(this.core);
|
|
121
|
+
if (el) {
|
|
122
|
+
this.iframe?.contentDocument?.body?.appendChild(el);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (this.contentWindow) {
|
|
127
|
+
const style = this.contentWindow.document.createElement('style');
|
|
128
|
+
style.id = 'tmagic-stage-render';
|
|
129
|
+
style.innerHTML = `
|
|
130
|
+
.${SELECTED_CLASS}, .${SELECTED_CLASS}-parent {
|
|
131
|
+
z-index: ${ZIndex.SELECTED_EL};
|
|
132
|
+
}
|
|
133
|
+
`;
|
|
134
|
+
this.contentWindow.document.head.appendChild(style);
|
|
135
|
+
}
|
|
136
|
+
};
|
|
121
137
|
}
|
package/src/const.ts
CHANGED
|
@@ -19,5 +19,31 @@
|
|
|
19
19
|
// 流式布局下拖动时需要clone一个镜像节点,镜像节点的id前缀
|
|
20
20
|
export const GHOST_EL_ID_PREFIX = 'ghost_el_';
|
|
21
21
|
|
|
22
|
+
export const DRAG_EL_ID_PREFIX = 'drag_el_';
|
|
23
|
+
|
|
22
24
|
// 默认放到缩小倍数
|
|
23
25
|
export const DEFAULT_ZOOM = 1;
|
|
26
|
+
|
|
27
|
+
export enum GuidesType {
|
|
28
|
+
HORIZONTAL = 'horizontal',
|
|
29
|
+
VERTICAL = 'vertical',
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export enum ZIndex {
|
|
33
|
+
MASK = '99999',
|
|
34
|
+
SELECTED_EL = '666',
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export enum MouseButton {
|
|
38
|
+
LEFT = 0,
|
|
39
|
+
MIDDLE = 1,
|
|
40
|
+
RIGHT = 2,
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export enum Mode {
|
|
44
|
+
ABSOLUTE = 'absolute',
|
|
45
|
+
FIXED = 'fixed',
|
|
46
|
+
SORTABLE = 'sortable',
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export const SELECTED_CLASS = 'tmagic-stage-selected-area';
|
package/src/types.ts
CHANGED
|
@@ -1,10 +1,29 @@
|
|
|
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
|
+
|
|
1
19
|
import { MoveableOptions } from 'react-moveable/declaration/types';
|
|
2
20
|
|
|
3
21
|
import { Id, MApp, MNode } from '@tmagic/schema';
|
|
4
22
|
|
|
23
|
+
import { GuidesType } from './const';
|
|
5
24
|
import StageCore from './StageCore';
|
|
6
25
|
|
|
7
|
-
export type CanSelect = (el: HTMLElement, stop: () => boolean) => boolean | Promise<boolean>;
|
|
26
|
+
export type CanSelect = (el: HTMLElement, event: MouseEvent, stop: () => boolean) => boolean | Promise<boolean>;
|
|
8
27
|
|
|
9
28
|
export type StageCoreConfig = {
|
|
10
29
|
/** 需要对齐的dom节点的CSS选择器字符串 */
|
|
@@ -41,23 +60,10 @@ export interface Offset {
|
|
|
41
60
|
top: number;
|
|
42
61
|
}
|
|
43
62
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
*
|
|
49
|
-
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
50
|
-
* you may not use this file except in compliance with the License.
|
|
51
|
-
* You may obtain a copy of the License at
|
|
52
|
-
*
|
|
53
|
-
* http://www.apache.org/licenses/LICENSE-2.0
|
|
54
|
-
*
|
|
55
|
-
* Unless required by applicable law or agreed to in writing, software
|
|
56
|
-
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
57
|
-
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
58
|
-
* See the License for the specific language governing permissions and
|
|
59
|
-
* limitations under the License.
|
|
60
|
-
*/
|
|
63
|
+
export interface GuidesEventData {
|
|
64
|
+
type: GuidesType;
|
|
65
|
+
guides: number[];
|
|
66
|
+
}
|
|
61
67
|
|
|
62
68
|
export interface UpdateEventData {
|
|
63
69
|
el: HTMLElement;
|
|
@@ -88,7 +94,7 @@ export interface RemoveData {
|
|
|
88
94
|
|
|
89
95
|
export interface Runtime {
|
|
90
96
|
beforeSelect?: (el: HTMLElement) => Promise<boolean> | boolean;
|
|
91
|
-
getSnapElements?: (el
|
|
97
|
+
getSnapElements?: (el?: HTMLElement) => HTMLElement[];
|
|
92
98
|
updateRootConfig: (config: MApp) => void;
|
|
93
99
|
updatePageId?: (id: Id) => void;
|
|
94
100
|
select?: (id: Id) => Promise<HTMLElement> | HTMLElement;
|
|
@@ -109,13 +115,7 @@ export interface RuntimeWindow extends Window {
|
|
|
109
115
|
magic: Magic;
|
|
110
116
|
}
|
|
111
117
|
|
|
112
|
-
export
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
export enum MouseButton {
|
|
118
|
-
LEFT = 0,
|
|
119
|
-
MIDDLE = 1,
|
|
120
|
-
RIGHT = 2,
|
|
118
|
+
export interface StageHighlightConfig {
|
|
119
|
+
core: StageCore;
|
|
120
|
+
container: HTMLElement;
|
|
121
121
|
}
|
package/src/util.ts
CHANGED
|
@@ -16,14 +16,9 @@
|
|
|
16
16
|
* limitations under the License.
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
|
+
import { Mode, SELECTED_CLASS } from './const';
|
|
19
20
|
import type { Offset } from './types';
|
|
20
21
|
|
|
21
|
-
export enum Mode {
|
|
22
|
-
ABSOLUTE = 'absolute',
|
|
23
|
-
FIXED = 'fixed',
|
|
24
|
-
SORTABLE = 'sortable',
|
|
25
|
-
}
|
|
26
|
-
|
|
27
22
|
export const getOffset = (el: HTMLElement): Offset => {
|
|
28
23
|
const { transform } = getComputedStyle(el);
|
|
29
24
|
const { offsetParent } = el;
|
|
@@ -113,8 +108,63 @@ export const isFixed = (el?: HTMLElement): boolean => {
|
|
|
113
108
|
return getComputedStyle(el).position === 'fixed';
|
|
114
109
|
};
|
|
115
110
|
|
|
111
|
+
export const isFixedParent = (el: HTMLElement) => {
|
|
112
|
+
let fixed = false;
|
|
113
|
+
let dom = el;
|
|
114
|
+
while (dom) {
|
|
115
|
+
fixed = isFixed(dom);
|
|
116
|
+
if (fixed) {
|
|
117
|
+
break;
|
|
118
|
+
}
|
|
119
|
+
const { parentElement } = dom;
|
|
120
|
+
if (!parentElement || parentElement.tagName === 'BODY') {
|
|
121
|
+
break;
|
|
122
|
+
}
|
|
123
|
+
dom = parentElement;
|
|
124
|
+
}
|
|
125
|
+
return fixed;
|
|
126
|
+
};
|
|
127
|
+
|
|
116
128
|
export const getMode = (el: HTMLElement): Mode => {
|
|
117
|
-
if (
|
|
129
|
+
if (isFixedParent(el)) return Mode.FIXED;
|
|
118
130
|
if (isStatic(el) || isRelative(el)) return Mode.SORTABLE;
|
|
119
131
|
return Mode.ABSOLUTE;
|
|
120
132
|
};
|
|
133
|
+
|
|
134
|
+
export const getScrollParent = (element: HTMLElement, includeHidden = false): HTMLElement | null => {
|
|
135
|
+
let style = getComputedStyle(element);
|
|
136
|
+
const overflowRegex = includeHidden ? /(auto|scroll|hidden)/ : /(auto|scroll)/;
|
|
137
|
+
|
|
138
|
+
if (isFixed(element)) return null;
|
|
139
|
+
for (let parent = element; parent.parentElement; ) {
|
|
140
|
+
parent = parent.parentElement;
|
|
141
|
+
style = getComputedStyle(parent);
|
|
142
|
+
if (isAbsolute(element) && isStatic(element)) {
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
if (overflowRegex.test(style.overflow + style.overflowY + style.overflowX)) return parent;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return null;
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
export const createDiv = ({ className, cssText }: { className: string; cssText: string }) => {
|
|
152
|
+
const el = globalThis.document.createElement('div');
|
|
153
|
+
el.className = className;
|
|
154
|
+
el.style.cssText = cssText;
|
|
155
|
+
return el;
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
export const removeSelectedClassName = (doc: Document) => {
|
|
159
|
+
const oldEl = doc.querySelector(`.${SELECTED_CLASS}`);
|
|
160
|
+
|
|
161
|
+
if (oldEl) {
|
|
162
|
+
oldEl.classList.remove(SELECTED_CLASS);
|
|
163
|
+
(oldEl.parentNode as HTMLDivElement)?.classList.remove(`${SELECTED_CLASS}-parent`);
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
export const addSelectedClassName = (el: Element) => {
|
|
168
|
+
el.classList.add(SELECTED_CLASS);
|
|
169
|
+
(el.parentNode as Element)?.classList.add(`${SELECTED_CLASS}-parent`);
|
|
170
|
+
};
|