module Gargantext.Components.InputWithAutocomplete where import Prelude import DOM.Simple as DOM import DOM.Simple.Event as DE import Data.Maybe (Maybe(..)) import Data.Nullable (Nullable, null, toMaybe) import Effect (Effect) import Effect.Timer (setTimeout) import Gargantext.Utils.Reactix as R2 import React.SyntheticEvent as E 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 = ( 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 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 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