Skip to content

Releases: ronickg/react-native-nitro-input

nitro-input v0.4.1

Choose a tag to compare

@ronickg ronickg released this 13 Oct 11:20
  • NitroInput's tabularNums prop: 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,
    so keepPlaceholder's tail slid left on each keystroke (by 14 dp over a
    card number), and an amount changed width as its digits did.
    tabularNums lays 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; false asks for its
    proportional ones (as does a style's fontVariant: ['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

Choose a tag to compare

@ronickg ronickg released this 13 Oct 01:18
  • Android: NitroNumber's numeric transition no longer blocks the UI
    thread (ANRs).
    A glyph mask the warm thread had not made yet was blurred
    in onDraw, and the shared mask cache made every mask inside its lock. Two
    ANRs on low-end phones followed: the UI thread Blocked in the cache's
    get behind the warm thread (which runs at MIN_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.
  • NitroInput lays out at its real size in the commit that mounts it.
    Its size came from the view, through onSizeChange and a React commit, so
    the first layout had none: an autoWidth field 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 new NitroInputMeasure), with the code the
    view sizes itself with - fonts, affixes and their seams, number and mask
    formatting, the placeholder and keepPlaceholder's tail - as React
    Native's TextInput measures 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, a transform worklet and a style that sets both sides keep the old
    path.
  • A framed field sizes itself. With no width or height from its style,
    an outlined or filled field 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's TextInput does. 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. An autoWidth secure 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 TextInput style no longer crashes a field, and NitroInput reads
    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") on NitroInput, NitroNumber and NitroText, and a
    numeric fontSize or color there 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. NitroInput also takes fontSize,
    fontWeight, fontFamily, color, letterSpacing, lineHeight,
    textAlign and textAlignVertical from style, as TextInput does (its
    style is typed as TextInput's); the props of the same name win.
  • NitroInput matches TextInput in three more places, found by a
    visual comparison of the two side by side:
    • Android: textAlign center / right on a plain field did nothing; the
      alignment set when the field became plain (before textAlign arrived in
      the same commit) overrode it.
    • A style padding or border insets the text and adds to the size the
      field works out for itself, as TextInput's does. An autoWidth field
      with paddingHorizontal: 8 squeezed 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: lineHeight sets a single line's height, as on Android; it was the
      font's own line box whatever the prop said.

nitro-input v0.3.8

Choose a tag to compare

@ronickg ronickg released this 05 Oct 19:22
  • NitroNumber shows what its format prints, 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 zeros Intl drops; pass
      fractionDigits to keep a fixed count.
  • value takes a bigint or a decimal string, and minorDigits reads 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 a 0x / 0o / 0b integer is
    shifted exactly too; a string that is not a number shows as NaN rather than
    an unshifted amount.

  • The components read their format in C++. What NitroNumber and
    NitroInput take from a NumberFormat (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.

  • NitroNumber keeps 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 duration of the
    last value, as the numeric transition does. A single change from rest rolls
    as before.

nitro-input v0.3.7

Choose a tag to compare

@ronickg ronickg released this 05 Oct 15:10
  • Android: no longer breaks VisionCamera frame processors. NitroInput
    linked libworklets.so although it uses only worklets' header-inline API,
    and as a Nitro package it loads at app start, so it became the first library
    to load libworklets.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
    (no DT_NEEDED), and installRuntime checks the UI runtime holder by type
    name instead of dynamic_cast, which is not reliable across shared libraries
    on Android.

nitro-input v0.3.6

Choose a tag to compare

@ronickg ronickg released this 29 Sep 02:18
  • A controlled selection rendered before the latest keystrokes is
    dropped
    , as value already was and as React Native's TextInput does.
    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 by selection took "123456" as "156234". Uncontrolled fields are
    unchanged.

nitro-input v0.3.5

Choose a tag to compare

@ronickg ronickg released this 29 Sep 01:07
  • focusedValue: pass a shared value (Reanimated's useSharedValue(false))
    and the field keeps it equal to its focus, set on the UI thread the moment
    focus changes, while onFocus / onBlur still 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. Needs react-native-worklets.
  • Worklet focus handlers see the first focus of an autoFocus field. They
    were registered in an effect, which can run after the view has attached and
    taken focus, so a worklet onFocus (and useNitroInputState's focused)
    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, with keyboardHandoffMs, 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 whole keyboardHandoffMs after
    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

Choose a tag to compare

@ronickg ronickg released this 28 Sep 20:05
  • 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's autoFocus field, which the push's own endEditing then 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.
    A focus() 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 dropped useEffect(() => 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, as autoFocus does. Both platforms.
  • iOS: an autoFocus field 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 selection given 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 maxLength or a
    transform: the IME's composition is left alone until it is committed,
    then cut to maxLength and transformed. The unconverted letters no longer
    count against maxLength, and a controlled parent echoing the text back
    mid-composition no longer ends it
    (react-native#56463,
    #52552).
  • Android: maxLength counts 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

Choose a tag to compare

@ronickg ronickg released this 28 Sep 14:36
  • 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 a returnKeyType set shows a bar above the keyboard with
    that key ("Next", "Go", "Done"...), as TextInput does. 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,
    onSubmitEditing and worklets run as for the real one.
  • returnKeyBar (iOS, default true): false leaves 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 used keyboardHandoffMs, 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 with keyboardHandoffMs
    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 KeyboardAwareScrollView did 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 value while the
    user types (a rejected edit, a digits-only filter) puts the value back, as
    TextInput does. It used to keep showing what was typed.
  • Fixed (Android): keyboardHandoffMs on 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's keyboardHandlingEnabled stays off).
  • Fixed (Android): a field on a screen react-native-screens covered and
    uncovered came back still flagged as focused, and focus() did nothing.
  • Fixed (Android): keyboard-controller saw no focused input for a
    NitroInput, so KeyboardAwareScrollView never scrolled to it.
  • Fixed (Android): changing clearTextOnFocus after mount had no effect.
  • Fixed (iOS): KeyboardAwareScrollView scrolled 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 / blur view commands now go
    through the field's own focus() / 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.sh drives 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

Choose a tag to compare

@ronickg ronickg released this 27 Sep 14:43
  • keepPlaceholder: the rest of a mask's placeholder stays visible,
    greyed, after what has been typed ("1234 5" then "678 9012"); without a
    placeholder the mask itself is shown, empty slots as
    maskSlotPlaceholder (_): +1 (212) ___-____. Plain and reflowing
    fields, both platforms.
  • maskAffinityFormats and maskAffinityStrategy: 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.
  • maskTextCase uppercases or lowercases letters as they arrive, and
    maskCharacterMap replaces 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 through onChangeText / 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

Choose a tag to compare

@ronickg ronickg released this 26 Sep 15:40
  • Signs: signDisplay ('auto', 'always', 'exceptZero',
    'negative', 'never', as Intl.NumberFormat's) puts a plus on gains,
    and a plus that turns into a minus swaps like any other glyph; plusSign
    and minusSign set the glyphs. A format gives its sign display and its
    locale's minus.
  • Styled parts: prefixColor, suffixColor, and fractionFontSize,
    fractionColor and fractionAlign for 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 on digits (the highest digit a position
    shows before it wraps, as NumberFlow's) and groupingSizes, both open to
    any figure.
  • Compact figures: a compact format rolls 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: digitGlyphs and groupingSizes, 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) unless shimmerColor was given; it uses the
    documented light neutral now.
  • Fixed: NumberFormat with notation: '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: NitroText crosses JNI to the reflow engine once per text
    change and once per frame, rather than once per character and several
    times a frame.