From 5f5837526eda384aa0fa8960535b1bc5f2da9d4b Mon Sep 17 00:00:00 2001 From: David Sancho Moreno Date: Thu, 16 Nov 2023 21:01:26 +0100 Subject: [PATCH] Add reanimated jota and gesturehandler --- drafts/rescript-gesture-handler/LICENSE | 21 + drafts/rescript-gesture-handler/README.md | 57 ++ drafts/rescript-gesture-handler/bsconfig.json | 18 + drafts/rescript-gesture-handler/package.json | 44 ++ .../src/Directions.res | 19 + drafts/rescript-gesture-handler/src/Fling.res | 49 ++ .../rescript-gesture-handler/src/Gesture.res | 10 + .../src/GestureDetector.res | 2 + .../src/GestureHandlerCommon.res | 128 +++ .../src/GestureStateManager.res | 11 + drafts/rescript-gesture-handler/src/Pan.res | 99 +++ drafts/rescript-jotai/README.md | 436 ++++++++++ drafts/rescript-jotai/bsconfig.json | 29 + drafts/rescript-jotai/package.json | 34 + drafts/rescript-jotai/src/Atom.res | 289 +++++++ drafts/rescript-jotai/src/Atom.resi | 67 ++ drafts/rescript-jotai/src/Provider.res | 20 + drafts/rescript-jotai/src/Store.res | 67 ++ drafts/rescript-jotai/src/Utils.res | 23 + .../src/utils/Utils_AtomWithDefault.res | 17 + .../src/utils/Utils_AtomWithReducer.res | 23 + .../src/utils/Utils_AtomWithReset.res | 15 + .../src/utils/Utils_AtomWithStorage.res | 17 + .../src/utils/Utils__AtomFamily.res | 57 ++ .../rescript-jotai/src/utils/Utils__Hooks.res | 42 + .../src/utils/Utils__Loadable.res | 55 ++ drafts/rescript-jotai/src/wrapper.js | 58 ++ drafts/rescript-jotai/src/wrapper.mjs | 60 ++ drafts/rescript-jotai/test/Atom_test.res | 97 +++ drafts/rescript-jotai/test/Utils_test.res | 100 +++ drafts/rescript-reanimated/CHANGELOG.md | 23 + drafts/rescript-reanimated/LICENSE | 21 + drafts/rescript-reanimated/README.md | 43 + drafts/rescript-reanimated/bsconfig.json | 18 + drafts/rescript-reanimated/package.json | 44 ++ drafts/rescript-reanimated/src/Reanimated.res | 744 ++++++++++++++++++ .../src/ReanimatedStyle.res | 628 +++++++++++++++ .../UseAnimatedScrollHandlerExample.res | 34 + drafts/rescript-reanimated/src/exports.js | 5 + 39 files changed, 3524 insertions(+) create mode 100644 drafts/rescript-gesture-handler/LICENSE create mode 100644 drafts/rescript-gesture-handler/README.md create mode 100644 drafts/rescript-gesture-handler/bsconfig.json create mode 100644 drafts/rescript-gesture-handler/package.json create mode 100644 drafts/rescript-gesture-handler/src/Directions.res create mode 100644 drafts/rescript-gesture-handler/src/Fling.res create mode 100644 drafts/rescript-gesture-handler/src/Gesture.res create mode 100644 drafts/rescript-gesture-handler/src/GestureDetector.res create mode 100644 drafts/rescript-gesture-handler/src/GestureHandlerCommon.res create mode 100644 drafts/rescript-gesture-handler/src/GestureStateManager.res create mode 100644 drafts/rescript-gesture-handler/src/Pan.res create mode 100644 drafts/rescript-jotai/README.md create mode 100644 drafts/rescript-jotai/bsconfig.json create mode 100644 drafts/rescript-jotai/package.json create mode 100644 drafts/rescript-jotai/src/Atom.res create mode 100644 drafts/rescript-jotai/src/Atom.resi create mode 100644 drafts/rescript-jotai/src/Provider.res create mode 100644 drafts/rescript-jotai/src/Store.res create mode 100644 drafts/rescript-jotai/src/Utils.res create mode 100644 drafts/rescript-jotai/src/utils/Utils_AtomWithDefault.res create mode 100644 drafts/rescript-jotai/src/utils/Utils_AtomWithReducer.res create mode 100644 drafts/rescript-jotai/src/utils/Utils_AtomWithReset.res create mode 100644 drafts/rescript-jotai/src/utils/Utils_AtomWithStorage.res create mode 100644 drafts/rescript-jotai/src/utils/Utils__AtomFamily.res create mode 100644 drafts/rescript-jotai/src/utils/Utils__Hooks.res create mode 100644 drafts/rescript-jotai/src/utils/Utils__Loadable.res create mode 100644 drafts/rescript-jotai/src/wrapper.js create mode 100644 drafts/rescript-jotai/src/wrapper.mjs create mode 100644 drafts/rescript-jotai/test/Atom_test.res create mode 100644 drafts/rescript-jotai/test/Utils_test.res create mode 100644 drafts/rescript-reanimated/CHANGELOG.md create mode 100644 drafts/rescript-reanimated/LICENSE create mode 100644 drafts/rescript-reanimated/README.md create mode 100644 drafts/rescript-reanimated/bsconfig.json create mode 100644 drafts/rescript-reanimated/package.json create mode 100644 drafts/rescript-reanimated/src/Reanimated.res create mode 100644 drafts/rescript-reanimated/src/ReanimatedStyle.res create mode 100644 drafts/rescript-reanimated/src/examples/UseAnimatedScrollHandlerExample.res create mode 100644 drafts/rescript-reanimated/src/exports.js diff --git a/drafts/rescript-gesture-handler/LICENSE b/drafts/rescript-gesture-handler/LICENSE new file mode 100644 index 0000000..4a0ef6b --- /dev/null +++ b/drafts/rescript-gesture-handler/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 Aleksa Bacovic + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/drafts/rescript-gesture-handler/README.md b/drafts/rescript-gesture-handler/README.md new file mode 100644 index 0000000..081f16d --- /dev/null +++ b/drafts/rescript-gesture-handler/README.md @@ -0,0 +1,57 @@ +# `rescript-gesture-handler` + +ReScript bindings for `react-native-gesture-handler`. + +`rescript-gesture-handler` `x.y.*` means it's compatible with `react-native-gesture-handler` `x.y.*` + +**NOTE:** `v2` isn't backwards compatible with `v1`, even though original `react-native-gesture-handler` is. Mainly because I believe they will be dropping support for it in the near future (they already warn you if you are using `v1` API). + +## Status + +Work in progress. + +### v1 (See `v1.10.0` branch) + +**NOTE:** There won't be any progress with `v1` until `v2` is finished. + +- [x] `FlingGestureHandler` +- [ ] `PanGestureHandler` +- [ ] ... + +### v2 + +- [x] `Fling` +- [x] `Pan` +- [ ] `Pinch` +- [ ] `Force` +- [ ] ... + +## Installation + +### v1 + +``` +npm install rescript-gesture-handler@1.10.0 +# or +yarn add rescript-gesture-handler@1.10.0 +``` + +### v2 + +``` +npm install rescript-gesture-handler +# or +yarn add rescript-gesture-handler +``` + +## Example (v2) + +### Fling + +```res +open Directions +let flingGesture = + Gesture.makeFling() + ->Fling.direction(Direction.make(directions.left)) + ->Fling.onEnd((_event, _success) => ResAnimated.runOnJS(__someFunction__)(.)) +``` diff --git a/drafts/rescript-gesture-handler/bsconfig.json b/drafts/rescript-gesture-handler/bsconfig.json new file mode 100644 index 0000000..0339081 --- /dev/null +++ b/drafts/rescript-gesture-handler/bsconfig.json @@ -0,0 +1,18 @@ +{ + "name": "rescript-gesture-handler", + "reason": { "react-jsx": 3 }, + "sources": [ + { + "dir": "src", + "subdirs": true + } + ], + "package-specs": [ + { + "module": "es6", + "in-source": true + } + ], + "suffix": ".bs.js", + "bs-dependencies": ["@rescript/react", "rescript-react-native"] +} diff --git a/drafts/rescript-gesture-handler/package.json b/drafts/rescript-gesture-handler/package.json new file mode 100644 index 0000000..b3a9467 --- /dev/null +++ b/drafts/rescript-gesture-handler/package.json @@ -0,0 +1,44 @@ +{ + "name": "rescript-gesture-handler", + "description": "ReScript bindings for react-native-gesture-handler", + "version": "2.2.2", + "repository": { + "type": "git", + "url": "git+https://github.com/reck753/rescript-gesture-handler.git" + }, + "publishConfig": { + "access": "public" + }, + "keywords": [ + "rescript", + "react-native", + "react-native-gesture-handler" + ], + "license": "MIT", + "scripts": { + "format:most": "prettier --write \"**/*.{md,json,js,css}\"", + "format:res": "rescript format -all", + "format:all": "yarn format:most && yarn format:res", + "clean": "rescript clean", + "build": "rescript clean && rescript build -with-deps", + "watch": "rescript clean && rescript build -with-deps -w" + }, + "peerDependencies": { + "@rescript/react": "^0.10.3", + "react-native-gesture-handler": "~2.2.0", + "rescript-react-native": "^0.64.3 || ^0.65.0 || ^0.66.0" + }, + "devDependencies": { + "@rescript/react": "^0.10.3", + "rescript-react-native": "^0.64.3 || ^0.65.0 || ^0.66.0", + "rescript": "^9.1.4", + "prettier": "^2.5.1" + }, + "prettier": { + "trailingComma": "all" + }, + "bugs": { + "url": "https://github.com/reck753/rescript-gesture-handler/issues" + }, + "homepage": "https://github.com/reck753/rescript-gesture-handler#readme" +} diff --git a/drafts/rescript-gesture-handler/src/Directions.res b/drafts/rescript-gesture-handler/src/Directions.res new file mode 100644 index 0000000..7471f28 --- /dev/null +++ b/drafts/rescript-gesture-handler/src/Directions.res @@ -0,0 +1,19 @@ +type directions = { + @as("RIGHT") right: int, + @as("LEFT") left: int, + @as("UP") up: int, + @as("DOWN") down: int, +} + +@module("react-native-gesture-handler") +external directions: directions = "Directions" + +module type Direction = { + type t + let make: int => t +} + +module Direction = { + type t = int + let make = direction => direction +} diff --git a/drafts/rescript-gesture-handler/src/Fling.res b/drafts/rescript-gesture-handler/src/Fling.res new file mode 100644 index 0000000..a6bedde --- /dev/null +++ b/drafts/rescript-gesture-handler/src/Fling.res @@ -0,0 +1,49 @@ +type handlerStateChangeEventPayload = { + // Flign specific + x: float, + y: float, + absoluteX: float, + absoluteY: float, + // Common + handlerTag: int, + numberOfPointers: int, + state: GestureHandlerCommon.state, + oldState: GestureHandlerCommon.state, +} + +module GestureHandlerCallbacks = GestureHandlerCommon.GestureHandlerCallbacks.Make({ + type gestureStateChangeEvent = handlerStateChangeEventPayload +}) + +type config = { + // Fling specific + direction: option, + numberOfPointers: option, + // Common + enabled: option, + shouldCancelWhenOutside: option, + hitSlop: option, + ref: option>>, + requireToFail: option>, + simultaneousWith: option>, + needsPointerData: option, + manualActivation: option, +} + +type t = { + // Fling specific + config: config, + // Common + handlerTag: int, + handlerName: string, + handlers: GestureHandlerCallbacks.t, +} + +// Fling specific +@send external direction: (t, Directions.Direction.t) => t = "direction" +@send external numberOfPointers: (t, int) => t = "numberOfPointers" +// Common +include GestureHandlerCommon.Methods({ + type t = t + type gestureStateChangeEvent = handlerStateChangeEventPayload +}) diff --git a/drafts/rescript-gesture-handler/src/Gesture.res b/drafts/rescript-gesture-handler/src/Gesture.res new file mode 100644 index 0000000..b7ee5c6 --- /dev/null +++ b/drafts/rescript-gesture-handler/src/Gesture.res @@ -0,0 +1,10 @@ +type t + +external fling: Fling.t => t = "%identity" +external pan: Pan.t => t = "%identity" + +@module("react-native-gesture-handler") @scope("Gesture") +external makeFling: unit => Fling.t = "Fling" + +@module("react-native-gesture-handler") @scope("Gesture") +external makePan: unit => Pan.t = "Pan" diff --git a/drafts/rescript-gesture-handler/src/GestureDetector.res b/drafts/rescript-gesture-handler/src/GestureDetector.res new file mode 100644 index 0000000..5df550f --- /dev/null +++ b/drafts/rescript-gesture-handler/src/GestureDetector.res @@ -0,0 +1,2 @@ +@react.component @module("react-native-gesture-handler") +external make: (~gesture: Gesture.t, ~children: React.element) => React.element = "GestureDetector" diff --git a/drafts/rescript-gesture-handler/src/GestureHandlerCommon.res b/drafts/rescript-gesture-handler/src/GestureHandlerCommon.res new file mode 100644 index 0000000..39fb1db --- /dev/null +++ b/drafts/rescript-gesture-handler/src/GestureHandlerCommon.res @@ -0,0 +1,128 @@ +type state = [#0 | #1 | #2 | #3 | #4 | #5] + +type eventType = [#0 | #1 | #2 | #3 | #4] + +type touchData = {id: int, x: float, y: float, absoluteX: float, absoluteY: float} + +type gestureEventPayload = {numberOfPointers: int, state: state} + +type gestureTouchEvent = { + handlerTag: int, + numberOfTouches: int, + state: state, + eventType: eventType, + allTouches: array, + changedTouches: array, +} + +module HitSlop = { + type t + type asFloat = float + type asAllSides = { + top: option, + bottom: option, + left: option, + right: option, + vertical: option, + horizontal: option, + } + type asWidthLeft = { + width: float, + left: float, + } + type asWidthRight = { + width: float, + right: float, + } + type asHeightTop = { + height: float, + top: float, + } + type asHeightBottom = { + height: float, + bottom: float, + } + + external float: asFloat => t = "%identity" + external allSides: asAllSides => t = "%identity" + external widthLeft: asWidthLeft => t = "%identity" + external widthRight: asWidthRight => t = "%identity" + external heightTop: asHeightTop => t = "%identity" + external heightBottom: asHeightBottom => t = "%identity" +} + +type touchEventHandlerType = (gestureTouchEvent, GestureStateManager.t) => unit + +module GestureHandlerCallbacks = { + module Make = ( + T: { + type gestureStateChangeEvent + }, + ) => { + type t = { + handlerTag: int, + onBegin: option unit>, + onStart: option unit>, + onEnd: option<(T.gestureStateChangeEvent, bool) => unit>, + onFinalize: option<(T.gestureStateChangeEvent, bool) => unit>, + // handlerStateUpdateEvent? + onUpdate: option unit>, + onChange: option unit>, + onTouchesDown: option, + onTouchesMove: option, + onTouchesUp: option, + onTouchesCancelled: option, + changeEventCalculator: option< + (gestureEventPayload, option) => gestureEventPayload, + >, + isWorklet: bool, + } + } +} + +// FIXME This is extremely hard since this has to be some twisted +// recursive type. This literally has to be `Fling.t` | `Pan.t` | ... type +module GestureType = { + type t +} + +module GestureRef = { + type t + type asFloat = float + type asGestureType = GestureType.t + type asRef = Js.Nullable.t> + + external float: asFloat => t = "%identity" + external gestureType: asGestureType => t = "%identity" + external ref: asRef => t = "%identity" +} + +module Methods = ( + T: { + type t + type gestureStateChangeEvent + }, +) => { + type onEndCallback = (T.gestureStateChangeEvent, bool) => unit + type onFinalizeCallback = (T.gestureStateChangeEvent, bool) => unit + @send external onBegin: (T.t, T.gestureStateChangeEvent => unit) => T.t = "onBegin" + @send external onStart: (T.t, T.gestureStateChangeEvent => unit) => T.t = "onStart" + @send external onEnd: (T.t, onEndCallback) => T.t = "onEnd" + @send external onFinalize: (T.t, onFinalizeCallback) => T.t = "onFinalize" + @send external onUpdate: (T.t, T.gestureStateChangeEvent => unit) => T.t = "onUpdate" + @send + external onTouchesDown: (T.t, touchEventHandlerType) => T.t = "onTouchesDown" + @send external onTouchesUp: (T.t, touchEventHandlerType) => T.t = "onTouchesUp" + @send + external onTouchesMove: (T.t, touchEventHandlerType) => T.t = "onTouchesMove" + @send + external onTouchesCancelled: (T.t, touchEventHandlerType) => T.t = "onTouchesCancelled" + @send external enabled: (T.t, bool) => T.t = "enabled" + @send external shouldCancelWhenOutside: (T.t, bool) => T.t = "shouldCancelWhenOutside" + @send external hitSlop: (T.t, HitSlop.t) => T.t = "hitSlop" + // @send external simultaneousWithExternalGesture: - TODO too hard to type right now. Appears like React.ref + // @send external requireExternalGestureToFail: - TODO too hard to type right now. Appears like React.ref + @send external initialize: (T.t, unit) => unit = "initialize" + @send external toGestureArray: (T.t, unit) => array = "toGestureArray" + @send external prepare: (T.t, unit) => unit = "prepare" +} diff --git a/drafts/rescript-gesture-handler/src/GestureStateManager.res b/drafts/rescript-gesture-handler/src/GestureStateManager.res new file mode 100644 index 0000000..d1e3ad9 --- /dev/null +++ b/drafts/rescript-gesture-handler/src/GestureStateManager.res @@ -0,0 +1,11 @@ +type t = { + begin: unit => unit, + activate: unit => unit, + fail: unit => unit, + end: unit => unit, +} + +type gestureStateManager = {create: int => t} + +@module("react-native-gesture-handler") +external gestureStateManager: gestureStateManager = "GestureStateManager" diff --git a/drafts/rescript-gesture-handler/src/Pan.res b/drafts/rescript-gesture-handler/src/Pan.res new file mode 100644 index 0000000..b4710ce --- /dev/null +++ b/drafts/rescript-gesture-handler/src/Pan.res @@ -0,0 +1,99 @@ +module Offset = { + type t + type asFloat = float + type asTuple = (float, float) + + external float: asFloat => t = "%identity" + external tuple: asTuple => t = "%identity" +} + +type handlerStateChangeEventPayload = { + // Pan specific + x: float, + y: float, + absoluteX: float, + absoluteY: float, + translationX: float, + translationY: float, + velocityX: float, + velocityY: float, + // Common + handlerTag: int, + numberOfPointers: int, + state: GestureHandlerCommon.state, + oldState: GestureHandlerCommon.state, +} + +module GestureHandlerCallbacks = GestureHandlerCommon.GestureHandlerCallbacks.Make({ + type gestureStateChangeEvent = handlerStateChangeEventPayload +}) + +type config = { + // Pan specific + activeOffsetYStart: option, + activeOffsetYEnd: option, + activeOffsetXStart: option, + activeOffsetXEnd: option, + failOffsetYStart: option, + failOffsetYEnd: option, + failOffsetXStart: option, + failOffsetXEnd: option, + activeOffsetY: option, + activeOffsetX: option, + failOffsetX: option, + failOffsetY: option, + // Common + enabled: option, + shouldCancelWhenOutside: option, + hitSlop: option, + ref: option>>, + requireToFail: option>, + simultaneousWith: option>, + needsPointerData: option, + manualActivation: option, +} + +type t = { + // Pan specific + config: config, + // Common + handlerTag: int, + handlerName: string, + handlers: GestureHandlerCallbacks.t, +} + +// Pan specific +type onChangeArg = { + x: float, + y: float, + absoluteX: float, + absoluteY: float, + translationX: float, + translationY: float, + velocityX: float, + velocityY: float, + changeX: float, + changeY: float, + handlerTag: int, + numberOfPointers: int, + state: GestureHandlerCommon.state, +} +type onChangeCallback = onChangeArg => unit +@send external activeOffsetY: (t, Offset.t) => t = "activeOffsetY" +@send external activeOffsetX: (t, Offset.t) => t = "activeOffsetX" +@send external failOffsetY: (t, Offset.t) => t = "failOffsetY" +@send external failOffsetX: (t, Offset.t) => t = "failOffsetX" +@send external minPointers: (t, int) => t = "minPointers" +@send external maxPointers: (t, int) => t = "maxPointers" +@send external minDistance: (t, int) => t = "minDistance" +@send external minVelocity: (t, int) => t = "minVelocity" +@send external minVelocityX: (t, int) => t = "minVelocityX" +@send external minVelocityY: (t, int) => t = "minVelocityY" +@send external averageTouches: (t, bool) => t = "averageTouches" +@send external enableTrackpadTwoFingerGesture: (t, bool) => t = "enableTrackpadTwoFingerGesture" +@send external onChange: (t, onChangeCallback) => t = "onChange" +// Common +include GestureHandlerCommon.Methods({ + type t = t + type gestureStateChangeEvent = handlerStateChangeEventPayload +}) diff --git a/drafts/rescript-jotai/README.md b/drafts/rescript-jotai/README.md new file mode 100644 index 0000000..23af13b --- /dev/null +++ b/drafts/rescript-jotai/README.md @@ -0,0 +1,436 @@ +# rescript-jotai + +[ReScript](https://rescript-lang.org/) bindings for [Jotai](https://github.com/pmndrs/jotai). Primitive and flexible state management for React. + +Versions below 0.3.0 support Jotai v1. + +Versions 0.3.0 and higher support Jotai v2. They also require at least rescript 10.1 (for async/await) and react 18+. + +## Installation + +Install with `npm`: + +```bash +npm install jotai @fattafatta/rescript-jotai +``` + +Or install with `yarn`: + +```bash +yarn add jotai @fattafatta/rescript-jotai +``` + +Add `@fattafatta/rescript-jotai` as a dependency to your `bsconfig.json`: + +```json +"bs-dependencies": ["@rescript/react", "@fattafatta/rescript-jotai"] +``` + +## Usage + +### Provider + +A Provider works just like React context provider. If you don't use a Provider, it works as provider-less mode with a default store. A Provider will be necessary if we need to hold different atom values for different component trees. The store property is optional + +```rescript +let store = Jotai.Store.make() + +module App = { + @react.component + let make = () => + + ... + +} +``` + +### Store + +Atoms are always stored in a store. If no specific store is passed to `Provider` a default store will be created. + +#### Create a store (`Jotai.Store.make`) + +Creates a store. + +```rescript +let store = Jotai.Store.make(); +``` + +#### Get default store (`Jotai.Store.getDefaultStore`) + +Get the default store that is created if no specific store was provided. + +```rescript +let store = Jotai.Store.getDefaultStore(); +``` + +#### Access a store + +A store supports 3 functions to access its content. + +`get` for getting atom values. + +```rescript +let value = Jotai.Store.get(store, atom); +``` + +`set` for setting atom values. + +```rescript +Jotai.Store.set(store, atom, 1); +``` + +`sub` for subscribing to atom changes. Returns a function to unsubscribe. + +```rescript +let unsub = Jotai.Store.sub(store, atom, () => { + Js.Console.log2("atom value is changed to", Jotai.Store.get(store, atom)) +}) + +// unsub() to unsubscribe +``` + +### Create atoms + +In Jotai there is no distinction between normal and async atoms (i.e. atoms that hold promise). So in Jotai hooks will always return the resolved value. +It's not possible to reproduce this one to one in ReScript. Therefore, wherever necessary, functions are provided for normal atoms, and separate `*Async` functions are provided to handle atoms with promises. (e.g. `useAtom` and `useAtomAsync`). + +CAUTION: Using the wrong function may not result in compile errors, but very likely runtime errors. As a simple guideline just remember that a hook should never return a promise. If the return type is `promise<>` use the async version of the function instead. + +#### Atom type + +Atoms have a value, a setter function (from `Atom.Actions`), and a set of tags that restrict which operations are allowed on the atom (e.g is the atom `#resettable`). +Normally the type will be inferred automatically. If annotation is required it should be sufficient to provide the first type (the value). + +Example: + +```rescript +let atom: Jotai.Atom.t = Jotai.Atom.make(1) +``` + +#### Primitive atom (`Jotai.Atom.make`) + +Create a (primitive) readable and writable atom (config). + +CAUTION: Don't pass a function as argument to `Atom.make`. That would implicitly create a computed atom and the compiler will produce weird types. Use `Atom.makeComputed` for that. + +```rescript +let atom1 = Jotai.Atom.make(1) +let atom2 = Jotai.Atom.make(["text"]) +// DON'T do this: +let atom3 = Jotai.Atom.make(() => 1) +``` + +#### Atom from thunk (`Jotai.Atom.makeThunk`) + +Create a readonly atom from a function. The function can be async. + +```rescript +let atom1 = Jotai.Atom.makeThunk(async () => 1) +// shorthand for +let atom2 = Jotai.Atom.makeComputed(async ({get}) => 1) +``` + +#### Computed atom (`Jotai.Atom.makeComputed`) + +Create a computed readonly atom. A computed atom can combine any number of readable atoms to create a single derived value. The syntax varies slightly from Jotai. +Note the curly braces in `({get})`. Requires `React.Suspense` or `Utils.Loadable` if the value is a promise (e.g. the getter is async) + +```rescript +let atom1 = Jotai.Atom.make(1) +let atom2 = Jotai.Atom.makeComputed(({get}) => get(atom1) + 1) +let atom3 = Jotai.Atom.makeComputed(({get}) => get(atom1) + get(atom2) + 1) + +// using async atoms requires React.Suspense +let atom4 = Jotai.Atom.makeComputed(async ({get}) => await get(asyncAtom) + 1) +``` + +#### (DEPRECATED) Computed async atom (`Jotai.Atom.makeComputedAsync`) + +This function is no longer necessary. Atoms no fully support async getters out of the box. + +(Requires React.Suspense) Create an computed readonly atom with an async getter. All components will be notified when the returned promise resolves. + +```rescript +let atom1 = Jotai.Atom.make(1) +let atom2 = Jotai.Atom.makeComputedAsync(async ({get}) => {atom1->get + 1}) +``` + +#### Computed writable atom (`Jotai.Atom.makeWritableComputed`) + +Create a computed atom that supports read and write. The getter may by async, but the setter must be synchronous. For async setters use `makeComputedAsync`. + +```rescript +let atom1 = Jotai.Atom.make(1) +let atom2 = Jotai.Atom.makeWritableComputed( + ({get}) => get(atom1) + 1, + ({get, set}, arg) => { + atom1->set(get(atom1) + arg) + }, +) +``` + +#### Computed writable async atom (`Jotai.Atom.makeWritableComputedAsync`) + +Create a computed atom with asynchronous write (setter). Jotai supports async write operations for computed atoms. Simply call 'set' when the promise resolves. + +```rescript +let atom1 = Jotai.Atom.make(1) +let atom2 = Jotai.Atom.makeWritableComputedAsync( + ({get}) => get(atom1) + 1, + async ({get, set}, arg) => { + // do async stuff + set(atom1, get(atom1) + arg) + }, +) +``` + +#### Computed writeonly atom (`Jotai.Atom.makeWriteOnly`) + +Create a writeOnly computed atom. (Note: Sometimes the type can not be inferred automatically and has to be annotated) + +```rescript +let atom1 = make(1) +let atom2: Jotai.Atom.t = Jotai.Atom.makeWriteOnly(({get, set}, args) => + atom1->set(get(atom1) + args) +) +``` + +#### Computed writeonly async atom (`Jotai.Atom.makeWriteOnlyAsync`) + +Create a writeOnly computed async atom (i.e. the setter is an async function) + +```rescript +let atom1 = make(1) +let atom2 = Jotai.Atom.makeWriteOnlyAsync(async ({get, set}, args) => atom1->set(get(atom1) + args)) + +``` + +#### OnMount (`Jotai.Atom.onMount`) + +`onMount` is a function which takes a function setAtom and returns `onUnmount` function optionally. The `onMount` function is called when the atom is first used in a provider, and `onUnmount` is called when it’s no longer used. + +```rescript +let atom1 = Jotai.Atom.make(1) +atom1->Jotai.Atom.onMount(setAtom => { + setAtom(a => a + 1) // increment count on mount + () => () // return onUnmount function +}) +``` + +### Core hooks + +#### Using read/write atoms (`Jotai.Atom.useAtom`) + +Standard hook to use with read/write synchronous atoms. (For handling of readOnly/writeOnly atoms see `Jotai.useAtomValue` or `Jotai.useSetAtom`) + +```rescript +let atom1 = Jotai.Atom.make(1) +let (value, setValue) = Jotai.Atom.useAtom(atom1) +``` + +#### Using async read/write atoms (`Jotai.Atom.useAtomAsync`) + +Standard hook to use with read/write async atoms (i.e. all atoms that contain a promise). (For handling of readOnly/writeOnly atoms see `Jotai.useAtomValueAsync` or `Jotai.useSetAtom`) + +```rescript +let atom1 = Jotai.Atom.makeAsync(async () => 1) +let (value, setValue) = Jotai.Atom.useAtomAsync(atom1) +``` + +#### Get only the update function (`Jotai.Atom.useSetAtom`) + +A hook that returns only the update function of an atom. Can be used to access writeOnly atoms. + +```rescript +let atom = Jotai.Atom.make(1) +let setValue = Jotai.Atom.useSetAtom(atom) +setValue(prev => prev + 1) +``` + +#### Get only the value (`Jotai.Atom.useAtomValue`) + +A hook that returns only the value of a synchronous atom. Can be used to access readOnly atoms. + +```rescript +let atom = Jotai.Atom.make(1) +let value = Jotai.Atom.useAtomValue(atom) +``` + +#### Get the value from an async atom (`Jotai.Atom.useAtomValueAsync`) + +A hook that returns only the value of an async atom (i.e. the atom contains a promise). Can be used to access readOnly async atoms. + +```rescript +let atom = Jotai.Atom.make(1) +let value = Jotai.Atom.useAtomValue(atom) +``` + +### Utils + +#### Atom with localStorage (`Jotai.Utils.AtomWithStorage.make`) + +Creates an atom with a value persisted in `localStorage`. Currently only `localStorage` is supported. + +```rescript +let atom1 = Jotai.Utils.AtomWithStorage.make('storageKey', 1) +``` + +#### Resettable atom (`Jotai.Utils.AtomWithReset.make`) + +Creates an atom that can be reset to its initialValue with the `useResetAtom` hook. + +```rescript +let atom = Jotai.Utils.AtomWithReset.make(1) +// ... change value ... +let reset = Jotai.Utils.useResetAtom(atom) +reset() +``` + +#### Atom with default (`Jotai.Utils.AtomWithDefault.make`) + +Create a resettable, writable atom. Its default value can be specified with a read function instead of an initial value. This function support sync and async getters. + +```rescript +let atom1 = Jotai.Atom.make(1) +let atom2 = Jotai.Utils.AtomWithDefault.make(({get}) => atom1->get + 1) +// async +let atom3 = Jotai.Atom.makeAsync(async () =>1) +let atom4 = Jotai.Utils.AtomWithDefault.make(async({get}) => await atom3->get + 1) +``` + +#### Atom with Reducer (`Jotai.Utils.AtomWithReducer.make`) + +Creates an atom that uses a reducer to update its value. + +```rescript +type actionType = Inc(int) | Dec(int) +let countReducer = (prev, action) => { + switch action { + | Inc(num) => prev + num + | Dec(num) => prev - num + } +} +let atom = Utils.AtomWithReducer.make(0, countReducer) +let (value, dispatch) = Atom.use(atom) +Inc(1)->dispatch +``` + +#### AtomFamily (`Jotai.Utils.AtomFamily`) + +Creates an atomFamily. If the compiler has trouble inferring the type, it is recommended to set the type directly on the function param. + +```rescript +let atomFamily = Jotai.Utils.AtomFamily.make((name: string) => Jotai.Atom.make(name)) +let atom = atomFamily("text") +``` + +##### With Equals function + +Creates an atomFamily with a supplied comparison function + +```rescript +let atomFamWithEqual = Jotai.Utils.AtomFamily.makeWithEqual( + name => Jotai.Atom.make(name), + (strA, strB) => strA == strB, +) +``` + +##### Remove + +Removes an atom from an atomFamily. + +```rescript +Jotai.Utils.AtomFamily.remove(atomFamily, "text") +``` + +##### SetShouldRemove (`Jotai.Utils.AtomFamily.setShouldRemove`) + +Register a shouldRemove function. + +```rescript +let shouldRemove = (createdAt, param) => param == "test" +Jotai.Utils.AtomFamily.setShouldRemove(atomFamily, shouldRemove) +``` + +Unregister the shouldRemove function with `Jotai.Utils.AtomFamily.setShouldRemoveUnregister`. + +```rescript +Jotai.Utils.AtomFamily.setShouldRemoveUnregister(atomFamily, Js.Null.empty) +``` + +#### Loadable (`Jotai.Utils.Loadable`) + +Can be used if you don't want async atoms to suspend or throw to an error boundary. + +```rescript +let atom = Jotai.Atom.makeThunk(async () => "stuff") +let loadableAtom = Jotai.Utils.Loadable.make(atom) + +// inside component: +let value = Jotai.Utils.Loadable.useLoadableValue(loadableAtom) +{switch (value.state, value.data, value.error) { + | (#hasData, Some(d), _) =>

{("Data: " ++ d)->React.string}

+ | (#hasError, _, Some(e)) =>

{e->React.string}

+ | _ =>

{"Loading..."->React.string}

+}} +``` + +### Utils Hooks + +#### Reset an atom (`Jotai.Utils.useResetAtom`) + +Returns a function that can be used to reset a resettable atom. + +```rescript +let atom = Jotai.Utils.AtomWithReset(1) // value: 1 +let (_, setValue) = Jotai.Atom.use(atom) +setValue(2) // value: 2 +let resetValue = Jotai.Utils.useResetAtom(atom) +resetValue() // value back to: 1 +``` + +#### Pass a reducer to a writable atom (`Jotai.Utils.useReducerAtom`) + +Allows to use a reducer function with a primitive atom. + +```rescript +type actionType = Inc(int) | Dec(int) +let countReducer = (prev, action) => { + switch action { + | Inc(num) => prev + num + | Dec(num) => prev - num + } +} +let atom = Jotai.Atom.make(0) +let (value, dispatch) = Jotai.Utils.useReducerAtom(atom, countReducer) +Inc(1)->dispatch +``` + +#### Use a loadable atom (`Jotai.Utils.Loadable.useLoadableValue`) + +Hook to use a loadable atom. + +```rescript +// inside component: +let value = Jotai.Utils.Loadable.useLoadableValue(loadableAtom) +``` + +## Alternatives + +This package was greatly inspired by [re-jotai](https://github.com/gaku-sei/re-jotai). I just preferred to have a different syntax for the get/set functions. + +## Missing functions from Jotai + +These functions are not (yet) supported. + +- atomWithObservable +- atomWithHash +- selectAtom +- useAtomCallback +- freezeAtom +- splitAtom +- useHydrateAtoms +- options for useAtom +- Vanilla library +- multi argument setters (write functions). This can be accoplished by simly using an array or a record as argument instead. diff --git a/drafts/rescript-jotai/bsconfig.json b/drafts/rescript-jotai/bsconfig.json new file mode 100644 index 0000000..b863813 --- /dev/null +++ b/drafts/rescript-jotai/bsconfig.json @@ -0,0 +1,29 @@ +{ + "name": "@fattafatta/rescript-jotai", + "namespace": "jotai", + "reason": { "react-jsx": 3 }, + "sources": [ + { + "dir": "src", + "subdirs": true, + "public": ["Atom", "Provider", "Utils", "Store"] + }, + { + "dir": "test", + "subdirs": true, + "type": "dev" + } + ], + "package-specs": [ + { + "module": "commonjs", + "in-source": true + } + ], + "suffix": ".bs.js", + "bs-dependencies": ["@rescript/react"], + "bs-dev-dependencies": [ + "@greenlabs/rescript-jest", + "@greenlabs/rescript-testing-library" + ] +} diff --git a/drafts/rescript-jotai/package.json b/drafts/rescript-jotai/package.json new file mode 100644 index 0000000..543242e --- /dev/null +++ b/drafts/rescript-jotai/package.json @@ -0,0 +1,34 @@ +{ + "name": "@fattafatta/rescript-jotai", + "version": "0.3.0", + "description": "Rescript bindings for Jotai (react state management).", + "keywords": [ + "rescript", + "react", + "jotai", + "binding" + ], + "author": "Fattafatta", + "license": "MIT", + "repository": "github:Fattafatta/rescript-jotai", + "scripts": { + "clean": "rescript clean", + "build": "rescript", + "build:deps": "rescript build -with-deps", + "start": "rescript build -w -with-deps", + "test": "npm run build && jest" + }, + "devDependencies": { + "@greenlabs/rescript-jest": "^1.0.1", + "@greenlabs/rescript-testing-library": "^2.0.1", + "jest-environment-jsdom": "^29.3.1", + "jest-localstorage-mock": "^2.4.22", + "jotai": "^2.0.3", + "rescript": "^10.1.2" + }, + "peerDependencies": { + "@rescript/react": "^0.11.0", + "jotai": "^2.0.3", + "react": "^18.0.0" + } +} diff --git a/drafts/rescript-jotai/src/Atom.res b/drafts/rescript-jotai/src/Atom.res new file mode 100644 index 0000000..de3eea0 --- /dev/null +++ b/drafts/rescript-jotai/src/Atom.res @@ -0,0 +1,289 @@ +// TYPES +/** Tags are used to restrict (or allow) operations on atoms. For example, some hooks can +only be used on `#primitive` atoms, or some atoms can be `#resettable`. Tags help transfer the +flexibility of Jotai into the strictly typed world of ReScript. +*/ +module Tags = { + type r = [#readable] + type w = [#writable] + type p = [#primitive] + type re = [#resettable] + type all = [r | w | p | re] +} + +/** Readonly atoms have no setter, while writeonly atoms have no getter but one is required in +the type signature. Setting an explicit none type prevents compiler problems with type annotation. +*/ +type none + +/** Atoms use different functions to update their values. For example an `AtomWithReducer` provides +a `dispatch` function that has an 'action parameter. + +```rescript +let (value, dispatch) = Atom.use(atomWithReducer) +dispatch(Increment(1)) + +let (value, setValue) = Atom.use(primitiveAtom) +setValue(prev => prev + 1) +``` +*/ +module Actions = { + type t<'action> + type set<'value> = t<('value => 'value) => unit> + type update<'value> = t<'value => unit> + type dispatch<'action> = t<'action => unit> + type none = t +} + +/** Atoms have a value, a setter function (from `Atom.Actions`), and a set of +tags that restrict which operations are allowed on the atom (e.g is the atom `#resettable`). +Normally the type will be inferred automatically. If annotation is required it should be +sufficient to provide the first type (the value). + +```rescript +let atom: Jotai.Atom.t, [#readable | #writable | #...]> = Jotai.Atom.make(1) +``` +*/ +type t<'value, 'action, 'tags> + constraint 'tags = [< Tags.all] constraint 'action = Actions.t<'setValue> + +type set<'value, 'action, 'tags> = t<'value, 'action, 'tags> constraint 'tags = [> Tags.w] + +type get<'value, 'action, 'tags> = t<'value, 'action, 'tags> constraint 'tags = [> Tags.r] + +type getter = {get: 'value 'action 'tags. get<'value, Actions.t<'action>, 'tags> => 'value} + +type setter = { + get: 'value 'action 'tags. get<'value, Actions.t<'action>, 'tags> => 'value, + set: 'value 'setValue 'action 'tags. (set<'value, Actions.t<'action>, 'tags>, 'setValue) => unit, +} + +type getValue<'value> = getter => 'value +type setValue<'args> = (setter, 'args) => unit +type setValueAsync<'args> = (setter, 'args) => promise + +// ATOMS +/** Create a (primitive) readable and writable atom (config). +CAUTION: Don't pass a function as argument to `Atom.make`. That would implicitly create a computed atom +and the compiler will produce weird types. Use `Atom.makeComputed` for that. + +```rescript +let atom1 = Jotai.Atom.make(1) +let atom2 = Jotai.Atom.make(["text"]) +// DON'T do this: +let atom3 = Jotai.Atom.make(() => 1) +``` +*/ +@module("jotai") +external make: 'value => t<'value, Actions.set<'value>, [Tags.r | Tags.w | Tags.p]> = "atom" + +/** Create a readonly atom from a function. The function can be async. + +```rescript +let atom1 = Jotai.Atom.makeThunk(async () => 1) +// shorthand for +let atom2 = Jotai.Atom.makeComputed(async ({get}) => 1) +``` +*/ +@module("jotai") +external makeThunk: (unit => 'value) => t<'value, Actions.none, [#readable]> = "atom" + +@module("jotai") @deprecated("[DEPRECATED] No longer needed. Use `Atom.makeThunk` instead.") +external makeAsync: (unit => promise<'value>) => t, Actions.none, [#readable]> = + "atom" + +/** Create a computed readonly atom. A computed atom can combine any number of +readable atoms to create a single derived value. The syntax varies slightly from Jotai. +Note the curly braces in `({get})`. +Requires `React.Suspense` or `Utils.Loadable` if the value is a promise (e.g. the getter is async) + +```rescript +let atom1 = Jotai.Atom.make(1) +let atom2 = Jotai.Atom.makeComputed(({get}) => get(atom1) + 1) +let atom3 = Jotai.Atom.makeComputed(({get}) => get(atom1) + get(atom2) + 1) + +// using async atoms requires React.Suspense +let atom4 = Jotai.Atom.makeComputed(async ({get}) => await get(asyncAtom) + 1) +``` +*/ +@module("./wrapper") +external makeComputed: getValue<'value> => t<'value, Actions.none, [#readable]> = "atomWrapped" + +/** (Requires React.Suspense or Utils.Loadable) Create a computed readonly atom with an async getter. It is +possible to combine sync and async atoms. All components will be notified when the returned promise resolves. + +```rescript +let atom1 = Jotai.Atom.makeAsync(() => 1) +let atom2 = Jotai.Atom.makeComputedAsync(async ({get}) => {await get(atom1) + 2}) +``` +*/ +@module("./wrapper") +@deprecated("[DEPRECATED] No longer needed. Use `Atom.makeComputed` instead.") +external makeComputedAsync: getValue<'value> => t<'value, Actions.none, [#readable]> = "atomWrapped" + +/** Create a computed atom that supports read and write. The getter may by async, +but the setter must be synchronous. For async setters use `makeComputedAsync`. + +```rescript +let atom1 = Jotai.Atom.make(1) +let atom2 = Jotai.Atom.makeWritableComputed( + ({get}) => get(atom1) + 1, + ({get, set}, arg) => { + atom1->set(get(atom1) + arg) + }, +) +``` +*/ +@module("./wrapper") +external makeWritableComputed: ( + getValue<'value>, + setValue<'args>, +) => t<'value, Actions.update<'args>, [Tags.r | Tags.w]> = "atomWrapped" + +/** Create a computed atom with asynchronous write (setter). Jotai supports async write +operations for computed atoms. Simply call 'set' when the promise resolves. + +```rescript +let atom1 = Jotai.Atom.make(1) +let atom2 = Jotai.Atom.makeWritableComputedAsync( + ({get}) => get(atom1) + 1, + async ({get, set}, arg) => { + // do async stuff + set(atom1, get(atom1) + arg) + }, +) +``` +*/ +@module("./wrapper") +external makeWritableComputedAsync: ( + getValue<'value>, + setValueAsync<'args>, +) => t<'value, Actions.update<'args>, [Tags.r | Tags.w]> = "atomWrapped" + +@module("./wrapper") +external _makeWOC: ( + Js.Nullable.t, + setValue<'args>, +) => t<'value, Actions.update<'args>, [Tags.w]> = "atomWrapped" + +@module("./wrapper") +external _makeWOCAsync: ( + Js.Nullable.t, + setValueAsync<'args>, +) => t<'value, Actions.update<'args>, [Tags.w]> = "atomWrapped" + +/** Create a writeOnly computed atom. (Note: Sometimes the type can not be inferred +automatically and has to be annotated) + +```rescript +let atom1 = make(1) +let atom2: Jotai.Atom.t = Jotai.Atom.makeWriteOnlyComputed(({get, set}, args) => + atom1->set(get(atom1) + args) +) +``` +*/ +@deprecated("[DEPRECATED] Use `Atom.makeWriteOnly` instead.") +let makeWriteOnlyComputed = getSet => _makeWOC(Js.Nullable.null, getSet) + +/** Create a writeOnly computed atom. (Note: Sometimes the type can not be inferred +automatically and has to be annotated) + +```rescript +let atom1 = make(1) +let atom2: Jotai.Atom.t = Jotai.Atom.makeWriteOnly(({get, set}, args) => + atom1->set(get(atom1) + args) +) +``` +*/ +let makeWriteOnly = getSet => _makeWOC(Js.Nullable.null, getSet) + +/** Create a writeOnly computed async atom (i.e. the setter is an async function) + +```rescript +let atom1 = make(1) +let atom2 = Jotai.Atom.makeWriteOnlyAsync(async ({get, set}, args) => atom1->set(get(atom1) + args)) + +``` +*/ +let makeWriteOnlyAsync = getSet => _makeWOCAsync(Js.Nullable.null, getSet) + +// HOOKS +/** Standard hook to use with read/write synchronous atoms. +(For handling of readOnly/writeOnly atoms see `Jotai.useAtomValue` or `Jotai.useSetAtom`) + +```rescript +let atom1 = Jotai.Atom.make(1) +let (value, setValue) = Jotai.Atom.useAtom(atom1) +``` +*/ +@module("jotai") +external useAtom: t<'value, Actions.t<'action>, [> Tags.r | Tags.w]> => ('value, 'action) = + "useAtom" + +/** Standard hook to use with read/write async atoms (i.e. all atoms that contain a promise). +(For handling of readOnly/writeOnly atoms see `Jotai.useAtomValueAsync` or `Jotai.useSetAtom`) + +```rescript +let atom1 = Jotai.Atom.makeAsync(async () => 1) +let (value, setValue) = Jotai.Atom.useAtomAsync(atom1) +``` +*/ +@module("jotai") +external useAtomAsync: t, Actions.t<'action>, [> Tags.r | Tags.w]> => ( + 'value, + 'action, +) = "useAtom" + +@deprecated("[DEPRECATED] Use `Atom.useAtom` instead.") @module("jotai") +external use: t<'value, Actions.t<'action>, [> Tags.r | Tags.w]> => ('value, 'action) = "useAtom" + +type setAtom<'value> = ('value => 'value) => unit +type onUnmount = unit => unit +/** `onMount` is a function which takes a function setAtom and returns `onUnmount` function +optionally. The `onMount` function is called when the atom is first used in a provider, and `onUnmount` is +called when it’s no longer used. + +```rescript +let atom1 = Jotai.Atom.make(1) +atom1->Jotai.Atom.onMount(setAtom => { + setAtom(a => a + 1) // increment count on mount + () => () // return onUnmount function +}) +``` +*/ +@set +external onMount: (t<'value, _, [> Tags.w]>, setAtom<'value> => onUnmount) => unit = "onMount" + +// useUpdateAtom +/** A hook that returns only the update function of an atom. Can be used to access writeOnly atoms. + +```rescript +let atom = Jotai.Atom.make(1) +let setValue = Jotai.Atom.useSetAtom(atom) +setValue(prev => prev + 1) +``` +*/ +@module("jotai") +external useSetAtom: t<'value, Actions.t<'action>, [> Tags.w]> => 'action = "useSetAtom" + +// useAtomValue +/** A hook that returns only the value of a synchronous atom. Can be used to access readOnly atoms. + +```rescript +let atom = Jotai.Atom.make(1) +let value = Jotai.Atom.useAtomValue(atom) +``` +*/ +@module("jotai") +external useAtomValue: t<'value, _, [> Tags.r]> => 'value = "useAtomValue" + +/** A hook that returns only the value of an async atom (i.e. the atom contains a promise). +Can be used to access readOnly async atoms. + +```rescript +let atom = Jotai.Atom.make(1) +let value = Jotai.Atom.useAtomValue(atom) +``` +*/ +@module("jotai") +external useAtomValueAsync: t, _, [> Tags.r]> => 'value = "useAtomValue" diff --git a/drafts/rescript-jotai/src/Atom.resi b/drafts/rescript-jotai/src/Atom.resi new file mode 100644 index 0000000..a3b996f --- /dev/null +++ b/drafts/rescript-jotai/src/Atom.resi @@ -0,0 +1,67 @@ +module Tags: { + type r = [#readable] + type w = [#writable] + type p = [#primitive] + type re = [#resettable] + type all = [#primitive | #readable | #resettable | #writable] +} +type none +module Actions: { + type t<'action> + type set<'value> = t<('value => 'value) => unit> + type update<'value> = t<'value => unit> + type dispatch<'action> = t<'action => unit> + type none = t +} +type t<'value, 'action, 'tags> + constraint 'tags = [< Tags.all] constraint 'action = Actions.t<'setValue> +type set<'value, 'action, 'tags> = t<'value, 'action, 'tags> constraint 'tags = [> Tags.w] +type get<'value, 'action, 'tags> = t<'value, 'action, 'tags> constraint 'tags = [> Tags.r] +type getter = {get: 'value 'action 'tags. get<'value, Actions.t<'action>, 'tags> => 'value} +type setter = { + get: 'value 'action 'tags. get<'value, Actions.t<'action>, 'tags> => 'value, + set: 'value 'setValue 'action 'tags. (set<'value, Actions.t<'action>, 'tags>, 'setValue) => unit, +} +type getValue<'value> = getter => 'value +type setValue<'args> = (setter, 'args) => unit +type setValueAsync<'args> = (setter, 'args) => promise +@module("jotai") +external make: 'value => t<'value, Actions.set<'value>, [Tags.r | Tags.w | Tags.p]> = "atom" +@module("jotai") +external makeThunk: (unit => promise<'value>) => t, Actions.none, [#readable]> = + "atom" +@module("jotai") +external makeAsync: (unit => promise<'value>) => t, Actions.none, [#readable]> = + "atom" +let makeComputed: getValue<'value> => t<'value, Actions.none, Tags.r> +let makeComputedAsync: getValue<'value> => t<'value, Actions.none, Tags.r> +let makeWritableComputed: ( + getValue<'value>, + setValue<'args>, +) => t<'value, Actions.update<'args>, [Tags.r | Tags.w]> +let makeWritableComputedAsync: ( + getValue<'value>, + setValueAsync<'args>, +) => t<'value, Actions.update<'args>, [#readable | #writable]> +let makeWriteOnlyComputed: setValue<'a> => t<'b, Actions.update<'a>, Tags.w> +let makeWriteOnly: setValue<'a> => t<'b, Actions.update<'a>, Tags.w> +let makeWriteOnlyAsync: setValueAsync<'a> => t<'b, Actions.update<'a>, Tags.w> +@module("jotai") +external use: t<'value, Actions.t<'action>, [> Tags.r | Tags.w]> => ('value, 'action) = "useAtom" +@module("jotai") +external useAtom: t<'value, Actions.t<'action>, [> Tags.r | Tags.w]> => ('value, 'action) = + "useAtom" +@module("jotai") +external useAtomAsync: t, Actions.t<'action>, [> Tags.r | Tags.w]> => ( + 'value, + 'action, +) = "useAtom" +type setAtom<'value> = ('value => 'value) => unit +type onUnmount = unit => unit +let onMount: (t<'value, _, [> Tags.w]>, setAtom<'value> => onUnmount) => unit +@module("jotai") +external useSetAtom: t<'value, Actions.t<'action>, [> Tags.w]> => 'action = "useSetAtom" +@module("jotai") +external useAtomValue: t<'value, _, [> Tags.r]> => 'value = "useAtomValue" +@module("jotai") @module("jotai") +external useAtomValueAsync: t, _, [> Tags.r]> => 'value = "useAtomValue" diff --git a/drafts/rescript-jotai/src/Provider.res b/drafts/rescript-jotai/src/Provider.res new file mode 100644 index 0000000..0d9d980 --- /dev/null +++ b/drafts/rescript-jotai/src/Provider.res @@ -0,0 +1,20 @@ +/** A Provider works just like React context provider. If you don't use a +Provider, it works as provider-less mode with a default store. A Provider will +be necessary if we need to hold different atom values for different component +trees. The store property is optional. + +```rescript +let store = Jotai.Store.make() + +module App = { + @react.component + let make = () => + + ... + +} +``` +*/ +@module("jotai") +@react.component +external make: (~children: React.element, ~store: Store.t=?) => React.element = "Provider" diff --git a/drafts/rescript-jotai/src/Store.res b/drafts/rescript-jotai/src/Store.res new file mode 100644 index 0000000..900e54c --- /dev/null +++ b/drafts/rescript-jotai/src/Store.res @@ -0,0 +1,67 @@ +type t + +/** Creates a store. + +```rescript +let store = Jotai.Store.make(); +``` +*/ +@module("jotai") +external make: unit => t = "createStore" + +/** Get the default store that is created if no specific store was provided. + +```rescript +let store = Jotai.Store.getDefaultStore(); +``` +*/ +@module("jotai") +external getDefaultStore: unit => t = "getDefaultStore" + +type unitToUnitFunc = unit => unit +type get<'value, 'action, 'tags> = Atom.t<'value, 'action, 'tags> => 'value +type set<'value, 'action, 'tags> = (Atom.t<'value, 'action, 'tags>, 'value) => unit +type sup<'value, 'action, 'tags> = ( + Atom.t<'value, 'action, 'tags>, + unitToUnitFunc, +) => unitToUnitFunc + +/** Returns the value of a given atom from the store. + +```rescript +let value = Jotai.Store.get(store, atom); +``` +*/ +@get +external get: t => get<'value, _, _> = "get" + +/** Sets a new value of a given atom in the store. + +```rescript +Jotai.Store.set(store, atom, 1); +``` +*/ +@get +external set: t => set<'value, _, _> = "set" + +/** Subscripe to changes of a given atom in the store. Returns a function to unsubscribe. + +```rescript +let unsub = Jotai.Store.sub(store, atom, () => { + Js.Console.log2("atom value is changed to", Jotai.Store.get(store, atom)) +}) + +// unsub() to unsubscribe +``` +*/ +@get +external sub: t => sup<_, _, _> = "sub" + +/** This hook returns a store within the component tree. + +```rescript +let store = Jotai.Store.useStore() +``` +*/ +@module("Jotai") +external useStore: unit => t = "useStore" diff --git a/drafts/rescript-jotai/src/Utils.res b/drafts/rescript-jotai/src/Utils.res new file mode 100644 index 0000000..31c8e52 --- /dev/null +++ b/drafts/rescript-jotai/src/Utils.res @@ -0,0 +1,23 @@ +module AtomWithStorage = Utils_AtomWithStorage + +module AtomWithDefault = Utils_AtomWithDefault + +module AtomWithReset = Utils_AtomWithReset + +module AtomWithReducer = Utils_AtomWithReducer + +module AtomFamily = Utils__AtomFamily + +module Loadable = Utils__Loadable + +include Utils__Hooks + +// TODO: Add missing utils +// - atomWithObservable +// - atomWithHash +// - selectAtom +// - useAtomCallback +// - freezeAtom +// - splitAtom +// - waitForAll +// - useHydrateAtoms diff --git a/drafts/rescript-jotai/src/utils/Utils_AtomWithDefault.res b/drafts/rescript-jotai/src/utils/Utils_AtomWithDefault.res new file mode 100644 index 0000000..5b2c6d6 --- /dev/null +++ b/drafts/rescript-jotai/src/utils/Utils_AtomWithDefault.res @@ -0,0 +1,17 @@ +/** Create a resettable, writable atom. Its default value can be specified +with a read function instead of an initial value. This function support sync and async getters. + +```rescript +let atom1 = Jotai.Atom.make(1) +let atom2 = Jotai.Utils.AtomWithDefault.make(({get}) => atom1->get + 1) +// async +let atom3 = Jotai.Atom.makeAsync(async () =>1) +let atom4 = Jotai.Utils.AtomWithDefault.make(async({get}) => await atom3->get + 1) +``` +*/ +@module("../wrapper") +external make: Atom.getValue<'value> => Atom.t< + 'value, + Atom.Actions.set<'value>, + [Atom.Tags.r | Atom.Tags.w | Atom.Tags.re], +> = "atomWithDefaultWrapped" diff --git a/drafts/rescript-jotai/src/utils/Utils_AtomWithReducer.res b/drafts/rescript-jotai/src/utils/Utils_AtomWithReducer.res new file mode 100644 index 0000000..3047bd3 --- /dev/null +++ b/drafts/rescript-jotai/src/utils/Utils_AtomWithReducer.res @@ -0,0 +1,23 @@ +type reducer<'value, 'action> = ('value, 'action) => 'value +type dispatch<'action> = 'action => unit + +/** Creates an atom that uses a reducer to update its value. + +```rescript +type actionType = Inc(int) | Dec(int) +let countReducer = (prev, action) => { + switch action { + | Inc(num) => prev + num + | Dec(num) => prev - num + } +} +let atom = Utils.AtomWithReducer.make(0, countReducer) +let (value, dispatch) = Atom.use(atom) +Inc(1)->dispatch +``` +*/ +@module("jotai/utils") +external make: ( + 'value, + reducer<'value, 'action>, +) => Atom.t<'value, Atom.Actions.dispatch<'action>, [Atom.Tags.r | Atom.Tags.w]> = "atomWithReducer" diff --git a/drafts/rescript-jotai/src/utils/Utils_AtomWithReset.res b/drafts/rescript-jotai/src/utils/Utils_AtomWithReset.res new file mode 100644 index 0000000..45d74df --- /dev/null +++ b/drafts/rescript-jotai/src/utils/Utils_AtomWithReset.res @@ -0,0 +1,15 @@ +/** Creates an atom that can be reset to its initialValue with the `useResetAtom` hook. + +```rescript +let atom = Jotai.Utils.AtomWithReset.make(1) +// ... change value ... +let reset = Jotai.Utils.useResetAtom(atom) +reset() +``` +*/ +@module("jotai/utils") +external make: 'value => Atom.t< + 'value, + Atom.Actions.set<'value>, + [Atom.Tags.r | Atom.Tags.w | Atom.Tags.p | Atom.Tags.re], +> = "atomWithReset" diff --git a/drafts/rescript-jotai/src/utils/Utils_AtomWithStorage.res b/drafts/rescript-jotai/src/utils/Utils_AtomWithStorage.res new file mode 100644 index 0000000..80b6be6 --- /dev/null +++ b/drafts/rescript-jotai/src/utils/Utils_AtomWithStorage.res @@ -0,0 +1,17 @@ +// TODO Add support for additional storage options +/** Creates an atom with a value persisted in `localStorage` +Currently only `localStorage` is supported. + +```rescript +let atom1 = Jotai.Utils.AtomWithStorage.make('storageKey', 1) +``` +*/ +@module("jotai/utils") +external make: ( + string, + 'value, +) => Atom.t<'value, Atom.Actions.set<'value>, [Atom.Tags.r | Atom.Tags.w | Atom.Tags.p]> = + "atomWithStorage" + +@module("jotai/utils") +external reset: 'reset = "RESET" diff --git a/drafts/rescript-jotai/src/utils/Utils__AtomFamily.res b/drafts/rescript-jotai/src/utils/Utils__AtomFamily.res new file mode 100644 index 0000000..362929d --- /dev/null +++ b/drafts/rescript-jotai/src/utils/Utils__AtomFamily.res @@ -0,0 +1,57 @@ +type t<'param, 'action, 'tags> = 'param => Atom.t<'param, 'action, 'tags> + +/** Creates an atomFamily. If the compiler has trouble inferring the type, +it is recommended to annotate the type directly on the function param. + +```rescript +let atomFamily = Jotai.Utils.AtomFamily.make((name: string) => Jotai.Atom.make(name)) +let atom = atomFamily("text") +``` +*/ +@module("jotai/utils") +external make: t<'param, 'action, 'tags> => t<'param, 'action, 'tags> = "atomFamily" + +/** Creates an atomFamily with a supplied comparison function + +```rescript +let atomFamWithEqual = Jotai.Utils.AtomFamily.makeWithEqual( + name => Jotai.Atom.make(name), + (strA, strB) => strA == strB, +) +``` +*/ +@module("jotai/utils") +external makeWithEqual: ( + t<'param, 'action, 'tags>, + ('param, 'param) => bool, +) => t<'param, 'action, 'tags> = "atomFamily" + +/** Removes an atom from an atomFamily. + +```rescript +Jotai.Utils.AtomFamily.remove(atomFamily, \"text\") +``` +*/ +@set +external remove: (t<'param, 'action, 'tags>, 'param) => unit = "remove" + +/** Registers a shouldRemove function. + +```rescript +let shouldRemove = (createdAt, param) => param == \"test\" +Jotai.Utils.AtomFamily.setShouldRemove(atomFamily, shouldRemove) +``` +*/ +@set +external setShouldRemove: (t<'param, 'action, 'tags>, (int, 'param) => bool) => unit = + "setShouldRemove" + +/** Unregisters the shouldRemove function. + +```rescript +Jotai.Utils.AtomFamily.setShouldRemoveUnregister(atomFamily, Js.Null.empty) +``` +*/ +@set +external setShouldRemoveUnregister: (t<'param, 'action, 'tags>, Js.Null.t<'null>) => unit = + "setShouldRemove" diff --git a/drafts/rescript-jotai/src/utils/Utils__Hooks.res b/drafts/rescript-jotai/src/utils/Utils__Hooks.res new file mode 100644 index 0000000..4836244 --- /dev/null +++ b/drafts/rescript-jotai/src/utils/Utils__Hooks.res @@ -0,0 +1,42 @@ +// useUpdateAtom +let useUpdateAtom = failwith("Moved to core. Use `Atom.useSetAtom` instead") + +// useAtomValue +let useAtomValue = failwith("Moved to core. Use `Atom.useAtomValue` instead") + +// useResetAtom +/** Returns a function that can be used to reset a resettable atom. + +```rescript +let atom = Jotai.Utils.AtomWithReset(1) // value: 1 +let (_, setValue) = Jotai.Atom.use(atom) +setValue(2) // value: 2 +let resetValue = Jotai.Utils.useResetAtom(atom) +resetValue() // value back to: 1 +``` +*/ +type reset = unit => unit +@module("jotai/utils") +external useResetAtom: Atom.t<'value, _, [> Atom.Tags.re]> => reset = "useResetAtom" + +// useReducerAtom +/* Allows to use a reducer function with a primitive atom. + +```rescript +type actionType = Inc(int) | Dec(int) +let countReducer = (prev, action) => { + switch action { + | Inc(num) => prev + num + | Dec(num) => prev - num + } +} +let atom = Jotai.Atom.make(0) +let (value, dispatch) = Jotai.Utils.useReducerAtom(atom, countReducer) +Inc(1)->dispatch +``` +*/ +@module("jotai/utils") +external useReducerAtom: ( + Atom.t<'value, _, [> Atom.Tags.p]>, + Utils_AtomWithReducer.reducer<'value, 'action>, +) => ('value, Utils_AtomWithReducer.dispatch<'action>) = "useReducerAtom" diff --git a/drafts/rescript-jotai/src/utils/Utils__Loadable.res b/drafts/rescript-jotai/src/utils/Utils__Loadable.res new file mode 100644 index 0000000..d154c43 --- /dev/null +++ b/drafts/rescript-jotai/src/utils/Utils__Loadable.res @@ -0,0 +1,55 @@ +type t<'data> = { + state: [#loading | #hasError | #hasData], + data: option<'data>, + error: option, +} + +// type component<'data> = ( +// t<'data>, +// React.element, +// string => React.element, +// 'data => React.element, +// ) => React.element + +// let createComponent: component<'data> = ( +// loadable, +// loadingComponent, +// errorComponent, +// dataComponent, +// ) => { +// switch (loadable.state, loadable.error, loadable.data) { +// | (#hasError, Some(e), _) => errorComponent(e) +// | (#hasData, _, Some(d)) => dataComponent(d) +// | _ => loadingComponent +// } +// } + +/** Can be used if you don't want async atoms to suspend or throw to an error boundary. + +```rescript +let atom = Jotai.Atom.makeThunk(async () => "stuff") +let loadableAtom = Jotai.Utils.Loadable.make(atom) + +// inside component: +let value = Jotai.Utils.Loadable.useLoadableValue(loadableAtom) +{switch (value.state, value.data, value.error) { + | (#hasData, Some(d), _) =>

{("Data: " ++ d)->React.string}

+ | (#hasError, _, Some(e)) =>

{e->React.string}

+ | _ =>

{"Loading..."->React.string}

+}} +``` +*/ +@module("jotai/utils") +external make: Atom.t, _, 'tags> => Atom.t, Atom.Actions.none, 'tags> = + "loadable" + +/** Hook to use a loadable atom. + +```rescript +// inside component: +let value = Jotai.Utils.Loadable.useLoadableValue(loadableAtom) +``` + +*/ +@module("jotai") +external useLoadableValue: Atom.t, _, _> => t<'data> = "useAtomValue" diff --git a/drafts/rescript-jotai/src/wrapper.js b/drafts/rescript-jotai/src/wrapper.js new file mode 100644 index 0000000..d3a4d12 --- /dev/null +++ b/drafts/rescript-jotai/src/wrapper.js @@ -0,0 +1,58 @@ +const { atom } = require("jotai"); +const { atomWithDefault } = require("jotai/utils"); + +/** + * The compiler stores a function type at time of definition and not application. + * This can result in problems when using higher-rank polymorphic functions. Ocaml + * provides two ways of handling these scenarios, and luckily they work in ReScript too. + * We use "universally quantified record fields" here. To accomplish this, the 'get' and 'set' functions + * are wrapped in a record. + * (See: https://ocaml.org/manual/polymorphism.html#s%3Ahigher-rank-poly) + * @param {*} getFunc + * @param {*} writeFunc + * @returns an atom config + */ +exports.atomWrapped = (getFunc, writeFunc) => { + return atom( + (get) => { + return getFunc({ get }); + }, + (get, set, args) => { + // TODO The get function used by Jotai is of type WriteGetter that has an optional parameter + // that is only used internally. But it messes up the Curry._1 function used by rescript. Keeping the + // optional parameter would make the function signature unnecessarily complex. + const getWithoutOptions = (a) => get(a, undefined); + // TODO The original function parameters are defined as (a, ..args). Therefore fn.length = 1. This results in + // the Curr._2 function applied here to mess up. This function makes sure that the arity is always at least 2. + const setWithArity2 = (a1, a2, ...args) => { + set(a1, ...[a2, ...args]); + }; + writeFunc( + { get: getWithoutOptions, set: setWithArity2, dispatch: set }, + args + ); + } + ); +}; +/** + * There is no way to check at compile time, if an atom was created with a function as argument. + * So a warning is logged instead. + * @param {*} val + * @returns an atom config + */ +exports.atomWarn = (val) => { + if (typeof val === "function") { + console.warn( + "Calling Atom.make with a function as argument is not allowed. Use Atom.makeComputed instead." + ); + } + return atom(val); +}; + +exports.something = undefined; + +exports.atomWithDefaultWrapped = (getFunc) => { + return atomWithDefault((get) => { + return getFunc({ get }); + }); +}; diff --git a/drafts/rescript-jotai/src/wrapper.mjs b/drafts/rescript-jotai/src/wrapper.mjs new file mode 100644 index 0000000..863f63b --- /dev/null +++ b/drafts/rescript-jotai/src/wrapper.mjs @@ -0,0 +1,60 @@ +import { atom } from "jotai"; +import { atomWithDefault } from "jotai/utils"; + +/** + * The compiler stores a function type at time of definition and not application. + * This can result in problems when using higher-rank polymorphic functions. Ocaml + * provides two ways of handling these scenarios, and luckily they work in ReScript too. + * We use "universally quantified record fields" here. To accomplish this, the 'get' and 'set' functions + * are wrapped in a record. + * (See: https://ocaml.org/manual/polymorphism.html#s%3Ahigher-rank-poly) + * @param {*} getFunc + * @param {*} writeFunc + * @returns + */ +const atomWrapped = (getFunc, writeFunc) => { + return atom( + (get) => { + return getFunc({ get }); + }, + (get, set, args) => { + // TODO The get function used by Jotai is of type WriteGetter that has an optional parameter + // that is only used internally. But it messes up the Curry._1 function used by rescript. Keeping the + // optional parameter would make the function signature unnecessarily complex. + const getWithoutOptions = (a) => get(a, undefined); + // TODO The original function parameters are defined as (a, ..args). Therefore fn.length = 1. This results in + // the Curr._2 function applied here to mess up. This function makes sure that the arity is always at least 2. + const setWithArity2 = (a1, a2, ...args) => { + set(a1, ...[a2, ...args]); + }; + writeFunc( + { get: getWithoutOptions, set: setWithArity2, dispatch: set }, + args + ); + } + ); +}; +/** + * There is no way to check at compile time, if an atom was created with a function as argument. + * So a warning is logged instead. + * @param {*} val + * @returns an atom config + */ +const atomWarn = (val) => { + if (typeof val === "function") { + console.warn( + "Calling Atom.make with a function as argument is not allowed. Use Atom.makeComputed instead." + ); + } + return atom(val); +}; + +const something = undefined; + +const atomWithDefaultWrapped = (getFunc) => { + return atomWithDefault((get) => { + return getFunc({ get }); + }); +}; + +export { atomWrapped, atomWithDefaultWrapped, atomWarn, something }; diff --git a/drafts/rescript-jotai/test/Atom_test.res b/drafts/rescript-jotai/test/Atom_test.res new file mode 100644 index 0000000..0f0a37a --- /dev/null +++ b/drafts/rescript-jotai/test/Atom_test.res @@ -0,0 +1,97 @@ +open Jest +open Expect +open TestingLibrary.React + +test("standard atom", () => { + let a = Atom.make(1) + let {result} = renderHook(() => Atom.use(a)) + let (value, setValue) = result.current + + expect(value)->toBe(1) + + act(() => setValue(p => p + 1)) + let (value, _) = result.current + expect(value)->toBe(2) +}) + +// test("async read atom", () => { +// act(() =>{ +// let a = Atom.makeAsync(async () => {1}) +// let {result} = renderHook(() => Atom.use(a)) +// let (value, _) = result.current + +// expect(value)->toBe(1) +// }) +// }) + +test("computed readonly atom", () => { + let a = Atom.make(1) + let c = Atom.makeComputed(({get}) => get(a) + 1) + let {result: r1} = renderHook(() => Atom.use(a)) + let {result: r2} = renderHook(() => Atom.useAtomValue(c)) + + expect(r2.current)->toBe(2) + let (_, setValue) = r1.current + + act(() => setValue(p => p + 1)) + expect(r2.current)->toBe(3) +}) + +test("computed writable atom", () => { + let a = Atom.make(1) + let rw = Atom.makeWritableComputed( + ({get}) => a->get + 1, + ({get, set}, arg) => { + a->set(a->get + arg) + }, + ) + let {result} = renderHook(() => Atom.use(rw)) + let (value, addValue) = result.current + expect(value)->toBe(2) + + act(() => addValue(1)) + let (value, _) = result.current + expect(value)->toBe(3) +}) + +// testPromise("async readonly atom", async () => { +// let a = Atom.make(1) +// let c = Atom.makeComputedAsync(({get}) => Js.Promise.resolve(get(a) + 1)) +// act(() => { +// let {result} = renderHook(() => Atom.useAtomValue(c), ()) +// t->equal(result.current, 2, "should be 2") +// }) +// done() +// }) + +test("computed writeonly atom", () => { + let a = Atom.make(1) + let b: Atom.t = Atom.makeWriteOnlyComputed(({set, _}, id) => { + a->set(id) + }) + let {result} = renderHook(() => Atom.useSetAtom(b)) + let setValue = result.current + act(() => setValue(2)) + let {result} = renderHook(() => Atom.use(a)) + let (value, _) = result.current + expect(value)->toBe(2) +}) + +test("useSetAtom hook", () => { + let a = Atom.make(1) + let {result} = renderHook(() => Atom.useSetAtom(a)) + let setValue = result.current + act(() => setValue(p => p + 1)) + let {result} = renderHook(() => Atom.use(a)) + let (value, _) = result.current + expect(value)->toBe(2) +}) + +test("useAtomValue hook", () => { + let a = Atom.make(1) + let {result} = renderHook(() => Atom.use(a)) + let (_, setValue) = result.current + act(() => setValue(p => p + 1)) + let {result} = renderHook(() => Atom.useAtomValue(a)) + expect(result.current)->toBe(2) +}) diff --git a/drafts/rescript-jotai/test/Utils_test.res b/drafts/rescript-jotai/test/Utils_test.res new file mode 100644 index 0000000..b499a9b --- /dev/null +++ b/drafts/rescript-jotai/test/Utils_test.res @@ -0,0 +1,100 @@ +open Jest +open Expect +open TestingLibrary.React + +@val external window: 'w = "window" + +test("AtomWithStorage", () => { + let a = Utils.AtomWithStorage.make("mykey", 1) + let {result} = renderHook(() => Atom.use(a)) + let (value, setValue) = result.current + expect(value)->toBe(1) + act(() => setValue(p => p + 1)) + let fromStorage = + window["localStorage"]["getItem"]("mykey")->Belt.Int.fromString->Belt.Option.getUnsafe + expect(fromStorage)->toBe(2) +}) + +test("AtomWithDefault", () => { + let a = Atom.make(1) + let b = Utils.AtomWithDefault.make(({get}) => a->get + 1) + let {result} = renderHook(() => Atom.use(b)) + let (value, setValue) = result.current + expect(value)->toBe(2) + act(() => setValue(_ => 1)) + let (value, _) = result.current + expect(value)->toBe(1) + let {result} = renderHook(() => Utils.useResetAtom(b)) + act(() => result.current()) + let {result} = renderHook(() => Atom.use(b)) + let (value, _) = result.current + expect(value)->toBe(2) +}) + +test("AtomWithReset", () => { + let a = Utils.AtomWithReset.make(1) + let {result} = renderHook(() => Atom.use(a)) + let (value, setValue) = result.current + expect(value)->toBe(1) + act(() => setValue(p => p + 1)) + let (value, _) = result.current + expect(value)->toBe(2) + let {result} = renderHook(() => Utils.useResetAtom(a)) + act(() => result.current()) + let {result} = renderHook(() => Atom.use(a)) + let (value, _) = result.current + expect(value)->toBe(1) +}) + +type actionType = Inc(int) | Dec(int) + +test("AtomWithReducer", () => { + let countReducer = (prev, action) => { + switch action { + | Inc(num) => prev + num + | Dec(num) => prev - num + } + } + let a = Utils.AtomWithReducer.make(0, countReducer) + let {result} = renderHook(() => Atom.use(a)) + let (value, dispatch) = result.current + expect(value)->toBe(0) + act(() => Inc(1)->dispatch) + let (value, _) = result.current + expect(value)->toBe(1) +}) + +test("useReducerAtom hook", () => { + let countReducer = (prev, action) => { + switch action { + | Inc(num) => prev + num + | Dec(num) => prev - num + } + } + let a = Atom.make(1) + let {result} = renderHook(() => Utils.useReducerAtom(a, countReducer)) + let (value, dispatch) = result.current + expect(value)->toBe(1) + act(() => Inc(1)->dispatch) + let (value, _) = result.current + expect(value)->toBe(2) +}) + +test("useUpdateAtom hook", () => { + let a = Atom.make(1) + let {result} = renderHook(() => Atom.useSetAtom(a)) + let setValue = result.current + act(() => setValue(p => p + 1)) + let {result} = renderHook(() => Atom.use(a)) + let (value, _) = result.current + expect(value)->toBe(2) +}) + +test("useAtomValue hook", () => { + let a = Atom.make(1) + let {result} = renderHook(() => Atom.use(a)) + let (_, setValue) = result.current + act(() => setValue(p => p + 1)) + let {result} = renderHook(() => Atom.useAtomValue(a)) + expect(result.current)->toBe(2) +}) diff --git a/drafts/rescript-reanimated/CHANGELOG.md b/drafts/rescript-reanimated/CHANGELOG.md new file mode 100644 index 0000000..6d8b0c5 --- /dev/null +++ b/drafts/rescript-reanimated/CHANGELOG.md @@ -0,0 +1,23 @@ +# Change Log + +All notable changes to this project will be documented in this file. +See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +## 2.3.11 (2022-11-13) + +- Initial support for layout animations with entering and exiting on Text, View, and ScrollView ([davisuga](https://github.com/davisuga)). +- Added support for `rescript-react-native` version `0.70` ([davisuga](https://github.com/davisuga)). + +## 2.3.10 (2022-10-05) + +### Breaking change + +- Renamed the module from `ResAnimated` to `Reanimated`. `Reanimated` should be more appropriate name since this is a binding to react-native-**reanimated** after all. + +## 2.3.9 (2022-10-05) + +- Added `useAnimatedScrollHandler` and `ScrollView` ([davisuga](https://github.com/davisuga)). + +## 2.3.8 (2022-10-03) + +- Updated `rescript` to version `10.0.1` ([davisuga](https://github.com/davisuga)). diff --git a/drafts/rescript-reanimated/LICENSE b/drafts/rescript-reanimated/LICENSE new file mode 100644 index 0000000..4a0ef6b --- /dev/null +++ b/drafts/rescript-reanimated/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 Aleksa Bacovic + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/drafts/rescript-reanimated/README.md b/drafts/rescript-reanimated/README.md new file mode 100644 index 0000000..bf88120 --- /dev/null +++ b/drafts/rescript-reanimated/README.md @@ -0,0 +1,43 @@ +# `rescript-reanimated` + +ReScript bindings for `react-native-reanimated v2`. + +Exposed as `Reanimated` module. + +`rescript-reanimated` `x.y.*` means it's compatible with `react-native-reanimated` `x.y.*` + +## Status + +Work in progress. + +- [x] `Animated.View` + - [ ] Add rest of the `View` props +- [x] `Animated.Text` + - [ ] Add rest of the `Text` props +- [x] `Animated.ScrollView` +- [x] `useAnimatedGestureHandler` +- [x] `runOnUI` +- [x] `runOnJS` +- [x] `useSharedValue` +- [x] `withTiming` +- [x] `useAnimatedStyle` +- [x] `useAnimatedScrollHandler` +- [x] `interpolate` +- [ ] ... +- [ ] ... +- [ ] ... +- [ ] ... + +## Installation + +When `react-native-reanimated` is properly installed and configured by following their installation instructions, you can install the bindings: + +``` +npm install rescript-reanimated +# or +yarn add rescript-reanimated +``` + +## Example + +An example repo is in progress. Until then you can refer to [this issue](https://github.com/reck753/rescript-reanimated/issues/1) for two small examples, or the examples folder. diff --git a/drafts/rescript-reanimated/bsconfig.json b/drafts/rescript-reanimated/bsconfig.json new file mode 100644 index 0000000..283b527 --- /dev/null +++ b/drafts/rescript-reanimated/bsconfig.json @@ -0,0 +1,18 @@ +{ + "name": "rescript-reanimated", + "reason": { "react-jsx": 3 }, + "sources": [ + { + "dir": "src", + "subdirs": true + } + ], + "package-specs": [ + { + "module": "es6", + "in-source": true + } + ], + "suffix": ".bs.js", + "bs-dependencies": ["@rescript/react", "rescript-react-native"] +} diff --git a/drafts/rescript-reanimated/package.json b/drafts/rescript-reanimated/package.json new file mode 100644 index 0000000..67e42bf --- /dev/null +++ b/drafts/rescript-reanimated/package.json @@ -0,0 +1,44 @@ +{ + "name": "rescript-reanimated", + "description": "ReScript bindings for react-native-reanimated v2", + "version": "2.3.11", + "repository": { + "type": "git", + "url": "git+https://github.com/reck753/rescript-reanimated.git" + }, + "publishConfig": { + "access": "public" + }, + "keywords": [ + "rescript", + "react-native", + "react-native-reanimated" + ], + "license": "MIT", + "scripts": { + "format:most": "prettier --write \"**/*.{md,json,js,css}\"", + "format:res": "rescript format -all", + "format:all": "yarn format:most && yarn format:res", + "clean": "rescript clean", + "build": "rescript clean && rescript build -with-deps", + "watch": "rescript clean && rescript build -with-deps -w" + }, + "peerDependencies": { + "@rescript/react": "^0.10.3", + "react-native-reanimated": "~2.3.1", + "rescript-react-native": "^0.64.3 || ^0.65.0 || ^0.66.0 || ^0.70" + }, + "devDependencies": { + "@rescript/react": "^0.10.3", + "prettier": "^2.5.1", + "rescript": "^10.0.1", + "rescript-react-native": "^0.64.3 || ^0.65.0 || ^0.66.0 || ^0.70" + }, + "prettier": { + "trailingComma": "all" + }, + "bugs": { + "url": "https://github.com/reck753/rescript-reanimated/issues" + }, + "homepage": "https://github.com/reck753/rescript-reanimated#readme" +} diff --git a/drafts/rescript-reanimated/src/Reanimated.res b/drafts/rescript-reanimated/src/Reanimated.res new file mode 100644 index 0000000..eef1729 --- /dev/null +++ b/drafts/rescript-reanimated/src/Reanimated.res @@ -0,0 +1,744 @@ +type animationCallback<'t> = (option, option<'t>) => unit +// Reanimated Style is ViewStyle + TextStyle + easing, originX, and originY +type reanimatedStyle +@obj +external reanimatedStyle: ( + ~alignContent: ReanimatedStyle.alignContent=?, + ~alignItems: ReanimatedStyle.alignItems=?, + ~alignSelf: ReanimatedStyle.alignSelf=?, + ~aspectRatio: float=?, + ~backfaceVisibility: ReanimatedStyle.backfaceVisibility=?, + ~backgroundColor: ReactNative.Color.t=?, + ~borderBottomColor: ReactNative.Color.t=?, + ~borderBottomEndRadius: float=?, + ~borderBottomLeftRadius: float=?, + ~borderBottomRightRadius: float=?, + ~borderBottomStartRadius: float=?, + ~borderBottomWidth: float=?, + ~borderColor: ReactNative.Color.t=?, + ~borderEndColor: ReactNative.Color.t=?, + ~borderEndWidth: float=?, + ~borderLeftColor: ReactNative.Color.t=?, + ~borderLeftWidth: float=?, + ~borderRadius: float=?, + ~borderRightColor: ReactNative.Color.t=?, + ~borderRightWidth: float=?, + ~borderStartColor: ReactNative.Color.t=?, + ~borderStartWidth: float=?, + ~borderStyle: ReanimatedStyle.borderStyle=?, + ~borderTopColor: ReactNative.Color.t=?, + ~borderTopEndRadius: float=?, + ~borderTopLeftRadius: float=?, + ~borderTopRightRadius: float=?, + ~borderTopStartRadius: float=?, + ~borderTopWidth: float=?, + ~borderWidth: float=?, + ~bottom: ReanimatedStyle.size=?, + ~direction: ReanimatedStyle.direction=?, + ~display: ReanimatedStyle.display=?, + ~easing: float => float=?, + ~elevation: float=?, + ~end: ReanimatedStyle.size=?, + ~flex: float=?, + ~flexBasis: ReanimatedStyle.margin=?, + ~flexDirection: ReanimatedStyle.flexDirection=?, + ~flexGrow: float=?, + ~flexShrink: float=?, + ~flexWrap: ReanimatedStyle.flexWrap=?, + ~height: ReanimatedStyle.size=?, + ~justifyContent: ReanimatedStyle.justifyContent=?, + ~left: ReanimatedStyle.size=?, + ~margin: ReanimatedStyle.margin=?, + ~marginBottom: ReanimatedStyle.margin=?, + ~marginEnd: ReanimatedStyle.margin=?, + ~marginHorizontal: ReanimatedStyle.margin=?, + ~marginLeft: ReanimatedStyle.margin=?, + ~marginRight: ReanimatedStyle.margin=?, + ~marginStart: ReanimatedStyle.margin=?, + ~marginTop: ReanimatedStyle.margin=?, + ~marginVertical: ReanimatedStyle.margin=?, + ~maxHeight: ReanimatedStyle.size=?, + ~maxWidth: ReanimatedStyle.size=?, + ~minHeight: ReanimatedStyle.size=?, + ~minWidth: ReanimatedStyle.size=?, + ~opacity: float=?, + ~originX: float=?, + ~originY: float=?, + ~overflow: ReanimatedStyle.overflow=?, + ~padding: ReanimatedStyle.size=?, + ~paddingBottom: ReanimatedStyle.size=?, + ~paddingEnd: ReanimatedStyle.size=?, + ~paddingHorizontal: ReanimatedStyle.size=?, + ~paddingLeft: ReanimatedStyle.size=?, + ~paddingRight: ReanimatedStyle.size=?, + ~paddingStart: ReanimatedStyle.size=?, + ~paddingTop: ReanimatedStyle.size=?, + ~paddingVertical: ReanimatedStyle.size=?, + ~position: ReanimatedStyle.position=?, + ~right: ReanimatedStyle.size=?, + ~shadowColor: ReactNative.Color.t=?, + ~shadowOffset: ReanimatedStyle.offset=?, + ~shadowOpacity: float=?, + ~shadowRadius: float=?, + ~start: ReanimatedStyle.size=?, + ~top: ReanimatedStyle.size=?, + ~transform: array=?, + ~width: ReanimatedStyle.size=?, + ~zIndex: int=?, + unit, +) => reanimatedStyle = "" + +module LayoutAnimation = { + type t + module Modifiers = { + @send + external duration: (t, float) => t = "duration" + @send + external delay: (t, float) => t = "delay" + @send + external easing: ('c, t, animationCallback<'c>) => t = "easing" + @send + external springify: t => t = "springify" + @send + external damping: (t, float) => t = "damping" + @send + external mass: (t, float) => t = "mass" + @send + external stiffness: (t, float) => t = "stiffness" + @send + external overshootClamping: (t, bool) => t = "overshootClamping" + @send + external restDisplacementThreshold: (t, float) => t = "restDisplacementThreshold" + @send + external restSpeedThreshold: (t, float) => t = "restSpeedThreshold" + @send + external withCallback: (t, bool => unit) => t = "withCallback" + @send + external withInitialValues: (t, float) => t = "withInitialValues" + @send + external randomDelay: t => t = "randomDelay" + } + include Modifiers + + @module("react-native-reanimated") + external fadeIn: t = "FadeIn" + @module("react-native-reanimated") + external fadeInRight: t = "FadeInRight" + @module("react-native-reanimated") + external fadeInLeft: t = "FadeInLeft" + @module("react-native-reanimated") + external fadeInUp: t = "FadeInUp" + @module("react-native-reanimated") + external fadeInDown: t = "FadeInDown" + @module("react-native-reanimated") + external bounceIn: t = "BounceIn" + @module("react-native-reanimated") + external bounceInRight: t = "BounceInRight" + @module("react-native-reanimated") + external bounceInLeft: t = "BounceInLeft" + @module("react-native-reanimated") + external bounceInUp: t = "BounceInUp" + @module("react-native-reanimated") + external bounceInDown: t = "BounceInDown" + @module("react-native-reanimated") + external flipInYRight: t = "FlipInYRight" + @module("react-native-reanimated") + external flipInYLeft: t = "FlipInYLeft" + @module("react-native-reanimated") + external flipInXUp: t = "FlipInXUp" + @module("react-native-reanimated") + external flipInXDown: t = "FlipInXDown" + @module("react-native-reanimated") + external flipInEasyX: t = "FlipInEasyX" + @module("react-native-reanimated") + external flipInEasyY: t = "FlipInEasyY" + @module("react-native-reanimated") + external stretchInX: t = "StretchInX" + @module("react-native-reanimated") + external stretchInY: t = "StretchInY" + @module("react-native-reanimated") + external zoomIn: t = "ZoomIn" + @module("react-native-reanimated") + external zoomInRotate: t = "ZoomInRotate" + @module("react-native-reanimated") + external zoomInRight: t = "ZoomInRight" + @module("react-native-reanimated") + external zoomInLeft: t = "ZoomInLeft" + @module("react-native-reanimated") + external zoomInUp: t = "ZoomInUp" + @module("react-native-reanimated") + external zoomInDown: t = "ZoomInDown" + @module("react-native-reanimated") + external zoomInEasyUp: t = "ZoomInEasyUp" + @module("react-native-reanimated") + external zoomInEasyDown: t = "ZoomInEasyDown" + @module("react-native-reanimated") + external slideInRight: t = "SlideInRight" + @module("react-native-reanimated") + external slideInLeft: t = "SlideInLeft" + @module("react-native-reanimated") + external slideInUp: t = "SlideInUp" + @module("react-native-reanimated") + external slideInDown: t = "SlideInDown" + @module("react-native-reanimated") + external lightSpeedInRight: t = "LightSpeedInRight" + @module("react-native-reanimated") + external lightSpeedInLeft: t = "LightSpeedInLeft" + @module("react-native-reanimated") + external pinwheelIn: t = "PinwheelIn" + @module("react-native-reanimated") + external rollInLeft: t = "RollInLeft" + @module("react-native-reanimated") + external rollInRight: t = "RollInRight" + @module("react-native-reanimated") + external rotateInDownLeft: t = "RotateInDownLeft" + @module("react-native-reanimated") + external rotateInDownRight: t = "RotateInDownRight" + @module("react-native-reanimated") + external rotateInUpLeft: t = "RotateInUpLeft" + @module("react-native-reanimated") + external rotateInUpRight: t = "RotateInUpRight" +} +include LayoutAnimation + +module KeyFrame = { + type keyframeConfig = { + from?: reanimatedStyle, + to?: reanimatedStyle, + // This looks a little bit stupid, but this has better developer experience and type inference than using a dict/object. + @as("0") _0?: reanimatedStyle, + @as("1") _1?: reanimatedStyle, + @as("2") _2?: reanimatedStyle, + @as("3") _3?: reanimatedStyle, + @as("4") _4?: reanimatedStyle, + @as("5") _5?: reanimatedStyle, + @as("6") _6?: reanimatedStyle, + @as("7") _7?: reanimatedStyle, + @as("8") _8?: reanimatedStyle, + @as("9") _9?: reanimatedStyle, + @as("10") _10?: reanimatedStyle, + @as("11") _11?: reanimatedStyle, + @as("12") _12?: reanimatedStyle, + @as("13") _13?: reanimatedStyle, + @as("14") _14?: reanimatedStyle, + @as("15") _15?: reanimatedStyle, + @as("16") _16?: reanimatedStyle, + @as("17") _17?: reanimatedStyle, + @as("18") _18?: reanimatedStyle, + @as("19") _19?: reanimatedStyle, + @as("20") _20?: reanimatedStyle, + @as("21") _21?: reanimatedStyle, + @as("22") _22?: reanimatedStyle, + @as("23") _23?: reanimatedStyle, + @as("24") _24?: reanimatedStyle, + @as("25") _25?: reanimatedStyle, + @as("26") _26?: reanimatedStyle, + @as("27") _27?: reanimatedStyle, + @as("28") _28?: reanimatedStyle, + @as("29") _29?: reanimatedStyle, + @as("30") _30?: reanimatedStyle, + @as("31") _31?: reanimatedStyle, + @as("32") _32?: reanimatedStyle, + @as("33") _33?: reanimatedStyle, + @as("34") _34?: reanimatedStyle, + @as("35") _35?: reanimatedStyle, + @as("36") _36?: reanimatedStyle, + @as("37") _37?: reanimatedStyle, + @as("38") _38?: reanimatedStyle, + @as("39") _39?: reanimatedStyle, + @as("40") _40?: reanimatedStyle, + @as("41") _41?: reanimatedStyle, + @as("42") _42?: reanimatedStyle, + @as("43") _43?: reanimatedStyle, + @as("44") _44?: reanimatedStyle, + @as("45") _45?: reanimatedStyle, + @as("46") _46?: reanimatedStyle, + @as("47") _47?: reanimatedStyle, + @as("48") _48?: reanimatedStyle, + @as("49") _49?: reanimatedStyle, + @as("50") _50?: reanimatedStyle, + @as("51") _51?: reanimatedStyle, + @as("52") _52?: reanimatedStyle, + @as("53") _53?: reanimatedStyle, + @as("54") _54?: reanimatedStyle, + @as("55") _55?: reanimatedStyle, + @as("56") _56?: reanimatedStyle, + @as("57") _57?: reanimatedStyle, + @as("58") _58?: reanimatedStyle, + @as("59") _59?: reanimatedStyle, + @as("60") _60?: reanimatedStyle, + @as("61") _61?: reanimatedStyle, + @as("62") _62?: reanimatedStyle, + @as("63") _63?: reanimatedStyle, + @as("64") _64?: reanimatedStyle, + @as("65") _65?: reanimatedStyle, + @as("66") _66?: reanimatedStyle, + @as("67") _67?: reanimatedStyle, + @as("68") _68?: reanimatedStyle, + @as("69") _69?: reanimatedStyle, + @as("70") _70?: reanimatedStyle, + @as("71") _71?: reanimatedStyle, + @as("72") _72?: reanimatedStyle, + @as("73") _73?: reanimatedStyle, + @as("74") _74?: reanimatedStyle, + @as("75") _75?: reanimatedStyle, + @as("76") _76?: reanimatedStyle, + @as("77") _77?: reanimatedStyle, + @as("78") _78?: reanimatedStyle, + @as("79") _79?: reanimatedStyle, + @as("80") _80?: reanimatedStyle, + @as("81") _81?: reanimatedStyle, + @as("82") _82?: reanimatedStyle, + @as("83") _83?: reanimatedStyle, + @as("84") _84?: reanimatedStyle, + @as("85") _85?: reanimatedStyle, + @as("86") _86?: reanimatedStyle, + @as("87") _87?: reanimatedStyle, + @as("88") _88?: reanimatedStyle, + @as("89") _89?: reanimatedStyle, + @as("90") _90?: reanimatedStyle, + @as("91") _91?: reanimatedStyle, + @as("92") _92?: reanimatedStyle, + @as("93") _93?: reanimatedStyle, + @as("94") _94?: reanimatedStyle, + @as("95") _95?: reanimatedStyle, + @as("96") _96?: reanimatedStyle, + @as("97") _97?: reanimatedStyle, + @as("98") _98?: reanimatedStyle, + @as("99") _99?: reanimatedStyle, + @as("100") _100?: reanimatedStyle, + } + @new + @module("react-native-reanimated") + /** +Receives a keyframe config with the style of each step. It returns a layout animation that can be used with modifiers, like duration. + +Example: + ``` + let exitKeyframe = keyframe({ + from: reanimatedStyle(~transform=[translateY(~translateY=0.)], ~opacity=1., ()), + to: reanimatedStyle(~transform=[translateY(~translateY=-60.)], ~opacity=0., ()), + })->duration(50.) + + + ``` + */ + external keyframe: keyframeConfig => LayoutAnimation.t = "Keyframe" +} +include KeyFrame + +module View = { + @react.component @module("./exports") + external make: ( + ~style: ReactNative.Style.t=?, + ~children: React.element=?, + ~onLayout: ReactNative.Event.layoutEvent => unit=?, + ~entering: LayoutAnimation.t=?, + ~exiting: LayoutAnimation.t=?, + ~pointerEvents: @string + [ + | #auto + | #none + | @as("box-none") #boxNone + | @as("box-only") #boxOnly + ]=?, + ) => React.element = "AnimatedView" +} + +module Text = { + @react.component @module("./exports") + external make: ( + ~style: ReactNative.Style.t=?, + ~children: React.element=?, + ~entering: LayoutAnimation.t=?, + ~exiting: LayoutAnimation.t=?, + ) => React.element = "AnimatedText" +} +module ScrollView = { + @react.component @module("./exports") + external make: ( + ~entering: LayoutAnimation.t=?, + ~exiting: LayoutAnimation.t=?, + ~alwaysBounceHorizontal: bool=?, + ~alwaysBounceVertical: bool=?, + ~automaticallyAdjustContentInsets: bool=?, + ~bounces: bool=?, + ~bouncesZoom: bool=?, + ~canCancelContentTouches: bool=?, + ~centerContent: bool=?, + ~contentContainerStyle: ReactNative.Style.t=?, + ~contentInset: ReactNative.View.edgeInsets=?, + ~contentInsetAdjustmentBehavior: ReactNative.ScrollView.contentInsetAdjustmentBehavior=?, + ~contentOffset: ReactNative.ScrollView.contentOffset=?, + ~decelerationRate: ReactNative.ScrollView.decelerationRate=?, + ~directionalLockEnabled: bool=?, + ~endFillColor: ReactNative.Color.t=?, + ~fadingEdgeLength: float=?, + ~horizontal: bool=?, + ~indicatorStyle: ReactNative.ScrollView.indicatorStyle=?, + ~keyboardDismissMode: @string + [ + | #none + | #interactive + | @as("on-drag") #onDrag + ]=?, + ~keyboardShouldPersistTaps: ReactNative.ScrollView.keyboardShouldPersistTaps=?, + ~maximumZoomScale: float=?, + ~minimumZoomScale: float=?, + ~nestedScrollEnabled: bool=?, + ~onContentSizeChange: ((float, float)) => unit=?, + ~onMomentumScrollBegin: ReactNative.Event.scrollEvent => unit=?, + ~onMomentumScrollEnd: ReactNative.Event.scrollEvent => unit=?, + ~onScroll: ReactNative.Event.scrollEvent => unit=?, + ~onScrollBeginDrag: ReactNative.Event.scrollEvent => unit=?, + ~onScrollEndDrag: ReactNative.Event.scrollEvent => unit=?, + ~overScrollMode: ReactNative.ScrollView.overScrollMode=?, + ~pagingEnabled: bool=?, + ~pinchGestureEnabled: bool=?, + ~refreshControl: React.element=?, + ~scrollEnabled: bool=?, + ~scrollEventThrottle: int=?, + ~scrollIndicatorInsets: ReactNative.View.edgeInsets=?, + ~scrollPerfTag: string=?, + ~scrollsToTop: bool=?, + ~scrollToOverflowEnabled: bool=?, + ~showsHorizontalScrollIndicator: bool=?, + ~showsVerticalScrollIndicator: bool=?, + ~snapToAlignment: ReactNative.ScrollView.snapToAlignment=?, + ~snapToEnd: bool=?, + ~snapToInterval: float=?, + ~snapToOffsets: array=?, + ~snapToStart: bool=?, + ~stickyHeaderHiddenOnScroll: bool=?, + ~stickyHeaderIndices: array=?, + ~zoomScale: float=?, + // rescript-react-native 0.64 || 0.65 || 0.66 View props + ~accessibilityActions: array=?, + ~accessibilityElementsHidden: bool=?, + ~accessibilityHint: string=?, + ~accessibilityIgnoresInvertColors: bool=?, + ~accessibilityLabel: string=?, + ~accessibilityLiveRegion: ReactNative.Accessibility.liveRegion=?, + ~accessibilityRole: ReactNative.Accessibility.role=?, + ~accessibilityState: ReactNative.Accessibility.state=?, + ~accessibilityValue: ReactNative.Accessibility.value=?, + ~accessibilityViewIsModal: bool=?, + ~accessible: bool=?, + ~collapsable: bool=?, + ~hitSlop: ReactNative.View.edgeInsets=?, + ~importantForAccessibility: @string + [ + | #auto + | #yes + | #no + | @as("no-hide-descendants") #noHideDescendants + ]=?, + ~nativeID: string=?, + ~needsOffscreenAlphaCompositing: bool=?, + ~onAccessibilityAction: ReactNative.Accessibility.actionEvent => unit=?, + ~onAccessibilityEscape: unit => unit=?, + ~onAccessibilityTap: unit => unit=?, + ~onLayout: ReactNative.Event.layoutEvent => unit=?, + ~onMagicTap: unit => unit=?, + // Gesture Responder props + ~onMoveShouldSetResponder: ReactNative.Event.pressEvent => bool=?, + ~onMoveShouldSetResponderCapture: ReactNative.Event.pressEvent => bool=?, + ~onResponderEnd: ReactNative.Event.pressEvent => unit=?, + ~onResponderGrant: ReactNative.Event.pressEvent => unit=?, + ~onResponderMove: ReactNative.Event.pressEvent => unit=?, + ~onResponderReject: ReactNative.Event.pressEvent => unit=?, + ~onResponderRelease: ReactNative.Event.pressEvent => unit=?, + ~onResponderStart: ReactNative.Event.pressEvent => unit=?, + ~onResponderTerminate: ReactNative.Event.pressEvent => unit=?, + ~onResponderTerminationRequest: ReactNative.Event.pressEvent => bool=?, + ~onStartShouldSetResponder: ReactNative.Event.pressEvent => bool=?, + ~onStartShouldSetResponderCapture: ReactNative.Event.pressEvent => bool=?, + ~pointerEvents: @string + [ + | #auto + | #none + | @as("box-none") #boxNone + | @as("box-only") #boxOnly + ]=?, + ~removeClippedSubviews: bool=?, + ~renderToHardwareTextureAndroid: bool=?, + ~shouldRasterizeIOS: bool=?, + ~style: ReactNative.Style.t=?, + ~testID: string=?, + ~children: React.element=?, + // react-native-web 0.16 View props + ~href: string=?, + ~hrefAttrs: ReactNative.Web.hrefAttrs=?, + ~onMouseDown: ReactEvent.Mouse.t => unit=?, + ~onMouseEnter: ReactEvent.Mouse.t => unit=?, + ~onMouseLeave: ReactEvent.Mouse.t => unit=?, + ~onMouseMove: ReactEvent.Mouse.t => unit=?, + ~onMouseOver: ReactEvent.Mouse.t => unit=?, + ~onMouseOut: ReactEvent.Mouse.t => unit=?, + ~onMouseUp: ReactEvent.Mouse.t => unit=?, + ) => React.element = "AnimatedScrollView" +} + +type reanimatedScrollHandler = ReactNative.Event.ScrollEvent.payload => unit + +type handlers = { + onMomentumEnd?: reanimatedScrollHandler, + onBeginDrag?: reanimatedScrollHandler, + onEndDrag?: reanimatedScrollHandler, + onMomentumBegin?: reanimatedScrollHandler, + onScroll?: reanimatedScrollHandler, +} + +type scrollHandler = ReactNative.Event.scrollEvent => unit + +@module("react-native-reanimated") +/** +Receives a record of scroll event handlers and returns a single handler that can be passed to Reanimated.ScrollView + + +Example: + ``` +let scrollHandler = useAnimatedScrollHandler({ + onScroll: event => { + translationY.value = event.contentOffset.y + }, + }) + + + ``` + */ +external useAnimatedScrollHandler: handlers => scrollHandler = "useAnimatedScrollHandler" + +module AnimatedGestureHandler = { + module Make = ( + Arguments: { + type event + type ctx + }, + ) => { + type t = { + onStart: option<(Arguments.event, Arguments.ctx) => unit>, + onActive: option<(Arguments.event, Arguments.ctx) => unit>, + onEnd: option<(Arguments.event, Arguments.ctx) => unit>, + onFail: option<(Arguments.event, Arguments.ctx) => unit>, + onCancel: option<(Arguments.event, Arguments.ctx) => unit>, + onFinish: option<(Arguments.event, Arguments.ctx) => unit>, + } + + @obj + external makeGestureHandlers: ( + ~onStart: (Arguments.event, Arguments.ctx) => unit=?, + ~onActive: (Arguments.event, Arguments.ctx) => unit=?, + ~onEnd: (Arguments.event, Arguments.ctx) => unit=?, + ~onFail: (Arguments.event, Arguments.ctx) => unit=?, + ~onCancel: (Arguments.event, Arguments.ctx) => unit=?, + ~onFinish: (Arguments.event, Arguments.ctx) => unit=?, + unit, + ) => t = "" + + type _returnType = Arguments.event => unit + type _gestureHandlers = t + + @module("react-native-reanimated") + external useAnimatedGestureHandler: _gestureHandlers => _returnType = + "useAnimatedGestureHandler" + @module("react-native-reanimated") + external useAnimatedGestureHandler0: (_gestureHandlers, @as(json`[]`) _) => _returnType = + "useAnimatedGestureHandler" + @module("react-native-reanimated") + external useAnimatedGestureHandle1: (_gestureHandlers, array<'a>) => _returnType = + "useAnimatedGestureHandler" + @module("react-native-reanimated") + external useAnimatedGestureHandle2: (_gestureHandlers, ('a, 'b)) => _returnType = + "useAnimatedGestureHandler" + @module("react-native-reanimated") + external useAnimatedGestureHandle3: (_gestureHandlers, ('a, 'b, 'c)) => _returnType = + "useAnimatedGestureHandler" + @module("react-native-reanimated") + external useAnimatedGestureHandle4: (_gestureHandlers, ('a, 'b, 'c, 'd)) => _returnType = + "useAnimatedGestureHandler" + } +} + +@module("react-native-reanimated") +external runOnUI: ('arg => 'return, . 'arg) => unit = "runOnUI" + +@module("react-native-reanimated") +external runOnJS: ('arg => 'return, . 'arg) => unit = "runOnJS" + +module SharedValue = { + type t<'t> = {mutable value: 't} + let make: 't => t<'t> = smth => {value: smth} +} + +@module("react-native-reanimated") +external useSharedValue: 't => SharedValue.t<'t> = "useSharedValue" + +module Timing = { + type justDuration = {duration: float} + + type justEasing = {easing: ReactNative.Easing.t} + + type both = { + duration: float, + easing: ReactNative.Easing.t, + } + + let makeConfig = (~duration: option=?, ~easing: option=?, ()) => { + switch (duration, easing) { + | (Some(duration), None) => Some(#Duration({duration: duration})) + | (None, Some(easing)) => Some(#Easing({easing: easing})) + | (Some(duration), Some(easing)) => Some(#Both({duration, easing})) + | (None, None) => None + } + } + + // DO NOT USE OR YOU WILL GET FIRED ;) + @module("react-native-reanimated") + external withTiming_: ( + 't, + @unwrap + [#Duration(justDuration) | #Easing(justEasing) | #Both(both) | #Nothing(option)], + option>, + ) => float = "withTiming" +} + +let withTiming = ( + ~toValue: 't, + ~userConfig: option< + [#Duration(Timing.justDuration) | #Easing(Timing.justEasing) | #Both(Timing.both)], + >, + ~callback: option>=?, + (), +) => { + Timing.withTiming_( + toValue, + switch userConfig { + | Some(#Duration(durationObj)) => #Duration(durationObj) + | Some(#Easing(easingObj)) => #Easing(easingObj) + | Some(#Both(bothObj)) => #Both(bothObj) + | None => #Nothing(None) + }, + callback, + ) +} + +module Spring = { + type config = { + mass: option, + stiffness: option, + overshootClamping: option, + restDisplacementThreshold: option, + restSpeedThreshold: option, + velocity: option, + damping: option, + } + let makeConfig = ( + ~mass: option=?, + ~stiffness: option=?, + ~overshootClamping: option=?, + ~restDisplacementThreshold: option=?, + ~restSpeedThreshold: option=?, + ~velocity: option=?, + ~damping: option=?, + (), + ) => { + mass, + stiffness, + overshootClamping, + restDisplacementThreshold, + restSpeedThreshold, + velocity, + damping, + } + @module("react-native-reanimated") + external withSpring_: ('t, option, option>) => float = "withSpring" +} + +let withSpring = ( + ~toValue: 't, + ~userConfig: option=?, + ~callback: option>=?, + (), +) => { + Spring.withSpring_(toValue, userConfig, callback) +} + +@module("react-native-reanimated") +external useAnimatedStyle: (@uncurry (unit => ReactNative.Style.t)) => ReactNative.Style.t = + "useAnimatedStyle" +@module("react-native-reanimated") +external useAnimatedStyle0: ( + @uncurry (unit => ReactNative.Style.t), + @as(json`[]`) _, +) => ReactNative.Style.t = "useAnimatedStyle" +@module("react-native-reanimated") +external useAnimatedStyle1: ( + @uncurry (unit => ReactNative.Style.t), + array<'a>, +) => ReactNative.Style.t = "useAnimatedStyle" +@module("react-native-reanimated") +external useAnimatedStyle2: ( + @uncurry (unit => ReactNative.Style.t), + ('a, 'b), +) => ReactNative.Style.t = "useAnimatedStyle" +@module("react-native-reanimated") +external useAnimatedStyle3: ( + @uncurry (unit => ReactNative.Style.t), + ('a, 'b, 'c), +) => ReactNative.Style.t = "useAnimatedStyle" +@module("react-native-reanimated") +external useAnimatedStyle4: ( + @uncurry (unit => ReactNative.Style.t), + ('a, 'b, 'c, 'd), +) => ReactNative.Style.t = "useAnimatedStyle" + +module Extrapolation = { + type t + + type extrapolation = { + @as("IDENTITY") identity: [#identity], + @as("CLAMP") clamp: [#clamp], + @as("EXTEND") extend: [#extend], + } + + external asExtrapolation: extrapolation => t = "%identity" + external asString: string => t = "%identity" +} + +type extrapolationConfig = { + extrapolateLeft: option, + extrapolateRight: option, +} + +module ExtrapolationType = { + type t + + external asExtrapolation: Extrapolation.extrapolation => t = "%identity" + external asExtrapolationConfig: extrapolationConfig => t = "%identity" + external asString: string => t = "%identity" +} + +module Interpolate = { + type x = float + type input = array + type output = array + type type_ = option +} + +// Turn into named arguments maybe? +@module("react-native-reanimated") +external interpolate: ( + Interpolate.x, + Interpolate.input, + Interpolate.output, + Interpolate.type_, +) => float = "interpolate" + +@module("react-native-reanimated") +external interpolateColor: ( + float, + array, + array, + option<[#RGB | #HSV]>, +) => ReactNative.Color.t = "interpolateColor" diff --git a/drafts/rescript-reanimated/src/ReanimatedStyle.res b/drafts/rescript-reanimated/src/ReanimatedStyle.res new file mode 100644 index 0000000..0f9bf83 --- /dev/null +++ b/drafts/rescript-reanimated/src/ReanimatedStyle.res @@ -0,0 +1,628 @@ +// Vendored from rescript-react-native 0.70 because of compatibility issues. +type t +open ReactNative + +external array: array => t = "%identity" +external arrayOption: array> => t = "%identity" + +// Escape hatch, in case something is added into RN but unsupported, +// Useful if you play with fancy platforms +// Use with caution +@val +external unsafeAddStyle: (@as(json`{}`) _, t, Js.t<'a>) => t = "Object.assign" + +external unsafeStyle: Js.t<'a> => t = "%identity" + +type size = string + +external dp: float => size = "%identity" + +let pct = num => num->Js.Float.toString ++ "%" + +type margin = size + +@inline +let auto = "auto" + +type offset +@obj external offset: (~height: float, ~width: float) => offset = "" + +type angle +let deg: float => angle = num => (num->Js.Float.toString ++ "deg")->Obj.magic +let rad: float => angle = num => (num->Js.Float.toString ++ "rad")->Obj.magic + +type transform +@obj external perspective: (~perspective: float) => transform = "" +@obj external rotate: (~rotate: angle) => transform = "" +@obj external rotateX: (~rotateX: angle) => transform = "" +@obj external rotateY: (~rotateY: angle) => transform = "" +@obj external rotateZ: (~rotateZ: angle) => transform = "" +@obj external scale: (~scale: float) => transform = "" +@obj external scaleX: (~scaleX: float) => transform = "" +@obj external scaleY: (~scaleY: float) => transform = "" +@obj external translateX: (~translateX: float) => transform = "" +@obj external translateY: (~translateY: float) => transform = "" +@obj external skewX: (~skewX: angle) => transform = "" +@obj external skewY: (~skewY: angle) => transform = "" +// @todo matrix + +external unsafeTransform: Js.t<'a> => transform = "%identity" + +type resizeMode = [#cover | #contain | #stretch | #repeat | #center] + +type fontStyle = [#normal | #italic] + +module FontWeight = { + // Note: we cannot model this as a polymorphic variant + // because #"100" = #100 = the number 100 in JS, but we need the string "100" here. + type t = string + + @inline + let normal = "normal" + @inline + let bold = "bold" + @inline + let _100 = "100" + @inline + let _200 = "200" + @inline + let _300 = "300" + @inline + let _400 = "400" + @inline + let _500 = "500" + @inline + let _600 = "600" + @inline + let _700 = "700" + @inline + let _800 = "800" + @inline + let _900 = "900" +} + +type fontWeight = FontWeight.t + +type fontVariant = [ + | #"small-caps" + | #"oldstyle-nums" + | #"lining-nums" + | #"tabular-nums" + | #"proportional-nums" +] + +type textAlign = [#auto | #left | #right | #center | #justify] + +type textAlignVertical = [#auto | #top | #bottom | #center] + +type textDecorationLine = [ + | #none + | #underline + | #"line-through" + | #"underline line-through" +] + +type textDecorationStyle = [#solid | #double | #dotted | #dashed] + +type textTransform = [#none | #uppercase | #lowercase | #capitalize] + +type writingDirection = [#auto | #ltr | #rtl] + +type backfaceVisibility = [#visible | #hidden] + +type borderStyle = [#solid | #dotted | #dashed] + +type display = [#none | #flex] + +type overflow = [#visible | #hidden | #scroll] + +type flexWrap = [#wrap | #nowrap] + +type position = [#absolute | #relative] + +type alignContent = [ + | #"flex-start" + | #"flex-end" + | #center + | #stretch + | #"space-around" + | #"space-between" +] + +type alignItems = [ + | #"flex-start" + | #"flex-end" + | #center + | #stretch + | #baseline +] + +type alignSelf = [ + | #auto + | #"flex-start" + | #"flex-end" + | #center + | #stretch + | #baseline +] + +type direction = [#inherit | #ltr | #rtl] + +type flexDirection = [ + | #row + | #"row-reverse" + | #column + | #"column-reverse" +] + +type justifyContent = [ + | #"flex-start" + | #"flex-end" + | #center + | #"space-around" + | #"space-between" + | #"space-evenly" +] + +// Styles are documented here +// https://github.com/facebook/react-native/blob/master/Libraries/StyleSheet/StyleSheetTypes.js + +// Note that all border*Width are in layout styles & view styles too +// React Native JS codebase have those in View Styles Props again but with different types +// because layout styles props don't accept animated values. +// We don't do the distinction as ReScript is an HMTS that doesn't support implicit subtyping + +// Dangerous Imprecise Style +// Contains all of +// - image style +// - text style +// - view style +// - transform style +// - shadow style +// - layout style + +// ____DangerouslyImpreciseStyle_Internal +// Dangerous Imprecise Style +// Contains all of +// - image style +// - text style +// - view style +// - transform style +// - shadow style +// - layout style +@obj +external style: ( + // Image Style Props (https://reactnative.dev/docs/image-style-props) + ~resizeMode: resizeMode=?, + ~overlayColor: Color.t=?, + ~tintColor: Color.t=?, + // Text Style Props (https://reactnative.dev/docs/text-style-props) + ~color: Color.t=?, + ~fontFamily: string=?, + ~fontSize: float=?, + ~fontStyle: fontStyle=?, + ~fontVariant: array=?, + ~fontWeight: fontWeight=?, + ~includeFontPadding: bool=?, + ~letterSpacing: float=?, + ~lineHeight: float=?, + ~textAlign: textAlign=?, + ~textAlignVertical: textAlignVertical=?, + ~textDecorationColor: Color.t=?, + ~textDecorationLine: textDecorationLine=?, + ~textDecorationStyle: textDecorationStyle=?, + ~textShadowColor: Color.t=?, + ~textShadowOffset: offset=?, + ~textShadowRadius: float=?, + ~textTransform: textTransform=?, + ~writingDirection: writingDirection=?, + // View styles https://reactnative.dev/docs/view-style-props + ~backfaceVisibility: backfaceVisibility=?, + ~backgroundColor: Color.t=?, + ~borderBottomColor: Color.t=?, + ~borderBottomEndRadius: float=?, + ~borderBottomLeftRadius: float=?, + ~borderBottomRightRadius: float=?, + ~borderBottomStartRadius: float=?, + ~borderBottomWidth: float=?, + ~borderColor: Color.t=?, + ~borderEndColor: Color.t=?, + ~borderEndWidth: float=?, + ~borderLeftColor: Color.t=?, + ~borderLeftWidth: float=?, + ~borderRadius: float=?, + ~borderRightColor: Color.t=?, + ~borderRightWidth: float=?, + ~borderStartColor: Color.t=?, + ~borderStartWidth: float=?, + ~borderStyle: borderStyle=?, + ~borderTopColor: Color.t=?, + ~borderTopEndRadius: float=?, + ~borderTopLeftRadius: float=?, + ~borderTopRightRadius: float=?, + ~borderTopStartRadius: float=?, + ~borderTopWidth: float=?, + ~borderWidth: float=?, + ~elevation: float=?, + ~opacity: float=?, + // Transform Props (https://reactnative.dev/docs/transforms#props) + ~transform: array=?, // all other transform props are deprecated + // Shadow Props (https://reactnative.dev/docs/shadow-props) + ~shadowColor: Color.t=?, + ~shadowOffset: offset=?, + ~shadowOpacity: float=?, + ~shadowRadius: float=?, + // Layout Style Props (https://reactnative.dev/docs/layout-props) + ~alignContent: alignContent=?, + ~alignItems: alignItems=?, + ~alignSelf: alignSelf=?, + ~aspectRatio: float=?, + // border*Width are commented because already in view styles props (see explanation at the top) + // ~borderBottomWidth: float=?, + // ~borderEndWidth: float=?, + // ~borderLeftWidth: float=?, + // ~borderRightWidth: float=?, + // ~borderStartWidth: float=?, + // ~borderTopWidth: float=?, + // ~borderWidth: float=?, + ~bottom: size=?, + ~direction: direction=?, + ~display: display=?, + ~end: size=?, + ~flex: float=?, + ~flexBasis: margin=?, + ~flexDirection: flexDirection=?, + ~flexGrow: float=?, + ~flexShrink: float=?, + ~flexWrap: flexWrap=?, + ~height: size=?, + ~justifyContent: justifyContent=?, + ~left: size=?, + ~margin: margin=?, + ~marginBottom: margin=?, + ~marginEnd: margin=?, + ~marginHorizontal: margin=?, + ~marginLeft: margin=?, + ~marginRight: margin=?, + ~marginStart: margin=?, + ~marginTop: margin=?, + ~marginVertical: margin=?, + ~maxHeight: size=?, + ~maxWidth: size=?, + ~minHeight: size=?, + ~minWidth: size=?, + ~overflow: overflow=?, + ~padding: size=?, + ~paddingBottom: size=?, + ~paddingEnd: size=?, + ~paddingHorizontal: size=?, + ~paddingLeft: size=?, + ~paddingRight: size=?, + ~paddingStart: size=?, + ~paddingTop: size=?, + ~paddingVertical: size=?, + ~position: position=?, + ~right: size=?, + ~start: size=?, + ~top: size=?, + ~width: size=?, + ~zIndex: int=?, + unit, +) => t = "" + +// ____ViewStyleProp_Internal +@obj +external viewStyle: ( + // View styles https://reactnative.dev/docs/view-style-props + ~backfaceVisibility: backfaceVisibility=?, + ~backgroundColor: Color.t=?, + ~borderBottomColor: Color.t=?, + ~borderBottomEndRadius: float=?, + ~borderBottomLeftRadius: float=?, + ~borderBottomRightRadius: float=?, + ~borderBottomStartRadius: float=?, + ~borderBottomWidth: float=?, + ~borderColor: Color.t=?, + ~borderEndColor: Color.t=?, + ~borderEndWidth: float=?, + ~borderLeftColor: Color.t=?, + ~borderLeftWidth: float=?, + ~borderRadius: float=?, + ~borderRightColor: Color.t=?, + ~borderRightWidth: float=?, + ~borderStartColor: Color.t=?, + ~borderStartWidth: float=?, + ~borderStyle: borderStyle=?, + ~borderTopColor: Color.t=?, + ~borderTopEndRadius: float=?, + ~borderTopLeftRadius: float=?, + ~borderTopRightRadius: float=?, + ~borderTopStartRadius: float=?, + ~borderTopWidth: float=?, + ~borderWidth: float=?, + ~elevation: float=?, + ~opacity: float=?, + // Transform Props (https://reactnative.dev/docs/transforms#props) + ~transform: array=?, // all other transform props are deprecated + // Shadow Props (https://reactnative.dev/docs/shadow-props) + ~shadowColor: Color.t=?, + ~shadowOffset: offset=?, + ~shadowOpacity: float=?, + ~shadowRadius: float=?, + // Layout Style Props (https://reactnative.dev/docs/layout-props) + ~alignContent: alignContent=?, + ~alignItems: alignItems=?, + ~alignSelf: alignSelf=?, + ~aspectRatio: float=?, + // border*Width are commented because already in view styles props (see explanation at the top) + // ~borderBottomWidth: float=?, + // ~borderEndWidth: float=?, + // ~borderLeftWidth: float=?, + // ~borderRightWidth: float=?, + // ~borderStartWidth: float=?, + // ~borderTopWidth: float=?, + // ~borderWidth: float=?, + ~bottom: size=?, + ~direction: direction=?, + ~display: display=?, + ~end: size=?, + ~flex: float=?, + ~flexBasis: margin=?, + ~flexDirection: flexDirection=?, + ~flexGrow: float=?, + ~flexShrink: float=?, + ~flexWrap: flexWrap=?, + ~height: size=?, + ~justifyContent: justifyContent=?, + ~left: size=?, + ~margin: margin=?, + ~marginBottom: margin=?, + ~marginEnd: margin=?, + ~marginHorizontal: margin=?, + ~marginLeft: margin=?, + ~marginRight: margin=?, + ~marginStart: margin=?, + ~marginTop: margin=?, + ~marginVertical: margin=?, + ~maxHeight: size=?, + ~maxWidth: size=?, + ~minHeight: size=?, + ~minWidth: size=?, + ~overflow: overflow=?, + ~padding: size=?, + ~paddingBottom: size=?, + ~paddingEnd: size=?, + ~paddingHorizontal: size=?, + ~paddingLeft: size=?, + ~paddingRight: size=?, + ~paddingStart: size=?, + ~paddingTop: size=?, + ~paddingVertical: size=?, + ~position: position=?, + ~right: size=?, + ~start: size=?, + ~top: size=?, + ~width: size=?, + ~zIndex: int=?, + unit, +) => t = "" + +// ____TextStyleProp_Internal +@obj +external textStyle: ( + // Text Style Props (https://reactnative.dev/docs/text-style-props) + ~color: Color.t=?, + ~fontFamily: string=?, + ~fontSize: float=?, + ~fontStyle: fontStyle=?, + ~fontVariant: array=?, + ~fontWeight: fontWeight=?, + ~includeFontPadding: bool=?, + ~letterSpacing: float=?, + ~lineHeight: float=?, + ~textAlign: textAlign=?, + ~textAlignVertical: textAlignVertical=?, + ~textDecorationColor: Color.t=?, + ~textDecorationLine: textDecorationLine=?, + ~textDecorationStyle: textDecorationStyle=?, + ~textShadowColor: Color.t=?, + ~textShadowOffset: offset=?, + ~textShadowRadius: float=?, + ~textTransform: textTransform=?, + ~writingDirection: writingDirection=?, + // View styles https://reactnative.dev/docs/view-style-props + ~backfaceVisibility: backfaceVisibility=?, + ~backgroundColor: Color.t=?, + ~borderBottomColor: Color.t=?, + ~borderBottomEndRadius: float=?, + ~borderBottomLeftRadius: float=?, + ~borderBottomRightRadius: float=?, + ~borderBottomStartRadius: float=?, + ~borderBottomWidth: float=?, + ~borderColor: Color.t=?, + ~borderEndColor: Color.t=?, + ~borderEndWidth: float=?, + ~borderLeftColor: Color.t=?, + ~borderLeftWidth: float=?, + ~borderRadius: float=?, + ~borderRightColor: Color.t=?, + ~borderRightWidth: float=?, + ~borderStartColor: Color.t=?, + ~borderStartWidth: float=?, + ~borderStyle: borderStyle=?, + ~borderTopColor: Color.t=?, + ~borderTopEndRadius: float=?, + ~borderTopLeftRadius: float=?, + ~borderTopRightRadius: float=?, + ~borderTopStartRadius: float=?, + ~borderTopWidth: float=?, + ~borderWidth: float=?, + ~elevation: float=?, + ~opacity: float=?, + // Transform Props (https://reactnative.dev/docs/transforms#props) + ~transform: array=?, // all other transform props are deprecated + // Shadow Props (https://reactnative.dev/docs/shadow-props) + ~shadowColor: Color.t=?, + ~shadowOffset: offset=?, + ~shadowOpacity: float=?, + ~shadowRadius: float=?, + // Layout Style Props (https://reactnative.dev/docs/layout-props) + ~alignContent: alignContent=?, + ~alignItems: alignItems=?, + ~alignSelf: alignSelf=?, + ~aspectRatio: float=?, + // border*Width are commented because already in view styles props (see explanation at the top) + // ~borderBottomWidth: float=?, + // ~borderEndWidth: float=?, + // ~borderLeftWidth: float=?, + // ~borderRightWidth: float=?, + // ~borderStartWidth: float=?, + // ~borderTopWidth: float=?, + // ~borderWidth: float=?, + ~bottom: size=?, + ~direction: direction=?, + ~display: display=?, + ~end: size=?, + ~flex: float=?, + ~flexBasis: margin=?, + ~flexDirection: flexDirection=?, + ~flexGrow: float=?, + ~flexShrink: float=?, + ~flexWrap: flexWrap=?, + ~height: size=?, + ~justifyContent: justifyContent=?, + ~left: size=?, + ~margin: margin=?, + ~marginBottom: margin=?, + ~marginEnd: margin=?, + ~marginHorizontal: margin=?, + ~marginLeft: margin=?, + ~marginRight: margin=?, + ~marginStart: margin=?, + ~marginTop: margin=?, + ~marginVertical: margin=?, + ~maxHeight: size=?, + ~maxWidth: size=?, + ~minHeight: size=?, + ~minWidth: size=?, + ~overflow: overflow=?, + ~padding: size=?, + ~paddingBottom: size=?, + ~paddingEnd: size=?, + ~paddingHorizontal: size=?, + ~paddingLeft: size=?, + ~paddingRight: size=?, + ~paddingStart: size=?, + ~paddingTop: size=?, + ~paddingVertical: size=?, + ~position: position=?, + ~right: size=?, + ~start: size=?, + ~top: size=?, + ~width: size=?, + ~zIndex: int=?, + unit, +) => t = "" + +// ____ImageStyleProp_Internal +@obj +external imageStyle: ( + // Image Style Props (https://reactnative.dev/docs/image-style-props) + ~resizeMode: resizeMode=?, + ~overlayColor: Color.t=?, + ~tintColor: Color.t=?, + // View styles https://reactnative.dev/docs/view-style-props + ~backfaceVisibility: backfaceVisibility=?, + ~backgroundColor: Color.t=?, + ~borderBottomColor: Color.t=?, + ~borderBottomEndRadius: float=?, + ~borderBottomLeftRadius: float=?, + ~borderBottomRightRadius: float=?, + ~borderBottomStartRadius: float=?, + ~borderBottomWidth: float=?, + ~borderColor: Color.t=?, + ~borderEndColor: Color.t=?, + ~borderEndWidth: float=?, + ~borderLeftColor: Color.t=?, + ~borderLeftWidth: float=?, + ~borderRadius: float=?, + ~borderRightColor: Color.t=?, + ~borderRightWidth: float=?, + ~borderStartColor: Color.t=?, + ~borderStartWidth: float=?, + ~borderStyle: borderStyle=?, + ~borderTopColor: Color.t=?, + ~borderTopEndRadius: float=?, + ~borderTopLeftRadius: float=?, + ~borderTopRightRadius: float=?, + ~borderTopStartRadius: float=?, + ~borderTopWidth: float=?, + ~borderWidth: float=?, + ~elevation: float=?, + ~opacity: float=?, + // Transform Props (https://reactnative.dev/docs/transforms#props) + ~transform: array=?, // all other transform props are deprecated + // Shadow Props (https://reactnative.dev/docs/shadow-props) + ~shadowColor: Color.t=?, + ~shadowOffset: offset=?, + ~shadowOpacity: float=?, + ~shadowRadius: float=?, + // Layout Style Props (https://reactnative.dev/docs/layout-props) + ~alignContent: alignContent=?, + ~alignItems: alignItems=?, + ~alignSelf: alignSelf=?, + ~aspectRatio: float=?, + // border*Width are commented because already in view styles props (see explanation at the top) + // ~borderBottomWidth: float=?, + // ~borderEndWidth: float=?, + // ~borderLeftWidth: float=?, + // ~borderRightWidth: float=?, + // ~borderStartWidth: float=?, + // ~borderTopWidth: float=?, + // ~borderWidth: float=?, + ~bottom: size=?, + ~direction: direction=?, + ~display: display=?, + ~end: size=?, + ~flex: float=?, + ~flexBasis: margin=?, + ~flexDirection: flexDirection=?, + ~flexGrow: float=?, + ~flexShrink: float=?, + ~flexWrap: flexWrap=?, + ~height: size=?, + ~justifyContent: justifyContent=?, + ~left: size=?, + ~margin: margin=?, + ~marginBottom: margin=?, + ~marginEnd: margin=?, + ~marginHorizontal: margin=?, + ~marginLeft: margin=?, + ~marginRight: margin=?, + ~marginStart: margin=?, + ~marginTop: margin=?, + ~marginVertical: margin=?, + ~maxHeight: size=?, + ~maxWidth: size=?, + ~minHeight: size=?, + ~minWidth: size=?, + ~overflow: overflow=?, + ~padding: size=?, + ~paddingBottom: size=?, + ~paddingEnd: size=?, + ~paddingHorizontal: size=?, + ~paddingLeft: size=?, + ~paddingRight: size=?, + ~paddingStart: size=?, + ~paddingTop: size=?, + ~paddingVertical: size=?, + ~position: position=?, + ~right: size=?, + ~start: size=?, + ~top: size=?, + ~width: size=?, + ~zIndex: int=?, + unit, +) => t = "" + +let empty: t = style() diff --git a/drafts/rescript-reanimated/src/examples/UseAnimatedScrollHandlerExample.res b/drafts/rescript-reanimated/src/examples/UseAnimatedScrollHandlerExample.res new file mode 100644 index 0000000..39791a4 --- /dev/null +++ b/drafts/rescript-reanimated/src/examples/UseAnimatedScrollHandlerExample.res @@ -0,0 +1,34 @@ +open Reanimated +open ReactNative.Style + +module Styles = { + let container = viewStyle(~flex=1., ~backgroundColor="black", ()) + let flex = viewStyle(~flex=1., ()) + let blue = viewStyle(~backgroundColor="blue", ()) + let content = viewStyle(~backgroundColor="white", ~height=dp(1000.), ()) +} + +@react.component +let make = () => { + let translationY = useSharedValue(0.) + + let scrollHandler = useAnimatedScrollHandler({ + onScroll: event => { + translationY.value = event.contentOffset.y + }, + }) + + let animatedStyles = useAnimatedStyle(() => + viewStyle(~transform=[translateY(~translateY=translationY.value)], ()) + ) + + + + + + {"really cool content here!"->React.string} + + + +} +let default = make diff --git a/drafts/rescript-reanimated/src/exports.js b/drafts/rescript-reanimated/src/exports.js new file mode 100644 index 0000000..ec79578 --- /dev/null +++ b/drafts/rescript-reanimated/src/exports.js @@ -0,0 +1,5 @@ +import Animated from "react-native-reanimated"; + +export const AnimatedView = Animated.View; +export const AnimatedText = Animated.Text; +export const AnimatedScrollView = Animated.ScrollView;