diff --git a/bower.json b/bower.json index 2a1d79e..7089497 100644 --- a/bower.json +++ b/bower.json @@ -23,7 +23,8 @@ "purescript-exceptions": "^4.0.0", "purescript-maybe": "^4.0.0", "purescript-nullable": "^4.0.0", - "purescript-typelevel-prelude": "^3.0.0" + "purescript-typelevel-prelude": "^3.0.0", + "purescript-tuples": "^5.1.0" }, "devDependencies": { "purescript-console": "^4.1.0", diff --git a/src/React.js b/src/React.js index db2c58b..4cb8b50 100644 --- a/src/React.js +++ b/src/React.js @@ -91,10 +91,6 @@ function getProps(this_) { } exports.getProps = getProps; -exports.childrenToArray = React.Children.toArray - -exports.childrenCount = React.Children.count; - function setStateImpl(this_) { return function(state){ return function(){ @@ -160,12 +156,3 @@ function createElementDynamic(class_) { }; exports.createElementDynamicImpl = createElementDynamic; exports.createElementTagNameDynamic = createElementDynamic; - -function createContext(defaultValue) { - var context = React.createContext(defaultValue); - return { - consumer: context.Consumer, - provider: context.Provider - }; -} -exports.createContext = createContext; diff --git a/src/React.purs b/src/React.purs index e7b2d0e..b59224a 100644 --- a/src/React.purs +++ b/src/React.purs @@ -2,7 +2,6 @@ module React ( TagName - , ReactElement , ReactComponent , ReactThis , ReactUnusedSnapshot @@ -31,8 +30,6 @@ module React , pureComponent , pureComponentWithDerivedState , statelessComponent - , ReactClass - , ReactRef , getProps , getState , setState @@ -53,21 +50,19 @@ module React , unsafeCreateLeafElement , createElementTagName , createElementTagNameDynamic - , Children - , childrenToArray - , childrenCount - , class IsReactElement - , toElement , fragmentWithKey - , Context - , ContextProvider - , ContextConsumer - , createContext + , createHookElement + , unsafeCreateHookElement + , createHookElementDynamic + , unsafeCreateHookElementDynamic + , createHookLeafElement + , unsafeCreateHookLeafElement + , createRenderPropsElement + , module Exports ) where import Prelude -import Data.Nullable (Nullable) import Effect (Effect) import Effect.Exception (Error) import Effect.Uncurried (EffectFn1) @@ -75,18 +70,22 @@ import Prim.Row as Row import Type.Row (type (+)) import Unsafe.Coerce (unsafeCoerce) +import React.Hook (Hook) +import React.Ref (Ref, DOMRef) +import React.Types (ReactElement, ReactClass, Children) +import React.Types + ( ReactClass + , ReactElement + , class IsReactElement + , toElement + , Children + , childrenToArray + , childrenCount + ) as Exports + -- | Name of a tag. type TagName = String --- | A virtual DOM node, or component. -foreign import data ReactElement :: Type - -instance semigroupReactElement :: Semigroup ReactElement where - append a b = toElement [ a, b ] - -instance monoidReactElement :: Monoid ReactElement where - mempty = toElement ([] :: Array ReactElement) - -- | A mounted react component foreign import data ReactComponent :: Type @@ -246,15 +245,8 @@ foreign import statelessComponent :: forall props. (Record props -> ReactElement) -> ReactClass (Record props) --- | React class for components. -foreign import data ReactClass :: Type -> Type - foreign import fragment :: ReactClass { children :: Children } --- | Type for React refs. This type is opaque, but you can use `Data.Foreign` --- | and `DOM` to validate the underlying representation. -foreign import data ReactRef :: Type - -- | Read the component props. foreign import getProps :: forall props state. ReactThis props state -> @@ -339,7 +331,7 @@ class ReactPropFields (required :: # Type) (given :: # Type) type ReservedReactPropFields r = ( key :: String - , ref :: SyntheticEventHandler (Nullable ReactRef) + , ref :: Ref DOMRef | r ) @@ -386,7 +378,7 @@ unsafeCreateElementDynamic :: forall props. unsafeCreateElementDynamic = createElementDynamicImpl foreign import createElementImpl :: forall required given children. - ReactClass required -> given -> Array children -> ReactElement + ReactClass required -> given -> children -> ReactElement foreign import createElementDynamicImpl :: forall required given children. ReactClass required -> given -> Array children -> ReactElement @@ -418,48 +410,72 @@ foreign import createElementTagName :: forall props. foreign import createElementTagNameDynamic :: forall props. TagName -> props -> Array ReactElement -> ReactElement --- | Internal representation for the children elements passed to a component -foreign import data Children :: Type - --- | Internal conversion function from children elements to an array of React elements -foreign import childrenToArray :: Children -> Array ReactElement - --- | Returns the number of children. -foreign import childrenCount :: Children -> Int - -class IsReactElement a where - toElement :: a -> ReactElement - -instance isReactElementString :: IsReactElement String where - toElement = unsafeCoerce - -instance isReactElementNumber :: IsReactElement Number where - toElement = unsafeCoerce - -instance isReactElementInt :: IsReactElement Int where - toElement = unsafeCoerce - -instance isReactElementChildren :: IsReactElement Children where - toElement = unsafeCoerce - -instance isReactElementReactElement :: IsReactElement ReactElement where - toElement = identity - -instance isReactElementArray :: IsReactElement (Array ReactElement) where - toElement = createElement fragment {} - -- | Creates a keyed fragment. fragmentWithKey :: String -> Array ReactElement -> ReactElement fragmentWithKey = createElement fragment <<< { key: _ } -type Context a = - { consumer :: ContextConsumer a - , provider :: ContextProvider a - } - -type ContextProvider a = ReactClass { children :: Children, value :: a } - -type ContextConsumer a = ReactClass { children :: a -> ReactElement } - --- | Create a new context provider/consumer pair given a default value. -foreign import createContext :: forall a. a -> Context a +-- | Create an element from a function using Hooks spreading the children array. Used when the children are known up front. +createHookElement + :: forall required given + . ReactPropFields required given + => ({ children :: Children | required } -> Hook ReactElement) + -> { | given } + -> Array ReactElement + -> ReactElement +createHookElement k = createElementImpl (unsafeCoerce k) + +-- | An unsafe version of `createHookElement` which does not enforce the reserved properties "key" and "ref". +unsafeCreateHookElement + :: forall props + . ({ children :: Children | props } -> Hook ReactElement) + -> { | props } + -> Array ReactElement + -> ReactElement +unsafeCreateHookElement k = createElementImpl (unsafeCoerce k) + +-- | Create an element from a function using Hooks passing the children array. Used for a dynamic array of children. +createHookElementDynamic + :: forall required given + . ReactPropFields required given + => ({ children :: Children | required } -> Hook ReactElement) + -> { | given } + -> Array ReactElement + -> ReactElement +createHookElementDynamic k = createElementDynamicImpl (unsafeCoerce k) + +-- | An unsafe version of `createHookElementDynamic` which does not enforce the reserved properties "key" and "ref". +unsafeCreateHookElementDynamic + :: forall props + . ({ children :: Children | props } -> Hook ReactElement) + -> { | props } + -> Array ReactElement + -> ReactElement +unsafeCreateHookElementDynamic k = createElementDynamicImpl (unsafeCoerce k) + +-- | Create an element from a function using Hooks that does not require children. +createHookLeafElement + :: forall required given + . ReactPropFields required given + => ({ | required } -> Hook ReactElement) + -> { | given } + -> ReactElement +createHookLeafElement k = createLeafElementImpl (unsafeCoerce k) + +-- | An unsafe version of `createHookLeafElement` which does not enforce the reserved +-- | properties "key" and "ref". +unsafeCreateHookLeafElement + :: forall props + . ({ | props } -> Hook ReactElement) + -> { | props } + -> ReactElement +unsafeCreateHookLeafElement k = createLeafElementImpl (unsafeCoerce k) + +-- | Create an element using the [render props pattern](https://reactjs.org/docs/render-props.html#using-props-other-than-render) when the name of the render prop is "children". +createRenderPropsElement + :: forall required given childrenProps + . ReactPropFields required given + => ReactClass { children :: childrenProps -> ReactElement | required } + -> { | given } + -> (childrenProps -> ReactElement) + -> ReactElement +createRenderPropsElement = createElementImpl diff --git a/src/React/Context.js b/src/React/Context.js new file mode 100644 index 0000000..020a1af --- /dev/null +++ b/src/React/Context.js @@ -0,0 +1,18 @@ +'use strict'; + +var React = require('react'); + +exports.getProvider = function getProvider(context) { + return context.Provider; +}; + +exports.getConsumer = function getConsumer(context) { + return context.Consumer; +}; + +exports.createContext_ = function createContext_(defaultValue, calculateChangedBits) { + return calculateChangedBits ? + React.createContext(defaultValue, calculateChangedBits) : + React.createContext(defaultValue) + ; +}; diff --git a/src/React/Context.purs b/src/React/Context.purs new file mode 100644 index 0000000..6bf5b72 --- /dev/null +++ b/src/React/Context.purs @@ -0,0 +1,30 @@ +module React.Context + ( Context + , createContext + , getProvider + , getConsumer + ) where + +import Prelude + +import Data.Function.Uncurried (Fn2, mkFn2, runFn2) +import Data.Maybe (Maybe) +import Data.Nullable (Nullable) +import Data.Nullable as Nullable + +import React.Types (ReactElement, ReactClass, Children) + +createContext :: forall a. a -> Maybe (a -> a -> Number) -> Context a +createContext a = runFn2 createContext_ a <<< Nullable.toNullable <<< map mkFn2 + +foreign import data Context :: Type -> Type + +foreign import getProvider :: forall a. Context a -> ReactClass { children :: Children, value :: a } + +foreign import getConsumer :: forall a. Context a -> ReactClass { children :: a -> ReactElement } + +foreign import createContext_ + :: forall a + . Fn2 a + (Nullable (Fn2 a a Number)) + (Context a) diff --git a/src/React/DOM/Props.purs b/src/React/DOM/Props.purs index e3b2b50..75002ef 100644 --- a/src/React/DOM/Props.purs +++ b/src/React/DOM/Props.purs @@ -5,7 +5,7 @@ import Prelude import Data.Nullable (Nullable) import Effect (Effect) import Effect.Uncurried (mkEffectFn1) -import React (ReactRef) +import React.Ref (Ref, DOMRef) import React.SyntheticEvent ( SyntheticEvent , SyntheticAnimationEvent @@ -894,8 +894,11 @@ onScrollCapture f = unsafeMkProps "onScrollCapture" (mkEffectFn1 f) onWheelCapture :: (SyntheticWheelEvent -> Effect Unit) -> Props onWheelCapture f = unsafeMkProps "onWheelCapture" (mkEffectFn1 f) -ref :: (Nullable ReactRef -> Effect Unit) -> Props -ref f = unsafeMkProps "ref" (mkEffectFn1 f) +ref :: Ref DOMRef -> Props +ref = unsafeMkProps "ref" + +callbackRef :: (Nullable DOMRef -> Effect Unit) -> Props +callbackRef f = unsafeMkProps "ref" (mkEffectFn1 f) suppressContentEditableWarning :: Boolean -> Props suppressContentEditableWarning = unsafeMkProps "suppressContentEditableWarning" diff --git a/src/React/Hook.js b/src/React/Hook.js new file mode 100644 index 0000000..189a55f --- /dev/null +++ b/src/React/Hook.js @@ -0,0 +1,81 @@ +'use strict'; + +var React = require('react'); + +exports.useState_ = function useState_(Tuple, initialState) { + var result = React.useState(initialState); + + var state = result[0]; + + var setState = result[1]; + + var tuple = Tuple(state)(setState); + + return tuple; +}; + +exports.useEffect_ = function useEffect_(effect, inputs) { + var result = inputs ? React.useEffect(effect, inputs) : React.useEffect(effect); + + return result; +}; + +exports.useContext_ = function useContext_(context) { + var result = React.useContext(context); + + return result; +} + +exports.useReducer_ = function useReducer_(Tuple, reducer, initialState) { + var result = React.useReducer(reducer, initialState); + + var state = result[0]; + + var dispatch = result[1]; + + var tuple = Tuple(state)(dispatch); + + return tuple; +}; + +exports.useReducerLazy_ = function useReducerLazy_(Tuple, reducer, initialState, initialAction) { + var result = React.useReducer(reducer, initialState, initialAction); + + var state = result[0]; + + var dispatch = result[1]; + + var tuple = Tuple(state)(dispatch); + + return tuple; +}; + +exports.useCallback_ = function useCallback_(callback, inputs) { + var result = inputs ? React.useCallback(callback, inputs) : React.useCallback(callback); + + return result; +}; + +exports.useMemo_ = function useMemo_(memo, inputs) { + var result = inputs ? React.useMemo(memo, inputs) : React.useMemo(memo); + + return result; +}; + +exports.useRef_ = function useRef_(initialValue) { + var result = React.useRef(initialValue); + + return result; +} + +exports.useImperativeMethods_ = function useImperativeMethods_(ref, imperativeMethods, inputs) { + var result = inputs ? React.useImperativeMethods(ref, imperativeMethods, inputs) : React.useImperativeMethods(ref, imperativeMethods); + + return result; +}; + +exports.useLayoutEffect_ = function useLayoutEffect_(layoutEffect, inputs) { + var result = inputs ? React.useLayoutEffect(layoutEffect, inputs) : React.useLayoutEffect(layoutEffect); + + return result; +}; diff --git a/src/React/Hook.purs b/src/React/Hook.purs new file mode 100644 index 0000000..881df51 --- /dev/null +++ b/src/React/Hook.purs @@ -0,0 +1,229 @@ +module React.Hook + ( Hook + , HookInput + , hookInput + , useState + , useStateLazy + , setState + , modifyState + , SetState + , useEffect + , useContext + , useReducer + , useReducerLazy + , dispatch + , Dispatch + , useCallback + , useMemo + , useRef + , useImperativeMethods + , useLayoutEffect + ) where + +import Prelude + +import Data.Function.Uncurried (Fn1, Fn2, Fn3, Fn4, mkFn2, runFn1, runFn2, runFn3, runFn4) +import Data.Maybe (Maybe) +import Data.Nullable (Nullable) +import Data.Nullable as Nullable +import Data.Tuple (Tuple(..)) + +import Effect (Effect) +import Effect.Uncurried (EffectFn1, runEffectFn1) + +import Unsafe.Coerce (unsafeCoerce) + +import React.Context (Context) +import React.Ref (Ref) + +useState + :: forall a + . a + -> Hook (Tuple a (SetState a)) +useState = runFn2 useState_ Tuple + +useStateLazy + :: forall a + . (Unit -> a) + -> Hook (Tuple a (SetState a)) +useStateLazy = runFn2 useState_ Tuple + +setState + :: forall a + . SetState a + -> a + -> Effect Unit +setState k = runEffectFn1 k' + where + k' :: EffectFn1 a Unit + k' = unsafeCoerce k + +modifyState + :: forall a + . SetState a + -> (a -> a) + -> Effect Unit +modifyState k = runEffectFn1 k' + where + k' :: EffectFn1 (a -> a) Unit + k' = unsafeCoerce k + +foreign import data SetState :: Type -> Type + +foreign import useState_ + :: forall a b + . Fn2 (b -> SetState b -> Tuple b (SetState b)) + a + (Hook (Tuple b (SetState b))) + +useEffect + :: forall a + . Effect (Effect a) + -> Maybe (Array HookInput) + -> Hook Unit +useEffect k = runFn2 useEffect_ k <<< Nullable.toNullable + +foreign import useEffect_ + :: forall a + . Fn2 (Effect (Effect a)) + (Nullable (Array HookInput)) + (Hook Unit) + +useContext :: forall a. Context a -> Hook a +useContext = runFn1 useContext_ + +foreign import useContext_ + :: forall a + . Fn1 (Context a) + (Hook a) + +useReducer + :: forall a b + . (a -> b -> a) + -> a + -> Hook (Tuple a (Dispatch a b)) +useReducer = runFn3 useReducer_ Tuple <<< mkFn2 + +useReducerLazy + :: forall a b + . (a -> b -> a) + -> a + -> b + -> Hook (Tuple a (Dispatch a b)) +useReducerLazy = runFn4 useReducerLazy_ Tuple <<< mkFn2 + +dispatch + :: forall a b + . Dispatch a b + -> b + -> Effect Unit +dispatch k = runEffectFn1 k' + where + k' :: EffectFn1 b Unit + k' = unsafeCoerce k + +foreign import data Dispatch :: Type -> Type -> Type + +foreign import useReducer_ + :: forall a b + . Fn3 (a -> Dispatch a b -> Tuple a (Dispatch a b)) + (Fn2 a b a) + a + (Hook (Tuple a (Dispatch a b))) + +foreign import useReducerLazy_ + :: forall a b + . Fn4 (a -> Dispatch a b -> Tuple a (Dispatch a b)) + (Fn2 a b a) + a + b + (Hook (Tuple a (Dispatch a b))) + +useCallback + :: forall a b + . (a -> b) + -> Maybe (Array HookInput) + -> Hook (a -> b) +useCallback k = runFn2 useCallback_ k <<< Nullable.toNullable + +foreign import useCallback_ + :: forall a b + . Fn2 (a -> b) + (Nullable (Array HookInput)) + (Hook (a -> b)) + +useMemo + :: forall a + . (Unit -> a) + -> Maybe (Array HookInput) + -> Hook a +useMemo k = runFn2 useMemo_ k <<< Nullable.toNullable + +foreign import useMemo_ + :: forall a + . Fn2 (Unit -> a) + (Nullable (Array HookInput)) + (Hook a) + +useRef :: forall a. Maybe a -> Hook (Ref a) +useRef = runFn1 useRef_ <<< Nullable.toNullable + +foreign import useRef_ + :: forall a + . Fn1 (Nullable a) + (Hook (Ref a)) + +useImperativeMethods + :: forall a + . Ref a + -> (Unit -> a) + -> Maybe (Array HookInput) + -> Hook Unit +useImperativeMethods a k = runFn3 useImperativeMethods_ a k <<< Nullable.toNullable + +foreign import useImperativeMethods_ + :: forall a + . Fn3 (Ref a) + (Unit -> a) + (Nullable (Array HookInput)) + (Hook Unit) + +useLayoutEffect + :: forall a + . Effect (Effect a) + -> Maybe (Array HookInput) + -> Hook Unit +useLayoutEffect k = runFn2 useLayoutEffect_ k <<< Nullable.toNullable + +foreign import useLayoutEffect_ + :: forall a + . Fn2 (Effect (Effect a)) + (Nullable (Array HookInput)) + (Hook Unit) + +foreign import data Hook :: Type -> Type + +instance functorHook :: Functor Hook where + map k = hook <<< k <<< unHook + +instance applyHook :: Apply Hook where + apply k fa = hook (unHook k (unHook fa)) + +instance applicativeHook :: Applicative Hook where + pure = hook + +instance bindHook :: Bind Hook where + bind fa k = k (unHook fa) + +instance monadHook :: Monad Hook + +unHook :: forall a. Hook a -> a +unHook = unsafeCoerce + +hook :: forall a. a -> Hook a +hook = unsafeCoerce + +foreign import data HookInput :: Type + +hookInput :: forall a. a -> HookInput +hookInput = unsafeCoerce diff --git a/src/React/Ref.js b/src/React/Ref.js new file mode 100644 index 0000000..44540e2 --- /dev/null +++ b/src/React/Ref.js @@ -0,0 +1,19 @@ +'use strict'; + +var React = require('react'); + +exports.createRef = function createRef() { + return React.createRef(); +}; + +exports.forwardRef_ = function forwardRef(render) { + return React.forwardRef(render); +} + +exports.getRef_ = function getRef_(ref) { + return ref.current; +} + +exports.setRef_ = function setRef_(ref, value) { + ref.current = value; +} diff --git a/src/React/Ref.purs b/src/React/Ref.purs new file mode 100644 index 0000000..78101fb --- /dev/null +++ b/src/React/Ref.purs @@ -0,0 +1,54 @@ +module React.Ref + ( Ref + , DOMRef + , createRef + , forwardRef + , getRef + , setRef + ) where + +import Prelude + +import Data.Function.Uncurried (Fn2, mkFn2) +import Data.Maybe (Maybe) +import Data.Nullable (Nullable) +import Data.Nullable as Nullable + +import Effect (Effect) +import Effect.Uncurried (EffectFn1, EffectFn2, runEffectFn1, runEffectFn2) + +import React.Types (ReactClass, ReactElement) + +-- | Type for React refs. +foreign import data Ref :: Type -> Type + +-- | Type for a Ref value that is a React component instance or a DOM element. +-- | This type is opaque, but you can use `Data.Foreign` and `DOM` to validate the underlying representation. +foreign import data DOMRef :: Type + +foreign import createRef :: forall a. Effect (Ref a) + +forwardRef :: forall props a. (props -> Ref a -> ReactElement) -> ReactClass props +forwardRef = forwardRef_ <<< mkFn2 + +foreign import forwardRef_ + :: forall props a + . Fn2 props (Ref a) ReactElement + -> ReactClass props + +getRef :: forall a. Ref a -> Effect (Maybe a) +getRef r = Nullable.toMaybe <$> runEffectFn1 getRef_ r + +foreign import getRef_ + :: forall a + . EffectFn1 (Ref a) + (Nullable a) + +setRef :: forall a. Ref a -> Maybe a -> Effect Unit +setRef r = runEffectFn2 setRef_ r <<< Nullable.toNullable + +foreign import setRef_ + :: forall a + . EffectFn2 (Ref a) + (Nullable a) + Unit diff --git a/src/React/Types.js b/src/React/Types.js new file mode 100644 index 0000000..b95bced --- /dev/null +++ b/src/React/Types.js @@ -0,0 +1,11 @@ +'use strict'; + +var React = require('react'); + +exports.toElementArray = function toElementArray(elements) { + return React.createElement.apply(React, [React.Fragment, { }].concat(elements)); +}; + +exports.childrenToArray = React.Children.toArray + +exports.childrenCount = React.Children.count; diff --git a/src/React/Types.purs b/src/React/Types.purs new file mode 100644 index 0000000..74dd9e8 --- /dev/null +++ b/src/React/Types.purs @@ -0,0 +1,57 @@ +module React.Types + ( ReactClass + , ReactElement + , class IsReactElement + , toElement + , Children + , childrenToArray + , childrenCount + ) where + +import Prelude + +import Unsafe.Coerce (unsafeCoerce) + +-- | React class for components. +foreign import data ReactClass :: Type -> Type + +-- | A virtual DOM node, or component. +foreign import data ReactElement :: Type + +instance semigroupReactElement :: Semigroup ReactElement where + append a b = toElement [ a, b ] + +instance monoidReactElement :: Monoid ReactElement where + mempty = toElement ([] :: Array ReactElement) + +class IsReactElement a where + toElement :: a -> ReactElement + +instance isReactElementString :: IsReactElement String where + toElement = unsafeCoerce + +instance isReactElementNumber :: IsReactElement Number where + toElement = unsafeCoerce + +instance isReactElementInt :: IsReactElement Int where + toElement = unsafeCoerce + +instance isReactElementChildren :: IsReactElement Children where + toElement = unsafeCoerce + +instance isReactElementReactElement :: IsReactElement ReactElement where + toElement = identity + +instance isReactElementArray :: IsReactElement (Array ReactElement) where + toElement = toElementArray + +foreign import toElementArray :: Array ReactElement -> ReactElement + +-- | Internal representation for the children elements passed to a component +foreign import data Children :: Type + +-- | Internal conversion function from children elements to an array of React elements +foreign import childrenToArray :: Children -> Array ReactElement + +-- | Returns the number of children. +foreign import childrenCount :: Children -> Int