Repository navigation
Releases: ronickg/react-native-nitro-input
Release list
nitro-input v0.4.1
NitroInput'stabularNumsprop: tabular digits in any font. Digits
were tabular only in fonts that ship tabular figures: in one without (Open
Runde) a typed "1" was far narrower than the placeholder's "0" it replaced,
sokeepPlaceholder's tail slid left on each keystroke (by 14 dp over a
card number), and an amount changed width as its digits did.
tabularNumslays every digit out as wide as the widest, centred, as
NitroNumber's columns are: in the field's own text, the placeholder, the
tail and the measure, on both platforms. Unset, the field asks for the
font's tabular figures as before, at no cost;falseasks for its
proportional ones (as does a style'sfontVariant: ['proportional-nums']).- Android: a plain field's text and placeholder ask for tabular figures.
The overlay's paints did, the edit text did not, so a font whose tabular
figures are off by default (ABC Diatype) shifted the tail the same way.
nitro-input v0.4.0
- Android:
NitroNumber's numeric transition no longer blocks the UI
thread (ANRs). A glyph mask the warm thread had not made yet was blurred
inonDraw, and the shared mask cache made every mask inside its lock. Two
ANRs on low-end phones followed: the UI threadBlockedin the cache's
getbehind the warm thread (which runs atMIN_PRIORITY), and the UI
thread waiting for a GC while the blur allocated two float copies of a large
glyph. Now masks are made outside the lock, the UI thread never blurs (a
mask not warmed yet is drawn sharp for a frame while the warm thread makes
it), and the blur reuses per-thread buffers instead of allocating per mask.
A mask the screen is waiting on is made ahead of the warm-up's queue (it
waited behind every digit at every blur level, ~50 ms), the warm thread runs
at background priority rather than the lowest, and a mask that cannot be
made is skipped rather than crashing the app. - iOS: the numeric transition no longer blurs on the main thread. Its
digits were blurred and rasterized ahead of the first swap, but on the main
queue: opening a screen of numeric figures spent ~1 s of the main thread on
it (Release, simulator), in blocks of up to ~180 ms, and a prefix or suffix
that changed text was blurred on demand mid-frame. The warm-up now runs on
a background queue, a blur the screen is waiting on on a second one ahead
of it (drawn sharp until it lands), and the shared glyph caches are
thread-safe. NitroInputlays out at its real size in the commit that mounts it.
Its size came from the view, throughonSizeChangeand a React commit, so
the first layout had none: anautoWidthfield took its parent's width (or
none) for a commit, and every field without a fixed height moved by a pixel
or two as its line box arrived. Now a single-line field is measured on the JS
thread before its view exists (a newNitroInputMeasure), with the code the
view sizes itself with - fonts, affixes and their seams, number and mask
formatting, the placeholder andkeepPlaceholder's tail - as React
Native'sTextInputmeasures in its shadow node. About 0.1 ms per field;
the view's own report then matches and no longer costs a commit. A wrapping
field, atransformworklet and a style that sets both sides keep the old
path.- A framed field sizes itself. With no width or height from its style,
anoutlinedorfilledfield took the size of its text line alone: the
frame's side insets and the label were left out, so a resting label was
drawn outside the field and a filled one cut its text off. Its size now
includes the frame: the side insets either side of the text or the resting
label, whichever is wider, and above and below it the same 16 of padding
(and a filled field's label line) a wrapping field already had. A field
given a height in its style is unchanged. - A secure field fits its bullets. Android set the password input type
after the typeface, which switches the field to monospace, so its bullets
were wider than the field measured them; the typeface is now put back, as
React Native'sTextInputdoes. On iOS the field measured its text as
bullets in its own font, but UIKit draws U+2219 in the system font; it now
measures those. AnautoWidthsecure field showed●●●●…for 8 characters
on iOS and 5 bullets on Android. - No size jump on mount, and no amount sliding in on a reused field. A
size the view reported before its props were applied (the defaults', or a
recycled view's last element's) reached JS and made the field jump to it and
back. And on iOS a recycled view took the empty text its props reset fed the
engine for its first, so a reflowing amount on a reused view slid in instead
of showing as it is. - Android:
keepPlaceholder's greyed tail stays inside the field. The
rest of the placeholder, drawn after what was typed, was not clipped to the
text area as the field's own text and hint are: in a padded field narrower
than its placeholder (or once the typed text filled it), the tail ran over
the padding to the field's edge. iOS already clipped it. - A
TextInputstyle no longer crashes a field, andNitroInputreads
its typography from it. Fabric hands a view its style's keys as props,
and six to eight of each view's native props shared a name with a text
style key:style={{ fontWeight: '700' }}threw ("Value is a string,
expected a number") onNitroInput,NitroNumberandNitroText, and a
numericfontSizeorcolorthere was fought over by the style and the
prop. The native props are renamed (textSize,textWeight,
textFamily,textColor,textLetterSpacing,textLineHeight,
textAlignment,textVerticalAlignment); the components' own props keep
their names. A test now fails if any native prop shares a name with a React
Native style key or view prop.NitroInputalso takesfontSize,
fontWeight,fontFamily,color,letterSpacing,lineHeight,
textAlignandtextAlignVerticalfromstyle, asTextInputdoes (its
styleis typed asTextInput's); the props of the same name win. NitroInputmatchesTextInputin three more places, found by a
visual comparison of the two side by side:- Android:
textAligncenter/righton a plain field did nothing; the
alignment set when the field became plain (beforetextAlignarrived in
the same commit) overrode it. - A
stylepadding or border insets the text and adds to the size the
field works out for itself, asTextInput's does. AnautoWidthfield
withpaddingHorizontal: 8squeezed its text to "…" on iOS; on Android
the padding did not inset the text at all (React Native never hands a
Nitro view its padding there, so the view now insets its text, overlay and
frame itself). - iOS:
lineHeightsets a single line's height, as on Android; it was the
font's own line box whatever the prop said.
- Android:
nitro-input v0.3.8
-
NitroNumbershows what itsformatprints, value by value. It read the
format once and showed every value at its maximum fraction digits, rounded
by the engine (half away from zero, through a double). Now each value is
formatted:trailingZeroDisplay: 'stripIfInteger'and a minimum below the maximum
show the digits the format prints ("$1,234", then "$1,234.50"), and the
decimal columns roll in and out as the count changes.- The figure is rounded with the format's
roundingMode(halfEven
included), on the exact decimal:"1.005"is $1.01. - A value that rounds to zero shows no minus.
- A format whose minimum fraction digits is below its maximum (a plain
decimal style, say) now drops the trailing zerosIntldrops; pass
fractionDigitsto keep a fixed count.
-
valuetakes abigintor a decimal string, andminorDigitsreads it in
minor units:<NitroNumber value={123456n} minorDigits={2} format={usd} />
shows $1,234.56. With a format, nothing goes through a double before it is
rounded; the rounded figure then rolls as a double, exact to 15 significant
digits. A string with an exponent ("1e5") or a0x/0o/0binteger is
shifted exactly too; a string that is not a number shows as NaN rather than
an unshifted amount. -
The components read their
formatin C++. WhatNitroNumberand
NitroInputtake from aNumberFormat(the figure per value, the prefix
and suffix, separators, grouping, sign placement, the locale's digits) is
read off the formatter's own parts natively, instead of formatting sample
values into JS objects and parsing them back there. -
NitroNumberkeeps rolling while its value keeps changing (a slider, a
dial, a live price) (#62).- With
easing="spring", each new value restarted the spring from rest, so
while a slider moved the figure stood still and only caught up when the
finger stopped. A wheel now keeps its speed through a new value (the
spring's response with the speed it had). - With any easing, a new value mid-roll sent each wheel to the new digit
from where it stood, the way the value moved, so a wheel that lagged
behind or ran past its digit went a whole turn round. A new value now
moves where the wheel is going, by the step from the digit it was
heading for (as NumberFlow accumulates), so a stream one way spins it
faster and a drag that turns back slows it and turns it round.
Every digit still rolls on its own and lands within one
durationof the
last value, as the numeric transition does. A single change from rest rolls
as before. - With
nitro-input v0.3.7
- Android: no longer breaks VisionCamera frame processors. NitroInput
linkedlibworklets.soalthough it uses only worklets' header-inline API,
and as a Nitro package it loads at app start, so it became the first library
to loadlibworklets.so. On Android that changes which copy of worklets'
weak RTTI each library binds to;react-native-vision-camera-worklets'
frame-processor queue then failed worklets'dynamic_cast, the runtime got a
null queue, and opening any frame-processor camera crashed with SIGSEGV in
worklets::WorkletRuntime::scheduleImpl. Worklets is now used headers-only
(noDT_NEEDED), andinstallRuntimechecks the UI runtime holder by type
name instead ofdynamic_cast, which is not reliable across shared libraries
on Android.
nitro-input v0.3.6
- A controlled
selectionrendered before the latest keystrokes is
dropped, asvaluealready was and as React Native'sTextInputdoes.
Typed faster than JS renders (a slow Android phone, a busy JS thread), a
caret computed for an earlier text landed in a field that already had more
in it, and the next characters went in behind the first ones: a code field
driven byselectiontook "123456" as "156234". Uncontrolled fields are
unchanged.
nitro-input v0.3.5
focusedValue: pass a shared value (Reanimated'suseSharedValue(false))
and the field keeps it equal to its focus, set on the UI thread the moment
focus changes, whileonFocus/onBlurstill run on JS. For focus styling
that must not wait for JS and that wraps more than the field: a border
around a row holding an icon or a country picker in front and a clear
button behind. Needsreact-native-worklets.- Worklet focus handlers see the first focus of an
autoFocusfield. They
were registered in an effect, which can run after the view has attached and
taken focus, so a workletonFocus(anduseNitroInputState'sfocused)
missed it. They are registered while rendering now. - iOS: no keyboard left over a screen popped back to. Going back from a
screen whose field had the keyboard, withkeyboardHandoffMs, to a screen
where no field takes it (a list whose search field was never focused), the
keyboard stayed up over that screen for the wholekeyboardHandoffMsafter
the pop. React unmounts the popped screen's field before the pop starts, so
the handoff could not tell a pop from a field swapped in place. The hold now
ends as the screen starts to leave when no field is waiting for the
keyboard; a field there that had it when it was covered, or one focused on
return, still takes it over.
nitro-input v0.3.4
- iOS: a focused field that took no typing (with
react-native-keyboard-controller). keyboard-controller wraps the focused
field's delegate in one composite shared by every input and forwards to the
delegate it stored last; when focus moves faster than it re-wraps (a pushed
screen'sautoFocusfield, which the push's ownendEditingthen resigns),
a field's delegate calls reach another field
(keyboard-controller#1588).
The field looked focused, but every keystroke was checked against the
other field's rules and applied to it (a number field threw the letters
away). A NitroInput now hands a delegate call for another field to that
field, and puts its own delegate back before it starts editing. focus()as a field mounts opens the keyboard even when it is down.
Afocus()that arrives before the view is on screen is held until it is,
and was then honoured only while the keyboard was still up: right for a
screen navigated back to (a keyboard dismissed on the way stays down), but
it also droppeduseEffect(() => ref.current?.focus(), [])on a field
swapped in after one without a keyboard (a free-text question after a
multiple-choice one). A field that has never been on screen now takes the
focus it was asked for, asautoFocusdoes. Both platforms.- iOS: an
autoFocusfield on a screen pushed without a slide
(animation: 'none','fade','simple_push') no longer blurs and
refocuses, and the keyboard no longer starts to close and comes back
(react-navigation#11643,
#11626).
UINavigationController ends editing in the incoming view mid-transition
and hands first responder back as it completes; the field now keeps it
through the transition. - iOS: a
selectiongiven while the field is not focused is where the
caret goes when it is focused, instead of the end of the text
(react-native#46943). - iOS: Japanese, Chinese and Korean input with
maxLengthor a
transform: the IME's composition is left alone until it is committed,
then cut tomaxLengthand transformed. The unconverted letters no longer
count againstmaxLength, and a controlled parent echoing the text back
mid-composition no longer ends it
(react-native#56463,
#52552). - Android:
maxLengthcounts characters when typing, as it already did
for text set by the program and as iOS does: an emoji took two places when
typed. - Android: changing
secureTextEntry(or another keyboard prop) on a
focused field keeps the caret where it was instead of moving it to the
start (react-native#38676).
nitro-input v0.3.3
keyboardHandoffMs: when the focused field is popped or unmounted,
the keyboard stays up this long for the next field to take over (the
screen you return to, the next step's field), instead of dropping and
rising again. Both platforms;Keyboard.dismiss()ends the hold at once.focus()on a field that is not on screen yet (a screen navigated back
to, refocusing its field before it is shown again) now focuses it as soon
as it is, while the keyboard is still up. On Android this alone keeps the
keyboard up across a stack pop.- iOS number pads get a return key: a field on the number, phone or
decimal pad with areturnKeyTypeset shows a bar above the keyboard with
that key ("Next", "Go", "Done"...), asTextInputdoes. The pads have no
return key of their own, so a form chained with Next stopped at its first
numeric field. The bar is the return key:submitBehavior,
onSubmitEditingand worklets run as for the real one. returnKeyBar(iOS, defaulttrue):falseleaves a number pad
without the return key bar, for a screen whose own button rides the
keyboard (a sticky or morphing CTA) and would otherwise be doubled.- Fixed (iOS): coming back to a screen whose field had the keyboard, when
the field on the leaving screen usedkeyboardHandoffMs, left the keyboard
down: the stand-in held it, so UIKit had nothing to give back. The field
now takes the keyboard back itself, with the back animation. - Fixed (iOS): a multiline field kept drawing its placeholder under the
first characters typed. - iOS 26: no dark keyboard on the way back. When you return to a screen
whose field had the keyboard, UIKit gives the field its focus back as the
transition starts, and iOS 26 then slides the keyboard in with the screen as
a flat dark snapshot. The field now takes its focus back once the screen has
settled, so the real keyboard rises from the bottom, as in the system apps
(Contacts). Focus the app asks for (focus(),autoFocus) is unchanged. - Fixed:
<Activity>(React 19.2). A hidden<Activity>deletes its
native views and creates new ones when shown again, so a NitroInput came
back with its first text (an uncontrolled field's text lived only
natively), and a handle kept from before hiding reached the pooled view,
which another field may have been given:focus()on a hidden field's
handle focused that other field. The text is now kept across hide and show,
and while hidden a field's handle does nothing (focus()/blur()) or
waits (setText);getText()returns the kept text. On Android, where
a hidden field keeps its view, the field takes it back when shown, and a
focus()asked for while the field is still hidden waits until it is
shown. - Fixed:
Keyboard.dismiss()right after a field withkeyboardHandoffMs
unmounted, on a screen with no other field (a questionnaire's tap-only
question), waited for the hold to run out (about 0.4 s) before the keyboard
closed. It closes at once now. - Fixed (iOS): keyboard-controller's
KeyboardAwareScrollViewdid not
scroll a field that got its focus back (the caret had not moved, so it got no
selection event and waited for one). The field now reports its selection
once editing begins. - Changed: a controlled field whose parent keeps its
valuewhile the
user types (a rejected edit, a digits-only filter) puts the value back, as
TextInputdoes. It used to keep showing what was typed. - Fixed (Android):
keyboardHandoffMson a field that unmounts could lose
the race with the frame that removes it (about two runs in three on a
Galaxy A22), and the keyboard dropped anyway. The handoff now runs on the
UI thread just before Fabric applies that frame's view changes. - Docs: "The keyboard across screens and lists" in the README: the
settings that make a NitroInput screen behave like the platform apps, back
navigation included (iOS gives the field its keyboard back as long as
native-stack'skeyboardHandlingEnabledstays off). - Fixed (Android): a field on a screen react-native-screens covered and
uncovered came back still flagged as focused, andfocus()did nothing. - Fixed (Android): keyboard-controller saw no focused input for a
NitroInput, soKeyboardAwareScrollViewnever scrolled to it. - Fixed (Android): changing
clearTextOnFocusafter mount had no effect. - Fixed (iOS):
KeyboardAwareScrollViewscrolled a reflowing field only
far enough to show the top of its line. - Fixed (iOS): a multiline field counted twice for keyboard-controller's
next / previous and its toolbar. - Fixed (iOS): React Native's
focus/blurview commands now go
through the field's ownfocus()/blur()(retrying a declined focus). - An on-device keyboard suite measures each of these: no keyboard hide when
focus moves, a field unmounts as the next autofocuses, a stack pops, a
search list filters under the field, or a scroll moves it out of sight.
The example app has an input lab (a search list and a form chained with
Next, each with NitroInput or TextInput), and
example/e2e/keyboard-lab-android.shdrives it with real touches over adb.
The lab also has a form with a sticky and a morphing CTA (keyboard-
controller), a push, a form sheet and a list of fields, and a scripted run
(globalThis.__lab.run()) that records keyboard-controller's events on a
device nothing can tap on.
nitro-input v0.3.2
keepPlaceholder: the rest of a mask's placeholder stays visible,
greyed, after what has been typed ("1234 5" then "678 9012"); without a
placeholderthe mask itself is shown, empty slots as
maskSlotPlaceholder(_):+1 (212) ___-____. Plain and reflowing
fields, both platforms.maskAffinityFormatsandmaskAffinityStrategy: several patterns
for one field, the best kept on every edit (a 4-6-5 Amex card beside
4-4-4-4). Compared on the value characters, and a tie keeps the format that
is showing, so the grouping does not flip back and forth while typing.maskTextCaseuppercases or lowercases letters as they arrive, and
maskCharacterMapreplaces characters before masking (Cyrillic
look-alikes, a comma for a dot).- Letter slots (
[A],[a]) take letters beyond ASCII: Latin with
diacritics, Greek, Cyrillic, Armenian, Hebrew, Arabic, Devanagari, Thai,
Hangul, kana and CJK. - A paste that repeats a mask's leading constants ("+63…" into
+63 [000] [000] [0000]) loses the repeat when it would otherwise overflow. - Fixed: a constant the mask inserts right at the caret now puts the
caret after it when typing and before it when deleting (the gravity was the
wrong way round), as RedMadRobot's input-mask does. - Fixed: an ellipsis after an optional slot (
[9…],[a…]) repeats that
slot; the format sanitizer used to move it to the front, where it took any
character. - Fixed (iOS): changing
mask(or a number format) re-masks the text and
reports it throughonChangeText/onChangeMask/getText(), as Android
does; the field showed the new text while JS kept the old one. - The mask engine is checked against ~1,900 cases ported from RedMadRobot's
input-mask (Android and iOS), maska and react-native-advanced-input-mask.
nitro-input v0.3.1
- Signs:
signDisplay('auto','always','exceptZero',
'negative','never', asIntl.NumberFormat's) puts a plus on gains,
and a plus that turns into a minus swaps like any other glyph;plusSign
andminusSignset the glyphs. Aformatgives its sign display and its
locale's minus. - Styled parts:
prefixColor,suffixColor, andfractionFontSize,
fractionColorandfractionAlignfor smaller, dimmer or superscript
cents. onAnimationStart/onAnimationEnd: when the figure sets off from
rest and when it comes to rest, once for a run of changes.NitroTime: seconds as a clock, timer or countdown (m:ss,
h:mm:ss…), its tens of seconds and minutes wrapping after 5 so 0:59 →
1:00 turns them one step. Built ondigits(the highest digit a position
shows before it wraps, as NumberFlow's) andgroupingSizes, both open to
any figure.- Compact figures: a compact
formatrolls the figure it shows and swaps
its suffix ("950" → "1.5K" → "2.3M"). continuous: the wheels below the highest one that changes turn a full
turn too, so a roll seems to pass through every value between.- Native digits and grouping:
digitGlyphsandgroupingSizes, or a
format's numbering system and group sizes (ar-EG's ٠١٢٣…,en-IN's
12,34,567). animated={false}shows every change at once, and
respectReduceMotion={false}animates under Reduce Motion.NitroText: a single line of text that morphs natively, shared
characters gliding to their new places ("Sign in" → "Signing in…",
"$1,204" → "$1,318"), as Torph does on the web. Its own native view: one
draw of cached glyphs at rest like a label, sized by a synchronous native
measurement in the render that mounts it, with the loading shimmer and its
options. Only the props you set cross to native, a static label never
touches the reflow engine, and a reused view keeps its drawn line: 1000
labels mount in 113 ms of main thread on an iPhone 11 Pro and 510 ms on a
Galaxy A22 (Text: 140 and 790,react-native-plain-text: 112 and 620).- Shimmer options:
shimmerAngle,shimmerWidth,shimmerBaseColor
(a skeleton),shimmerDirection(following the layout direction by
default, so a right-to-left layout now sweeps right to left) and
shimmerDelay. - Fixed: on iOS the loading glint was drawn in the label colour (black
on dark text, so invisible) unlessshimmerColorwas given; it uses the
documented light neutral now. - Fixed:
NumberFormatwithnotation: 'compact'dropped the currency and
the percent sign ("950" for "$950"; Android printed a compact currency in
full) and signDisplay's plus. It prints them as V8 does now. - Android:
NitroTextcrosses JNI to the reflow engine once per text
change and once per frame, rather than once per character and several
times a frame.