Commit 9e872a96 authored by James Laver's avatar James Laver

v0.1.0

parent 953a4679
......@@ -20,13 +20,12 @@
"pulp": "^12.4.0",
"purescript": "^0.12.5",
"purs-loader": "^3.2.0",
"react-testing-library": "^5.9.0",
"react-testing-library": "^6.1.2",
"spago": "^0.7.5"
},
"dependencies": {
"react": "^16.8.6",
"react-dom": "^16.8.6",
"react-testing-library": "^6.1.2"
"react-dom": "^16.8.6"
},
"eslintConfig": {
"extends": "react-app"
......
......@@ -123,6 +123,11 @@ let additions =
, "spec", "spec-mocha", "unsafe-coerce" ]
"https://github.com/irresponsible/purescript-dom-simple"
"master"
, ffi-simple =
mkPackage
[ "functions", "nullable", "prelude" ]
"https://github.com/irresponsible/purescript-ffi-simple"
"v0.1.2"
, spec-mocha =
mkPackage
[ "console", "foldable-traversable", "exceptions", "spec" ]
......
......@@ -6,10 +6,12 @@
[ "console"
, "dom-simple"
, "effect"
, "ffi-simple"
, "functions"
, "newtype"
, "nullable"
, "prelude"
, "refs"
, "spec"
, "spec-mocha"
, "unsafe-coerce" ]
......
module Reactix.DOM.Raw
(LeafFactory, TreeFactory
, button, div, div', i, i', p, p', span, span'
(LeafFactory, ElementFactory
, button, div, div', hr
, i, i', p, p', span, span'
, text) where
import Reactix.React (Element, createDOMElement)
import Reactix.React (Element, createElement)
import Unsafe.Coerce (unsafeCoerce)
createLeafDOMElement :: forall props. String -> Record props -> Element
createLeafDOMElement e p = createDOMElement e p []
createLeafElement :: forall props. String -> Record props -> Element
createLeafElement e p = createElement e p []
-- A factory function for a DOM element with no children
type LeafFactory = forall props. Record props -> Element
-- A factory function for a DOM element with children
type TreeFactory = forall props. Record props -> Array Element -> Element
type ElementFactory = forall props. Record props -> Array Element -> Element
text :: String -> Element
text = unsafeCoerce
button :: TreeFactory
button = createDOMElement "button"
button :: ElementFactory
button = createElement "button"
div :: TreeFactory
div = createDOMElement "div"
div :: ElementFactory
div = createElement "div"
div' :: LeafFactory
div' = createLeafDOMElement "div"
div' = createLeafElement "div"
i :: TreeFactory
i = createDOMElement "i"
hr :: LeafFactory
hr = createLeafElement "hr"
i :: ElementFactory
i = createElement "i"
i' :: LeafFactory
i' = createLeafDOMElement "i"
i' = createLeafElement "i"
p :: TreeFactory
p = createDOMElement "p"
p :: ElementFactory
p = createElement "p"
p' :: LeafFactory
p' = createLeafDOMElement "p"
p' = createLeafElement "p"
span :: TreeFactory
span = createDOMElement "span"
span :: ElementFactory
span = createElement "span"
span' :: LeafFactory
span' = createLeafDOMElement "span"
span' = createLeafElement "span"
......@@ -4,12 +4,6 @@ var React = require("react");
function _simple(prop) {
return function() { return React[prop].apply(React, arguments); };
}
function _tuple(prop) {
return function(ctor) {
const r = React[prop].apply(React, Array.prototype.slice.call(arguments, 1));
return ctor(r[0])(r[1]);
}
}
function _memo(prop) {
return function() {
var args = Array.prototype.slice.call(arguments);
......@@ -17,16 +11,13 @@ function _memo(prop) {
return React[prop].apply(React, args);
}
}
exports._tuple = function tuple(ctor, v) { return ctor(v[0])(v[1]); };
exports._useContext = _simple('useContext');
exports._useDebugValue = _simple('useDebugValue');
exports._useDebugValuePrime = _simple('useDebugValue');
// exports._useImperativeHandle = _simple('useImperativeHandle');
exports._useState = _tuple('useState');
exports._useReducer = _tuple('useReducer');
exports._useRef = function(ctor, value) {
const r = React.useRef(value);
const set = function(v) { r.current = v; };
......@@ -40,16 +31,3 @@ exports._useMemo3 = _memo('useMemo');
exports._useMemo4 = _memo('useMemo');
exports._useMemo5 = _memo('useMemo');
exports._useEffect = _simple('useEffect');
exports._useEffect1 = _memo('useEffect');
exports._useEffect2 = _memo('useEffect');
exports._useEffect3 = _memo('useEffect');
exports._useEffect4 = _memo('useEffect');
exports._useEffect5 = _memo('useEffect');
exports._useLayoutEffect = _simple('useLayoutEffect');
exports._useLayoutEffect1 = _memo('useLayoutEffect');
exports._useLayoutEffect2 = _memo('useLayoutEffect');
exports._useLayoutEffect3 = _memo('useLayoutEffect');
exports._useLayoutEffect4 = _memo('useLayoutEffect');
exports._useLayoutEffect5 = _memo('useLayoutEffect');
module Reactix.Hooks
( State, useState
, HookEffect
, useEffect, useEffect1, useEffect2, useEffect3, useEffect4, useEffect5
, useLayoutEffect, useLayoutEffect1, useLayoutEffect2
, useLayoutEffect3, useLayoutEffect4, useLayoutEffect5
, useMemo, useMemo1, useMemo2, useMemo3, useMemo4, useMemo5
, Reducer, useReducer, useReducer'
, Ref, useRef
, useContext
, useDebugValue, useDebugValue'
-- , Reducer
-- , useReducer, useReducer'
-- , useContext
-- , useMemo, useMemo1, useMemo2 --, useMemo3, useMemo4, useMemo5
-- , Ref, useRef
-- , useDebugValue, useDebugValue'
-- , useImperativeHandle
)
where
import Prelude
import Data.Function.Uncurried (Fn2, mkFn2)
import Data.Tuple (Tuple(..))
import Effect (Effect)
import Data.Function.Uncurried ( Fn2, mkFn2, runFn2 )
import Data.Tuple ( Tuple(..) )
import Effect ( Effect )
import Effect.Uncurried ( EffectFn1, runEffectFn1, EffectFn2, runEffectFn2, EffectFn3, runEffectFn3, EffectFn4, runEffectFn4, EffectFn5, runEffectFn5, EffectFn6, runEffectFn6 )
import FFI.Simple ( (...), delay, args2, args3, args4, args5 )
import DOM.Simple.Console
import Reactix.React ( Hooks, react, unsafeHooksEffect )
import Reactix.React ( class MonadHooks, unsafeHooksEffect, Context )
--- useState
......@@ -26,113 +32,111 @@ import Reactix.React ( class MonadHooks, unsafeHooksEffect, Context )
-- | A state hook is a tuple of value and setter
type State state = Tuple state (EffectFn1 state Unit)
-- | Given an Effect function returning an initial value, returns a UseState hook
useState :: forall s m. MonadHooks m => Effect s -> m (State s)
useState = unsafeHooksEffect <<< runEffectFn2 _useState Tuple
-- | Given an Effect function returning an initial value, returns a State
useState :: forall s. (Unit -> Effect s) -> Hooks (State s)
useState s = hook $ \_ -> pure $ tuple $ react ... "useState" $ [ delay s ]
-- -- useReducer
-- type Reducer state action = Tuple state (EffectFn1 action Unit)
-- useReducer :: forall s a i. (s -> a -> s) -> (i -> s) -> i -> Hooks (Reducer s a)
-- useReducer f i j = pure $ tuple $ react ... "useReducer" $ args3 f i j
-- useReducer' :: forall s a. (s -> a -> s) -> s -> Hooks (Reducer s a)
-- useReducer' r = useReducer r identity
-- instance readReducer :: Read (Reducer s a) s where
-- read = _read0
-- instance writeReducer :: Write (Reducer s a) a where
-- write = runEffectFn2 _call1
-- | Takes an effect function which calculates the initial state
foreign import _useState :: forall s t. EffectFn2 (s -> t -> Tuple s t) (Effect s) (State s)
-- useEffect
type HookEffect = Unit -> Effect (Unit -> Effect Unit)
wrapEffect :: HookEffect -> Effect (Effect Unit)
wrapEffect f = do
cleanup <- f unit
pure $ delay cleanup
_useEffect :: forall a. HookEffect -> a -> Hooks Unit
_useEffect e a = hook $ \_ -> pure $ react ... "useEffect" $ args2 (wrapEffect e) a
-- | Given an Effect function which returns a cleanup Effect function,
-- | register an effect to be called after rendering
useEffect :: forall m. MonadHooks m => Effect (Effect Unit) -> m Unit
useEffect = unsafeHooksEffect <<< runEffectFn1 _useEffect
useEffect :: HookEffect -> Hooks Unit
useEffect e = hook $ \_ -> pure $ react ... "useEffect" $ [ wrapEffect e ]
-- | Like useEffect, but with a memo value
useEffect1 :: forall a m. MonadHooks m => a -> Effect (Effect Unit) -> m Unit
useEffect1 a = unsafeHooksEffect <<< runEffectFn2 _useEffect1 a
useEffect1 :: forall a. a -> HookEffect -> Hooks Unit
useEffect1 a e = _useEffect e [a]
-- | Like useEffect, but with 2 memo values
useEffect2 :: forall a b m. MonadHooks m => a -> b -> Effect (Effect Unit) -> m Unit
useEffect2 a b = unsafeHooksEffect <<< runEffectFn3 _useEffect2 a b
useEffect2 :: forall a b. a -> b -> HookEffect -> Hooks Unit
useEffect2 a b e = _useEffect e $ args2 a b
-- | Like useEffect, but with 3 memo values
useEffect3 :: forall a b c m. MonadHooks m => a -> b -> c -> Effect (Effect Unit) -> m Unit
useEffect3 a b c = unsafeHooksEffect <<< runEffectFn4 _useEffect3 a b c
useEffect3 :: forall a b c. a -> b -> c -> HookEffect -> Hooks Unit
useEffect3 a b c e = _useEffect e $ args3 a b c
-- | Like useEffect, but with 4 memo values
useEffect4 :: forall a b c d m. MonadHooks m => a -> b -> c -> d -> Effect (Effect Unit) -> m Unit
useEffect4 a b c d = unsafeHooksEffect <<< runEffectFn5 _useEffect4 a b c d
useEffect4 :: forall a b c d. a -> b -> c -> d -> HookEffect -> Hooks Unit
useEffect4 a b c d e = _useEffect e $ args4 a b c d
-- | Like useEffect, but with 5 memo values
useEffect5 :: forall a b c d e m. MonadHooks m => a -> b -> c -> d -> e -> Effect (Effect Unit) -> m Unit
useEffect5 a b c d e = unsafeHooksEffect <<< runEffectFn6 _useEffect5 a b c d e
foreign import _useEffect :: EffectFn1 (Effect (Effect Unit)) Unit
foreign import _useEffect1 :: forall a. EffectFn2 a (Effect (Effect Unit)) Unit
foreign import _useEffect2 :: forall a b. EffectFn3 a b (Effect (Effect Unit)) Unit
foreign import _useEffect3 :: forall a b c. EffectFn4 a b c(Effect (Effect Unit)) Unit
foreign import _useEffect4 :: forall a b c d. EffectFn5 a b c d (Effect (Effect Unit)) Unit
foreign import _useEffect5 :: forall a b c d e. EffectFn6 a b c d e (Effect (Effect Unit)) Unit
useEffect5 :: forall a b c d e. a -> b -> c -> d -> e -> HookEffect -> Hooks Unit
useEffect5 a b c d f e = _useEffect e $ args5 a b c d f
-- useLayoutEffect
-- | Given an Effect function which returns a cleanup Effect function,
-- | register an effect to be called in the same phase as
-- | `componentDidMount` and `componentDidUpdate` used to be.
useLayoutEffect :: forall m. MonadHooks m => Effect (Effect Unit) -> m Unit
useLayoutEffect = unsafeHooksEffect <<< runEffectFn1 _useLayoutEffect
useLayoutEffect :: HookEffect -> Hooks Unit
useLayoutEffect e = hook $ \_ -> pure $ react ... "useLayoutEffect" $ [ wrapEffect e ]
_useLayoutEffect :: forall a. HookEffect -> a -> Hooks Unit
_useLayoutEffect e a = hook $ \_ -> pure $ react ... "useLayoutEffect" $ args2 (wrapEffect e) a
-- | Like useLayoutEffect, but with a memo value
useLayoutEffect1 :: forall a m. MonadHooks m => a -> Effect (Effect Unit) -> m Unit
useLayoutEffect1 a = unsafeHooksEffect <<< runEffectFn2 _useLayoutEffect1 a
useLayoutEffect1 :: forall a. a -> HookEffect -> Hooks Unit
useLayoutEffect1 a e = _useLayoutEffect e [a]
-- | Like useLayoutEffect, but with 2 memo values
useLayoutEffect2 :: forall a b m. MonadHooks m => a -> b -> Effect (Effect Unit) -> m Unit
useLayoutEffect2 a b = unsafeHooksEffect <<< runEffectFn3 _useLayoutEffect2 a b
useLayoutEffect2 :: forall a b. a -> b -> HookEffect -> Hooks Unit
useLayoutEffect2 a b e = _useLayoutEffect e $ args2 a b
-- | Like useLayoutEffect, but with 3 memo values
useLayoutEffect3 :: forall a b c m. MonadHooks m => a -> b -> c -> Effect (Effect Unit) -> m Unit
useLayoutEffect3 a b c = unsafeHooksEffect <<< runEffectFn4 _useLayoutEffect3 a b c
useLayoutEffect3 :: forall a b c. a -> b -> c -> HookEffect -> Hooks Unit
useLayoutEffect3 a b c e = _useLayoutEffect e $ args3 a b c
-- | Like useLayoutEffect, but with 4 memo values
useLayoutEffect4 :: forall a b c d m. MonadHooks m => a -> b -> c -> d -> Effect (Effect Unit) -> m Unit
useLayoutEffect4 a b c d = unsafeHooksEffect <<< runEffectFn5 _useLayoutEffect4 a b c d
useLayoutEffect4 :: forall a b c d. a -> b -> c -> d -> HookEffect -> Hooks Unit
useLayoutEffect4 a b c d e = _useLayoutEffect e $ args4 a b c d
-- | Like useLayoutEffect, but with 5 memo values
useLayoutEffect5 :: forall a b c d e m. MonadHooks m => a -> b -> c -> d -> e -> Effect (Effect Unit) -> m Unit
useLayoutEffect5 a b c d e= unsafeHooksEffect <<< runEffectFn6 _useLayoutEffect5 a b c d e
foreign import _useLayoutEffect :: EffectFn1 (Effect (Effect Unit)) Unit
foreign import _useLayoutEffect1 :: forall a. EffectFn2 a (Effect (Effect Unit)) Unit
foreign import _useLayoutEffect2 :: forall a b. EffectFn3 a b (Effect (Effect Unit)) Unit
foreign import _useLayoutEffect3 :: forall a b c. EffectFn4 a b c(Effect (Effect Unit)) Unit
foreign import _useLayoutEffect4 :: forall a b c d. EffectFn5 a b c d (Effect (Effect Unit)) Unit
foreign import _useLayoutEffect5 :: forall a b c d e. EffectFn6 a b c d e (Effect (Effect Unit)) Unit
useLayoutEffect5 :: forall a b c d e. a -> b -> c -> d -> e -> HookEffect -> Hooks Unit
useLayoutEffect5 a b c d f e = _useLayoutEffect e $ args5 a b c d f
-- useMemo
useMemo :: forall a m. MonadHooks m => Effect a -> m a
useMemo = unsafeHooksEffect <<< runEffectFn1 _useMemo
useMemo1 :: forall a b m. MonadHooks m => b -> Effect a -> m a
useMemo1 a = unsafeHooksEffect <<< runEffectFn2 _useMemo1 a
useMemo2 :: forall a b c m. MonadHooks m => b -> c -> Effect a -> m a
useMemo2 a b = unsafeHooksEffect <<< runEffectFn3 _useMemo2 a b
useMemo3 :: forall a b c d m. MonadHooks m => b -> c -> d -> Effect a -> m a
useMemo3 a b c = unsafeHooksEffect <<< runEffectFn4 _useMemo3 a b c
useMemo4 :: forall a b c d e m. MonadHooks m => b -> c -> d -> e -> Effect a -> m a
useMemo4 a b c d = unsafeHooksEffect <<< runEffectFn5 _useMemo4 a b c d
useMemo5 :: forall a b c d e f m. MonadHooks m => b -> c -> d -> e -> f -> Effect a -> m a
useMemo5 a b c d e = unsafeHooksEffect <<< runEffectFn6 _useMemo5 a b c d e
-- useMemo :: forall a. Effect a -> Hooks a
-- useMemo = unsafeHooksEffect <<< runEffectFn1 _useMemo
-- useMemo1 :: forall a b. b -> Effect a -> Hooks a
-- useMemo1 a = unsafeHooksEffect <<< runEffectFn2 _useMemo1 a
-- useMemo2 :: forall a b c. b -> c -> Effect a -> Hooks a
-- useMemo2 a b = unsafeHooksEffect <<< runEffectFn3 _useMemo2 a b
foreign import _useMemo :: forall a. EffectFn1 (Effect a) a
foreign import _useMemo1 :: forall a b. EffectFn2 b (Effect a) a
foreign import _useMemo2 :: forall a b c. EffectFn3 b c (Effect a) a
foreign import _useMemo3 :: forall a b c d. EffectFn4 b c d (Effect a) a
foreign import _useMemo4 :: forall a b c d e. EffectFn5 b c d e (Effect a) a
foreign import _useMemo5 :: forall a b c d e f. EffectFn6 b c d e f (Effect a) a
-- foreign import _useMemo :: forall a. EffectFn1 (Effect a) a
-- foreign import _useMemo1 :: forall a b. EffectFn2 b (Effect a) a
-- foreign import _useMemo2 :: forall a b c. EffectFn3 b c (Effect a) a
......@@ -143,47 +147,41 @@ type Ref state = Tuple state (EffectFn1 state Unit)
foreign import _useRef :: forall r s. EffectFn2 (r -> s -> Tuple r s) r (Ref r)
useRef :: forall r m. MonadHooks m => r -> m (Ref r)
useRef :: forall r. r -> Hooks (Ref r)
useRef r = unsafeHooksEffect $ runEffectFn2 _useRef Tuple r
-- useReducer
type Reducer state action = Tuple state (EffectFn1 action Unit)
useReducer :: forall s a i m. MonadHooks m => (s -> a -> s) -> (i -> s) -> i -> m (Reducer s a)
useReducer f i j = unsafeHooksEffect $ runEffectFn4 _useReducer Tuple (mkFn2 f) j i
useReducer' :: forall s a m. MonadHooks m => (s -> a -> s) -> s -> m (Reducer s a)
useReducer' r = useReducer r identity
-- instance readReducer :: Read (Reducer s a) s where
-- read = _read0
-- instance writeReducer :: Write (Reducer s a) a where
-- write = runEffectFn2 _call1
foreign import _useReducer :: forall s a i x y. EffectFn4 (x -> y -> Tuple x y) (Fn2 s a s) i (i -> s) (Reducer s a)
-- useContext
foreign import _useContext :: forall a m. MonadHooks m => Context a -> m a
-- foreign import _useContext :: forall a. Context a -> Hooks a
useContext :: forall a m. MonadHooks m => Context a -> m a
useContext = _useContext
-- useContext :: forall a. Context a -> Hooks a
-- useContext = _useContext
-- useDebugValue
useDebugValue :: forall v v' m. MonadHooks m => v -> (v -> v') -> m Unit
useDebugValue :: forall v v'. v -> (v -> v') -> Hooks Unit
useDebugValue v = unsafeHooksEffect <<< runEffectFn2 _useDebugValue v
useDebugValue' :: forall v m. MonadHooks m => v -> m Unit
useDebugValue' :: forall v. v -> Hooks Unit
useDebugValue' = unsafeHooksEffect <<< runEffectFn1 _useDebugValuePrime
foreign import _useDebugValue :: forall v v'. EffectFn2 v (v -> v') Unit
foreign import _useDebugValuePrime :: forall v. EffectFn1 v Unit
-- foreign import _useImperativeHandle ::
-- ffi utilities
tuple :: forall a b c. a -> Tuple b c
tuple = runFn2 _tuple Tuple
foreign import _tuple :: forall a b c. Fn2 (a -> b -> Tuple a b) c (Tuple a b)
hook :: forall v. (Unit -> Effect v) -> Hooks v
hook = unsafeHooksEffect <<< delay
-- foreign import _useImperativeHandle ::
'use strict';
exports.react = require("react");
exports.reactDOM = require('react-dom');
var React = require("react");
var react_dom = require('react-dom');
function _simple(prop) {
return function() { return React[prop].apply(React, arguments); };
}
exports._isValid = _simple('isValidElement');
exports._children = function(c) { return React.Children.toArray(c); };
exports._memo = _simple('memo');
exports._memoPrime = _simple('memo');
exports._createRef = function() { return React.createRef(); };
exports._deref = function(r) { return r.current; };
// https://reactjs.org/docs/react-api.html#reactforwardref
// exports._forwardRef = _simple('forwardRef');
// creating and cloning elements
exports._cloneElement = _simple('cloneElement');
function _createElement(elem, props, children) {
var c = children.slice();
c.unshift(props);
c.unshift(elem);
return React.createElement.apply(React, c);
}
exports._createElement = _createElement;
exports._createFragment = function(children) {
return _createElement(React.Fragment, {}, children);
};
exports._contextProvider = function(c) { return c.Provider; };
exports._contextConsumer = function(c) { return c.Consumer; };
exports._createContext = function(ctor, val) {
var c = React.createContext(val);
return {provider: c.Provider, consumer: c.Consumer, context: c};
};
exports._render = function(a,b) { return react_dom.render(a,b); };
exports._named = function(name, f) { Object.defineProperty(f, 'name', {value: name, writable: false}); return f; };
module Reactix.React
( Element, cloneElement, createDOMElement
, Children, children
, class Childless
, class MonadHooks, unsafeHooksEffect
, Context, ContextProvider, ContextConsumer, createContext, provider, consumer
( React, react
, ReactDOM, reactDOM
, Element
, Hooks, unsafeHooksEffect, runHooks
, Context, Provider, Consumer, createContext, provider, consumer, consume
, render
, Component
, pureLeaf, pureTree, hooksLeaf, hooksTree
, class Componentesque
, createLeaf, createTree
, class IsComponent
, Component, createElement
, staticComponent, hooksComponent
, fragment
, NullableRef, createRef
, NullableRef, createRef, readNullableRef
, isValid
, Memo
, memo, memo'
, Memo, memo, memo'
)
where
import Prelude
import Data.Function.Uncurried ( Fn2, runFn2, mkFn2, Fn3, runFn3 )
import Data.Function.Uncurried (mkFn2)
import Data.Maybe ( Maybe )
import Data.Nullable ( Nullable, toMaybe )
import Effect ( Effect )
import Effect.Class ( class MonadEffect, liftEffect )
import Effect.Uncurried (EffectFn1, mkEffectFn1, EffectFn2, runEffectFn2)
import Effect.Uncurried (EffectFn1, mkEffectFn1, EffectFn2)
import Unsafe.Coerce (unsafeCoerce)
import Prim.Row (class Lacks)
import DOM.Simple as DOM
import FFI.Simple.PseudoArray as PA
import FFI.Simple ( (..), (...), args2, args3, delay, defineProperty )
foreign import data React :: Type
foreign import data ReactDOM :: Type
foreign import react :: React
foreign import reactDOM :: ReactDOM
-- basic types
newtype Component props = Component (EffectFn1 (Record props) Element)
-- | A React Element node
foreign import data Element :: Type
-- | A wrapper over an Array of Elements
foreign import data Children :: Type
-- | The Hooks monad
newtype Hooks a = Hooks (Effect a)
-- | A convenience for adding `children` to a list of props
type WithChildren p = ( children :: Children | p )
runHooks :: forall a. Hooks a -> Effect a
runHooks (Hooks a) = a
-- | This is to hide that it's actually implemented with Effect
class Monad m <= MonadHooks m where
unsafeHooksEffect :: forall a. Effect a -> m a
instance functorHooks :: Functor Hooks where
map f (Hooks a) = Hooks (map f a)
instance monadHooksEffect :: MonadHooks Effect where
unsafeHooksEffect = unsafeCoerce
class Childless (props :: # Type)
instance applyHooks :: Apply Hooks where
apply (Hooks f) (Hooks a) = Hooks (apply f a)
instance childlessLacksChildren :: Lacks "children" props => Childless props
instance applicativeHooks :: Applicative Hooks where
pure = Hooks <<< pure
class Componentesque (c :: # Type -> Type)
instance bindHooks :: Bind Hooks where
bind (Hooks a) f = Hooks (a >>= (runHooks <<< f))
newtype Component p = Component (EffectFn1 (Record p) Element)
instance monadHooks :: Monad Hooks
unsafeHooksEffect :: forall a. Effect a -> Hooks a
unsafeHooksEffect = Hooks
instance componentesqueComponent :: Componentesque Component
class IsComponent component (props :: # Type) children
| component -> props
, component -> children
foreign import data Memo :: # Type -> Type
instance componentIsComponent :: IsComponent (Component props) props (Array Element)
instance memoIsComponent :: IsComponent (Memo props) props (Array Element)
instance stringIsComponent :: IsComponent String props (Array Element)
instance providerIsComponent :: IsComponent (Provider v) (value :: v) (Array Element)
instance consumerIsComponent :: IsComponent (Consumer v) () (v -> Element)
instance componentesqueMemo :: Componentesque Memo
createElement
:: forall component props
. IsComponent component props (Array Element)
=> component -> Record props -> Array Element -> Element
createElement = rawCreateElement
-- Component building
-- | Creates a pure leaf component from a function
pureLeaf ::
forall props. Childless props
=> String
-> (Record props -> Element)
-> Component props
pureLeaf name f = named name $ Component (mkEffectFn1 $ pure <<< f)
-- | Creates a pure tree component from a function
pureTree ::
forall props. Childless props
=> String
-> (Record props -> Array Element -> Element)
-> Component (WithChildren props)
pureTree name c = named name $ Component $ mkEffectFn1 c'
where
c' :: Record (WithChildren props) -> Effect Element
c' props = pure $ c (unsafeCoerce props) (children props.children)
-- | Creates a hooks leaf component from a function
hooksLeaf ::
forall props. Childless props
=> String
-> (forall m. MonadHooks m => Record props -> m Element)
-> Component props
hooksLeaf name c = named name $ Component (mkEffectFn1 c)
hooksTree ::
forall props. Childless props
=> String
-> (forall m. MonadHooks m
=> Record props
-> Array Element
-> m Element)
-> Component (WithChildren props)
hooksTree name c = named name $ Component $ mkEffectFn1 c'
where
c' :: Record (WithChildren props) -> Effect Element
c' props = c (unsafeCoerce props) (children props.children)
-- element creation
-- | Creates a DOM element of the given tag
createDOMElement :: forall props. String -> Record props -> Array Element -> Element
createDOMElement = runFn3 _createElement
-- | The type of a function that can be turned into a component with
-- | `staticComponent`. Will not have access to the `Hooks` Monad.
-- | Creates a leaf component from a props Record
createLeaf ::
forall props cpt.
Childless props
=> Componentesque cpt
=> cpt props
-> Record props
-> Element
createLeaf c p = runFn3 _createElement c p []
type StaticComponent props = Record props -> Array Element -> Element
-- | Creates a tree component from a props Record and an Array of children
createTree ::
forall props cpt.
Childless props
=> Componentesque cpt
=> cpt (WithChildren props)
-> Record props
-> Array Element
-> Element
createTree = runFn3 _createElement
-- createElement :: forall c p. CreateElement c p => c -> p -> Array Element ->
-- | Turns a `StaticComponent` function into a Component
staticComponent :: forall props. String -> StaticComponent props -> Component props
staticComponent name c = Component $ named name $ mkEffectFn1 c'
where
c' :: Record props -> Effect Element
c' props = pure $ c props (children props)
foreign import _createElement :: forall c p cs e. Fn3 c p cs e
-- | The type of a function that can be turned into a component with
-- | `hooksComponent`. Will have access to the `Hooks` Monad.
type HooksComponent props = Record props -> Array Element -> Hooks Element
-- | Turns a `HooksComponent` function into a Component
hooksComponent :: forall props. String -> HooksComponent props -> Component props
hooksComponent name c = Component $ named name $ mkEffectFn1 c'
where
c' :: Record props -> Effect Element
c' props = runHooks $ c props (children props)
rawCreateElement :: forall c p cs. c -> p -> Array cs -> Element
rawCreateElement c p cs = react ... "createElement" $ args
where args = PA.unshift c $ PA.unshift p cs
-- Element cloning
-- | Clones an element,
cloneElement :: forall props. Element -> Record props -> Element
cloneElement = runFn2 _cloneElement
-- | Clones an element. Quite unsafe because tripping through Element
-- | loses the type of the props. Be careful.
foreign import _cloneElement :: forall p. Fn2 Element p Element
-- cloneElement :: forall props. Element -> Record props -> Element
-- cloneElement e p = react ... "cloneElement" $ args2 e p
-- Fragment creation
-- TODO: add key support
-- | Combines several elements together
fragment :: Array Element -> Element
fragment = _createFragment
foreign import _createFragment :: Array Element -> Element
fragment es = rawCreateElement (react .. "Fragment") {} es
instance semigroupElement :: Semigroup Element where
append a b = fragment [a, b]
-- | Renders a React Element to a real Element
render :: forall m. MonadEffect m => MonadHooks m => Element -> DOM.Element -> m Unit
render e d = liftEffect (runEffectFn2 _render e d)
foreign import _render :: EffectFn2 Element DOM.Element Unit
render :: Element -> DOM.Element -> Effect Unit
render e d = delay \_ -> react ... "render" $ args2 e d
-- -- Memoisation
-- Memoisation
foreign import data Memo :: # Type -> Type
memo ::
forall props.
Component props
-> (Record props -> Record props -> Boolean)
-> Memo props
memo c f = runFn2 _memo c (mkFn2 f)
memo c f = react ... "memo" $ args2 c (mkFn2 f)
memo' :: forall props. Component props -> Memo props
memo' = _memoPrime
foreign import _memo :: forall c f r. Fn2 c f r
foreign import _memoPrime :: forall c r. c -> r
-- Children
foreign import _children :: Children -> Array Element
children :: Children -> Array Element
children = _children
memo' c = react ... "memo" $ [ c ]
......@@ -201,40 +158,34 @@ children = _children
foreign import data Context :: Type -> Type
-- | The Provider for a React Context
foreign import data ContextProvider :: Type -> Type
foreign import data Provider :: Type -> Type
-- | The Consumer for a React Context
foreign import data ContextConsumer :: Type -> Type
foreign import _createContext :: forall v. v -> Context v
foreign import _contextProvider :: forall v. Context v -> ContextProvider v
foreign import _contextConsumer :: forall v. Context v -> ContextConsumer v
foreign import data Consumer :: Type -> Type
-- | Creates a `Context` from a given value
createContext :: forall v. v -> Context v
createContext = _createContext
createContext v = react ... "createContext" $ [v]
provider :: forall v. Context v -> ContextProvider v
provider = _contextProvider
provider :: forall v. Context v -> Provider v
provider c = c .. "Provider"
consumer :: forall v. Context v -> ContextConsumer v
consumer = _contextConsumer
consumer :: forall v. Context v -> Consumer v
consumer c = c .. "Consumer"
consume :: forall v. Context v -> (v -> Element) -> Element
consume c f = rawCreateElement c {} [f]
-- Ref creation
foreign import data NullableRef :: Type -> Type
foreign import _createRef :: forall r. Unit -> NullableRef r
createRef :: forall r. Unit -> NullableRef r
createRef = _createRef
foreign import _deref :: forall r. NullableRef r -> Nullable r
createRef _ = react ... "createRef" $ []
readNullableRef :: forall r. NullableRef r -> Maybe r
readNullableRef = toMaybe <<< _deref
readNullableRef r = toMaybe $ r .. "current"
-- Ref Forwarding
......@@ -244,12 +195,18 @@ readNullableRef = toMaybe <<< _deref
-- foreign import _forwardRef :: forall r p. (Fn2 p r Element) -> Forwarded p
named :: forall c. String -> c -> c
named = runFn2 _named
named
:: forall props
. String
-> EffectFn1 (Record props) Element
-> EffectFn1 (Record props) Element
named = flip $ defineProperty "name"
isValid :: forall a. a -> Boolean
isValid a = react ... "isValidElement" $ [ a ]
foreign import _named :: forall c. Fn2 String c c
-- Utils
foreign import _isValid :: forall a. a -> Boolean
children :: forall a. a -> Array Element
children a = react .. "Children" ... "toArray" $ [ (a .. "children") ]
isValid :: forall a. a -> Boolean
isValid = _isValid
-- | https://reactjs.org/docs/events.html
module Reactix.SyntheticEvent where
import Prelude
import DOM.Simple as DOM
import Effect ( Effect )
import Effect.Uncurried ( EffectFn1, runEffectFn1 )
import FFI.Simple ( (..), (...) )
class IsSyntheticEvent e
foreign import data NativeEvent :: Type
foreign import data MouseEvent :: Type
foreign import data KeyboardEvent :: Type
instance keyboardEventIsSyntheticEvent :: IsSyntheticEvent KeyboardEvent
instance mouseEventIsSyntheticEvent :: IsSyntheticEvent MouseEvent
bubbles :: forall e. IsSyntheticEvent e => e -> Boolean
bubbles e = e .. "bubbles"
cancelable :: forall e. IsSyntheticEvent e => e -> Boolean
cancelable e = e .. "cancelable"
isTrusted :: forall e. IsSyntheticEvent e => e -> Boolean
isTrusted e = e .. "isTrusted"
defaultPrevented :: forall e. IsSyntheticEvent e => e -> Boolean
defaultPrevented e = e .. "defaultPrevented"
eventPhase :: forall e. IsSyntheticEvent e => e -> Number
eventPhase e = e .. "eventPhase"
timestamp :: forall e. IsSyntheticEvent e => e -> Number
timestamp e = e .. "timeStamp"
type' :: forall e. IsSyntheticEvent e => e -> String
type' e = e .. "type"
-- target :: forall e. IsSyntheticEvent e => e -> NativeEventTarget
-- target e = e .. "target"
-- currentTarget :: forall e. IsSyntheticEvent e => e -> NativeEventTarget
-- currentTarget e = e .. "currentTarget"
-- nativeEvent :: forall e. IsSyntheticEvent e => e -> NativeEvent
-- nativeEvent e = e .. "nativeEvent"
stopPropagation :: forall e. IsSyntheticEvent e => e -> Effect Unit
stopPropagation e = e ... "stopPropagation" $ []
preventDefault :: forall e. IsSyntheticEvent e => e -> Effect Unit
preventDefault e = e ... "preventDefault" $ []
isPropagationStopped :: forall e. IsSyntheticEvent e => e -> Effect Unit
isPropagationStopped e = e ... "isPropagationStopped" $ []
isDefaultPrevented :: forall e. IsSyntheticEvent e => e -> Effect Unit
isDefaultPrevented e = e ... "isDefaultPrevented" $ []
-- Events with Modifier keys
-- | This class is used to access information about modifier keys for
-- | supported events
class HasModifierKeys e
instance mouseEventHasModifierKeys :: HasModifierKeys MouseEvent
instance keyboardEventHasModifierKeys :: HasModifierKeys KeyboardEvent
-- instance touchEventHasModifierKeys :: HasModifierKeys TouchEvent
altKey :: forall e. HasModifierKeys e => e -> Boolean
altKey e = e .. "altKey"
ctrlKey :: forall e. HasModifierKeys e => e -> Boolean
ctrlKey e = e .. "ctrlKey"
shiftKey :: forall e. HasModifierKeys e => e -> Boolean
shiftKey e = e .. "shiftKey"
metaKey :: forall e. HasModifierKeys e => e -> Boolean
metaKey e = e .. "metaKey"
getModifierState :: forall e. HasModifierKeys e => e -> String -> Boolean
getModifierState e s = e ... "getModifierState" $ [ s ]
-- Keyboard Events
key :: KeyboardEvent -> String
key e = e .. "key"
which :: KeyboardEvent -> Number
which e = e .. "which"
charCode :: KeyboardEvent -> Int
charCode e = e .. "charCode"
keyCode :: KeyboardEvent -> Number
keyCode e = e .. "keyCode"
locale :: KeyboardEvent -> String
locale e = e .. "locale"
location :: KeyboardEvent -> Number
location e = e .. "location"
repeat :: KeyboardEvent -> Boolean
repeat e = e .. "repeat"
button :: MouseEvent -> Number
button e = e .. "button"
buttons :: MouseEvent -> Number
buttons e = e .. "buttons"
-- relatedTarget :: MouseEvent -> NativeEventTarget
-- relatedTarget e = e .. "relatedTarget"
clientX :: MouseEvent -> Number
clientX e = e .. "clientX"
clientY :: MouseEvent -> Number
clientY e = e .. "clientY"
pageX :: MouseEvent -> Number
pageX e = e .. "pageX"
pageY :: MouseEvent -> Number
pageY e = e .. "pageY"
screenX :: MouseEvent -> Number
screenX e = e .. "screenX"
screenY :: MouseEvent -> Number
screenY e = e .. "screenY"
-- foreign import data TouchEvent :: Type
-- changedTouches :: TouchEvent -> NativeTouchList
-- targetTouches :: TouchEvent -> NativeTouchList
-- touches :: TouchEvent -> NativeTouchList
-- foreign import data NativeDataTransfer :: Type
-- foreign import data NativeAbstractView :: Type
-- foreign import data NativeTouchList :: Type
-- foreign import data AnimationEvent :: Type
-- animationName :: AnimationEvent -> String
-- pseudoElement :: AnimationEvent -> String
-- elapsedTime :: AnimationEvent -> Number
-- foreign import data ClipboardEvent :: Type
-- clipboardData :: ClipboardEvent -> NativeDataTransfer
-- foreign import data CompositionEvent :: Type
-- data' :: SyntheticCompositionEvent -> String
-- foreign import data FocusEvent :: Type
-- relatedTarget :: FocusEvent -> NativeEventTarget
-- foreign import data TransitionEvent :: Type
-- propertyName :: TransitionEvent -> String
-- pseudoElement :: TransitionEvent -> String
-- elapsedTime :: TransitionEvent -> Number
-- foreign import data UIEvent :: Type
-- detail :: UIEvent -> Number
-- view :: UIEvent -> NativeAbstractView
-- foreign import data WheelEvent :: Type
-- deltaMode :: WheelEvent -> Number
-- deltaX :: WheelEvent -> Number
-- deltaY :: WheelEvent -> Number
-- deltaZ :: WheelEvent -> Number
'use strict';
const utils = require('react-dom/test-utils');
const test = require('react-testing-library');
function spread(obj,prop) {
return function() {
return obj[prop].apply(obj, Array.from(arguments));
};
}
exports._act = function(e) {
var ret;
utils.act(function() { ret = e(); });
return ret;
};
exports._render = spread(test,'render');
exports._fireEvent = function(name, obj) {
test.fireEvent[name].call(test.fireEvent, obj);
};
'use strict';
exports.testUtils = require('react-dom/test-utils');
exports.testingLibrary = require('react-testing-library');
......@@ -3,36 +3,48 @@ module Reactix.Test
, render
, Rendered
, fireEvent, fireClick
, cleanup
) where
import Prelude ( Unit )
import Prelude
import Effect ( Effect )
import Effect.Uncurried ( EffectFn1, runEffectFn1, EffectFn2, runEffectFn2 )
import Data.Function.Uncurried ( Fn2, runFn2 )
import DOM.Simple as DOM
import Reactix.React ( Element )
import Reactix.React ( react, Element )
import FFI.Simple ( (..), (...), delay )
import DOM.Simple.Console
foreign import data TestUtils :: Type
foreign import testUtils :: TestUtils
foreign import data Testing :: Type
foreign import testingLibrary :: Testing
type Rendered =
{ getByText :: EffectFn1 String Element
, getByTestId :: EffectFn1 String Element
{ getByText :: String -> Effect Element
, getByTestId :: String -> Effect Element
, container :: DOM.Element
, asFragment :: Effect DOM.Fragment }
render :: Element -> Effect Rendered
render = runEffectFn1 _render
foreign import _render :: EffectFn1 Element Rendered
render e = pure $ raw { getByText=getByText, getByTestId=getByTestId }
where getByText = runEffectFn1 raw.getByText
getByTestId = runEffectFn1 raw.getByTestId
raw = testingLibrary ... "render" $ [e]
-- | Make react behave more predictably in tests
act :: forall t. Effect t -> Effect t
act = runEffectFn1 _act
foreign import _act :: forall t. EffectFn1 (Effect t) t
act :: forall t. (Unit -> Effect t) -> Effect t
act f = testUtils ... "act" $ [ delay f ]
fireClick :: DOM.Element -> Effect Unit
fireClick = fireEvent "click"
fireEvent :: String -> DOM.Element -> Effect Unit
fireEvent = runEffectFn2 _fireEvent
fireEvent ev el = pure $ (testingLibrary .. "fireEvent") ... ev $ [el]
cleanup :: Effect Unit
cleanup = delay $ \_ -> pure $ testingLibrary ... "cleanup" $ []
foreign import _fireEvent :: EffectFn2 String DOM.Element Unit
......@@ -8,35 +8,35 @@ import Data.Traversable ( traverse, traverse_, sequence_ )
import Data.Tuple ( Tuple(..) )
import Data.Tuple.Nested ( (/\) )
import Effect ( Effect )
import Effect.Aff ( Aff )
import Effect.Class ( liftEffect )
import Effect.Ref as Ref
import Effect.Uncurried ( EffectFn1, mkEffectFn1, runEffectFn1 )
-- import Effect.Aff (launchAff_)
import Test.Spec ( Spec, describe, it )
import Test.Spec.Assertions ( shouldEqual )
-- import Test.Spec.QuickCheck (quickCheck')
import DOM.Simple.Console ( log2 )
import DOM.Simple as DOM
import DOM.Simple.Document as Document
import DOM.Simple.Element as Element
import DOM.Simple.Event as Event
import Reactix as R
import Reactix.Test as RT
import Reactix.DOM.Raw ( button, div, i, text )
import Reactix.Hooks ( useState )
import Reactix.Hooks ( useState, useEffect, useLayoutEffect )
staticTest :: Spec Unit
staticTest =
describe "Basic DOM rendering" do
it "Simple elements" do
describe "Basic DOM rendering" $ do
it "Simple elements" $ do
root <- liftEffect $ RT.render elem
(Element.name <$> Element.children root.container) `shouldEqual` ["I"]
it "Fragments" do
children /\ count <- liftEffect $
do let root = Document.createElement "div"
RT.act $ R.render frag root
pure $ Tuple (Element.children root) (Element.childCount root)
count `shouldEqual` 2
let children = Element.children root.container
(Element.name <$> children) `shouldEqual` ["I"]
(Element.innerHTML <$> children) `shouldEqual` ["hello world"]
it "Fragments" $ do
root <- liftEffect $ RT.render $ frag
Element.childCount root.container `shouldEqual` 2
let children = Element.children root.container
A.length children `shouldEqual` 2
(Element.name <$> children) `shouldEqual` ["I", "I"]
(Element.innerHTML <$> children) `shouldEqual` ["hello","world"]
......@@ -46,28 +46,26 @@ staticTest =
type CounterProps = ( count :: Int )
counterCpt :: R.Component CounterProps
counterCpt = R.hooksLeaf "Counter" cpt
counterCpt = R.hooksComponent "Counter" cpt
where
cpt :: forall m. R.MonadHooks m => Record CounterProps -> m R.Element
cpt {count} = do
y /\ setY <- useState $ pure count
cpt {count} _ = do
y /\ setY <- useState $ \_ -> pure count
pure $ div { className: "counter" }
[ button { type: "button", onClick: onclick setY (y + 1) } [ text "++" ]
, div {} [ text (show y) ] ]
onclick set to = mkEffectFn1 $ \e -> do
runEffectFn1 set to
onclick set to = mkEffectFn1 $ \e -> runEffectFn1 set to
counterTest :: Spec Unit
counterTest =
describe "Counter" do
it "Works for plain components" $ do
let counter = R.createLeaf counterCpt {count: 0}
let counter = R.createElement counterCpt {count: 0} []
liftEffect (RT.render counter) >>= test
it "Works for memoised components" $ do
let counter = R.createLeaf (R.memo counterCpt (==)) {count: 0}
let counter = R.createElement (R.memo counterCpt (==)) {count: 0} []
liftEffect (RT.render counter) >>= test
it "works for memo'ised components" $ do
let counter = R.createLeaf (R.memo' counterCpt) {count: 0}
let counter = R.createElement (R.memo' counterCpt) {count: 0} []
liftEffect (RT.render counter) >>= test
where
test root = do
......@@ -86,12 +84,105 @@ counterTest =
A.length children4 `shouldEqual` 2
(Element.innerHTML <$> children4) `shouldEqual` ["++", "2"]
listTest :: Spec Unit
listTest = pure unit
data EffectorState = Fresh | Initialised | Done
derive instance eqEffectorState :: Eq EffectorState
instance showEffectorState :: Show EffectorState where
show Fresh = "fresh"
show Initialised = "initialised"
show Done = "done"
type EffectorProps = ( stateRef :: Ref.Ref EffectorState )
effectorCpt :: R.Component EffectorProps
effectorCpt = R.hooksComponent "Effector" cpt
where cpt {stateRef} _ = do
useEffect $ \_ -> do
Ref.write Initialised stateRef
pure $ \_ -> Ref.write Done stateRef
pure $ div {} []
-- TODO: test it's firing at the right time
effectorTest :: Spec Unit
effectorTest =
describe "Effector" do
it "Works for plain components" $
test $ effectorCpt
it "works for memo'ised components" $
test $ R.memo' effectorCpt
where
test :: forall cpt. R.IsComponent cpt EffectorProps (Array R.Element) => cpt -> Aff Unit
test cpt = do
ref <- liftEffect $ Ref.new Fresh
let effector = R.createElement cpt {stateRef: ref} []
root <- liftEffect (RT.render effector)
state <- liftEffect $ Ref.read ref
state `shouldEqual` Initialised
liftEffect $ RT.cleanup
state' <- liftEffect $ Ref.read ref
state' `shouldEqual` Done
layoutEffectorCpt :: R.Component EffectorProps
layoutEffectorCpt = R.hooksComponent "LayoutEffector" cpt
where cpt {stateRef} _ = do
useLayoutEffect $ \_ -> do
Ref.write Initialised stateRef
pure $ \_ -> Ref.write Done stateRef
pure $ div {} []
-- TODO: test it's firing at the right time
layoutEffectorTest :: Spec Unit
layoutEffectorTest =
describe "LayoutEffector" do
it "Works for plain components" $
test $ layoutEffectorCpt
it "works for memo'ised components" $
test $ R.memo' layoutEffectorCpt
where
test :: forall cpt. R.IsComponent cpt EffectorProps (Array R.Element) => cpt -> Aff Unit
test cpt = do
ref <- liftEffect $ Ref.new Fresh
let effector = R.createElement cpt {stateRef: ref} []
root <- liftEffect (RT.render effector)
state <- liftEffect $ Ref.read ref
state `shouldEqual` Initialised
liftEffect $ RT.cleanup
state' <- liftEffect $ Ref.read ref
state' `shouldEqual` Done
type ContextProps = ()
-- contextualCpt :: R.Component ContextProps
-- contextualCpt = R.hooksComponent "Contextual" cpt
-- where cpt {stateRef} _ = do
-- useEffect $ \_ -> do
-- Ref.write Initialised stateRef
-- pure $ \_ -> Ref.write Done stateRef
-- pure $ div {} []
-- contextTest :: Spec Unit
-- contextTest =
-- describe "Context" do
-- it "Works for plain components" $
-- test $ contextualCpt
-- where test cpt = pure unit
-- reducerTest :: Spec Unit
-- memoTest :: Spec Unit
-- refTest :: Spec Unit
-- imperativeHandleTest :: Spec Unit
-- debugValueTest :: Spec Unit
-- listTest :: Spec Unit
-- listTest = pure unit
spec :: Spec Unit
spec = sequence_
[ staticTest
, counterTest
, listTest
, effectorTest
, layoutEffectorTest
]
-- , listTest
-- ]
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment