diff --git a/examples/controlled-input/src/ControlledInput.purs b/examples/controlled-input/src/ControlledInput.purs index 2a36b73..37c1fc1 100644 --- a/examples/controlled-input/src/ControlledInput.purs +++ b/examples/controlled-input/src/ControlledInput.purs @@ -3,11 +3,10 @@ module ControlledInput where import Prelude import Data.Maybe (Maybe(..), fromMaybe, maybe) -import Data.Nullable (toMaybe) import React.Basic (ReactComponent, react) import React.Basic.DOM as R -import React.Basic.DOM.Events (targetValue, timeStamp) -import React.Basic.DOM.Events as Events +import React.Basic.DOM.Events (preventDefault, targetValue, timeStamp) +import React.Basic.Events as Events component :: ReactComponent {} component = react @@ -16,14 +15,15 @@ component = react , receiveProps: \_ _ _ -> pure unit , render: \_ state setState -> R.div_ - [ R.p_ [ R.input { onChange: Events.handler (Events.preventDefault >>> Events.merge { targetValue, timeStamp }) \{ timeStamp, targetValue } -> - setState \_ -> { value: fromMaybe "" (toMaybe targetValue) - , timeStamp: Just timeStamp - } + [ R.p_ [ R.input { onChange: Events.handler (preventDefault >>> Events.merge { targetValue, timeStamp }) + \{ timeStamp, targetValue } -> setState \_ -> + { value: fromMaybe "" targetValue + , timeStamp: Just timeStamp + } , value: state.value } ] , R.p_ [ R.text ("Current value = " <> show state.value) ] - , R.p_ (maybe [] (\ts -> [R.text ("Changed at: " <> show ts)]) state.timeStamp) + , R.p_ [ R.text ("Changed at = " <> maybe "never" show state.timeStamp) ] ] } diff --git a/generated-docs/React/Basic/DOM/Events.md b/generated-docs/React/Basic/DOM/Events.md index 0ef5c7c..ec636d4 100644 --- a/generated-docs/React/Basic/DOM/Events.md +++ b/generated-docs/React/Basic/DOM/Events.md @@ -1,23 +1,6 @@ ## Module React.Basic.DOM.Events -This module defines safe event function and property accessors. - -#### `EventHandler` - -``` purescript -type EventHandler = EffFn1 (react :: ReactFX) SyntheticEvent Unit -``` - -An event handler, which receives a `SyntheticEvent` and performs some -effects in return. - -#### `SyntheticEvent` - -``` purescript -data SyntheticEvent :: Type -``` - -Event data that we receive from React. +This module defines safe DOM event function and property accessors. #### `DOMNode` @@ -35,62 +18,6 @@ data DOMEvent :: Type The underlying browser Event. -#### `EventFn` - -``` purescript -newtype EventFn a b -``` - -Encapsulates a safe event operation. `EventFn`s can be composed -to perform multiple operations. - -For example: - -```purs -input { onChange: handler (preventDefault >>> targetValue) - \value -> setState \_ -> { value } - } -``` - -##### Instances -``` purescript -Semigroupoid EventFn -Category EventFn -(IsSymbol l, RowCons l (EventFn a b) fns_rest fns, RowCons l b r_rest r, RowLacks l fns_rest, RowLacks l r_rest, Merge rest fns_rest a r_rest) => Merge (Cons l (EventFn a b) rest) fns a r -``` - -#### `handler` - -``` purescript -handler :: forall a. EventFn SyntheticEvent a -> (a -> Eff (react :: ReactFX) Unit) -> EventHandler -``` - -Create an `EventHandler`, given an `EventFn` and a callback. - -For example: - -```purs -input { onChange: handler targetValue - \value -> setState \_ -> { value } - } -``` - -#### `merge` - -``` purescript -merge :: forall a fns fns_list r. RowToList fns fns_list => Merge fns_list fns a r => { | fns } -> EventFn a ({ | r }) -``` - -Merge multiple `EventFn` operations and collect their results. - -For example: - -```purs -input { onChange: handler (merge { targetValue, timeStamp }) - \{ targetValue, timeStamp } -> setState \_ -> { ... } - } -``` - #### `bubbles` ``` purescript @@ -184,13 +111,13 @@ target :: EventFn SyntheticEvent DOMNode #### `targetChecked` ``` purescript -targetChecked :: EventFn SyntheticEvent (Nullable Boolean) +targetChecked :: EventFn SyntheticEvent (Maybe Boolean) ``` #### `targetValue` ``` purescript -targetValue :: EventFn SyntheticEvent (Nullable String) +targetValue :: EventFn SyntheticEvent (Maybe String) ``` #### `timeStamp` @@ -205,17 +132,4 @@ timeStamp :: EventFn SyntheticEvent Number type_ :: EventFn SyntheticEvent String ``` -#### `Merge` - -``` purescript -class Merge (rl :: RowList) fns a r | rl -> fns, rl a -> r where - mergeImpl :: RLProxy rl -> { | fns } -> EventFn a ({ | r }) -``` - -##### Instances -``` purescript -Merge Nil () a () -(IsSymbol l, RowCons l (EventFn a b) fns_rest fns, RowCons l b r_rest r, RowLacks l fns_rest, RowLacks l r_rest, Merge rest fns_rest a r_rest) => Merge (Cons l (EventFn a b) rest) fns a r -``` - diff --git a/generated-docs/React/Basic/Events.md b/generated-docs/React/Basic/Events.md new file mode 100644 index 0000000..4bcd4b6 --- /dev/null +++ b/generated-docs/React/Basic/Events.md @@ -0,0 +1,98 @@ +## Module React.Basic.Events + +#### `EventHandler` + +``` purescript +type EventHandler = EffFn1 (react :: ReactFX) SyntheticEvent Unit +``` + +An event handler, which receives a `SyntheticEvent` and performs some +effects in return. + +#### `SyntheticEvent` + +``` purescript +data SyntheticEvent :: Type +``` + +Event data that we receive from React. + +#### `EventFn` + +``` purescript +newtype EventFn a b +``` + +Encapsulates a safe event operation. `EventFn`s can be composed +to perform multiple operations. + +For example: + +```purs +input { onChange: handler (preventDefault >>> targetValue) + \value -> setState \_ -> { value } + } +``` + +##### Instances +``` purescript +Semigroupoid EventFn +Category EventFn +(IsSymbol l, RowCons l (EventFn a b) fns_rest fns, RowCons l b r_rest r, RowLacks l fns_rest, RowLacks l r_rest, Merge rest fns_rest a r_rest) => Merge (Cons l (EventFn a b) rest) fns a r +``` + +#### `unsafeEventFn` + +``` purescript +unsafeEventFn :: forall a b. (a -> b) -> EventFn a b +``` + +Unsafely create an `EventFn`. This function should be avoided. +Use the helper functions specific to your platform (such as `React.Basic.DOM.Events`). + +#### `handler` + +``` purescript +handler :: forall a. EventFn SyntheticEvent a -> (a -> Eff (react :: ReactFX) Unit) -> EventHandler +``` + +Create an `EventHandler`, given an `EventFn` and a callback. + +For example: + +```purs +input { onChange: handler targetValue + \value -> setState \_ -> { value } + } +``` + +#### `merge` + +``` purescript +merge :: forall a fns fns_list r. RowToList fns fns_list => Merge fns_list fns a r => { | fns } -> EventFn a ({ | r }) +``` + +Merge multiple `EventFn` operations and collect their results. + +For example: + +```purs +input { onChange: handler (merge { targetValue, timeStamp }) + \{ targetValue, timeStamp } -> setState \_ -> { ... } + } +``` + +#### `Merge` + +``` purescript +class Merge (rl :: RowList) fns a r | rl -> fns, rl a -> r where + mergeImpl :: RLProxy rl -> { | fns } -> EventFn a ({ | r }) +``` + +##### Instances +``` purescript +Merge Nil () a () +(IsSymbol l, RowCons l (EventFn a b) fns_rest fns, RowCons l b r_rest r, RowLacks l fns_rest, RowLacks l r_rest, Merge rest fns_rest a r_rest) => Merge (Cons l (EventFn a b) rest) fns a r +``` + + diff --git a/src/React/Basic/DOM.purs b/src/React/Basic/DOM.purs index 64a0bcb..288c468 100644 --- a/src/React/Basic/DOM.purs +++ b/src/React/Basic/DOM.purs @@ -8,7 +8,7 @@ module React.Basic.DOM where import React.Basic (JSX, ReactComponent, createElement) -import React.Basic.DOM.Events (EventHandler) +import React.Basic.Events (EventHandler) import Unsafe.Coerce (unsafeCoerce) -- | Create a text node. diff --git a/src/React/Basic/DOM/Events.purs b/src/React/Basic/DOM/Events.purs index ae0ea61..256a9f2 100644 --- a/src/React/Basic/DOM/Events.purs +++ b/src/React/Basic/DOM/Events.purs @@ -1,13 +1,8 @@ --- | This module defines safe event function and property accessors. +-- | This module defines safe DOM event function and property accessors. module React.Basic.DOM.Events - ( EventHandler - , SyntheticEvent - , DOMNode + ( DOMNode , DOMEvent - , EventFn - , handler - , merge , bubbles , cancelable , currentTarget @@ -27,112 +22,30 @@ module React.Basic.DOM.Events , targetValue , timeStamp , type_ - , class Merge - , mergeImpl ) where -import Prelude - -import Control.Monad.Eff (Eff) -import Control.Monad.Eff.Uncurried (EffFn1, mkEffFn1) -import Data.Nullable (Nullable) -import Data.Record (delete, get, insert) -import Data.Symbol (class IsSymbol, SProxy(SProxy)) -import React.Basic (ReactFX) -import Type.Row (kind RowList, class RowToList, class RowLacks, RLProxy(..), Cons, Nil) +import Data.Maybe (Maybe) +import Data.Nullable (toMaybe) +import React.Basic.Events (EventFn, SyntheticEvent, unsafeEventFn) import Unsafe.Coerce (unsafeCoerce) --- | An event handler, which receives a `SyntheticEvent` and performs some --- | effects in return. -type EventHandler = EffFn1 (react :: ReactFX) SyntheticEvent Unit - --- | Event data that we receive from React. -foreign import data SyntheticEvent :: Type - -- | An _actual_ DOM node (not a virtual DOM element!) foreign import data DOMNode :: Type -- | The underlying browser Event. foreign import data DOMEvent :: Type --- | Encapsulates a safe event operation. `EventFn`s can be composed --- | to perform multiple operations. --- | --- | For example: --- | --- | ```purs --- | input { onChange: handler (preventDefault >>> targetValue) --- | \value -> setState \_ -> { value } --- | } --- | ``` -newtype EventFn a b = EventFn (a -> b) - -derive newtype instance semigroupoidBuilder :: Semigroupoid EventFn -derive newtype instance categoryBuilder :: Category EventFn - --- | Create an `EventHandler`, given an `EventFn` and a callback. --- | --- | For example: --- | --- | ```purs --- | input { onChange: handler targetValue --- | \value -> setState \_ -> { value } --- | } --- | ``` -handler :: forall a. EventFn SyntheticEvent a -> (a -> Eff (react :: ReactFX) Unit) -> EventHandler -handler (EventFn fn) cb = mkEffFn1 $ fn >>> cb - -class Merge (rl :: RowList) fns a r | rl -> fns, rl a -> r where - mergeImpl :: RLProxy rl -> Record fns -> EventFn a (Record r) - -instance mergeNil :: Merge Nil () a () where - mergeImpl _ _ = EventFn \_ -> {} - -instance mergeCons - :: ( IsSymbol l - , RowCons l (EventFn a b) fns_rest fns - , RowCons l b r_rest r - , RowLacks l fns_rest - , RowLacks l r_rest - , Merge rest fns_rest a r_rest - ) - => Merge (Cons l (EventFn a b) rest) fns a r - where - mergeImpl _ fns = EventFn \a -> - let EventFn inner = mergeImpl (RLProxy :: RLProxy rest) (delete l fns) - EventFn f = get l fns - in insert l (f a) (inner a) - where - l = SProxy :: SProxy l - --- | Merge multiple `EventFn` operations and collect their results. --- | --- | For example: --- | --- | ```purs --- | input { onChange: handler (merge { targetValue, timeStamp }) --- | \{ targetValue, timeStamp } -> setState \_ -> { ... } --- | } --- | ``` -merge - :: forall a fns fns_list r - . RowToList fns fns_list - => Merge fns_list fns a r - => Record fns - -> EventFn a (Record r) -merge = mergeImpl (RLProxy :: RLProxy fns_list) - bubbles :: EventFn SyntheticEvent Boolean -bubbles = EventFn \e -> (unsafeCoerce e).bubbles +bubbles = unsafeEventFn \e -> (unsafeCoerce e).bubbles cancelable :: EventFn SyntheticEvent Boolean -cancelable = EventFn \e -> (unsafeCoerce e).cancelable +cancelable = unsafeEventFn \e -> (unsafeCoerce e).cancelable currentTarget :: EventFn SyntheticEvent DOMNode -currentTarget = EventFn \e -> (unsafeCoerce e).currentTarget +currentTarget = unsafeEventFn \e -> (unsafeCoerce e).currentTarget eventPhase :: EventFn SyntheticEvent Int -eventPhase = EventFn \e -> (unsafeCoerce e).eventPhase +eventPhase = unsafeEventFn \e -> (unsafeCoerce e).eventPhase eventPhaseNone :: Int eventPhaseNone = 0 @@ -147,42 +60,42 @@ eventPhaseBubbling :: Int eventPhaseBubbling = 3 isTrusted :: EventFn SyntheticEvent Boolean -isTrusted = EventFn \e -> (unsafeCoerce e).isTrusted +isTrusted = unsafeEventFn \e -> (unsafeCoerce e).isTrusted nativeEvent :: EventFn SyntheticEvent DOMEvent -nativeEvent = EventFn \e -> (unsafeCoerce e).nativeEvent +nativeEvent = unsafeEventFn \e -> (unsafeCoerce e).nativeEvent preventDefault :: EventFn SyntheticEvent SyntheticEvent -preventDefault = EventFn unsafePreventDefault +preventDefault = unsafeEventFn unsafePreventDefault foreign import unsafePreventDefault :: SyntheticEvent -> SyntheticEvent isDefaultPrevented :: EventFn SyntheticEvent Boolean -isDefaultPrevented = EventFn unsafeIsDefaultPrevented +isDefaultPrevented = unsafeEventFn unsafeIsDefaultPrevented foreign import unsafeIsDefaultPrevented :: SyntheticEvent -> Boolean stopPropagation :: EventFn SyntheticEvent SyntheticEvent -stopPropagation = EventFn unsafeStopPropagation +stopPropagation = unsafeEventFn unsafeStopPropagation foreign import unsafeStopPropagation :: SyntheticEvent -> SyntheticEvent isPropagationStopped :: EventFn SyntheticEvent Boolean -isPropagationStopped = EventFn unsafeIsPropagationStopped +isPropagationStopped = unsafeEventFn unsafeIsPropagationStopped foreign import unsafeIsPropagationStopped :: SyntheticEvent -> Boolean target :: EventFn SyntheticEvent DOMNode -target = EventFn \e -> (unsafeCoerce e).target +target = unsafeEventFn \e -> (unsafeCoerce e).target -targetChecked :: EventFn SyntheticEvent (Nullable Boolean) -targetChecked = EventFn \e -> (unsafeCoerce e).target.checked +targetChecked :: EventFn SyntheticEvent (Maybe Boolean) +targetChecked = unsafeEventFn \e -> toMaybe (unsafeCoerce e).target.checked -targetValue :: EventFn SyntheticEvent (Nullable String) -targetValue = EventFn \e -> (unsafeCoerce e).target.value +targetValue :: EventFn SyntheticEvent (Maybe String) +targetValue = unsafeEventFn \e -> toMaybe (unsafeCoerce e).target.value timeStamp :: EventFn SyntheticEvent Number -timeStamp = EventFn \e -> (unsafeCoerce e).timeStamp +timeStamp = unsafeEventFn \e -> (unsafeCoerce e).timeStamp type_ :: EventFn SyntheticEvent String -type_ = EventFn \e -> (unsafeCoerce e)."type" +type_ = unsafeEventFn \e -> (unsafeCoerce e)."type" diff --git a/src/React/Basic/Events.purs b/src/React/Basic/Events.purs new file mode 100644 index 0000000..265b33d --- /dev/null +++ b/src/React/Basic/Events.purs @@ -0,0 +1,101 @@ +module React.Basic.Events + ( EventHandler + , SyntheticEvent + , EventFn + , unsafeEventFn + , handler + , merge + , class Merge + , mergeImpl + ) where + +import Prelude + +import Control.Monad.Eff (Eff) +import Control.Monad.Eff.Uncurried (EffFn1, mkEffFn1) +import Data.Record (delete, get, insert) +import Data.Symbol (class IsSymbol, SProxy(SProxy)) +import React.Basic (ReactFX) +import Type.Row (kind RowList, class RowToList, class RowLacks, RLProxy(..), Cons, Nil) + +-- | An event handler, which receives a `SyntheticEvent` and performs some +-- | effects in return. +type EventHandler = EffFn1 (react :: ReactFX) SyntheticEvent Unit + +-- | Event data that we receive from React. +foreign import data SyntheticEvent :: Type + +-- | Encapsulates a safe event operation. `EventFn`s can be composed +-- | to perform multiple operations. +-- | +-- | For example: +-- | +-- | ```purs +-- | input { onChange: handler (preventDefault >>> targetValue) +-- | \value -> setState \_ -> { value } +-- | } +-- | ``` +newtype EventFn a b = EventFn (a -> b) + +-- | Unsafely create an `EventFn`. This function should be avoided as it can allow +-- | a `SyntheticEvent` to escape its scope. Accessing a React event's properties is only +-- | valid in a synchronous event callback. +-- | +-- | Instead, use the helper functions specific to your platform, such as `React.Basic.DOM.Events`. +unsafeEventFn :: forall a b. (a -> b) -> EventFn a b +unsafeEventFn = EventFn + +derive newtype instance semigroupoidBuilder :: Semigroupoid EventFn +derive newtype instance categoryBuilder :: Category EventFn + +-- | Create an `EventHandler`, given an `EventFn` and a callback. +-- | +-- | For example: +-- | +-- | ```purs +-- | input { onChange: handler targetValue +-- | \value -> setState \_ -> { value } +-- | } +-- | ``` +handler :: forall a. EventFn SyntheticEvent a -> (a -> Eff (react :: ReactFX) Unit) -> EventHandler +handler (EventFn fn) cb = mkEffFn1 $ fn >>> cb + +class Merge (rl :: RowList) fns a r | rl -> fns, rl a -> r where + mergeImpl :: RLProxy rl -> Record fns -> EventFn a (Record r) + +instance mergeNil :: Merge Nil () a () where + mergeImpl _ _ = EventFn \_ -> {} + +instance mergeCons + :: ( IsSymbol l + , RowCons l (EventFn a b) fns_rest fns + , RowCons l b r_rest r + , RowLacks l fns_rest + , RowLacks l r_rest + , Merge rest fns_rest a r_rest + ) + => Merge (Cons l (EventFn a b) rest) fns a r + where + mergeImpl _ fns = EventFn \a -> + let EventFn inner = mergeImpl (RLProxy :: RLProxy rest) (delete l fns) + EventFn f = get l fns + in insert l (f a) (inner a) + where + l = SProxy :: SProxy l + +-- | Merge multiple `EventFn` operations and collect their results. +-- | +-- | For example: +-- | +-- | ```purs +-- | input { onChange: handler (merge { targetValue, timeStamp }) +-- | \{ targetValue, timeStamp } -> setState \_ -> { ... } +-- | } +-- | ``` +merge + :: forall a fns fns_list r + . RowToList fns fns_list + => Merge fns_list fns a r + => Record fns + -> EventFn a (Record r) +merge = mergeImpl (RLProxy :: RLProxy fns_list)