Search field (external piece)
Status: implemented as
day-piece-searchfield, an external Day Piece (likeday-piece-combobox) registered link-time into each backend’s renderer slice without touching day. One API: a native search input, bound two-way to aSignal<String>, realized as each toolkit’s dedicated search control.
Authoring
use day_piece_searchfield::search_field;
let query = Signal::new(String::new());
search_field(query).placeholder("Search fruit…").id("search")
search_field(query) takes a Signal<String> bound two-way: every native edit writes the text back
to the signal, and setting the signal (e.g. a Clear button doing query.set(String::new())) patches the
control. .placeholder(impl IntoText) sets the empty-state prompt; it accepts a constant,
Signal<String>, or closure, read once for the initial value (the placeholder is fixed at build).
SearchField implements Piece, so .id()/.a11y()/.frame() chain via Decorate. Like day-core’s
text_field it is a width-growing leaf (grow_w = true, natural single-line height): a search
field fills its row, so constrain it with .frame(w, h) if you need a fixed width.
The signal is a controlled input (§4.4): a per-build echo guard remembers the last value that
arrived from the native control so bind_seeded does not patch that same value straight back (which
some toolkits would re-emit as a change → a feedback loop). The programmatic-sync side is additionally
guarded per backend (see the table).
Per-backend native realization
| AppKit | UIKit | GTK | Qt | Android | XAML |
|---|---|---|---|---|---|
NSSearchField | UISearchTextField (iOS 13+) | GtkSearchEntry | QLineEdit search shim (clear button + leading magnifier) | EditText (single-line, IME_ACTION_SEARCH) | AutoSuggestBox (query magnifier) |
Each control reports edits through Event::TextChanged(String), the same event a built-in text
field emits, so dayscript’s input: step drives the piece on every backend without touching native
code. The change plumbing per backend:
- AppKit: a per-node delegate implements
NSControlTextEditingDelegate::controlTextDidChange:. ProgrammaticsetStringValuedoes not fire that delegate, so no suppression is needed (update only writes when the value actually differs). - UIKit: a per-node target on
UIControlEvents::EditingChanged. ProgrammaticsetTextdoes not fireEditingChanged, so no suppression is needed. - GTK:
GtkSearchEntry::"search-changed". That signal does fire on programmaticset_text, so a per-nodesuppresscell guards the sync inupdate. - Qt / XAML: each carries its own C++ shim inside this crate (
src/lib-qt-shim.cpp,src/lib-xaml-shim.cpp), compiled by the crate’sbuild.rs. The Qt shim wraps aQLineEdit(setClearButtonEnabled(true)+ a leadingedit-findaction) and wraps programmaticsetTextinblockSignals. The XAML shim boxes itsAutoSuggestBoxinto a Day handle through theday_xaml_box/day_xaml_unboxseam thatday-xaml-sysexports, the same mechanism the picker/media XAML shims use, so a piece never touches day-xaml’s private handle wrapper. - Android: carries its own Java factory
(
android/java/dev/daybrite/day/piece/searchfield/DaySearch.java), folded into the app’s Gradle build automatically via[package.metadata.day.android], with no edits to day-android (see docs/extending.md). ATextWatchercallsDayBridge.nativeOnEvent(id, 1, …)(kind 1 =TextChanged); the programmatic setter guards on equality (a plainEditText, so no Gradle dependency or manifest permission).
Verification
The showcase Controls page (controls.rs search_section) binds a search_field to a query signal that filters a
small fruit list (Apple, Banana, Cherry, Date, Elderberry) case-insensitively. Each match is a
when-gated label in a column, and a #search-result label shows the first match. A Clear button
sets the signal to "" to prove the reverse binding patches the native field. The walkthrough navigates
to search, types "ch" into #search-input, asserts #search-result reads Cherry (the two-way
binding round-tripping the signal), taps #search-clear, and screenshots. Rust is clippy-clean
(-D warnings) and cargo fmt-clean for every backend feature; host-verified for AppKit/GTK/Qt/mock,
cross-compiled for iOS-sim (uikit) and Android (mdc).
Follow-ups
- Reactive placeholder (currently fixed at build).
- A submit rail (
Event::Submittedon the search-action key / return) for “search on enter” semantics. - Disabled/enabled state; scoped-search tokens (macOS
NSSearchFieldrecent-searches menu).
XAML is CI-only (built under cfg(windows) + the Windows SDK); it isn’t buildable on the macOS/Linux
dev hosts.