Convenience Methods

The WithWireFlow trait includes convenience methods that wrap common multi-step operations into single calls. These cover moving nodes, focusing the camera, creating connections, visual feedback, and visibility/lock state.

All convenience methods

Method Description
flowUpdate(array $targets, array $options) Update nodes/edges/viewport instantly
flowAnimate(array $targets, array $options) Animate nodes/edges/viewport with smooth transitions (300ms default)
flowMoveNode(string $id, float $x, float $y, ?int $duration) Move a single node. Instant by default, pass duration for smooth transition.
flowUpdateNode(string $id, array $changes, ?int $duration) Update any node properties (position, data, style, class, etc.)
flowFocusNode(string $id, ?int $duration, float $padding) Pan and zoom to center on a specific node. Defaults to 300ms smooth.
flowConnect(string $source, string $target, ?int $duration, ?string $edgeId, array $options) Create an edge between two nodes. Pass duration for draw-in animation. Auto-generates edge ID if omitted.
flowDisconnect(string $source, string $target, ?int $duration) Remove edge(s) between two nodes. Pass duration for fade-out animation.
flowHighlightNode(string $id, string $style, ?int $duration) Flash a preset visual state then revert. Presets: 'success', 'error', 'warning', 'info'. Default: 1500ms.
flowHighlightPath(array $nodeIds, array $options) Fire particles along edges connecting a sequence of nodes. Options: color, size, duration, delay.
flowLockNode(string $id) Lock a node (prevent drag, show dashed border)
flowUnlockNode(string $id) Unlock a node
flowHideNode(string $id) Hide a node from rendering
flowShowNode(string $id) Show a hidden node
flowSelectNodes(array $ids) Select specific nodes (deselects all others first)
flowSelectEdges(array $ids) Select specific edges (deselects all others first)

flowMoveNode

Move a single node to new coordinates. Pass duration in milliseconds for a smooth transition, or omit for an instant move.

// Instant move
$this->flowMoveNode('step-3', 400, 200);

// Smooth move over 500ms
$this->flowMoveNode('step-3', 400, 200, duration: 500);

flowUpdateNode

Update any combination of node properties. Merges with existing values.

// Update label and style
$this->flowUpdateNode('step-1', [
    'data' => ['label' => 'Updated Label', 'status' => 'complete'],
    'class' => 'bg-green-100 border-green-500',
]);

// Animate position + data change over 400ms
$this->flowUpdateNode('step-1', [
    'position' => ['x' => 500, 'y' => 100],
    'data' => ['label' => 'Moved!'],
], duration: 400);

flowFocusNode

Pan and zoom the viewport to center on a specific node. Defaults to a 300ms smooth transition.

// Default smooth focus
$this->flowFocusNode('step-2');

// Custom duration and padding
$this->flowFocusNode('step-2', duration: 600, padding: 100);

flowConnect / flowDisconnect

Create or remove edges between nodes. Optionally animate the transition.

// Create edge instantly
$this->flowConnect('step-1', 'step-2');

// Create edge with draw-in animation and custom ID
$this->flowConnect('step-1', 'step-2', duration: 600, edgeId: 'approval-edge');

// Create edge with additional options
$this->flowConnect('step-1', 'step-2', duration: 400, options: [
    'type' => 'smoothstep',
    'animated' => true,
]);

// Remove edge(s) between nodes
$this->flowDisconnect('step-1', 'step-2');

// Remove with fade-out animation
$this->flowDisconnect('step-1', 'step-2', duration: 300);

flowHighlightNode

Flash a preset visual style on a node, then automatically revert. Four presets are available:

Preset Visual
'success' Green glow
'error' Red glow
'warning' Amber glow
'info' Blue glow
// Default 1500ms highlight
$this->flowHighlightNode('step-1', 'success');

// Custom duration
$this->flowHighlightNode('step-1', 'error', duration: 3000);

flowHighlightPath

Fire particles along edges connecting a sequence of nodes, creating a cascading trail effect.

// Simple path highlight
$this->flowHighlightPath(['step-1', 'step-2', 'step-3']);

// Customized particles
$this->flowHighlightPath(['step-1', 'step-2', 'step-3'], [
    'color' => '#22c55e',
    'size' => 6,
    'duration' => 1000,
    'delay' => 200,       // Delay between each segment
]);

flowLockNode / flowUnlockNode

Lock prevents dragging and shows a dashed border. Unlock restores normal interaction.

$this->flowLockNode('step-1');    // Prevent drag, show dashed border
$this->flowUnlockNode('step-1'); // Restore normal interaction

flowHideNode / flowShowNode

Toggle node visibility without removing it from the data.

$this->flowHideNode('debug-panel');
$this->flowShowNode('debug-panel');

flowSelectNodes / flowSelectEdges

Programmatically select nodes or edges. Deselects all others first.

$this->flowSelectNodes(['step-1', 'step-3']);
$this->flowSelectEdges(['e-1-2']);

Example: Workflow step approval

A complete approval workflow that locks the completed step, highlights it green, draws in the connecting edge, fires particles along the path, and focuses the next step:

<?php

namespace App\Livewire;

use ArtisanFlow\WireFlow\Concerns\WithWireFlow;
use Livewire\Attributes\Renderless;
use Livewire\Component;

class ApprovalFlow extends Component
{
    use WithWireFlow;

    public array $nodes = [
        ['id' => 'draft', 'position' => ['x' => 0, 'y' => 0], 'data' => ['label' => 'Draft', 'status' => 'current']],
        ['id' => 'review', 'position' => ['x' => 300, 'y' => 0], 'data' => ['label' => 'Review', 'status' => 'pending']],
        ['id' => 'approved', 'position' => ['x' => 600, 'y' => 0], 'data' => ['label' => 'Approved', 'status' => 'pending']],
    ];

    public array $edges = [];

    #[Renderless]
    public function approve(string $stepId): void
    {
        $nextStep = $this->getNextStep($stepId);

        $this->flowLockNode($stepId);
        $this->flowHighlightNode($stepId, 'success');
        $this->flowConnect($stepId, $nextStep, duration: 600);
        $this->flowHighlightPath([$stepId, $nextStep]);
        $this->flowFocusNode($nextStep);
    }

    #[Renderless]
    public function reject(string $stepId): void
    {
        $this->flowHighlightNode($stepId, 'error');
        $this->flowFocusNode($stepId);
    }

    private function getNextStep(string $current): string
    {
        $order = ['draft', 'review', 'approved'];
        $index = array_search($current, $order);

        return $order[$index + 1] ?? $order[$index];
    }

    public function render()
    {
        return view('livewire.approval-flow');
    }
}
{{-- resources/views/livewire/approval-flow.blade.php --}}
<div>
    <x-flow :nodes="$nodes" :edges="$edges" :fit-view-on-init="true" style="height: 400px;">
        <x-slot:node>
            <x-flow-handle type="target" position="left" />
            <div class="p-3">
                <div class="font-semibold" x-text="node.data.label"></div>
                <div class="mt-2 flex gap-1">
                    <button
                        x-show="node.data.status === 'current'"
                        x-on:click="$wire.approve(node.id)"
                        class="rounded bg-green-500 px-2 py-1 text-xs text-white"
                    >
                        Approve
                    </button>
                    <button
                        x-show="node.data.status === 'current'"
                        x-on:click="$wire.reject(node.id)"
                        class="rounded bg-red-500 px-2 py-1 text-xs text-white"
                    >
                        Reject
                    </button>
                </div>
            </div>
            <x-flow-handle type="source" position="right" />
        </x-slot:node>
    </x-flow>
</div>
<div x-data="flowCanvas({
    nodes: [
        { id: 'submit', position: { x: 0, y: 50 }, data: { label: 'Submit' } },
        { id: 'review', position: { x: 200, y: 50 }, data: { label: 'Review' } },
        { id: 'done', position: { x: 400, y: 50 }, data: { label: 'Done' } },
    ],
    edges: [
        { id: 'e1', source: 'submit', target: 'review' },
        { id: 'e2', source: 'review', target: 'done' },
    ],
    background: 'dots',
    fitViewOnInit: true,
    controls: false,
    pannable: false,
    zoomable: false,
})" class="flow-container" style="height: 200px;"
   x-init="
        document.getElementById('demo-approve').addEventListener('click', () => {
            const n = nodes.find(n => n.id === 'review');
            if (n) { n.class = 'flow-node-success'; n.draggable = false; }
            edges.filter(e => e.source === 'review').forEach(e => {
                $flow.sendParticle(e.id, { color: '#14B8A6', size: 4, duration: '1s' });
            });
        });
        document.getElementById('demo-reset').addEventListener('click', () => {
            const n = nodes.find(n => n.id === 'review');
            if (n) { n.class = ''; n.draggable = true; }
        });
   ">
    <div x-flow-viewport>
        <template x-for="node in nodes" :key="node.id">
            <div x-flow-node="node">
                <div x-flow-handle:target></div>
                <span x-text="node.data.label"></span>
                <div x-flow-handle:source></div>
            </div>
        </template>
    </div>
</div>

Example: Move and focus

#[Renderless]
public function repositionNode(string $id, float $x, float $y): void
{
    $this->flowMoveNode($id, $x, $y, duration: 500);
    $this->flowFocusNode($id, duration: 400);
}

Example: Remote control panel

A panel of buttons demonstrating several convenience methods working together:

<?php

namespace App\Livewire;

use ArtisanFlow\WireFlow\Concerns\WithWireFlow;
use Livewire\Attributes\Renderless;
use Livewire\Component;

class RemoteControl extends Component
{
    use WithWireFlow;

    public array $nodes = [
        ['id' => 'a', 'position' => ['x' => 0, 'y' => 0], 'data' => ['label' => 'Node A']],
        ['id' => 'b', 'position' => ['x' => 300, 'y' => 0], 'data' => ['label' => 'Node B']],
        ['id' => 'c', 'position' => ['x' => 150, 'y' => 200], 'data' => ['label' => 'Node C']],
    ];

    public array $edges = [
        ['id' => 'e-a-b', 'source' => 'a', 'target' => 'b'],
        ['id' => 'e-b-c', 'source' => 'b', 'target' => 'c'],
    ];

    #[Renderless]
    public function moveNodeA(): void
    {
        $this->flowMoveNode('a', 100, 100, duration: 600);
    }

    #[Renderless]
    public function selectAll(): void
    {
        $this->flowSelectNodes(['a', 'b', 'c']);
    }

    #[Renderless]
    public function highlightSuccess(): void
    {
        $this->flowHighlightNode('b', 'success');
    }

    #[Renderless]
    public function connectAC(): void
    {
        $this->flowConnect('a', 'c', duration: 500);
    }

    #[Renderless]
    public function lockB(): void
    {
        $this->flowLockNode('b');
    }

    #[Renderless]
    public function unlockB(): void
    {
        $this->flowUnlockNode('b');
    }

    public function render()
    {
        return view('livewire.remote-control');
    }
}
{{-- resources/views/livewire/remote-control.blade.php --}}
<div>
    <div class="mb-4 flex flex-wrap gap-2">
        <button wire:click="moveNodeA" class="rounded bg-blue-500 px-3 py-1 text-sm text-white">Move A</button>
        <button wire:click="selectAll" class="rounded bg-purple-500 px-3 py-1 text-sm text-white">Select All</button>
        <button wire:click="highlightSuccess" class="rounded bg-green-500 px-3 py-1 text-sm text-white">Highlight B</button>
        <button wire:click="connectAC" class="rounded bg-indigo-500 px-3 py-1 text-sm text-white">Connect A-C</button>
        <button wire:click="lockB" class="rounded bg-amber-500 px-3 py-1 text-sm text-white">Lock B</button>
        <button wire:click="unlockB" class="rounded bg-gray-500 px-3 py-1 text-sm text-white">Unlock B</button>
    </div>

    <x-flow :nodes="$nodes" :edges="$edges" :fit-view-on-init="true" style="height: 400px;">
        <x-slot:node>
            <x-flow-handle type="target" position="top" />
            <span x-text="node.data.label"></span>
            <x-flow-handle type="source" position="bottom" />
        </x-slot:node>
    </x-flow>
</div>
<div x-data="flowCanvas({
    nodes: [
        { id: 'a', position: { x: 50, y: 50 }, data: { label: 'Alpha' } },
        { id: 'b', position: { x: 300, y: 50 }, data: { label: 'Beta' } },
        { id: 'c', position: { x: 175, y: 180 }, data: { label: 'Gamma' } },
    ],
    edges: [
        { id: 'e1', source: 'a', target: 'b' },
        { id: 'e2', source: 'b', target: 'c' },
    ],
    background: 'dots',
    fitViewOnInit: true,
})" class="flow-container" style="height: 280px;"
   x-init="
        document.getElementById('demo-rc-move').addEventListener('click', () => {
            const n = nodes.find(n => n.id === 'c');
            if (n) { n.position = { x: n.position.x + 30, y: n.position.y }; }
        });
        document.getElementById('demo-rc-highlight').addEventListener('click', () => {
            const n = nodes.find(n => n.id === 'b');
            if (n) n.class = n.class === 'flow-node-warning' ? '' : 'flow-node-warning';
        });
        document.getElementById('demo-rc-lock').addEventListener('click', () => {
            const n = nodes.find(n => n.id === 'a');
            if (n) { n.draggable = !n.draggable; n.class = n.draggable === false ? 'flow-node-muted' : ''; }
        });
        document.getElementById('demo-rc-focus').addEventListener('click', () => {
            $flow.fitView({ nodes: ['c'], duration: 300, padding: 0.5 });
        });
   ">
    <div x-flow-viewport>
        <template x-for="node in nodes" :key="node.id">
            <div x-flow-node="node">
                <div x-flow-handle:target></div>
                <span x-text="node.data.label"></span>
                <div x-flow-handle:source></div>
            </div>
        </template>
    </div>
</div>