@tmagic/stage 1.0.0-rc.1 → 1.0.0-rc.12

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/src/StageMask.ts CHANGED
@@ -83,6 +83,7 @@ export default class StageMask extends Rule {
83
83
  public wrapperWidth = 0;
84
84
  public maxScrollTop = 0;
85
85
  public maxScrollLeft = 0;
86
+ public intersectionObserver: IntersectionObserver | null = null;
86
87
 
87
88
  private mode: Mode = Mode.ABSOLUTE;
88
89
  private pageResizeObserver: ResizeObserver | null = null;
@@ -132,6 +133,27 @@ export default class StageMask extends Rule {
132
133
  this.page = page;
133
134
  this.pageScrollParent = getScrollParent(page) || this.core.renderer.contentWindow?.document.documentElement || null;
134
135
  this.pageResizeObserver?.disconnect();
136
+ this.wrapperResizeObserver?.disconnect();
137
+ this.intersectionObserver?.disconnect();
138
+
139
+ if (typeof IntersectionObserver !== 'undefined') {
140
+ this.intersectionObserver = new IntersectionObserver(
141
+ (entries) => {
142
+ entries.forEach((entry) => {
143
+ const { target, intersectionRatio } = entry;
144
+ if (intersectionRatio <= 0) {
145
+ this.scrollIntoView(target);
146
+ }
147
+ this.intersectionObserver?.unobserve(target);
148
+ });
149
+ },
150
+ {
151
+ root: this.pageScrollParent,
152
+ rootMargin: '0px',
153
+ threshold: 1.0,
154
+ },
155
+ );
156
+ }
135
157
 
136
158
  if (typeof ResizeObserver !== 'undefined') {
137
159
  this.pageResizeObserver = new ResizeObserver((entries) => {
@@ -140,9 +162,10 @@ export default class StageMask extends Rule {
140
162
  this.setHeight(clientHeight);
141
163
  this.setWidth(clientWidth);
142
164
 
143
- this.fixScrollValue();
144
165
  this.scroll();
145
- this.core.dr.updateMoveable();
166
+ if (this.core.dr.moveable) {
167
+ this.core.dr.updateMoveable();
168
+ }
146
169
  });
147
170
 
148
171
  this.pageResizeObserver.observe(page);
@@ -173,6 +196,14 @@ export default class StageMask extends Rule {
173
196
  this.setMode(isFixedParent(el) ? Mode.FIXED : Mode.ABSOLUTE);
174
197
  }
175
198
 
199
+ public scrollIntoView(el: Element): void {
200
+ el.scrollIntoView();
201
+ if (!this.pageScrollParent) return;
202
+ this.scrollLeft = this.pageScrollParent.scrollLeft;
203
+ this.scrollTop = this.pageScrollParent.scrollTop;
204
+ this.scroll();
205
+ }
206
+
176
207
  /**
177
208
  * 销毁实例
178
209
  */
@@ -188,6 +219,8 @@ export default class StageMask extends Rule {
188
219
  }
189
220
 
190
221
  private scroll() {
222
+ this.fixScrollValue();
223
+
191
224
  let { scrollLeft, scrollTop } = this;
192
225
 
193
226
  if (this.pageScrollParent) {
@@ -234,14 +267,14 @@ export default class StageMask extends Rule {
234
267
  * 计算并设置最大滚动宽度
235
268
  */
236
269
  private setMaxScrollLeft(): void {
237
- this.maxScrollLeft = this.width - this.wrapperWidth;
270
+ this.maxScrollLeft = Math.max(this.width - this.wrapperWidth, 0);
238
271
  }
239
272
 
240
273
  /**
241
274
  * 计算并设置最大滚动高度
242
275
  */
243
276
  private setMaxScrollTop(): void {
244
- this.maxScrollTop = this.height - this.wrapperHeight;
277
+ this.maxScrollTop = Math.max(this.height - this.wrapperHeight, 0);
245
278
  }
246
279
 
247
280
  /**
@@ -303,8 +336,6 @@ export default class StageMask extends Rule {
303
336
  this.scrollLeft = this.scrollLeft + deltaX;
304
337
  }
305
338
 
306
- this.fixScrollValue();
307
-
308
339
  this.scroll();
309
340
 
310
341
  this.emit('scroll', event);
@@ -18,7 +18,6 @@
18
18
 
19
19
  import { EventEmitter } from 'events';
20
20
 
21
- import { SELECTED_CLASS, ZIndex } from './const';
22
21
  import StageCore from './StageCore';
23
22
  import type { Runtime, RuntimeWindow, StageRenderConfig } from './types';
24
23
  import { getHost, isSameDomain } from './util';
@@ -82,10 +81,6 @@ export default class StageRender extends EventEmitter {
82
81
  }
83
82
 
84
83
  el.appendChild<HTMLIFrameElement>(this.iframe);
85
-
86
- this.contentWindow = this.iframe?.contentWindow as RuntimeWindow;
87
-
88
- this.contentWindow.magic = this.getMagicApi();
89
84
  } else {
90
85
  throw Error('mount 失败');
91
86
  }
@@ -114,7 +109,9 @@ export default class StageRender extends EventEmitter {
114
109
  }
115
110
 
116
111
  private loadHandler = async () => {
117
- this.emit('onload');
112
+ this.contentWindow = this.iframe?.contentWindow as RuntimeWindow;
113
+
114
+ this.contentWindow.magic = this.getMagicApi();
118
115
 
119
116
  if (this.render) {
120
117
  const el = await this.render(this.core);
@@ -123,15 +120,13 @@ export default class StageRender extends EventEmitter {
123
120
  }
124
121
  }
125
122
 
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
- }
123
+ this.emit('onload');
124
+
125
+ this.contentWindow.postMessage(
126
+ {
127
+ tmagicRuntimeReady: true,
128
+ },
129
+ '*',
130
+ );
136
131
  };
137
132
  }
@@ -20,6 +20,7 @@
20
20
  import { EventEmitter } from 'events';
21
21
 
22
22
  import { Mode } from './const';
23
+ import StageCore from './StageCore';
23
24
  import StageDragResize from './StageDragResize';
24
25
  import StageMask from './StageMask';
25
26
  import type { Offset, TargetCalibrateConfig } from './types';
@@ -32,7 +33,8 @@ export default class TargetCalibrate extends EventEmitter {
32
33
  public parent: HTMLElement;
33
34
  public mask: StageMask;
34
35
  public dr: StageDragResize;
35
- public operationEl: HTMLElement;
36
+ public core: StageCore;
37
+ public operationEl: HTMLDivElement;
36
38
 
37
39
  constructor(config: TargetCalibrateConfig) {
38
40
  super();
@@ -40,23 +42,30 @@ export default class TargetCalibrate extends EventEmitter {
40
42
  this.parent = config.parent;
41
43
  this.mask = config.mask;
42
44
  this.dr = config.dr;
45
+ this.core = config.core;
43
46
 
44
47
  this.operationEl = globalThis.document.createElement('div');
45
48
  this.parent.append(this.operationEl);
46
49
  }
47
50
 
48
51
  public update(el: HTMLElement, prefix: String): HTMLElement {
49
- const { width, height } = el.getBoundingClientRect();
50
52
  const { left, top } = this.getOffset(el);
53
+ const { transform } = getComputedStyle(el);
51
54
  this.operationEl.style.cssText = `
52
55
  position: absolute;
56
+ transform: ${transform};
53
57
  left: ${left}px;
54
58
  top: ${top}px;
55
- width: ${width}px;
56
- height: ${height}px;
59
+ width: ${el.clientWidth}px;
60
+ height: ${el.clientHeight}px;
57
61
  `;
58
62
 
59
63
  this.operationEl.id = `${prefix}${el.id}`;
64
+
65
+ if (typeof this.core.config.updateDragEl === 'function') {
66
+ this.core.config.updateDragEl(this.operationEl, el);
67
+ }
68
+
60
69
  return this.operationEl;
61
70
  }
62
71
 
@@ -65,35 +74,10 @@ export default class TargetCalibrate extends EventEmitter {
65
74
  }
66
75
 
67
76
  private getOffset(el: HTMLElement): Offset {
68
- const { transform } = getComputedStyle(el);
69
77
  const { offsetParent } = el;
70
78
 
71
- let left = el.offsetLeft;
72
- let top = el.offsetTop;
73
-
74
- if (transform.indexOf('matrix') > -1) {
75
- let a = 1;
76
- let b = 1;
77
- let c = 1;
78
- let d = 1;
79
- let e = 0;
80
- let f = 0;
81
- transform.replace(
82
- /matrix\((.+), (.+), (.+), (.+), (.+), (.+)\)/,
83
- ($0: string, $1: string, $2: string, $3: string, $4: string, $5: string, $6: string): string => {
84
- a = +$1;
85
- b = +$2;
86
- c = +$3;
87
- d = +$4;
88
- e = +$5;
89
- f = +$6;
90
- return transform;
91
- },
92
- );
93
-
94
- left = a * left + c * top + e;
95
- top = b * left + d * top + f;
96
- }
79
+ const left = el.offsetLeft;
80
+ const top = el.offsetTop;
97
81
 
98
82
  if (offsetParent) {
99
83
  const parentOffset = this.getOffset(offsetParent as HTMLElement);
package/src/const.ts CHANGED
@@ -16,36 +16,58 @@
16
16
  * limitations under the License.
17
17
  */
18
18
 
19
- // 流式布局下拖动时需要clone一个镜像节点,镜像节点的id前缀
19
+ /** 流式布局下拖动时需要clone一个镜像节点,镜像节点的id前缀 */
20
20
  export const GHOST_EL_ID_PREFIX = 'ghost_el_';
21
21
 
22
+ /** 拖动的时候需要在蒙层中创建一个占位节点,该节点的id前缀 */
22
23
  export const DRAG_EL_ID_PREFIX = 'drag_el_';
23
24
 
25
+ /** 高亮时需要在蒙层中创建一个占位节点,该节点的id前缀 */
24
26
  export const HIGHLIGHT_EL_ID_PREFIX = 'highlight_el_';
25
27
 
26
- // 默认放到缩小倍数
28
+ /** 默认放到缩小倍数 */
27
29
  export const DEFAULT_ZOOM = 1;
28
30
 
31
+ /** 参考线类型 */
29
32
  export enum GuidesType {
33
+ /** 水平 */
30
34
  HORIZONTAL = 'horizontal',
35
+ /** 垂直 */
31
36
  VERTICAL = 'vertical',
32
37
  }
33
38
 
39
+ /** css z-index */
34
40
  export enum ZIndex {
41
+ /** 蒙层,用于监听用户操作,需要置于顶层 */
35
42
  MASK = '99999',
43
+ /** 选中的节点 */
36
44
  SELECTED_EL = '666',
45
+ GHOST_EL = '700',
46
+ DRAG_EL = '9',
37
47
  }
38
48
 
49
+ /** 鼠标按键 */
39
50
  export enum MouseButton {
51
+ /** 左键 */
40
52
  LEFT = 0,
53
+ /** z中健 */
41
54
  MIDDLE = 1,
55
+ /** 右键 */
42
56
  RIGHT = 2,
43
57
  }
44
58
 
59
+ /** 布局方式 */
45
60
  export enum Mode {
61
+ /** 绝对定位布局 */
46
62
  ABSOLUTE = 'absolute',
63
+ /** 固定定位布局 */
47
64
  FIXED = 'fixed',
65
+ /** 流式布局 */
48
66
  SORTABLE = 'sortable',
49
67
  }
50
68
 
69
+ /** 选中节点的class name */
51
70
  export const SELECTED_CLASS = 'tmagic-stage-selected-area';
71
+
72
+ export const H_GUIDE_LINE_STORAGE_KEY = '$MagicStageHorizontalGuidelinesData';
73
+ export const V_GUIDE_LINE_STORAGE_KEY = '$MagicStageVerticalGuidelinesData';
package/src/types.ts CHANGED
@@ -16,8 +16,9 @@
16
16
  * limitations under the License.
17
17
  */
18
18
 
19
- import { MoveableOptions } from 'react-moveable/declaration/types';
19
+ import { MoveableOptions } from 'moveable';
20
20
 
21
+ import Core from '@tmagic/core';
21
22
  import { Id, MApp, MNode } from '@tmagic/schema';
22
23
 
23
24
  import { GuidesType } from './const';
@@ -37,6 +38,8 @@ export type StageCoreConfig = {
37
38
  /** runtime 的HTML地址,可以是一个HTTP地址,如果和编辑器不同域,需要设置跨域,也可以是一个相对或绝对路径 */
38
39
  runtimeUrl?: string;
39
40
  render?: (renderer: StageCore) => Promise<HTMLElement> | HTMLElement;
41
+ autoScrollIntoView?: boolean;
42
+ updateDragEl?: (el: HTMLDivElement, target: HTMLElement) => void;
40
43
  };
41
44
 
42
45
  export interface StageRenderConfig {
@@ -56,6 +59,7 @@ export type Rect = {
56
59
  width: number;
57
60
  height: number;
58
61
  } & Offset;
62
+
59
63
  export interface Offset {
60
64
  left: number;
61
65
  top: number;
@@ -70,10 +74,14 @@ export interface UpdateEventData {
70
74
  el: HTMLElement;
71
75
  ghostEl: HTMLElement;
72
76
  style: {
73
- width: number;
74
- height: number;
77
+ width?: number;
78
+ height?: number;
75
79
  left?: number;
76
80
  top?: number;
81
+ transform?: {
82
+ rotate?: string;
83
+ scale?: string;
84
+ };
77
85
  };
78
86
  }
79
87
 
@@ -94,9 +102,10 @@ export interface RemoveData {
94
102
  }
95
103
 
96
104
  export interface Runtime {
105
+ getApp?: () => Core;
97
106
  beforeSelect?: (el: HTMLElement) => Promise<boolean> | boolean;
98
107
  getSnapElements?: (el?: HTMLElement) => HTMLElement[];
99
- updateRootConfig: (config: MApp) => void;
108
+ updateRootConfig?: (config: MApp) => void;
100
109
  updatePageId?: (id: Id) => void;
101
110
  select?: (id: Id) => Promise<HTMLElement> | HTMLElement;
102
111
  add?: (data: UpdateData) => void;
@@ -125,4 +134,5 @@ export interface TargetCalibrateConfig {
125
134
  parent: HTMLElement;
126
135
  mask: StageMask;
127
136
  dr: StageDragResize;
137
+ core: StageCore;
128
138
  }
package/src/util.ts CHANGED
@@ -16,39 +16,24 @@
16
16
  * limitations under the License.
17
17
  */
18
18
 
19
- import { Mode, SELECTED_CLASS } from './const';
19
+ import { GuidesType, H_GUIDE_LINE_STORAGE_KEY, Mode, SELECTED_CLASS, V_GUIDE_LINE_STORAGE_KEY } from './const';
20
20
  import type { Offset } from './types';
21
21
 
22
+ const getParents = (el: Element, relative: Element) => {
23
+ let cur: Element | null = el.parentElement;
24
+ const parents: Element[] = [];
25
+ while (cur && cur !== relative) {
26
+ parents.push(cur);
27
+ cur = cur.parentElement;
28
+ }
29
+ return parents;
30
+ };
31
+
22
32
  export const getOffset = (el: HTMLElement): Offset => {
23
- const { transform } = getComputedStyle(el);
24
33
  const { offsetParent } = el;
25
34
 
26
- let left = el.offsetLeft;
27
- let top = el.offsetTop;
28
-
29
- if (transform.indexOf('matrix') > -1) {
30
- let a = 1;
31
- let b = 1;
32
- let c = 1;
33
- let d = 1;
34
- let e = 0;
35
- let f = 0;
36
- transform.replace(
37
- /matrix\((.+), (.+), (.+), (.+), (.+), (.+)\)/,
38
- ($0: string, $1: string, $2: string, $3: string, $4: string, $5: string, $6: string): string => {
39
- a = +$1;
40
- b = +$2;
41
- c = +$3;
42
- d = +$4;
43
- e = +$5;
44
- f = +$6;
45
- return transform;
46
- },
47
- );
48
-
49
- left = a * left + c * top + e;
50
- top = b * left + d * top + f;
51
- }
35
+ const left = el.offsetLeft;
36
+ const top = el.offsetTop;
52
37
 
53
38
  if (offsetParent) {
54
39
  const parentOffset = getOffset(offsetParent as HTMLElement);
@@ -133,15 +118,17 @@ export const getMode = (el: HTMLElement): Mode => {
133
118
 
134
119
  export const getScrollParent = (element: HTMLElement, includeHidden = false): HTMLElement | null => {
135
120
  let style = getComputedStyle(element);
121
+ const excludeStaticParent = style.position === 'absolute';
136
122
  const overflowRegex = includeHidden ? /(auto|scroll|hidden)/ : /(auto|scroll)/;
137
123
 
138
- if (isFixed(element)) return null;
124
+ if (style.position === 'fixed') return null;
125
+
139
126
  for (let parent = element; parent.parentElement; ) {
140
127
  parent = parent.parentElement;
141
128
  style = getComputedStyle(parent);
142
- if (isAbsolute(element) && isStatic(element)) {
143
- continue;
144
- }
129
+
130
+ if (excludeStaticParent && style.position === 'static') continue;
131
+
145
132
  if (overflowRegex.test(style.overflow + style.overflowY + style.overflowX)) return parent;
146
133
  }
147
134
 
@@ -161,10 +148,36 @@ export const removeSelectedClassName = (doc: Document) => {
161
148
  if (oldEl) {
162
149
  oldEl.classList.remove(SELECTED_CLASS);
163
150
  (oldEl.parentNode as HTMLDivElement)?.classList.remove(`${SELECTED_CLASS}-parent`);
151
+ doc.querySelectorAll(`.${SELECTED_CLASS}-parents`).forEach((item) => {
152
+ item.classList.remove(`${SELECTED_CLASS}-parents`);
153
+ });
164
154
  }
165
155
  };
166
156
 
167
- export const addSelectedClassName = (el: Element) => {
157
+ export const addSelectedClassName = (el: Element, doc: Document) => {
168
158
  el.classList.add(SELECTED_CLASS);
169
159
  (el.parentNode as Element)?.classList.add(`${SELECTED_CLASS}-parent`);
160
+ getParents(el, doc.body).forEach((item) => {
161
+ item.classList.add(`${SELECTED_CLASS}-parents`);
162
+ });
163
+ };
164
+
165
+ export const getGuideLineFromCache = (type: GuidesType): number[] => {
166
+ const key = {
167
+ [GuidesType.HORIZONTAL]: H_GUIDE_LINE_STORAGE_KEY,
168
+ [GuidesType.VERTICAL]: V_GUIDE_LINE_STORAGE_KEY,
169
+ }[type];
170
+
171
+ if (!key) return [];
172
+
173
+ const guideLineCacheData = globalThis.localStorage.getItem(key);
174
+ if (guideLineCacheData) {
175
+ try {
176
+ return JSON.parse(guideLineCacheData) || [];
177
+ } catch (e) {
178
+ console.error(e);
179
+ }
180
+ }
181
+
182
+ return [];
170
183
  };