Skip to content

Upstream parity sweep: xyflow as of 2026-09-24 and the open pull requests #23

Description

@thedanchez

Summary

A parity sweep against the xyflow packages as of 2026-09-24 (React Flow 12.12.0, Svelte Flow 1.7.0, @xyflow/system 0.0.83) and the pull requests open on xyflow/xyflow at that date. Each item names the upstream change so it can be checked off as it lands here. Items are grouped by whether upstream has already shipped them, has them open, or has them in the next major.

Already shipped upstream, to apply now

  • Bump @xyflow/system from 0.0.81 to 0.0.83: NodeResizer always fires onResizeEnd after onResizeStart, and resize values are correct when shouldResize returns false (fix(XYResizer): roll back values when shouldResize returns false xyflow/xyflow#5998), plus the attribution-warning guard (#5997).

  • useKeyPress: key combinations of three or more keys such as Meta+Shift+s (#5929). Not applicable: that fix is to React Flow's string-combo parser; our definitions carry a modifier list ({ key, modifier: ["meta", "shift"] }), which already expresses any number of modifiers.

  • (already present) Pane: prefer touch panning over box selection when selectionOnDrag is combined with mouse-button-specific panOnDrag settings, so no selection box appears when dragging the pane by touch (#5918).

  • screenToFlowPosition: accept a snapGrid override in its options, alongside the existing snapToGrid flag (#5722).

  • A hidden node hides its edges (#5977). The structural expectation was wrong (the measurement root kept the bounds); the edge layout join now drops an edge while either endpoint is hidden, pinned headless and in the DOM.

  • Verify: provider store reset on unmount (#5994). Documented difference kept. Our contract differs on purpose (a hoisted provider keeps its data; the canvas releases the DOM); confirm the documented behavior and leave it.

  • Edge pathOptions reach the built-in edge types (found during the Svelte Flow 2 review): React Flow's EdgeWrapper passes edge.pathOptions through; ours dropped curvature, offset and borderRadius on built-in edges. Fixed, and the smooth-step edge forwards system's stepPosition option too.

Open upstream pull requests, to port once merged (APIs may still move)

  • (waits for a system release: the scaling lives in wheelDelta inside XYPanZoom) zoomSensitivity prop, default 1, scaling zoom per scroll or pinch gesture (#5988; closes upstream #3481, the most-requested open enhancement).
  • deselectOnSelection prop, default true; when false a box drag keeps the existing selection and unions the new one (#5960; closes upstream #2782).
  • isNodeSelectable predicate for box and lasso selection, applied to the candidates getNodesInside returns, with a shared filterSelectedNodes helper in system (#6004).
  • Keyboard-created connections for accessibility: focusable handles and pane, Enter or Space on a handle starts or completes a click connection, Escape cancels, a handlesFocusable prop (default true), and KeyboardEvent accepted by the connect start and end callbacks (#6012; upstream #5620).
  • onNodeMove reporting keyboard node movement (#6011).
  • Check: perf(system) avoiding the style recalc on cursor change (#6029). We already cover the pane with a leaf element instead of changing an inherited cursor; nothing to port unless the system change alters the pane classes we read.

Next majors (React Flow 13, Svelte Flow 2; draft #5866), to evaluate when they ship

Reviewed 2026-09-26. Svelte Flow 2's user-visible changes are: hook parameters as accessors (already our rule), useNode / useEdge and useNodes / useEdges with set / update that route through the new changesets, onnodeschange / onedgeschange receiving NodeChangeset / EdgeChangeset instances, colorMode replaced by CSS-only system mode with forceColorMode, the library CSS on an xyflow cascade layer with style.css and base.css exports, useStore renamed to useSvelteFlowStore, a changeParentNode helper in system, and the Handle geometry type renamed HandleBounds. The same branch adds @xyflow/vue (Vue Flow brought in-house by its author).

Re-reviewed 2026-09-28 against the branch head a6c1ffa (2026-09-24, no commits since the first review), this time reading the public exports of React Flow 13 as well. Prereleases on npm (next tag, all 2026-08-19): @xyflow/svelte 2.0.0-next.3, @xyflow/system 1.0.0-next.3, @xyflow/vue 2.0.0-next.17; React Flow 13 has none yet (the branch still says 12.11.5). Items marked "for next.22" are done locally and ship in 1.0.0-next.22.

  • NodeChangeset / EdgeChangeset classes for the change callbacks. Both React Flow 13 and Svelte Flow 2 export them from system, and React Flow 13 no longer exports applyNodeChanges / applyEdgeChanges; Vue keeps applyChanges. Decide our answer before 1.0 (we have neither).
    • What they are (packages/system/src/changes/index.ts at a6c1ffa): an id-indexed queue of the existing NodeChange / EdgeChange records (add, remove, position, select, dimensions, replace) with reads (getByType, getForNode, iteration, toArray), edits before applying (add, remove(change), removeForNode), and applyTo(nodes), which is applyNodeChanges returning a new array. They live in @xyflow/system 1.0 (prerelease).
    • React Flow 13 keeps its contract: the flow hands the changeset to onNodesChange and the user applies it (setNodes((nodes) => changes.applyTo(nodes))); interception stays in experimental_useOnNodesChangeMiddleware.
    • Svelte Flow 2 queues every flow-initiated write (drags, selection, dimensions, useNode().set) into a pending changeset, calls onnodeschange(pending) BEFORE applying, then applies it itself (initial-store.svelte.ts flushNodeChanges). The callback is a middleware seam: inspect, drop or rewrite changes, and the rest applies.
    • Our model has no change callbacks by design (llms.txt: "NO onNodesChange / onEdgesChange / applyNodeChanges / applyEdgeChanges — state is store-based; mutate your store"). The flow writes per-row draft edits straight into the user's store; that is the drag lead (27 ms script over 60 moves vs 460 for Svelte Flow) and what makes optimistic stores work. Routing writes through change objects and fresh arrays would undo it.
    • What users lack today is one general interception point. The targeted ones exist (onBeforeDelete, isValidConnection, isNodeSelectable, nodeExtent / translateExtent / snapGrid, the drag / delete / selection events), but nothing lets them see or veto every flow-initiated change with its intent: arbitrary drag rules, custom snapping, undo/redo that records "moved" / "selected" instead of diffing the store.
    • Recommendation: no changeset state model. After the @xyflow/system 1.x bump, consider an OPTIONAL Svelte-shaped interception hook: onNodesChange / onEdgesChange receive an upstream NodeChangeset / EdgeChangeset before the flow applies its own writes, may drop or rewrite changes, and the survivors are applied through the existing per-row draft path. Zero cost when the prop is unset; a few change objects per gesture frame when set; the type names match upstream so Svelte Flow 2 code ports. Needs a bench pass on the drag, selection and bulk rows with the prop set, and the llms.txt line rewritten to say the callback observes and filters, it does not own state.
  • useNode / useEdge hooks. Already exported here (useNode(id: Accessor<string>), useEdge alike). React Flow 13 and Svelte Flow 2 take a plain id: string; Vue's useNode(id?) falls back to the node context id and also returns the parent, connected edges and element. Ours stays accessor-shaped (house rule).
  • A helper for changing a node's parent (changeParentNode in system: re-bases the child's position from absolute to the new parent, honoring origin). It reaches users as a changeParent(nodeId, parentId | null) method on useReactFlow / useSvelteFlow, not a top-level export: ours would be a changeParent command on useSolidFlow / FlowCommands, once our system version has the util.
  • colorMode dropped for CSS-only system mode with a forceColorMode override (color-scheme: light dark on the container, --xy-* values through light-dark()), for next.22: theme variables through light-dark() (light text still inherits the page color via currentColor, where upstream uses CanvasText); forceColorMode?: "light" | "dark" replaces colorMode / colorModeSSR; a data-theme attribute on an ancestor forces the scheme, the prop wins over it; useColorMode and flow.colorMode removed (Svelte Flow 2 deletes useColorMode too); ColorMode is "light" | "dark" and ColorModeClass is gone, as in system 1.x; @solid-primitives/media dropped.
  • Library CSS on an xyflow cascade layer with style.css and base.css exports, for next.22. Correction to the first review: there are no new theme values; upstream's next-major CSS differs from 1.x only by the light-dark() conversion. What landed:
    • The stylesheets split into structure (init.css + node resizer) and skins: the default theme (style.css) and the minimal theme (base.css, which React Flow 12 / Svelte Flow 1.7 already ship).
    • Both sit in the xyflow cascade layer; exported as ./style.css, ./base.css, ./dist/style.css, ./dist/base.css (./styles kept as an alias) and ./package.json; sideEffects: ["*.css"] (the published sideEffects: false let webpack drop the stylesheet import: verified against next.21).
    • Theme values that differed from upstream: dark edge labels, dark background pattern colors.
    • Parity fixes from the rule-by-rule comparison: default-color arrowheads (black / invisible without --xy-edge-stroke, xyflow 85c6266a), ViewportPortal (rendered into document.body; now target front / back as in Svelte Flow), panel centering, resizer handle size and centering at zoom (9c2108f4), horizontal Controls borders and the control-button selector, the selected built-in node's border, focus outlines, direction: ltr, pane touch-action: none, the Background color fallback order (346268a5), container user-select: none, <EdgeLabel transparent>.
    • Kept on purpose: panel pointer-events during a box selection stays inline; upstream moved it to .xy-flow__pane.selection .xy-flow__panel, which matches nothing in either upstream library because panels render outside the pane (worth an upstream look).
  • Group nodes (experimental, #5937: a groups module in system and XYDrag changes) together with the removal of expandParent from system and the resizer (#5916). We read expandParent in the measure path; a system bump past that removal will need the group-node model instead.
  • Svelte Flow 2 changes hook parameters to accessors, the rule this port already follows; no action, noted for the docs. Upstream is not consistent yet: Svelte Flow 2's useNode(id: string) takes a plain string while its useInternalNode takes an accessor.
  • Handle geometry type renamed HandleBounds in system (major): our measurement code names it NodeHandleBounds already; check the public re-exports on the bump.
  • useNodes / useEdges gaining set / update (Svelte Flow 2): ours return accessors; decide whether to add write helpers or leave writes to commands.
  • useOnSelectionChange (React Flow and Svelte Flow, current and next majors), for next.22: useOnSelectionChange(onChange) gets { nodes, edges } on mount and on every change of the selected ids, removed with its component; the onSelectionChange prop shares its implementation. useSelectedNodes / useSelectedEdges stay as the reactive form (ours only).
  • NodeResizeControl keeps its name in both next majors and Vue; our ResizeControl is renamed to it, for next.22.
  • useInternalNodes(ids) (React Flow 13): internal nodes for a list of ids, subscribing to those ids only. Ours would take Accessor<readonly string[]>.
  • Not applicable (we export no store hook): store access changes. React Flow 13 replaces useStore / useStoreApi with slice hooks (useOptionsStore, useViewportStore, useConnectionStore, useNodesStore, useEdgesStore, useSelectionStore, useReactFlowStoreApi, useShallow); Svelte Flow 2 renames useStore to useSvelteFlowStore.
  • Not applicable, removed upstream: useHandleConnections (React Flow 13; we never exported it) and colorModeSSR (both).
  • @xyflow/vue extras to consider: useEdgesData(id | ids) (the edge twin of useNodesData, which React and Svelte lack), useHandle (the connect logic without <Handle>), and useNode(id?) / useEdge(id?) falling back to the context id.
  • Also for next.22, while aligning the public surface: the package now exports only the public components (the React Flow / Svelte Flow union; NodeWrapper, EdgeWrapper, the renderers, Pane, Zoom, the *EdgeInternal edges and the rest stay internal), pinned by a runtime export-list test.

Notes

Every item lands with tests first, behind the full gate, and the interaction rows of the private bench are re-run for anything on a gesture path.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions