@tmagic/stage 1.7.8 → 1.7.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.
@@ -151,6 +151,26 @@ var ActionManager = class extends EventEmitter$1 {
151
151
  return null;
152
152
  }
153
153
  /**
154
+ * 获取鼠标下方第二个可选中元素(跳过最上层),用于双击穿透选中下方元素
155
+ * @param event 鼠标事件
156
+ * @returns 鼠标下方第二个可选中元素,不存在时返回 null
157
+ */
158
+ async getNextElementFromPoint(event) {
159
+ const els = this.getElementsFromPoint(event);
160
+ let stopped = false;
161
+ const stop = () => stopped = true;
162
+ let skippedFirst = false;
163
+ for (const el of els) if (!getIdFromEl()(el)?.startsWith("ghost_el_") && await this.isElCanSelect(el, event, stop)) {
164
+ if (stopped) break;
165
+ if (!skippedFirst) {
166
+ skippedFirst = true;
167
+ continue;
168
+ }
169
+ return el;
170
+ }
171
+ return null;
172
+ }
173
+ /**
154
174
  * 判断一个元素能否在当前场景被选中
155
175
  * @param el 被判断的元素
156
176
  * @param event 鼠标事件
package/dist/es/Rule.js CHANGED
@@ -79,6 +79,7 @@ var Rule = class extends EventEmitter$1 {
79
79
  this.hGuides?.off("changeGuides", this.hGuidesChangeGuidesHandler);
80
80
  this.vGuides?.off("changeGuides", this.vGuidesChangeGuidesHandler);
81
81
  this.containerResizeObserver?.disconnect();
82
+ this.container = void 0;
82
83
  this.removeAllListeners();
83
84
  }
84
85
  destroyGuides() {
@@ -89,7 +90,6 @@ var Rule = class extends EventEmitter$1 {
89
90
  });
90
91
  this.hGuides = void 0;
91
92
  this.vGuides = void 0;
92
- this.container = void 0;
93
93
  }
94
94
  getGuidesStyle = (type) => ({
95
95
  position: "fixed",
@@ -43,7 +43,31 @@ var StageCore = class extends EventEmitter {
43
43
  * @param id 选中的id
44
44
  */
45
45
  async select(id, event) {
46
- const el = this.renderer?.getTargetElement(id) || null;
46
+ if (!this.renderer) return;
47
+ let el = this.renderer.getTargetElement(id) || null;
48
+ if (!el) el = await new Promise((resolve) => {
49
+ const observer = new MutationObserver(() => {
50
+ const target = this.renderer?.getTargetElement(id);
51
+ if (target) {
52
+ observer.disconnect();
53
+ clearTimeout(timer);
54
+ resolve(target);
55
+ }
56
+ });
57
+ const body = this.renderer?.getDocument()?.body;
58
+ if (!body) {
59
+ resolve(null);
60
+ return;
61
+ }
62
+ observer.observe(body, {
63
+ childList: true,
64
+ subtree: true
65
+ });
66
+ const timer = setTimeout(() => {
67
+ observer.disconnect();
68
+ resolve(this.renderer?.getTargetElement(id) || null);
69
+ }, 1e3);
70
+ });
47
71
  if (el === this.actionManager?.getSelectedEl()) return;
48
72
  await this.renderer?.select([id]);
49
73
  if (el) this.mask?.setLayout(el);
@@ -157,6 +181,13 @@ var StageCore = class extends EventEmitter {
157
181
  this.renderer?.reloadIframe(url);
158
182
  }
159
183
  /**
184
+ * 将指定id的dom元素生成为图片
185
+ */
186
+ async getElementImage(id, type = "png", options = {}) {
187
+ if (!this.renderer) throw new Error("Renderer is not initialized");
188
+ return this.renderer.getElementImage(id, type, options);
189
+ }
190
+ /**
160
191
  * 销毁实例
161
192
  */
162
193
  destroy() {
@@ -3,6 +3,7 @@ import { addSelectedClassName, removeSelectedClassName } from "./util.js";
3
3
  import style_default from "./style.js";
4
4
  import { EventEmitter } from "events";
5
5
  import { getElById, getHost, guid, injectStyle, isSameDomain } from "@tmagic/core";
6
+ import { snapdom } from "@zumer/snapdom";
6
7
  //#region packages/stage/src/StageRender.ts
7
8
  var StageRender = class extends EventEmitter {
8
9
  /** 组件的js、css执行的环境,直接渲染为当前window,iframe渲染则为iframe.contentWindow */
@@ -103,6 +104,21 @@ var StageRender = class extends EventEmitter {
103
104
  getTargetElement(id) {
104
105
  return getElById()(this.getDocument(), id);
105
106
  }
107
+ /**
108
+ * 将指定id的dom元素生成为图片
109
+ * @param id 目标元素的id
110
+ * @param options 图片生成选项
111
+ * @returns data URL 格式的图片数据
112
+ */
113
+ async getElementImage(id, type = "png", options = {}) {
114
+ const el = this.getTargetElement(id);
115
+ if (!el) throw new Error(`Element with id "${id}" not found`);
116
+ el.scrollIntoView();
117
+ const toFunc = `to${type.charAt(0).toUpperCase() + type.slice(1)}`;
118
+ const result = await snapdom(el, options);
119
+ if (toFunc in result) return result[toFunc]();
120
+ throw new Error(`Invalid type: ${type}`);
121
+ }
106
122
  postTmagicRuntimeReady() {
107
123
  this.contentWindow = this.iframe?.contentWindow;
108
124
  this.contentWindow.magic = this.getMagicApi();
@@ -1,14 +1,15 @@
1
1
  (function(global, factory) {
2
- typeof exports === "object" && typeof module !== "undefined" ? factory(exports, require("events"), require("@tmagic/core"), require("keycon"), require("moveable-helper"), require("moveable"), require("@scena/guides")) : typeof define === "function" && define.amd ? define([
2
+ typeof exports === "object" && typeof module !== "undefined" ? factory(exports, require("events"), require("@tmagic/core"), require("keycon"), require("moveable-helper"), require("moveable"), require("@scena/guides"), require("@zumer/snapdom")) : typeof define === "function" && define.amd ? define([
3
3
  "exports",
4
4
  "events",
5
5
  "@tmagic/core",
6
6
  "keycon",
7
7
  "moveable-helper",
8
8
  "moveable",
9
- "@scena/guides"
10
- ], factory) : (global = typeof globalThis !== "undefined" ? globalThis : global || self, factory(global.TMagicStage = {}, global.events, global._tmagic_core, global.keycon, global.moveable_helper, global.moveable, global._scena_guides));
11
- })(this, function(exports, events, _tmagic_core, keycon, moveable_helper, moveable, _scena_guides) {
9
+ "@scena/guides",
10
+ "@zumer/snapdom"
11
+ ], factory) : (global = typeof globalThis !== "undefined" ? globalThis : global || self, factory(global.TMagicStage = {}, global.events, global._tmagic_core, global.keycon, global.moveable_helper, global.moveable, global._scena_guides, global._zumer_snapdom));
12
+ })(this, function(exports, events, _tmagic_core, keycon, moveable_helper, moveable, _scena_guides, _zumer_snapdom) {
12
13
  Object.defineProperties(exports, {
13
14
  __esModule: { value: true },
14
15
  [Symbol.toStringTag]: { value: "Module" }
@@ -3619,6 +3620,26 @@
3619
3620
  return null;
3620
3621
  }
3621
3622
  /**
3623
+ * 获取鼠标下方第二个可选中元素(跳过最上层),用于双击穿透选中下方元素
3624
+ * @param event 鼠标事件
3625
+ * @returns 鼠标下方第二个可选中元素,不存在时返回 null
3626
+ */
3627
+ async getNextElementFromPoint(event) {
3628
+ const els = this.getElementsFromPoint(event);
3629
+ let stopped = false;
3630
+ const stop = () => stopped = true;
3631
+ let skippedFirst = false;
3632
+ for (const el of els) if (!(0, _tmagic_core.getIdFromEl)()(el)?.startsWith("ghost_el_") && await this.isElCanSelect(el, event, stop)) {
3633
+ if (stopped) break;
3634
+ if (!skippedFirst) {
3635
+ skippedFirst = true;
3636
+ continue;
3637
+ }
3638
+ return el;
3639
+ }
3640
+ return null;
3641
+ }
3642
+ /**
3622
3643
  * 判断一个元素能否在当前场景被选中
3623
3644
  * @param el 被判断的元素
3624
3645
  * @param event 鼠标事件
@@ -4013,6 +4034,7 @@
4013
4034
  this.hGuides?.off("changeGuides", this.hGuidesChangeGuidesHandler);
4014
4035
  this.vGuides?.off("changeGuides", this.vGuidesChangeGuidesHandler);
4015
4036
  this.containerResizeObserver?.disconnect();
4037
+ this.container = void 0;
4016
4038
  this.removeAllListeners();
4017
4039
  }
4018
4040
  destroyGuides() {
@@ -4023,7 +4045,6 @@
4023
4045
  });
4024
4046
  this.hGuides = void 0;
4025
4047
  this.vGuides = void 0;
4026
- this.container = void 0;
4027
4048
  }
4028
4049
  getGuidesStyle = (type) => ({
4029
4050
  position: "fixed",
@@ -4424,6 +4445,21 @@
4424
4445
  getTargetElement(id) {
4425
4446
  return (0, _tmagic_core.getElById)()(this.getDocument(), id);
4426
4447
  }
4448
+ /**
4449
+ * 将指定id的dom元素生成为图片
4450
+ * @param id 目标元素的id
4451
+ * @param options 图片生成选项
4452
+ * @returns data URL 格式的图片数据
4453
+ */
4454
+ async getElementImage(id, type = "png", options = {}) {
4455
+ const el = this.getTargetElement(id);
4456
+ if (!el) throw new Error(`Element with id "${id}" not found`);
4457
+ el.scrollIntoView();
4458
+ const toFunc = `to${type.charAt(0).toUpperCase() + type.slice(1)}`;
4459
+ const result = await (0, _zumer_snapdom.snapdom)(el, options);
4460
+ if (toFunc in result) return result[toFunc]();
4461
+ throw new Error(`Invalid type: ${type}`);
4462
+ }
4427
4463
  postTmagicRuntimeReady() {
4428
4464
  this.contentWindow = this.iframe?.contentWindow;
4429
4465
  this.contentWindow.magic = this.getMagicApi();
@@ -4543,7 +4579,31 @@
4543
4579
  * @param id 选中的id
4544
4580
  */
4545
4581
  async select(id, event) {
4546
- const el = this.renderer?.getTargetElement(id) || null;
4582
+ if (!this.renderer) return;
4583
+ let el = this.renderer.getTargetElement(id) || null;
4584
+ if (!el) el = await new Promise((resolve) => {
4585
+ const observer = new MutationObserver(() => {
4586
+ const target = this.renderer?.getTargetElement(id);
4587
+ if (target) {
4588
+ observer.disconnect();
4589
+ clearTimeout(timer);
4590
+ resolve(target);
4591
+ }
4592
+ });
4593
+ const body = this.renderer?.getDocument()?.body;
4594
+ if (!body) {
4595
+ resolve(null);
4596
+ return;
4597
+ }
4598
+ observer.observe(body, {
4599
+ childList: true,
4600
+ subtree: true
4601
+ });
4602
+ const timer = setTimeout(() => {
4603
+ observer.disconnect();
4604
+ resolve(this.renderer?.getTargetElement(id) || null);
4605
+ }, 1e3);
4606
+ });
4547
4607
  if (el === this.actionManager?.getSelectedEl()) return;
4548
4608
  await this.renderer?.select([id]);
4549
4609
  if (el) this.mask?.setLayout(el);
@@ -4657,6 +4717,13 @@
4657
4717
  this.renderer?.reloadIframe(url);
4658
4718
  }
4659
4719
  /**
4720
+ * 将指定id的dom元素生成为图片
4721
+ */
4722
+ async getElementImage(id, type = "png", options = {}) {
4723
+ if (!this.renderer) throw new Error("Renderer is not initialized");
4724
+ return this.renderer.getElementImage(id, type, options);
4725
+ }
4726
+ /**
4660
4727
  * 销毁实例
4661
4728
  */
4662
4729
  destroy() {
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "1.7.8",
2
+ "version": "1.7.10",
3
3
  "name": "@tmagic/stage",
4
4
  "type": "module",
5
5
  "sideEffects": false,
@@ -31,6 +31,7 @@
31
31
  "dependencies": {
32
32
  "@scena/guides": "^0.29.2",
33
33
  "events": "^3.3.0",
34
+ "@zumer/snapdom": "^2.8.0",
34
35
  "keycon": "^1.4.0",
35
36
  "lodash-es": "^4.17.21",
36
37
  "moveable": "^0.53.0",
@@ -42,8 +43,8 @@
42
43
  "@types/lodash-es": "^4.17.4"
43
44
  },
44
45
  "peerDependencies": {
45
- "typescript": "^5.9.3",
46
- "@tmagic/core": "1.7.8"
46
+ "typescript": "^6.0.2",
47
+ "@tmagic/core": "1.7.10"
47
48
  },
48
49
  "peerDependenciesMeta": {
49
50
  "typescript": {
@@ -236,6 +236,30 @@ export default class ActionManager extends EventEmitter {
236
236
  return null;
237
237
  }
238
238
 
239
+ /**
240
+ * 获取鼠标下方第二个可选中元素(跳过最上层),用于双击穿透选中下方元素
241
+ * @param event 鼠标事件
242
+ * @returns 鼠标下方第二个可选中元素,不存在时返回 null
243
+ */
244
+ public async getNextElementFromPoint(event: MouseEvent): Promise<HTMLElement | null> {
245
+ const els = this.getElementsFromPoint(event as Point);
246
+
247
+ let stopped = false;
248
+ const stop = () => (stopped = true);
249
+ let skippedFirst = false;
250
+ for (const el of els) {
251
+ if (!getIdFromEl()(el)?.startsWith(GHOST_EL_ID_PREFIX) && (await this.isElCanSelect(el, event, stop))) {
252
+ if (stopped) break;
253
+ if (!skippedFirst) {
254
+ skippedFirst = true;
255
+ continue;
256
+ }
257
+ return el;
258
+ }
259
+ }
260
+ return null;
261
+ }
262
+
239
263
  /**
240
264
  * 判断一个元素能否在当前场景被选中
241
265
  * @param el 被判断的元素
package/src/Rule.ts CHANGED
@@ -124,6 +124,7 @@ export default class Rule extends EventEmitter {
124
124
  this.hGuides?.off('changeGuides', this.hGuidesChangeGuidesHandler);
125
125
  this.vGuides?.off('changeGuides', this.vGuidesChangeGuidesHandler);
126
126
  this.containerResizeObserver?.disconnect();
127
+ this.container = undefined;
127
128
  this.removeAllListeners();
128
129
  }
129
130
 
@@ -137,7 +138,6 @@ export default class Rule extends EventEmitter {
137
138
 
138
139
  this.hGuides = undefined;
139
140
  this.vGuides = undefined;
140
- this.container = undefined;
141
141
  }
142
142
 
143
143
  private getGuidesStyle = (type: GuidesType) => ({
package/src/StageCore.ts CHANGED
@@ -18,6 +18,7 @@
18
18
 
19
19
  import { EventEmitter } from 'events';
20
20
 
21
+ import { SnapdomOptions } from '@zumer/snapdom';
21
22
  import type { MoveableOptions, OnDragStart } from 'moveable';
22
23
 
23
24
  import type { Id } from '@tmagic/core';
@@ -88,7 +89,38 @@ export default class StageCore extends EventEmitter {
88
89
  * @param id 选中的id
89
90
  */
90
91
  public async select(id: Id, event?: MouseEvent): Promise<void> {
91
- const el = this.renderer?.getTargetElement(id) || null;
92
+ if (!this.renderer) {
93
+ return;
94
+ }
95
+
96
+ let el = this.renderer.getTargetElement(id) || null;
97
+
98
+ if (!el) {
99
+ el = await new Promise<HTMLElement | null>((resolve) => {
100
+ const observer = new MutationObserver(() => {
101
+ const target = this.renderer?.getTargetElement(id);
102
+ if (target) {
103
+ observer.disconnect();
104
+ clearTimeout(timer);
105
+ resolve(target);
106
+ }
107
+ });
108
+
109
+ const body = this.renderer?.getDocument()?.body;
110
+ if (!body) {
111
+ resolve(null);
112
+ return;
113
+ }
114
+
115
+ observer.observe(body, { childList: true, subtree: true });
116
+
117
+ const timer = setTimeout(() => {
118
+ observer.disconnect();
119
+ resolve(this.renderer?.getTargetElement(id) || null);
120
+ }, 1000);
121
+ });
122
+ }
123
+
92
124
  if (el === this.actionManager?.getSelectedEl()) return;
93
125
 
94
126
  await this.renderer?.select([id]);
@@ -241,6 +273,21 @@ export default class StageCore extends EventEmitter {
241
273
  this.renderer?.reloadIframe(url);
242
274
  }
243
275
 
276
+ /**
277
+ * 将指定id的dom元素生成为图片
278
+ */
279
+ public async getElementImage(
280
+ id: Id,
281
+ type: 'download' | 'raw' | 'svg' | 'canvas' | 'png' | 'jpeg' | 'webp' | 'blob' = 'png',
282
+ options: SnapdomOptions = {},
283
+ ) {
284
+ if (!this.renderer) {
285
+ throw new Error('Renderer is not initialized');
286
+ }
287
+
288
+ return this.renderer.getElementImage(id, type, options);
289
+ }
290
+
244
291
  /**
245
292
  * 销毁实例
246
293
  */
@@ -18,6 +18,8 @@
18
18
 
19
19
  import { EventEmitter } from 'events';
20
20
 
21
+ import { snapdom, SnapdomOptions } from '@zumer/snapdom';
22
+
21
23
  import type { Id } from '@tmagic/core';
22
24
  import { getElById, getHost, guid, injectStyle, isSameDomain } from '@tmagic/core';
23
25
 
@@ -162,6 +164,35 @@ export default class StageRender extends EventEmitter {
162
164
  return getElById()(this.getDocument(), id);
163
165
  }
164
166
 
167
+ /**
168
+ * 将指定id的dom元素生成为图片
169
+ * @param id 目标元素的id
170
+ * @param options 图片生成选项
171
+ * @returns data URL 格式的图片数据
172
+ */
173
+ public async getElementImage(
174
+ id: Id,
175
+ type: 'download' | 'raw' | 'svg' | 'canvas' | 'png' | 'jpeg' | 'webp' | 'blob' = 'png',
176
+ options: SnapdomOptions = {},
177
+ ) {
178
+ const el = this.getTargetElement(id);
179
+ if (!el) {
180
+ throw new Error(`Element with id "${id}" not found`);
181
+ }
182
+
183
+ el.scrollIntoView();
184
+
185
+ const toFunc = `to${type.charAt(0).toUpperCase() + type.slice(1)}`;
186
+
187
+ const result = await snapdom(el, options);
188
+
189
+ if (toFunc in result) {
190
+ return result[toFunc]();
191
+ }
192
+
193
+ throw new Error(`Invalid type: ${type}`);
194
+ }
195
+
165
196
  public postTmagicRuntimeReady() {
166
197
  this.contentWindow = this.iframe?.contentWindow as RuntimeWindow;
167
198
 
package/types/index.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import * as _tmagic_editor0 from "@tmagic/editor";
2
2
  import EventEmitter$1, { EventEmitter } from "events";
3
+ import { SnapdomOptions } from "@zumer/snapdom";
3
4
  import { MoveableManagerInterface, MoveableOptions, OnDrag, OnDragGroup, OnDragGroupStart, OnDragStart, OnResize, OnResizeGroup, OnResizeGroupStart, OnResizeStart, OnRotate, OnRotateStart, OnScale, OnScaleStart, Renderer } from "moveable";
4
5
  import Core, { Id, MApp, MContainer, MNode } from "@tmagic/core";
5
6
  import Guides, { GuidesOptions, GuidesOptions as GuidesOptions$1 } from "@scena/guides";
@@ -468,6 +469,12 @@ declare class ActionManager extends EventEmitter$1 {
468
469
  * @returns 鼠标下方第一个可选中元素
469
470
  */
470
471
  getElementFromPoint(event: MouseEvent): Promise<HTMLElement | null>;
472
+ /**
473
+ * 获取鼠标下方第二个可选中元素(跳过最上层),用于双击穿透选中下方元素
474
+ * @param event 鼠标事件
475
+ * @returns 鼠标下方第二个可选中元素,不存在时返回 null
476
+ */
477
+ getNextElementFromPoint(event: MouseEvent): Promise<HTMLElement | null>;
471
478
  /**
472
479
  * 判断一个元素能否在当前场景被选中
473
480
  * @param el 被判断的元素
@@ -711,6 +718,13 @@ declare class StageRender extends EventEmitter {
711
718
  */
712
719
  getElementsFromPoint(point: Point): HTMLElement[];
713
720
  getTargetElement(id: Id): HTMLElement | null;
721
+ /**
722
+ * 将指定id的dom元素生成为图片
723
+ * @param id 目标元素的id
724
+ * @param options 图片生成选项
725
+ * @returns data URL 格式的图片数据
726
+ */
727
+ getElementImage(id: Id, type?: 'download' | 'raw' | 'svg' | 'canvas' | 'png' | 'jpeg' | 'webp' | 'blob', options?: SnapdomOptions): Promise<any>;
714
728
  postTmagicRuntimeReady(): void;
715
729
  reloadIframe(url: string): void;
716
730
  destroyIframe(): void;
@@ -801,6 +815,10 @@ declare class StageCore extends EventEmitter {
801
815
  disableMultiSelect(): void;
802
816
  enableMultiSelect(): void;
803
817
  reloadIframe(url: string): void;
818
+ /**
819
+ * 将指定id的dom元素生成为图片
820
+ */
821
+ getElementImage(id: Id, type?: 'download' | 'raw' | 'svg' | 'canvas' | 'png' | 'jpeg' | 'webp' | 'blob', options?: SnapdomOptions): Promise<any>;
804
822
  /**
805
823
  * 销毁实例
806
824
  */