module Gargantext.Components.InputWithAutocomplete where import DOM.Simple (contains) import DOM.Simple as DOM import DOM.Simple.Event as DE import Data.Maybe (Maybe(..), maybe) import Data.Nullable (Nullable, null, toMaybe) import Data.String as S import Effect (Effect) import FFI.Simple ((..)) import Gargantext.Utils.Reactix as R2 import Prelude import Reactix as R import Reactix.DOM.HTML as H import Toestand as T here :: R2.Here here = R2.here "Gargantext.Components.InputWithAutocomplete" type Completions = Array String type Props = ( autoFocus :: Boolean , autocompleteSearch :: String -> Effect Completions , classes :: String , onAutocompleteClick :: String -> Effect Unit , onEnterPress :: String -> Effect Unit , placeholder :: String , pattern :: String , title :: String , state :: T.Box String ) inputWithAutocomplete :: R2.Component Props inputWithAutocomplete = R2.component inputWithAutocompleteCpt inputWithAutocompleteCpt :: R.Component Props inputWithAutocompleteCpt = here.component "inputWithAutocomplete" cpt where cpt { autoFocus , autocompleteSearch , classes , onAutocompleteClick , onEnterPress , placeholder , pattern , title , state } children = do -- States state' <- T.useLive T.unequal state containerRef <- R.useRef null inputRef <- R.useRef null completions <- T.useBox [] R.useEffectOnce' $ do cs <- autocompleteSearch state' T.write_ cs completions -- Render pure $ H.div { className: "input-with-autocomplete " <> classes , ref: containerRef } ( [ completionsCpt { completions, onAutocompleteClick, state } [] , H.input { type: "text" , ref: inputRef , autoFocus , className: "form-control" , value: state' , pattern , title , placeholder , on: { focus: onFocus completions state' , input: onInput completions , change: onInput completions , keyDown: onInputKeyDown inputRef , keyUp: onInputKeyUp inputRef , blur: onBlur completions containerRef } } ] <> children ) -- Helpers where -- (!) `onBlur` DOM.Event is triggered before any `onClick` DOM.Event -- So when a completion is being clicked, the UX will be broken -- -- ↳ As a solution we chose to check if the click is made from -- the autocompletion list onBlur :: forall event . T.Box Completions -> R.Ref (Nullable DOM.Element) -> event -> Effect Unit onBlur completions containerRef event = if isInnerEvent then pure $ (event .. "preventDefault") else T.write_ [] completions where mContains = do a <- toMaybe $ R.readRef containerRef b <- toMaybe (event .. "relatedTarget") Just (contains a b) isInnerEvent = maybe false identity mContains onFocus :: forall event. T.Box Completions -> String -> event -> Effect Unit onFocus completions st _ = do cs <- autocompleteSearch st T.write_ cs completions onInput :: forall event. T.Box Completions -> event -> Effect Unit onInput completions e = do let val = R.unsafeEventValue e T.write_ val state cs <- autocompleteSearch val T.write_ cs completions onInputKeyUp :: R.Ref (Nullable DOM.Element) -> DE.KeyboardEvent -> Effect Boolean onInputKeyUp inputRef e = do if DE.key e == "Enter" then do R2.preventDefault e R2.stopPropagation e let val = R.unsafeEventValue e let mInput = toMaybe $ R.readRef inputRef T.write_ val state onEnterPress val case mInput of Nothing -> pure false Just input -> do R2.blur input pure false else pure false onInputKeyDown :: R.Ref (Nullable DOM.Element) -> DE.KeyboardEvent -> Effect Boolean onInputKeyDown _ e = do if DE.key e == "Enter" then do R2.preventDefault e R2.stopPropagation e pure false else pure false --------------------------------------------------------- type CompletionsProps = ( completions :: T.Box Completions , onAutocompleteClick :: String -> Effect Unit , state :: T.Box String ) completionsCpt :: R2.Component CompletionsProps completionsCpt = R.createElement completionsCptCpt completionsCptCpt :: R.Component CompletionsProps completionsCptCpt = here.component "completionsCpt" cpt where cpt { completions, onAutocompleteClick, state } _ = do -- State completions' <- T.useLive T.unequal completions let className = "completions shadow" <> (if completions' == [] then "d-none" else "") -- Render pure $ H.div { className } [ H.div { className: "list-group" } (cCpt <$> completions') ] -- Helpers where cCpt c = H.button { type: "button" , className: "list-group-item" , on: { click: onClick c } } [ H.text c ] onClick c _ = do T.write_ c state T.write_ [] completions onAutocompleteClick c