-
-
+
diff --git a/packages/editor/src/services/editor.ts b/packages/editor/src/services/editor.ts
index 0f25963c..90a23736 100644
--- a/packages/editor/src/services/editor.ts
+++ b/packages/editor/src/services/editor.ts
@@ -24,6 +24,8 @@ import type { Id, MApp, MComponent, MContainer, MNode, MPage, MPageFragment } fr
import { NodeType } from '@tmagic/schema';
import { getNodePath, isNumber, isPage, isPageFragment, isPop } from '@tmagic/utils';
+import BaseService from '@editor/services//BaseService';
+import propsService from '@editor/services//props';
import depService from '@editor/services/dep';
import historyService from '@editor/services/history';
import storageService, { Protocol } from '@editor/services/storage';
@@ -44,9 +46,6 @@ import {
} from '@editor/utils/editor';
import { beforePaste, getAddParent } from '@editor/utils/operator';
-import BaseService from './BaseService';
-import propsService from './props';
-
class Editor extends BaseService {
public state: StoreState = reactive({
root: null,
diff --git a/packages/editor/src/services/stageOverlay.ts b/packages/editor/src/services/stageOverlay.ts
new file mode 100644
index 00000000..0cddd172
--- /dev/null
+++ b/packages/editor/src/services/stageOverlay.ts
@@ -0,0 +1,182 @@
+import { reactive } from 'vue';
+
+import StageCore from '@tmagic/stage';
+
+import { useStage } from '@editor/hooks/use-stage';
+import BaseService from '@editor/services//BaseService';
+import editorService from '@editor/services//editor';
+import type { StageOptions, StageOverlayState } from '@editor/type';
+
+class StageOverlay extends BaseService {
+ private state: StageOverlayState = reactive({
+ wrapDiv: document.createElement('div'),
+ sourceEl: null,
+ contentEl: null,
+ stage: null,
+ stageOptions: null,
+ wrapWidth: 0,
+ wrapHeight: 0,
+ stageOverlayVisible: false,
+ });
+
+ constructor() {
+ super([
+ { name: 'openOverlay', isAsync: false },
+ { name: 'closeOverlay', isAsync: false },
+ { name: 'updateOverlay', isAsync: false },
+ { name: 'createStage', isAsync: false },
+ ]);
+
+ this.get('wrapDiv').classList.add('tmagic-editor-sub-stage-wrap');
+ }
+
+ public get
(name: K): StageOverlayState[K] {
+ return this.state[name];
+ }
+
+ public set(name: K, value: T) {
+ this.state[name] = value;
+ }
+
+ public openOverlay(el: HTMLElement | undefined | null) {
+ const stageOptions = this.get('stageOptions');
+ if (!el || !stageOptions) return;
+
+ this.set('sourceEl', el);
+
+ this.createContentEl();
+
+ this.set('stageOverlayVisible', true);
+
+ editorService.on('update', this.updateHandler);
+ editorService.on('add', this.addHandler);
+ editorService.on('remove', this.removeHandler);
+ }
+
+ public closeOverlay() {
+ this.set('stageOverlayVisible', false);
+ const subStage = this.get('stage');
+ const wrapDiv = this.get('wrapDiv');
+ subStage?.destroy();
+ wrapDiv.remove();
+
+ this.set('stage', null);
+ this.set('sourceEl', null);
+ this.set('contentEl', null);
+
+ editorService.off('update', this.updateHandler);
+ editorService.off('add', this.addHandler);
+ editorService.off('remove', this.removeHandler);
+ }
+
+ public updateOverlay() {
+ const sourceEl = this.get('sourceEl');
+
+ if (!sourceEl) return;
+
+ const { scrollWidth, scrollHeight } = sourceEl;
+
+ this.set('wrapWidth', scrollWidth);
+ this.set('wrapHeight', scrollHeight);
+ }
+
+ public createStage(stageOptions: StageOptions = {}) {
+ return useStage({
+ ...stageOptions,
+ runtimeUrl: '',
+ autoScrollIntoView: false,
+ render: async (stage: StageCore) => {
+ this.copyDocumentElement();
+
+ const rootEls = stage.renderer.getDocument()?.body.children;
+ if (rootEls) {
+ Array.from(rootEls).forEach((element) => {
+ if (['SCRIPT', 'STYLE'].includes(element.tagName)) {
+ return;
+ }
+ element.remove();
+ });
+ }
+
+ const wrapDiv = this.get('wrapDiv');
+ const sourceEl = this.get('sourceEl');
+
+ wrapDiv.style.cssText = `
+ width: ${sourceEl?.scrollWidth}px;
+ height: ${sourceEl?.scrollHeight}px;
+ background-color: #fff;
+ `;
+
+ await this.render();
+
+ return wrapDiv;
+ },
+ });
+ }
+
+ private createContentEl() {
+ const sourceEl = this.get('sourceEl');
+ if (!sourceEl) return;
+
+ const contentEl = sourceEl.cloneNode(true) as HTMLElement;
+ this.set('contentEl', contentEl);
+
+ contentEl.style.position = 'static';
+ contentEl.style.overflow = 'visible';
+ }
+
+ private copyDocumentElement() {
+ const subStage = this.get('stage');
+ const stage = editorService.get('stage');
+
+ const doc = subStage?.renderer.getDocument();
+ const documentElement = stage?.renderer.getDocument()?.documentElement;
+
+ if (doc && documentElement) {
+ doc.replaceChild(documentElement.cloneNode(true), doc.documentElement);
+ }
+ }
+
+ private async render() {
+ this.createContentEl();
+
+ const contentEl = this.get('contentEl');
+ const wrapDiv = this.get('wrapDiv');
+ const subStage = this.get('stage');
+ const stageOptions = this.get('stageOptions');
+
+ if (!contentEl) return;
+
+ Array.from(wrapDiv.children).forEach((element) => {
+ element.remove();
+ });
+ wrapDiv.appendChild(contentEl);
+
+ setTimeout(() => {
+ subStage?.renderer.contentWindow?.magic.onPageElUpdate(wrapDiv);
+ });
+
+ if (await stageOptions?.canSelect?.(contentEl)) {
+ subStage?.select(contentEl);
+ }
+ }
+
+ private updateHandler = () => {
+ this.render();
+ this.updateOverlay();
+ };
+
+ private addHandler = () => {
+ this.render();
+ this.updateOverlay();
+ };
+
+ private removeHandler = () => {
+ this.render();
+ this.updateOverlay();
+ };
+}
+
+export type StageOverlayService = StageOverlay;
+
+export default new StageOverlay();
diff --git a/packages/editor/src/theme/stage.scss b/packages/editor/src/theme/stage.scss
index 8f99244c..48c280c7 100644
--- a/packages/editor/src/theme/stage.scss
+++ b/packages/editor/src/theme/stage.scss
@@ -32,7 +32,7 @@
top: 0;
width: 100%;
height: 100%;
- background-color: rgba(0, 0, 0, 0.6);
+ background-color: #fff;
display: flex;
z-index: 20;
overflow: auto;
@@ -42,11 +42,12 @@
position: relative;
flex-shrink: 0;
margin: auto;
+ box-shadow: rgba(0, 0, 0, 0.04) 0px 3px 5px;
}
.m-editor-stage-overlay-close.tmagic-design-icon {
position: fixed;
- right: 10px;
+ right: 20px;
top: 10px;
}
diff --git a/packages/editor/src/type.ts b/packages/editor/src/type.ts
index c505c17d..e261209f 100644
--- a/packages/editor/src/type.ts
+++ b/packages/editor/src/type.ts
@@ -39,6 +39,7 @@ import type { EventsService } from './services/events';
import type { HistoryService } from './services/history';
import type { KeybindingService } from './services/keybinding';
import type { PropsService } from './services/props';
+import type { StageOverlayService } from './services/stageOverlay';
import type { StorageService } from './services/storage';
import type { UiService } from './services/ui';
import type { UndoRedo } from './utils/undo-redo';
@@ -118,22 +119,23 @@ export interface Services {
depService: DepService;
dataSourceService: DataSourceService;
keybindingService: KeybindingService;
+ stageOverlayService: StageOverlayService;
}
export interface StageOptions {
- runtimeUrl: string;
- autoScrollIntoView: boolean;
- containerHighlightClassName: string;
- containerHighlightDuration: number;
- containerHighlightType: ContainerHighlightType;
+ runtimeUrl?: string;
+ autoScrollIntoView?: boolean;
+ containerHighlightClassName?: string;
+ containerHighlightDuration?: number;
+ containerHighlightType?: ContainerHighlightType;
disabledDragStart?: boolean;
- render: (stage: StageCore) => HTMLDivElement | Promise;
- moveableOptions: MoveableOptions | ((config?: CustomizeMoveableOptionsCallbackConfig) => MoveableOptions);
- canSelect: (el: HTMLElement) => boolean | Promise;
- isContainer: (el: HTMLElement) => boolean | Promise;
- updateDragEl: UpdateDragEl;
- renderType: RenderType;
- guidesOptions: Partial;
+ render?: (stage: StageCore) => HTMLDivElement | Promise;
+ moveableOptions?: MoveableOptions | ((config?: CustomizeMoveableOptionsCallbackConfig) => MoveableOptions);
+ canSelect?: (el: HTMLElement) => boolean | Promise;
+ isContainer?: (el: HTMLElement) => boolean | Promise;
+ updateDragEl?: UpdateDragEl;
+ renderType?: RenderType;
+ guidesOptions?: Partial;
disabledMultiSelect?: boolean;
}
@@ -160,6 +162,17 @@ export interface PropsState {
relateIdMap: Record;
}
+export interface StageOverlayState {
+ wrapDiv: HTMLDivElement;
+ sourceEl: HTMLElement | null;
+ contentEl: HTMLElement | null;
+ stage: StageCore | null;
+ stageOptions: StageOptions | null;
+ wrapWidth: number;
+ wrapHeight: number;
+ stageOverlayVisible: boolean;
+}
+
export interface ComponentGroupState {
list: ComponentGroup[];
}
diff --git a/packages/stage/src/ActionManager.ts b/packages/stage/src/ActionManager.ts
index 77d8c1ba..2362ab4d 100644
--- a/packages/stage/src/ActionManager.ts
+++ b/packages/stage/src/ActionManager.ts
@@ -79,7 +79,7 @@ export default class ActionManager extends EventEmitter {
private getTargetElement: GetTargetElement;
private getElementsFromPoint: GetElementsFromPoint;
private canSelect: CanSelect;
- private isContainer: IsContainer;
+ private isContainer?: IsContainer;
private getRenderDocument: GetRenderDocument;
private disabledMultiSelect = false;
private config: ActionManagerConfig;
@@ -328,7 +328,7 @@ export default class ActionManager extends EventEmitter {
const els = this.getElementsFromPoint(event);
for (const el of els) {
- if (!el.id.startsWith(GHOST_EL_ID_PREFIX) && (await this.isContainer(el)) && !excludeElList.includes(el)) {
+ if (!el.id.startsWith(GHOST_EL_ID_PREFIX) && (await this.isContainer?.(el)) && !excludeElList.includes(el)) {
addClassName(el, doc, this.containerHighlightClassName);
break;
}
diff --git a/packages/stage/src/StageCore.ts b/packages/stage/src/StageCore.ts
index c3f87da3..472ca579 100644
--- a/packages/stage/src/StageCore.ts
+++ b/packages/stage/src/StageCore.ts
@@ -252,6 +252,10 @@ export default class StageCore extends EventEmitter {
* 监听页面大小变化
*/
private observePageResize(page: HTMLElement): void {
+ if (this.pageResizeObserver) {
+ this.pageResizeObserver.disconnect();
+ }
+
if (typeof ResizeObserver !== 'undefined') {
this.pageResizeObserver = new ResizeObserver((entries) => {
this.mask.pageResize(entries);
diff --git a/packages/stage/src/types.ts b/packages/stage/src/types.ts
index 85942e5a..c51ddccb 100644
--- a/packages/stage/src/types.ts
+++ b/packages/stage/src/types.ts
@@ -66,7 +66,7 @@ export interface StageCoreConfig {
/** 放大倍数,默认1倍 */
zoom?: number;
canSelect?: CanSelect;
- isContainer: IsContainer;
+ isContainer?: IsContainer;
containerHighlightClassName?: string;
containerHighlightDuration?: number;
containerHighlightType?: ContainerHighlightType;
@@ -91,7 +91,7 @@ export interface ActionManagerConfig {
disabledDragStart?: boolean;
disabledMultiSelect?: boolean;
canSelect?: CanSelect;
- isContainer: IsContainer;
+ isContainer?: IsContainer;
getRootContainer: GetRootContainer;
getRenderDocument: GetRenderDocument;
updateDragEl?: UpdateDragEl;
diff --git a/packages/stage/src/util.ts b/packages/stage/src/util.ts
index 194449d9..a53ee5c5 100644
--- a/packages/stage/src/util.ts
+++ b/packages/stage/src/util.ts
@@ -66,6 +66,7 @@ export const getTargetElStyle = (el: TargetElement, zIndex?: ZIndex) => {
width: ${el.clientWidth}px;
height: ${el.clientHeight}px;
border: ${border};
+ opacity: 0;
${typeof zIndex !== 'undefined' ? `z-index: ${zIndex};` : ''}
`;
};
diff --git a/playground/src/pages/Editor.vue b/playground/src/pages/Editor.vue
index 4b03e6fa..b864b1a0 100644
--- a/playground/src/pages/Editor.vue
+++ b/playground/src/pages/Editor.vue
@@ -187,6 +187,13 @@ const moveableOptions = (config?: CustomizeMoveableOptionsCallbackConfig): Movea
options.resizable = !isPage;
options.rotatable = !isPage;
+ // 双击后在弹层中编辑时,根组件不能拖拽
+ if (config?.targetEl?.parentElement?.classList.contains('tmagic-editor-sub-stage-wrap')) {
+ options.draggable = false;
+ options.resizable = false;
+ options.rotatable = false;
+ }
+
return options;
};
diff --git a/playground/src/pages/FormEditor.vue b/playground/src/pages/FormEditor.vue
index 6d08d181..05467e6e 100644
--- a/playground/src/pages/FormEditor.vue
+++ b/playground/src/pages/FormEditor.vue
@@ -8,6 +8,7 @@
:render="render"
:can-select="canSelect"
:disabled-page-fragment="true"
+ :disabled-stage-overlay="true"
:stage-rect="{ width: 'calc(100% - 70px)', height: '100%' }"
:moveable-options="{ resizable: false }"
>