diff --git a/packages/editor/src/hooks/use-stage.ts b/packages/editor/src/hooks/use-stage.ts index d2c6b1bf..eca95517 100644 --- a/packages/editor/src/hooks/use-stage.ts +++ b/packages/editor/src/hooks/use-stage.ts @@ -1,4 +1,4 @@ -import { computed, watch } from 'vue'; +import { computed, onBeforeUnmount, watch } from 'vue'; import type { MNode } from '@tmagic/schema'; import StageCore, { GuidesType, RemoveEventData, SortEventData, UpdateEventData } from '@tmagic/stage'; @@ -62,7 +62,7 @@ export const useStage = (stageOptions: StageOptions) => { }, ); - stage.mask.setGuides([ + stage.mask?.setGuides([ getGuideLineFromCache(getGuideLineKey(H_GUIDE_LINE_STORAGE_KEY)), getGuideLineFromCache(getGuideLineKey(V_GUIDE_LINE_STORAGE_KEY)), ]); @@ -131,5 +131,9 @@ export const useStage = (stageOptions: StageOptions) => { } }); + onBeforeUnmount(() => { + stage.destroy(); + }); + return stage; }; diff --git a/packages/editor/src/initService.ts b/packages/editor/src/initService.ts index 5cae4236..c32b7776 100644 --- a/packages/editor/src/initService.ts +++ b/packages/editor/src/initService.ts @@ -242,7 +242,7 @@ export const initServiceEvents = ( const getApp = () => { const stage = editorService.get('stage'); - return stage?.renderer.runtime?.getApp?.(); + return stage?.renderer?.runtime?.getApp?.(); }; const updateDataSourceSchema = () => { diff --git a/packages/editor/src/layouts/sidebar/ComponentListPanel.vue b/packages/editor/src/layouts/sidebar/ComponentListPanel.vue index 33cc2b9f..971410fa 100644 --- a/packages/editor/src/layouts/sidebar/ComponentListPanel.vue +++ b/packages/editor/src/layouts/sidebar/ComponentListPanel.vue @@ -111,7 +111,7 @@ const dragendHandler = () => { globalThis.clearTimeout(timeout); timeout = undefined; } - const doc = stage.value?.renderer.getDocument(); + const doc = stage.value?.renderer?.getDocument(); if (doc && stageOptions?.containerHighlightClassName) { removeClassNameByClassName(doc, stageOptions.containerHighlightClassName); } diff --git a/packages/editor/src/layouts/workspace/viewer/NodeListMenu.vue b/packages/editor/src/layouts/workspace/viewer/NodeListMenu.vue index 86a9c2bd..cdd15281 100644 --- a/packages/editor/src/layouts/workspace/viewer/NodeListMenu.vue +++ b/packages/editor/src/layouts/workspace/viewer/NodeListMenu.vue @@ -61,7 +61,7 @@ const unWatch = watch( nextTick(() => unWatch()); stage.on('select', (el: HTMLElement, event: MouseEvent) => { - const els = stage.renderer.getElementsFromPoint(event) || []; + const els = stage.renderer?.getElementsFromPoint(event) || []; const ids = els.map((el) => getIdFromEl()(el)).filter((id) => Boolean(id)) as string[]; buttonVisible.value = ids.length > 3; diff --git a/packages/editor/src/layouts/workspace/viewer/Stage.vue b/packages/editor/src/layouts/workspace/viewer/Stage.vue index d7e14881..22e649d1 100644 --- a/packages/editor/src/layouts/workspace/viewer/Stage.vue +++ b/packages/editor/src/layouts/workspace/viewer/Stage.vue @@ -196,7 +196,7 @@ const dropHandler = async (e: DragEvent) => { e.preventDefault(); - const doc = stage?.renderer.contentWindow?.document; + const doc = stage?.renderer?.contentWindow?.document; const parentEl: HTMLElement | null | undefined = doc?.querySelector(`.${stageOptions?.containerHighlightClassName}`); let parent: MContainer | undefined | null = page.value; @@ -209,7 +209,7 @@ const dropHandler = async (e: DragEvent) => { const layout = await services?.editorService.getLayout(parent); const containerRect = stageContainer.value.getBoundingClientRect(); - const { scrollTop, scrollLeft } = stage.mask; + const { scrollTop, scrollLeft } = stage.mask!; const { style = {} } = config.data; let top = 0; diff --git a/packages/editor/src/layouts/workspace/viewer/StageOverlay.vue b/packages/editor/src/layouts/workspace/viewer/StageOverlay.vue index 83b8a7ae..1eded88a 100644 --- a/packages/editor/src/layouts/workspace/viewer/StageOverlay.vue +++ b/packages/editor/src/layouts/workspace/viewer/StageOverlay.vue @@ -34,7 +34,7 @@ const style = computed(() => ({ watch(stage, (stage) => { if (stage) { stage.on('dblclick', async (event: MouseEvent) => { - const el = await stage.actionManager.getElementFromPoint(event); + const el = (await stage.actionManager?.getElementFromPoint(event)) || null; services?.stageOverlayService.openOverlay(el); }); } else { @@ -53,8 +53,8 @@ watch(stageOverlay, (stageOverlay) => { const { mask, renderer } = subStage; - const { contentWindow } = renderer; - mask.showRule(false); + const { contentWindow } = renderer!; + mask?.showRule(false); services?.stageOverlayService.updateOverlay(); diff --git a/packages/editor/src/services/editor.ts b/packages/editor/src/services/editor.ts index 6d691e58..06bb5940 100644 --- a/packages/editor/src/services/editor.ts +++ b/packages/editor/src/services/editor.ts @@ -267,7 +267,7 @@ class Editor extends BaseService { if (node?.id) { this.get('stage') - ?.renderer.runtime?.getApp?.() + ?.renderer?.runtime?.getApp?.() ?.page?.emit( 'editor:select', { @@ -737,7 +737,7 @@ class Editor extends BaseService { public async doPaste(config: MNode[], position: PastePosition = {}): Promise { propsService.clearRelateId(); - const doc = this.get('stage')?.renderer.contentWindow?.document; + const doc = this.get('stage')?.renderer?.contentWindow?.document; const pasteConfigs = beforePaste(position, cloneDeep(config), doc); return pasteConfigs; } @@ -756,7 +756,7 @@ class Editor extends BaseService { if (!node.style) return config; const stage = this.get('stage'); - const doc = stage?.renderer.contentWindow?.document; + const doc = stage?.renderer?.contentWindow?.document; if (doc) { const el = getElById()(doc, node.id); diff --git a/packages/editor/src/services/stageOverlay.ts b/packages/editor/src/services/stageOverlay.ts index ca74f544..c79f408a 100644 --- a/packages/editor/src/services/stageOverlay.ts +++ b/packages/editor/src/services/stageOverlay.ts @@ -64,6 +64,12 @@ class StageOverlay extends BaseService { const subStage = this.get('stage'); const wrapDiv = this.get('wrapDiv'); subStage?.destroy(); + + for (let i = 0, l = wrapDiv.children.length; i < l; i++) { + const child = wrapDiv.children[i]; + child.remove(); + } + wrapDiv.remove(); this.set('stage', null); @@ -97,7 +103,7 @@ class StageOverlay extends BaseService { render: async (stage: StageCore) => { this.copyDocumentElement(); - const rootEls = stage.renderer.getDocument()?.body.children; + const rootEls = stage.renderer?.getDocument()?.body.children; if (rootEls) { Array.from(rootEls).forEach((element) => { if (['SCRIPT', 'STYLE'].includes(element.tagName)) { @@ -135,8 +141,8 @@ class StageOverlay extends BaseService { const subStage = this.get('stage'); const stage = editorService.get('stage'); - const doc = subStage?.renderer.getDocument(); - const documentElement = stage?.renderer.getDocument()?.documentElement; + const doc = subStage?.renderer?.getDocument(); + const documentElement = stage?.renderer?.getDocument()?.documentElement; if (doc && documentElement) { doc.replaceChild(documentElement.cloneNode(true), doc.documentElement); @@ -160,13 +166,15 @@ class StageOverlay extends BaseService { background-color: #fff; `; - Array.from(wrapDiv.children).forEach((element) => { - element.remove(); - }); + for (let i = 0, l = wrapDiv.children.length; i < l; i++) { + const child = wrapDiv.children[i]; + child.remove(); + } + wrapDiv.appendChild(contentEl); setTimeout(() => { - subStage?.renderer.contentWindow?.magic.onPageElUpdate(wrapDiv); + subStage?.renderer?.contentWindow?.magic.onPageElUpdate(wrapDiv); }); if (await stageOptions?.canSelect?.(contentEl)) { diff --git a/packages/editor/src/utils/content-menu.ts b/packages/editor/src/utils/content-menu.ts index 27a0ea93..0bfa337d 100644 --- a/packages/editor/src/utils/content-menu.ts +++ b/packages/editor/src/utils/content-menu.ts @@ -47,10 +47,10 @@ export const usePasteMenu = (menu?: Ref | undef const rect = menu.value.$el.getBoundingClientRect(); const parentRect = stage?.container?.getBoundingClientRect(); const initialLeft = - calcValueByFontsize(stage?.renderer.getDocument(), (rect.left || 0) - (parentRect?.left || 0)) / + calcValueByFontsize(stage?.renderer?.getDocument(), (rect.left || 0) - (parentRect?.left || 0)) / services.uiService.get('zoom'); const initialTop = - calcValueByFontsize(stage?.renderer.getDocument(), (rect.top || 0) - (parentRect?.top || 0)) / + calcValueByFontsize(stage?.renderer?.getDocument(), (rect.top || 0) - (parentRect?.top || 0)) / services.uiService.get('zoom'); services?.editorService?.paste({ left: initialLeft, top: initialTop }); } else { diff --git a/packages/editor/src/utils/editor.ts b/packages/editor/src/utils/editor.ts index e01d340a..70ef320d 100644 --- a/packages/editor/src/utils/editor.ts +++ b/packages/editor/src/utils/editor.ts @@ -109,12 +109,16 @@ const getMiddleTop = (node: MNode, parentNode: MNode, stage: StageCore | null) = } const { height: parentHeight } = parentNode.style; - // wrapperHeight 是未 calcValue的高度, 所以要将其calcValueByFontsize一下, 否则在pad or pc端计算的结果有误 - const { scrollTop = 0, wrapperHeight } = stage.mask; - const wrapperHeightDeal = calcValueByFontsize(stage.renderer.getDocument()!, wrapperHeight); - const scrollTopDeal = calcValueByFontsize(stage.renderer.getDocument()!, scrollTop); - if (isPage(parentNode)) { - return (wrapperHeightDeal - height) / 2 + scrollTopDeal; + + let wrapperHeightDeal = parentHeight; + if (stage.mask && stage.renderer) { + // wrapperHeight 是未 calcValue的高度, 所以要将其calcValueByFontsize一下, 否则在pad or pc端计算的结果有误 + const { scrollTop = 0, wrapperHeight } = stage.mask; + wrapperHeightDeal = calcValueByFontsize(stage.renderer.getDocument()!, wrapperHeight); + const scrollTopDeal = calcValueByFontsize(stage.renderer.getDocument()!, scrollTop); + if (isPage(parentNode)) { + return (wrapperHeightDeal - height) / 2 + scrollTopDeal; + } } // 如果容器的元素高度大于当前视口高度的2倍, 添加的元素居中位置也会看不见, 所以要取最小值计算 @@ -263,7 +267,7 @@ export const fixNodePosition = (config: MNode, parent: MContainer, stage: StageC return { ...(config.style || {}), top: getMiddleTop(config, parent, stage), - left: fixNodeLeft(config, parent, stage?.renderer.contentWindow?.document), + left: fixNodeLeft(config, parent, stage?.renderer?.contentWindow?.document), }; }; diff --git a/packages/stage/src/ActionManager.ts b/packages/stage/src/ActionManager.ts index b6ba2ce8..fe0859d4 100644 --- a/packages/stage/src/ActionManager.ts +++ b/packages/stage/src/ActionManager.ts @@ -65,9 +65,9 @@ const defaultContainerHighlightDuration = 800; * @extends EventEmitter */ export default class ActionManager extends EventEmitter { - private dr: StageDragResize; - private multiDr?: StageMultiDragResize; - private highlightLayer: StageHighlight; + private dr: StageDragResize | null = null; + private multiDr: StageMultiDragResize | null = null; + private highlightLayer: StageHighlight | null = null; /** 单选、多选、高亮的容器(蒙层的content) */ private container: HTMLElement; /** 当前选中的节点 */ @@ -143,7 +143,7 @@ export default class ActionManager extends EventEmitter { this.disabledMultiSelect = true; if (this.multiDr) { this.multiDr.destroy(); - this.multiDr = undefined; + this.multiDr = null; } } @@ -161,7 +161,7 @@ export default class ActionManager extends EventEmitter { * @param guidelines 参考线坐标数组 */ public setGuidelines(type: GuidesType, guidelines: number[]): void { - this.dr.setGuidelines(type, guidelines); + this.dr?.setGuidelines(type, guidelines); this.multiDr?.setGuidelines(type, guidelines); } @@ -169,7 +169,7 @@ export default class ActionManager extends EventEmitter { * 清空所有参考线 */ public clearGuides(): void { - this.dr.clearGuides(); + this.dr?.clearGuides(); this.multiDr?.clearGuides(); } @@ -178,7 +178,7 @@ export default class ActionManager extends EventEmitter { * @param el 变更的元素 */ public updateMoveable(el?: HTMLElement): void { - this.dr.updateMoveable(el); + this.dr?.updateMoveable(el); // 多选时不可配置元素,因此不存在多选元素变更,不需要传el this.multiDr?.updateMoveable(); } @@ -204,7 +204,7 @@ export default class ActionManager extends EventEmitter { } public getMoveableOption(key: K): MoveableOptions[K] | undefined { - if (this.dr.getTarget()) { + if (this.dr?.getTarget()) { return this.dr.getOption(key); } if (this.multiDr?.targetList.length) { @@ -271,7 +271,7 @@ export default class ActionManager extends EventEmitter { public select(el: HTMLElement | null, event?: MouseEvent): void { this.setSelectedEl(el); this.clearSelectStatus(SelectStatus.MULTI_SELECT); - this.dr.select(el, event); + this.dr?.select(el, event); } public multiSelect(ids: Id[]): void { @@ -310,14 +310,14 @@ export default class ActionManager extends EventEmitter { } if (el === this.highlightedEl || !el) return; - this.highlightLayer.highlight(el); + this.highlightLayer?.highlight(el); this.highlightedEl = el; this.emit('highlight', el); } public clearHighlight(): void { this.setHighlightEl(undefined); - this.highlightLayer.clearHighlight(); + this.highlightLayer?.clearHighlight(); } /** @@ -329,7 +329,7 @@ export default class ActionManager extends EventEmitter { this.multiDr?.clearSelectStatus(); this.selectedElList = []; } else { - this.dr.clearSelectStatus(); + this.dr?.clearSelectStatus(); } } @@ -373,7 +373,7 @@ export default class ActionManager extends EventEmitter { } public getDragStatus() { - return this.dr.getDragStatus(); + return this.dr?.getDragStatus(); } public destroy(): void { @@ -382,9 +382,16 @@ export default class ActionManager extends EventEmitter { this.container.removeEventListener('mouseleave', this.mouseLeaveHandler); this.container.removeEventListener('wheel', this.mouseWheelHandler); this.container.removeEventListener('dblclick', this.dblclickHandler); - this.dr.destroy(); + this.selectedEl = null; + this.selectedElList = []; + + this.dr?.destroy(); this.multiDr?.destroy(); - this.highlightLayer.destroy(); + this.highlightLayer?.destroy(); + + this.dr = null; + this.multiDr = null; + this.highlightLayer = null; } public on( @@ -431,7 +438,7 @@ export default class ActionManager extends EventEmitter { this.emit('select-parent'); }) .on(AbleActionEventType.REMOVE, () => { - const drTarget = this.dr.getTarget(); + const drTarget = this.dr?.getTarget(); if (!drTarget) return; const data: RemoveEventData = { data: [{ el: drTarget }], diff --git a/packages/stage/src/DragResizeHelper.ts b/packages/stage/src/DragResizeHelper.ts index 19714372..31c1a2ec 100644 --- a/packages/stage/src/DragResizeHelper.ts +++ b/packages/stage/src/DragResizeHelper.ts @@ -50,7 +50,7 @@ export default class DragResizeHelper { /** 目标节点在蒙层上的占位节点,用于跟鼠标交互,避免鼠标事件直接作用到目标节点 */ private targetShadow: TargetShadow; /** 要操作的原始目标节点 */ - private target!: HTMLElement; + private target: HTMLElement | null = null; /** 多选:目标节点组 */ private targetList: HTMLElement[] = []; /** 响应拖拽的状态事件,修改绝对定位布局下targetShadow的dom。 @@ -84,6 +84,8 @@ export default class DragResizeHelper { } public destroy(): void { + this.target = null; + this.targetList = []; this.targetShadow.destroy(); this.destroyGhostEl(); this.moveableHelper.clear(); @@ -114,8 +116,8 @@ export default class DragResizeHelper { public onResizeStart(e: OnResizeStart): void { this.moveableHelper.onResizeStart(e); - this.frameSnapShot.top = this.target.offsetTop; - this.frameSnapShot.left = this.target.offsetLeft; + this.frameSnapShot.top = this.target!.offsetTop; + this.frameSnapShot.left = this.target!.offsetLeft; } public onResize(e: OnResize): void { @@ -123,33 +125,33 @@ export default class DragResizeHelper { const { beforeTranslate } = drag; // 流式布局 if (this.mode === Mode.SORTABLE) { - this.target.style.top = '0px'; + this.target!.style.top = '0px'; if (this.targetShadow.el) { this.targetShadow.el.style.width = `${width}px`; this.targetShadow.el.style.height = `${height}px`; } } else { this.moveableHelper.onResize(e); - const { marginLeft, marginTop } = getMarginValue(this.target); - this.target.style.left = `${this.frameSnapShot.left + beforeTranslate[0] - marginLeft}px`; - this.target.style.top = `${this.frameSnapShot.top + beforeTranslate[1] - marginTop}px`; + const { marginLeft, marginTop } = getMarginValue(this.target!); + this.target!.style.left = `${this.frameSnapShot.left + beforeTranslate[0] - marginLeft}px`; + this.target!.style.top = `${this.frameSnapShot.top + beforeTranslate[1] - marginTop}px`; } - const { borderLeftWidth, borderRightWidth, borderTopWidth, borderBottomWidth } = getBorderWidth(this.target); + const { borderLeftWidth, borderRightWidth, borderTopWidth, borderBottomWidth } = getBorderWidth(this.target!); - this.target.style.width = `${width + borderLeftWidth + borderRightWidth}px`; - this.target.style.height = `${height + borderTopWidth + borderBottomWidth}px`; + this.target!.style.width = `${width + borderLeftWidth + borderRightWidth}px`; + this.target!.style.height = `${height + borderTopWidth + borderBottomWidth}px`; } public onDragStart(e: OnDragStart): void { this.moveableHelper.onDragStart(e); if (this.mode === Mode.SORTABLE) { - this.ghostEl = this.generateGhostEl(this.target); + this.ghostEl = this.generateGhostEl(this.target!); } - this.frameSnapShot.top = this.target.offsetTop; - this.frameSnapShot.left = this.target.offsetLeft; + this.frameSnapShot.top = this.target!.offsetTop; + this.frameSnapShot.left = this.target!.offsetLeft; } public onDrag(e: OnDrag): void { @@ -161,10 +163,10 @@ export default class DragResizeHelper { this.moveableHelper.onDrag(e); - const { marginLeft, marginTop } = getMarginValue(this.target); + const { marginLeft, marginTop } = getMarginValue(this.target!); - this.target.style.left = `${this.frameSnapShot.left + e.beforeTranslate[0] - marginLeft}px`; - this.target.style.top = `${this.frameSnapShot.top + e.beforeTranslate[1] - marginTop}px`; + this.target!.style.left = `${this.frameSnapShot.left + e.beforeTranslate[0] - marginLeft}px`; + this.target!.style.top = `${this.frameSnapShot.top + e.beforeTranslate[1] - marginTop}px`; } public onRotateStart(e: OnRotateStart): void { @@ -174,7 +176,7 @@ export default class DragResizeHelper { public onRotate(e: OnRotate): void { this.moveableHelper.onRotate(e); const frame = this.moveableHelper.getFrame(e.target); - this.target.style.transform = frame?.toCSSObject().transform || ''; + this.target!.style.transform = frame?.toCSSObject().transform || ''; } public onScaleStart(e: OnScaleStart): void { @@ -184,7 +186,7 @@ export default class DragResizeHelper { public onScale(e: OnScale): void { this.moveableHelper.onScale(e); const frame = this.moveableHelper.getFrame(e.target); - this.target.style.transform = frame?.toCSSObject().transform || ''; + this.target!.style.transform = frame?.toCSSObject().transform || ''; } public getGhostEl(): HTMLElement | undefined { diff --git a/packages/stage/src/StageCore.ts b/packages/stage/src/StageCore.ts index f095a8d6..3511186b 100644 --- a/packages/stage/src/StageCore.ts +++ b/packages/stage/src/StageCore.ts @@ -47,9 +47,9 @@ import type { */ export default class StageCore extends EventEmitter { public container?: HTMLDivElement; - public renderer: StageRender; - public mask: StageMask; - public actionManager: ActionManager; + public renderer: StageRender | null = null; + public mask: StageMask | null = null; + public actionManager: ActionManager | null = null; private pageResizeObserver: ResizeObserver | null = null; private autoScrollIntoView: boolean | undefined; @@ -87,17 +87,18 @@ export default class StageCore extends EventEmitter { * @param id 选中的id */ public async select(id: Id, event?: MouseEvent): Promise { - const el = this.renderer.getTargetElement(id); - if (el === this.actionManager.getSelectedEl()) return; + const el = this.renderer?.getTargetElement(id) || null; + if (el === this.actionManager?.getSelectedEl()) return; - await this.renderer.select([id]); + await this.renderer?.select([id]); - el && this.mask.setLayout(el); - - this.actionManager.select(el, event); + if (el) { + this.mask?.setLayout(el); + } + this.actionManager?.select(el, event); if (el && (this.autoScrollIntoView || el.dataset.autoScrollIntoView)) { - this.mask.observerIntersection(el); + this.mask?.observerIntersection(el); } } @@ -106,20 +107,20 @@ export default class StageCore extends EventEmitter { * @param ids 选中元素的id列表 */ public async multiSelect(ids: Id[]): Promise { - const els = ids.map((id) => this.renderer.getTargetElement(id)).filter((el) => Boolean(el)); + const els = ids.map((id) => this.renderer?.getTargetElement(id)).filter((el) => Boolean(el)); if (els.length === 0) return; const lastEl = els[els.length - 1]; // 是否减少了组件选择 - const isReduceSelect = els.length < this.actionManager.getSelectedElList().length; - await this.renderer.select(ids); + const isReduceSelect = els.length < this.actionManager!.getSelectedElList().length; + await this.renderer?.select(ids); - lastEl && this.mask.setLayout(lastEl); + lastEl && this.mask?.setLayout(lastEl); - this.actionManager.multiSelect(ids); + this.actionManager?.multiSelect(ids); if (lastEl && (this.autoScrollIntoView || lastEl.dataset.autoScrollIntoView) && !isReduceSelect) { - this.mask.observerIntersection(lastEl); + this.mask?.observerIntersection(lastEl); } } @@ -128,11 +129,11 @@ export default class StageCore extends EventEmitter { * @param el 要高亮的元素 */ public highlight(id: Id): void { - this.actionManager.highlight(id); + this.actionManager?.highlight(id); } public clearHighlight(): void { - this.actionManager.clearHighlight(); + this.actionManager?.clearHighlight(); } /** @@ -142,13 +143,13 @@ export default class StageCore extends EventEmitter { public async update(data: UpdateData): Promise { const { config } = data; - await this.renderer.update(data); + await this.renderer?.update(data); // 通过setTimeout等画布中组件完成渲染更新 setTimeout(() => { - const el = this.renderer.getTargetElement(`${config.id}`); - if (el && this.actionManager.isSelectedEl(el)) { + const el = this.renderer?.getTargetElement(`${config.id}`); + if (el && this.actionManager?.isSelectedEl(el)) { // 更新了组件的布局,需要重新设置mask是否可以滚动 - this.mask.setLayout(el); + this.mask?.setLayout(el); // 组件有更新,需要set this.actionManager.setSelectedEl(el); this.actionManager.updateMoveable(el); @@ -161,7 +162,7 @@ export default class StageCore extends EventEmitter { * @param data 组件信息数据 */ public async add(data: UpdateData): Promise { - return await this.renderer.add(data); + return await this.renderer?.add(data); } /** @@ -169,11 +170,11 @@ export default class StageCore extends EventEmitter { * @param data 组件信息数据 */ public async remove(data: RemoveData): Promise { - return await this.renderer.remove(data); + return await this.renderer?.remove(data); } public setZoom(zoom: number = DEFAULT_ZOOM): void { - this.renderer.setZoom(zoom); + this.renderer?.setZoom(zoom); } /** @@ -184,8 +185,8 @@ export default class StageCore extends EventEmitter { this.container = el; const { mask, renderer } = this; - await renderer.mount(el); - mask.mount(el); + await renderer?.mount(el); + mask?.mount(el); this.emit('mounted'); } @@ -194,8 +195,8 @@ export default class StageCore extends EventEmitter { * 清空所有参考线 */ public clearGuides() { - this.mask.clearGuides(); - this.actionManager.clearGuides(); + this.mask?.clearGuides(); + this.actionManager?.clearGuides(); } /** @@ -216,23 +217,23 @@ export default class StageCore extends EventEmitter { * @returns timeoutId,调用方在鼠标移走时要取消该timeout,阻止标记 */ public delayedMarkContainer(event: MouseEvent, excludeElList: Element[] = []): NodeJS.Timeout | undefined { - return this.actionManager.delayedMarkContainer(event, excludeElList); + return this.actionManager?.delayedMarkContainer(event, excludeElList); } public getMoveableOption(key: K): MoveableOptions[K] | undefined { - return this.actionManager.getMoveableOption(key); + return this.actionManager?.getMoveableOption(key); } public getDragStatus() { - return this.actionManager.getDragStatus(); + return this.actionManager?.getDragStatus(); } public disableMultiSelect() { - this.actionManager.disableMultiSelect(); + this.actionManager?.disableMultiSelect(); } public enableMultiSelect() { - this.actionManager.enableMultiSelect(); + this.actionManager?.enableMultiSelect(); } /** @@ -241,14 +242,18 @@ export default class StageCore extends EventEmitter { public destroy(): void { const { mask, renderer, actionManager, pageResizeObserver } = this; - renderer.destroy(); - mask.destroy(); - actionManager.destroy(); + renderer?.destroy(); + mask?.destroy(); + actionManager?.destroy(); pageResizeObserver?.disconnect(); this.removeAllListeners(); this.container = undefined; + this.renderer = null; + this.mask = null; + this.actionManager = null; + this.pageResizeObserver = null; } public on( @@ -272,8 +277,8 @@ export default class StageCore extends EventEmitter { if (typeof ResizeObserver !== 'undefined') { this.pageResizeObserver = new ResizeObserver((entries) => { - this.mask.pageResize(entries); - this.actionManager.updateMoveable(); + this.mask?.pageResize(entries); + this.actionManager?.updateMoveable(); }); this.pageResizeObserver.observe(page); @@ -286,26 +291,26 @@ export default class StageCore extends EventEmitter { containerHighlightDuration: config.containerHighlightDuration, containerHighlightType: config.containerHighlightType, moveableOptions: config.moveableOptions, - container: this.mask.content, + container: this.mask!.content, disabledDragStart: config.disabledDragStart, disabledMultiSelect: config.disabledMultiSelect, canSelect: config.canSelect, isContainer: config.isContainer, updateDragEl: config.updateDragEl, getRootContainer: () => this.container, - getRenderDocument: () => this.renderer.getDocument(), - getTargetElement: (id: Id) => this.renderer.getTargetElement(id), - getElementsFromPoint: (point: Point) => this.renderer.getElementsFromPoint(point), + getRenderDocument: () => this.renderer!.getDocument(), + getTargetElement: (id: Id) => this.renderer!.getTargetElement(id), + getElementsFromPoint: (point: Point) => this.renderer!.getElementsFromPoint(point), }; return actionManagerConfig; } private initRenderEvent(): void { - this.renderer.on('runtime-ready', (runtime: Runtime) => { + this.renderer?.on('runtime-ready', (runtime: Runtime) => { this.emit('runtime-ready', runtime); }); - this.renderer.on('page-el-update', (el: HTMLElement) => { + this.renderer?.on('page-el-update', (el: HTMLElement) => { this.mask?.observe(el); this.observePageResize(el); @@ -314,8 +319,8 @@ export default class StageCore extends EventEmitter { } private initMaskEvent(): void { - this.mask.on('change-guides', (data: GuidesEventData) => { - this.actionManager.setGuidelines(data.type, data.guides); + this.mask?.on('change-guides', (data: GuidesEventData) => { + this.actionManager?.setGuidelines(data.type, data.guides); this.emit('change-guides', data); }); } @@ -336,7 +341,7 @@ export default class StageCore extends EventEmitter { */ private initActionManagerEvent(): void { this.actionManager - .on('before-select', (el: HTMLElement, event?: MouseEvent) => { + ?.on('before-select', (el: HTMLElement, event?: MouseEvent) => { const id = getIdFromEl()(el); id && this.select(id, event); }) @@ -359,7 +364,7 @@ export default class StageCore extends EventEmitter { */ private initDrEvent(): void { this.actionManager - .on('update', (data: UpdateEventData) => { + ?.on('update', (data: UpdateEventData) => { this.emit('update', data); }) .on('sort', (data: SortEventData) => { @@ -379,11 +384,11 @@ export default class StageCore extends EventEmitter { private initMulDrEvent(): void { this.actionManager // 多选切换到单选 - .on('change-to-select', (id: Id, e: MouseEvent) => { + ?.on('change-to-select', (id: Id, e: MouseEvent) => { this.select(id); // 先保证画布内完成渲染,再通知外部更新 setTimeout(() => { - const el = this.renderer.getTargetElement(id); + const el = this.renderer?.getTargetElement(id); el && this.emit('select', el, e); }); }) @@ -396,7 +401,7 @@ export default class StageCore extends EventEmitter { * 初始化Highlight类通过ActionManager抛出来的事件监听 */ private initHighlightEvent(): void { - this.actionManager.on('highlight', (highlightEl: HTMLElement) => { + this.actionManager?.on('highlight', (highlightEl: HTMLElement) => { this.emit('highlight', highlightEl); }); } @@ -406,7 +411,7 @@ export default class StageCore extends EventEmitter { */ private initMouseEvent(): void { this.actionManager - .on('mousemove', (event: MouseEvent) => { + ?.on('mousemove', (event: MouseEvent) => { this.emit('mousemove', event); }) .on('mouseleave', (event: MouseEvent) => { diff --git a/packages/stage/src/StageDragResize.ts b/packages/stage/src/StageDragResize.ts index 6f80cdb6..3da78357 100644 --- a/packages/stage/src/StageDragResize.ts +++ b/packages/stage/src/StageDragResize.ts @@ -125,6 +125,7 @@ export default class StageDragResize extends MoveableOptionsManager { * 销毁实例 */ public destroy(): void { + this.target = null; this.moveable?.destroy(); this.dragResizeHelper.destroy(); this.dragStatus = StageDragStatus.END; diff --git a/packages/stage/src/StageHighlight.ts b/packages/stage/src/StageHighlight.ts index d37c7f0f..e38eeebc 100644 --- a/packages/stage/src/StageHighlight.ts +++ b/packages/stage/src/StageHighlight.ts @@ -81,6 +81,7 @@ export default class StageHighlight extends EventEmitter { * 销毁实例 */ public destroy(): void { + this.target = undefined; this.moveable?.destroy(); this.targetShadow?.destroy(); this.moveable = undefined; diff --git a/runtime/tmagic-form/package.json b/runtime/tmagic-form/package.json index bb79d3c7..16c6b2a2 100644 --- a/runtime/tmagic-form/package.json +++ b/runtime/tmagic-form/package.json @@ -1,5 +1,5 @@ { - "version": "1.1.1", + "version": "1.1.2", "name": "@tmagic/tmagic-form-runtime", "type": "module", "main": "dist/tmagic-form-runtime.umd.cjs", diff --git a/runtime/tmagic-form/src/App.vue b/runtime/tmagic-form/src/App.vue index ca030b90..3047b08d 100644 --- a/runtime/tmagic-form/src/App.vue +++ b/runtime/tmagic-form/src/App.vue @@ -23,8 +23,8 @@ const { mForm, formConfig, config, values } = useFormConfig(props); watch(formConfig, async () => { setTimeout(() => { - const page = props.stage.renderer.getDocument()?.querySelector('.m-form'); - page && props.stage.renderer.contentWindow?.magic.onPageElUpdate(page); + const page = props.stage.renderer?.getDocument()?.querySelector('.m-form'); + page && props.stage.renderer?.contentWindow?.magic.onPageElUpdate(page); }); }); diff --git a/runtime/tmagic-form/src/index.ts b/runtime/tmagic-form/src/index.ts index 0d9a6856..06668a11 100644 --- a/runtime/tmagic-form/src/index.ts +++ b/runtime/tmagic-form/src/index.ts @@ -24,21 +24,25 @@ export const useRuntime = ({ fillConfig?: (config: FormConfig, mForm: any) => FormConfig; } = {}) => { const render = (stage: StageCore) => { - injectStyle(stage.renderer.getDocument()!, cssStyle); - injectStyle( - stage.renderer.getDocument()!, - `html, - body, - #app { - width: 100%; - height: 100%; - margin: 0; - } - ::-webkit-scrollbar { - width: 0; - } - `, - ); + const doc = stage.renderer?.getDocument(); + + if (doc) { + injectStyle(doc, cssStyle); + injectStyle( + doc, + `html, + body, + #app { + width: 100%; + height: 100%; + margin: 0; + } + ::-webkit-scrollbar { + width: 0; + } + `, + ); + } const el: HTMLDivElement = globalThis.document.createElement('div'); el.id = 'app'; diff --git a/runtime/tmagic-form/src/useFormConfig.ts b/runtime/tmagic-form/src/useFormConfig.ts index f5850ae0..7332c4ff 100644 --- a/runtime/tmagic-form/src/useFormConfig.ts +++ b/runtime/tmagic-form/src/useFormConfig.ts @@ -9,7 +9,7 @@ import { getElById, getNodePath, replaceChildNode } from '@tmagic/utils'; import { AppProps } from './types'; export const useFormConfig = (props: AppProps) => { - const { contentWindow } = props.stage.renderer; + const { contentWindow } = props.stage.renderer!; const mForm = ref>(); const root = ref();