release: v19.0.0 — control schemes, connection flows, accessibility, unified connector#314
Merged
Merged
Conversation
…ossing prepareDragSequence() ran against the pointermove that crossed the 3px drag threshold, so onPointerDownPosition was captured several px from the grab point and the first move was deferred to the next pointermove. On a fast flick this offset the dragged item from the cursor for the entire drag and produced a visible 20-50px lag before the item snapped to catch up (#309). Store the original pointer-down event and prepare the drag against it, then apply the threshold-crossing move within the same event. Node dragging, canvas panning, resize, rotate and connection creation now track the cursor from the first frame with the grab point preserved. Verified in a live browser (node drag and canvas pan track from the first move; sub-threshold clicks still do not start a drag) and via nx build f-flow. Fixes #309. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The connection preview line is painted by an <svg overflow:visible> inside a zero-size host (.f-connections-container is 0x0). Safari/WebKit does not invalidate the ink painted outside that box on display:none, so on drop the preview line - often several accumulated frames of it - stayed painted on the canvas until an unrelated repaint occurred (e.g. moving the pointer off the flow). Force that repaint once in onPointerUp by toggling f-canvas - the element that owns the compositing layer the ink is rasterized into - out of and back into the render tree, which re-rasters the whole layer and clears the remnant. The toggle is synchronous, so the canvas size is unchanged by the end of the frame and no ResizeObserver fires; the canvas transform is untouched. Emit ordering is unchanged: CreateConnectionFinalize emits the create event before calling onPointerUp, so this cleanup cannot affect edge creation. Confirmed fixed on Safari 26.5 / macOS; no regression in Chromium (canvas not stuck hidden, transform preserved, nodes and connections intact) and nx build f-flow passes. Fixes #311. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add withControlScheme(...) for provideFFlow(...) - a provider feature that installs a control scheme: the complete gesture-to-action mapping (node move, canvas pan, selection, create/reassign connection, resize, rotate, and wheel pan-vs-zoom) driving fDraggable, f-canvas[fZoom] and f-selection-area together. FControlSchemeController switches or tweaks the scheme at runtime via setScheme(). Ships three presets: F_DEFAULT_CONTROL_SCHEME (drag pans, wheel zooms, Shift+drag selects; applied when the feature is absent, so existing apps are unchanged), F_SCROLL_PAN_CONTROL_SCHEME (Miro-like: scroll pans, Ctrl/Cmd+scroll or pinch zooms, drag on the empty canvas selects, middle-drag pans) and F_DRAG_SELECT_CONTROL_SCHEME (draw.io-like: wheel zooms, drag on the empty canvas selects, middle-drag pans). - Trigger inputs keep their names and always-callable types and override the active scheme when set; fWheelTrigger also overrides scrollPan routing. - Scroll-to-pan routes through a new ScrollCanvasRequest execution; the new fPinchStep input gives trackpad pinch its own zoom step. Closes #312 and enables the mouse-wheel scrolling use case from #218. - The middle mouse button joins the drag pipeline only when the active scheme's canvasMove gesture claims it - inert without a scheme, as before. - A pointer-down that only starts the selection rectangle still runs single-selection unless Shift is held, so drag-to-select schemes clear on an empty-canvas click and replace on marquee while Shift stays additive; the default Shift-gated behavior is unchanged. - New helpers: primaryButtonEventTrigger, middleButtonEventTrigger, isOnFlowBackground. - Docs: /docs/control-scheme guide, /examples/control-schemes demo with preview images, updated trigger defaults in the interaction guides, llms.txt/llms-full.txt entries. Verified in a live browser for the default scheme (wheel zoom, left-drag pan, inert middle button, Shift+click keeps selection, Shift+marquee adds) and the scroll-pan scheme (wheel pan, Ctrl+wheel zoom, middle-drag pan, click clears, marquee replaces); nx build f-flow and portal pass. Closes #312. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Bump the docs version badge and the stats fallback (v18.6.0 -> v19.0.0, stars 478 -> 496) and drop a stray blank line in the selection-area example styles. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ection API Add a single [fConnector] directive that replaces the legacy fNodeInput/fNodeOutput/fNodeOutlet trio: - one id per connector (fConnectorId); behavior is controlled by fConnectorType: 'source' | 'target' | 'source-target' (default) | 'outlet' - inputs: fConnectorDisabled, fConnectorMultiple (default true), fConnectorCategory, fConnectorConnectableSide, fConnectorSelfConnectable, fCanBeConnectedTo (replaces fCanBeConnectedInputs), fConnectionFromOutlet - neutral .f-connector* CSS classes and data-f-connector-* host attributes; theme mixins in _connector.scss cover both legacy and new classes - outlet keeps the shared-start-surface semantics: the emitted FCreateConnectionEvent.sourceId is always the resolved real source id f-connection gets canonical fSourceId/fTargetId and fSourceSide/fTargetSide inputs; fOutputId/fInputId/fOutputSide/fInputSide are deprecated but keep working (a new input wins when set, otherwise the deprecated one is used). Internals: - unified FComponentsStore.connectors registry; legacy connectors stay in outputs/inputs/outlets (the same id may legally be both an input and an output there), all resolution goes through findSourceConnector/ findTargetConnector fallback helpers and isSource/isTarget/isOutlet connector predicates - create-connection gets a from-connector preparation branch; outlet resolution, reassign, resize/drag/rotate handlers and node connection calculators are unified-registry aware - internal consumers read connection.sourceId()/targetId() instead of the deprecated fOutputId()/fInputId() - specs: fallback resolution units plus a mounted-directive smoke test Docs/portal: - new f-connector-directive page (New badge) and unified-connector example mirroring connector-outlet; legacy connector pages get Deprecated badges - migration notes: fConnectorMultiple defaults to true (legacy outputs were single-connection) and connector ids are unique across all types - llms.txt / llms-full.txt updated Legacy directives keep working unchanged and are deprecated via JSDoc. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Make the library reliable for LLMs and AI coding agents, which work against the installed npm package and iterate on console/build output. Diagnostics (dev-mode, stable FFxxxx codes linking to /docs/errors): - FF1001: a <f-connection> endpoint id matching no rendered connector - the most common silent failure - now warns once per connection with the list of registered connector ids; suppressed while progressive rendering is pending. - FF1002: zero-height f-flow host (invisible flow) warns after first paint. - FF1003: a connector outside [fNode]/[fGroup] now throws an actionable error instead of a bare NullInjectorError (shared injectConnectorNode helper). - Warnings are stripped from production builds via ngDevMode. - JSDoc added to the previously undocumented FNodeBase and FConnectorBase public members, so the published .d.ts explains the API agents read first. Agent rules shipped with the package: - ng add @foblex/flow writes a marker-delimited Foblex Flow section into the workspace AGENTS.md (created if missing) pointing agents at the version-matched node_modules/@foblex/flow/AI.md, llms.txt/llms-full.txt and the FFxxxx reference; re-running refreshes only the managed block; --skip-agent-rules opts out. - AI.md rewritten for the current API: unified fConnector, fSourceId/fTargetId, provider features, a minimal three-file working setup (including the imports: [FFlowModule] step) and a silent-failure checklist. LLM docs freshness: - llms-full.txt updated to v18.6.1 and gains reflow (withReflowOnResize, FReflowController, fReflowIgnore) and canvas layer ordering (EFCanvasLayer, fLayers, withFCanvas); llms.txt links every registered docs page. - scripts/validate-llms-content.mjs (wired into seo:check/prebuild) fails the build when the llms files drift from the package version, the docs page list, or the REQUIRED_SYMBOLS API checklist. - New /docs/errors and /docs/ai pages; quickstart gains the FFlowModule snippet; READMEs and robots.txt link the LLM docs; scripts/ai-eval scaffolds a web-codegen-scorer eval comparing baseline vs bundled-docs generation. Verified: FF1001 reproduced live in schema-designer (broken seed id -> warning with registered-id list), healthy app emits no FF warnings, f-flow and portal builds pass, llms validator green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Nodes, groups, or connections rendered inside nested @if/@for blocks (or a wrapper element) are not projected into <f-canvas>: Angular creates the instances - they register with the flow - but their elements stay detached from the document, geometry collapses to 0x0, and nothing renders, with no error anywhere. This is one of the most common integration mistakes and was previously invisible even to the FF1001 endpoint check, because the detached connectors still resolve by id. Detect it directly: after each settled nodes change, warn once per registered node/group/connection whose hostElement.isConnected is false, naming the item and the exact fix (<ng-container ngProjectAs="[fNodes]"> / "[fGroups]" / "[fConnections]"). Dev-mode only via ngDevMode. Docs: FF1004 section with a before/after example in /docs/errors, a hard rule plus silent-failure entry in the bundled AI.md, the pitfall in the quickstart and in llms-full.txt's mental model, and ngProjectAs added to the llms validator's required symbols. Verified live in schema-designer: wrapping the nodes @for in @if(true) makes all 10 nodes vanish silently on HEAD; with this change the console emits FF1004 naming each node and the fix; adding the suggested ng-container restores all nodes and connections; the healthy app emits no warnings. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A native <select> dropdown (and other OS-level popups) opened from inside a node swallows the pointerup that ends a drag sequence. The document-level mousemove/pointerup listeners stayed armed, so the next mouse move - with no button pressed - crossed the 3px threshold and started a phantom drag that glued the node or canvas to the cursor until an unrelated pointerup (e.g. a right-click) was delivered. Treat a buttonless mousemove as the missed pointerup: if a drag is already in progress, finalize it at the last position the button was actually held at (the current buttonless position would teleport the item), then end the sequence and detach the listeners. Verified in the browser against the drag pipeline: mousedown followed by buttonless moves no longer drags nodes or pans the canvas; a drag in progress ends at its last held position when the buttonless move arrives (finalize grid-snap only); subsequent normal drags work; reproduced scenario matches the Custom Nodes example's embedded select. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ssues Extract the settled-nodes dev checks into a RunDevDiagnostics execution (FF1004 moved there from FFlowComponent) and add five new codes, each targeting a silent failure that repeatedly appears in issues: - FF1005: interaction features (fDragHandle, f-selection-area, f-connection-for-create, snap, resize/rotate handles) present while fDraggable is missing on <f-flow> - everything renders but every pointer interaction is inert. Warned once per feature. - FF1006: a connector hidden with CSS (display:none) registers but its geometry is a 0x0 point, so connections attach wrongly (issue #207 class). - FF1007: an fNode element nested inside another node element - the outer node wins the drag and inner bindings never fire (issue #101). - FF1008: fNodeParentId/fGroupParentId referencing an id no rendered group has; the warning lists registered ids (FF1001-style resolution for hierarchy). - FF1009: fitToScreen()/resetScaleAndCenter()/centerGroupOrNode() called before the first fFullRendered computes against an incomplete node set (issues #281/#28/#196); checked against RenderLifecycleState. FF1001 now recognizes unresolved ids that look like unevaluated template expressions (node.outputId) and suggests the missing property-binding brackets (issue #69). Docs: FF1005-FF1009 sections in /docs/errors; the bundled AI.md gains the new checklist entries plus an Additional Rules section covering the docs-only pitfalls (zone/OnPush mutation, stable ids, global styles, autosize-vs-restore, multiple flows, SELECTED_* markers, category allow-list semantics, fCache/fVirtualFor at scale); llms-full.txt lists all diagnostic codes. Verified live in schema-designer: each of the five codes reproduced with a temporary breakage and emits an actionable one-shot warning; the healthy app emits no warnings (no false positives across 86 connectors, groups, and the fFullRendered-driven fitToScreen); f-flow build passes and the llms validator is green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…F1009) fitToScreen()/resetScaleAndCenter()/centerGroupOrNode() compute from the nodes bounding box, so calling them once the nodes are rendered and measured is already correct - connections do not affect the result. Warning on "before fFullRendered" was too strict: (fNodesRendered) is a legitimate and earlier moment for initial positioning. Gate FF1009 on RenderLifecycleState.isNodesRendered instead of isFullRendered, and point the message and docs at (fNodesRendered) as the earliest safe hook, with (fFullRendered) as the alternative. Verified live: fitToScreen() driven by (fNodesRendered) emits no warning; the early-call path (ngAfterViewInit) still warns. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…nt create-connection session Extract the connection-creation logic from the drag handler into FCreateConnectionSession — begin/update/resolveTarget/complete/cancel — which owns the preview line, snap highlighting, connectable marking, target resolution and the fCreateConnection emission. The drag handler and finalize step become thin delegates, so any gesture that drives the session gets identical validation, snapping and events. withConnectionFlow(...) in provideFFlow(...) selects the gesture: 'click' installs the built-in click-to-connect (click a source connector to arm, the preview follows the cursor with no button held, click a connectable target to commit; Escape or clicking elsewhere cancels, clicking another source re-arms; drag-to-connect keeps working alongside), and a custom Type<IFConnectionFlow> installs your own strategy. The token carries the strategy type; fDraggable instantiates it against the f-flow element injector so flow-scoped services resolve even though the feature is registered at the consumer component. Ships the Click to Connect example, llms/AI docs entries, and the validator symbol. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Move the Click to Connect example from the Connectors group to Connections - Editing (page markdown follows), and register the auto-pan example as a lazy docs component so the f-auto-pan guide renders it live. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Drop the pre-seeded connection so the example demonstrates the gesture from a clean state. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…oard connection creation Every flow now applies ARIA semantics automatically: roles and generated accessible names on nodes, groups and connections (attributes the consumer sets are never overridden, generated connection names track endpoint changes), aria-hidden on the minimap and connection previews, and a visually-hidden live region announcing selection, movement, connection and deletion. Inert attributes only — no behavior changes without opting in. provideFFlow(withA11y()) enables the keyboard layer. DOM focus stays on the f-flow host (the single tab stop, role=application, transparent focus ring that Windows High Contrast repaints); arrows drive the SELECTION directly with the active item exposed via aria-activedescendant, so there is no separate focus state. Navigation is spatial and edge-based, travels over nodes and connections alike, extends the selection with Shift, walks the topology with Ctrl+arrow, and returns to the source on the opposite arrow. Space moves the whole selection both ways — hold-and-arrow with drop on release, or tap-to-grab / tap-to-drop for users who cannot chord keys — and emits the same fMoveNodes a pointer drag does. C starts a connection from the single selected node through the shared FCreateConnectionSession, defaulting to the nearest connector on another node that is not already connected; Enter emits fCreateConnection. Delete/Backspace emits the new fDeleteSelected event — the library never mutates the graph. +/-/0 zoom. Native interactive content inside nodes is never hijacked, single-character keys yield to OS shortcuts, and active modes invalidate when their items leave the data. withA11y(config) tunes movement steps, per-action key bindings (IFA11yKeys, empty array disables an action) and the fully localizable announcement catalog (IFA11yMessages). Ships the Accessibility example, the /docs/accessibility guide, llms/AI docs entries and the validator symbol. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…e the repaint workaround to WebKit Click-to-connect now obeys the same contracts as the drag gesture: it stays inert when fDraggableDisabled is set and consults the createConnection trigger of the active control scheme before arming. The remnant-ink canvas repaint after a connection drop is WebKit-specific, so other engines now skip the forced synchronous layout flush entirely. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
New "Feature Deep Dives" blog group with standalone design stories for the v19 headline features: keyboard accessibility (selection-instead-of-focus model), the unified fConnector model, and control schemes. Titles and descriptions are intent-distinct from the docs pages, examples and the release post to avoid SEO overlap; each article embeds its live demo. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ibility previews The release post embeds the four feature demos as live ng-components (the blog convention) instead of bare links; the accessibility example gets generated light/dark preview images wired into its page entry. Markdown italics normalized by prettier. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1028 -> 1169 documents; the accessibility guide/example, click-to-connect, control schemes, the unified connector pages, the v19.0.0 release post and the three feature deep-dives are now searchable. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
portal:build now depends on build-search-index (alongside generate-artifacts), so the index can no longer silently drift from the content — it had already missed an entire release cycle. The target is cached on the markdown/sections/script inputs, so repeat builds and serve are a no-op when content is unchanged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
While creating or reassigning a connection the pointer rides the edge of a connection path — the preview line ends exactly at the cursor — so hover hit-testing flipped between the path and the background every frame, flickering :hover styles and the cursor type. The connection host was already inert; the real hit targets are its children (.f-connection-group with pointer-events: all, the stroke paths with pointer-events: stroke, waypoint handles). Connections take no part in drop targeting, so those targets now give up pointer events while the flow host carries the f-dragging or f-connections-dragging class, and get them back on release. Verified live: selection stroke goes stroke -> none -> stroke across create-connection and node drags. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The generator writes minified JSON, but lint-staged prettified the 5 MB artifact on commit, so every subsequent build left the tree dirty and defeated the nx output cache. The file is now prettier-ignored and committed byte-identical to what build-search-index produces. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Drop the builtAt wall-clock field from the index payload — it made every regeneration differ by one byte-range, so the nx output cache kept restoring a stale artifact over the committed one and every build left the tree dirty. Same inputs now produce byte-identical output (verified by hash across runs), so cache restores, fresh runs and the committed file all agree. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…croll-pan PR #313 by @yaroslavyushchenko independently implemented scroll-pan and fPinchStep against the pre-v19 zoom directive. It is merged to main so the contribution stays in history; this merge resolves every touched file to the v19 implementation, which covers the same behavior through the control scheme with deltaMode-normalized pan deltas and the mediator scroll path. Credited in the changelog. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…chor The spec encoded the pre-#309 behavior: the drag anchored at the threshold-crossing point and swallowed the first mousemove, so a single move to clientX -250 left the node in place and the pointerup at 0 nudged it by +250. Since #309 the drag anchors at the pointer-down position and the threshold-crossing move applies immediately, so the node followed the pointer off-viewport and Cypress failed the actionability check on the next trigger. The spec now drags relative to the node center and asserts the pointer-tracking delta. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…the base rules The connection-limits branch returned the whitelist matches directly, skipping the target.canBeConnected check and the self-connect rule — an allow-list could offer a disabled or at-capacity target, or a connection inside the same node. Target filtering now lives in a pure filterConnectableTargets helper where the allow-list only NARROWS the candidates and the base rules always apply, with unit specs covering the bypass cases and category matching. Also from the follow-up review: - test noise: the library's own suites suppress FF dev warnings via a new fSuppressDevWarnings switch (module-load setup spec) — fixtures legitimately violate the checks and the noise buried real warnings - FA11yController gets lifecycle specs (arrow selection, interactive content guard, OS shortcut passthrough, multi-node grab/drop/revert, quasimode release, fDeleteSelected gating, key rebinding/disable, inert-without-config) - changelog: the host application-role semantics are correctly attributed to the opt-in keyboard layer, and the navigation description matches the shipped edge-gap algorithm instead of the discarded sector one - examples: no non-null assertions after guards Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This was referenced Jul 5, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
v19.0.0 — one story: who decides how the editor is driven
Until v19 there was exactly one way to drive a Foblex Flow editor: one gesture mapping, one way to create connections, one input device. This release opens all of it up — the pointer scheme, the connection gesture, the keyboard, and the AI agent writing code against the library are now first-class ways to drive the editor.
Features
provideFFlow(withControlScheme(...)): the complete gesture-to-action mapping as one object drivingfDraggable,fZoomandf-selection-areatogether. Presets: default, Miro-like (F_SCROLL_PAN_CONTROL_SCHEME), draw.io-like (F_DRAG_SELECT_CONTROL_SCHEME); runtime switching viaFControlSchemeController; explicit trigger inputs still win. Closes [Feature]: fScrollPan and fPinchStep inputs for better trackpad support #312, enables [Feature]: enable mouse wheel scrolling #218.withConnectionFlow('click')adds click-to-connect alongside drag (closes discussion Start connection with a single click #264). Both gestures drive the new gesture-independentFCreateConnectionSession(preview, snapping, marking, target resolution,fCreateConnectionemission); a customType<IFConnectionFlow>gets all of it for free.aria-hiddenhelpers; consumer attributes are never overridden). The keyboard layer is opt-in viawithA11y(): DOM focus stays on the host (aria-activedescendant), arrows drive the selection spatially over nodes and connections,Ctrl+arrow walks the topology,Spacemoves the selection (hold-and-release or tap-to-grab),Ccreates connections through the shared session,Deleteemits the newfDeleteSelected, keys are remappable (IFA11yKeys), every string localizable (IFA11yMessages).[fConnector]directive (fConnectorType: source | target | source-target | outlet) replaces thefNodeInput/fNodeOutput/fNodeOutlettrio; connections gain canonicalfSourceId/fTargetId. Legacy directives and ids keep working, deprecated.FF1001–FF1009with documented causes at/docs/errors(stripped from prod builds);ng addwrites a managed AGENTS.md block pointing agents at the version-matchedAI.mdinside the npm package;llms.txt/llms-full.txtvalidated in CI.Fixes
<select>opened inside a node no longer leaves a phantom drag after closing (swallowedpointerupdetected).fDraggableDisabledand the active scheme'screateConnectiontrigger.⚠ Breaking changes
roleonly with the keyboard layer on; items getrole,aria-roledescription, generated ids/labels — consumer-set attributes always win). Behavior changes only after opting intowithA11y().FDraggableBasegains two abstract members (fDeleteSelected,fCreateConnectionTrigger) — affects direct subclasses only.fConnectorMultipledefaults totrue; connector ids unique across types).Docs & portal
/docs/accessibility,/docs/control-scheme,/docs/errors,/docs/ai,/docs/f-connector-directive.portal:build(cached, deterministic output) — it had silently drifted a full release cycle.Verification
f-flow172/172 unit tests; lint clean;f-flowandportalbuilds green; llms validator green (18 API symbols, 46 docs links).🤖 Generated with Claude Code