@tmagic/stage 1.0.0-beta.8 → 1.0.0-rc.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.
@@ -1,19 +1,46 @@
1
+ /** 流式布局下拖动时需要clone一个镜像节点,镜像节点的id前缀 */
1
2
  export declare const GHOST_EL_ID_PREFIX = "ghost_el_";
3
+ /** 拖动的时候需要在蒙层中创建一个占位节点,该节点的id前缀 */
4
+ export declare const DRAG_EL_ID_PREFIX = "drag_el_";
5
+ /** 高亮事需要在蒙层中创建一个占位节点,该节点的id前缀 */
6
+ export declare const HIGHLIGHT_EL_ID_PREFIX = "highlight_el_";
7
+ /** 默认放到缩小倍数 */
2
8
  export declare const DEFAULT_ZOOM = 1;
9
+ /** 参考线类型 */
3
10
  export declare enum GuidesType {
11
+ /** 水平 */
4
12
  HORIZONTAL = "horizontal",
13
+ /** 垂直 */
5
14
  VERTICAL = "vertical"
6
15
  }
16
+ /** css z-index */
7
17
  export declare enum ZIndex {
8
- MASK = "99999"
18
+ /** 蒙层,用于监听用户操作,需要置于顶层 */
19
+ MASK = "99999",
20
+ /** 选中的节点 */
21
+ SELECTED_EL = "666",
22
+ GHOST_EL = "700",
23
+ DRAG_EL = "9"
9
24
  }
25
+ /** 鼠标按键 */
10
26
  export declare enum MouseButton {
27
+ /** 左键 */
11
28
  LEFT = 0,
29
+ /** z中健 */
12
30
  MIDDLE = 1,
31
+ /** 右键 */
13
32
  RIGHT = 2
14
33
  }
34
+ /** 布局方式 */
15
35
  export declare enum Mode {
36
+ /** 绝对定位布局 */
16
37
  ABSOLUTE = "absolute",
38
+ /** 固定定位布局 */
17
39
  FIXED = "fixed",
40
+ /** 流式布局 */
18
41
  SORTABLE = "sortable"
19
42
  }
43
+ /** 选中节点的class name */
44
+ export declare const SELECTED_CLASS = "tmagic-stage-selected-area";
45
+ export declare const H_GUIDE_LINE_STORAGE_KEY = "$MagicStageHorizontalGuidelinesData";
46
+ export declare const V_GUIDE_LINE_STORAGE_KEY = "$MagicStageVerticalGuidelinesData";
@@ -1,8 +1,11 @@
1
- import { MoveableOptions } from 'react-moveable/declaration/types';
1
+ import { MoveableOptions } from 'moveable';
2
+ import Core from '@tmagic/core';
2
3
  import { Id, MApp, MNode } from '@tmagic/schema';
3
4
  import { GuidesType } from './const';
4
5
  import StageCore from './StageCore';
5
- export declare type CanSelect = (el: HTMLElement, stop: () => boolean) => boolean | Promise<boolean>;
6
+ import StageDragResize from './StageDragResize';
7
+ import StageMask from './StageMask';
8
+ export declare type CanSelect = (el: HTMLElement, event: MouseEvent, stop: () => boolean) => boolean | Promise<boolean>;
6
9
  export declare type StageCoreConfig = {
7
10
  /** 需要对齐的dom节点的CSS选择器字符串 */
8
11
  snapElementQuerySelector?: string;
@@ -13,6 +16,8 @@ export declare type StageCoreConfig = {
13
16
  /** runtime 的HTML地址,可以是一个HTTP地址,如果和编辑器不同域,需要设置跨域,也可以是一个相对或绝对路径 */
14
17
  runtimeUrl?: string;
15
18
  render?: (renderer: StageCore) => Promise<HTMLElement> | HTMLElement;
19
+ autoScrollIntoView?: boolean;
20
+ updateDragEl?: (el: HTMLDivElement, target: HTMLElement) => void;
16
21
  };
17
22
  export interface StageRenderConfig {
18
23
  core: StageCore;
@@ -40,10 +45,14 @@ export interface UpdateEventData {
40
45
  el: HTMLElement;
41
46
  ghostEl: HTMLElement;
42
47
  style: {
43
- width: number;
44
- height: number;
48
+ width?: number;
49
+ height?: number;
45
50
  left?: number;
46
51
  top?: number;
52
+ transform?: {
53
+ rotate?: string;
54
+ scale?: string;
55
+ };
47
56
  };
48
57
  }
49
58
  export interface SortEventData {
@@ -60,8 +69,9 @@ export interface RemoveData {
60
69
  root: MApp;
61
70
  }
62
71
  export interface Runtime {
72
+ getApp?: () => Core;
63
73
  beforeSelect?: (el: HTMLElement) => Promise<boolean> | boolean;
64
- getSnapElements?: (el: HTMLElement) => HTMLElement[];
74
+ getSnapElements?: (el?: HTMLElement) => HTMLElement[];
65
75
  updateRootConfig: (config: MApp) => void;
66
76
  updatePageId?: (id: Id) => void;
67
77
  select?: (id: Id) => Promise<HTMLElement> | HTMLElement;
@@ -78,3 +88,13 @@ export interface Magic {
78
88
  export interface RuntimeWindow extends Window {
79
89
  magic: Magic;
80
90
  }
91
+ export interface StageHighlightConfig {
92
+ core: StageCore;
93
+ container: HTMLElement;
94
+ }
95
+ export interface TargetCalibrateConfig {
96
+ parent: HTMLElement;
97
+ mask: StageMask;
98
+ dr: StageDragResize;
99
+ core: StageCore;
100
+ }
@@ -1,4 +1,4 @@
1
- import { Mode } from './const';
1
+ import { GuidesType, Mode } from './const';
2
2
  import type { Offset } from './types';
3
3
  export declare const getOffset: (el: HTMLElement) => Offset;
4
4
  export declare const getAbsolutePosition: (el: HTMLElement, { top, left }: Offset) => {
@@ -18,3 +18,6 @@ export declare const createDiv: ({ className, cssText }: {
18
18
  className: string;
19
19
  cssText: string;
20
20
  }) => HTMLDivElement;
21
+ export declare const removeSelectedClassName: (doc: Document) => void;
22
+ export declare const addSelectedClassName: (el: Element, doc: Document) => void;
23
+ export declare const getGuideLineFromCache: (type: GuidesType) => number[];
package/package.json CHANGED
@@ -1,7 +1,9 @@
1
1
  {
2
- "version": "1.0.0-beta.8",
2
+ "version": "1.0.0-rc.10",
3
3
  "name": "@tmagic/stage",
4
- "sideEffects": false,
4
+ "sideEffects": [
5
+ "dist/*"
6
+ ],
5
7
  "main": "dist/tmagic-stage.umd.js",
6
8
  "module": "dist/tmagic-stage.es.js",
7
9
  "types": "dist/types/src/index.d.ts",
@@ -9,8 +11,10 @@
9
11
  ".": {
10
12
  "import": "./dist/tmagic-stage.es.js",
11
13
  "require": "./dist/tmagic-stage.umd.js"
12
- }
14
+ },
15
+ "./*": "./*"
13
16
  },
17
+ "license": "Apache-2.0",
14
18
  "scripts": {
15
19
  "build": "vite build"
16
20
  },
@@ -23,11 +27,13 @@
23
27
  },
24
28
  "dependencies": {
25
29
  "@scena/guides": "^0.17.0",
26
- "@tmagic/schema": "^1.0.0-beta.8",
27
- "@tmagic/utils": "^1.0.0-beta.8",
30
+ "@tmagic/core": "1.0.0-rc.10",
31
+ "@tmagic/schema": "1.0.0-rc.10",
32
+ "@tmagic/utils": "1.0.0-rc.10",
28
33
  "events": "^3.3.0",
29
34
  "lodash-es": "^4.17.21",
30
- "moveable": "^0.28.0"
35
+ "moveable": "^0.29.4",
36
+ "moveable-helper": "^0.4.0"
31
37
  },
32
38
  "devDependencies": {
33
39
  "@types/events": "^3.0.0",
@@ -37,6 +43,5 @@
37
43
  "typescript": "^4.3.4",
38
44
  "vite": "^2.3.7",
39
45
  "vite-plugin-dts": "^0.9.6"
40
- },
41
- "gitHead": "ff8e32901775ecc145a82f906d6af6119652d61e"
46
+ }
42
47
  }
package/src/Rule.ts CHANGED
@@ -2,13 +2,14 @@ import EventEmitter from 'events';
2
2
 
3
3
  import Guides, { GuidesEvents } from '@scena/guides';
4
4
 
5
- import { GuidesType } from './const';
5
+ import { GuidesType, H_GUIDE_LINE_STORAGE_KEY, V_GUIDE_LINE_STORAGE_KEY } from './const';
6
+ import { getGuideLineFromCache } from './util';
6
7
 
7
8
  export default class Rule extends EventEmitter {
8
9
  public hGuides: Guides;
9
10
  public vGuides: Guides;
10
- public horizontalGuidelines: number[] = [];
11
- public verticalGuidelines: number[] = [];
11
+ public horizontalGuidelines: number[] = getGuideLineFromCache(GuidesType.HORIZONTAL);
12
+ public verticalGuidelines: number[] = getGuideLineFromCache(GuidesType.VERTICAL);
12
13
 
13
14
  private container: HTMLDivElement;
14
15
  private containerResizeObserver: ResizeObserver;
@@ -17,8 +18,8 @@ export default class Rule extends EventEmitter {
17
18
  super();
18
19
 
19
20
  this.container = container;
20
- this.hGuides = this.createGuides(GuidesType.HORIZONTAL);
21
- this.vGuides = this.createGuides(GuidesType.VERTICAL);
21
+ this.hGuides = this.createGuides(GuidesType.HORIZONTAL, this.horizontalGuidelines);
22
+ this.vGuides = this.createGuides(GuidesType.VERTICAL, this.verticalGuidelines);
22
23
 
23
24
  this.hGuides.on('changeGuides', this.hGuidesChangeGuidesHandler);
24
25
  this.vGuides.on('changeGuides', this.vGuidesChangeGuidesHandler);
@@ -68,6 +69,9 @@ export default class Rule extends EventEmitter {
68
69
  type: GuidesType.HORIZONTAL,
69
70
  guides: [],
70
71
  });
72
+
73
+ globalThis.localStorage.setItem(V_GUIDE_LINE_STORAGE_KEY, '[]');
74
+ globalThis.localStorage.setItem(H_GUIDE_LINE_STORAGE_KEY, '[]');
71
75
  }
72
76
 
73
77
  /**
@@ -138,6 +142,8 @@ export default class Rule extends EventEmitter {
138
142
  type: GuidesType.HORIZONTAL,
139
143
  guides: this.horizontalGuidelines,
140
144
  });
145
+
146
+ globalThis.localStorage.setItem(H_GUIDE_LINE_STORAGE_KEY, JSON.stringify(e.guides));
141
147
  };
142
148
 
143
149
  private vGuidesChangeGuidesHandler = (e: GuidesEvents['changeGuides']) => {
@@ -146,5 +152,7 @@ export default class Rule extends EventEmitter {
146
152
  type: GuidesType.VERTICAL,
147
153
  guides: this.verticalGuidelines,
148
154
  });
155
+
156
+ globalThis.localStorage.setItem(V_GUIDE_LINE_STORAGE_KEY, JSON.stringify(e.guides));
149
157
  };
150
158
  }
package/src/StageCore.ts CHANGED
@@ -22,6 +22,7 @@ import { Id } from '@tmagic/schema';
22
22
 
23
23
  import { DEFAULT_ZOOM, GHOST_EL_ID_PREFIX } from './const';
24
24
  import StageDragResize from './StageDragResize';
25
+ import StageHighlight from './StageHighlight';
25
26
  import StageMask from './StageMask';
26
27
  import StageRender from './StageRender';
27
28
  import {
@@ -34,15 +35,19 @@ import {
34
35
  UpdateData,
35
36
  UpdateEventData,
36
37
  } from './types';
38
+ import { addSelectedClassName, removeSelectedClassName } from './util';
37
39
 
38
40
  export default class StageCore extends EventEmitter {
39
41
  public selectedDom: Element | undefined;
42
+ public highlightedDom: Element | undefined;
40
43
 
41
44
  public renderer: StageRender;
42
45
  public mask: StageMask;
43
46
  public dr: StageDragResize;
47
+ public highlightLayer: StageHighlight;
44
48
  public config: StageCoreConfig;
45
49
  public zoom = DEFAULT_ZOOM;
50
+ public container?: HTMLDivElement;
46
51
  private canSelect: CanSelect;
47
52
 
48
53
  constructor(config: StageCoreConfig) {
@@ -56,9 +61,14 @@ export default class StageCore extends EventEmitter {
56
61
  this.renderer = new StageRender({ core: this });
57
62
  this.mask = new StageMask({ core: this });
58
63
  this.dr = new StageDragResize({ core: this, container: this.mask.content });
64
+ this.highlightLayer = new StageHighlight({ core: this, container: this.mask.wrapper });
59
65
 
60
- this.renderer.on('runtime-ready', (runtime: Runtime) => this.emit('runtime-ready', runtime));
61
- this.renderer.on('page-el-update', (el: HTMLElement) => this.mask?.observe(el));
66
+ this.renderer.on('runtime-ready', (runtime: Runtime) => {
67
+ this.emit('runtime-ready', runtime);
68
+ });
69
+ this.renderer.on('page-el-update', (el: HTMLElement) => {
70
+ this.mask?.observe(el);
71
+ });
62
72
 
63
73
  this.mask
64
74
  .on('beforeSelect', (event: MouseEvent) => {
@@ -70,6 +80,18 @@ export default class StageCore extends EventEmitter {
70
80
  .on('changeGuides', (data: GuidesEventData) => {
71
81
  this.dr.setGuidelines(data.type, data.guides);
72
82
  this.emit('changeGuides', data);
83
+ })
84
+ .on('highlight', async (event: MouseEvent) => {
85
+ await this.setElementFromPoint(event);
86
+ if (this.highlightedDom === this.selectedDom) {
87
+ this.highlightLayer.clearHighlight();
88
+ return;
89
+ }
90
+ this.highlightLayer.highlight(this.highlightedDom as HTMLElement);
91
+ this.emit('highlight', this.highlightedDom);
92
+ })
93
+ .on('clearHighlight', async () => {
94
+ this.highlightLayer.clearHighlight();
73
95
  });
74
96
 
75
97
  // 要先触发select,在触发update
@@ -102,8 +124,12 @@ export default class StageCore extends EventEmitter {
102
124
  let stopped = false;
103
125
  const stop = () => (stopped = true);
104
126
  for (const el of els) {
105
- if (!el.id.startsWith(GHOST_EL_ID_PREFIX) && (await this.canSelect(el, stop))) {
127
+ if (!el.id.startsWith(GHOST_EL_ID_PREFIX) && (await this.canSelect(el, event, stop))) {
106
128
  if (stopped) break;
129
+ if (event.type === 'mousemove') {
130
+ this.highlight(el);
131
+ break;
132
+ }
107
133
  this.select(el, event);
108
134
  break;
109
135
  }
@@ -115,58 +141,85 @@ export default class StageCore extends EventEmitter {
115
141
  * @param idOrEl 组件Dom节点的id属性,或者Dom节点
116
142
  */
117
143
  public async select(idOrEl: Id | HTMLElement, event?: MouseEvent): Promise<void> {
118
- let el;
119
- if (typeof idOrEl === 'string' || typeof idOrEl === 'number') {
120
- const runtime = await this.renderer?.getRuntime();
121
- el = await runtime?.select?.(`${idOrEl}`);
122
- if (!el) throw new Error(`不存在ID为${idOrEl}的元素`);
123
- } else {
124
- el = idOrEl;
125
- }
144
+ const el = await this.getTargetElement(idOrEl);
126
145
 
127
146
  if (el === this.selectedDom) return;
128
147
 
129
- const runtime = await this.renderer?.getRuntime();
148
+ const runtime = await this.renderer.getRuntime();
149
+
150
+ await runtime?.select?.(el.id);
151
+
130
152
  if (runtime?.beforeSelect) {
131
153
  await runtime.beforeSelect(el);
132
154
  }
133
155
 
134
156
  this.mask.setLayout(el);
135
- this.dr?.select(el, event);
157
+ this.dr.select(el, event);
158
+
159
+ if (this.config.autoScrollIntoView || el.dataset.autoScrollIntoView) {
160
+ this.mask.intersectionObserver?.observe(el);
161
+ }
162
+
136
163
  this.selectedDom = el;
164
+
165
+ if (this.renderer.contentWindow) {
166
+ removeSelectedClassName(this.renderer.contentWindow.document);
167
+ if (this.selectedDom) {
168
+ addSelectedClassName(this.selectedDom, this.renderer.contentWindow.document);
169
+ }
170
+ }
137
171
  }
138
172
 
139
173
  /**
140
174
  * 更新选中的节点
141
175
  * @param data 更新的数据
142
176
  */
143
- public update(data: UpdateData): void {
177
+ public update(data: UpdateData): Promise<void> {
144
178
  const { config } = data;
145
179
 
146
- this.renderer?.getRuntime().then((runtime) => {
180
+ return this.renderer?.getRuntime().then((runtime) => {
147
181
  runtime?.update?.(data);
148
182
  // 更新配置后,需要等组件渲染更新
149
183
  setTimeout(() => {
150
184
  const el = this.renderer.contentWindow?.document.getElementById(`${config.id}`);
151
- if (el) {
185
+ // 有可能dom已经重新渲染,不再是原来的dom了,所以这里判断id,而不是判断el === this.selectedDom
186
+ if (el && el.id === this.selectedDom?.id) {
187
+ this.selectedDom = el;
152
188
  // 更新了组件的布局,需要重新设置mask是否可以滚动
153
189
  this.mask.setLayout(el);
154
- this.dr?.select(el);
190
+ this.dr.updateMoveable(el);
155
191
  }
156
192
  }, 0);
157
193
  });
158
194
  }
159
195
 
160
- public sortNode(data: SortEventData): void {
161
- this.renderer?.getRuntime().then((runtime) => runtime?.sortNode?.(data));
196
+ /**
197
+ * 高亮选中组件
198
+ * @param el 页面Dom节点
199
+ */
200
+ public async highlight(idOrEl: HTMLElement | Id): Promise<void> {
201
+ let el;
202
+ try {
203
+ el = await this.getTargetElement(idOrEl);
204
+ } catch (error) {
205
+ this.highlightLayer.clearHighlight();
206
+ return;
207
+ }
208
+ if (el === this.highlightedDom) return;
209
+ this.highlightLayer.highlight(el);
210
+ this.highlightedDom = el;
162
211
  }
163
212
 
164
- public add(data: UpdateData): void {
165
- this.renderer?.getRuntime().then((runtime) => runtime?.add?.(data));
213
+ public sortNode(data: SortEventData): Promise<void> {
214
+ return this.renderer?.getRuntime().then((runtime) => runtime?.sortNode?.(data));
166
215
  }
167
216
 
168
- public remove(data: RemoveData): void {
169
- this.renderer?.getRuntime().then((runtime) => runtime?.remove?.(data));
217
+ public add(data: UpdateData): Promise<void> {
218
+ return this.renderer?.getRuntime().then((runtime) => runtime?.add?.(data));
219
+ }
220
+
221
+ public remove(data: RemoveData): Promise<void> {
222
+ return this.renderer?.getRuntime().then((runtime) => runtime?.remove?.(data));
170
223
  }
171
224
 
172
225
  public setZoom(zoom: number = DEFAULT_ZOOM): void {
@@ -178,6 +231,7 @@ export default class StageCore extends EventEmitter {
178
231
  * @param el 将stage挂载到该Dom节点上
179
232
  */
180
233
  public mount(el: HTMLDivElement): void {
234
+ this.container = el;
181
235
  const { mask, renderer } = this;
182
236
 
183
237
  renderer.mount(el);
@@ -198,12 +252,24 @@ export default class StageCore extends EventEmitter {
198
252
  * 销毁实例
199
253
  */
200
254
  public destroy(): void {
201
- const { mask, renderer, dr } = this;
255
+ const { mask, renderer, dr, highlightLayer } = this;
202
256
 
203
257
  renderer.destroy();
204
258
  mask.destroy();
205
259
  dr.destroy();
260
+ highlightLayer.destroy();
206
261
 
207
262
  this.removeAllListeners();
263
+
264
+ this.container = undefined;
265
+ }
266
+
267
+ private async getTargetElement(idOrEl: Id | HTMLElement): Promise<HTMLElement> {
268
+ if (typeof idOrEl === 'string' || typeof idOrEl === 'number') {
269
+ const el = this.renderer.contentWindow?.document.getElementById(`${idOrEl}`);
270
+ if (!el) throw new Error(`不存在ID为${idOrEl}的元素`);
271
+ return el;
272
+ }
273
+ return idOrEl;
208
274
  }
209
275
  }