1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
module Gargantext.Components.InputWithAutocomplete where
import Prelude
import Data.Maybe (Maybe(..))
import Data.Nullable (Nullable, null, toMaybe)
import Data.Tuple.Nested ((/\))
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 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 e = 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 e = 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 Unit
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 unit
Just input -> R2.blur input
else
pure $ unit
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