Skip to content

Events

Edit this page

Widgets communicate with your application through events. Tui uses the Symfony EventDispatcher, so all events flow through a single dispatcher shared by the entire widget tree.

Per-Widget Listeners

The most common way to handle events is to register a callback on the widget that emits them:

1
2
3
4
5
6
7
8
9
use Symfony\Component\Tui\Event\SubmitEvent;

$input->onSubmit(function (SubmitEvent $event) {
    $value = $event->getValue();
});

$input->onCancel(function () use ($tui) {
    $tui->stop();
});

Per-widget listeners are automatically scoped to the target widget. They are stored on the widget itself, so they are kept when you remove the widget from the tree and add it back later. While the widget is detached, its own listeners are still called, but global listeners don't receive its events.

8.2

In Symfony versions prior to 8.2, per-widget listeners were removed when the widget was detached from the tree.

Removing Per-Widget Listeners

The onSubmit(), onCancel(), etc. methods are shortcuts of the generic on() method. Use the off() method to remove the listeners registered with any of them:

1
2
3
4
5
6
7
8
9
10
$listener = function (SubmitEvent $event) {
    // ...
};
$input->onSubmit($listener);

// removes all the registrations of this listener for this event
$input->off(SubmitEvent::class, $listener);

// removes all the listeners of this widget for this event
$input->off(SubmitEvent::class);

8.2

The off() method was introduced in Symfony 8.2.

Listeners are compared in the same way as in removeListener(). A first-class callable such as $service->handle(...) creates a new closure on each call, but off() still considers it equal to the one passed to on(). Closures defined with function () { ... } or fn () => ... are not equal to other closures defined elsewhere, which is why the example above stores the closure in a variable.

The off() method only removes the listeners stored on the widget. It doesn't remove the global listeners registered with addListener().

Global Listeners

Register a listener on the Tui to catch events from any widget. The event class is inferred from the listener's type hint:

1
2
3
4
5
6
use Symfony\Component\Tui\Event\CancelEvent;

$tui->addListener(function (CancelEvent $event) {
    // Fires when ANY widget dispatches CancelEvent
    $tui->stop();
});

This is useful when multiple widgets should trigger the same behavior (e.g. stopping the Tui on cancel).

When you need to distinguish which widget fired the event, use getTarget():

1
2
3
4
5
6
7
use Symfony\Component\Tui\Event\SubmitEvent;

$tui->addListener(function (SubmitEvent $event) use ($editor) {
    if ($event->getTarget() === $editor) {
        // handle editor submit
    }
});

Global Input Interceptor

InputEvent is dispatched before focus navigation and before the focused widget receives input. Register a listener with Tui::addListener() to intercept raw input. Call stopPropagation() to consume the input and prevent further processing:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
use Symfony\Component\Tui\Event\InputEvent;
use Symfony\Component\Tui\Input\Keybindings;

$keys = new Keybindings(['quit' => ['ctrl+c', 'ctrl+q']]);
$tui->addListener(function (InputEvent $event) use ($tui, $keys): void {
    if ($keys->matches($event->getData(), 'quit')) {
        $tui->stop();
    }
});

$tui->addListener(function (InputEvent $event): void {
    if ('?' === $event->getData()) {
        // show a help overlay
        $event->stopPropagation();
    }
});

Multiple listeners can be registered; they run in priority order. The input flow is: InputEvent listeners, then focus manager (F6 cycling), then the focused widget.

Event Types

  • InputEvent: raw terminal input received. Dispatched before focus handling; call stopPropagation() to consume the input.
  • SubmitEvent: the user confirmed input (Enter). Carries a text value.
  • CancelEvent: the user cancelled (Escape, Ctrl+C).
  • ChangeEvent: the text content changed. Carries a text value.
  • SelectEvent: the user picked an item from a list. Carries the value, label and full item array.
  • SelectionChangeEvent: the highlighted item changed (arrow keys, scroll).
  • MultiSelectEvent: the user confirmed the checked items of a multiselect list. Carries the checked items and their values.
  • SelectionToggleEvent: an item of a multiselect list was checked or unchecked. Carries the toggled item, its new state and all the items checked after the toggle.
  • SettingChangeEvent: a setting value changed. Carries the setting id and new value.
  • TabChangeEvent: the active tab changed. Carries previous and new indices/ids.
  • FocusEvent: focus moved between widgets. Carries the new and previous widget.
This work, including the code samples, is licensed under a Creative Commons BY-SA 3.0 license.
TOC
    Version