Skip to content

Commit 989d201

Browse files
summary nodes move event (#185)
* feat: add fMoveNodes event emitter and FMoveNodesEvent class for node movement handling * docs: enhance drag-handle example with fMoveNodes event and position change handling
1 parent f475b63 commit 989d201

File tree

9 files changed

+100
-22
lines changed

9 files changed

+100
-22
lines changed

projects/f-examples/nodes/drag-handle/drag-handle.component.html

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,26 @@
1-
<f-flow fDraggable (fLoaded)="onLoaded()">
1+
<f-flow fDraggable (fLoaded)="onLoaded()" (fMoveNodes)="onMoveNodes($event)">
22
<f-canvas>
33
<div fNode fDragHandle
4-
[fNodePosition]="{ x: 0, y: 0 }">
4+
[fNodePosition]="{ x: 0, y: 0 }" (fNodePositionChange)="onNodePositionChange($event)">
55
Node is the drag handle
66
</div>
77

88
<div fNode
9-
[fNodePosition]="{ x: 120, y: 100 }" class="drag-handle-inside">
9+
[fNodePosition]="{ x: 120, y: 100 }" class="drag-handle-inside" (fNodePositionChange)="onNodePositionChange($event)">
1010
<span fDragHandle class="f-icon f-drag-handle-icon"></span>
1111
Only the icon is the drag handle
1212
</div>
1313

1414
<div fNode
15-
[fNodePosition]="{ x: 350, y: 0 }" class="drag-handle-outside">
15+
[fNodePosition]="{ x: 350, y: 0 }" class="drag-handle-outside" (fNodePositionChange)="onNodePositionChange($event)">
1616
<div fDragHandle>
1717
<span class="f-icon f-drag-handle-icon"></span>
1818
</div>
1919
Only the icon is the drag handle
2020
</div>
2121

2222
<div fNode
23-
[fNodePosition]="{ x: 130, y: 200 }">
23+
[fNodePosition]="{ x: 130, y: 200 }" (fNodePositionChange)="onNodePositionChange($event)">
2424
Only the image is the drag handle
2525
<div fDragHandle>
2626
<img src="https://material.angular.io/assets/img/examples/shiba2.jpg">

projects/f-examples/nodes/drag-handle/drag-handle.component.ts

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import { ChangeDetectionStrategy, Component, viewChild } from '@angular/core';
22
import {
33
FCanvasComponent,
4-
FFlowModule
4+
FFlowModule, FMoveNodesEvent
55
} from '@foblex/flow';
6+
import {IPoint} from "@foblex/2d";
67

78
@Component({
89
selector: 'drag-handle',
@@ -17,7 +18,30 @@ import {
1718
export class DragHandleComponent {
1819
protected readonly fCanvas = viewChild(FCanvasComponent);
1920

21+
/**
22+
* Triggered after the <f-flow> component is fully loaded.
23+
* Resets the canvas scale and centers the view without animation.
24+
*/
2025
protected onLoaded(): void {
2126
this.fCanvas()?.resetScaleAndCenter(false);
2227
}
28+
29+
/**
30+
* Called when one or more nodes are moved.
31+
* Can be used to track movements or persist state.
32+
*
33+
* @param event - Node movement event containing affected nodes and delta.
34+
*/
35+
protected onMoveNodes(event: FMoveNodesEvent): void {
36+
// Handle node movement.
37+
}
38+
39+
/**
40+
* Called when a single node's position changes.
41+
*
42+
* @param position - The new position of the node.
43+
*/
44+
protected onNodePositionChange(position: IPoint): void {
45+
// Handle node position change.
46+
}
2347
}

projects/f-flow/src/f-draggable/f-draggable-base.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { FCreateNodeEvent } from '../f-external-item';
55
import { FDragStartedEvent, FNodeIntersectedWithConnections } from './domain';
66
import { FDropToGroupEvent } from './f-drop-to-group';
77
import {DragAndDropBase, ICanRunOutsideAngular} from "../drag-toolkit";
8+
import {FMoveNodesEvent} from "./f-node-move";
89

910
@Directive()
1011
export abstract class FDraggableBase extends DragAndDropBase {
@@ -17,6 +18,8 @@ export abstract class FDraggableBase extends DragAndDropBase {
1718

1819
public abstract fCreateNode: EventEmitter<FCreateNodeEvent>;
1920

21+
public abstract fMoveNodes: EventEmitter<FMoveNodesEvent>;
22+
2023
public abstract fReassignConnection: EventEmitter<FReassignConnectionEvent>;
2124

2225
public abstract fCreateConnection: EventEmitter<FCreateConnectionEvent>;

projects/f-flow/src/f-draggable/f-draggable.directive.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
} from "@angular/core";
1111
import { FDraggableBase } from './f-draggable-base';
1212
import {
13+
FMoveNodesEvent,
1314
FNodeMoveFinalizeRequest,
1415
FNodeMovePreparationRequest
1516
} from './f-node-move';
@@ -161,6 +162,9 @@ export class FDraggableDirective extends FDraggableBase implements OnInit, After
161162
@Output()
162163
public override fCreateNode = new EventEmitter<FCreateNodeEvent>();
163164

165+
@Output()
166+
public override fMoveNodes = new EventEmitter<FMoveNodesEvent>();
167+
164168
@Output()
165169
public override fReassignConnection = new EventEmitter<FReassignConnectionEvent>();
166170

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import {IPoint} from '@foblex/2d';
2+
3+
export class FMoveNodesEvent {
4+
5+
constructor(
6+
public fNodes: { id: string; position: IPoint }[],
7+
) {
8+
}
9+
}

projects/f-flow/src/f-draggable/f-node-move/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ export * from './move-finalize';
1010

1111
export * from './f-line-alignment.drag-handler';
1212

13+
export * from './f-move-nodes.event';
14+
1315
export * from './f-node-move.drag-handler';
1416

1517
export * from './point-bounds-limiter';

projects/f-flow/src/f-draggable/f-node-move/move-finalize/f-node-move-finalize.execution.ts

Lines changed: 26 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,19 @@
1-
import { inject, Injectable } from '@angular/core';
2-
import { FNodeMoveFinalizeRequest } from './f-node-move-finalize.request';
3-
import { IPoint, Point } from '@foblex/2d';
4-
import { FExecutionRegister, FMediator, IExecution } from '@foblex/mediator';
5-
import { FComponentsStore } from '../../../f-storage';
6-
import { FDraggableDataContext } from '../../f-draggable-data-context';
1+
import {inject, Injectable} from '@angular/core';
2+
import {FNodeMoveFinalizeRequest} from './f-node-move-finalize.request';
3+
import {IPoint, Point} from '@foblex/2d';
4+
import {FExecutionRegister, FMediator, IExecution} from '@foblex/mediator';
5+
import {FComponentsStore} from '../../../f-storage';
6+
import {FDraggableDataContext} from '../../f-draggable-data-context';
77
import {
88
IsConnectionUnderNodeRequest
99
} from '../../domain';
10-
import { IFDragHandler } from '../../f-drag-handler';
11-
import { FNodeDropToGroupDragHandler } from '../../f-drop-to-group';
12-
import { ILineAlignmentResult, INearestCoordinateResult } from '../../../f-line-alignment';
13-
import { FLineAlignmentDragHandler } from '../f-line-alignment.drag-handler';
14-
import { FSummaryNodeMoveDragHandler } from '../f-summary-node-move.drag-handler';
15-
import { FNodeBase } from '../../../f-node';
10+
import {IFDragHandler} from '../../f-drag-handler';
11+
import {FNodeDropToGroupDragHandler} from '../../f-drop-to-group';
12+
import {ILineAlignmentResult, INearestCoordinateResult} from '../../../f-line-alignment';
13+
import {FLineAlignmentDragHandler} from '../f-line-alignment.drag-handler';
14+
import {FSummaryNodeMoveDragHandler} from '../f-summary-node-move.drag-handler';
15+
import {FNodeBase} from '../../../f-node';
16+
import {FMoveNodesEvent} from "../f-move-nodes.event";
1617

1718
@Injectable()
1819
@FExecutionRegister(FNodeMoveFinalizeRequest)
@@ -48,9 +49,19 @@ export class FNodeMoveFinalizeExecution implements IExecution<FNodeMoveFinalizeR
4849

4950
private _finalizeMove(difference: IPoint): void {
5051
this._getItems().forEach((x) => {
51-
x.onPointerMove({ ...difference });
52+
x.onPointerMove({...difference});
5253
x.onPointerUp?.();
5354
});
55+
56+
if (this._getItems().length) {
57+
const event = this._getItems()[0].fData.fNodeIds.map((id: string) => {
58+
return {
59+
id,
60+
position: this._fComponentsStore.fNodes.find(x => x.fId === id)!.position,
61+
}
62+
});
63+
this._fComponentsStore.fDraggable?.fMoveNodes.emit(new FMoveNodesEvent(event));
64+
}
5465
}
5566

5667
private _getItems(): IFDragHandler[] {
@@ -99,10 +110,10 @@ export class FNodeMoveFinalizeExecution implements IExecution<FNodeMoveFinalizeR
99110
}
100111

101112
private _isDraggedJustOneNode(): boolean {
102-
return (this._fDraggableDataContext.draggableItems[ 0 ] as FSummaryNodeMoveDragHandler).fHandlers.length === 1;
113+
return (this._fDraggableDataContext.draggableItems[0] as FSummaryNodeMoveDragHandler).fHandlers.length === 1;
103114
}
104115

105116
private _getFirstNodeOrGroup(): FNodeBase {
106-
return (this._fDraggableDataContext.draggableItems[ 0 ] as FSummaryNodeMoveDragHandler).fHandlers[ 0 ].fNode;
117+
return (this._fDraggableDataContext.draggableItems[0] as FSummaryNodeMoveDragHandler).fHandlers[0].fNode;
107118
}
108119
}

public/markdown/examples/drag-handle.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,27 @@
44

55
This example showcases how to add a [DragHandle](./docs/f-drag-handle-directive) for nodes, allowing users to move them easily within Angular and Foblex Flow.
66

7+
The `fDragHandle` directive can be applied to any HTML element inside a node `fNode`. It defines which part of the node should respond to drag gestures. This enables you to:
8+
- Make the entire node draggable
9+
- Limit dragging to a specific element like an icon, header, or image
10+
- Build flexible and intuitive drag behavior for custom UIs
11+
12+
#### How to Handle Position Changes
13+
14+
To track and persist node positions, you should handle the `fNodePositionChange` event emitted by each node. This event returns a new position of the node `{ x: number, y: number }` whenever it is moved.
15+
16+
You can use this event to:
17+
- Save the new position to a model or store
18+
- Sync changes to a backend or local storage
19+
- Trigger layout adjustments or constraints
20+
21+
In addition, the `fMoveNodes` event emitted by `<f-flow>` is triggered whenever one or more nodes are moved, including single-node moves.
22+
It provides an array of updated nodes, each with its new position, making it ideal for:
23+
24+
- Tracking bulk movements (e.g., group drag)
25+
- Implementing `undo/redo` systems
26+
- Managing external logic like snapping or alignment
27+
728
## Example
829

930
::: ng-component <drag-handle></drag-handle> [height]="600"

src/app/examples.config.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,11 @@ function nodesGroup() {
154154
image_width: 795,
155155
image_height: 600,
156156
image_type: 'image/png',
157-
date: new Date('2024-10-02 16:04:08')
157+
date: new Date('2025-19-07 13:00:22'),
158+
badge: {
159+
text: 'Updated',
160+
type: 'warning'
161+
},
158162
}, {
159163
link: 'node-selection',
160164
text: 'Node Selection',

0 commit comments

Comments
 (0)