Text input
<text-input> is built to work with Signal Forms with no adapter code at all. Signal Forms'
FormField directive looks for a value model on a value control, and <text-input>'s value is
a model(), so <text-input [formField]="f.name" /> is the entire integration. It also works as a
plain two-way binding without a form at all: [(value)].
import { TextInput, InputAccessoryView } from '@ng-native/components';
<text-input><input-accessory-view> once they are in the component's imports<text-input placeholder="Name" [formField]="profile.name" />The Signal Forms contract
<text-input> carries the whole control contract FormField writes to, not just value:
disabled and readonly both turn editable off (disabled also wins the announced
accessibility state); invalid and touched are published as the data-invalid and
data-touched attributes, since nothing native reads them - they exist for a stylesheet; and
touch fires from the native blur event, because there is no DOM blur for Signal Forms to listen
to instead.
text-input {
border-width: 1px;
border-color: #3a3a42;
}
text-input[data-invalid][data-touched] {
border-color: #ff6b6b;
}The point of writing data-invalid/data-touched only once both are true is the usual one: a
field an app has not touched yet should not look broken before anyone has had a chance to fill it
in.
Controlled, as in React Native
Bind value and the field shows what your state says. A keystroke emits valueChange and
changeText; whatever your state holds once the handler has run is what the field ends up
showing. Take the text as typed, refuse it, or rewrite it:
<!-- Digits only: anything else leaves the field as it was. -->
<text-input [value]="pin()" (changeText)="isDigits($event) && pin.set($event)" />
<!-- Shows what is typed in capitals, as it is typed. -->
<text-input [value]="code()" (changeText)="code.set($event.toUpperCase())" />When your state takes the text as typed, native already shows it and nothing is sent back. When
it refuses or rewrites it, native is showing text your state does not hold, and a refused
keystroke leaves your binding where it was, so there is no prop change to carry the correction.
Once the change detection pass the keystroke started has finished, <text-input> compares
value with the text native reported and, if they differ, sends React Native's
setTextAndSelection command. A value set from code after the user has typed goes the same way.
Any binding makes the field controlled, one-way [value] included, so a field bound one way with
nothing handling the change cannot be typed into, as in React Native. Decide in the handler, and
do it synchronously: the comparison runs once the pass has finished, so a value that arrives later
is compared as a refusal and then applied when it arrives. Leave value unbound and the field
keeps what is typed. [formField] counts as a binding, and since a form takes every value the
control gives it, typing into a form field sends nothing back.
That command carries the same counter as the rest of the typing protocol, the one React Native
uses to keep fast typing safe: native reports each change with a counter, the component sends the
latest one back, and native ignores anything older than what it already has. Without it, typing
fast into a controlled field drops characters. None of this is something an app manages; it is
what makes binding [(value)] straight to a signal safe. <switch>, on the
switch page, is controlled the same way.
Keyboards and behavior
keyboardType picks the keyboard ('email-address', 'number-pad', 'phone-pad' and the rest);
returnKeyType labels the return key, and submitBehavior decides what pressing it does
('submit', 'blurAndSubmit', or 'newline' - the default on a multiline field). multiline
allows more than one line, secureTextEntry hides what is typed, autoCapitalize and
autoCorrect control the usual typing assists, and maxLength is enforced natively so nothing
flickers past the limit before being trimmed back.
A multiline field's text starts at the top, as in a <textarea>, on both platforms. Android's own
default centres it, so there a multiline field commits textAlignVertical: 'top' unless the
textAlignVertical input or a CSS vertical-align says otherwise. Both are Android-only: iOS
always starts a multiline field at the top and centres a single-line one.
A rows attribute or binding on a multiline field with a line-height and no height of its own
is its height: rows="3" is three lines, with the field's padding and border, as a browser sizes a
<textarea rows="3">. A height written for the field wins, and a single-line field ignores rows.
The engine reads it and does not send it on to the native field.
<text-input> exposes focus(), blur(), clear(), setSelection(start, end?) and
isFocused() as methods, reachable through a template reference or nativeRef. (changeText)
carries just the new string, matching React Native's onChangeText; (change), (focus),
(blur), (submitEditing), (endEditing), (selectionChange), (keyPress) and
(contentSizeChange) are the raw element events. (keyPress) names the key in
$event.nativeEvent.key: Backspace, Enter, or the character typed (' ' for a space), on a
device and in a browser alike. iOS reports no Enter from a single-line field, so use
(submitEditing) for the return key there. (selectionChange) carries
$event.nativeEvent.selection, a { start, end } that changes as the caret moves or a range is
selected.
Bind [selection] to place the caret or select a range from your state: { start: 3 } is a caret
after the third character, and { start: 1, end: 4 } a range. Whenever the bound selection
differs from the one native last had, on the first render and after, it goes out with the same
setTextAndSelection command, on iOS and Android alike. As in React Native the binding is
controlled, so a caret the user moves goes back to the bound one; to let the user move it, update
your state from (selectionChange).
On Android, cursorColor and selectionHandleColor follow selectionColor unless they are set,
as React Native's do, so one selectionColor tints the caret, the handles and the highlight there,
as it does on iOS.
(contentSizeChange) fires for a multiline field only, as React Native's does, and carries
$event.nativeEvent.contentSize, the { width, height } of the text. Android reports it as the
text changes. iOS reports it when the field itself changes size, as React
Native's field does there: on the first layout, and as a field with no height of its own grows or
shrinks with its text. A field held at a fixed height on iOS reports nothing while its text scrolls
inside it, so a field that should grow with its text there leaves its height unset rather than
setting it from this event.
To follow the field's own height as it grows, leave the height unset, cap it with max-height, and
read (layout). It fires on both platforms whenever the field resizes, including when it reaches
the cap. (contentSizeChange) is the size of the text, which leaves out the padding and goes on
past the cap on Android.
<text-input
multiline
style="max-height: 160px"
[(value)]="draft"
(layout)="fieldHeight.set($event.nativeEvent.layout.height)"
/>text-inputdirective@ng-native/componentsimport { TextInput } from '@ng-native/components';and add TextInput to the component's imports A text field, wired for signal forms. Commits as TextInput , single or multi line by prop.
Inputs
disabledunknown Refuse input and announce as disabled. Wins over editable .
readonlyunknown Refuse input, though still announced as enabled. Wins over editable .
invalidboolean = false Published as data-invalid ; nothing native reads it.
touchedboolean = false Published as data-touched ; nothing native reads it.
multilineunknownAllow more than one line.
scrollEnabledunknowniOS: whether a multiline field scrolls its text; off, it grows with it instead.
editableunknownSet to false to make the field read-only.
placeholderstringShown while the field is empty.
placeholderTextColorstringkeyboardTypeKeyboardTypeWhich keyboard to show.
returnKeyTypeReturnKeyTypeThe label on the return key.
submitBehaviorSubmitBehavior What return does. Defaults to blurAndSubmit on one line and newline on many.
secureTextEntryunknownHide what is typed, for passwords.
autoCapitalize'none' | 'sentences' | 'words' | 'characters' When the keyboard capitalises. Defaults to sentences .
autoCorrectunknownAuto-correct while typing. Defaults to true.
autoFocusunknownFocus the field as soon as it mounts.
maxLengthunknownThe most characters allowed, enforced natively so nothing flickers.
numberOfLinesunknownAndroid: the height of a multiline field, in lines.
rowsunknown How many lines of its line-height a multiline field is tall, where nothing sets its height, as a <textarea rows> is. Read by the engine, which sizes the field; never sent to native.
selection{ readonly start: number; readonly end?: number } The selected range, or the caret when end is left out or equals start . Sent to native whenever it differs from what native last had, so a caret the user moves goes back to it.
selectionColorstringselectTextOnFocusunknownSelect everything on focus.
caretHiddenunknowncontextMenuHiddenunknownDisable the copy, paste and share menu.
textAlign'left' | 'center' | 'right'allowFontScalingunknownmaxFontSizeMultiplierunknowninputAccessoryViewIDstring iOS: the nativeID of an <input-accessory-view> to dock above the keyboard.
clearButtonMode'never' | 'while-editing' | 'unless-editing' | 'always'iOS: when the clear button shows.
clearTextOnFocusunknowniOS: empty the field on focus.
enablesReturnKeyAutomaticallyunknowniOS: disable the return key while the field is empty.
keyboardAppearance'default' | 'light' | 'dark'iOS: the keyboard's colour scheme.
passwordRulesstringiOS: rules for a generated password, in Apple's Password Rules syntax.
spellCheckunknown iOS: underline misspellings. Follows autoCorrect by default.
smartInsertDeleteunknowniOS: add and remove spaces around pasted and deleted words.
dataDetectorTypesstring | readonly string[]iOS, multiline: turn phone numbers, links and addresses into taps.
textContentTypestringiOS: what the field holds, so the system can autofill it.
autoCompletestringAndroid: the autofill hint.
underlineColorAndroidstring = 'transparent'Android: the underline colour, transparent by default.
cursorColorstring Android: the caret colour. Follows selectionColor by default, as RN's does.
selectionHandleColorstring Android: the selection handle colour. Follows selectionColor by default, as RN's does.
textAlignVertical'auto' | 'top' | 'bottom' | 'center'Android: where text sits in a tall field.
importantForAutofill'auto' | 'no' | 'noExcludeDescendants' | 'yes' | 'yesExcludeDescendants'Android: whether autofill may fill this field.
showSoftInputOnFocusunknownAndroid: set to false to focus without raising the keyboard.
disableFullscreenUIunknownAndroid: keep the field in place in landscape rather than going full screen.
inlineImageLeftstringAndroid: a drawable resource shown at the left of the field.
inlineImagePaddingunknowntextBreakStrategy'simple' | 'highQuality' | 'balanced'Android: the line-breaking strategy.
Two-way
valuestring = ''Outputs
changeTextstringThe new text alone, on every native change.
touchvoidFocus left the field. What Signal Forms marks a field touched from.
Methods
focus(): voidBring the keyboard up on this field. A view command, so it reaches native directly.
clear(): voidEmpty the field.
setSelection(start: number, end = start): voidMove the caret, or select a range.
isFocused(): booleanWhether this field currently has the keyboard.
Line height
A single-line field's line-height sets its height, as it sets an <input>'s in a browser: line
height, padding and border, with the text centred in it. That holds on iOS as on Android, and a
Tailwind font-size utility, which brings a line height with it, sizes a field the same way on both.
An explicit height, or a larger min-height, still wins, and the text is centred in it on both.
The line box grows with the system text size, as the text in it does, unless allowFontScaling is
false, and no further than maxFontSizeMultiplier. In a multiline field the line height spaces
the lines, as in a <textarea>.
Props a browser has no name for
Several inputs exist only because a native keyboard is a different object than an <input>:
textContentType and autoComplete hint at autofill (a password manager, a one-time code) the
way autocomplete does on the web, but with platform-specific values. passwordRules (iOS)
constrains what a generated password looks like. On iOS, dataDetectorTypes makes recognized
links and phone numbers tappable when multiline is true and editable is false.
keyboardAppearance (iOS) sets
the keyboard's own light or dark look independent of the app's theme, and showSoftInputOnFocus
lets a field take focus (for a caret and a custom picker) without the system keyboard appearing at
all.
Input accessory view
<input-accessory-view> docks a bar above the keyboard on iOS. Pair it with a <text-input> by
giving the view a nativeID and the input the same string as inputAccessoryViewID. Android has
no native counterpart to this and the element commits as a plain view, so content placed inside it
simply sits inert rather than floating above the keyboard.
On iOS one bar goes with one field: when the bar appears, it attaches to the first <text-input>
in the window whose inputAccessoryViewID names it, so a form with a bar under every field needs
one <input-accessory-view>, with its own nativeID, per field.
A bar written inside a <scroll-view> is still drawn above the keyboard, outside the scroll view,
so on iOS a tap on it that no control takes, on a disabled button or the space between two, leaves
the keyboard up, whatever keyboardShouldPersistTaps says. React Native's own scroll view
dismisses the keyboard for that tap. On Android the element is a plain view in the scroll view, and
a tap on it counts as any other.
input-accessory-viewdirective@ng-native/componentsimport { InputAccessoryView } from '@ng-native/components';and add InputAccessoryView to the component's imports A bar docked above the keyboard, iOS only. Pair it with a text input by giving this view a nativeID and the input the same inputAccessoryViewID .
Inputs
backgroundColorstringThe bar's background.