v1.0
Introduction / Page 01·03

Controllers

Learn how to manipulate Flexiboard components via their controllers.

A controller exposes a board, target, or widget’s state and actions. Choose access based on where your code runs:

TaskAccess
Keep a controller for a parent component’s event handlersonfirstcreate
Render from a surrounding widget’s stateThe framework-specific context helper or hook below
Set the first layout, including SSRinitialLayout in server-rendering configuration
Replace a layout after initializationimportLayout() on a stored controller

The examples in the access sections are excerpts. Insert your existing targets and widgets where indicated; they focus on how to obtain the controller.

Method 1: onfirstcreate callback

Components with controllers accept onfirstcreate. The callback receives the controller once. Use it to keep a reference for later actions. Timing depends on the adapter, as described below.

<script lang="ts">
	import { FlexiBoard, type FlexiBoardController } from '@flexiboards/svelte';

	let board = $state<FlexiBoardController>();

	function rememberBoard(controller: FlexiBoardController) {
		board = controller;
	}
</script>

<FlexiBoard onfirstcreate={rememberBoard}>
	<!-- ... -->
</FlexiBoard>

The callback runs during component setup, including SSR. Keep browser-only work in client event handlers or effects. Use initialLayout for data that must appear in the server-rendered board.

Method 2: bind:controller prop

Bind controller to a state variable when the parent needs the instance. Guard reads before initialization.

Here’s an example of using it to access the controller of a FlexiBoard:

<script lang="ts">
	import { FlexiBoard, type FlexiBoardController } from '@flexiboards/svelte';

	let boardController = $state<FlexiBoardController>();

	function clearBoard() {
		boardController?.clear();
	}
</script>

<button onclick={clearBoard}>Clear board</button>
<FlexiBoard bind:controller={boardController}>
	<!-- Existing targets and widgets. -->
</FlexiBoard>

The button handler runs on the client after initialization. For setup-time controller access, use onfirstcreate; for server-provided layout data, use initialLayout.

By the time the onfirstcreate callback fires, any variable bound to controller already holds the controller instance. If you prefer, you can read that variable rather than the controller parameter passed to the callback.

Method 3: context helper

From any component rendered inside a Flexiboards component, you can reach the surrounding widget’s controller through the getFlexiwidgetCtx() helper. It uses the Svelte Context API under the hood, so it must be called from the top level of a component.

<!-- my-widget-content.svelte -->
<script lang="ts">
	import { getFlexiwidgetCtx } from '@flexiboards/svelte';

	const widget = getFlexiwidgetCtx();
</script>

<div class:opacity-50={widget.isGrabbed}>...</div>

Changing the board from code

Once you hold a controller you can change the board without a drag. Placement actions run the grid rules. Successful layout mutations notify onLayoutChange; import and export have separate behavior shown below.

CallWhat it does
widget.delete()Removes the widget from its target. Fires onWidgetDelete.
widget.moveTo({ x, y })Moves the widget within its target. Returns false and leaves it in place if the grid refuses the spot.
widget.moveTo({ target })Moves the widget to another target, wherever that target’s grid puts it. Pass x and y too to choose the cell.
target.createWidget(config)Adds a widget. Returns undefined if it cannot be placed.
target.clear()Deletes every widget in the target.
board.clear()Deletes every widget in every target.
board.importLayout(layout)Replaces widgets in targets named by the saved layout, bare or in a { version, layout } envelope. Does not fire onLayoutChange.
board.exportLayoutEnvelope()The layout with its format version, the shape to persist. See Exporting & Importing.

This excerpt assumes done and doing are target controllers from the same board:

// Move the first "done" card back into "doing", at the top.
const card = done.widgets.values().next().value;
card?.moveTo({ target: doing, x: 0, y: 0 });

moveTo bypasses canDrop. Validate application permissions before calling it; grid placement rules still apply. A refused move returns false. A successful placement notifies even if the coordinates are unchanged. Clears notify only if widgets are removed.

Reacting to interactions

Use canDrop for validation, grab and enter/leave callbacks for interaction progress, and drop/resize callbacks for committed changes. onLayoutChange reports the committed layout in a batched microtask before animations settle.

These configuration excerpts log accepted moves between targets and deletions. Keep your existing target declarations inside the board:

<script lang="ts">
	import { FlexiBoard, type FlexiBoardConfiguration } from '@flexiboards/svelte';

	const config: FlexiBoardConfiguration = {
		onWidgetDrop: ({ widget, sourceTarget, target }) => {
			if (sourceTarget !== target) {
				console.info('Moved', widget.userProvidedId ?? widget.id, 'to', target.key);
			}
		},
		onWidgetDelete: ({ widget }) => console.info('Deleted', widget.userProvidedId ?? widget.id),
		// Only the "done" column accepts cards that are marked complete.
		canDrop: ({ widget, target }) => target.key !== 'done' || widget.metadata?.complete === true
	};
</script>

<FlexiBoard {config}><!-- Existing targets and widgets. --></FlexiBoard>
CallbackFires when
onWidgetGrabThe user picks a widget up, by pointer or keyboard.
onWidgetDropA move commits, before its animation settles. sourceTarget is where it came from; it is undefined for a widget that arrived through a FlexiAdd.
onWidgetCancelThe user presses Escape, or lets go where nothing accepts the widget. The widget is back where it started.
onWidgetDeleteA widget is dropped on a FlexiDelete, or widget.delete() is called.
onWidgetResizeA resize the user was making commits.
onWidgetEnterTarget, onWidgetLeaveTargetA widget being moved is carried over a target, or leaves it. Useful for styling a column while it is the candidate.
canDropWhile the user hovers and again on release. Return false to refuse; the drop preview shows the rejection and the widget returns to its origin.
onLayoutChangeAccepted drops/resizes and programmatic creation, movement, or deletion. Batched in a microtask with committed coordinates. Hover, validation, import, and export do not trigger it.

canDrop runs alongside the grid’s own rules (bounds, collisions, size limits), which apply whether or not you provide it. A target’s own configuration can carry a canDrop too, for rules that belong to one list rather than the board; both must agree.

Controller APIs

Each controller’s properties and methods are listed on its component’s page: FlexiBoard, FlexiTarget, and FlexiWidget.