From d2838ba8eefa0920d7a432758c16827663e795d4 Mon Sep 17 00:00:00 2001 From: robonen Date: Tue, 11 Aug 2026 03:22:28 +0700 Subject: [PATCH] fix(primitives): flow renders visibly by default and its declared events fire MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - pane fills its parent instead of collapsing to the 0px strip every consumer debugged as a data bug; background/viewport/panel get a default stacking triple (0/1/2) so chrome no longer paints over nodes - nodeClick/edgeClick/paneClick were declared in FlowRootEmits but never emitted — wired for real; nodeDoubleClick synthesized in the drag layer (it already tells clicks from drags), and dblclick-zoom ignores [data-flow-node] so opening a node no longer also zooms the canvas - FlowEdge.label was typed but never rendered — the default edge now draws a haloed midpoint label, and label joins the v-memo keys so edits are not frozen by the memo - fitViewOnMount prop fits once nodes AND the pane are measured (either can finish first), skipped when the viewport is controlled Co-Authored-By: Claude Opus 5 (1M context) --- .../src/canvas/flow/FlowBackground.vue | 2 +- vue/primitives/src/canvas/flow/FlowEdge.vue | 18 +- vue/primitives/src/canvas/flow/FlowPane.vue | 15 +- vue/primitives/src/canvas/flow/FlowPanel.vue | 4 +- vue/primitives/src/canvas/flow/FlowRoot.vue | 57 +++++- .../src/canvas/flow/FlowViewport.vue | 3 + .../src/canvas/flow/__test__/Fixes.test.ts | 191 ++++++++++++++++++ .../canvas/flow/composables/useNodeDrag.ts | 24 +++ .../src/canvas/flow/composables/usePanZoom.ts | 4 +- vue/primitives/src/canvas/flow/context.ts | 4 + 10 files changed, 315 insertions(+), 7 deletions(-) create mode 100644 vue/primitives/src/canvas/flow/__test__/Fixes.test.ts diff --git a/vue/primitives/src/canvas/flow/FlowBackground.vue b/vue/primitives/src/canvas/flow/FlowBackground.vue index dd86c77..38ede46 100644 --- a/vue/primitives/src/canvas/flow/FlowBackground.vue +++ b/vue/primitives/src/canvas/flow/FlowBackground.vue @@ -54,7 +54,7 @@ const linePath = computed(() => { diff --git a/vue/primitives/src/canvas/flow/FlowPane.vue b/vue/primitives/src/canvas/flow/FlowPane.vue index 43f3737..6bf6204 100644 --- a/vue/primitives/src/canvas/flow/FlowPane.vue +++ b/vue/primitives/src/canvas/flow/FlowPane.vue @@ -65,8 +65,10 @@ useKeyboard(currentElement, ctx, useViewportApi(ctx)); useEventListener(currentElement, 'click', (event: MouseEvent) => { const target = event.target as Element | null; - if (target && !target.closest('[data-flow-node],[data-flow-edge]')) + if (target && !target.closest('[data-flow-node],[data-flow-edge]')) { ctx.clearSelection(); + ctx.emitPaneClick(event as PointerEvent); + } }); @@ -79,7 +81,16 @@ useEventListener(currentElement, 'click', (event: MouseEvent) => { :data-interactive="ctx.interactive.value ? '' : undefined" :role="ctx.disableKeyboardA11y.value ? undefined : 'application'" :tabindex="ctx.disableKeyboardA11y.value ? undefined : 0" - :style="{ position: 'relative', overflow: 'hidden', touchAction: 'none' }" + :style="{ + position: 'relative', + overflow: 'hidden', + touchAction: 'none', + // Everything inside is absolutely positioned, so content-sizing always + // collapsed to 0×N and the graph rendered into an invisible strip. + // Vue merges a consumer's style attr over this, so it stays overridable. + width: '100%', + height: '100%', + }" > diff --git a/vue/primitives/src/canvas/flow/FlowPanel.vue b/vue/primitives/src/canvas/flow/FlowPanel.vue index 7e44c53..ca8040f 100644 --- a/vue/primitives/src/canvas/flow/FlowPanel.vue +++ b/vue/primitives/src/canvas/flow/FlowPanel.vue @@ -28,7 +28,9 @@ const { forwardRef } = useForwardExpose(); const style = computed(() => { const [v, h] = position.split('-') as ['top' | 'bottom', 'left' | 'center' | 'right']; - const s: CSSProperties = { position: 'absolute', pointerEvents: 'all' }; + // Above the viewport's explicit layer (zIndex 1): a positioned sibling + // with z-index auto would otherwise paint underneath the graph. + const s: CSSProperties = { position: 'absolute', pointerEvents: 'all', zIndex: 2 }; s[v] = '0'; if (h === 'center') { s.left = '50%'; diff --git a/vue/primitives/src/canvas/flow/FlowRoot.vue b/vue/primitives/src/canvas/flow/FlowRoot.vue index d3057da..0677829 100644 --- a/vue/primitives/src/canvas/flow/FlowRoot.vue +++ b/vue/primitives/src/canvas/flow/FlowRoot.vue @@ -73,6 +73,14 @@ export interface FlowRootProps extends PrimitiveProps { isValidConnection?: IsValidConnection; /** Cull nodes/edges outside the viewport — for large graphs. @default false */ onlyRenderVisibleElements?: boolean; + /** + * Frame the whole graph once after the initial nodes are measured. Skipped + * when an explicit `viewport` / `defaultViewport` is provided — a restored + * viewport must not be stomped by a fit. With virtualization the fit uses + * whatever is measured plus declared node sizes; fully unmeasured nodes are + * framed by position alone. @default false + */ + fitViewOnMount?: boolean | FitViewParams; /** Extra px kept rendered around the viewport when virtualizing. @default 200 */ virtualizationBuffer?: number; } @@ -87,12 +95,13 @@ export interface FlowRootEmits { selectionChange: [selection: { nodes: string[]; edges: string[] }]; paneClick: [event: PointerEvent]; nodeClick: [id: string, event: PointerEvent]; + nodeDoubleClick: [id: string, event: PointerEvent]; edgeClick: [id: string, event: PointerEvent]; }