From ea583a390e9b873fc6f57221579ba522b552adce Mon Sep 17 00:00:00 2001 From: Freddy Harris Date: Sun, 25 Oct 2026 11:57:42 +0100 Subject: [PATCH 01/11] upgrade to react-native 0.85 and rescript 12.3.1 - remove StyleSheet.absoluteFillObject (removed upstream) - add Animated.useAnimatedValueXY and Animated.useAnimatedColor - add EventEmitter module - add selection to TextInput.ChangeEvent payload - add onPressMove prop to Pressable - add scrollsChildToFocus android prop to ScrollView Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 11 +++++ package-lock.json | 75 +++++++++++++++++------------------ package.json | 4 +- src/apis/Animated.res | 11 +++++ src/apis/EventEmitter.res | 15 +++++++ src/apis/EventEmitter.res.js | 1 + src/apis/StyleSheet.res | 2 - src/components/Pressable.res | 1 + src/components/ScrollView.res | 1 + src/components/TextInput.res | 14 ++++--- 10 files changed, 88 insertions(+), 47 deletions(-) create mode 100644 src/apis/EventEmitter.res create mode 100644 src/apis/EventEmitter.res.js diff --git a/CHANGELOG.md b/CHANGELOG.md index 358bc7a1d..da6be309a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,16 @@ # Changelog of `rescript-react-native` +## Unreleased + +- Upgrade to `react-native@0.85` + - Breaking: remove `StyleSheet.absoluteFillObject` (removed upstream, use `StyleSheet.absoluteFill`) + - Add `Animated.useAnimatedValueXY` and `Animated.useAnimatedColor` (with `Animated.Color.initialValue`: `Rgba` / `String`) + - Add `EventEmitter` module + - Add `selection` to `TextInput.ChangeEvent` payload + - Add `onPressMove` prop to `Pressable` + - Add `scrollsChildToFocus` Android prop to `ScrollView` +- Bump dev dependency to `rescript@12.3.1` + ## 0.84.0 - 2026-09-15 - Upgrade to `rescript@12` and `@rescript/react@0.15` diff --git a/package-lock.json b/package-lock.json index c8a91a9d8..fe471aa53 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,7 +15,7 @@ "npm-run-all": "^4.0.0", "npmpub": "^6.1.0", "prettier": "^2.0.0", - "rescript": "~12.0.2" + "rescript": "~12.3.1" }, "funding": { "url": "https://github.com/rescript-react-native/rescript-react-native?sponsor=1" @@ -941,13 +941,14 @@ } }, "node_modules/@rescript/darwin-arm64": { - "version": "12.0.2", - "resolved": "https://registry.npmjs.org/@rescript/darwin-arm64/-/darwin-arm64-12.0.2.tgz", - "integrity": "sha512-GcfiPE0L2N7smWXMBEW4pvlcqblSbKh2tisZTuOaLPr9+WmaXnBLap6OUT+XoEWzTY0RA4QURfpNJLAgqZPrzw==", + "version": "12.3.1", + "resolved": "https://registry.npmjs.org/@rescript/darwin-arm64/-/darwin-arm64-12.3.1.tgz", + "integrity": "sha512-pwdnHhuIMH4Y5nuEwBLAMOYvGIcAVgnSZq/DWUNZJWoUr7YZpNkABJp0TFGiyYePT5ld+nvjQ40oI0rKjEWK8w==", "cpu": [ "arm64" ], "dev": true, + "license": "(LGPL-3.0-or-later AND MIT)", "optional": true, "os": [ "darwin" @@ -957,13 +958,14 @@ } }, "node_modules/@rescript/darwin-x64": { - "version": "12.0.2", - "resolved": "https://registry.npmjs.org/@rescript/darwin-x64/-/darwin-x64-12.0.2.tgz", - "integrity": "sha512-di4eucoDdLsLqz86h7Cwywsmt+xFup7rpVlkx8L7i22RTqyTw+CD6aGjFj1Ddj2sVUd5SSFiE9Hj86QggzUgbg==", + "version": "12.3.1", + "resolved": "https://registry.npmjs.org/@rescript/darwin-x64/-/darwin-x64-12.3.1.tgz", + "integrity": "sha512-Eu39cHkzoHyC18BCRqnmZnLgP42c7JhR74UBl6XhaFOzuf63tddOssgTyX9mEihzok2xFBGLpETrCvZN8Qqtkg==", "cpu": [ "x64" ], "dev": true, + "license": "(LGPL-3.0-or-later AND MIT)", "optional": true, "os": [ "darwin" @@ -973,13 +975,14 @@ } }, "node_modules/@rescript/linux-arm64": { - "version": "12.0.2", - "resolved": "https://registry.npmjs.org/@rescript/linux-arm64/-/linux-arm64-12.0.2.tgz", - "integrity": "sha512-x6WOyimX0kw9DGADQ02R2rmvKu8SQOrskfYrKREfc2CgDT+FOKAH+qxvpUiljKhrvthoHkVBJOXtlKk854OdjQ==", + "version": "12.3.1", + "resolved": "https://registry.npmjs.org/@rescript/linux-arm64/-/linux-arm64-12.3.1.tgz", + "integrity": "sha512-Nsk3qfDRrp9b6RI85t0xzrr8LoxwXAI678V7zpQMaRfhltYl+N5fsK66MWx9rSrv6SIWPVSyU8FrJG0MskONwQ==", "cpu": [ "arm64" ], "dev": true, + "license": "(LGPL-3.0-or-later AND MIT)", "optional": true, "os": [ "linux" @@ -989,13 +992,14 @@ } }, "node_modules/@rescript/linux-x64": { - "version": "12.0.2", - "resolved": "https://registry.npmjs.org/@rescript/linux-x64/-/linux-x64-12.0.2.tgz", - "integrity": "sha512-5QHE+Ca1h3qXxDkw343923WTEdEyBeXphrv+8SZLhLOFNabQ5t6m4rWf52RluDqQpa67j6GB8kVAZpqc2rA2Qw==", + "version": "12.3.1", + "resolved": "https://registry.npmjs.org/@rescript/linux-x64/-/linux-x64-12.3.1.tgz", + "integrity": "sha512-CfAJ3q/y/GSwrSJCLdCdyLWWdb7kTZkl2vjC+mSAw3pMqknEyCbt4ukS64Xn8PrqxVGHIs+kdefEphU7dt77xA==", "cpu": [ "x64" ], "dev": true, + "license": "(LGPL-3.0-or-later AND MIT)", "optional": true, "os": [ "linux" @@ -1017,21 +1021,21 @@ } }, "node_modules/@rescript/runtime": { - "version": "12.2.0", - "resolved": "https://registry.npmjs.org/@rescript/runtime/-/runtime-12.2.0.tgz", - "integrity": "sha512-NwfljDRq1rjFPHUaca1nzFz13xsa9ZGkBkLvMhvVgavJT5+A4rMcLu8XAaVTi/oAhO/tlHf9ZDoOTF1AfyAk9Q==", + "version": "12.3.1", + "resolved": "https://registry.npmjs.org/@rescript/runtime/-/runtime-12.3.1.tgz", + "integrity": "sha512-yTM0iTl6owyA37UEtPAZR0A3G1kFffJWb+UE6+rcG/N2kxYQnuSlNDfVvsry7l8O+o/ME6QE7Zt7efjCDGM3xg==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@rescript/win32-x64": { - "version": "12.0.2", - "resolved": "https://registry.npmjs.org/@rescript/win32-x64/-/win32-x64-12.0.2.tgz", - "integrity": "sha512-QcNQiok3H2Xd+cReB1kpH4hlPXnT7DA6ETsJfZugTUOKOdAYLTJ+Imynv+ojuafkUrwa2v0tZAiqHaQsDjf9IQ==", + "version": "12.3.1", + "resolved": "https://registry.npmjs.org/@rescript/win32-x64/-/win32-x64-12.3.1.tgz", + "integrity": "sha512-1ffFbw9IJnJmGTNshWzRcG6wQ7GkzOeWxLLrarvxI38p2aYPQKTbVovUraXpw1Jfg2bbWkm8hPMMrFIezI/pkA==", "cpu": [ "x64" ], "dev": true, + "license": "(LGPL-3.0-or-later AND MIT)", "optional": true, "os": [ "win32" @@ -5557,11 +5561,11 @@ } }, "node_modules/rescript": { - "version": "12.0.2", - "resolved": "https://registry.npmjs.org/rescript/-/rescript-12.0.2.tgz", - "integrity": "sha512-H1K9ovIxg2BVwJcXN1cZv0Q1pJEyFgNcUDKHSkmKmv5eBZMpXBASTZ2+grDt0zFFNoeFA1B9abAjSf+oTTi7hg==", + "version": "12.3.1", + "resolved": "https://registry.npmjs.org/rescript/-/rescript-12.3.1.tgz", + "integrity": "sha512-Q4y0SQ7EUShiX1ymEG5iOCGzbVSOh7CCLEnQUkq19FX2i64eQ2uEGeMCKFl0AsL7TcK5pS+66QTSP6jf6LSgaA==", "dev": true, - "license": "SEE LICENSE IN LICENSE", + "license": "(LGPL-3.0-or-later AND MIT)", "workspaces": [ "packages/playground", "packages/@rescript/*", @@ -5570,10 +5574,11 @@ "tests/docstring_tests", "tests/gentype_tests/**", "tests/tools_tests", + "tests/commonjs_tests", "scripts/res" ], "dependencies": { - "@rescript/runtime": "12.0.2" + "@rescript/runtime": "12.3.1" }, "bin": { "bsc": "cli/bsc.js", @@ -5586,18 +5591,12 @@ "node": ">=20.11.0" }, "optionalDependencies": { - "@rescript/darwin-arm64": "12.0.2", - "@rescript/darwin-x64": "12.0.2", - "@rescript/linux-arm64": "12.0.2", - "@rescript/linux-x64": "12.0.2", - "@rescript/win32-x64": "12.0.2" - } - }, - "node_modules/rescript/node_modules/@rescript/runtime": { - "version": "12.0.2", - "resolved": "https://registry.npmjs.org/@rescript/runtime/-/runtime-12.0.2.tgz", - "integrity": "sha512-Jma1QEgns/WlWeVap/Mv6dVbFEKmftPOrmwQbzLQnnrQ9z6PJjorHIvDMQUigQKnvM/4RhRJ3LiG+CsNtgS9ww==", - "dev": true + "@rescript/darwin-arm64": "12.3.1", + "@rescript/darwin-x64": "12.3.1", + "@rescript/linux-arm64": "12.3.1", + "@rescript/linux-x64": "12.3.1", + "@rescript/win32-x64": "12.3.1" + } }, "node_modules/resolve": { "version": "1.22.12", diff --git a/package.json b/package.json index ba54cd90b..748d1cf00 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "version": "0.84.0", "peerDependencies": { "@rescript/react": ">=0.15.0", - "react-native": ">=0.84.0", + "react-native": ">=0.85.0", "rescript": ">=12.0.0" }, "overrides": { @@ -45,7 +45,7 @@ "npm-run-all": "^4.0.0", "npmpub": "^6.1.0", "prettier": "^2.0.0", - "rescript": "~12.0.2" + "rescript": "~12.3.1" }, "prettier": { "trailingComma": "all" diff --git a/src/apis/Animated.res b/src/apis/Animated.res index 0cb2ff5a7..36ce11b2a 100644 --- a/src/apis/Animated.res +++ b/src/apis/Animated.res @@ -192,6 +192,9 @@ module Color = { @new @scope("Animated") @module("react-native") external createFromString: Color.t => t = "Color" + + type rgba = {r: float, g: float, b: float, a: float} + @unboxed type initialValue = Rgba(rgba) | String(Color.t) } @module("react-native") @scope("Animated") @@ -299,3 +302,11 @@ type config = {useNativeDriver: bool} @module("react-native") external useAnimatedValue: (~initialValue: float, ~config: config=?) => Value.t = "useAnimatedValue" + +@module("react-native") +external useAnimatedValueXY: (~initialValue: ValueXY.rawValue, ~config: config=?) => ValueXY.t = + "useAnimatedValueXY" + +@module("react-native") +external useAnimatedColor: (~initialValue: Color.initialValue=?, ~config: config=?) => Color.t = + "useAnimatedColor" diff --git a/src/apis/EventEmitter.res b/src/apis/EventEmitter.res new file mode 100644 index 000000000..c9bc585d8 --- /dev/null +++ b/src/apis/EventEmitter.res @@ -0,0 +1,15 @@ +type t + +@new @module("react-native") +external make: unit => t = "EventEmitter" + +@send +external addListener: (t, string, 'a => unit, ~context: 'context=?) => EventSubscription.t = + "addListener" + +@send external emit: (t, string, 'a) => unit = "emit" + +@send +external removeAllListeners: (t, ~eventType: string=?) => unit = "removeAllListeners" + +@send external listenerCount: (t, string) => int = "listenerCount" diff --git a/src/apis/EventEmitter.res.js b/src/apis/EventEmitter.res.js new file mode 100644 index 000000000..ae1b9f17e --- /dev/null +++ b/src/apis/EventEmitter.res.js @@ -0,0 +1 @@ +/* This output is empty. Its source's type definitions, externals and/or unused code got optimized away. */ diff --git a/src/apis/StyleSheet.res b/src/apis/StyleSheet.res index 58ae818ef..294a80773 100644 --- a/src/apis/StyleSheet.res +++ b/src/apis/StyleSheet.res @@ -2,8 +2,6 @@ external hairlineWidth: float = "hairlineWidth" @module("react-native") @scope("StyleSheet") external absoluteFill: Style.t = "absoluteFill" -@module("react-native") @scope("StyleSheet") -external absoluteFillObject: Style.t = "absoluteFillObject" @module("react-native") @scope("StyleSheet") external create: ({..} as 'a) => ({..} as 'a) = "create" diff --git a/src/components/Pressable.res b/src/components/Pressable.res index bd60fcb37..8d6e40d87 100644 --- a/src/components/Pressable.res +++ b/src/components/Pressable.res @@ -47,6 +47,7 @@ type props = { onLongPress?: Event.pressEvent => unit, onPress?: Event.pressEvent => unit, onPressIn?: Event.pressEvent => unit, + onPressMove?: Event.pressEvent => unit, onPressOut?: Event.pressEvent => unit, pressRetentionOffset?: Rect.t, style?: interactionState => Style.t, diff --git a/src/components/ScrollView.res b/src/components/ScrollView.res index d6e2667b5..186426ca5 100644 --- a/src/components/ScrollView.res +++ b/src/components/ScrollView.res @@ -65,6 +65,7 @@ type androidProps = { nestedScrollEnabled?: bool, fadingEdgeLength?: float, persistentScrollbar?: bool, + scrollsChildToFocus?: bool, } type scrollViewPropsWithoutListConflict = { diff --git a/src/components/TextInput.res b/src/components/TextInput.res index b9950562e..b7d81e48d 100644 --- a/src/components/TextInput.res +++ b/src/components/TextInput.res @@ -1,9 +1,18 @@ include TextInputElement + +type selection = { + start: int, + end: int, +} + module ChangeEvent = { type payload = { eventCount: int, target: int, text: string, + // This is usually only used as a callback param and not created by the user. + // Therefore prefer option<...> to an optional field for easier matching. + selection: option, } include Event.SyntheticEvent({type _payload = payload}) @@ -42,11 +51,6 @@ module ScrollEvent = { type scrollEvent = ScrollEvent.t -type selection = { - start: int, - end: int, -} - module SelectionChangeEvent = { type payload = { selection: selection, From 8770a67a87376e9a3f82998c31b28cab4ce9ef15 Mon Sep 17 00:00:00 2001 From: Freddy Harris Date: Sun, 25 Oct 2026 12:38:01 +0100 Subject: [PATCH 02/11] Animated: fix Color callbacks and StyleProp.color, unify Color creation, optional parallel config - Color listener/stopAnimation/resetAnimation callbacks receive a color string - fix StyleProp.color returning Animated.Color.t instead of Color.t - add StyleProp.animatedColor - unify Color.create(~initialValue=?, ~config=?) with Color.input, Color.setValue takes input - add ~config to Value.create and ValueXY.create, add ValueXY.createFromValues - add Timing/Spring fromInterpolation - add hasListeners, attachNativeEvent, forkEvent, unforkEvent - parallel takes an optional ~config Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 13 ++++- src/apis/Animated.res | 107 +++++++++++++++++++++++++-------------- src/apis/Animated.res.js | 6 +-- 3 files changed, 84 insertions(+), 42 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index da6be309a..924c1c96d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,11 +4,22 @@ - Upgrade to `react-native@0.85` - Breaking: remove `StyleSheet.absoluteFillObject` (removed upstream, use `StyleSheet.absoluteFill`) - - Add `Animated.useAnimatedValueXY` and `Animated.useAnimatedColor` (with `Animated.Color.initialValue`: `Rgba` / `String`) + - Add `Animated.useAnimatedValueXY` and `Animated.useAnimatedColor` - Add `EventEmitter` module - Add `selection` to `TextInput.ChangeEvent` payload - Add `onPressMove` prop to `Pressable` - Add `scrollsChildToFocus` Android prop to `ScrollView` +- `Animated` improvements + - Breaking: `Animated.Color.create(~initialValue=?, ~config=?)` takes `Animated.Color.input` (`Rgba({r, g, b, a})` / `String("red")`), replaces `create(rawValue)` / `createFromString`; `rgbaValue` removed, `rawValue` is now the `Animated.Color.rgba` record + - Breaking: `Animated.Color.setValue` takes `Animated.Color.input` + - Breaking: `Animated.Color` listener / `stopAnimation` / `resetAnimation` callbacks receive a color string (was wrongly typed as an rgba object) + - Breaking: `Animated.parallel(anims, ~config=?)`, `stopTogether` is optional (`parallelPayload` renamed `parallelConfig`) + - Fix `Animated.StyleProp.color` returning `Animated.Color.t` instead of `Color.t` + - Add `Animated.StyleProp.animatedColor` to use an `Animated.Color` in styles + - Add optional `~config` to `Animated.Value.create` and `Animated.ValueXY.create` + - Add `Animated.ValueXY.createFromValues` (from existing `Animated.Value`s) + - Add `Timing.fromInterpolation` / `Spring.fromInterpolation` for `toValue` + - Add `hasListeners`, `Animated.attachNativeEvent`, `Animated.forkEvent`, `Animated.unforkEvent` - Bump dev dependency to `rescript@12.3.1` ## 0.84.0 - 2026-09-15 diff --git a/src/apis/Animated.res b/src/apis/Animated.res index 36ce11b2a..efbbae840 100644 --- a/src/apis/Animated.res +++ b/src/apis/Animated.res @@ -1,3 +1,8 @@ +// alias of the global `Color.t` (a string), shadowed below by `Animated.Color` +type colorValue = Color.t + +type config = {useNativeDriver: bool} + module Animation = { type t type endResult = {finished: bool} @@ -13,6 +18,7 @@ module type Value = { type t type rawValue type addListenerCallback + type callback } type calculated @@ -21,6 +27,26 @@ type regular type value<'a> +module Interpolation = { + type t = value + type outputRange + external fromStringArray: array => outputRange = "%identity" + external fromFloatArray: array => outputRange = "%identity" + + type extrapolate = [#extend | #clamp | #identity] + + type config = { + inputRange: array, + outputRange: outputRange, + easing?: Easing.t, + extrapolate?: extrapolate, + extrapolateLeft?: extrapolate, + extrapolateRight?: extrapolate, + } + + @send external interpolate: (value<'a>, config) => t = "interpolate" +} + module ValueAnimations = (Val: Value) => { module Decay = { type config = { @@ -40,6 +66,7 @@ module ValueAnimations = (Val: Value) => { type toValue external fromRawValue: Val.rawValue => toValue = "%identity" external fromAnimatedValue: Val.t => toValue = "%identity" + external fromInterpolation: Interpolation.t => toValue = "%identity" type config = { toValue: toValue, @@ -70,6 +97,7 @@ module ValueAnimations = (Val: Value) => { external fromRawValue: Val.rawValue => toValue = "%identity" external fromAnimatedValue: Val.t => toValue = "%identity" + external fromInterpolation: Interpolation.t => toValue = "%identity" type config = { toValue: toValue, @@ -87,26 +115,6 @@ module ValueAnimations = (Val: Value) => { } } -module Interpolation = { - type t = value - type outputRange - external fromStringArray: array => outputRange = "%identity" - external fromFloatArray: array => outputRange = "%identity" - - type extrapolate = [#extend | #clamp | #identity] - - type config = { - inputRange: array, - outputRange: outputRange, - easing?: Easing.t, - extrapolate?: extrapolate, - extrapolateLeft?: extrapolate, - extrapolateRight?: extrapolate, - } - - @send external interpolate: (value<'a>, config) => t = "interpolate" -} - module ValueOperations = { @module("react-native") @scope("Animated") external add: (value<'a>, value<'b>) => value = "add" @@ -127,7 +135,7 @@ module ValueMethods = (Val: Value) => { type t = Val.t type rawValue = Val.rawValue type addListenerCallback = Val.addListenerCallback - type callback = rawValue => unit + type callback = Val.callback @send external setValue: (t, rawValue) => unit = "setValue" @send external setOffset: (t, rawValue) => unit = "setOffset" @@ -137,6 +145,7 @@ module ValueMethods = (Val: Value) => { external addListener: (t, addListenerCallback) => string = "addListener" @send external removeListener: (t, string) => unit = "removeListener" @send external removeAllListeners: t => unit = "removeAllListeners" + @send external hasListeners: t => bool = "hasListeners" @send external resetAnimation: (t, ~callback: callback=?) => unit = "resetAnimation" @@ -152,10 +161,11 @@ module Value = { type t = value type rawValue = float type addListenerCallback = {"value": rawValue} => unit + type callback = rawValue => unit }) @new @scope("Animated") @module("react-native") - external create: float => t = "Value" + external create: (float, ~config: config=?) => t = "Value" include ValueOperations } @@ -165,6 +175,7 @@ module ValueXY = { type t = {"x": Value.t, "y": Value.t} type rawValue = {"x": float, "y": float} type addListenerCallback = rawValue => unit + type callback = rawValue => unit }) type layout = { @@ -172,29 +183,33 @@ module ValueXY = { top: Value.t, } @new @scope("Animated") @module("react-native") - external create: rawValue => t = "ValueXY" + external create: (rawValue, ~config: config=?) => t = "ValueXY" + // reuse existing `Animated.Value`s for x and y + @new @scope("Animated") @module("react-native") + external createFromValues: (t, ~config: config=?) => t = "ValueXY" @send external getLayout: t => layout = "getLayout" @send external getTranslateTransform: t => array = "getTranslateTransform" } module Color = { + type rgba = {r: float, g: float, b: float, a: float} + include ValueMethods({ type t = {"r": Value.t, "g": Value.t, "b": Value.t, "a": Value.t} - type rawValue = {"r": float, "g": float, "b": float, "a": float} - type addListenerCallback = rawValue => unit + type rawValue = rgba + // listeners receive the current color as a string, e.g. "rgba(255, 0, 0, 1)" + type addListenerCallback = colorValue => unit + type callback = colorValue => unit }) - @obj external rgbaValue: (~r: float, ~g: float, ~b: float, ~a: float) => rawValue = "" + @unboxed type input = Rgba(rgba) | String(colorValue) @new @scope("Animated") @module("react-native") - external create: rawValue => t = "Color" + external create: (~initialValue: input=?, ~config: config=?) => t = "Color" - @new @scope("Animated") @module("react-native") - external createFromString: Color.t => t = "Color" - - type rgba = {r: float, g: float, b: float, a: float} - @unboxed type initialValue = Rgba(rgba) | String(Color.t) + // shadows `ValueMethods.setValue` to also accept a color string + @send external setValue: (t, input) => unit = "setValue" } @module("react-native") @scope("Animated") @@ -203,9 +218,9 @@ external delay: float => Animation.t = "delay" @module("react-native") @scope("Animated") external sequence: array => Animation.t = "sequence" -type parallelPayload = {stopTogether: bool} +type parallelConfig = {stopTogether?: bool} @module("react-native") @scope("Animated") -external parallel: (array, parallelPayload) => Animation.t = "parallel" +external parallel: (array, ~config: parallelConfig=?) => Animation.t = "parallel" @module("react-native") @scope("Animated") external stagger: (float, array) => Animation.t = "stagger" @@ -230,6 +245,23 @@ external event1: (array<'mapping>, ~config: eventConfig<'a, 'platformConfig>=?) external event2: (('mapping1, 'mapping2), ~config: eventConfig<'a, 'platformConfig>=?) => 'a = "event" +type attachedEvent = {detach: unit => unit} + +@module("react-native") @scope("Animated") +external attachNativeEvent: ( + 'viewRef, + string, + array<'mapping>, + ~platformConfig: 'platformConfig=?, +) => attachedEvent = "attachNativeEvent" + +// adds a JS listener to an event handler (e.g. one returned by `event1`) +@module("react-native") @scope("Animated") +external forkEvent: (option<'handler>, 'handler) => 'handler = "forkEvent" + +@module("react-native") @scope("Animated") +external unforkEvent: (option<'handler>, 'handler) => unit = "unforkEvent" + @module("react-native") @scope("Animated") external createAnimatedComponent: React.component<'props> => React.component<'props> = "createAnimatedComponent" @@ -259,7 +291,8 @@ module StyleProp = { external angle: value => Style.angle = "%identity" external size: value<'a> => Style.size = "%identity" external margin: value<'a> => Style.margin = "%identity" - external color: value => Color.t = "%identity" + external color: value => colorValue = "%identity" + external animatedColor: Color.t => colorValue = "%identity" // UNSAFE: allows to pass animated values everywhere, // use of more precise methods above is recommended @@ -298,8 +331,6 @@ module View = { external make: View.props => React.element = "View" } -type config = {useNativeDriver: bool} - @module("react-native") external useAnimatedValue: (~initialValue: float, ~config: config=?) => Value.t = "useAnimatedValue" @@ -308,5 +339,5 @@ external useAnimatedValueXY: (~initialValue: ValueXY.rawValue, ~config: config=? "useAnimatedValueXY" @module("react-native") -external useAnimatedColor: (~initialValue: Color.initialValue=?, ~config: config=?) => Color.t = +external useAnimatedColor: (~initialValue: Color.input=?, ~config: config=?) => Color.t = "useAnimatedColor" diff --git a/src/apis/Animated.res.js b/src/apis/Animated.res.js index b28e12ea1..4a74019d3 100644 --- a/src/apis/Animated.res.js +++ b/src/apis/Animated.res.js @@ -5,6 +5,8 @@ import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js let Animation = {}; +let Interpolation = {}; + function ValueAnimations(Val) { let Decay = {}; let Spring = {}; @@ -16,8 +18,6 @@ function ValueAnimations(Val) { }; } -let Interpolation = {}; - function interpolate(prim0, prim1) { return prim0.interpolate(prim1); } @@ -114,8 +114,8 @@ let View = {}; export { Animation, - ValueAnimations, Interpolation, + ValueAnimations, ValueOperations, ValueMethods, Value, From 4b47899dcd57099e4e38b0a6dde3fdc99d54d9ea Mon Sep 17 00:00:00 2001 From: Freddy Harris Date: Sun, 25 Oct 2026 13:45:05 +0100 Subject: [PATCH 03/11] fix Color.hsla generating an invalid hsl() color hsla() produced `hsl(h, s%, l%, a)`, which React Native's color parser rejects. Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 1 + src/apis/Color.res | 2 +- src/apis/Color.res.js | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 924c1c96d..b0cbfde0d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ - Add `Animated.ValueXY.createFromValues` (from existing `Animated.Value`s) - Add `Timing.fromInterpolation` / `Spring.fromInterpolation` for `toValue` - Add `hasListeners`, `Animated.attachNativeEvent`, `Animated.forkEvent`, `Animated.unforkEvent` +- Fix `Color.hsla` generating an invalid `hsl(h, s%, l%, a)` color instead of `hsla(...)` - Bump dev dependency to `rescript@12.3.1` ## 0.84.0 - 2026-09-15 diff --git a/src/apis/Color.res b/src/apis/Color.res index 007af7329..7e4f3fc27 100644 --- a/src/apis/Color.res +++ b/src/apis/Color.res @@ -11,7 +11,7 @@ let rgba = (~r: int, ~g: int, ~b: int, ~a: float) => let hsl = (~h: float, ~s: float, ~l: float) => `hsl(${h->Float.toString}, ${s->Float.toString}%, ${l->Float.toString}%)` let hsla = (~h: float, ~s: float, ~l: float, ~a: float) => - `hsl(${h->Float.toString}, ${s->Float.toString}%, ${l->Float.toString}%, ${a->Float.toString})` + `hsla(${h->Float.toString}, ${s->Float.toString}%, ${l->Float.toString}%, ${a->Float.toString})` @inline let transparent = "transparent" diff --git a/src/apis/Color.res.js b/src/apis/Color.res.js index bacddb17a..10997139a 100644 --- a/src/apis/Color.res.js +++ b/src/apis/Color.res.js @@ -14,7 +14,7 @@ function hsl(h, s, l) { } function hsla(h, s, l, a) { - return `hsl(` + h.toString() + `, ` + s.toString() + `%, ` + l.toString() + `%, ` + a.toString() + `)`; + return `hsla(` + h.toString() + `, ` + s.toString() + `%, ` + l.toString() + `%, ` + a.toString() + `)`; } export { From 86a2e04ee69e6b0b4f0f3b61cc645f2f4327c079 Mon Sep 17 00:00:00 2001 From: Freddy Harris Date: Sun, 25 Oct 2026 13:45:05 +0100 Subject: [PATCH 04/11] Animated: use externals instead of let aliases to avoid JS wrapper functions timing, spring, decay, start, stop, reset and Value.interpolate were `let` aliases of externals, compiled to wrapper functions. Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 1 + src/apis/Animated.res | 21 ++++++++++++------- src/apis/Animated.res.js | 45 +++------------------------------------- 3 files changed, 18 insertions(+), 49 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b0cbfde0d..c04e82247 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ - Add `Animated.ValueXY.createFromValues` (from existing `Animated.Value`s) - Add `Timing.fromInterpolation` / `Spring.fromInterpolation` for `toValue` - Add `hasListeners`, `Animated.attachNativeEvent`, `Animated.forkEvent`, `Animated.unforkEvent` + - Zero-cost `Animated.timing` / `spring` / `decay` / `start` / `stop` / `reset` and `Value.interpolate` (were compiled to JS wrapper functions) - Fix `Color.hsla` generating an invalid `hsl(h, s%, l%, a)` color instead of `hsla(...)` - Bump dev dependency to `rescript@12.3.1` diff --git a/src/apis/Animated.res b/src/apis/Animated.res index efbbae840..1e5800400 100644 --- a/src/apis/Animated.res +++ b/src/apis/Animated.res @@ -128,7 +128,8 @@ module ValueOperations = { external subtract: (value<'a>, value<'b>) => value = "subtract" @module("react-native") @scope("Animated") external diffClamp: (value<'a>, float, float) => value = "diffClamp" - let interpolate = Interpolation.interpolate + // externals rather than `let` aliases, which would compile to JS wrapper functions + @send external interpolate: (value<'a>, Interpolation.config) => Interpolation.t = "interpolate" } module ValueMethods = (Val: Value) => { @@ -266,17 +267,23 @@ external unforkEvent: (option<'handler>, 'handler) => unit = "unforkEvent" external createAnimatedComponent: React.component<'props> => React.component<'props> = "createAnimatedComponent" -let timing = Value.Timing.timing +// shortcuts for `Value`, as externals rather than `let` aliases, +// which would compile to JS wrapper functions +@module("react-native") @scope("Animated") +external timing: (Value.t, Value.Timing.config) => Animation.t = "timing" -let spring = Value.Spring.spring +@module("react-native") @scope("Animated") +external spring: (Value.t, Value.Spring.config) => Animation.t = "spring" -let decay = Value.Decay.decay +@module("react-native") @scope("Animated") +external decay: (Value.t, Value.Decay.config) => Animation.t = "decay" -let start = Animation.start +@send +external start: (Animation.t, ~endCallback: Animation.endCallback=?) => unit = "start" -let stop = Animation.stop +@send external stop: Animation.t => unit = "stop" -let reset = Animation.reset +@send external reset: Animation.t => unit = "reset" module StyleProp = { // methods to allow use of Animated values with Style props diff --git a/src/apis/Animated.res.js b/src/apis/Animated.res.js index 4a74019d3..e55dcce56 100644 --- a/src/apis/Animated.res.js +++ b/src/apis/Animated.res.js @@ -1,7 +1,5 @@ -import * as ReactNative from "react-native"; -import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js"; let Animation = {}; @@ -18,13 +16,7 @@ function ValueAnimations(Val) { }; } -function interpolate(prim0, prim1) { - return prim0.interpolate(prim1); -} - -let ValueOperations = { - interpolate: interpolate -}; +let ValueOperations = {}; function ValueMethods(Val) { let Decay = {}; @@ -46,8 +38,7 @@ let Timing = {}; let Value = { Decay: Decay, Spring: Spring, - Timing: Timing, - interpolate: interpolate + Timing: Timing }; let Decay$1 = {}; @@ -74,30 +65,6 @@ let Color = { Timing: Timing$2 }; -function timing(prim0, prim1) { - return ReactNative.Animated.timing(prim0, prim1); -} - -function spring(prim0, prim1) { - return ReactNative.Animated.spring(prim0, prim1); -} - -function decay(prim0, prim1) { - return ReactNative.Animated.decay(prim0, prim1); -} - -function start(prim0, prim1) { - prim0.start(prim1 !== undefined ? Primitive_option.valFromOption(prim1) : undefined); -} - -function stop(prim) { - prim.stop(); -} - -function reset(prim) { - prim.reset(); -} - let StyleProp = {}; let FlatList = {}; @@ -121,12 +88,6 @@ export { Value, ValueXY, Color, - timing, - spring, - decay, - start, - stop, - reset, StyleProp, FlatList, Image, @@ -135,4 +96,4 @@ export { Text, View, } -/* react-native Not a pure module */ +/* No side effect */ From 2ede575fed72474cf34405b0e435c2c3bbe16174 Mon Sep 17 00:00:00 2001 From: Freddy Harris Date: Sun, 25 Oct 2026 13:53:12 +0100 Subject: [PATCH 05/11] add I18nManager, DeviceEventEmitter and ReactNativeVersion bindings Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 1 + src/apis/DeviceEventEmitter.res | 12 ++++++++++++ src/apis/DeviceEventEmitter.res.js | 1 + src/apis/I18nManager.res | 25 +++++++++++++++++++++++++ src/apis/I18nManager.res.js | 1 + src/apis/ReactNativeVersion.res | 14 ++++++++++++++ src/apis/ReactNativeVersion.res.js | 1 + 7 files changed, 55 insertions(+) create mode 100644 src/apis/DeviceEventEmitter.res create mode 100644 src/apis/DeviceEventEmitter.res.js create mode 100644 src/apis/I18nManager.res create mode 100644 src/apis/I18nManager.res.js create mode 100644 src/apis/ReactNativeVersion.res create mode 100644 src/apis/ReactNativeVersion.res.js diff --git a/CHANGELOG.md b/CHANGELOG.md index c04e82247..0386115e0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ - Add `hasListeners`, `Animated.attachNativeEvent`, `Animated.forkEvent`, `Animated.unforkEvent` - Zero-cost `Animated.timing` / `spring` / `decay` / `start` / `stop` / `reset` and `Value.interpolate` (were compiled to JS wrapper functions) - Fix `Color.hsla` generating an invalid `hsl(h, s%, l%, a)` color instead of `hsla(...)` +- New APIs: `I18nManager`, `DeviceEventEmitter`, `ReactNativeVersion` - Bump dev dependency to `rescript@12.3.1` ## 0.84.0 - 2026-09-15 diff --git a/src/apis/DeviceEventEmitter.res b/src/apis/DeviceEventEmitter.res new file mode 100644 index 000000000..fe3ca62cd --- /dev/null +++ b/src/apis/DeviceEventEmitter.res @@ -0,0 +1,12 @@ +@module("react-native") @scope("DeviceEventEmitter") +external addListener: (string, 'a => unit, ~context: 'context=?) => EventSubscription.t = + "addListener" + +@module("react-native") @scope("DeviceEventEmitter") +external emit: (string, 'a) => unit = "emit" + +@module("react-native") @scope("DeviceEventEmitter") +external removeAllListeners: (~eventType: string=?) => unit = "removeAllListeners" + +@module("react-native") @scope("DeviceEventEmitter") +external listenerCount: string => int = "listenerCount" diff --git a/src/apis/DeviceEventEmitter.res.js b/src/apis/DeviceEventEmitter.res.js new file mode 100644 index 000000000..ae1b9f17e --- /dev/null +++ b/src/apis/DeviceEventEmitter.res.js @@ -0,0 +1 @@ +/* This output is empty. Its source's type definitions, externals and/or unused code got optimized away. */ diff --git a/src/apis/I18nManager.res b/src/apis/I18nManager.res new file mode 100644 index 000000000..d16935bfa --- /dev/null +++ b/src/apis/I18nManager.res @@ -0,0 +1,25 @@ +type constants = { + isRTL: bool, + doLeftAndRightSwapInRTL: bool, + // This is usually only read and not created by the user. + // Therefore prefer option<...> to an optional field for easier matching. + localeIdentifier: option, +} + +@module("react-native") @scope("I18nManager") +external isRTL: bool = "isRTL" + +@module("react-native") @scope("I18nManager") +external doLeftAndRightSwapInRTL: bool = "doLeftAndRightSwapInRTL" + +@module("react-native") @scope("I18nManager") +external getConstants: unit => constants = "getConstants" + +@module("react-native") @scope("I18nManager") +external allowRTL: bool => unit = "allowRTL" + +@module("react-native") @scope("I18nManager") +external forceRTL: bool => unit = "forceRTL" + +@module("react-native") @scope("I18nManager") +external swapLeftAndRightInRTL: bool => unit = "swapLeftAndRightInRTL" diff --git a/src/apis/I18nManager.res.js b/src/apis/I18nManager.res.js new file mode 100644 index 000000000..ae1b9f17e --- /dev/null +++ b/src/apis/I18nManager.res.js @@ -0,0 +1 @@ +/* This output is empty. Its source's type definitions, externals and/or unused code got optimized away. */ diff --git a/src/apis/ReactNativeVersion.res b/src/apis/ReactNativeVersion.res new file mode 100644 index 000000000..de585cd77 --- /dev/null +++ b/src/apis/ReactNativeVersion.res @@ -0,0 +1,14 @@ +@module("react-native") @scope("ReactNativeVersion") +external major: int = "major" + +@module("react-native") @scope("ReactNativeVersion") +external minor: int = "minor" + +@module("react-native") @scope("ReactNativeVersion") +external patch: int = "patch" + +@module("react-native") @scope("ReactNativeVersion") +external prerelease: Null.t = "prerelease" + +@module("react-native") @scope("ReactNativeVersion") +external getVersionString: unit => string = "getVersionString" diff --git a/src/apis/ReactNativeVersion.res.js b/src/apis/ReactNativeVersion.res.js new file mode 100644 index 000000000..ae1b9f17e --- /dev/null +++ b/src/apis/ReactNativeVersion.res.js @@ -0,0 +1 @@ +/* This output is empty. Its source's type definitions, externals and/or unused code got optimized away. */ From 2cecab95eaf1852ed58471b3881fd5efcebb5550 Mon Sep 17 00:00:00 2001 From: Freddy Harris Date: Sun, 25 Oct 2026 13:53:27 +0100 Subject: [PATCH 06/11] add missing View, Pressable, TouchableWithoutFeedback, TextInput and Image props, fix StickyHeaderComponent type - View: touch events, onFocusCapture/onBlurCapture, nextFocus* (Android), experimental_accessibilityOrder - Pressable: cancelable - TouchableWithoutFeedback: rejectResponderTermination - TextInput: inputAccessoryViewButtonLabel - Image: src - ScrollView: StickyHeaderComponent is a component, not an element (+ Animated.Value.fromStickyHeaderScrollValue) Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 4 +++ src/apis/Animated.res | 4 +++ src/components/Image.res | 1 + src/components/Pressable.res | 1 + src/components/ScrollView.res | 18 +++++++++++++- src/components/TextInput.res | 1 + src/components/TouchableWithoutFeedback.res | 1 + src/components/View.res | 27 +++++++++++++++++++-- 8 files changed, 54 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0386115e0..4a70a36ef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,10 @@ - Zero-cost `Animated.timing` / `spring` / `decay` / `start` / `stop` / `reset` and `Value.interpolate` (were compiled to JS wrapper functions) - Fix `Color.hsla` generating an invalid `hsl(h, s%, l%, a)` color instead of `hsla(...)` - New APIs: `I18nManager`, `DeviceEventEmitter`, `ReactNativeVersion` +- New props + - `View`: touch events (`onTouchStart`, `onTouchMove`, `onTouchEnd`, `onTouchCancel` and their `Capture` variants), `onFocusCapture`, `onBlurCapture`, `nextFocus*` (Android), `experimental_accessibilityOrder` + - `Pressable`: `cancelable`; `TouchableWithoutFeedback`: `rejectResponderTermination`; `TextInput`: `inputAccessoryViewButtonLabel`; `Image`: `src` +- Breaking: `ScrollView` `StickyHeaderComponent` is a `React.component` (was wrongly typed as `React.element`), add `Animated.Value.fromStickyHeaderScrollValue` - Bump dev dependency to `rescript@12.3.1` ## 0.84.0 - 2026-09-15 diff --git a/src/apis/Animated.res b/src/apis/Animated.res index 1e5800400..c2aef8083 100644 --- a/src/apis/Animated.res +++ b/src/apis/Animated.res @@ -168,6 +168,10 @@ module Value = { @new @scope("Animated") @module("react-native") external create: (float, ~config: config=?) => t = "Value" + // the scroll value received by a `ScrollView` `StickyHeaderComponent` + external fromStickyHeaderScrollValue: ScrollView.stickyHeaderScrollAnimatedValue => t = + "%identity" + include ValueOperations } diff --git a/src/components/Image.res b/src/components/Image.res index 3f2e02f0c..582daf5bd 100644 --- a/src/components/Image.res +++ b/src/components/Image.res @@ -109,6 +109,7 @@ type imageProps = { referrerPolicy?: referrerPolicy, resizeMode?: Style.resizeMode, source: Source.t, + src?: string, srcSet?: string, tintColor?: Color.t, width?: float, diff --git a/src/components/Pressable.res b/src/components/Pressable.res index 8d6e40d87..737de5ed9 100644 --- a/src/components/Pressable.res +++ b/src/components/Pressable.res @@ -36,6 +36,7 @@ type props = { ref?: ref, android_disableSound?: bool, android_ripple?: rippleConfig, + cancelable?: bool, children?: interactionState => React.element, delayLongPress?: int, disabled?: bool, diff --git a/src/components/ScrollView.res b/src/components/ScrollView.res index 186426ca5..15f185515 100644 --- a/src/components/ScrollView.res +++ b/src/components/ScrollView.res @@ -25,6 +25,22 @@ type overScrollMode = [#always | #never | #auto] type snapToAlignment = [#start | #center | #end] +// an `Animated.Value`, see `Animated.Value.fromStickyHeaderScrollValue` +type stickyHeaderScrollAnimatedValue + +// These are only received by the component and not created by the user. +// Therefore prefer option<...> to optional fields for easier matching. +type stickyHeaderProps = { + children: option, + nextHeaderLayoutY: option, + onLayout: Event.layoutEvent => unit, + scrollAnimatedValue: stickyHeaderScrollAnimatedValue, + inverted: option, + scrollViewHeight: option, + nativeID: option, + hiddenOnScroll: option, +} + type maintainVisibleContentPosition = { autoscrollToTopThreshold?: float, minIndexForVisible: int, @@ -97,7 +113,7 @@ type scrollViewPropsWithoutListConflict = { stickyHeaderIndices?: array, disableIntervalMomentum?: bool, disableScrollViewPanResponder?: bool, - \"StickyHeaderComponent"?: React.element, + \"StickyHeaderComponent"?: React.component, } type scrollViewProps = { diff --git a/src/components/TextInput.res b/src/components/TextInput.res index b7d81e48d..f841dca80 100644 --- a/src/components/TextInput.res +++ b/src/components/TextInput.res @@ -286,6 +286,7 @@ type props = { importantForAutofill?: importantForAutofill, inlineImageLeft?: string, inlineImagePadding?: float, + inputAccessoryViewButtonLabel?: string, inputAccessoryViewID?: string, // `inputMode` has precedence over `keyboardType` inputMode?: inputMode, diff --git a/src/components/TouchableWithoutFeedback.res b/src/components/TouchableWithoutFeedback.res index dad06e278..d6929fc45 100644 --- a/src/components/TouchableWithoutFeedback.res +++ b/src/components/TouchableWithoutFeedback.res @@ -17,6 +17,7 @@ type coreProps = { onPressIn?: Event.pressEvent => unit, onPressOut?: Event.pressEvent => unit, pressRetentionOffset?: Rect.t, + rejectResponderTermination?: bool, testID?: string, touchSoundDisabled?: bool, children?: React.element, diff --git a/src/components/View.res b/src/components/View.res index 2e2f3d8dd..6df113a61 100644 --- a/src/components/View.res +++ b/src/components/View.res @@ -29,6 +29,17 @@ type gestureResponderHandlersProps = { onStartShouldSetResponderCapture?: Event.pressEvent => bool, } +type touchEventProps = { + onTouchCancel?: Event.pressEvent => unit, + onTouchCancelCapture?: Event.pressEvent => unit, + onTouchEnd?: Event.pressEvent => unit, + onTouchEndCapture?: Event.pressEvent => unit, + onTouchMove?: Event.pressEvent => unit, + onTouchMoveCapture?: Event.pressEvent => unit, + onTouchStart?: Event.pressEvent => unit, + onTouchStartCapture?: Event.pressEvent => unit, +} + // W3C pointer handlers (excludes onClick — covered by webClickProps). type pointerEventProps = { onGotPointerCapture?: Event.pointerEvent => unit, @@ -55,8 +66,11 @@ type pointerEventProps = { type accessibilityProps = Accessibility.viewProps - -type iosProps = {shouldRasterizeIOS?: bool} +type iosProps = { + shouldRasterizeIOS?: bool, + // nativeID values of descendants, in accessibility focus order + experimental_accessibilityOrder?: array, +} @unboxed type tabIndex = | @as(0) Focusable | @as(-1) NotFocusable @@ -65,6 +79,12 @@ type androidProps = { renderToHardwareTextureAndroid?: bool, focusable?: bool, tabIndex?: tabIndex, + // native view tags (see `NativeMethods`) of the next view to focus + nextFocusDown?: int, + nextFocusForward?: int, + nextFocusLeft?: int, + nextFocusRight?: int, + nextFocusUp?: int, } type webLinkProps = { @@ -80,7 +100,9 @@ type webClickProps = { type webFocusProps = { onFocus?: ReactEvent.Focus.t => unit, + onFocusCapture?: ReactEvent.Focus.t => unit, onBlur?: ReactEvent.Focus.t => unit, + onBlurCapture?: ReactEvent.Focus.t => unit, } type webKeyboardProps = { @@ -129,6 +151,7 @@ type coreProps = { type viewPropsWithoutChildren = { ...gestureResponderHandlersProps, + ...touchEventProps, ...pointerEventProps, ...accessibilityProps, ...iosProps, From 2ca1bc7b15ac3a839dac02964c010d36749eca0b Mon Sep 17 00:00:00 2001 From: Freddy Harris Date: Sun, 25 Oct 2026 13:53:34 +0100 Subject: [PATCH 07/11] add Style.matrix, type VirtualizedList cellRendererComponentProps, deprecate InteractionManager - InteractionManager is deprecated by React Native; runAfterInteractions returns a cancellable task Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 3 +++ src/apis/InteractionManager.res | 22 ++++++++++++++++------ src/apis/Style.res | 2 +- src/apis/Style.resi | 2 +- src/components/VirtualizedList.res | 13 +++++++++++-- 5 files changed, 32 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4a70a36ef..8e1250ff6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,9 @@ - `View`: touch events (`onTouchStart`, `onTouchMove`, `onTouchEnd`, `onTouchCancel` and their `Capture` variants), `onFocusCapture`, `onBlurCapture`, `nextFocus*` (Android), `experimental_accessibilityOrder` - `Pressable`: `cancelable`; `TouchableWithoutFeedback`: `rejectResponderTermination`; `TextInput`: `inputAccessoryViewButtonLabel`; `Image`: `src` - Breaking: `ScrollView` `StickyHeaderComponent` is a `React.component` (was wrongly typed as `React.element`), add `Animated.Value.fromStickyHeaderScrollValue` +- Add `Style.matrix` transform +- Breaking: `VirtualizedList.cellRendererComponentProps` is now typed (`cellKey`, `children`, `index`, `item`, `onLayout`, …) +- Deprecate `InteractionManager` (deprecated by React Native), `runAfterInteractions` returns a cancellable `task` - Bump dev dependency to `rescript@12.3.1` ## 0.84.0 - 2026-09-15 diff --git a/src/apis/InteractionManager.res b/src/apis/InteractionManager.res index 68e95d410..35f2cf4de 100644 --- a/src/apis/InteractionManager.res +++ b/src/apis/InteractionManager.res @@ -4,15 +4,25 @@ type interactionHandle -@module("react-native") @scope("InteractionManager") -external /* @todo this returns a cancellable "promise", we need to type return correctly */ -runAfterInteractions: (unit => unit) => {.} = "runAfterInteractions" +// cancellable "promise" returned by `runAfterInteractions` +type task = {cancel: unit => unit} -@module("react-native") @scope("InteractionManager") +@deprecated("InteractionManager is deprecated by React Native.") +@module("react-native") +@scope("InteractionManager") +external runAfterInteractions: (unit => unit) => task = "runAfterInteractions" + +@deprecated("InteractionManager is deprecated by React Native.") +@module("react-native") +@scope("InteractionManager") external createInteractionHandle: unit => interactionHandle = "createInteractionHandle" -@module("react-native") @scope("InteractionManager") +@deprecated("InteractionManager is deprecated by React Native.") +@module("react-native") +@scope("InteractionManager") external clearInteractionHandle: interactionHandle => unit = "clearInteractionHandle" -@module("react-native") @scope("InteractionManager") +@deprecated("InteractionManager is deprecated by React Native.") +@module("react-native") +@scope("InteractionManager") external setDeadline: float => unit = "setDeadline" diff --git a/src/apis/Style.res b/src/apis/Style.res index 3d4ad22a6..1e980daba 100644 --- a/src/apis/Style.res +++ b/src/apis/Style.res @@ -41,7 +41,7 @@ type transform @obj external translateY: (~translateY: size) => transform = "" @obj external skewX: (~skewX: angle) => transform = "" @obj external skewY: (~skewY: angle) => transform = "" -// @todo matrix +@obj external matrix: (~matrix: array) => transform = "" external unsafeTransform: {..} => transform = "%identity" diff --git a/src/apis/Style.resi b/src/apis/Style.resi index 354db9afb..ed030b185 100644 --- a/src/apis/Style.resi +++ b/src/apis/Style.resi @@ -37,7 +37,7 @@ type transform @obj external translateY: (~translateY: size) => transform = "" @obj external skewX: (~skewX: angle) => transform = "" @obj external skewY: (~skewY: angle) => transform = "" -// @todo matrix +@obj external matrix: (~matrix: array) => transform = "" external unsafeTransform: {..} => transform = "%identity" diff --git a/src/components/VirtualizedList.res b/src/components/VirtualizedList.res index 913ecdbdf..30f7253bc 100644 --- a/src/components/VirtualizedList.res +++ b/src/components/VirtualizedList.res @@ -18,8 +18,17 @@ type renderItemProps<'item> = { type renderItemCallback<'item> = renderItemProps<'item> => React.element -// TODO -type cellRendererComponentProps<'item> = {.} +// These are only received by the component and not created by the user. +// Therefore prefer option<...> to optional fields for easier matching. +type cellRendererComponentProps<'item> = { + cellKey: string, + children: React.element, + index: int, + item: 'item, + onFocusCapture: option unit>, + onLayout: option unit>, + style: Style.t, +} type cellRendererComponent<'item> = React.component> type viewableItem<'item> = { From d6986a1fc88bc719996bbb23b1ce98cd0a9180c4 Mon Sep 17 00:00:00 2001 From: Freddy Harris Date: Sun, 25 Oct 2026 13:53:42 +0100 Subject: [PATCH 08/11] add aria-* accessibility props and all fontVariant values - aria-busy, aria-checked (true/false/mixed), aria-disabled, aria-expanded, aria-hidden, aria-label, aria-selected, aria-value*, aria-modal (iOS), aria-labelledby and aria-live (Android) - fontVariant: ligatures, contextual and stylistic-one..twenty Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 2 ++ src/apis/Style.res | 35 ++++++++++++++++++++++++++++------- src/apis/Style.resi | 28 ++++++++++++++++++++++++++++ src/components/View.res | 3 --- src/types/Accessibility.res | 17 +++++++++++++++++ src/types/Accessibility.resi | 17 +++++++++++++++++ 6 files changed, 92 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8e1250ff6..d1a3af391 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,10 +26,12 @@ - New props - `View`: touch events (`onTouchStart`, `onTouchMove`, `onTouchEnd`, `onTouchCancel` and their `Capture` variants), `onFocusCapture`, `onBlurCapture`, `nextFocus*` (Android), `experimental_accessibilityOrder` - `Pressable`: `cancelable`; `TouchableWithoutFeedback`: `rejectResponderTermination`; `TextInput`: `inputAccessoryViewButtonLabel`; `Image`: `src` + - `aria-*` accessibility props (`ariaLabel`, `ariaChecked`, `ariaHidden`, `ariaLive`, …) on all components using `Accessibility` props - Breaking: `ScrollView` `StickyHeaderComponent` is a `React.component` (was wrongly typed as `React.element`), add `Animated.Value.fromStickyHeaderScrollValue` - Add `Style.matrix` transform - Breaking: `VirtualizedList.cellRendererComponentProps` is now typed (`cellKey`, `children`, `index`, `item`, `onLayout`, …) - Deprecate `InteractionManager` (deprecated by React Native), `runAfterInteractions` returns a cancellable `task` +- Add all `fontVariant` values (ligatures, `contextual`, `stylistic-*`) - Bump dev dependency to `rescript@12.3.1` ## 0.84.0 - 2026-09-15 diff --git a/src/apis/Style.res b/src/apis/Style.res index 1e980daba..e2455c4e1 100644 --- a/src/apis/Style.res +++ b/src/apis/Style.res @@ -120,19 +120,40 @@ type fontStyle = [#normal | #italic] type fontWeight = [#normal | #bold | #100 | #200 | #300 | #400 | #500 | #600 | #700 | #800 | #900] -// @todo in 0.71.0 -// Apparently there are more `fontVariant` options IOS specific -// (`stylistic-one` ... `stylistic-twenty`) but they are not documented. -// Found in the 0.71.0 changelog: -// https://github.com/facebook/react-native/blob/main/CHANGELOG.md#ios-specific-4 -// and found in code: -// https://github.com/facebook/react-native/blob/4e117cb09d208ca02f513d2101592d0ac10d4739/Libraries/StyleSheet/StyleSheetTypes.js#L784 type fontVariant = [ | #"small-caps" | #"oldstyle-nums" | #"lining-nums" | #"tabular-nums" + | #"common-ligatures" + | #"no-common-ligatures" + | #"discretionary-ligatures" + | #"no-discretionary-ligatures" + | #"historical-ligatures" + | #"no-historical-ligatures" + | #contextual + | #"no-contextual" | #"proportional-nums" + | #"stylistic-one" + | #"stylistic-two" + | #"stylistic-three" + | #"stylistic-four" + | #"stylistic-five" + | #"stylistic-six" + | #"stylistic-seven" + | #"stylistic-eight" + | #"stylistic-nine" + | #"stylistic-ten" + | #"stylistic-eleven" + | #"stylistic-twelve" + | #"stylistic-thirteen" + | #"stylistic-fourteen" + | #"stylistic-fifteen" + | #"stylistic-sixteen" + | #"stylistic-seventeen" + | #"stylistic-eighteen" + | #"stylistic-nineteen" + | #"stylistic-twenty" ] type textAlign = [#auto | #left | #right | #center | #justify] diff --git a/src/apis/Style.resi b/src/apis/Style.resi index ed030b185..b171eed02 100644 --- a/src/apis/Style.resi +++ b/src/apis/Style.resi @@ -121,7 +121,35 @@ type fontVariant = [ | #"oldstyle-nums" | #"lining-nums" | #"tabular-nums" + | #"common-ligatures" + | #"no-common-ligatures" + | #"discretionary-ligatures" + | #"no-discretionary-ligatures" + | #"historical-ligatures" + | #"no-historical-ligatures" + | #contextual + | #"no-contextual" | #"proportional-nums" + | #"stylistic-one" + | #"stylistic-two" + | #"stylistic-three" + | #"stylistic-four" + | #"stylistic-five" + | #"stylistic-six" + | #"stylistic-seven" + | #"stylistic-eight" + | #"stylistic-nine" + | #"stylistic-ten" + | #"stylistic-eleven" + | #"stylistic-twelve" + | #"stylistic-thirteen" + | #"stylistic-fourteen" + | #"stylistic-fifteen" + | #"stylistic-sixteen" + | #"stylistic-seventeen" + | #"stylistic-eighteen" + | #"stylistic-nineteen" + | #"stylistic-twenty" ] type textAlign = [#auto | #left | #right | #center | #justify] diff --git a/src/components/View.res b/src/components/View.res index 6df113a61..5960921cf 100644 --- a/src/components/View.res +++ b/src/components/View.res @@ -2,9 +2,6 @@ type nativeElement include NativeElement.Impl({type t = nativeElement}) -// @todo in 0.71.0 -// after adding `aria-*` props, make sure `aria-checked` can be true, false or "mixed" - type importantForAccessibility = Accessibility.importantForAccessibility type pointerEvents = [ diff --git a/src/types/Accessibility.res b/src/types/Accessibility.res index e30b3218a..011e34b78 100644 --- a/src/types/Accessibility.res +++ b/src/types/Accessibility.res @@ -27,6 +27,8 @@ type value type liveRegion = [#none | #polite | #assertive] +type ariaLive = [#polite | #assertive | #off] + type role = [ | #adjustable | #alert @@ -87,6 +89,18 @@ type props = { accessibilityState?: state, accessibilityValue?: value, onAccessibilityAction?: actionEvent => unit, + // `aria-*` props have precedence over the matching `accessibility*` props + @as("aria-busy") ariaBusy?: bool, + @as("aria-checked") ariaChecked?: checked, + @as("aria-disabled") ariaDisabled?: bool, + @as("aria-expanded") ariaExpanded?: bool, + @as("aria-hidden") ariaHidden?: bool, + @as("aria-label") ariaLabel?: string, + @as("aria-selected") ariaSelected?: bool, + @as("aria-valuemax") ariaValuemax?: float, + @as("aria-valuemin") ariaValuemin?: float, + @as("aria-valuenow") ariaValuenow?: float, + @as("aria-valuetext") ariaValuetext?: string, } type iosProps = { @@ -94,6 +108,7 @@ type iosProps = { accessibilityIgnoresInvertColors?: bool, accessibilityLanguage?: string, accessibilityViewIsModal?: bool, + @as("aria-modal") ariaModal?: bool, onAccessibilityEscape?: unit => unit, onAccessibilityTap?: unit => unit, onMagicTap?: unit => unit, @@ -102,6 +117,8 @@ type iosProps = { type androidProps = { accessibilityLabelledBy?: array, accessibilityLiveRegion?: liveRegion, + @as("aria-labelledby") ariaLabelledby?: string, + @as("aria-live") ariaLive?: ariaLive, importantForAccessibility?: importantForAccessibility, } diff --git a/src/types/Accessibility.resi b/src/types/Accessibility.resi index ba25ed126..6bba32085 100644 --- a/src/types/Accessibility.resi +++ b/src/types/Accessibility.resi @@ -27,6 +27,8 @@ type value type liveRegion = [#none | #polite | #assertive] +type ariaLive = [#polite | #assertive | #off] + // `accessibilityRole` communicates the purpose of a component to the user of an assistive technology. // roles that are specific for react-native-web are also included: // article, banner, complementary, contentinfo, form, list, listItem, main, navigation, region @@ -90,6 +92,18 @@ type props = { accessibilityState?: state, accessibilityValue?: value, onAccessibilityAction?: actionEvent => unit, + // `aria-*` props have precedence over the matching `accessibility*` props + @as("aria-busy") ariaBusy?: bool, + @as("aria-checked") ariaChecked?: checked, + @as("aria-disabled") ariaDisabled?: bool, + @as("aria-expanded") ariaExpanded?: bool, + @as("aria-hidden") ariaHidden?: bool, + @as("aria-label") ariaLabel?: string, + @as("aria-selected") ariaSelected?: bool, + @as("aria-valuemax") ariaValuemax?: float, + @as("aria-valuemin") ariaValuemin?: float, + @as("aria-valuenow") ariaValuenow?: float, + @as("aria-valuetext") ariaValuetext?: string, } type iosProps = { @@ -97,6 +111,7 @@ type iosProps = { accessibilityIgnoresInvertColors?: bool, accessibilityLanguage?: string, accessibilityViewIsModal?: bool, + @as("aria-modal") ariaModal?: bool, onAccessibilityEscape?: unit => unit, onAccessibilityTap?: unit => unit, onMagicTap?: unit => unit, @@ -105,6 +120,8 @@ type iosProps = { type androidProps = { accessibilityLabelledBy?: array, accessibilityLiveRegion?: liveRegion, + @as("aria-labelledby") ariaLabelledby?: string, + @as("aria-live") ariaLive?: ariaLive, importantForAccessibility?: importantForAccessibility, } From 6333755e098bef7f4b315b524be0dbfeaf41adfd Mon Sep 17 00:00:00 2001 From: Freddy Harris Date: Sun, 25 Oct 2026 14:07:07 +0100 Subject: [PATCH 09/11] deprecate APIs and props deprecated or extracted by React Native 0.85 - ProgressBarAndroid, Clipboard (extracted from core) - StatusBar.setNetworkActivityIndicatorVisible - TextInput blurOnSubmit, hasTVPreferredFocus on Button/TouchableHighlight/ TouchableOpacity Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 2 ++ src/apis/Clipboard.res | 12 ++++++++++-- src/components/Button.res | 2 +- src/components/ProgressBarAndroid.res | 3 +++ src/components/StatusBar.res | 2 +- src/components/TextInput.res | 2 +- src/components/TouchableHighlight.res | 2 +- src/components/TouchableOpacity.res | 2 +- 8 files changed, 20 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d1a3af391..8c2e58906 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,6 +31,8 @@ - Add `Style.matrix` transform - Breaking: `VirtualizedList.cellRendererComponentProps` is now typed (`cellKey`, `children`, `index`, `item`, `onLayout`, …) - Deprecate `InteractionManager` (deprecated by React Native), `runAfterInteractions` returns a cancellable `task` +- Deprecate `ProgressBarAndroid` and `Clipboard` (extracted from react-native core), `StatusBar.setNetworkActivityIndicatorVisible` +- Mark as deprecated (documentation only, ReScript does not warn on record fields): `TextInput` `blurOnSubmit` (use `submitBehavior`), `hasTVPreferredFocus` on `Button` / `TouchableHighlight` / `TouchableOpacity` (use `focusable`) - Add all `fontVariant` values (ligatures, `contextual`, `stylistic-*`) - Bump dev dependency to `rescript@12.3.1` diff --git a/src/apis/Clipboard.res b/src/apis/Clipboard.res index b646099ab..0444e7ca0 100644 --- a/src/apis/Clipboard.res +++ b/src/apis/Clipboard.res @@ -1,5 +1,13 @@ -@module("react-native") @scope("Clipboard") +@deprecated( + "Clipboard has been extracted from react-native core and will be removed in a future release, use @react-native-clipboard/clipboard instead." +) +@module("react-native") +@scope("Clipboard") external getString: unit => promise = "getString" -@module("react-native") @scope("Clipboard") +@deprecated( + "Clipboard has been extracted from react-native core and will be removed in a future release, use @react-native-clipboard/clipboard instead." +) +@module("react-native") +@scope("Clipboard") external setString: string => unit = "setString" diff --git a/src/components/Button.res b/src/components/Button.res index dc56458cc..f82f125e7 100644 --- a/src/components/Button.res +++ b/src/components/Button.res @@ -11,7 +11,7 @@ type props = { accessible?: bool, color?: Color.t, disabled?: bool, - hasTVPreferredFocus?: bool, + @deprecated("Use `focusable` instead.") hasTVPreferredFocus?: bool, nextFocusDown?: int, nextFocusForward?: int, nextFocusLeft?: int, diff --git a/src/components/ProgressBarAndroid.res b/src/components/ProgressBarAndroid.res index f6aa3a1fd..f5c99766e 100644 --- a/src/components/ProgressBarAndroid.res +++ b/src/components/ProgressBarAndroid.res @@ -22,5 +22,8 @@ type props = { styleAttr?: styleAttr, } +@deprecated( + "ProgressBarAndroid has been extracted from react-native core and will be removed in a future release, use @react-native-community/progress-bar-android instead." +) @module("react-native") external make: React.component = "ProgressBarAndroid" diff --git a/src/components/StatusBar.res b/src/components/StatusBar.res index 675566697..00ce3152e 100644 --- a/src/components/StatusBar.res +++ b/src/components/StatusBar.res @@ -25,7 +25,7 @@ external setHidden: (bool, showHideTransition) => unit = "setHidden" @module("react-native") @scope("StatusBar") external setBarStyle: (barStyle, bool) => unit = "setBarStyle" -@module("react-native") @scope("StatusBar") +@deprecated("Deprecated by React Native.") @module("react-native") @scope("StatusBar") external setNetworkActivityIndicatorVisible: bool => unit = "setNetworkActivityIndicatorVisible" @module("react-native") @scope("StatusBar") diff --git a/src/components/TextInput.res b/src/components/TextInput.res index f841dca80..afa258a98 100644 --- a/src/components/TextInput.res +++ b/src/components/TextInput.res @@ -268,7 +268,7 @@ type props = { autoComplete?: autoComplete, autoCorrect?: bool, autoFocus?: bool, - blurOnSubmit?: bool, + @deprecated("Use `submitBehavior` instead.") blurOnSubmit?: bool, submitBehavior?: submitBehavior, caretHidden?: bool, clearButtonMode?: clearButtonMode, diff --git a/src/components/TouchableHighlight.res b/src/components/TouchableHighlight.res index c02336dbb..aab8a7c65 100644 --- a/src/components/TouchableHighlight.res +++ b/src/components/TouchableHighlight.res @@ -6,7 +6,7 @@ type props = { ref?: ref, ...TouchableWithoutFeedback.coreProps, activeOpacity?: float, - hasTVPreferredFocus?: bool, + @deprecated("Use `focusable` instead.") hasTVPreferredFocus?: bool, onHideUnderlay?: unit => unit, onShowUnderlay?: unit => unit, style?: Style.t, diff --git a/src/components/TouchableOpacity.res b/src/components/TouchableOpacity.res index d77ebd78d..7ee41875e 100644 --- a/src/components/TouchableOpacity.res +++ b/src/components/TouchableOpacity.res @@ -9,7 +9,7 @@ type props = { nextFocusLeft?: float, nextFocusRight?: float, nextFocusUp?: float, - hasTVPreferredFocus?: bool, + @deprecated("Use `focusable` instead.") hasTVPreferredFocus?: bool, style?: Style.t, tvParallaxProperties?: TV.parallax, } From a65d9cf49de88214fcf1ca01409b2efa75252462 Mon Sep 17 00:00:00 2001 From: Freddy Harris Date: Sun, 25 Oct 2026 14:42:27 +0100 Subject: [PATCH 10/11] add requestIdleCallback, setImmediate, queueMicrotask globals and ErrorUtils - Global: requestIdleCallback / cancelIdleCallback, setImmediate / clearImmediate, queueMicrotask - ErrorUtils: setGlobalHandler, getGlobalHandler, reportError, reportFatalError - InteractionManager deprecation points to Global.requestIdleCallback Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 3 ++- src/apis/ErrorUtils.res | 18 ++++++++++++++++++ src/apis/ErrorUtils.res.js | 1 + src/apis/Global.res | 23 +++++++++++++++++++++++ src/apis/InteractionManager.res | 16 ++++++++++++---- 5 files changed, 56 insertions(+), 5 deletions(-) create mode 100644 src/apis/ErrorUtils.res create mode 100644 src/apis/ErrorUtils.res.js diff --git a/CHANGELOG.md b/CHANGELOG.md index 8c2e58906..2aa59dc8b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,7 @@ - Zero-cost `Animated.timing` / `spring` / `decay` / `start` / `stop` / `reset` and `Value.interpolate` (were compiled to JS wrapper functions) - Fix `Color.hsla` generating an invalid `hsl(h, s%, l%, a)` color instead of `hsla(...)` - New APIs: `I18nManager`, `DeviceEventEmitter`, `ReactNativeVersion` +- New globals installed by React Native: `Global.requestIdleCallback` / `cancelIdleCallback` / `setImmediate` / `clearImmediate` / `queueMicrotask`, `ErrorUtils` (`setGlobalHandler`, `getGlobalHandler`, `reportError`, `reportFatalError`) - New props - `View`: touch events (`onTouchStart`, `onTouchMove`, `onTouchEnd`, `onTouchCancel` and their `Capture` variants), `onFocusCapture`, `onBlurCapture`, `nextFocus*` (Android), `experimental_accessibilityOrder` - `Pressable`: `cancelable`; `TouchableWithoutFeedback`: `rejectResponderTermination`; `TextInput`: `inputAccessoryViewButtonLabel`; `Image`: `src` @@ -30,7 +31,7 @@ - Breaking: `ScrollView` `StickyHeaderComponent` is a `React.component` (was wrongly typed as `React.element`), add `Animated.Value.fromStickyHeaderScrollValue` - Add `Style.matrix` transform - Breaking: `VirtualizedList.cellRendererComponentProps` is now typed (`cellKey`, `children`, `index`, `item`, `onLayout`, …) -- Deprecate `InteractionManager` (deprecated by React Native), `runAfterInteractions` returns a cancellable `task` +- Deprecate `InteractionManager` (deprecated by React Native, use `Global.requestIdleCallback`), `runAfterInteractions` returns a cancellable `task` - Deprecate `ProgressBarAndroid` and `Clipboard` (extracted from react-native core), `StatusBar.setNetworkActivityIndicatorVisible` - Mark as deprecated (documentation only, ReScript does not warn on record fields): `TextInput` `blurOnSubmit` (use `submitBehavior`), `hasTVPreferredFocus` on `Button` / `TouchableHighlight` / `TouchableOpacity` (use `focusable`) - Add all `fontVariant` values (ligatures, `contextual`, `stylistic-*`) diff --git a/src/apis/ErrorUtils.res b/src/apis/ErrorUtils.res new file mode 100644 index 000000000..415073f47 --- /dev/null +++ b/src/apis/ErrorUtils.res @@ -0,0 +1,18 @@ +/* + `ErrorUtils` global, installed by React Native + */ + +// receives the error (usually a JS `Error`, but anything can be thrown) and `isFatal` +type handler = (JsExn.t, bool) => unit + +@val @scope("ErrorUtils") +external setGlobalHandler: handler => unit = "setGlobalHandler" + +@val @scope("ErrorUtils") +external getGlobalHandler: unit => handler = "getGlobalHandler" + +@val @scope("ErrorUtils") +external reportError: JsExn.t => unit = "reportError" + +@val @scope("ErrorUtils") +external reportFatalError: JsExn.t => unit = "reportFatalError" diff --git a/src/apis/ErrorUtils.res.js b/src/apis/ErrorUtils.res.js new file mode 100644 index 000000000..ae1b9f17e --- /dev/null +++ b/src/apis/ErrorUtils.res.js @@ -0,0 +1 @@ +/* This output is empty. Its source's type definitions, externals and/or unused code got optimized away. */ diff --git a/src/apis/Global.res b/src/apis/Global.res index dd7c0fb00..5f09d4650 100644 --- a/src/apis/Global.res +++ b/src/apis/Global.res @@ -2,3 +2,26 @@ @scope("global") @val external hermesInternal: option<{..}> = "HermesInternal" + +type immediateId + +@val external setImmediate: (unit => unit) => immediateId = "setImmediate" +@val external clearImmediate: immediateId => unit = "clearImmediate" + +@val external queueMicrotask: (unit => unit) => unit = "queueMicrotask" + +type idleCallbackId + +type idleDeadline = { + didTimeout: bool, + timeRemaining: unit => float, +} + +type idleRequestOptions = {timeout?: float} + +@val +external requestIdleCallback: ( + idleDeadline => unit, + ~options: idleRequestOptions=?, +) => idleCallbackId = "requestIdleCallback" +@val external cancelIdleCallback: idleCallbackId => unit = "cancelIdleCallback" diff --git a/src/apis/InteractionManager.res b/src/apis/InteractionManager.res index 35f2cf4de..810ece499 100644 --- a/src/apis/InteractionManager.res +++ b/src/apis/InteractionManager.res @@ -7,22 +7,30 @@ type interactionHandle // cancellable "promise" returned by `runAfterInteractions` type task = {cancel: unit => unit} -@deprecated("InteractionManager is deprecated by React Native.") +@deprecated( + "InteractionManager is deprecated by React Native: split long tasks into smaller ones and use `Global.requestIdleCallback` instead." +) @module("react-native") @scope("InteractionManager") external runAfterInteractions: (unit => unit) => task = "runAfterInteractions" -@deprecated("InteractionManager is deprecated by React Native.") +@deprecated( + "InteractionManager is deprecated by React Native: split long tasks into smaller ones and use `Global.requestIdleCallback` instead." +) @module("react-native") @scope("InteractionManager") external createInteractionHandle: unit => interactionHandle = "createInteractionHandle" -@deprecated("InteractionManager is deprecated by React Native.") +@deprecated( + "InteractionManager is deprecated by React Native: split long tasks into smaller ones and use `Global.requestIdleCallback` instead." +) @module("react-native") @scope("InteractionManager") external clearInteractionHandle: interactionHandle => unit = "clearInteractionHandle" -@deprecated("InteractionManager is deprecated by React Native.") +@deprecated( + "InteractionManager is deprecated by React Native: split long tasks into smaller ones and use `Global.requestIdleCallback` instead." +) @module("react-native") @scope("InteractionManager") external setDeadline: float => unit = "setDeadline" From 76ca3fc8ae2946b20e65f937f3de2961bb666ddd Mon Sep 17 00:00:00 2001 From: Freddy Harris Date: Sun, 25 Oct 2026 14:55:56 +0100 Subject: [PATCH 11/11] Image: make source optional so src / srcSet can be used alone Matches React Native, where source is optional (source?: ImageSource). Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 2 +- src/components/Image.res | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2aa59dc8b..4311ed569 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,7 +26,7 @@ - New globals installed by React Native: `Global.requestIdleCallback` / `cancelIdleCallback` / `setImmediate` / `clearImmediate` / `queueMicrotask`, `ErrorUtils` (`setGlobalHandler`, `getGlobalHandler`, `reportError`, `reportFatalError`) - New props - `View`: touch events (`onTouchStart`, `onTouchMove`, `onTouchEnd`, `onTouchCancel` and their `Capture` variants), `onFocusCapture`, `onBlurCapture`, `nextFocus*` (Android), `experimental_accessibilityOrder` - - `Pressable`: `cancelable`; `TouchableWithoutFeedback`: `rejectResponderTermination`; `TextInput`: `inputAccessoryViewButtonLabel`; `Image`: `src` + - `Pressable`: `cancelable`; `TouchableWithoutFeedback`: `rejectResponderTermination`; `TextInput`: `inputAccessoryViewButtonLabel`; `Image`: `src` (`source` is now optional) - `aria-*` accessibility props (`ariaLabel`, `ariaChecked`, `ariaHidden`, `ariaLive`, …) on all components using `Accessibility` props - Breaking: `ScrollView` `StickyHeaderComponent` is a `React.component` (was wrongly typed as `React.element`), add `Animated.Value.fromStickyHeaderScrollValue` - Add `Style.matrix` transform diff --git a/src/components/Image.res b/src/components/Image.res index 582daf5bd..aeb403f2e 100644 --- a/src/components/Image.res +++ b/src/components/Image.res @@ -108,7 +108,8 @@ type imageProps = { onLoadStart?: unit => unit, referrerPolicy?: referrerPolicy, resizeMode?: Style.resizeMode, - source: Source.t, + // `source`, `src` or `srcSet` + source?: Source.t, src?: string, srcSet?: string, tintColor?: Color.t,