Skip to content

Releases

v0.22.2

Choose a tag to compare

@github-actions github-actions released this 06 Nov 17:29

🩹 Fixes

  • background-image: var(--g) now draws the gradients its token holds, currentColor in a background token's last layer is now the text colour, and a written background: platform-color(...) in a stylesheet now takes away a weaker rule's image, as Chrome does for each. (#1395)

    background-image: var(--g) was dropped with a warning in a stylesheet and in a style attribute or [style.background-image] binding. It now reads a token from a stylesheet, one set on an element, or a fallback. A token of none, one with a colour in it or one that holds no image leaves no image, as in Chrome, where such a value is invalid.

    A token such as --x: center currentColor in background: linear-gradient(...), var(--x) or background: var(--x) was read as invalid, so nothing was drawn. Its colour is now the element's text colour, its own or inherited, and follows it when it changes. The same holds for a token set on an element, a var() fallback, and background: linear-gradient(...), currentColor in a style attribute or [style.background] binding, which was dropped with a warning.

    background: platform-color(link) in a stylesheet now writes no image and the initial position, size and repeat, as background: red does, where a weaker rule's image stayed. A system colour such as background: LinkText already did this, and so did a style attribute or a [style] binding.

  • ScreenCapture.protectAppSwitcher() now hides the app's snapshot in recent apps on Android 13 and later without blocking screenshots, where it rejected with an UnavailabilityError from expo-screen-capture. (#1414, #1413)

    It calls Activity.setRecentsScreenshotEnabled(false) through this package's native half, and unprotectAppSwitcher() calls it with true. The setting is applied again to an activity that is recreated while it is on. It needs a development build. On Android before 13, and in Expo Go on Android, both calls now resolve without doing anything. blurIntensity is used on iOS only, which is unchanged.

  • A text-shadow with no colour, or in currentColor written out, in a token or as the fallback of a var(), in a stylesheet is now drawn in the element's text colour, as Chrome draws it, where it was dropped with a warning. It follows the text colour when that changes, as a box-shadow with no colour does, and a text inside it with a colour of its own draws the shadow it inherits in that colour, as CSS inherits currentColor as the keyword. (#1398)

  • A null bound to an optional number or boolean input in @ng-native/components or @ng-native/expo, or to an optional boolean input in @ng-native/router, is now unset, as undefined is. Before, a number input sent NaN to native and a boolean input sent false. (#1409, #1408)

    Among them are [numberOfLines] on <text>, [maxLength] on <text-input>, [spacing] on <expo-glass-container> and [collapsable] on <view>; [size] on <expo-symbol> takes its default of 24. A binding that turns from a value to null removes the prop, so native goes back to its default. A boolean bound to null now takes the default an unset one takes rather than false, which shows most where that default is true: [editable] on <text-input> leaves the field editable, [animating] on <activity-indicator> spins and [scrollEnabled] on <scroll-view> scrolls. The same goes for [accessible] and [focusable] on a component that is accessible or focusable by default, [stickySectionHeadersEnabled] (sticky on iOS), [alwaysBounceVertical] on a vertical scroll view, and aria-busy, aria-checked, aria-disabled, aria-expanded and aria-selected, which are left out of the accessibility state rather than set to false.

  • A property children inherit now eases in them while an element transitions or animates it: the text inside a view whose color transitions takes each frame's colour, where it jumped to the end colour at once, and the text inside a view whose color a @keyframes animation plays follows the frames, where it kept no colour at all. A child that sets the property itself keeps its own. (#1397)

  • Tabs that are each flex: 1 1 0% (Tailwind's flex-1) in a row as wide as its content now share the row's width as in Chrome, so tabs of different lengths come out the same width wherever that keeps each word whole, where a w-fit, max-w-max or inline-flex row had each tab at the width of its own text. (#1403)

    A tab whose even share would be narrower than its longest word is held at that word, and the other tabs share what is left, so no word is broken. That applies to an item with a flex-basis of 0% or 0 in any row fitted to its content, including one kept from stretching by align-self: flex-start and one stretched across a width: fit-content box, where such tabs were the same width even when that was narrower than a word. Chrome under the layout reset keeps the even share and lets the word overflow the tab, so there the widths differ from Chrome's. An item with a min-width of its own keeps that minimum, and none is held wider than its row.

    An item with a basis of 0% that does not grow is held at its longest word in such a row, where it was at the width of its text, or 0 wide in a row kept from stretching. One with a basis of 0 points that does not grow stays 0 wide, as in Chrome.

    Reading each tab's longest word takes two more commits when such a row is first laid out or what it holds changes. An item's own min-width: auto in such a row now holds as in Chrome too, where it had the even share or the width of its text.

❤️ Thank You

v0.22.1

Choose a tag to compare

@github-actions github-actions released this 05 Nov 20:27

🩹 Fixes

  • injectToken('--accent') from @ng-native/device reads a CSS custom property's resolved value as a signal, at the root or at a given node; on iOS and Android it follows the colour scheme, media conditions and stylesheet changes, and on the web it follows class and inline style changes under the mount point, the dark class mount keeps included. Engine.token and Engine.watchToken do the same without Angular. A shape's fill or stroke written as var() on the shape itself is resolved against the tokens in scope. (#1411, #1410)

❤️ Thank You

  • Ashley Hunter

v0.22.0

Choose a tag to compare

@github-actions github-actions released this 04 Nov 14:45

🩹 Fixes

  • On iOS a large title no longer drops into place on every push when a header item holds a ui-host with matchContents, on a stack whose root page has no header, in a development build with @ng-native/expo. (#1400, #1399, #1405)

    react-native-screens lays the navigation bar out at once whenever a header item's size changes, and such a host has its size only once SwiftUI lays it out, during the push. @ng-native/expo now defers that layout until the push or pop ends, for every header item. Expo Go keeps the drop; a host with a fixed size, or systemImageName, avoids it there.

  • A box whose box-sizing alone changes is now laid out again on iOS and Android, where it kept the size it had: React Native's Yoga does not compare boxSizing when a view's props change. To do that, a content-box box whose sizes, padding and border are each in points or unset is committed as the border box it makes, with boxSizing: 'border-box' and its padding and border added to its width, height and their least and most. It lays out the same, but the props a test reads from a commit change for such a box, a web-compat div and text field among them. (#1393)

    A content-box box with a percentage size or padding is committed as it was, and still keeps its size on iOS and Android when its box-sizing alone changes.

    A content-box box with an aspect-ratio and a width or height in points is committed with the ratio of its border box, so the ratio is of its content box, as in Chrome, where Yoga took it of the border box. One with an aspect-ratio and no size in points, or with a least or most size, is committed as it was. The padding added to the width is read in the box's direction, its own, inherited, or the app's.

  • A z-index of a math function in a stylesheet, such as calc(3 / 2) or min(-2.5, 4), now compiles to the integer Chrome rounds it to, as the same value set in a style already did, where it was dropped with a warning. (#1387)

    A math function that comes to a length, such as calc(1px), is still dropped with a warning, as Chrome drops it. One that comes to NaN, such as sqrt(-1), compiles to 0, as Chrome reads it. A round() to the nearest, such as round(-1.5, 1), is dropped with a warning, since the compiler would round a negative tie away from zero where Chrome rounds it up. A round() with a negative interval, such as round(up, 3, -2), rounds as Chrome does, and the function and strategy names are read in any case. A z-index past the range of a 32-bit integer, such as calc(infinity) in a stylesheet or calc(1e20) in either, is clamped to that range, as Chrome clamps it. One with a string in it, such as calc("2"), is dropped with a warning, as Chrome drops it.

  • On iOS a stack's root page with a large title now launches with the title expanded, where it showed collapsed in the bar until the page was pulled down. (#1402, #1401)

    <native-stack-outlet> commits its RNSScreenStack only once its first screen has joined it, with that screen, as react-native-screens' own JavaScript does. Committed empty, the stack was laid out with a compact bar before the root page arrived, and the page's scroll view kept the offset that bar gave it. A test that reads the committed tree of an outlet with no screen no longer finds an RNSScreenStack there.

  • On Android a tap on an open <native-search-bar> no longer presses the <native-header> item hidden under it: while the field is open, the header's other <native-header-item>s take no touches. (#1406, #1404)

    react-native-screens hides those items when the field opens, but a hidden view keeps its bounds, and React Native's Android touch dispatch finds a target by bounds alone. The items' content now inherits pointer-events: none between the search bar's topOpen and topClose, so the props committed for that content change while the field is open, including an item added while it is open. Content that sets its own pointer-events to auto still takes touches. iOS sends no open or close and is unchanged.

❤️ Thank You

  • Ashley Hunter

v0.21.0

Choose a tag to compare

@github-actions github-actions released this 03 Nov 22:28

🩹 Fixes

  • background: <gradient>, var(--x) now keeps or drops its gradients as Chrome does for a token holding a position, a size or a colour beside them, such as 10% 20%, center / cover or center red, and a stylesheet's token can now hold a gradient. (#1388)

    A last layer the token holds is read as a whole: a position with an optional size, a repeat, an attachment, up to two boxes and a colour, each at most once, so none none, fixed scroll and center center center now make the shorthand invalid, as in Chrome. This holds for a token in a stylesheet, one set on an element, and a stylesheet fallback such as var(--x, 10px 20px).

    --g: linear-gradient(...) in a stylesheet is no longer dropped with a warning, and background: var(--g) draws it.

    background: var(--x) of a colour now takes away an image a weaker rule declared, as Chrome does, where the image was left before.

    [style.background-image] and a background-image in a style attribute now draw linear gradients and none, where the text was committed as it was before. Any other value, a token among them, is dropped with a warning.

  • A stylesheet's background: <gradient>, var(--x, center / cover) now keeps the gradient when --x is unset, as Chrome does, where a fallback of several words with no colour made the shorthand invalid. (#1390)

    In development such a fallback no longer draws a warning that the custom property is undefined and the declaration dropped, which it said for var(--x, none) too.

  • @ng-native/expo has a TextRecognition service whose recognize(imageUri, { languages, level }) reads the text in an image on iOS, answering the whole text and each line with its confidence and its box in the image's pixels. (#1385)

    It is Vision's VNRecognizeTextRequest, from new native code in @ng-native/expo, so it needs a new development build. It runs offline and does not need Apple Intelligence. A box is measured from the image's top left corner with the orientation in the file's metadata applied. In Expo Go, on Android, on the web and in Node, available is false and recognize rejects with a MissingModuleError; a test provides a stand-in through TextRecognition.SOURCE.

  • @ng-native/router has a TabBarInsets service whose bottom signal is what the tab bar covers of the bottom of the window now, for an overlay outside the tabs: 0 while the bar is hidden or covered, or with no tabs. (#1375, #1359)

    On iOS, each opened tab of a <native-tabs-outlet> holds a view of no width after its page to measure the bar, and a <safe-area-provider> in the app is what tells an iPad's bar at the top from one along the bottom. On iOS, a page presented in a tab's own stack counts as covering the bar. On Android, it does not, since the bar stays.

    On Android, the height comes from a new native module in @ng-native/expo, so the app needs that package and a new development build. In Android Expo Go, which cannot load it, bottom stays 0. The value includes the home indicator or the system navigation bar, so an overlay takes the larger of it and the safe-area inset rather than their sum.

  • A box-shadow with no colour in a stylesheet is now drawn in the element's text colour, as Chrome draws it, where it was dropped with a warning, and a stylesheet's font shorthand now puts font-style back to normal, so a weaker rule's italic no longer shows through. (#1389)

    The shadow follows the text colour when it changes, own or inherited, as a border with no colour does. Such a shadow in a @keyframes frame is still dropped with a warning, as other uses of currentColor there are.

  • A box written as wide as its content in a right-to-left box, with a physical inset on one side and a logical one on the other, such as left: 10px; inset-inline-start: 5px, is fitted between the two as in a browser, where it was stretched between them. (#1377)

    In a right-to-left box inset-inline-start is the right, so with left it gives the box an inset on each side.

  • A development reload while a page of another tab is presented over the tab in front comes back with that page presented, where it logged NG04002: Cannot match any routes and came back without it. (#1379, #1368)

  • A null bound to [width] on <native-toolbar-item>, or to [titleFontSize], [largeTitleFontSize] or [backTitleFontSize] on <native-header>, is now unset as undefined is, where it sent native NaN. (#1378, #1370)

  • An item with a percentage flex-basis and a width of its own, such as flex: 0 0 50%; width: 10px, is measured at that width in a row kept from stretching by align-self: flex-start or its column's align-items, as in a browser, where it was measured at its content. (#1381)

    The share is then taken of the width the row comes to. A change to that width, set on the element or by a class, has the row measured again; before, a row fitted to its content kept its old width until something else in it changed.

  • @ng-native/router lays a stack's hidden navigation bar out with a large title before the first <native-header largeTitle> page is pushed onto it, so on iOS that push no longer starts from the compact bar, with the inline title fading out and the large title dropping into place. (#1396, #1394)

    The bar is hidden while the stack's root page has no header, and react-native-screens turns large titles on only once the push has started. A header with a large title now asks a new native module in @ng-native/expo to lay the hidden bar out once beforehand, so the app needs that package and a new development build. In Expo Go, which cannot load it, the first such push still starts from the compact bar.

  • An aspect-ratio of a width and a height set in a style, such as 16 / 9 or calc(16 / 9) / 1, is now committed as the one number native takes, and a style value or a custom property with a bracket left open, such as translateX(1px, is read with it closed, as Chrome reads both. (#1386)

    Before, a ratio with math on either side reached native as text and drew no ratio, and a value with a bracket left open was dropped or reached native as text. A ratio with a side of 0, or with auto in it, still reaches native as it is written.

    A ratio in a style with a side CSS reads no number from, such as 1 / 0x10, 1 / Infinity or 1 / -2, or with a third side, is dropped with a warning, so the stylesheet's ratio shows, as in Chrome.

  • A negative written out in a style, such as width: -4px, -1em or -10vw, on a property with no value below zero is now dropped with a warning, as Chrome drops it, so the stylesheet's value shows instead. (#1384)

    This covers the props a math function below zero is clamped on: sizes, padding, font size, line height, border widths and radii, gaps, flex basis, grow and shrink, and aspect ratio. A negative opacity is kept, since CSS clamps it. This includes a binding such as [style.width.px] that comes out negative, which Angular sends as -4px. A style object's own numbers, such as { width: -4 }, are React Native's and are unchanged.

    A shorthand in a style with one part a browser drops, such as padding: 1px -1px or padding: 1px calc(1px + 2), is now dropped whole with a warning, where its other sides were kept before.

  • A font-size of 0 no longer throws on Android or draws its text at the default size on iOS: the engine commits a size of 0.001 in its place, too small to see. (#1376, #1369)

    React Native on Android requires a font size above zero, so font-size: 0, 0em or a calc() clamped to 0 threw FontSize should be a positive value, from a stylesheet, an inline style or a token alike. Android draws no text smaller than one device pixel, so there the text leaves a faint mark one pixel tall in a line box about 1dp tall, where Chrome leaves no box.

  • @ng-native/router lets a canDeactivate guard decide a native pop of a pushed screen, and follows a screen native has already dismissed without asking its guards, so a refusing guard no longer leaves the url on a screen that is gone. (#1380, #1366, #1367)

    The pushed screen in front holds preventNativeDismiss while leaving it would run a guard, as a presented screen already did, and lets go once it would not. On iOS a swipe back, the back button and a pick from its menu then go to the router, where the guard answers, as Android's Back button and header back arrow already did. On iOS a held push cannot be swiped back interactively: the swipe asks rather than following the finger, as with React Navigation's usePreventRemove.

    A screen native dismisses anyway, such as a formSheet dragged down or closed by a tap outside it on Android, is followed without asking its guards or those of the routes showing inside it. A refusal used to keep the router on the dismissed screen, where its guard went on refusi...

Read more

v0.20.0

Choose a tag to compare

@github-actions github-actions released this 02 Nov 19:29

🩹 Fixes

  • A background shorthand of gradients over a token's colour, background: linear-gradient(180deg, #ffffff33 0%, #ffffff00 67.7%), var(--primary), compiles to the gradients as its image and the token as its background-color, where Metro dropped the whole declaration with "'background' mixes var() with other values". The token has to be the whole of the last layer, with no token in the other layers. A last layer that is a gradient with a token in it goes through the existing gradient path as before, and a token anywhere else is still dropped with that warning. (#1308, #1295)

  • @ng-native/expo has AddressSearch, from @ng-native/expo/address-search: suggest(query, options) answers a signal of the postal addresses that match a query signal, and resolve(suggestion) answers the picked one's line one, town, postcode, country and other parts, with no permission and no API key. (#1310, #1300)

    On iOS it uses MapKit's MKLocalSearchCompleter and MKLocalSearch. On Android it uses the platform's Geocoder once the query has not changed for 300 ms, and that geocoder answers whole addresses rather than completions. region: 'GB' keeps the suggestions to that country on Android, to the area around it on iOS 18 and later, and puts that area first on earlier iOS versions. It is native code of @ng-native/expo, so an app needs a new development build to use it. In Expo Go the suggestions stay empty.

  • In @ng-native/router, a <native-header-item> at the left or the right with no content and a systemImageName or a title is a native UIBarButtonItem on iOS, with <native-toolbar-item>'s inputs and (press). (#1309)

    It commits in the header's headerLeftBarButtonItems or headerRightBarButtonItems rather than as a custom view, in its place among the side's items as written, and takes barButtonItemStyle (plain or prominent), tintColor, disabled, accessibilityLabel, hidesSharedBackground and sharesBackground. An item with content stays a custom view, as before. The new hidden input leaves any header item out of the bar. Android's header draws nothing for a bar button and says so once in development.

  • In @ng-native/expo, Location.geocode() and Location.reverseGeocode() no longer ask for the location permission on iOS, so an app that only geocodes shows no location prompt there. (#1307)

    Apple's geocoder needs no permission, and expo-location asks for none before geocoding on iOS. Before, both methods asked for the foreground permission first on every platform and answered an empty list when it was refused. On Android they still ask first and answer an empty list without it, because expo-location refuses to geocode there without the foreground permission.

  • FontRegistry.loadSheet() warns in development when it is given a null sheet, as loadFonts() does. (#1346, #1338)

    The warning is said once per process, whichever of the two receives the null, and its wording names both. A null sheet is what styleSheetOf() returns for a class with no compiled sheet.

  • A percentage width capped at max-content is the smaller of the two, a width: fit-content or max-content capped box between a left and a right stays as wide as its content, and a percentage flex-basis in a box fitted to its content is a share of that box's width, as in a browser. (#1348, #1341, #1342, #1343)

    These change committed layout. A width: 50%; max-width: max-content box with wide content took all of it, and now stops at the percentage: maxWidth is committed as the percentage, and a height capped the same way behaves alike. An absolute or fixed box written as wide as its content filled the room between its insets, and is now fitted within what they and its margins leave, which needs layout read back from native and costs two more commits when something in it changes. An item with flex: 0 0 50% in a width: fit-content row was as wide as its content, and now takes half of the row. A basis of 0% is unchanged. With direction: rtl on a subtree, such a box between two insets sits at the left inset.

  • A row that is width: fit-content or max-width: max-content stays as wide as its items when one of them grows, as in a browser, where with layoutConformance: 'strict' it had no width at all when the item grew from a basis of zero points (flex: 1 1 0px, or React Native's flex: 1), and could otherwise fill all the room on offer. (#1327, #1315)

    The engine fits such a row to its content, as it already fits a row with align-self: flex-start in a column, and fits a width: fit-content box around a row the same way. A row first laid out with a width, a stretch or a growth of its own, and then let go of it in its style, is fitted to its content again, where it kept the width Yoga gave it, which could be none or all the room on offer.

  • A row as wide as its content, such as one with align-self: flex-start in a column, stays as wide as its items when one of them grows by flex-grow or React Native's flex, as in a browser, where it filled all the room it could have; and a flex of several values, a keyword or a basis in a style attribute or a [style] binding commits its grow, shrink and basis. (#1305, #1295)

    React Native's Yoga gives such a row all the room on offer once any item grows, so a segmented control whose tabs are flex: 1 filled the screen. The engine fits the row to its content with the items' growth set aside, as it already did for items with a percentage basis, and commits it at that width, which costs two more commits when something in the row changes. A row that is stretched, or has a width, still shares all of it among its items.

    style="flex: 1 1 auto" was committed as a string native drops, so the item did not grow at all. A flex such as 1 1 auto, 2 3, none, auto or 30% now commits flexGrow, flexShrink and flexBasis, as a stylesheet does, and one that is no CSS flex, such as 1 -1 or four values, is dropped with a warning in development. One number, flex: 1, is still React Native's own flex in a style attribute, a binding and a style object.

  • @ng-native/expo has a config plugin with a pointerEvents option, off by default, that turns on React Native's W3C pointer events on iOS and Android, so a trackpad, a mouse or a pencil reaches Pressable.hovered and @defer (on hover). (#1357, #1325)

    Add ["@ng-native/expo", { "pointerEvents": true }] to the plugins in app.json, then run npx expo prebuild and make a new development build. It also sets UIApplicationSupportsIndirectInputEvents in Info.plist, which iOS 16 needs to tell a trackpad or a mouse from a finger. It has no effect in Expo Go. A finger's pointer events do not set hovered or fire @defer (on hover). The development warning for @defer (on hover) now names the option.

  • styleSheetOf() and loadFonts() warn in development in a browser build too, where ngDevMode stands in for the __DEV__ a browser bundler does not define. (#1347, #1339)

    The warning about a styleSheetOf() read from a static field of the component's own class, and the one about a null sheet given to loadFonts() or FontRegistry.loadSheet(), were said on a device only. __DEV__ still decides wherever it is defined, so a release build on a device stays silent. The globalStyles: null warning of mount() from @ng-native/platform is still a device warning, since @ng-native/web has its own mount().

  • Clamp a math function that comes out below zero to 0 on a property that cannot be negative, in a stylesheet, a style attribute, a [style] binding and a calc() of tokens, and refuse a length added to a number with a warning. (#1352, #1330, #1337)

    The properties are sizes, padding, font-size, line-height, gap, border and outline widths, border radii, flex-basis, flex-grow, flex-shrink, aspect-ratio and opacity. width: calc(1px - 5px) commits 0 where it committed -4. A margin, an inset and letter-spacing keep their negative.

    A negative written out on one of these properties other than opacity in a stylesheet, such as width: -4px, is now dropped with a warning, as a browser drops it, where it was committed. opacity clamps a negative however it is written. A token or fallback that holds a negative written out and is used whole on one other than opacity, width: var(--t) with --t: -4px, leaves the declaration unset, in a stylesheet and set on an element, as it is invalid there in a browser. A fallback in em or a viewport unit is clamped to 0 instead.

    A stylesheet calc() or min() that mixes a length and a number, such as width: calc(1px + 2), is dropped with a warning where it compi...

Read more

v0.19.2

Choose a tag to compare

@github-actions github-actions released this 29 Oct 16:40

🩹 Fixes

  • On iOS, Notifications puts the data of a push sent through FCM or straight to APNs in request.content.data, which expo-notifications fills only for a push from Expo's push service. (#1289, #1287)

    This covers latest, response, take(), presented(), and the notification a setHandler() handler is passed, including the one on a timeout error given to its handleError. The keys come from request.trigger.payload and are used only when content.data is null or missing. The keys the push services add are left out: aps, FCM's fcm_options, gcm. and google. keys, and on a push from Expo's service its experienceId, projectId and scopeKey. Android, local notifications and an Expo push whose data the module already read are unchanged.

  • fileRoutes gives a lazy page's canMatch guards the same instances of its routeMeta providers that the page and its resolvers get, as Angular does for an eager page. (#1292)

    Before, a lazy page's canMatch guards ran in an injector of their own, made for each navigation and destroyed after it, so a service a guard wrote was not the one the page read. The injector is now made once for the page's route, and the page's routes are loaded into it. With withAutoCleanupInjectors(), it is destroyed when the page is left and made again on the next visit, as an eager page's is.

  • On iOS, a <refresh-control> that is already refreshing when its page is pushed, or when its tab is shown for the first time, starts spinning once the screen has appeared, so it no longer leaves a gap under a large title. (#1298)

    Before, native began it as the page mounted, before UIKit had laid the page out under its bars. Under a large title that left a gap the spinner's height between the title and the content, which stayed after the spinner stopped and showed on pages shown after it. React Native's own RefreshControl does the same. Now a control under a <native-stack-outlet> or <native-tabs-outlet> commits refreshing from the screen's appear (a tab's didAppear), so the spinner shows as the push finishes rather than during it. On Android, and on a view no outlet shows, nothing changes.

    SCREEN_SHOWN in @ng-native/device is the signal it reads: false for a screen until native says the screen has appeared, then true, and always true outside an outlet.

    In a Node test rendered for iOS, a refresh control under an outlet now commits refreshing: false until the test emits the screen's topAppear (for a tab, topDidAppear) with fabric.emit, since the fake Fabric sends no appear events.

  • nx g @ng-native/nx:library in the TypeScript preset adds the app's Babel 7 @babel/core to the library's dev dependencies, so pnpm 10 no longer prints "Failed to create bin" for expo and unmet @babel/core peers on every install. (#1288, #1285, #1286)

    @angular/compiler-cli depends on Babel 8, which otherwise answered React Native's @babel/core peer in the library under pnpm 10, giving the library its own copies of react-native and expo. pnpm 11 resolves the peer to the root's Babel 7 already. A library generated before this can add "@babel/core": "^7.29.7" to its devDependencies by hand.

❤️ Thank You

  • Ashley Hunter

v0.19.1

Choose a tag to compare

@github-actions github-actions released this 28 Oct 16:23

🩹 Fixes

  • FirebaseMessaging.unregister() in @ng-native/firebase-messaging deletes the app's Firebase installation, so FCM answers UNREGISTERED for its FID, sets installationId to null, and stops re-registering on a token roll and re-reading the FID on coming back to the front until register() runs again. Call it on sign-out when pushes belong to the signed-in user. (#1284, #1282, #1283)

    On Android, a call without google-services.json in the app now rejects with ERR_NO_FIREBASE_CONFIG, as on iOS, rather than a generic error. FirebaseMessaging reads nothing from Notifications until register() runs, so a test that fakes Notifications without devicePushToken can inject it. The docs show an app.config.js that leaves out a missing Firebase config file, so prebuild works without one.

❤️ Thank You

  • Ashley Hunter

v0.19.0

Choose a tag to compare

@github-actions github-actions released this 28 Oct 13:43

🩹 Fixes

  • On iOS, @ng-native/expo registers its @expo/ui modifiers once per app launch instead of on every reload, which could crash a development build in ViewModifierRegistry.register during a hot reload. (#1261)

    @expo/ui's modifier registry is shared by the whole process and has no lock, and Expo recreates modules on the JavaScript thread on each reload while the main thread can still be reading the registry for SwiftUI views on screen. The modifiers are the same on every reload, so they are now registered the first time the module is created.

  • On iOS, a ui-bottom-sheet whose content is in a navigation stack puts its modifiers on the content inside that stack, so ignoreSafeArea lets the content run under the home indicator. (#1269)

    The content is in a navigation stack where the build has SwiftUI's toolbar (@expo/ui 57.0.20 and later) and the sheet is not sized to its content. The stack sets up the safe area again for what is inside it, so the modifiers were outside its reach on the ui-group; they are now on the ui-zstack inside the stack, and the sheet's own presentation modifiers stay on the group. Presentation modifiers given this way, such as presentationCornerRadius, still apply. A content modifier such as background or padding there applies below the sheet's bar, where it has one. A sheet sized to its content, and a build without the toolbar, are unchanged.

  • A <virtual-list> row whose item comes back to the window after leaving it, with nothing arriving to take its place (a filter put on below the scroll position and taken off again), takes back the slot it left. It took another parked slot, so the window held its key twice: with track row.key Angular threw NG0955 (duplicated keys) and the row was drawn twice, and with track row.slot a hidden copy of it was kept. An item that comes back as another itemType gets a slot of that type, and its old one is let go. (#1265)

  • A <native-tabs-outlet> under a route parameter, such as city/:name, now finds a url inside a tab when the parameter is written with characters a url escapes (an accent or a space, são-paulo, new york) or with matrix parameters (rio;lang=pt). It compared the router's url with the tab's url built from the unescaped segment, so no url was inside a tab: tapping back to a tab opened its first screen instead of the page it was on, and a tap dropped the matrix parameters. A page pushed into such a tab before it was opened now has the tab's first screen under it, as it has elsewhere. (#1266)

    A url that gives a tab's own segment matrix parameters, such as library;sort=a/7, is now inside that tab too, under a parameter or not. It was in no tab, so tapping away and back opened the tab's first screen without them.

  • Fixes from a security review. (#1279)

    • @ng-native/web-compat: XMLSerializer escapes the namespace it writes in an xmlns attribute, so a namespace holding a quote or markup can no longer add attributes or elements to the output.
    • @ng-native/web-compat: only an <iframe> in the HTML namespace becomes a web view or an emulated frame. One in SVG or MathML, such as one inside an SVG image's markup, or one made with createElementNS in another namespace, is a plain element, and its srcdoc is not loaded.
    • @ng-native/web-compat: on Android, a message from a frame inside a web view's page that reports its origin as null arrives with the origin null instead of the web view's own origin. A srcdoc page loaded with no base, which Android also reports as null, is still heard from the app's origin.
    • @ng-native/web-compat: a message posted to an iframe for an origin (not *) waits until the page its new src or srcdoc names has loaded. On Android, one posted as srcdoc replaced a data: page reached that page.
    • @ng-native/web-compat: on iOS, an iframe whose src has no host, such as a data: URL, is shown as an empty page with a warning. react-native-webview there threw on it and closed the app.
    • @ng-native/web-compat: an SVG <img> in a template's content, a DOMParser document or markup setHTML is sanitizing is not fetched until it is put in a document that loads it.
    • @ng-native/web-compat: an emulated frame more than ten frames deep stays blank, so a page that frames itself no longer loads without end.
    • @ng-native/web-compat: DOMParser parses XML that declares a namespace prefix on every level of deep nesting in time that grows with the declarations instead of with their square.
    • @ng-native/router: native tabs leave a tap on a tab that was visited and sits behind a canActivate, canActivateChild or canMatch guard to the router: the tab is selected once the guard agrees, instead of showing its kept page while the guard runs. This needs react-native-screens 4.30 or later; earlier versions select at once as before.
    • @ng-native/router: a page present() shows over another tab runs each guard of the routes above it with the providers down to that route, not with the page's own.
    • @ng-native/router: fileRoutes runs a page's canMatch guards with the providers in its routeMeta, eager or lazy, as Angular runs a route's guards with its own providers.
    • @ng-native/components: the Markdown component compares each changed block once when a new source arrives, where deeply nested blocks such as quotes inside quotes cost three times more for every level.
    • @ng-native/tailwind: a var() name holding a character that has a meaning in a regular expression, such as --a**, no longer stops the build with a SyntaxError. A line break in the Tailwind entry's file name no longer ends the generated module's header comment.
    • @ng-native/web: an image whose referrerPolicy is set is painted with an <img> that sends that policy, since a CSS background is fetched with the page's.
    • @ng-native/metro: the dev server keeps a parked route history exactly as the app sent it, so a deeply nested body can no longer crash the server.
  • The safe HTML methods in @ng-native/web-compat (setHTML, appendHTML, prependHTML, beforeHTML, afterHTML and replaceWithHTML) now remove an <iframe> inside <svg> or <math>, and any element registered as a web view, such as <web-view> or <dom-component>. Before, such an element passed the safe sanitizer and became a react-native-webview with scripts on, so untrusted markup could run a script at the page's origin and post messages to the app as that origin. (#1271)

    <dom-component> in @ng-native/expo now sends a changed input only to its own page. Before, once a link in the content took the web view to another site, that site could define window.__ngNative and receive every input the app changed afterwards.

  • On iOS 26 and later, a stack page's :host layout (flex-direction, justify-content, align-items, gap) lays out the elements at the root of its template again, and a :host > ... selector matches them again. (#1260, #1212)

    The safe area view the stack outlet puts a page's content in on iOS 26 and later, which starts it below an opaque <native-header>, is now made by the engine as a view of the screen's own (Engine.wrapChildren) rather than as an element of the page's tree. The page's template stays in its host, the screen, so its :host rules and selectors reach it as written, and the layout :host gives its children is committed on the safe area view, which holds them. For a page whose :host gives no such layout, the native views and their props are the ones committed before, and the header is still a child of the screen either way. Before iOS 26 and on Android nothing changes.

  • On iOS, a navigation that would put a screen on a stack hidden behind a fullScreenModal another stack presents now resolves false and leaves the url and the screens as they were, where before it resolved true and showed nothing. (#1270, #1253)

    A screen a tab's own stack presents as fullScreenModal takes the app's stack out of the window, and react-native-screens' iOS stack makes no change while it is out of the window. So present() or push() of a top-level route, present() of a page of another tab, or a plain router navigation to such a route, from that modal, changed the url without showing the screen, and the next back() took off the screen nobody saw. These are now aborted before the url changes. A navigation inside the stack presenting the modal, to another tab, back to a screen still on the app's stack, or a replace() or reset(), goes ahead as before. Android is unchanged.

  • A running Metro dev server compiles a component again once its templateUrl or styleUrl file changes or is deleted, so a reload, a relaunch or a hot update gets the component as the file is now rather than as it was compiled before. (#1259, #1166)

    Metro transforms again only the file that changed, and kept the component's own module, with the old template or sheet compiled into it, until the component's .ts file changed or Metro restarted; a touch that left the .ts file's text as it was did not help. withAngularNative now wraps server.enhanceMiddleware to reach the dev server, which checks the templates and stylesheets each module was compiled from before it answers a bundle request or sends a hot update. A ...

Read more

v0.18.2

Choose a tag to compare

@github-actions github-actions released this 25 Oct 10:25

🩹 Fixes

  • Keyboard.windowTop on Android now includes how far down the screen the app's window starts, in the lower half of a split screen or in a freeform window, when provideNativeKeyboard() from @ng-native/expo is provided. (#1246, #1189)

    <keyboard-avoiding-view>, a focused <text-input> and web-compat's innerHeight compared the keyboard's top on the screen with views measured in a window that starts lower down, so they treated the keyboard as lower than it was. A KeyboardSource can now report the window's top on the screen as screenTop, and the NgNativeKeyboard module reads it. Expo Go, and apps without provideNativeKeyboard(), still leave it out: React Native has no way to read it.

  • @ng-native/expo/calendar reads and writes the device's own calendars, their events and, on iOS, their reminders. (#1244)

    inject(Calendar) lists the calendars and the events in a range, finds a calendar, event or reminder by id, creates a calendar, and holds full, write-only (iOS 17 and later) and reminders permissions as signals. It wraps expo-calendar's object API, which needs a development build. What only iOS has, the default calendar, the picker, the sources and the reminders, answers empty on Android instead of throwing. EntityTypes, Frequency and Availability come with it, usable without loading the module.

  • <text-input> on iOS 26 and later no longer loses characters typed quickly into an empty field, because iOS is sent the latest eventCount as the mostRecentEventCount prop only with a change to a multiline field's text style. (#1257, #1251)

    UIKit gives an empty field's first typed text a paragraph style whose line break strategy differs from the one React Native compares an echoed text prop with, so an echo carrying the latest count replaced that text with the same string and dropped the keystrokes queued behind the replace. On iOS the prop now stays at 0, the count a field starts at, so an initial value and a value set before anything is typed still arrive as the prop; text set from code after typing, a refused or rewritten keystroke and a bound selection arrive with the count through setTextAndSelection, as before. The commit that changes a multiline field's color, font or another text style carries the latest count, so the text already typed is restyled. Android is unchanged. In @ng-native/testing, fireEvent.changeText and userEvent.type count each field's changes themselves, so an iOS field's committed mostRecentEventCount reads 0 after typing, but after a multiline field is restyled.

  • A <text-input> focused while the keyboard is up now scrolls clear of the keyboard in a sheet attached to the bottom of the screen on iOS, and no longer asks a scroll view resting under a navigation bar to scroll to zero. (#1258, #1247)

    On iOS, React Native measures a view in a presented sheet from the sheet's top, while the keyboard's top counts from the top of the screen, so engine.reveal() stopped short by the sheet's distance from the top of the screen and could leave the field behind the keyboard. It now takes that distance off the keyboard's top for a screen presented as modal, formSheet or pageSheet, and for a <modal> with presentationStyle pageSheet or formSheet, when the sheet is as wide as the window and taller than the room above the keyboard: such a sheet is attached to the bottom of the screen. A narrower sheet (an iPad's, or one floating at a partial detent) and a shorter one, which may have risen over the keyboard, are measured as before. Android is unchanged.

    reveal() also clamped its target at zero, so a scroll view resting above zero under a navigation bar (contentInsetAdjustmentBehavior="automatic") was sent to zero, under the bar, for a move of a few points. It now sends it no further up than zero or where it already is, whichever is further up. React Native's own scrollTo on iOS still stops at zero when the scroll view has no contentInset of its own.

  • nx g @ng-native/nx:nativescript <app> sets up @ng-native/nativescript in an app: the packages, the config plugin, the iOS and Android types, and provideNativeScript(). (#931)

  • @ng-native/nativescript calls iOS and Android APIs from TypeScript through @nativescript/react-native, and shows native views built in TypeScript. (#931)

    provideNativeScript() installs the runtime, NativeScript.call() runs a function for the platform on the JavaScript thread, runOnUI() runs a function on the platform's main thread, <ns-view> shows a UIView or an Android View a worklet builds, and androidContext() is the Activity to build an Android view with. The app's config plugins list "@ng-native/nativescript". See https://ng-native.com/packages/nativescript.

    On Android the config plugin sets android.enableMinifyInReleaseBuilds=false and nsSkipStaticBindings=true in gradle.properties, since @nativescript/react-native 9.1.2 cannot build or run a release on Expo SDK 58 with R8 on, and its release bindings task needs React Native's CLI, which an Expo app does not have. A release APK is larger without R8.

  • An app with @nativescript/react-native installed and no Babel config of its own gets the worklets plugin with strictGlobal, so a worklet naming a native class, such as UILabel.new(), no longer fails as the app starts with Property 'UILabel' doesn't exist. (#931)

    An app with its own Babel config gets a warning saying what to add to it.

  • An equal padding-inline-start and padding-inline-end compile to paddingStart and paddingEnd in a stylesheet with an @media rule in it, as they do in one without, and so do margin-inline-*, inset-inline-* and border-inline-*. They were compiled to left and right. A sheet whose only blocks inside blocks are top-level at-rules such as @media is no longer printed again by lightningcss before it is compiled, so it compiles as it would without them. (#868)

    An overflow-x and an overflow-y set to the same value in one rule, which compiled to overflow beside an @media rule, are now refused there with the warning to write overflow, as they already were in a sheet without one.

❤️ Thank You

  • Ashley Hunter
  • erKam
  • Shahar Kazaz

v0.18.1

Choose a tag to compare

@github-actions github-actions released this 24 Oct 08:14

🩹 Fixes

  • An app or library @ng-native/nx generates in a workspace with tsconfig path aliases resolves them in its tests with vite-tsconfig-paths, so on Nx 24 a test that imports one of the workspace's libraries no longer fails with Cannot find package. (#1242)

    nxViteTsPaths from @nx/vite, which the generators wrote before, is a stub that resolves nothing from Nx 24. The vitest-tsconfig-paths migration rewrites each Vitest config the generators wrote that way, and adds vite-tsconfig-paths to the root package.json when it lists none.

❤️ Thank You

  • Ashley Hunter