module Gargantext.Components.InputWithAutocomplete where import Prelude import Data.Maybe (Maybe(..)) import Data.Nullable (Nullable, null, toMaybe) import DOM.Simple as DOM import DOM.Simple.Event as DE import Effect (Effect) import Effect.Timer (setTimeout) import Reactix as R import Reactix.DOM.HTML as H import React.SyntheticEvent as E import Toestand as T import Gargantext.Utils.Reactix as R2 here :: R2.Here here = R2.here "Gargantext.Components.InputWithAutocomplete" type Completions = Array String type Props = ( autocompleteSearch :: String -> Completions , classes :: String , onAutocompleteClick :: String -> Effect Unit , onEnterPress :: String -> Effect Unit , state :: T.Box String ) inputWithAutocomplete :: R2.Component Props inputWithAutocomplete = R.createElement inputWithAutocompleteCpt inputWithAutocompleteCpt :: R.Component Props inputWithAutocompleteCpt = here.component "inputWithAutocomplete" cpt where cpt props@{ autocompleteSearch , classes , onAutocompleteClick , onEnterPress , state } _ = do state' <- T.useLive T.unequal state inputRef <- R.useRef null completions <- T.useBox $ autocompleteSearch state' let onFocus completions' _ = T.write_ (autocompleteSearch state') completions' pure $ H.span { className: "input-with-autocomplete " <> classes } [ completionsCpt { completions, onAutocompleteClick, state } [] , H.input { type: "text" , ref: inputRef , className: "form-control" , value: state' , on: { blur: onBlur completions , focus: onFocus completions , input: onInput completions , change: onInput completions , keyUp: onInputKeyUp inputRef } } ] where -- setTimeout is a bit of a hack here -- clicking on autocomplete -- element will clear out the blur first, so the autocomplete click -- won't fire without a timeout here. However, blur is very handy and -- handles automatic autocomplete search, otherwise I'd have to hide it -- in various different places (i.e. carefully handle all possible -- events where blur happens and autocomplete should hide). onBlur completions _ = setTimeout 100 $ do T.write_ [] completions onInput completions e = do let val = R.unsafeEventValue e T.write_ val state T.write_ (autocompleteSearch val) completions onInputKeyUp :: R.Ref (Nullable DOM.Element) -> DE.KeyboardEvent -> Effect Boolean onInputKeyUp inputRef e = do if DE.key e == "Enter" then do 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 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 completions' <- T.useLive T.unequal completions let className = "completions " <> (if completions' == [] then "d-none" else "") pure $ H.div { className } [ H.div { className: "list-group" } (cCpt <$> completions') ] 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 onAutocompleteClick c