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:
| Task | Access |
|---|---|
| Keep a controller for a parent component’s event handlers | onfirstcreate |
| Render from a surrounding widget’s state | The framework-specific context helper or hook below |
| Set the first layout, including SSR | initialLayout in server-rendering configuration |
| Replace a layout after initialization | importLayout() 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.
| Call | What 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> | Callback | Fires when |
|---|---|
onWidgetGrab | The user picks a widget up, by pointer or keyboard. |
onWidgetDrop | A move commits, before its animation settles. sourceTarget is where it came from; it is undefined for a widget that arrived through a FlexiAdd. |
onWidgetCancel | The user presses Escape, or lets go where nothing accepts the widget. The widget is back where it started. |
onWidgetDelete | A widget is dropped on a FlexiDelete, or widget.delete() is called. |
onWidgetResize | A resize the user was making commits. |
onWidgetEnterTarget, onWidgetLeaveTarget | A widget being moved is carried over a target, or leaves it. Useful for styling a column while it is the candidate. |
canDrop | While the user hovers and again on release. Return false to refuse; the drop preview shows the rejection and the widget returns to its origin. |
onLayoutChange | Accepted 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.