Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
793dbc4
fix(f-draggable): anchor drag to pointer-down instead of threshold cr…
siarheihuzarevich Jul 2, 2026
7636c03
fix(create-connection): clear preview line remnant on Safari after drop
siarheihuzarevich Jul 2, 2026
bd6334b
feat(control-scheme): provider-driven control schemes with presets
siarheihuzarevich Jul 3, 2026
2f04867
chore(portal): refresh version references and stats fallback for v19.0.0
siarheihuzarevich Jul 3, 2026
78f8559
feat(connectors): unified fConnector directive and source/target conn…
siarheihuzarevich Jul 3, 2026
5501ae3
feat(ai): dev diagnostics, agent rules in ng-add, and fresh LLM docs
siarheihuzarevich Jul 3, 2026
bc3a247
feat(diagnostics): detect content not projected into the canvas (FF1004)
siarheihuzarevich Jul 3, 2026
87aa73c
fix(f-draggable): end the drag sequence when pointerup was swallowed
siarheihuzarevich Jul 3, 2026
dff5d15
feat(diagnostics): five new dev checks mined from recurring support i…
siarheihuzarevich Jul 4, 2026
0f4c6a1
fix(diagnostics): gate the viewport-helper warning on nodes render (F…
siarheihuzarevich Jul 4, 2026
4119cdd
feat(connection-flow): add withConnectionFlow and a gesture-independe…
siarheihuzarevich Jul 4, 2026
16ebf6d
docs(portal): regroup click-to-connect and embed the auto-pan example
siarheihuzarevich Jul 4, 2026
d32a76f
docs(examples): start the click-to-connect example with an empty canvas
siarheihuzarevich Jul 4, 2026
985579c
feat(a11y): built-in semantics and an opt-in keyboard layer with keyb…
siarheihuzarevich Jul 5, 2026
28dc752
fix(connection-flow): honor public gates in click-to-connect and scop…
siarheihuzarevich Jul 5, 2026
5cab7e7
docs(blog): v19.0.0 release post
siarheihuzarevich Jul 5, 2026
fe56fd8
docs(blog): feature deep-dives section with three v19 design articles
siarheihuzarevich Jul 5, 2026
c50dde8
docs(portal): embed live demos in the v19 release post and add access…
siarheihuzarevich Jul 5, 2026
dee9cf4
chore(portal): rebuild the search index with the v19 pages
siarheihuzarevich Jul 5, 2026
baefa1d
chore(portal): run the search-index build as part of the portal build
siarheihuzarevich Jul 5, 2026
b81090d
fix(f-connection): suppress connection hover hit-testing during drags
siarheihuzarevich Jul 5, 2026
d34e6dd
chore(portal): keep the search index in its generated single-line form
siarheihuzarevich Jul 5, 2026
8bbb815
chore(portal): make the search index deterministic
siarheihuzarevich Jul 5, 2026
a1fc0ca
chore(release): cut the v19.0.0 changelog
siarheihuzarevich Jul 5, 2026
7eefbcf
chore(release): merge main with #313 and unify on the scheme-driven s…
siarheihuzarevich Jul 5, 2026
227af73
test(e2e): calibrate the drag-handle spec to the pointer-down drag an…
siarheihuzarevich Jul 5, 2026
379823f
fix(connectors): the fCanBeConnectedTo allow-list no longer bypasses …
siarheihuzarevich Jul 5, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,4 @@ testem.log

.DS_Store
Thumbs.db
apps/f-flow-portal/public/search-index.json
2 changes: 2 additions & 0 deletions AGENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -375,6 +375,8 @@ The files `apps/f-flow-portal/public/llms.txt` (short index) and `apps/f-flow-po

- `llms.txt` is the short index following the [llms.txt spec](https://llmstxt.org): H1 title, blockquote, H2 sections with `- [Link](url): description` entries. Keep it as a link directory — no inline API content.
- `llms-full.txt` is the full inline reference with all inputs, outputs, methods, types, and code examples. Keep the existing numbered section structure and table format.
- `scripts/validate-llms-content.mjs` (part of `npm run seo:check`, runs on prebuild) enforces freshness: the stated version must match `libs/f-flow/package.json`, every docs page from `docs.pages.ts` must be linked in `llms.txt`, and every symbol in its `REQUIRED_SYMBOLS` list must appear in `llms-full.txt`. When shipping a new headline public API, add its key symbol to `REQUIRED_SYMBOLS`.
- The consumer-facing `libs/f-flow/AI.md` ships inside the npm package (`ng-package.json` assets) — update it alongside the llms files for the same triggers; it is what agents read from `node_modules/@foblex/flow/AI.md`.
- Update the version number when bumping the package version.
- Add new components/directives in the API Reference section following the existing table format.
- Update the Angular Version Compatibility table when compatibility changes.
Expand Down
36 changes: 35 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,41 @@

All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.

## Unreleased
## [19.0.0](https://github.com/Foblex/f-flow/compare/v18.6.1...v19.0.0) (2026-07-05)

### Highlights

- **Control Schemes** — `withControlScheme(...)` maps gestures to canvas actions for the whole editor at once, with Miro-like and draw.io-like presets and runtime switching through `FControlSchemeController`.
- **Connection Flows** — `withConnectionFlow('click')` adds click-to-connect alongside drag, built on the new gesture-independent `FCreateConnectionSession` that custom gestures can drive too.
- **Accessibility** — ARIA semantics in every flow by default, plus an opt-in keyboard layer (`withA11y()`): spatial arrow navigation over nodes and connections, grab-and-move, keyboard connection creation, screen-reader announcements, remappable keys, localizable messages.
- **Unified Connector Model** — one `[fConnector]` directive replaces the `fNodeInput`/`fNodeOutput`/`fNodeOutlet` trio; connections gain canonical `fSourceId`/`fTargetId`. Legacy APIs keep working, deprecated.
- **AI-Ready Toolchain** — dev-mode diagnostics with stable `FF1001`–`FF1009` codes, agent rules installed by `ng add`, and CI-validated LLM docs shipped inside the npm package.

### Features

- **control-scheme:** add `withControlScheme(...)` for `provideFFlow(...)` — a provider feature that installs a control scheme: the whole 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, plus an `FControlSchemeController` for switching or tweaking the scheme at runtime via `setScheme()`. Ships the `IFControlScheme` type and 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). The interaction trigger inputs (`[fNodeMoveTrigger]`, `[fCanvasMoveTrigger]`, `[fWheelTrigger]`, `[fTrigger]`, …) keep their names and types and override the active scheme when set. New `primaryButtonEventTrigger`, `middleButtonEventTrigger` and `isOnFlowBackground` helpers are exported for building custom schemes. The middle mouse button joins the drag pipeline only when the active scheme's `canvasMove` gesture claims it — without a scheme it stays inert as before.
- **f-zoom:** scroll-to-pan and pinch step — when the active scheme's `scrollPan` is on, a plain wheel or two-finger trackpad scroll pans the canvas while `Ctrl`/`Cmd`+wheel and trackpad pinch still zoom; the new `fPinchStep` input gives trackpad pinch a separate zoom step from the mouse wheel (falls back to `fZoomStep`). Closes [#312](https://github.com/Foblex/f-flow/issues/312) and enables the mouse-wheel scrolling use case from [#218](https://github.com/Foblex/f-flow/issues/218). Scroll-pan and the pinch step were independently proposed in [#313](https://github.com/Foblex/f-flow/pull/313) by [@yaroslavyushchenko](https://github.com/yaroslavyushchenko) — merged and unified into the scheme-driven implementation.
- **selection:** a pointer-down that only starts the selection rectangle now still runs single-selection unless `Shift` is held, so under schemes that bind the selection area to a plain drag a click on the empty canvas clears the selection and a marquee replaces it, while `Shift`+drag stays additive. With the default `Shift`-gated selection area every marquee is a `Shift` gesture, so pointer-down semantics are unchanged.
- **a11y:** built-in accessibility layer in every flow, including keyboard connection creation. Semantics run always: nodes, groups and connections get `role`/`aria-roledescription`/accessible names (a connection reads "Connection from X to Y" and the generated name tracks endpoint changes; attributes you set yourself are never overridden), a persistent live region announces selection, movement, connection and deletion (WCAG 4.1.3), and the minimap/connection previews are hidden from screen readers. The keyboard layer is opt-in (`provideFFlow(withA11y())`) — existing apps with their own key handling are never double-driven. With it on, the host becomes the widget's entry point (`tabindex="0"`, `role="application"` so screen readers hand the arrow keys to the canvas, keyboard instructions as its description) and the keys drive SELECTION directly — DOM focus stays on the `f-flow` host and the active item is exposed through `aria-activedescendant`, so there is no separate focus state at all: spatial navigation is edge-based (facing-edge gaps with a doubled off-axis penalty, so wide and tall nodes resolve correctly and a near diagonal neighbor beats a far straight one; the opposite arrow returns to where you came from), `Shift`+arrow extends the selection, `Ctrl`+arrow walks the topology (the connection is focused and selected first — so `Delete` works on it — then the node on the other end), `Ctrl`+`A`/`Escape` complete the selection keys, `Space` moves the selection both ways — hold it and arrow, release to drop (a quasimode with no state to forget), or tap to grab and tap to drop for users who cannot chord keys (`Shift` for coarse steps, `Escape` reverts, the drop emits `fMoveNodes`), `C` starts a connection from the selected node (exactly one node must be selected) through the shared `FCreateConnectionSession` (arrows move between connectable targets spatially with the usual preview and snapping, `Enter` emits `fCreateConnection`, `Escape` cancels), `Delete`/`Backspace` emits the new `fDeleteSelected: FDeleteSelectedEvent` (the library never mutates the graph), and `+`/`-`/`0` zoom. Native interactive content inside nodes (inputs, textareas, selects, buttons, links) is never hijacked, and single-key shortcuts respect OS modifiers (`Ctrl`+`C` stays copy). `provideFFlow(withA11y(...))` tunes the layer: `keyboard: false`, `moveStep`/`coarseMoveStep`, a fully localizable message catalog (`IFA11yMessages`), and per-action key bindings (`IFA11yKeys` — e.g. `keys: { connect: ['n'], deleteSelected: [] }`; an empty array disables an action). The selected style is the single visual state — the host's own focus ring is transparent (repainted with system colors in Windows High Contrast, so forced-colors users keep an indicator); keyboard navigation auto-pans off-screen nodes into view (WCAG 2.4.11). Ships the `Accessibility` example (`/examples/accessibility`).
- **connection-flow:** click-to-connect now obeys the same public gates as the drag gesture — `fDraggableDisabled` and the `createConnection` trigger of the active control scheme; the Safari remnant-ink canvas repaint after a connection drop now runs only on WebKit, other engines skip the synchronous layout flush.
- **connection-flow:** add `withConnectionFlow(...)` for `provideFFlow(...)` — a provider feature that selects the connection-creation gesture. `'click'` installs click-to-connect: click a source connector to arm a connection, the preview line follows the cursor with no button held, click a connectable target to commit; `Escape` or clicking anything else cancels, and clicking another source re-arms from it. Drag-to-connect keeps working alongside, and passing a custom `Type<IFConnectionFlow>` installs your own gesture. Both gestures drive the new `FCreateConnectionSession` — the connection-creation engine extracted from the drag handler (`begin`/`update`/`resolveTarget`/`complete`/`cancel`) that owns the preview line, snap highlighting, connectable marking, target resolution and the `fCreateConnection` emission — so validation, snapping and the emitted event are identical in every mode, and custom strategies get them for free. Ships the `Click to Connect` example (`/examples/click-to-connect`).
- **examples:** add the `Control Schemes` example (`/examples/control-schemes`) that switches between the default, Miro-like and draw.io-like presets at runtime, and a `Control Scheme` guide under `/docs/control-scheme`.
- **diagnostics:** dev-mode diagnostics with stable `FFxxxx` codes linking to the new `/docs/errors` reference. `FF1001` warns when a `<f-connection>` endpoint id matches no rendered connector — the most common silent failure — listing the registered connector ids (suppressed during progressive rendering, deduplicated per connection). `FF1002` warns when the `f-flow` host has zero height (invisible flow). `FF1003` replaces the bare `NullInjectorError` for a connector placed outside `[fNode]` / `[fGroup]` with an actionable error. `FF1004` detects flow content that Angular did not project into the canvas — nodes/groups/connections inside nested `@if`/`@for` blocks register but render detached and invisible with no error; the warning names the item and the exact `<ng-container ngProjectAs="[fNodes]">` / `"[fGroups]"` / `"[fConnections]"` fix. Five more checks mined from recurring support issues run after each settled nodes change via a dedicated `RunDevDiagnostics` execution: `FF1005` — interaction features (`fDragHandle`, `f-selection-area`, `f-connection-for-create`, resize/rotate handles) present without `fDraggable` on `<f-flow>`; `FF1006` — a connector hidden with CSS (`display: none`) whose geometry collapses to a 0×0 point; `FF1007` — an `fNode` element nested inside another node element; `FF1008` — `fNodeParentId`/`fGroupParentId` referencing an id no rendered group has (lists registered ids); `FF1009` — `fitToScreen()`/`resetScaleAndCenter()`/`centerGroupOrNode()` called before the nodes were rendered (the helpers compute from the nodes bounding box, so `(fNodesRendered)` is the earliest safe moment; `(fFullRendered)` also works). The `FF1001` message now also recognizes ids that look like unevaluated template expressions (`node.outputId`) and suggests the missing property-binding brackets. Warnings are stripped from production builds via `ngDevMode`. Also adds JSDoc to the previously undocumented `FNodeBase` and `FConnectorBase` public members, so the published `.d.ts` documents `fNodePosition`, `fNodeSize`, group auto-sizing options, and connector semantics.
- **ng-add:** `ng add @foblex/flow` now writes a marker-delimited Foblex Flow section into the workspace `AGENTS.md` (created if missing) pointing AI coding agents at the version-matched guide bundled in the package (`node_modules/@foblex/flow/AI.md`), the hosted `llms.txt` / `llms-full.txt`, and the `FFxxxx` error reference. Re-running refreshes only the managed block; opt out with `--skip-agent-rules`.
- **ai-docs:** refresh the LLM-facing docs to the current API — `llms-full.txt` gains reflow (`withReflowOnResize`, `FReflowController`, `fReflowIgnore`), canvas layer ordering (`EFCanvasLayer`, `[fLayers]`, `withFCanvas`) and the current version; the bundled `AI.md` now covers the unified `fConnector`, `fSourceId`/`fTargetId`, provider features, a minimal three-file working setup and a silent-failure checklist. A new `scripts/validate-llms-content.mjs` check (part of `seo:check`/prebuild) fails the build when the llms files drift from the package version, docs pages, or key API symbols. New `/docs/ai` page documents all agent channels; the quickstart now includes the required `imports: [FFlowModule]` snippet; READMEs and robots.txt link the LLM docs.

### ⚠ Breaking changes

- **a11y:** every flow now applies ARIA semantics by default — nodes/groups/connections gain `role`, `aria-roledescription`, a DOM `id` when they have none, and (for connections) a generated `aria-label`; any attribute you set yourself wins. These are inert attributes: no behavior changes without opting into the keyboard layer via `withA11y()` (only then does the host gain `tabindex="0"`, `role="application"` and the keyboard instructions).
- **f-draggable:** `FDraggableBase` has two new abstract members — the `fDeleteSelected` output and the `fCreateConnectionTrigger` gate. This only affects code that subclasses `FDraggableBase` directly; templates and the `FDraggableDirective` API are unaffected.

### Fixes

- **f-draggable:** a native `<select>` dropdown (or any OS-level popup) opened from inside a node swallows the `pointerup` that ends a drag sequence, leaving the document listeners armed — the next mouse move then started a phantom drag that glued the node (or canvas) to the cursor with no button pressed, recoverable only by an unrelated click. A buttonless `mousemove` is now treated as the missed `pointerup`: an in-progress drag is finalized at the last position the button was actually held at, and the sequence ends. Reproducible in the Custom Nodes example via its embedded select.

- **f-draggable:** anchor a drag to the pointer-down position instead of the point where the drag threshold was crossed, and apply that threshold-crossing move within the same event. `prepareDragSequence()` previously ran against the `pointermove` that crossed the 3px threshold, so `onPointerDownPosition` was captured several px away from the grab point. On a fast flick (cursor moved far within one frame) this offset the dragged item from the cursor for the whole drag and delayed the first visible move to the next `pointermove` — a 20–50px lag/jump. The shared drag base now prepares against the original pointer-down event and applies the first move immediately, so node dragging, canvas panning, resize, rotate, and connection creation all track the cursor from the first frame with the grab point preserved. Fixes [#309](https://github.com/Foblex/f-flow/issues/309).
- **f-connection:** stop hover flicker while dragging. When a connection is being created or reassigned, the pointer often rides the edge of a connection path (the preview line ends at the cursor), so hover hit-testing flipped between the path and the background every frame — flickering `:hover` styles and the cursor. Connections take no part in drop targeting, so their hit targets (`.f-connection-group`, the stroke paths, waypoint handles) now give up pointer events for the duration of any drag (`f-dragging` / `f-connections-dragging` on the flow host) and restore them on release.
- **create-connection:** force the canvas compositing layer to repaint once when the connection preview is hidden on drop. The preview line is painted by an `<svg overflow: visible>` inside a zero-size host, and Safari/WebKit does not invalidate the ink painted outside that box on `display: none`, so the preview line — often several accumulated frames of it — stayed painted on the canvas after a drop that created no connection, until an unrelated repaint (e.g. moving the pointer off the flow) occurred. `f-canvas` owns the compositing layer the ink is rasterized into, so toggling it out of and back into the render tree re-rasters the 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. Chrome invalidates the full region on hide and was unaffected. Fixes [#311](https://github.com/Foblex/f-flow/issues/311).

## [18.6.1](https://github.com/Foblex/f-flow/compare/v18.6.0...v18.6.1) (2026-05-14)

Expand Down
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,11 @@ Full guide: [Default Theme and Styling](https://flow.foblex.com/docs/default-the
- [Roadmap](https://github.com/Foblex/f-flow/blob/main/ROADMAP.md)
- [Changelog](https://github.com/Foblex/f-flow/blob/main/CHANGELOG.md)

### For AI Agents and LLMs

- [llms.txt](https://flow.foblex.com/llms.txt) — docs index for agents; [llms-full.txt](https://flow.foblex.com/llms-full.txt) — complete LLM-readable API reference
- [AI usage guide](libs/f-flow/AI.md) — strict code-generation rules, shipped inside the npm package at `node_modules/@foblex/flow/AI.md`

## Custom Development and Consulting

Need a custom Angular node editor, workflow builder, or interactive diagram?
Expand Down
13 changes: 10 additions & 3 deletions apps/f-flow-portal-e2e/cypress/e2e/drag-handle.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,30 @@ describe('DragHandle', () => {
});

it('should drag fNode element and update its transform translate', function () {
// The drag anchors to the pointer-down position and the threshold-crossing move
// is applied immediately (#309), so the node follows the pointer from the first
// mousemove. The previous version of this test relied on the pre-#309 anchor
// (threshold point), which swallowed the first move.
cy.wait(500)
.get('.f-node.f-drag-handle')
.first()
.then(($dragHandle: JQuery<HTMLElement>) => {
const dragHandleRect = $dragHandle.get(0).getBoundingClientRect();
const centerX = dragHandleRect.x + dragHandleRect.width / 2;
const centerY = dragHandleRect.y + dragHandleRect.height / 2;

cy.get('.f-node.f-drag-handle')
.first()
.trigger('mousedown', { button: 0, force: true })
.trigger('mousemove', { clientX: -250, clientY: 0 })
.trigger('pointerup', { clientX: 0, clientY: 0 });
.trigger('mousemove', { clientX: centerX + 150, clientY: centerY, force: true })
.trigger('pointerup', { clientX: centerX + 150, clientY: centerY, force: true });

cy.wait(1500)
.get('.f-node.f-drag-handle')
.first()
.then(($dragHandle2: JQuery<HTMLElement>) => {
const dragHandleRect2 = $dragHandle2.get(0).getBoundingClientRect();
expect(Math.round(dragHandleRect.x + 250)).to.equal(Math.round(dragHandleRect2.x));
expect(Math.round(dragHandleRect.x + 150)).to.equal(Math.round(dragHandleRect2.x));
});
});
});
Expand Down
8 changes: 7 additions & 1 deletion apps/f-flow-portal/project.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"tags": ["scope:portal", "type:app"],
"targets": {
"build": {
"dependsOn": ["generate-artifacts"],
"dependsOn": ["generate-artifacts", "build-search-index"],
"executor": "@angular-devkit/build-angular:application",
"outputs": ["{options.outputPath}"],
"options": {
Expand Down Expand Up @@ -76,6 +76,12 @@
},
"build-search-index": {
"executor": "nx:run-commands",
"cache": true,
"inputs": [
"{workspaceRoot}/apps/f-flow-portal/public/markdown/**/*",
"{workspaceRoot}/apps/f-flow-portal/src/app/sections/**/*",
"{workspaceRoot}/scripts/build-search-index.mjs"
],
"outputs": ["{workspaceRoot}/apps/f-flow-portal/public/search-index.json"],
"options": {
"command": "node scripts/build-search-index.mjs"
Expand Down
Loading
Loading