Skip to main content

Controls

One control can appear as a button, inline widget, or combo-box item. Actions, settings, and forms use controls so these representations share one value and behavior.

Mental model

A control owns the value or behavior. Its button, inline widget, and combo-box items are different views of the same control.

Controls

AbstractControl defines three representations:

abstract representAsBtn(): Btn;
abstract representAsControl(options?: RepresentAsControlOptions): React.JSX.Element;
abstract representAsComboBoxItems(options?: RepresentAsComboBoxItemsEvent): ComboBoxItem[];

This lets a Boolean, date, entity selector, set selector, or action participate in a panel, floating menu, and combo box without duplicating its state transitions.

Common controls include:

  • BooleanControl
  • SetControl
  • DateControl
  • EntityControl
  • FileControl
  • ButtonControl
  • ActionButtonControl

AbstractValueControl owns a mutable value and emits value-change events. All of its renderers use that same value.

const status = new SetControl({
initialValue: 'review',
options: [
{ key: 'draft', label: 'Draft' },
{ key: 'review', label: 'In review' },
{ key: 'done', label: 'Done' }
]
});

status.registerListener({
valueChanged: (value) => saveStatus(value)
});

The same instance can render as a selector in a panel, supply a button descriptor to another widget, or generate items for a combo box.

Common pitfall

Do not create a separate control instance for every representation when those surfaces are meant to edit the same value. They will drift into independent state.

Action controls

ActionButtonControl adapts an event-bound action. It asks the action for its current button descriptor, keeping the label, icon, validation, indicator, and activation callback consistent.

const control = action.representAsControl({
eventData: { targetEntity: todo }
});

The Actions sandbox shows the same action rendered through standard button, icon-only, panel-sized control, compact control, and combo-box item representations.

Accept controls in reusable UI

Accept an AbstractControl when reusable UI needs a value or behavior but should not require one specific widget. The reusable UI can ask for the representation it needs:

function ToolbarValue({ control }: { control: AbstractControl }) {
return control.representAsControl({ size: LayoutContextSize.SMALL });
}

Settings, forms, entity selection, and actions all use this pattern. The surrounding UI chooses the layout; the control manages the value or behavior.

Implementing a control

Implement all three representations even if one is the primary surface. representAsBtn() should return a descriptor and not render React itself. representAsControl() selects the standard Reactor widget for the current context. representAsComboBoxItems() exposes equivalent choices or activation behavior to menus.

Keep state and callbacks in the control. Do not make each representation maintain its own selection state. For value controls, update value so registered listeners and every active representation observe the same change.

Pro tip

Accept AbstractControl in reusable components. Callers can supply a different control without changing the component.

Controls in forms and settings

ControlInput embeds a control in a form. AbstractUserSetting embeds one in the settings system. Both preserve the control as the source of interaction state while adding their own concerns:

  • forms add labels, validation, visibility, and submission values;
  • settings add keys for saved data, defaults, categories, and readiness.

See Forms for input modeling and Settings and persistence for persisted controls.

Go deeper