@tmagic/stage 1.7.6 → 1.7.8-beta.1
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 +235 -164
- package/dist/es/ActionManager.js +470 -0
- package/dist/es/DragResizeHelper.js +280 -0
- package/dist/es/MoveableActionsAble.js +64 -0
- package/dist/es/MoveableOptionsManager.js +204 -0
- package/dist/es/Rule.js +139 -0
- package/dist/es/StageCore.js +307 -0
- package/dist/es/StageDragResize.js +233 -0
- package/dist/es/StageHighlight.js +62 -0
- package/dist/es/StageMask.js +256 -0
- package/dist/es/StageMultiDragResize.js +153 -0
- package/dist/es/StageRender.js +186 -0
- package/dist/es/TargetShadow.js +68 -0
- package/dist/es/const.js +91 -0
- package/dist/es/index.js +12 -0
- package/dist/es/moveable-able.js +4 -0
- package/dist/es/style.js +4 -0
- package/dist/es/util.js +192 -0
- package/dist/tmagic-stage.umd.cjs +4811 -5308
- package/package.json +5 -4
- package/types/index.d.ts +857 -841
- package/dist/tmagic-stage.js +0 -2833
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
import { Mode, ZIndex } from "./const.js";
|
|
2
|
+
import { getScrollParent, isFixedParent } from "./util.js";
|
|
3
|
+
import Rule from "./Rule.js";
|
|
4
|
+
import { createDiv, getDocument, injectStyle } from "@tmagic/core";
|
|
5
|
+
//#region packages/stage/src/StageMask.ts
|
|
6
|
+
var wrapperClassName = "editor-mask-wrapper";
|
|
7
|
+
var hideScrollbar = () => {
|
|
8
|
+
injectStyle(getDocument(), `.${wrapperClassName}::-webkit-scrollbar { width: 0 !important; display: none }`);
|
|
9
|
+
};
|
|
10
|
+
var createContent = () => createDiv({
|
|
11
|
+
className: "editor-mask",
|
|
12
|
+
cssText: `
|
|
13
|
+
position: absolute;
|
|
14
|
+
top: 0;
|
|
15
|
+
left: 0;
|
|
16
|
+
transform: translate3d(0, 0, 0);
|
|
17
|
+
`
|
|
18
|
+
});
|
|
19
|
+
var createWrapper = () => {
|
|
20
|
+
const el = createDiv({
|
|
21
|
+
className: wrapperClassName,
|
|
22
|
+
cssText: `
|
|
23
|
+
position: absolute;
|
|
24
|
+
top: 0;
|
|
25
|
+
left: 0;
|
|
26
|
+
height: 100%;
|
|
27
|
+
width: 100%;
|
|
28
|
+
overflow: hidden;
|
|
29
|
+
z-index: ${ZIndex.MASK};
|
|
30
|
+
`
|
|
31
|
+
});
|
|
32
|
+
hideScrollbar();
|
|
33
|
+
return el;
|
|
34
|
+
};
|
|
35
|
+
/**
|
|
36
|
+
* 蒙层
|
|
37
|
+
* @description 用于拦截页面的点击动作,避免点击时触发组件自身动作;在编辑器中点击组件应当是选中组件;
|
|
38
|
+
*/
|
|
39
|
+
var StageMask = class extends Rule {
|
|
40
|
+
content = createContent();
|
|
41
|
+
wrapper;
|
|
42
|
+
page = null;
|
|
43
|
+
scrollTop = 0;
|
|
44
|
+
scrollLeft = 0;
|
|
45
|
+
width = 0;
|
|
46
|
+
height = 0;
|
|
47
|
+
wrapperHeight = 0;
|
|
48
|
+
wrapperWidth = 0;
|
|
49
|
+
maxScrollTop = 0;
|
|
50
|
+
maxScrollLeft = 0;
|
|
51
|
+
mode = Mode.ABSOLUTE;
|
|
52
|
+
pageScrollParent = null;
|
|
53
|
+
intersectionObserver = null;
|
|
54
|
+
wrapperResizeObserver = null;
|
|
55
|
+
constructor(options) {
|
|
56
|
+
const wrapper = createWrapper();
|
|
57
|
+
super(wrapper, options);
|
|
58
|
+
this.wrapper = wrapper;
|
|
59
|
+
this.content.addEventListener("wheel", this.mouseWheelHandler);
|
|
60
|
+
this.wrapper.appendChild(this.content);
|
|
61
|
+
}
|
|
62
|
+
setMode(mode) {
|
|
63
|
+
this.mode = mode;
|
|
64
|
+
this.scroll();
|
|
65
|
+
this.content.dataset.mode = mode;
|
|
66
|
+
if (mode === Mode.FIXED) {
|
|
67
|
+
this.content.style.width = `${this.wrapperWidth}px`;
|
|
68
|
+
this.content.style.height = `${this.wrapperHeight}px`;
|
|
69
|
+
} else {
|
|
70
|
+
this.content.style.width = `${this.width}px`;
|
|
71
|
+
this.content.style.height = `${this.height}px`;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* 初始化视窗和蒙层监听,监听元素是否在视窗区域、监听mask蒙层所在的wrapper大小变化
|
|
76
|
+
* @description 初始化视窗和蒙层监听
|
|
77
|
+
* @param page 页面Dom节点
|
|
78
|
+
*/
|
|
79
|
+
observe(page) {
|
|
80
|
+
if (!page) return;
|
|
81
|
+
this.page = page;
|
|
82
|
+
this.initObserverIntersection();
|
|
83
|
+
this.initObserverWrapper();
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* 处理页面大小变更,同步页面和mask大小
|
|
87
|
+
* @param entries ResizeObserverEntry,获取页面最新大小
|
|
88
|
+
*/
|
|
89
|
+
pageResize(entries) {
|
|
90
|
+
const [entry] = entries;
|
|
91
|
+
const { clientHeight, clientWidth } = entry.target;
|
|
92
|
+
this.setHeight(clientHeight);
|
|
93
|
+
this.setWidth(clientWidth);
|
|
94
|
+
this.scroll();
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* 监听一个组件是否在画布可视区域内
|
|
98
|
+
* @param el 被选中的组件,可能是左侧目录树中选中的
|
|
99
|
+
*/
|
|
100
|
+
observerIntersection(el) {
|
|
101
|
+
this.intersectionObserver?.observe(el);
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* 挂载Dom节点
|
|
105
|
+
* @param el 将蒙层挂载到该Dom节点上
|
|
106
|
+
*/
|
|
107
|
+
mount(el) {
|
|
108
|
+
if (!this.content) throw new Error("content 不存在");
|
|
109
|
+
el.appendChild(this.wrapper);
|
|
110
|
+
}
|
|
111
|
+
setLayout(el) {
|
|
112
|
+
this.setMode(isFixedParent(el) ? Mode.FIXED : Mode.ABSOLUTE);
|
|
113
|
+
}
|
|
114
|
+
scrollIntoView(el) {
|
|
115
|
+
if (!this.page || el.getBoundingClientRect().left >= this.page.scrollWidth) return;
|
|
116
|
+
const scrollParent = getScrollParent(el);
|
|
117
|
+
if (scrollParent && scrollParent !== this.pageScrollParent) {
|
|
118
|
+
this.scrollIntoView(scrollParent);
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
el.scrollIntoView();
|
|
122
|
+
if (!this.pageScrollParent) return;
|
|
123
|
+
this.scrollLeft = this.pageScrollParent.scrollLeft;
|
|
124
|
+
this.scrollTop = this.pageScrollParent.scrollTop;
|
|
125
|
+
this.scroll();
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* 销毁实例
|
|
129
|
+
*/
|
|
130
|
+
destroy() {
|
|
131
|
+
super.destroy();
|
|
132
|
+
this.content?.remove();
|
|
133
|
+
this.page = null;
|
|
134
|
+
this.pageScrollParent = null;
|
|
135
|
+
this.wrapperResizeObserver?.disconnect();
|
|
136
|
+
}
|
|
137
|
+
on(eventName, listener) {
|
|
138
|
+
return super.on(eventName, listener);
|
|
139
|
+
}
|
|
140
|
+
emit(eventName, ...args) {
|
|
141
|
+
return super.emit(eventName, ...args);
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* 监听选中元素是否在画布可视区域内,如果目标元素不在可视区域内,通过滚动使该元素出现在可视区域
|
|
145
|
+
*/
|
|
146
|
+
initObserverIntersection() {
|
|
147
|
+
this.pageScrollParent = getScrollParent(this.page) || null;
|
|
148
|
+
this.intersectionObserver?.disconnect();
|
|
149
|
+
if (typeof IntersectionObserver !== "undefined") this.intersectionObserver = new IntersectionObserver((entries) => {
|
|
150
|
+
entries.forEach((entry) => {
|
|
151
|
+
const { target, intersectionRatio } = entry;
|
|
152
|
+
if (intersectionRatio <= 0) this.scrollIntoView(target);
|
|
153
|
+
this.intersectionObserver?.unobserve(target);
|
|
154
|
+
});
|
|
155
|
+
}, {
|
|
156
|
+
root: this.pageScrollParent,
|
|
157
|
+
rootMargin: "0px",
|
|
158
|
+
threshold: 1
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* 监听mask的容器大小变化
|
|
163
|
+
*/
|
|
164
|
+
initObserverWrapper() {
|
|
165
|
+
this.wrapperResizeObserver?.disconnect();
|
|
166
|
+
if (typeof ResizeObserver !== "undefined") {
|
|
167
|
+
this.wrapperResizeObserver = new ResizeObserver((entries) => {
|
|
168
|
+
const [entry] = entries;
|
|
169
|
+
const { clientHeight, clientWidth } = entry.target;
|
|
170
|
+
this.wrapperHeight = clientHeight;
|
|
171
|
+
this.wrapperWidth = clientWidth;
|
|
172
|
+
if (this.mode === Mode.FIXED) {
|
|
173
|
+
this.content.style.width = `${this.wrapperWidth}px`;
|
|
174
|
+
this.content.style.height = `${this.wrapperHeight}px`;
|
|
175
|
+
}
|
|
176
|
+
this.setMaxScrollLeft();
|
|
177
|
+
this.setMaxScrollTop();
|
|
178
|
+
});
|
|
179
|
+
this.wrapperResizeObserver.observe(this.wrapper);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
scroll() {
|
|
183
|
+
this.fixScrollValue();
|
|
184
|
+
let { scrollLeft, scrollTop } = this;
|
|
185
|
+
if (this.pageScrollParent) this.pageScrollParent.scrollTo({
|
|
186
|
+
top: scrollTop,
|
|
187
|
+
left: scrollLeft
|
|
188
|
+
});
|
|
189
|
+
if (this.mode === Mode.FIXED) {
|
|
190
|
+
scrollLeft = 0;
|
|
191
|
+
scrollTop = 0;
|
|
192
|
+
}
|
|
193
|
+
this.scrollRule(scrollTop);
|
|
194
|
+
this.scrollTo(scrollLeft, scrollTop);
|
|
195
|
+
}
|
|
196
|
+
scrollTo(scrollLeft, scrollTop) {
|
|
197
|
+
this.content.style.transform = `translate3d(${-scrollLeft}px, ${-scrollTop}px, 0)`;
|
|
198
|
+
const event = new CustomEvent("customScroll", { detail: {
|
|
199
|
+
scrollLeft: this.scrollLeft,
|
|
200
|
+
scrollTop: this.scrollTop
|
|
201
|
+
} });
|
|
202
|
+
this.content.dispatchEvent(event);
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* 设置蒙层高度
|
|
206
|
+
* @param height 高度
|
|
207
|
+
*/
|
|
208
|
+
setHeight(height) {
|
|
209
|
+
this.height = height;
|
|
210
|
+
this.setMaxScrollTop();
|
|
211
|
+
if (this.mode !== Mode.FIXED) this.content.style.height = `${height}px`;
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* 设置蒙层宽度
|
|
215
|
+
* @param width 宽度
|
|
216
|
+
*/
|
|
217
|
+
setWidth(width) {
|
|
218
|
+
this.width = width;
|
|
219
|
+
this.setMaxScrollLeft();
|
|
220
|
+
if (this.mode !== Mode.FIXED) this.content.style.width = `${width}px`;
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* 计算并设置最大滚动宽度
|
|
224
|
+
*/
|
|
225
|
+
setMaxScrollLeft() {
|
|
226
|
+
this.maxScrollLeft = Math.max(this.width - this.wrapperWidth, 0);
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* 计算并设置最大滚动高度
|
|
230
|
+
*/
|
|
231
|
+
setMaxScrollTop() {
|
|
232
|
+
this.maxScrollTop = Math.max(this.height - this.wrapperHeight, 0);
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* 修复滚动距离
|
|
236
|
+
* 由于滚动容器变化等因素,会导致当前滚动的距离不正确
|
|
237
|
+
*/
|
|
238
|
+
fixScrollValue() {
|
|
239
|
+
if (this.scrollTop < 0) this.scrollTop = 0;
|
|
240
|
+
if (this.scrollLeft < 0) this.scrollLeft = 0;
|
|
241
|
+
if (this.maxScrollTop < this.scrollTop) this.scrollTop = this.maxScrollTop;
|
|
242
|
+
if (this.maxScrollLeft < this.scrollLeft) this.scrollLeft = this.maxScrollLeft;
|
|
243
|
+
}
|
|
244
|
+
mouseWheelHandler = (event) => {
|
|
245
|
+
if (!this.page) throw new Error("page 未初始化");
|
|
246
|
+
const { deltaY, deltaX } = event;
|
|
247
|
+
if (this.page.clientHeight < this.wrapperHeight && deltaY) return;
|
|
248
|
+
if (this.page.clientWidth < this.wrapperWidth && deltaX) return;
|
|
249
|
+
if (this.maxScrollTop > 0) this.scrollTop = this.scrollTop + deltaY;
|
|
250
|
+
if (this.maxScrollLeft > 0) this.scrollLeft = this.scrollLeft + deltaX;
|
|
251
|
+
this.scroll();
|
|
252
|
+
this.emit("scroll", event);
|
|
253
|
+
};
|
|
254
|
+
};
|
|
255
|
+
//#endregion
|
|
256
|
+
export { StageMask as default };
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { DRAG_EL_ID_PREFIX, Mode, StageDragStatus } from "./const.js";
|
|
2
|
+
import { getMode } from "./util.js";
|
|
3
|
+
import MoveableOptionsManager from "./MoveableOptionsManager.js";
|
|
4
|
+
import { getIdFromEl } from "@tmagic/core";
|
|
5
|
+
import Moveable from "moveable";
|
|
6
|
+
//#region packages/stage/src/StageMultiDragResize.ts
|
|
7
|
+
var StageMultiDragResize = class extends MoveableOptionsManager {
|
|
8
|
+
/** 画布容器 */
|
|
9
|
+
container;
|
|
10
|
+
/** 多选:目标节点组 */
|
|
11
|
+
targetList = [];
|
|
12
|
+
/** Moveable多选拖拽类实例 */
|
|
13
|
+
moveableForMulti;
|
|
14
|
+
dragStatus = StageDragStatus.END;
|
|
15
|
+
dragResizeHelper;
|
|
16
|
+
getRenderDocument;
|
|
17
|
+
delayedMarkContainer;
|
|
18
|
+
markContainerEnd;
|
|
19
|
+
constructor(config) {
|
|
20
|
+
const moveableOptionsManagerConfig = {
|
|
21
|
+
container: config.container,
|
|
22
|
+
moveableOptions: config.moveableOptions,
|
|
23
|
+
getRootContainer: config.getRootContainer
|
|
24
|
+
};
|
|
25
|
+
super(moveableOptionsManagerConfig);
|
|
26
|
+
this.delayedMarkContainer = config.delayedMarkContainer;
|
|
27
|
+
this.markContainerEnd = config.markContainerEnd;
|
|
28
|
+
this.container = config.container;
|
|
29
|
+
this.getRenderDocument = config.getRenderDocument;
|
|
30
|
+
this.dragResizeHelper = config.dragResizeHelper;
|
|
31
|
+
this.on("update-moveable", () => {
|
|
32
|
+
if (this.moveableForMulti) this.updateMoveable();
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* 多选
|
|
37
|
+
* @param els
|
|
38
|
+
*/
|
|
39
|
+
multiSelect(els) {
|
|
40
|
+
if (els.length === 0) return;
|
|
41
|
+
this.mode = getMode(els[0]);
|
|
42
|
+
this.targetList = els;
|
|
43
|
+
this.dragResizeHelper.updateGroup(els);
|
|
44
|
+
this.setElementGuidelines(this.targetList);
|
|
45
|
+
this.moveableForMulti?.destroy();
|
|
46
|
+
this.dragResizeHelper.clear();
|
|
47
|
+
this.moveableForMulti = new Moveable(this.container, this.getOptions(true, { target: this.dragResizeHelper.getShadowEls() }));
|
|
48
|
+
let timeout;
|
|
49
|
+
this.moveableForMulti.on("resizeGroupStart", (e) => {
|
|
50
|
+
this.dragResizeHelper.onResizeGroupStart(e);
|
|
51
|
+
this.dragStatus = StageDragStatus.START;
|
|
52
|
+
}).on("resizeGroup", (e) => {
|
|
53
|
+
this.dragResizeHelper.onResizeGroup(e);
|
|
54
|
+
this.dragStatus = StageDragStatus.ING;
|
|
55
|
+
}).on("resizeGroupEnd", () => {
|
|
56
|
+
this.update(true);
|
|
57
|
+
this.dragStatus = StageDragStatus.END;
|
|
58
|
+
}).on("dragGroupStart", (e) => {
|
|
59
|
+
this.dragResizeHelper.onDragGroupStart(e);
|
|
60
|
+
this.dragStatus = StageDragStatus.START;
|
|
61
|
+
}).on("dragGroup", (e) => {
|
|
62
|
+
if (timeout) {
|
|
63
|
+
globalThis.clearTimeout(timeout);
|
|
64
|
+
timeout = void 0;
|
|
65
|
+
}
|
|
66
|
+
timeout = this.delayedMarkContainer(e.inputEvent, this.targetList);
|
|
67
|
+
this.dragResizeHelper.onDragGroup(e);
|
|
68
|
+
this.dragStatus = StageDragStatus.ING;
|
|
69
|
+
}).on("dragGroupEnd", () => {
|
|
70
|
+
if (timeout) {
|
|
71
|
+
globalThis.clearTimeout(timeout);
|
|
72
|
+
timeout = void 0;
|
|
73
|
+
}
|
|
74
|
+
const parentEl = this.markContainerEnd();
|
|
75
|
+
this.update(false, parentEl);
|
|
76
|
+
this.dragStatus = StageDragStatus.END;
|
|
77
|
+
}).on("clickGroup", (e) => {
|
|
78
|
+
const { inputTarget, targets } = e;
|
|
79
|
+
if (targets.length > 1 && targets.includes(inputTarget)) {
|
|
80
|
+
const id = getIdFromEl()(inputTarget)?.replace(DRAG_EL_ID_PREFIX, "");
|
|
81
|
+
id && this.emit("change-to-select", id, e.inputEvent);
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
canSelect(el, selectedEl) {
|
|
86
|
+
const currentTargetMode = getMode(el);
|
|
87
|
+
let selectedElMode = "";
|
|
88
|
+
if (currentTargetMode === Mode.SORTABLE) return false;
|
|
89
|
+
if (this.targetList.length === 0 && selectedEl) selectedElMode = getMode(selectedEl);
|
|
90
|
+
else if (this.targetList.length > 0) selectedElMode = getMode(this.targetList[0]);
|
|
91
|
+
if (currentTargetMode !== selectedElMode) return false;
|
|
92
|
+
return true;
|
|
93
|
+
}
|
|
94
|
+
updateMoveable(eleList = this.targetList) {
|
|
95
|
+
if (!this.moveableForMulti) return;
|
|
96
|
+
if (!eleList) throw new Error("未选中任何节点");
|
|
97
|
+
this.targetList = eleList;
|
|
98
|
+
this.dragResizeHelper.setTargetList(eleList);
|
|
99
|
+
const options = this.getOptions(true, { target: this.dragResizeHelper.getShadowEls() });
|
|
100
|
+
Object.entries(options).forEach(([key, value]) => {
|
|
101
|
+
this.moveableForMulti[key] = value;
|
|
102
|
+
});
|
|
103
|
+
this.moveableForMulti.updateRect();
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* 清除多选状态
|
|
107
|
+
*/
|
|
108
|
+
clearSelectStatus() {
|
|
109
|
+
if (!this.moveableForMulti) return;
|
|
110
|
+
this.dragResizeHelper.clearMultiSelectStatus();
|
|
111
|
+
this.moveableForMulti.target = null;
|
|
112
|
+
this.moveableForMulti.updateTarget();
|
|
113
|
+
this.targetList = [];
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* 销毁实例
|
|
117
|
+
*/
|
|
118
|
+
destroy() {
|
|
119
|
+
this.moveableForMulti?.destroy();
|
|
120
|
+
this.dragResizeHelper.destroy();
|
|
121
|
+
}
|
|
122
|
+
on(eventName, listener) {
|
|
123
|
+
return super.on(eventName, listener);
|
|
124
|
+
}
|
|
125
|
+
emit(eventName, ...args) {
|
|
126
|
+
return super.emit(eventName, ...args);
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* 拖拽完成后将更新的位置信息暴露给上层业务方,业务方可以接收事件进行保存
|
|
130
|
+
* @param isResize 是否进行大小缩放
|
|
131
|
+
*/
|
|
132
|
+
update(isResize = false, parentEl = null) {
|
|
133
|
+
if (this.targetList.length === 0) return;
|
|
134
|
+
const doc = this.getRenderDocument();
|
|
135
|
+
if (!doc) return;
|
|
136
|
+
const data = this.targetList.map((targetItem) => {
|
|
137
|
+
const rect = this.dragResizeHelper.getUpdatedElRect(targetItem, parentEl, doc);
|
|
138
|
+
return {
|
|
139
|
+
el: targetItem,
|
|
140
|
+
style: isResize ? rect : {
|
|
141
|
+
left: rect.left,
|
|
142
|
+
top: rect.top
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
});
|
|
146
|
+
this.emit("update", {
|
|
147
|
+
data,
|
|
148
|
+
parentEl
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
//#endregion
|
|
153
|
+
export { StageMultiDragResize as default };
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import { RenderType } from "./const.js";
|
|
2
|
+
import { addSelectedClassName, removeSelectedClassName } from "./util.js";
|
|
3
|
+
import style_default from "./style.js";
|
|
4
|
+
import { EventEmitter } from "events";
|
|
5
|
+
import { getElById, getHost, guid, injectStyle, isSameDomain } from "@tmagic/core";
|
|
6
|
+
//#region packages/stage/src/StageRender.ts
|
|
7
|
+
var StageRender = class extends EventEmitter {
|
|
8
|
+
/** 组件的js、css执行的环境,直接渲染为当前window,iframe渲染则为iframe.contentWindow */
|
|
9
|
+
contentWindow = null;
|
|
10
|
+
runtime = null;
|
|
11
|
+
iframe;
|
|
12
|
+
nativeContainer;
|
|
13
|
+
runtimeUrl;
|
|
14
|
+
zoom = 1;
|
|
15
|
+
renderType;
|
|
16
|
+
customizedRender;
|
|
17
|
+
constructor({ runtimeUrl, zoom, customizedRender, renderType = RenderType.IFRAME }) {
|
|
18
|
+
super();
|
|
19
|
+
this.renderType = renderType;
|
|
20
|
+
this.runtimeUrl = runtimeUrl || "";
|
|
21
|
+
this.customizedRender = customizedRender;
|
|
22
|
+
this.setZoom(zoom);
|
|
23
|
+
if (this.renderType === RenderType.IFRAME) this.createIframe();
|
|
24
|
+
else if (this.renderType === RenderType.NATIVE) this.createNativeContainer();
|
|
25
|
+
}
|
|
26
|
+
getMagicApi = () => ({
|
|
27
|
+
id: guid(),
|
|
28
|
+
onPageElUpdate: (el) => {
|
|
29
|
+
this.emit("page-el-update", el);
|
|
30
|
+
},
|
|
31
|
+
onRuntimeReady: (runtime) => {
|
|
32
|
+
if (this.runtime) return;
|
|
33
|
+
this.runtime = runtime;
|
|
34
|
+
globalThis.runtime = runtime;
|
|
35
|
+
this.emit("runtime-ready", runtime);
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
async add(data) {
|
|
39
|
+
return (await this.getRuntime())?.add?.(data);
|
|
40
|
+
}
|
|
41
|
+
async remove(data) {
|
|
42
|
+
return (await this.getRuntime())?.remove?.(data);
|
|
43
|
+
}
|
|
44
|
+
async update(data) {
|
|
45
|
+
(await this.getRuntime())?.update?.(data);
|
|
46
|
+
}
|
|
47
|
+
async select(ids) {
|
|
48
|
+
const runtime = await this.getRuntime();
|
|
49
|
+
for (const id of ids) {
|
|
50
|
+
await runtime?.select?.(id);
|
|
51
|
+
this.flagSelectedEl(this.getTargetElement(id));
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
setZoom(zoom = 1) {
|
|
55
|
+
this.zoom = zoom;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* 挂载Dom节点
|
|
59
|
+
* @param el 将页面挂载到该Dom节点上
|
|
60
|
+
*/
|
|
61
|
+
async mount(el) {
|
|
62
|
+
if (this.iframe) {
|
|
63
|
+
if (!isSameDomain(this.runtimeUrl) && this.runtimeUrl) {
|
|
64
|
+
let html = await fetch(this.runtimeUrl).then((res) => res.text());
|
|
65
|
+
const base = `${location.protocol}//${getHost(this.runtimeUrl)}`;
|
|
66
|
+
html = html.replace("<head>", `<head>\n<base href="${base}">`);
|
|
67
|
+
this.iframe.srcdoc = html;
|
|
68
|
+
}
|
|
69
|
+
el.appendChild(this.iframe);
|
|
70
|
+
this.postTmagicRuntimeReady();
|
|
71
|
+
} else if (this.nativeContainer) el.appendChild(this.nativeContainer);
|
|
72
|
+
}
|
|
73
|
+
getRuntime = () => {
|
|
74
|
+
if (this.runtime) return Promise.resolve(this.runtime);
|
|
75
|
+
return new Promise((resolve) => {
|
|
76
|
+
const listener = (runtime) => {
|
|
77
|
+
this.off("runtime-ready", listener);
|
|
78
|
+
resolve(runtime);
|
|
79
|
+
};
|
|
80
|
+
this.on("runtime-ready", listener);
|
|
81
|
+
});
|
|
82
|
+
};
|
|
83
|
+
getDocument() {
|
|
84
|
+
return this.contentWindow?.document;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* 通过坐标获得坐标下所有HTML元素数组
|
|
88
|
+
* @param point 坐标
|
|
89
|
+
* @returns 坐标下方所有HTML元素数组,会包含父元素直至html,元素层叠时返回顺序是从上到下
|
|
90
|
+
*/
|
|
91
|
+
getElementsFromPoint(point) {
|
|
92
|
+
let x = point.clientX;
|
|
93
|
+
let y = point.clientY;
|
|
94
|
+
if (this.iframe) {
|
|
95
|
+
const rect = this.iframe.getClientRects()[0];
|
|
96
|
+
if (rect) {
|
|
97
|
+
x = x - rect.left;
|
|
98
|
+
y = y - rect.top;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return this.getDocument()?.elementsFromPoint(x / this.zoom, y / this.zoom);
|
|
102
|
+
}
|
|
103
|
+
getTargetElement(id) {
|
|
104
|
+
return getElById()(this.getDocument(), id);
|
|
105
|
+
}
|
|
106
|
+
postTmagicRuntimeReady() {
|
|
107
|
+
this.contentWindow = this.iframe?.contentWindow;
|
|
108
|
+
this.contentWindow.magic = this.getMagicApi();
|
|
109
|
+
this.contentWindow.postMessage({ tmagicRuntimeReady: true }, "*");
|
|
110
|
+
}
|
|
111
|
+
reloadIframe(url) {
|
|
112
|
+
if (this.renderType !== RenderType.IFRAME) return;
|
|
113
|
+
const el = this.iframe?.parentElement;
|
|
114
|
+
this.destroyIframe();
|
|
115
|
+
this.runtimeUrl = url;
|
|
116
|
+
this.createIframe();
|
|
117
|
+
this.mount(el);
|
|
118
|
+
this.runtime = null;
|
|
119
|
+
}
|
|
120
|
+
destroyIframe() {
|
|
121
|
+
this.iframe?.removeEventListener("load", this.iframeLoadHandler);
|
|
122
|
+
this.contentWindow = null;
|
|
123
|
+
this.iframe?.remove();
|
|
124
|
+
this.iframe = void 0;
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* 销毁实例
|
|
128
|
+
*/
|
|
129
|
+
destroy() {
|
|
130
|
+
this.destroyIframe();
|
|
131
|
+
globalThis.runtime = void 0;
|
|
132
|
+
this.removeAllListeners();
|
|
133
|
+
}
|
|
134
|
+
on(eventName, listener) {
|
|
135
|
+
return super.on(eventName, listener);
|
|
136
|
+
}
|
|
137
|
+
emit(eventName, ...args) {
|
|
138
|
+
return super.emit(eventName, ...args);
|
|
139
|
+
}
|
|
140
|
+
createIframe() {
|
|
141
|
+
this.iframe = globalThis.document.createElement("iframe");
|
|
142
|
+
this.iframe.src = this.runtimeUrl && isSameDomain(this.runtimeUrl) ? this.runtimeUrl : "";
|
|
143
|
+
this.iframe.style.cssText = `
|
|
144
|
+
border: 0;
|
|
145
|
+
width: 100%;
|
|
146
|
+
height: 100%;
|
|
147
|
+
`;
|
|
148
|
+
this.iframe.addEventListener("load", this.iframeLoadHandler);
|
|
149
|
+
return this.iframe;
|
|
150
|
+
}
|
|
151
|
+
async createNativeContainer() {
|
|
152
|
+
this.contentWindow = globalThis;
|
|
153
|
+
this.nativeContainer = globalThis.document.createElement("div");
|
|
154
|
+
this.contentWindow.magic = this.getMagicApi();
|
|
155
|
+
if (this.customizedRender) {
|
|
156
|
+
const el = await this.customizedRender();
|
|
157
|
+
if (el) this.nativeContainer.appendChild(el);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* 在runtime中对被选中的元素进行标记,部分组件有对选中态进行特殊显示的需求
|
|
162
|
+
* @param el 被选中的元素
|
|
163
|
+
*/
|
|
164
|
+
flagSelectedEl(el) {
|
|
165
|
+
const doc = this.getDocument();
|
|
166
|
+
if (doc) {
|
|
167
|
+
removeSelectedClassName(doc);
|
|
168
|
+
el && addSelectedClassName(el, doc);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
iframeLoadHandler = () => {
|
|
172
|
+
const handler = async () => {
|
|
173
|
+
if (!this.contentWindow?.magic) this.postTmagicRuntimeReady();
|
|
174
|
+
if (!this.contentWindow) return;
|
|
175
|
+
if (this.customizedRender) {
|
|
176
|
+
const el = await this.customizedRender();
|
|
177
|
+
if (el) this.contentWindow.document?.body?.appendChild(el);
|
|
178
|
+
}
|
|
179
|
+
this.emit("onload");
|
|
180
|
+
injectStyle(this.contentWindow.document, style_default);
|
|
181
|
+
};
|
|
182
|
+
handler();
|
|
183
|
+
};
|
|
184
|
+
};
|
|
185
|
+
//#endregion
|
|
186
|
+
export { StageRender as default };
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { Mode } from "./const.js";
|
|
2
|
+
import { getTargetElStyle, isFixedParent } from "./util.js";
|
|
3
|
+
import { getElById, getIdFromEl, guid, setIdToEl } from "@tmagic/core";
|
|
4
|
+
//#region packages/stage/src/TargetShadow.ts
|
|
5
|
+
/**
|
|
6
|
+
* 将选中的节点修正定位后,添加一个操作节点到蒙层上
|
|
7
|
+
*/
|
|
8
|
+
var TargetShadow = class {
|
|
9
|
+
el;
|
|
10
|
+
els = [];
|
|
11
|
+
idPrefix = `target_calibrate_${guid()}`;
|
|
12
|
+
container;
|
|
13
|
+
scrollLeft = 0;
|
|
14
|
+
scrollTop = 0;
|
|
15
|
+
zIndex;
|
|
16
|
+
updateDragEl;
|
|
17
|
+
constructor(config) {
|
|
18
|
+
this.container = config.container;
|
|
19
|
+
if (config.updateDragEl) this.updateDragEl = config.updateDragEl;
|
|
20
|
+
if (typeof config.zIndex !== "undefined") this.zIndex = config.zIndex;
|
|
21
|
+
if (config.idPrefix) this.idPrefix = `${config.idPrefix}_${guid()}`;
|
|
22
|
+
this.container.addEventListener("customScroll", this.scrollHandler);
|
|
23
|
+
}
|
|
24
|
+
update(target) {
|
|
25
|
+
this.el = this.updateEl(target, this.el);
|
|
26
|
+
return this.el;
|
|
27
|
+
}
|
|
28
|
+
updateGroup(targetGroup) {
|
|
29
|
+
if (this.els.length > targetGroup.length) this.els.slice(targetGroup.length - 1).forEach((el) => {
|
|
30
|
+
el.remove();
|
|
31
|
+
});
|
|
32
|
+
this.els = targetGroup.map((target, index) => this.updateEl(target, this.els[index]));
|
|
33
|
+
return this.els;
|
|
34
|
+
}
|
|
35
|
+
destroyEl() {
|
|
36
|
+
this.el?.remove();
|
|
37
|
+
this.el = void 0;
|
|
38
|
+
}
|
|
39
|
+
destroyEls() {
|
|
40
|
+
this.els.forEach((el) => {
|
|
41
|
+
el.remove();
|
|
42
|
+
});
|
|
43
|
+
this.els = [];
|
|
44
|
+
}
|
|
45
|
+
destroy() {
|
|
46
|
+
this.container.removeEventListener("customScroll", this.scrollHandler);
|
|
47
|
+
this.destroyEl();
|
|
48
|
+
this.destroyEls();
|
|
49
|
+
}
|
|
50
|
+
updateEl(target, src) {
|
|
51
|
+
const el = src || globalThis.document.createElement("div");
|
|
52
|
+
setIdToEl()(el, `${this.idPrefix}_${getIdFromEl()(target)}`);
|
|
53
|
+
el.style.cssText = getTargetElStyle(target, this.zIndex);
|
|
54
|
+
if (typeof this.updateDragEl === "function") this.updateDragEl(el, target, this.container);
|
|
55
|
+
const isFixed = isFixedParent(target);
|
|
56
|
+
const mode = this.container.dataset.mode || Mode.ABSOLUTE;
|
|
57
|
+
if (isFixed && mode !== Mode.FIXED) el.style.transform = `translate3d(${this.scrollLeft}px, ${this.scrollTop}px, 0)`;
|
|
58
|
+
else if (!isFixed && mode === Mode.FIXED) el.style.transform = `translate3d(${-this.scrollLeft}px, ${-this.scrollTop}px, 0)`;
|
|
59
|
+
if (!getElById()(globalThis.document, getIdFromEl()(el))) this.container.append(el);
|
|
60
|
+
return el;
|
|
61
|
+
}
|
|
62
|
+
scrollHandler = (e) => {
|
|
63
|
+
this.scrollLeft = e.detail.scrollLeft;
|
|
64
|
+
this.scrollTop = e.detail.scrollTop;
|
|
65
|
+
};
|
|
66
|
+
};
|
|
67
|
+
//#endregion
|
|
68
|
+
export { TargetShadow as default };
|