@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.
@@ -0,0 +1,307 @@
1
+ import "./const.js";
2
+ import ActionManager from "./ActionManager.js";
3
+ import StageMask from "./StageMask.js";
4
+ import StageRender from "./StageRender.js";
5
+ import { EventEmitter } from "events";
6
+ import { getIdFromEl } from "@tmagic/core";
7
+ //#region packages/stage/src/StageCore.ts
8
+ /**
9
+ * 负责管理画布,管理renderer、mask、actionManager三个核心类,并负责统一对外通信,包括提供接口和抛事件
10
+ */
11
+ var StageCore = class extends EventEmitter {
12
+ container;
13
+ renderer = null;
14
+ mask = null;
15
+ actionManager = null;
16
+ pageResizeObserver = null;
17
+ autoScrollIntoView;
18
+ customizedRender;
19
+ constructor(config) {
20
+ super();
21
+ this.autoScrollIntoView = config.autoScrollIntoView;
22
+ this.customizedRender = config.render;
23
+ this.renderer = new StageRender({
24
+ runtimeUrl: config.runtimeUrl,
25
+ zoom: config.zoom,
26
+ renderType: config.renderType,
27
+ customizedRender: async () => {
28
+ if (this?.customizedRender) return await this.customizedRender(this);
29
+ return null;
30
+ }
31
+ });
32
+ this.mask = new StageMask({
33
+ guidesOptions: config.guidesOptions,
34
+ disabledRule: config.disabledRule
35
+ });
36
+ this.actionManager = new ActionManager(this.getActionManagerConfig(config));
37
+ this.initRenderEvent();
38
+ this.initActionEvent();
39
+ this.initMaskEvent();
40
+ }
41
+ /**
42
+ * 单选选中元素
43
+ * @param id 选中的id
44
+ */
45
+ async select(id, event) {
46
+ const el = this.renderer?.getTargetElement(id) || null;
47
+ if (el === this.actionManager?.getSelectedEl()) return;
48
+ await this.renderer?.select([id]);
49
+ if (el) this.mask?.setLayout(el);
50
+ this.actionManager?.select(el, event);
51
+ if (el && (this.autoScrollIntoView || el.dataset.autoScrollIntoView)) this.mask?.observerIntersection(el);
52
+ }
53
+ /**
54
+ * 多选选中多个元素
55
+ * @param ids 选中元素的id列表
56
+ */
57
+ async multiSelect(ids) {
58
+ const els = ids.map((id) => this.renderer?.getTargetElement(id)).filter((el) => Boolean(el));
59
+ if (els.length === 0) return;
60
+ const lastEl = els[els.length - 1];
61
+ const isReduceSelect = els.length < this.actionManager.getSelectedElList().length;
62
+ await this.renderer?.select(ids);
63
+ lastEl && this.mask?.setLayout(lastEl);
64
+ this.actionManager?.multiSelect(ids);
65
+ if (lastEl && (this.autoScrollIntoView || lastEl.dataset.autoScrollIntoView) && !isReduceSelect) this.mask?.observerIntersection(lastEl);
66
+ }
67
+ /**
68
+ * 高亮选中元素
69
+ * @param el 要高亮的元素
70
+ */
71
+ highlight(id) {
72
+ this.actionManager?.highlight(id);
73
+ }
74
+ clearHighlight() {
75
+ this.actionManager?.clearHighlight();
76
+ }
77
+ /**
78
+ * 更新组件
79
+ * @param data 更新组件的数据
80
+ */
81
+ async update(data) {
82
+ const { config } = data;
83
+ await this.renderer?.update(data);
84
+ setTimeout(() => {
85
+ const el = this.renderer?.getTargetElement(`${config.id}`);
86
+ if (el && this.actionManager?.isSelectedEl(el)) {
87
+ this.mask?.setLayout(el);
88
+ this.actionManager.setSelectedEl(el);
89
+ this.actionManager.updateMoveable(el);
90
+ }
91
+ });
92
+ }
93
+ /**
94
+ * 往画布增加一个组件
95
+ * @param data 组件信息数据
96
+ */
97
+ async add(data) {
98
+ return await this.renderer?.add(data);
99
+ }
100
+ /**
101
+ * 从画布删除一个组件
102
+ * @param data 组件信息数据
103
+ */
104
+ async remove(data) {
105
+ return await this.renderer?.remove(data);
106
+ }
107
+ setZoom(zoom = 1) {
108
+ this.renderer?.setZoom(zoom);
109
+ }
110
+ /**
111
+ * 挂载Dom节点
112
+ * @param el 将stage挂载到该Dom节点上
113
+ */
114
+ async mount(el) {
115
+ this.container = el;
116
+ const { mask, renderer } = this;
117
+ await renderer?.mount(el);
118
+ mask?.mount(el);
119
+ this.emit("mounted");
120
+ }
121
+ /**
122
+ * 清空所有参考线
123
+ */
124
+ clearGuides() {
125
+ this.mask?.clearGuides();
126
+ this.actionManager?.clearGuides();
127
+ }
128
+ /**
129
+ * @deprecated 废弃接口,建议用delayedMarkContainer代替
130
+ */
131
+ getAddContainerHighlightClassNameTimeout(event, excludeElList = []) {
132
+ return this.delayedMarkContainer(event, excludeElList);
133
+ }
134
+ /**
135
+ * 鼠标拖拽着元素,在容器上方悬停,延迟一段时间后,对容器进行标记,如果悬停时间够长将标记成功,悬停时间短,调用方通过返回的timeoutId取消标记
136
+ * 标记的作用:1、高亮容器,给用户一个加入容器的交互感知;2、释放鼠标后,通过标记的标志找到要加入的容器
137
+ * @param event 鼠标事件
138
+ * @param excludeElList 计算鼠标所在容器时要排除的元素列表
139
+ * @returns timeoutId,调用方在鼠标移走时要取消该timeout,阻止标记
140
+ */
141
+ delayedMarkContainer(event, excludeElList = []) {
142
+ return this.actionManager?.delayedMarkContainer(event, excludeElList);
143
+ }
144
+ getMoveableOption(key) {
145
+ return this.actionManager?.getMoveableOption(key);
146
+ }
147
+ getDragStatus() {
148
+ return this.actionManager?.getDragStatus();
149
+ }
150
+ disableMultiSelect() {
151
+ this.actionManager?.disableMultiSelect();
152
+ }
153
+ enableMultiSelect() {
154
+ this.actionManager?.enableMultiSelect();
155
+ }
156
+ reloadIframe(url) {
157
+ this.renderer?.reloadIframe(url);
158
+ }
159
+ /**
160
+ * 销毁实例
161
+ */
162
+ destroy() {
163
+ const { mask, renderer, actionManager, pageResizeObserver } = this;
164
+ renderer?.destroy();
165
+ mask?.destroy();
166
+ actionManager?.destroy();
167
+ pageResizeObserver?.disconnect();
168
+ this.removeAllListeners();
169
+ this.container = void 0;
170
+ this.renderer = null;
171
+ this.mask = null;
172
+ this.actionManager = null;
173
+ this.pageResizeObserver = null;
174
+ }
175
+ on(eventName, listener) {
176
+ return super.on(eventName, listener);
177
+ }
178
+ emit(eventName, ...args) {
179
+ return super.emit(eventName, ...args);
180
+ }
181
+ /**
182
+ * 监听页面大小变化
183
+ */
184
+ observePageResize(page) {
185
+ if (this.pageResizeObserver) this.pageResizeObserver.disconnect();
186
+ if (typeof ResizeObserver !== "undefined") {
187
+ this.pageResizeObserver = new ResizeObserver((entries) => {
188
+ this.mask?.pageResize(entries);
189
+ this.actionManager?.updateMoveable();
190
+ });
191
+ this.pageResizeObserver.observe(page);
192
+ }
193
+ }
194
+ getActionManagerConfig(config) {
195
+ return {
196
+ containerHighlightClassName: config.containerHighlightClassName,
197
+ containerHighlightDuration: config.containerHighlightDuration,
198
+ containerHighlightType: config.containerHighlightType,
199
+ moveableOptions: config.moveableOptions,
200
+ container: this.mask.content,
201
+ disabledDragStart: config.disabledDragStart,
202
+ disabledMultiSelect: config.disabledMultiSelect,
203
+ canSelect: config.canSelect,
204
+ isContainer: config.isContainer,
205
+ updateDragEl: config.updateDragEl,
206
+ getRootContainer: () => this.container,
207
+ getRenderDocument: () => this.renderer.getDocument(),
208
+ getTargetElement: (id) => this.renderer.getTargetElement(id),
209
+ getElementsFromPoint: (point) => this.renderer.getElementsFromPoint(point)
210
+ };
211
+ }
212
+ initRenderEvent() {
213
+ this.renderer?.on("runtime-ready", (runtime) => {
214
+ this.emit("runtime-ready", runtime);
215
+ });
216
+ this.renderer?.on("page-el-update", (el) => {
217
+ this.mask?.observe(el);
218
+ this.observePageResize(el);
219
+ this.emit("page-el-update", el);
220
+ });
221
+ }
222
+ initMaskEvent() {
223
+ this.mask?.on("change-guides", (data) => {
224
+ this.actionManager?.setGuidelines(data.type, data.guides);
225
+ this.emit("change-guides", data);
226
+ });
227
+ }
228
+ /**
229
+ * 初始化操作相关事件监听
230
+ */
231
+ initActionEvent() {
232
+ this.initActionManagerEvent();
233
+ this.initDrEvent();
234
+ this.initMulDrEvent();
235
+ this.initHighlightEvent();
236
+ this.initMouseEvent();
237
+ }
238
+ /**
239
+ * 初始化ActionManager类本身抛出来的事件监听
240
+ */
241
+ initActionManagerEvent() {
242
+ this.actionManager?.on("before-select", (el, event) => {
243
+ const id = getIdFromEl()(el);
244
+ id && this.select(id, event);
245
+ }).on("select", (selectedEl, event) => {
246
+ this.emit("select", selectedEl, event);
247
+ }).on("before-multi-select", (els) => {
248
+ this.multiSelect(els.map((el) => getIdFromEl()(el)).filter((id) => Boolean(id)));
249
+ }).on("multi-select", (selectedElList, event) => {
250
+ this.emit("multi-select", selectedElList, event);
251
+ }).on("dblclick", (event) => {
252
+ this.emit("dblclick", event);
253
+ });
254
+ }
255
+ /**
256
+ * 初始化DragResize类通过ActionManager抛出来的事件监听
257
+ */
258
+ initDrEvent() {
259
+ this.actionManager?.on("update", (data) => {
260
+ this.emit("update", data);
261
+ }).on("sort", (data) => {
262
+ this.emit("sort", data);
263
+ }).on("select-parent", () => {
264
+ this.emit("select-parent");
265
+ }).on("rerender", () => {
266
+ this.emit("rerender");
267
+ }).on("remove", (data) => {
268
+ this.emit("remove", data);
269
+ });
270
+ }
271
+ /**
272
+ * 初始化MultiDragResize类通过ActionManager抛出来的事件监听
273
+ */
274
+ initMulDrEvent() {
275
+ this.actionManager?.on("change-to-select", (id, e) => {
276
+ this.select(id);
277
+ setTimeout(() => {
278
+ const el = this.renderer?.getTargetElement(id);
279
+ el && this.emit("select", el, e);
280
+ });
281
+ }).on("multi-update", (data) => {
282
+ this.emit("update", data);
283
+ });
284
+ }
285
+ /**
286
+ * 初始化Highlight类通过ActionManager抛出来的事件监听
287
+ */
288
+ initHighlightEvent() {
289
+ this.actionManager?.on("highlight", (highlightEl) => {
290
+ this.emit("highlight", highlightEl);
291
+ });
292
+ }
293
+ /**
294
+ * 初始化Highlight类通过ActionManager抛出来的事件监听
295
+ */
296
+ initMouseEvent() {
297
+ this.actionManager?.on("mousemove", (event) => {
298
+ this.emit("mousemove", event);
299
+ }).on("mouseleave", (event) => {
300
+ this.emit("mouseleave", event);
301
+ }).on("drag-start", (e) => {
302
+ this.emit("drag-start", e);
303
+ });
304
+ }
305
+ };
306
+ //#endregion
307
+ export { StageCore as default };
@@ -0,0 +1,233 @@
1
+ import { Mode, StageDragStatus } from "./const.js";
2
+ import { down, getMode, up } 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/StageDragResize.ts
7
+ /**
8
+ * 管理单选操作,响应选中操作,初始化moveableOption参数并初始化moveable,处理moveable回调事件对组件进行更新
9
+ * @extends MoveableOptionsManager
10
+ */
11
+ var StageDragResize = class extends MoveableOptionsManager {
12
+ /** 目标节点 */
13
+ target = null;
14
+ /** Moveable拖拽类实例 */
15
+ moveable;
16
+ /** 拖动状态 */
17
+ dragStatus = StageDragStatus.END;
18
+ dragResizeHelper;
19
+ disabledDragStart;
20
+ getRenderDocument;
21
+ markContainerEnd;
22
+ delayedMarkContainer;
23
+ constructor(config) {
24
+ super(config);
25
+ this.getRenderDocument = config.getRenderDocument;
26
+ this.markContainerEnd = config.markContainerEnd;
27
+ this.delayedMarkContainer = config.delayedMarkContainer;
28
+ this.disabledDragStart = config.disabledDragStart;
29
+ this.dragResizeHelper = config.dragResizeHelper;
30
+ this.on("update-moveable", () => {
31
+ if (this.moveable) this.updateMoveable();
32
+ });
33
+ }
34
+ getTarget() {
35
+ return this.target;
36
+ }
37
+ /**
38
+ * 将选中框渲染并覆盖到选中的组件Dom节点上方
39
+ * 当选中的节点不是absolute时,会创建一个新的节点出来作为拖拽目标
40
+ * @param el 选中组件的Dom节点元素
41
+ * @param event 鼠标事件
42
+ */
43
+ select(el, event) {
44
+ if (!el) {
45
+ this.moveable?.destroy();
46
+ this.moveable = void 0;
47
+ return;
48
+ }
49
+ if (!this.moveable || el !== this.target) this.initMoveable(el);
50
+ else this.updateMoveable(el);
51
+ if (event && !this.disabledDragStart) this.moveable?.dragStart(event);
52
+ }
53
+ /**
54
+ * 初始化选中框并渲染出来
55
+ */
56
+ updateMoveable(el = this.target) {
57
+ if (!this.moveable) return;
58
+ if (!el) throw new Error("未选中任何节点");
59
+ const options = this.init(el);
60
+ Object.entries(options).forEach(([key, value]) => {
61
+ this.moveable[key] = value;
62
+ });
63
+ this.moveable.updateRect();
64
+ }
65
+ clearSelectStatus() {
66
+ if (!this.moveable) return;
67
+ this.dragResizeHelper.destroyShadowEl();
68
+ this.moveable.target = null;
69
+ this.moveable.updateRect();
70
+ }
71
+ getDragStatus() {
72
+ return this.dragStatus;
73
+ }
74
+ /**
75
+ * 销毁实例
76
+ */
77
+ destroy() {
78
+ this.target = null;
79
+ this.moveable?.destroy();
80
+ this.dragResizeHelper.destroy();
81
+ this.dragStatus = StageDragStatus.END;
82
+ this.removeAllListeners();
83
+ }
84
+ on(eventName, listener) {
85
+ return super.on(eventName, listener);
86
+ }
87
+ emit(eventName, ...args) {
88
+ return super.emit(eventName, ...args);
89
+ }
90
+ init(el) {
91
+ if (/(auto|scroll)/.test(el.style.overflow)) el.style.overflow = "hidden";
92
+ this.target = el;
93
+ this.mode = getMode(el);
94
+ this.dragResizeHelper.updateShadowEl(el);
95
+ this.dragResizeHelper.setMode(this.mode);
96
+ this.setElementGuidelines([this.target]);
97
+ return this.getOptions(false, { target: this.dragResizeHelper.getShadowEl() });
98
+ }
99
+ initMoveable(el) {
100
+ const options = this.init(el);
101
+ this.dragResizeHelper.clear();
102
+ this.moveable?.destroy();
103
+ this.moveable = new Moveable(this.container, { ...options });
104
+ this.bindResizeEvent();
105
+ this.bindDragEvent();
106
+ this.bindRotateEvent();
107
+ this.bindScaleEvent();
108
+ }
109
+ bindResizeEvent() {
110
+ if (!this.moveable) throw new Error("moveable 未初始化");
111
+ this.moveable.on("resizeStart", (e) => {
112
+ if (!this.target) return;
113
+ this.dragStatus = StageDragStatus.START;
114
+ this.dragResizeHelper.onResizeStart(e);
115
+ }).on("resize", (e) => {
116
+ if (!this.moveable || !this.target || !this.dragResizeHelper.getShadowEl()) return;
117
+ this.dragStatus = StageDragStatus.ING;
118
+ this.dragResizeHelper.onResize(e);
119
+ }).on("resizeEnd", () => {
120
+ this.dragStatus = StageDragStatus.END;
121
+ this.update(true);
122
+ });
123
+ }
124
+ bindDragEvent() {
125
+ if (!this.moveable) throw new Error("moveable 未初始化");
126
+ let timeout;
127
+ this.moveable.on("dragStart", (e) => {
128
+ if (!this.target) throw new Error("未选中组件");
129
+ this.dragStatus = StageDragStatus.START;
130
+ this.dragResizeHelper.onDragStart(e);
131
+ this.emit("drag-start", e);
132
+ }).on("drag", (e) => {
133
+ if (!this.target || !this.dragResizeHelper.getShadowEl()) return;
134
+ if (timeout) {
135
+ globalThis.clearTimeout(timeout);
136
+ timeout = void 0;
137
+ }
138
+ timeout = this.delayedMarkContainer(e.inputEvent, [this.target]);
139
+ this.dragStatus = StageDragStatus.ING;
140
+ this.dragResizeHelper.onDrag(e);
141
+ }).on("dragEnd", () => {
142
+ if (timeout) {
143
+ globalThis.clearTimeout(timeout);
144
+ timeout = void 0;
145
+ }
146
+ const parentEl = this.markContainerEnd();
147
+ if (this.dragStatus === StageDragStatus.ING) if (parentEl) this.update(false, parentEl);
148
+ else switch (this.mode) {
149
+ case Mode.SORTABLE:
150
+ this.sort();
151
+ break;
152
+ default: this.update();
153
+ }
154
+ this.dragStatus = StageDragStatus.END;
155
+ this.dragResizeHelper.destroyGhostEl();
156
+ });
157
+ }
158
+ bindRotateEvent() {
159
+ if (!this.moveable) throw new Error("moveable 未初始化");
160
+ this.moveable.on("rotateStart", (e) => {
161
+ this.dragStatus = StageDragStatus.START;
162
+ this.dragResizeHelper.onRotateStart(e);
163
+ }).on("rotate", (e) => {
164
+ if (!this.target || !this.dragResizeHelper.getShadowEl()) return;
165
+ this.dragStatus = StageDragStatus.ING;
166
+ this.dragResizeHelper.onRotate(e);
167
+ }).on("rotateEnd", (e) => {
168
+ this.dragStatus = StageDragStatus.END;
169
+ const frame = this.dragResizeHelper?.getFrame(e.target);
170
+ if (this.target && frame) this.emit("update", {
171
+ data: [{
172
+ el: this.target,
173
+ style: { transform: frame.get("transform") }
174
+ }],
175
+ parentEl: null
176
+ });
177
+ });
178
+ }
179
+ bindScaleEvent() {
180
+ if (!this.moveable) throw new Error("moveable 未初始化");
181
+ this.moveable.on("scaleStart", (e) => {
182
+ this.dragStatus = StageDragStatus.START;
183
+ this.dragResizeHelper.onScaleStart(e);
184
+ }).on("scale", (e) => {
185
+ if (!this.target || !this.dragResizeHelper.getShadowEl()) return;
186
+ this.dragStatus = StageDragStatus.ING;
187
+ this.dragResizeHelper.onScale(e);
188
+ }).on("scaleEnd", (e) => {
189
+ this.dragStatus = StageDragStatus.END;
190
+ const frame = this.dragResizeHelper.getFrame(e.target);
191
+ if (this.target && frame) this.emit("update", {
192
+ data: [{
193
+ el: this.target,
194
+ style: { transform: frame.get("transform") }
195
+ }],
196
+ parentEl: null
197
+ });
198
+ });
199
+ }
200
+ sort() {
201
+ if (!this.target || !this.dragResizeHelper.getGhostEl()) throw new Error("未知错误");
202
+ const { top } = this.dragResizeHelper.getGhostEl().getBoundingClientRect();
203
+ const { top: oriTop } = this.target.getBoundingClientRect();
204
+ const deltaTop = top - oriTop;
205
+ if (Math.abs(deltaTop) >= this.target.clientHeight / 2) if (deltaTop > 0) this.emit("sort", down(deltaTop, this.target));
206
+ else this.emit("sort", up(deltaTop, this.target));
207
+ else {
208
+ const id = getIdFromEl()(this.target);
209
+ id && this.emit("sort", {
210
+ src: id,
211
+ dist: id
212
+ });
213
+ }
214
+ }
215
+ update(isResize = false, parentEl = null) {
216
+ if (!this.target) return;
217
+ const doc = this.getRenderDocument();
218
+ if (!doc) return;
219
+ const rect = this.dragResizeHelper.getUpdatedElRect(this.target, parentEl, doc);
220
+ this.emit("update", {
221
+ data: [{
222
+ el: this.target,
223
+ style: isResize ? rect : {
224
+ left: rect.left,
225
+ top: rect.top
226
+ }
227
+ }],
228
+ parentEl
229
+ });
230
+ }
231
+ };
232
+ //#endregion
233
+ export { StageDragResize as default };
@@ -0,0 +1,62 @@
1
+ import { HIGHLIGHT_EL_ID_PREFIX, ZIndex } from "./const.js";
2
+ import TargetShadow from "./TargetShadow.js";
3
+ import { EventEmitter } from "events";
4
+ import Moveable from "moveable";
5
+ //#region packages/stage/src/StageHighlight.ts
6
+ var StageHighlight = class extends EventEmitter {
7
+ container;
8
+ target;
9
+ moveable;
10
+ targetShadow;
11
+ getRootContainer;
12
+ constructor(config) {
13
+ super();
14
+ this.container = config.container;
15
+ this.getRootContainer = config.getRootContainer;
16
+ this.targetShadow = new TargetShadow({
17
+ container: config.container,
18
+ updateDragEl: config.updateDragEl,
19
+ zIndex: ZIndex.HIGHLIGHT_EL,
20
+ idPrefix: HIGHLIGHT_EL_ID_PREFIX
21
+ });
22
+ }
23
+ /**
24
+ * 高亮鼠标悬停的组件
25
+ * @param el 选中组件的Dom节点元素
26
+ */
27
+ highlight(el) {
28
+ if (!el || el === this.target) return;
29
+ this.target = el;
30
+ this.targetShadow?.update(el);
31
+ if (this.moveable) {
32
+ this.moveable.zoom = 2;
33
+ this.moveable.updateRect();
34
+ } else this.moveable = new Moveable(this.container, {
35
+ target: this.targetShadow?.el,
36
+ origin: false,
37
+ rootContainer: this.getRootContainer(),
38
+ zoom: 2
39
+ });
40
+ }
41
+ /**
42
+ * 清空高亮
43
+ */
44
+ clearHighlight() {
45
+ if (!this.moveable || !this.target) return;
46
+ this.moveable.zoom = 0;
47
+ this.moveable.updateRect();
48
+ this.target = void 0;
49
+ }
50
+ /**
51
+ * 销毁实例
52
+ */
53
+ destroy() {
54
+ this.target = void 0;
55
+ this.moveable?.destroy();
56
+ this.targetShadow?.destroy();
57
+ this.moveable = void 0;
58
+ this.targetShadow = void 0;
59
+ }
60
+ };
61
+ //#endregion
62
+ export { StageHighlight as default };