-
Notifications
You must be signed in to change notification settings - Fork 0
LayrzSelectInput
Single-value selection field with searchable dropdown list surface.
Specification Status:
LayrzSelectInputhas shipped and is implemented atlib/src/inputs/src/select/select_input.dart(+select_input_surface.dart). The sections below marked Shipped behavior describe the actual, current implementation and were corrected as part of DESIGN-40 (dropdown height rule) and DESIGN-144 (focus node wiring).Everything below that, starting at "Deltas from the Input Contract", is the original pre-implementation design sketch carried over from the
layrz_thememigration plan. It predates the actual implementation and does not match the shipped API in several places (for example: the shipped surface isLayrzAnchoredPanelon desktop /LayrzBottomSheeton mobile, notRawDialog; there is nodialogConstraints,itemExtent,overrideHeightDialog,autoclose,autoSelectFirst, orreturnNullOnClose;LayrzSelectItem<T>haslabelText,value,child, andsearchableAttributes— noicon,leading,onTap, orisRemoved). Reconciling the rest of this legacy sketch with the shipped widget is out of scope for DESIGN-40/144 and is left for a dedicated documentation pass.
The desktop selection surface follows one rule: height = min(content, 300.0), scroll past
300. This is enforced exactly once, via maxHeight: 300.0 passed to the LayrzAnchoredPanel
that anchors the surface below (or above) the field:
- If the filtered item list plus the search field fit in fewer than 300 logical pixels, the panel shrinks to content — it is never padded out to a fixed height.
- If content exceeds 300 logical pixels, the panel clamps to exactly 300 and scrolls.
The surface itself (LayrzSelectInputSurface) applies no height cap of its own — a second,
disagreeing cap inside the surface was the original defect (a fixed SizedBox(height: 300)
around a list separately limited to LimitedBox(maxHeight: 300), which together overflowed by
the search field's height once enough items were present). The rule is applied exactly once, by
the caller.
On mobile (compact viewports), the surface has no height cap of its own either — it renders
inside LayrzBottomSheet, whose own scrollable is the single scroll owner for that path.
LayrzSelectInput.focusNode (optional; an internal node is created and disposed when omitted)
is attached to the focus tree on both platforms:
-
Desktop: the field's anchor content is wrapped in
Focus(focusNode: ...), and the same node is passed asLayrzAnchoredPanel.childFocusNodeso focus returns to the field when the panel closes. -
Mobile (compact): the field's anchor content is likewise wrapped in
Focus(focusNode: ...).
A caller-supplied FocusNode is therefore genuinely usable — calling .requestFocus() on it
results in .hasFocus == true — on both platforms.
| Property | Value |
|---|---|
| Mirror | ThemedSelectInput<T> |
| Phase | M3 Inputs |
| Domain | Inputs |
| SDK Primitive | Composes LayrzTextInput (read-only) + RawDialog for dropdown surface |
LayrzSelectInput conforms to the Input Contract. Like all picker-style inputs, it composes LayrzTextInput internally in read-only mode and opens a selection surface on tap. The entire contract applies: labels, prefix/suffix, help affordance, focus management, padding, and validation error display.
-
Value type: Generic
T— any comparable type - Selection surface: Searchable dropdown dialog with a single-selection list
- Summary display: The field displays the label of the selected item; unselected state shows placeholder or empty
-
Item support type:
LayrzSelectItem<T>(derived from layrz_theme'sThemedSelectItem<T>)
LayrzSelectInput introduces a support type, LayrzSelectItem<T>, to represent each selectable item:
// Design sketch — illustrative only
class LayrzSelectItem<T> {
/// Human-readable label displayed in the dropdown list.
final String label;
/// The value returned when this item is selected.
final T? value;
/// Optional icon displayed in the list and/or prefix.
final IconData? icon;
/// Optional custom widget displayed in the list (overrides icon and label).
final Widget? content;
/// Optional custom leading widget (e.g., avatar, thumbnail).
final Widget? leading;
/// Callback invoked when this item is tapped in the list.
final VoidCallback? onTap;
/// Attributes to include in search filtering (e.g., category, code).
final Set<String> searchableAttributes;
const LayrzSelectItem({
required this.label,
required this.value,
this.icon,
this.content,
this.leading,
this.onTap,
this.searchableAttributes = const {},
});
}// Design sketch — illustrative only
class LayrzSelectInput<T> extends StatefulWidget {
/// List of items available for selection.
final List<LayrzSelectItem<T>> items;
/// Callback invoked when user selects an item.
/// Receives the selected item or null if unselected.
final void Function(LayrzSelectItem<T>?)? onChanged;
/// Currently selected value (matched against LayrzSelectItem.value).
final T? value;
/// Whether to enable search filtering in the dropdown.
final bool enableSearch;
/// Whether to automatically close the dropdown after selection.
final bool autoclose;
/// Whether the user can deselect the current item (uncheck it).
final bool canUnselect;
/// Custom filter function for search.
/// If null, performs case-insensitive label search.
final bool Function(String searchText, LayrzSelectItem<T>)? filter;
/// Text displayed when the filtered list is empty.
final String? emptyListText;
/// Constraints for the dropdown dialog size.
final BoxConstraints dialogConstraints;
/// Height of each list item (affects scrollable area).
final double itemExtent;
const LayrzSelectInput({
required this.items,
this.onChanged,
this.value,
this.enableSearch = true,
this.autoclose = true,
this.canUnselect = false,
this.filter,
this.emptyListText,
this.dialogConstraints = const BoxConstraints(maxWidth: 500, maxHeight: 500),
this.itemExtent = 50,
// ... shared contract parameters (label, placeholder, prefix, suffix, etc.)
});
}All of the following are inherited from LayrzTextInput:
- labelText and label (mutually exclusive)
- placeholder
- prefixIcon, prefixWidget, onPrefixTap (mutually exclusive icon and widget)
- suffixIcon, suffixWidget, onSuffixTap (mutually exclusive icon and widget)
- helpTitleText, helpContentText (two-part help tooltip)
- readOnly (always true for select input)
- onTap (opens the dropdown; can be overridden by caller if needed)
- focusNode, controller (focus and value management)
- padding (defaults to spacing tokens)
- disabled and error display
ThemedSelectInput<T> (source: lib/src/inputs/src/general/select_input.dart):
| Parameter | Type | Notes |
|---|---|---|
labelText |
String? |
Label text (or use label Widget instead) |
label |
Widget? |
Label widget (mutually exclusive with labelText) |
items |
List<ThemedSelectItem<T>> |
List of selectable items |
value |
T? |
Currently selected value |
onChanged |
void Function(ThemedSelectItem<T>?)? |
Callback when item selected |
prefixIcon |
IconData? |
Icon in prefix slot |
prefixText |
String? |
Text in prefix slot |
onPrefixTap |
VoidCallback? |
Prefix tap handler |
enableSearch |
bool |
Enable search in dropdown (default: true) |
autoclose |
bool |
Close after selection (default: true) |
canUnselect |
bool |
Allow deselection (default: false) |
disabled |
bool |
Disable the field (default: false) |
errors |
List<String> |
Error messages (default: []) |
hideDetails |
bool |
Hide error/helper text (default: false) |
hideTitle |
bool |
Hide title in dropdown dialog (default: false) |
isRequired |
bool |
Mark field as required (default: false) |
padding |
EdgeInsets? |
Field padding |
filter |
bool Function(String, ThemedSelectItem<T>)? |
Custom search filter |
dialogConstraints |
BoxConstraints |
Dropdown dialog size |
itemExtent |
double |
Height of each list item (default: 50) |
overrideHeightDialog |
double? |
Override computed dialog height |
customChild |
Widget? |
Replace entire input with custom widget |
returnNullOnClose |
bool |
Return null if dialog closed without selection (default: false) |
autoSelectFirst |
bool |
Auto-select first item on first render (default: false) |
translations |
Map<String, String> |
i18n strings (cancel, save, search, empty messages) |
overridesLayrzTranslations |
bool |
Use custom translations over defaults (default: false) |
ThemedSelectItem<T>:
-
label(String): Item display label -
value(T?): Item value -
icon(IconData?): Icon to display -
leading(Widget?): Custom leading widget -
content(Widget?): Custom content (overrides label/icon/leading) -
onTap(VoidCallback?): Item tap callback -
searchableAttributes(Set): Additional searchable text -
isRemoved(bool): Marks item as removed (forcanUnselectflow)
- LayrzTextInput — must ship first; LayrzSelectInput composes it.
-
LayrzTooltip — required for help affordances if using
helpTitleText/helpContentText. - Material-free TextSelectionControls — if LayrzTextInput requires copy/paste in read-only mode, this blocker applies.
-
Naming: Is
LayrzSelectItem<T>the correct name, or should it follow a different pattern (e.g.,SelectOption<T>,MenuItem<T>)? -
Value update semantics: When
valuechanges externally, does the field automatically find and display the matchingLayrzSelectItem, or is the caller responsible for keeping the value in sync with the items list? -
Dialog positioning: Should the dropdown be smart-positioned (above/below based on available space), or always below the field?
-
Custom item rendering: Does
LayrzSelectItem.contenttake precedence overicon,leading, andlabel, or are there specific rendering priorities? -
Search filtering: If
filteris not supplied, does it search all attributes (label + searchableAttributes), or label only? -
Unselect UI: When
canUnselectis true, how is the unselect action presented — a separate button, toggle behavior on the selected item, or both? -
Empty state text: Is there a default empty-list message, or must the caller provide one?
-
Keyboard navigation: Are arrow keys, Enter, and Escape supported in the dropdown list for desktop?
Last updated: 2026-08-25 (DESIGN-40 / DESIGN-144 — selection surface height rule and focus node
wiring corrected to match the shipped implementation; the remaining pre-implementation sections
are unchanged and still need a full documentation pass)
Related documents: Input Contract, Component Catalog, Design Tokens
Made with ❤️ by Golden M, Inc.
- LayrzAnchoredPanel
- LayrzBottomSheet
- LayrzDialog
- LayrzDropdownMenu
- LayrzResponsiveModal
- LayrzPageTransition
- LayrzTextInput
- LayrzSelectableAction
- LayrzSelectionToolbar
- LayrzTextSelectionControls
- LayrzSelectionMagnifier
- LayrzSelectionHandlePainter
- LayrzTextAreaInput
- LayrzComboBoxInput
- LayrzNumberInput
- LayrzPasswordInput
- LayrzCheckboxInput
- LayrzRadioInput
- LayrzSelectInput
- LayrzMultiSelectInput
- LayrzSearchInput
- LayrzDualListInput
- LayrzDurationInput
- LayrzSlider
- LayrzStepper