Repository navigation
Releases: limonify/zest-ui
Release list
v0.12.6
Fixed
- A
Comboboxpopup no longer opens underneath the keyboard. The positioner only knew the size of the window, so a popup opened from low on the screen sat behind the on-screen keyboard, along with the input inside it.Combobox.PositionerandAutocomplete.Positionernow raise the bottom of the collision boundary by the keyboard's height, so the popup moves above its anchor. autoFocusopens the keyboard inside aModalon Android. ACombobox.Inputin the popup got the caret but no keyboard, because the Modal's window does not have input focus yet when it appears, and the user had to tap the input.Combobox.Portalnow appliesautoFocusitself, shortly after the Modal has shown.modalProps.onShowis chained rather than replaced.- A query no longer shows on
Combobox.Trigger.Combobox.Valuerendered the input text, so what the user typed into the popup replaced the selection on the trigger, and stayed there if the list was dismissed. As in Base UI, a single-selection combobox with a trigger now shows the selected label, and its input is cleared whenever the list opens or closes, so every open starts blank. In that shapeonInputValueChangeno longer reports the chosen label, and clearing a query reports theinput-clearreason. A combobox without a trigger, amultipleone andAutocompleteare unchanged, andstate.valueis still the input text.
Added
- A development warning for an input the user cannot type into. A
Combobox.InputoutsideCombobox.Portalloses focus when the list opens, because aModalis a separate window. The portal now says so on iOS and Android, and names the two ways out: aCombobox.Triggerwith the input in the popup, ormodal={false}.
Documentation
- "Keeping the input focused" on the Combobox page, with a warning under the first example, whose shape has this problem. It also covers
keyboardShouldPersistTaps="handled", which everyScrollViewabove a combobox needs or the first tap on a row only dismisses the keyboard, andautoCorrect={false}, since an iOS autocorrect suggestion takes that tap as well.
v0.12.5
Fixed
- A cached avatar hides its fallback on iOS. iOS delivers a cached image's
onLoadbefore itsonLoadStart, andAvatar.Imagemapped every load start to'loading', so the status wentloaded→loadingand stayed there:Avatar.Fallbacknever hid, and the initials showed beside the picture. A load that has settled (loaded or errored) now stays settled until the image's source changes, compared by what it points at (a uri, an asset number), so an equal source on a new render is the same load. Android, which keeps the order, is unchanged. (#10)
v0.12.4
[0.12.4] - 2026-10-18
Fixed
- A swipe that dismisses a toast keeps the distance it reached.
Toast.Rootreset
state.swipeMovementto 0 as the finger lifted, right after the same swipe had closed the toast,
so an exit animation reading it started from the resting place: a thrown toast snapped back to the
middle before it left. The distance is now kept on a toast a swipe closed, so the exit can carry on
the way it was thrown, as the web's does with--toast-swipe-movement-*. A swipe released short of
the threshold still springs back to 0.
v0.12.3
[0.12.3] - 2026-10-17
Fixed
-
Types hold on React Native 0.88. RN 0.88 made
ViewandTextInputfunction components, so
TextInputused as a type names the component, not its instance. The public
Comboboxstore'sinputRefanduseFieldControl'sinputRefwere typed
RefObject<TextInput | null>, and a consumer calling.focus()/.blur()on them failed to
type-check. They are now typed from the component's ownrefprop (React.ComponentRef). That
resolves to the class instance on older RN, so thereact-native >=0.79peer range is unchanged.
The source type-checks against RN 0.79, 0.83, 0.86 and 0.88. The handler event types RN 0.88 renamed
(TextInputFocusEventData,TextInputKeyPressEventData,ImageErrorEventData) and the
narrowedModalonRequestCloseevent are derived from the props in the same way. -
preventZestUIHandlerworks in a menu item'sonPress.Menu.Item,Menu.LinkItem,
Menu.CheckboxItemandMenu.RadioItemcall the consumer'sonPressthemselves rather than
throughmergeProps, so the event never got the method its type promised, and calling it threw.
The event is now preventable, and preventing it skips the item's own close, asmergePropsskips
Zest's handler everywhere else.
v0.12.2
[0.12.2] - 2026-10-16
Fixed
- Popups lay themselves out in the provided direction. A
Modalis a new layout root, and a
new root takes its direction fromI18nManagerrather than from the tree that opened it. Under a
DirectionProviderthat disagreed with the device, every portalled popup —Dialog,
AlertDialog,Drawer,Popover,Menu,Select,Combobox,Tooltip— kept the device's
direction: rows, padding and text alignment stayed LTR in an RTL app while the parts that read the
context flipped. The portal's gesture root now carriesuseDirection()as itsdirectionstyle.
v0.12.1
[0.12.1] - 2026-10-16
Fixed
-
A popup with no room on either side of its axis drops below (or above) its anchor instead of
running off the screen. The clearest case is aMenu.SubmenuRooton a phone: the submenu opens
inline-endof a row in a menu that already spans most of a 393pt screen, so neither the right nor
the left has room, and it stayed on the side it overflowed least — half of it past the edge, since
shiftonly moves a popup along its anchor.flipnow takesfallbackAxisSideDirection: 'end', as
Base UI'scollisionAvoidancedefault does, so floating-ui tries the perpendicular sides too. -
Cross-axis overflow no longer flips a popup.
flipran with floating-ui's default
crossAxis: true, under which a popover that was only too wide for the screen counted as
overflowing its side and could be moved to the opposite one. It iscrossAxis: 'alignment'now,
as in Base UI: a popup leaves its side only when that side has no room along the main axis, and
overflow across the axis isshift's to correct.
v0.12.0
[0.12.0] - 2026-09-25
Fixed
-
Opening a
Tooltipno longer crashes on Android.Tooltip.Popupset bothroleand
accessibilityRoleto'tooltip', and'tooltip'is not anaccessibilityRoleAndroid's
accessibility delegate knows, so the app went down the moment a tooltip opened. The popup now sets
rolealone, which React Native maps onto each platform itself; the tooltip semantics are
unchanged on iOS. -
CheckboxGroupinside aFieldvalidates the array it submits. EveryCheckbox.Rootin the
group registered its own boolean with the surroundingField, so a field'svalidatesaw
true/falseper box instead of the group's value, and aFormsubmit validated the field once
per checkbox. The group now registers with the field and reports the array, marking it changed and
touched on every press, and it inherits the field'sdisabled. A checkbox inside a group leaves
the field alone; a standaloneCheckboxis unaffected.<Field.Root name="colors" validate={(value) => (value.length >= 2 ? null : 'Pick two')}> <CheckboxGroup>…</CheckboxGroup> {/* validate receives ['red', 'blue'] */} <Field.Error /> </Field.Root>
Changed
- A read-only
Selectopens for browsing.readOnlynow locks the value, not the interaction,
as Base UI 1.8 does: pressingSelect.Triggeropens the list so the options and the current choice
can be seen, and pressing aSelect.Itemdoes nothing. Until now the trigger ignored the press, so
a read-only select showed its value and nothing else, while the same prop on the web opened.
aria-readonlystill marks the trigger. If a read-only field should not open at all, that is
disabled.
v0.11.0
[0.11.0] - 2026-09-15
0.10.0 moved every gesture to gesture-handler's hook API and raised the peer to >=3.1.0. That put
this package outside every stock Expo project, and this release undoes it. The peer is >=2.32.0
again and the gestures are back on the Gesture.*() builder — which, it turns out, works on both
lines, so nothing has to be given up to get there.
This release is breaking, which is why it is a minor bump. The break is narrow: only
simultaneousGesturechanges shape, back to what it was in 0.9.x.
Fixed
-
zest installs and runs in a stock Expo project again. Expo SDK 57 pins
react-native-gesture-handlerto~2.32.0in itsbundledNativeModules.json, and the hook API
does not exist in 2.x, so 0.10.0's>=3.1.0peer was disjoint from the version Expo hands you.
Three things followed from that, all of them now gone:npm installfailed outright with
ERESOLVEfor anyone who had installed gesture-handler the way the docs say to;expo-doctor
reported the project off-SDK on every run and in every EAS build log; and Expo Go could not
load the app at all, because its binary embeds native 2.32 and installing JS 3.3 against it
crashes at gesture attach rather than at build. Trying zest now needs no development build. -
The
simultaneousGesture"is it actually attached?" tests assert something again. Under the
hook API a gesture registers with gesture-handler's jest registry when it is created, so
getByGestureTestIdfound gestures that had never reached aGestureDetectorand the check
passed with the composition deleted — 0.10.0 removed the Slider's as worthless rather than
fixing it. The builder registers at attach time, so the assertion is real, and both
Slider.ControlandDrawer.Popupnow have one.
Breaking
-
react-native-gesture-handleris a>=2.32.0peer, down from>=3.1.0. No one needs to
change a version to take this: 2.32 satisfies it, and so does 3.x. -
simultaneousGesturetakes a builder gesture again, on bothSlider.Controland
Drawer.Popup. The prop isGestureType, notSingleGesture | ComposedGesture:- const gesture = usePanGesture({ onUpdate: … }) + const gesture = Gesture.Pan().onUpdate(…) <Slider.Control simultaneousGesture={gesture} />
This only affects code written against 0.10.0, which was current for one day.
Gesture.Pan()is
available on 2.x and 3.x alike, so the snippet above compiles on whichever line you are on, and
it no longer has to be built inside a component.
Changed
-
The five gesture parts are back on the builder.
Slider.Control,Drawer.Popup,
Drawer.SwipeArea,Toast.RootandNumberField.ScrubAreabuild their pan with
Gesture.Pan()and compose withGesture.Simultaneous. Behaviour is unchanged, and
Drawer.Popup'ssimultaneousGesture— added in 0.10.0 — is kept, just expressed in builder
form.The builder is deprecated in 3.x but not removed, and it emits no runtime warning: 3.x still
exportsGesture.Pan()andGesture.Simultaneous(), and itsGestureDetectorbranches to the
legacy detector when it is handed one (props.gesture instanceof BaseGesture). One code path
therefore covers 2.32 and 3.3, which is what makes the wide peer range honest rather than
optimistic. The one thing it costs is that a singleGestureDetectoris locked to one gesture
family, so a consumer'ssimultaneousGesturehas to be a builder gesture too — available to
them on either line.This is a deliberate hold, not an oversight: the hooks come back when Expo ships an SDK whose
pinned gesture-handler is 3.x, and doing it before that buys nothing but costs everyone Expo Go. -
The example app runs in Expo Go again, and the docs site is back on gesture-handler 2.x with
it. Both pin~2.32.0, the version SDK 57 bundles, sonpx expo startis enough — no
npx expo run:ios. -
The README documents the gesture-handler peer. It listed only React and React Native, and
told Expo users to match their SDK's version, which 0.10.0 had quietly made impossible.
v0.10.0
[0.10.0]
gesture-handler deprecated the Gesture.*() builder in v3 in favour of a hook API, and every
gesture in this package has moved to it. Drawer.Popup gains the UI-thread escape hatch
Slider.Control already had, which is the one place the JS-thread round trip is most visible.
This release is breaking, which is why it is a minor bump rather than a patch. Both breaks
follow from the same fact: the builder and the hooks produce different gesture shapes, and
gesture-handler cannot compose one with the other. There is therefore no version of this package
that can support both.
Added
Drawer.Popuptakes asimultaneousGesture, so a consumer can move the sheet on the UI
thread. The same arrangementSlider.Controlhas had since 0.9.0, and it matters more here:
the popup's own handlers touch React state, so they run on JS (runOnJS: true) and the sheet's
position reaches the consumer asstate.swipeMovement— a render sits between the finger and the
sheet, once per frame of a drag. A thumb travels twenty points and a sheet travels half a screen,
so the same lag is far more visible on a drawer. zest neither animates nor takes an animation
dependency, so instead it now runs a gesture of the consumer's own alongside its own: both see the
same touch,state.swipeMovementand dismissal atswipeThresholdare unchanged, and the sheet
can follow the finger from a shared value with no render in the path. Nothing changes for anyone
who does not pass the prop.
Breaking
-
react-native-gesture-handleris now a>=3.1.0peer, up from>=2.32.0. The hooks do not
exist in 2.x. An app still on 2.x stays on0.9.x. -
Slider.Control'ssimultaneousGesturetakes a hook gesture, not a builder one. It was typed
as the deprecatedGestureTypeand composed withGesture.Simultaneous, which meant a consumer
passing it was pinned to a deprecated API with no way out — a hook gesture did not fit the slot,
and casting one in would have handedGesture.Simultaneousan object it calls builder methods on.
The prop is nowSingleGesture | ComposedGesture:- const gesture = Gesture.Pan().onUpdate(…) + const gesture = usePanGesture({ onUpdate: … }) <Slider.Control simultaneousGesture={gesture} />
Note the gesture must now be built inside a component, because
usePanGestureis a hook.
Changed
-
The five parts that use gesture-handler are on the hook API.
Slider.Control,
Drawer.Popup,Drawer.SwipeArea,Toast.RootandNumberField.ScrubAreaall build their pan
withusePanGesture({ … }). Behaviour is unchanged: the sametestIDreaches gesture-handler's
registry, so existing tests that find a gesture through it keep working, and the handlers still
run on the JS thread (runOnJS: true) because they touch React state. The callback renames are
gesture-handler's:.onStartisonActivateand.onEndisonDeactivate. -
The example app needs a development build. Expo Go ships gesture-handler 2.x, so it can no
longer load the example;npx expo run:iosreplacesexpo startfor it. The docs site moved to
3.x for the same reason — its Next build resolves this package's source directly and failed on
Export usePanGesture doesn't exist in target module.
v0.9.2
[0.9.2]
Added
- The combobox root's four contexts are exported, so a consumer can re-provide them around a
part rendered outside the root's own tree.ComboboxRootContext,ComboboxItemsContext,
ComboboxSelectionContextandComboboxTransitionContext, together with their
use…Contexthooks, are now named exports. They exist for the same reason as
Combobox.Portal modal={false}: hoisting a list into a root-level overlay moves it above the
<Combobox.Root>that supplies these contexts, so the consumer re-provides them around the
hoisted part.