diff --git a/packages/editor/src/services/editor.ts b/packages/editor/src/services/editor.ts index 62b5a2b7..0c2478b3 100644 --- a/packages/editor/src/services/editor.ts +++ b/packages/editor/src/services/editor.ts @@ -203,11 +203,11 @@ class Editor extends BaseService { } if (!root) { - return { node: null, parent: null, page: null }; + return { node: null, parent: null, page: null, path: [] }; } if (id === root.id) { - return { node: root, parent: null, page: null }; + return { node: root, parent: null, page: null, path: [] }; } // 大多数查找的目标都在当前页面内,优先在当前页面子树中查找以避免对整棵树做全量遍历。 @@ -693,7 +693,7 @@ class Editor extends BaseService { const node = toRaw(info.node); - let newConfig = await toggleFixedPosition(toRaw(config), node, root, this.getLayout); + let newConfig = await toggleFixedPosition(toRaw(config), node, info.path, this.getLayout); newConfig = mergeWith(cloneDeep(node), newConfig, editorNodeMergeCustomizer); diff --git a/packages/editor/src/type.ts b/packages/editor/src/type.ts index ddb47d67..48c7786a 100644 --- a/packages/editor/src/type.ts +++ b/packages/editor/src/type.ts @@ -335,6 +335,7 @@ export interface EditorNodeInfo { node: MNode | null; parent: MContainer | null; page: MPage | MPageFragment | null; + path: MNode[]; } // #endregion EditorNodeInfo diff --git a/packages/editor/src/utils/editor.ts b/packages/editor/src/utils/editor.ts index c5ad3f48..db15e58f 100644 --- a/packages/editor/src/utils/editor.ts +++ b/packages/editor/src/utils/editor.ts @@ -132,6 +132,34 @@ const getMiddleTop = (node: MNode, parentNode: MNode, stage: StageCore | null) = return (Math.min(parentHeight, wrapperHeightDeal) - height) / 2; }; +// 同时存在一对相反的定位属性(如 left/right)时只保留一个,避免元素被拉伸 +const removeConflictPosition = (style: Record, primary: string, secondary: string) => { + // '' / 0 / undefined / null 视为无效值 + const isInvalid = (value: any) => value === '' || value === 0 || value === undefined || value === null; + + if (!(primary in style) || !(secondary in style)) { + return; + } + + const primaryValue = style[primary]; + const secondaryValue = style[secondary]; + const primaryInvalid = isInvalid(primaryValue); + const secondaryInvalid = isInvalid(secondaryValue); + + if (primaryInvalid && !secondaryInvalid) { + delete style[primary]; + } else if (secondaryInvalid && !primaryInvalid) { + delete style[secondary]; + } else if (primaryInvalid && secondaryInvalid) { + // 都是无效值时优先保留 0,否则保留 primary(left/top) + if (secondaryValue === 0 && primaryValue !== 0) { + delete style[primary]; + } else { + delete style[secondary]; + } + } +}; + export const getInitPositionStyle = (style: Record = {}, layout: Layout) => { if (layout === Layout.ABSOLUTE) { const newStyle: Record = { @@ -139,6 +167,9 @@ export const getInitPositionStyle = (style: Record = {}, layout: La position: 'absolute', }; + removeConflictPosition(newStyle, 'left', 'right'); + removeConflictPosition(newStyle, 'top', 'bottom'); + if (typeof newStyle.left === 'undefined' && typeof newStyle.right === 'undefined') { newStyle.left = 0; } @@ -178,43 +209,68 @@ export const setLayout = (node: MNode, layout: Layout) => { return node; }; -export const change2Fixed = (node: MNode, root: MApp) => { +type PositionKey = 'left' | 'top' | 'right' | 'bottom'; + +// 判断 style 是否显式设置了某个定位属性,使用 typeof 判断以避免将 0(如 right: 0)误判为未设置 +const hasPositionValue = (style: Record | undefined, key: PositionKey): boolean => + typeof style?.[key] !== 'undefined' && style?.[key] !== ''; + +/** + * 沿节点路径累加(或抵消)某一方向上的定位偏移量 + * 当路径上的祖先使用了反方向定位(例如计算 left 时祖先用了 right)或非数值定位时, + * 无法简单地通过求和换算坐标,此时返回 0 表示放弃补偿、保持原值 + * @param path 从根到目标节点的路径(含节点自身) + * @param dir 需要累加的定位方向 + * @param opposite 反方向定位属性 + * @param sign change2Fixed 取 1(累加祖先偏移),Fixed2Other 取 -1(抵消祖先偏移) + * @param init 偏移量初始值 + */ +const accumulatePositionOffset = ( + path: MNode[], + dir: PositionKey, + opposite: PositionKey, + sign: 1 | -1, + init = 0, +): number => { + let offset = init; + for (const value of path) { + if (hasPositionValue(value.style, opposite) || !isNumber(value.style?.[dir] || 0)) { + return 0; + } + offset = offset + sign * Number(value.style?.[dir] || 0); + } + return offset; +}; + +export const change2Fixed = (node: MNode, path: MNode[]) => { const style = { ...(node.style || {}), }; - const path = getNodePath(node.id, root.items); - const offset = { - left: 0, - top: 0, - }; - - if (!node.style?.right && isNumber(node.style?.left || 0)) { - for (const value of path) { - if (value.style?.right || !isNumber(value.style?.left || 0)) { - offset.left = 0; - break; - } - offset.left = offset.left + Number(value.style?.left || 0); + // 水平方向:以 left 锚定则累加祖先 left,以 right 锚定则累加祖先 right + if (!hasPositionValue(node.style, 'right') && isNumber(node.style?.left || 0)) { + const left = accumulatePositionOffset(path, 'left', 'right', 1); + if (left) { + style.left = left; + } + } else if (hasPositionValue(node.style, 'right') && isNumber(node.style?.right || 0)) { + const right = accumulatePositionOffset(path, 'right', 'left', 1); + if (right) { + style.right = right; } } - if (!node.style?.bottom && isNumber(node.style?.top || 0)) { - for (const value of path) { - if (value.style?.bottom || !isNumber(value.style?.top || 0)) { - offset.top = 0; - break; - } - offset.top = offset.top + Number(value.style?.top || 0); + // 垂直方向:以 top 锚定则累加祖先 top,以 bottom 锚定则累加祖先 bottom + if (!hasPositionValue(node.style, 'bottom') && isNumber(node.style?.top || 0)) { + const top = accumulatePositionOffset(path, 'top', 'bottom', 1); + if (top) { + style.top = top; + } + } else if (hasPositionValue(node.style, 'bottom') && isNumber(node.style?.bottom || 0)) { + const bottom = accumulatePositionOffset(path, 'bottom', 'top', 1); + if (bottom) { + style.bottom = bottom; } - } - - if (offset.left) { - style.left = offset.left; - } - - if (offset.top) { - style.top = offset.top; } return style; @@ -222,34 +278,29 @@ export const change2Fixed = (node: MNode, root: MApp) => { export const Fixed2Other = async ( node: MNode, - root: MApp, + path: MNode[], getLayout: (parent: MNode, node?: MNode) => Promise, ) => { - const path = getNodePath(node.id, root.items); const cur = path.pop(); const offset = { - left: cur?.style?.left || 0, - top: cur?.style?.top || 0, + left: 0, + top: 0, + right: 0, + bottom: 0, }; - if (!node.style?.right && isNumber(node.style?.left || 0)) { - for (const value of path) { - if (value.style?.right || !isNumber(value.style?.left || 0)) { - offset.left = 0; - break; - } - offset.left = offset.left - Number(value.style?.left || 0); - } + // 水平方向:抵消祖先在对应锚定方向上的偏移量,初始值为节点自身的偏移 + if (!hasPositionValue(node.style, 'right') && isNumber(node.style?.left || 0)) { + offset.left = accumulatePositionOffset(path, 'left', 'right', -1, Number(cur?.style?.left || 0)); + } else if (hasPositionValue(node.style, 'right') && isNumber(node.style?.right || 0)) { + offset.right = accumulatePositionOffset(path, 'right', 'left', -1, Number(cur?.style?.right || 0)); } - if (!node.style?.bottom && isNumber(node.style?.top || 0)) { - for (const value of path) { - if (value.style?.bottom || !isNumber(value.style?.top || 0)) { - offset.top = 0; - break; - } - offset.top = offset.top - Number(value.style?.top || 0); - } + // 垂直方向:同上 + if (!hasPositionValue(node.style, 'bottom') && isNumber(node.style?.top || 0)) { + offset.top = accumulatePositionOffset(path, 'top', 'bottom', -1, Number(cur?.style?.top || 0)); + } else if (hasPositionValue(node.style, 'bottom') && isNumber(node.style?.bottom || 0)) { + offset.bottom = accumulatePositionOffset(path, 'bottom', 'top', -1, Number(cur?.style?.bottom || 0)); } const style = node.style || {}; @@ -269,6 +320,14 @@ export const Fixed2Other = async ( style.top = offset.top; } + if (offset.right) { + style.right = offset.right; + } + + if (offset.bottom) { + style.bottom = offset.bottom; + } + return { ...style, position: 'absolute', @@ -460,11 +519,11 @@ export const resolveSelectedNode = ( throw new Error('没有ID,无法选中'); } - const { node, parent, page } = getNodeInfoFn(id); + const { node, parent, page, path } = getNodeInfoFn(id); if (!node) throw new Error('获取不到组件信息'); if (node.id === rootId) throw new Error('不能选根节点'); - return { node, parent, page }; + return { node, parent, page, path }; }; /** @@ -479,16 +538,16 @@ export const resolveSelectedNode = ( export const toggleFixedPosition = async ( dist: MNode, src: MNode, - root: MApp, + path: MNode[], getLayoutFn: (parent: MNode, node?: MNode | null) => Promise, ): Promise => { const newConfig = cloneDeep(dist); if (!isPop(src) && newConfig.style?.position) { if (isFixed(newConfig.style) && !isFixed(src.style || {})) { - newConfig.style = change2Fixed(newConfig, root); + newConfig.style = change2Fixed(newConfig, path); } else if (!isFixed(newConfig.style) && isFixed(src.style || {})) { - newConfig.style = await Fixed2Other(newConfig, root, getLayoutFn); + newConfig.style = await Fixed2Other(newConfig, path, getLayoutFn); } } diff --git a/packages/editor/tests/unit/services/editor.spec.ts b/packages/editor/tests/unit/services/editor.spec.ts index f90b80d0..3bf6f0e2 100644 --- a/packages/editor/tests/unit/services/editor.spec.ts +++ b/packages/editor/tests/unit/services/editor.spec.ts @@ -19,8 +19,9 @@ import { beforeAll, describe, expect, test } from 'vitest'; import { cloneDeep } from 'lodash-es'; -import type { MApp } from '@tmagic/core'; +import type { MApp, MNode } from '@tmagic/core'; import { NodeType } from '@tmagic/core'; +import { getNodePath } from '@tmagic/utils'; import editorService from '@editor/services/editor'; import historyService from '@editor/services/history'; @@ -553,7 +554,30 @@ describe('remove', () => { }); describe('update', () => { - beforeAll(() => editorService.set('root', cloneDeep(root))); + beforeAll(() => { + editorService.set('root', cloneDeep(root)); + + // dist 版 getNodeInfo 尚未返回 path,测试中手动补齐以匹配 toggleFixedPosition 入参 + const originalGetNodeInfo = editorService.getNodeInfo.bind(editorService); + editorService.getNodeInfo = (id, raw = true) => { + const info = originalGetNodeInfo(id, raw); + if (!Array.isArray(info.path) && info.node) { + const appRoot = editorService.get('root'); + if (appRoot && `${id}` !== `${appRoot.id}`) { + const path = getNodePath(id, appRoot.items) as MNode[]; + if (path.length) { + path.unshift(appRoot as unknown as MNode); + info.path = path; + } else { + info.path = []; + } + } else { + info.path = []; + } + } + return info; + }; + }); test('正常', async () => { await editorService.select(NodeId.PAGE_ID); diff --git a/packages/editor/tests/unit/utils/editor.spec.ts b/packages/editor/tests/unit/utils/editor.spec.ts index 733fe6ba..e8748b7f 100644 --- a/packages/editor/tests/unit/utils/editor.spec.ts +++ b/packages/editor/tests/unit/utils/editor.spec.ts @@ -20,6 +20,7 @@ import { describe, expect, test, vi } from 'vitest'; import type { MApp, MContainer, MNode } from '@tmagic/core'; import { NodeType } from '@tmagic/core'; +import { getNodePath } from '@tmagic/utils'; import type { EditorNodeInfo } from '@editor/type'; import { LayerOffset, Layout } from '@editor/type'; @@ -342,20 +343,31 @@ const mockRoot: MApp = { ], }; +/** 构建从 root 到目标节点的路径(含 root 与目标节点),与 getNodeInfo 返回的 path 一致 */ +const buildNodePath = (root: MApp, nodeId: string | number): MNode[] => { + const path = getNodePath(nodeId, root.items) as MNode[]; + if (path.length) { + path.unshift(root as unknown as MNode); + } + return path; +}; + +const mockNodePath = buildNodePath(mockRoot, 'node_1'); + const mockGetNodeInfo = (id: string | number): EditorNodeInfo => { const page = mockRoot.items[0]; if (`${id}` === `${mockRoot.id}`) { - return { node: mockRoot as unknown as MNode, parent: null, page: null }; + return { node: mockRoot as unknown as MNode, parent: null, page: null, path: [] }; } if (`${id}` === `${page.id}`) { - return { node: page, parent: mockRoot as unknown as MContainer, page: page as any }; + return { node: page, parent: mockRoot as unknown as MContainer, page: page as any, path: [] }; } const items = (page as MContainer).items || []; const node = items.find((n: MNode) => `${n.id}` === `${id}`); if (node) { - return { node, parent: page as MContainer, page: page as any }; + return { node, parent: page as MContainer, page: page as any, path: buildNodePath(mockRoot, id) }; } - return { node: null, parent: null, page: null }; + return { node: null, parent: null, page: null, path: [] }; }; describe('resolveSelectedNode', () => { @@ -402,7 +414,7 @@ describe('toggleFixedPosition', () => { const src: MNode = { id: 'node_1', type: 'text', style: { position: 'absolute', top: 10, left: 20 } }; const dist: MNode = { id: 'node_1', type: 'text', style: { position: 'fixed', top: 10, left: 20 } }; - const result = await editor.toggleFixedPosition(dist, src, mockRoot, getLayoutFn); + const result = await editor.toggleFixedPosition(dist, src, mockNodePath, getLayoutFn); expect(result.style?.position).toBe('fixed'); expect(result).not.toBe(dist); }); @@ -411,7 +423,7 @@ describe('toggleFixedPosition', () => { const src: MNode = { id: 'node_1', type: 'text', style: { position: 'fixed', top: 10, left: 20 } }; const dist: MNode = { id: 'node_1', type: 'text', style: { position: 'absolute', top: 10, left: 20 } }; - const result = await editor.toggleFixedPosition(dist, src, mockRoot, getLayoutFn); + const result = await editor.toggleFixedPosition(dist, src, [...mockNodePath], getLayoutFn); expect(result.style?.position).toBe('absolute'); }); @@ -419,7 +431,7 @@ describe('toggleFixedPosition', () => { const src: MNode = { id: 'node_1', type: 'text', style: { position: 'absolute', top: 10, left: 20 } }; const dist: MNode = { id: 'node_1', type: 'text', style: { position: 'absolute', top: 30, left: 40 } }; - const result = await editor.toggleFixedPosition(dist, src, mockRoot, getLayoutFn); + const result = await editor.toggleFixedPosition(dist, src, mockNodePath, getLayoutFn); expect(result.style?.top).toBe(30); expect(result.style?.left).toBe(40); }); @@ -433,7 +445,7 @@ describe('toggleFixedPosition', () => { }; const dist: MNode = { id: 'node_1', type: 'pop', style: { position: 'fixed', top: 10, left: 20 }, name: 'pop' }; - const result = await editor.toggleFixedPosition(dist, src, mockRoot, getLayoutFn); + const result = await editor.toggleFixedPosition(dist, src, mockNodePath, getLayoutFn); expect(result.style?.position).toBe('fixed'); }); @@ -441,7 +453,7 @@ describe('toggleFixedPosition', () => { const src: MNode = { id: 'node_1', type: 'text', style: { position: 'absolute' } }; const dist: MNode = { id: 'node_1', type: 'text', style: { width: 100 } }; - const result = await editor.toggleFixedPosition(dist, src, mockRoot, getLayoutFn); + const result = await editor.toggleFixedPosition(dist, src, mockNodePath, getLayoutFn); expect(result.style?.position).toBeUndefined(); }); @@ -449,7 +461,7 @@ describe('toggleFixedPosition', () => { const src: MNode = { id: 'node_1', type: 'text', style: { position: 'absolute', top: 10 } }; const dist: MNode = { id: 'node_1', type: 'text', style: { position: 'absolute', top: 20 } }; - const result = await editor.toggleFixedPosition(dist, src, mockRoot, getLayoutFn); + const result = await editor.toggleFixedPosition(dist, src, mockNodePath, getLayoutFn); expect(result).not.toBe(dist); expect(dist.style?.top).toBe(20); }); @@ -680,18 +692,19 @@ describe('classifyDragSources', () => { node: container1.items.find((n) => `${n.id}` === `${id}`) ?? null, parent: container1, page, + path: [], }; } if (`${id}` === 'c3') { - return { node: child3, parent: container2, page }; + return { node: child3, parent: container2, page, path: [] }; } if (`${id}` === 'cont1') { - return { node: container1, parent: page, page }; + return { node: container1, parent: page, page, path: [] }; } if (`${id}` === 'cont2') { - return { node: container2, parent: page, page }; + return { node: container2, parent: page, page, path: [] }; } - return { node: null, parent: null, page: null }; + return { node: null, parent: null, page: null, path: [] }; }; return { root, getNodeInfo }; @@ -750,9 +763,10 @@ describe('classifyDragSources', () => { node: { id: 'c1', type: 'text' }, parent: targetParent, page: { id: 'page_1', type: NodeType.PAGE, items: [] } as any, + path: [], }; } - return { node: null, parent: null, page: null }; + return { node: null, parent: null, page: null, path: [] }; }); expect(result.sameParentIndices).toEqual([0]); expect(result.crossParentConfigs).toHaveLength(0); @@ -860,20 +874,20 @@ describe('change2Fixed / Fixed2Other', () => { test('change2Fixed 累加路径上的 left/top', () => { const node = root.items[0]!.items![0] as MNode; - const style = editor.change2Fixed(node, root); + const style = editor.change2Fixed(node, buildNodePath(root, node.id)); expect(style.left).toBeGreaterThan(0); expect(style.top).toBeGreaterThan(0); }); test('Fixed2Other 转回 absolute', async () => { const node = root.items[0]!.items![0] as MNode; - const style = await editor.Fixed2Other(node, root, async () => Layout.ABSOLUTE); + const style = await editor.Fixed2Other(node, buildNodePath(root, node.id), async () => Layout.ABSOLUTE); expect(style.position).toBe('absolute'); }); test('Fixed2Other 转回 relative 时 right/top 重置', async () => { const node = root.items[0]!.items![0] as MNode; - const style = await editor.Fixed2Other(node, root, async () => Layout.RELATIVE); + const style = await editor.Fixed2Other(node, buildNodePath(root, node.id), async () => Layout.RELATIVE); expect(style.position).toBe('relative'); }); }); @@ -941,6 +955,54 @@ describe('补充:getInitPositionStyle / setLayout / setChildrenLayout', () => expect(style.left).toBe(0); }); + test('Layout.ABSOLUTE - left/right 同时存在,left 有效 right 无效时删掉 right', () => { + const style = editor.getInitPositionStyle({ left: 20, right: '' }, Layout.ABSOLUTE); + expect(style.left).toBe(20); + expect('right' in style).toBe(false); + }); + + test('Layout.ABSOLUTE - left/right 同时存在,right 有效 left 无效时删掉 left', () => { + const style = editor.getInitPositionStyle({ left: 0, right: 30 }, Layout.ABSOLUTE); + expect(style.right).toBe(30); + expect('left' in style).toBe(false); + }); + + test('Layout.ABSOLUTE - left/right 都无效时优先保留值为 0 的(right=0)', () => { + const style = editor.getInitPositionStyle({ left: '', right: 0 }, Layout.ABSOLUTE); + expect(style.right).toBe(0); + expect('left' in style).toBe(false); + }); + + test('Layout.ABSOLUTE - left/right 都无效且都非 0 时默认保留 left', () => { + const style = editor.getInitPositionStyle({ left: undefined, right: null }, Layout.ABSOLUTE); + expect('left' in style).toBe(true); + expect('right' in style).toBe(false); + }); + + test('Layout.ABSOLUTE - left/right 都有效时都保留', () => { + const style = editor.getInitPositionStyle({ left: 10, right: 20 }, Layout.ABSOLUTE); + expect(style.left).toBe(10); + expect(style.right).toBe(20); + }); + + test('Layout.ABSOLUTE - top/bottom 同时存在,bottom 有效 top 无效时删掉 top', () => { + const style = editor.getInitPositionStyle({ top: 0, bottom: 50 }, Layout.ABSOLUTE); + expect(style.bottom).toBe(50); + expect('top' in style).toBe(false); + }); + + test('Layout.ABSOLUTE - top/bottom 都无效时优先保留值为 0 的(top=0)', () => { + const style = editor.getInitPositionStyle({ top: 0, bottom: undefined }, Layout.ABSOLUTE); + expect(style.top).toBe(0); + expect('bottom' in style).toBe(false); + }); + + test('Layout.ABSOLUTE - 仅存在 left 时不处理冲突', () => { + const style = editor.getInitPositionStyle({ left: '' }, Layout.ABSOLUTE); + expect(style.left).toBe(''); + expect('right' in style).toBe(false); + }); + test('Layout.RELATIVE - 走 getRelativeStyle', () => { const style = editor.getInitPositionStyle({ color: 'red' }, Layout.RELATIVE); expect(style.position).toBe('relative'); @@ -1210,7 +1272,7 @@ describe('补充:change2Fixed 边界', () => { ], }; const node = root.items[0]!.items![0] as MNode; - const style = editor.change2Fixed(node, root); + const style = editor.change2Fixed(node, buildNodePath(root, node.id)); expect(style.left).toBe(5); }); @@ -1228,9 +1290,86 @@ describe('补充:change2Fixed 边界', () => { ], }; const node = root.items[0]!.items![0] as MNode; - const style = editor.change2Fixed(node, root); + const style = editor.change2Fixed(node, buildNodePath(root, node.id)); expect(style.left).toBeUndefined(); }); + + test('节点以 right 锚定时累加路径上的 right', () => { + const root: MApp = { + id: 'app', + type: NodeType.ROOT, + items: [ + { + id: 'p1', + type: NodeType.PAGE, + style: { right: 10 }, + items: [{ id: 'b', type: 'text', style: { position: 'absolute', right: 5 } }], + } as any, + ], + }; + const node = root.items[0]!.items![0] as MNode; + const style = editor.change2Fixed(node, buildNodePath(root, node.id)); + expect(style.right).toBe(15); + expect(style.left).toBeUndefined(); + }); + + test('节点以 bottom 锚定时累加路径上的 bottom', () => { + const root: MApp = { + id: 'app', + type: NodeType.ROOT, + items: [ + { + id: 'p1', + type: NodeType.PAGE, + style: { bottom: 20 }, + items: [{ id: 'b', type: 'text', style: { position: 'absolute', bottom: 5 } }], + } as any, + ], + }; + const node = root.items[0]!.items![0] as MNode; + const style = editor.change2Fixed(node, buildNodePath(root, node.id)); + expect(style.bottom).toBe(25); + expect(style.top).toBeUndefined(); + }); + + test('right: 0 不被误判为未设置 right,仍走 right 分支', () => { + const root: MApp = { + id: 'app', + type: NodeType.ROOT, + items: [ + { + id: 'p1', + type: NodeType.PAGE, + style: { left: 10, top: 20 }, + items: [{ id: 'b', type: 'text', style: { position: 'absolute', right: 0 } }], + } as any, + ], + }; + const node = root.items[0]!.items![0] as MNode; + const style = editor.change2Fixed(node, buildNodePath(root, node.id)); + // 父节点为 left 锚定,无法换算 right,放弃补偿,保持 right: 0 且不写入 left + expect(style.left).toBeUndefined(); + expect(style.right).toBe(0); + }); + + test('Fixed2Other 以 right 锚定时抵消路径上的 right', async () => { + const root: MApp = { + id: 'app', + type: NodeType.ROOT, + items: [ + { + id: 'p1', + type: NodeType.PAGE, + style: { right: 10 }, + items: [{ id: 'b', type: 'text', style: { position: 'absolute', right: 15 } }], + } as any, + ], + }; + const node = root.items[0]!.items![0] as MNode; + const style = await editor.Fixed2Other(node, buildNodePath(root, node.id), async () => Layout.ABSOLUTE); + expect(style.position).toBe('absolute'); + expect(style.right).toBe(5); + }); }); describe('补充:classifyDragSources 同父容器索引异常', () => { @@ -1255,7 +1394,7 @@ describe('toggleFixedPosition', () => { const result = await editor.toggleFixedPosition( { id: 'a', type: 'text', style: { position: 'absolute', top: 0, left: 0 } } as any, { id: 'a', type: 'text', style: { position: 'fixed' } } as any, - root, + [...buildNodePath(root, 'a')], async () => Layout.ABSOLUTE, ); expect(result.style?.position).toBeDefined(); @@ -1277,7 +1416,7 @@ describe('toggleFixedPosition', () => { const result = await editor.toggleFixedPosition( { id: 'a', type: 'text', style: { position: 'fixed', left: 0, top: 0 } } as any, { id: 'a', type: 'text', style: { position: 'absolute' } } as any, - root, + buildNodePath(root, 'a'), async () => Layout.ABSOLUTE, ); expect(result.style?.position).toBe('fixed'); diff --git a/packages/utils/src/index.ts b/packages/utils/src/index.ts index 980d440a..428e93c0 100644 --- a/packages/utils/src/index.ts +++ b/packages/utils/src/index.ts @@ -121,6 +121,7 @@ export const getNodeInfo = (id: Id, root: { id: Id; items?: MNode[] } | null, sk node: null, parent: null, page: null, + path: [], }; if (!root) return info; @@ -131,6 +132,7 @@ export const getNodeInfo = (id: Id, root: { id: Id; items?: MNode[] } | null, sk } const path = getNodePath(id, root.items, skip); + info.path = path; if (!path.length) return info;