diff --git a/frontend/src/components/canvas/DiagramCanvas.vue b/frontend/src/components/canvas/DiagramCanvas.vue index c0655d7..67e0717 100644 --- a/frontend/src/components/canvas/DiagramCanvas.vue +++ b/frontend/src/components/canvas/DiagramCanvas.vue @@ -75,7 +75,10 @@ const isWhiteboard = computed(() => activeType.value === 'whiteboard') // strategy, so activeType would read 'block'. On a unified doc the shared block // substrate and the whiteboard layer compose over one canvas (the auto-layout // types become frames in a later phase); legacy single-type docs are unchanged. -const isUnified = computed(() => isUnifiedDocument(store.state)) +// A unified doc renders the composed canvas — EXCEPT while a frame is focused for +// editing, when the mode strategy is overridden to that sub-model's type and the +// editor renders as its single-type editor (so isUnified goes false here). +const isUnified = computed(() => isUnifiedDocument(store.state) && !editorUi.state.focusedFrame) const showBlockLayer = computed(() => !rendersOwnLayer.value || isUnified.value) const mindmapLayout = computed(() => @@ -379,6 +382,14 @@ function openAtActualSize() { let resizeObserver = null +// Entering/leaving a frame's focus mode reframes the view: on enter, fit the now +// single-type content (the frame); on exit, fit the unified canvas. Without this +// the frame renders at its own coords, off-screen from where the frame sat. +watch( + () => editorUi.state.focusedFrame, + () => nextTick(fitToView), +) + onMounted(() => { openAtActualSize() // Route editorUi.fit() (bottom-left control + ⇧1 shortcut) through fitToView so @@ -835,6 +846,7 @@ const surfaceCursor = computed(() => { :stroke-width="selectedFrame === 'mindmap' ? 1.5 : 0" stroke-dasharray="6 4" style="cursor: move" @pointerdown.stop="startFrameDrag('mindmap', $event)" + @dblclick.stop="editorUi.setFocusedFrame('mindmap')" /> @@ -849,6 +861,7 @@ const surfaceCursor = computed(() => { :stroke-width="selectedFrame === 'flowchart' ? 1.5 : 0" stroke-dasharray="6 4" style="cursor: move" @pointerdown.stop="startFrameDrag('flowchart', $event)" + @dblclick.stop="editorUi.setFocusedFrame('flowchart')" /> diff --git a/frontend/src/pages/EditorShell.vue b/frontend/src/pages/EditorShell.vue index ccb9ff6..07c5bbd 100644 --- a/frontend/src/pages/EditorShell.vue +++ b/frontend/src/pages/EditorShell.vue @@ -19,6 +19,7 @@ import { useCollaboration } from '@/composables/useCollaboration.js' import { useAppSettings } from '@/composables/useAppSettings.js' import TopToolbar from '@/components/toolbar/TopToolbar.vue' import DiagramCanvas from '@/components/canvas/DiagramCanvas.vue' +import LucideIcon from '@/icons/LucideIcon.vue' import Minimap from '@/components/canvas/Minimap.vue' import WhiteboardMinimap from '@/components/canvas/WhiteboardMinimap.vue' import MindMapOverlay from '@/components/canvas/MindMapOverlay.vue' @@ -41,8 +42,13 @@ const editorUi = createEditorUi() provideDiagramStore(store) provideEditorUi(editorUi) -// Active mode module for this diagram's type (spec diagram-types §0/G1). -const modeStrategy = computed(() => getModeStrategy(store.state.diagramType)) +// Active mode module for this diagram's type (spec diagram-types §0/G1). On the +// unified canvas, entering a frame (editorUi.state.focusedFrame) overrides the +// strategy to that sub-model's type, so the whole editor becomes its single-type +// editor (full node editing/keyboard/toolbar) until "Back to canvas". +const modeStrategy = computed(() => + getModeStrategy(editorUi.state.focusedFrame || store.state.diagramType), +) provideModeStrategy(modeStrategy) // Surface-interaction delegation seam (spec diagram-types Part G1/G4). The active @@ -128,6 +134,16 @@ onMounted(() => {
+ + diff --git a/frontend/src/stores/useEditorUi.js b/frontend/src/stores/useEditorUi.js index 773c4cc..05d9db4 100644 --- a/frontend/src/stores/useEditorUi.js +++ b/frontend/src/stores/useEditorUi.js @@ -20,6 +20,10 @@ export function createEditorUi() { infiniteCanvas: true, // The selected section id (chrome — sections aren't part of shape selection). selectedSectionId: null, + // Unified canvas: the frame being edited in focus mode ('mindmap'|'flowchart' + // |null). While set, the editor behaves as that sub-model's single-type editor + // (full node editing/keyboard/toolbar); "Back to canvas" clears it. + focusedFrame: null, // True for a short window after a layout op (tidy / flip) so node positions // tween instead of jumping (spec 17.1). Off during free drag → no lag. animateLayout: false, @@ -48,6 +52,11 @@ function assembleUi(state, viewport) { // Switching to draw remembers the chosen shape so the tool can be re-armed. function attachTools(ui, state) { ui.setTool = (tool) => (state.tool = tool) + // Enter/leave a frame's focused single-type editing mode (unified canvas). + ui.setFocusedFrame = (kind) => { + state.focusedFrame = kind || null + state.tool = 'select' + } ui.setDrawShape = (type) => { state.drawShapeType = type state.lastShapeType = type