Skip to content

Commit 70bdf2b

Browse files
authored
Merge pull request #275 from dmtrKovalenko/search-input
feat: Implement shortcut to focus search
2 parents a9dce8d + bc896e9 commit 70bdf2b

File tree

2 files changed

+113
-8
lines changed

2 files changed

+113
-8
lines changed

src/components/DocSearch.js

Lines changed: 60 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/components/DocSearch.res

Lines changed: 53 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,40 @@ type options = {
44
inputSelector: string,
55
}
66

7+
@bs.val @bs.scope("document")
8+
external activeElement: option<Dom.element> = "activeElement"
9+
710
@bs.val @bs.scope("window")
811
external docsearch: option<options => unit> = "docsearch"
912

13+
type keyboardEventLike = {key: string, ctrlKey: bool, metaKey: bool}
14+
15+
@bs.val @bs.scope("window")
16+
external addKeyboardEventListener: (string, keyboardEventLike => unit) => unit = "addEventListener"
17+
18+
@bs.val @bs.scope("window")
19+
external removeKeyboardEventListener: (string, keyboardEventLike => unit) => unit =
20+
"addEventListener"
21+
22+
@bs.send
23+
external keyboardEventPreventDefault: keyboardEventLike => unit = "preventDefault"
24+
1025
type state =
1126
| Active
1227
| Inactive
1328

29+
@bs.get external isContentEditable: Dom.element => bool = "isContentEditable"
30+
@bs.get external tagName: Dom.element => string = "tagName"
1431
@bs.send external focus: Dom.element => unit = "focus"
1532
@bs.send external blur: Dom.element => unit = "blur"
1633
@bs.set external value: (Dom.element, string) => unit = "value"
1734

1835
@react.component
1936
let make = () => {
37+
// Used for the text input
38+
let inputRef = React.useRef(Js.Nullable.null)
39+
let (state, setState) = React.useState(_ => Inactive)
40+
2041
React.useEffect1(() => {
2142
switch docsearch {
2243
| Some(init) =>
@@ -27,12 +48,41 @@ let make = () => {
2748
})
2849
| None => ()
2950
}
51+
3052
None
3153
}, [])
3254

33-
// Used for the text input
34-
let inputRef = React.useRef(Js.Nullable.null)
35-
let (state, setState) = React.useState(_ => Inactive)
55+
React.useEffect1(() => {
56+
let isEditableTag = el =>
57+
switch el->tagName {
58+
| "TEXTAREA" | "SELECT" | "INPUT" => true
59+
| _ => false
60+
}
61+
62+
let focusSearch = e => {
63+
switch activeElement {
64+
| Some(el) when el->isEditableTag => ()
65+
| Some(el) when el->isContentEditable => ()
66+
| _ => {
67+
setState(_ => Active)
68+
inputRef.current->Js.Nullable.toOption->Belt.Option.forEach(focus)
69+
70+
e->keyboardEventPreventDefault
71+
}
72+
}
73+
}
74+
75+
let handleGlobalKeyDown = e => {
76+
switch e.key {
77+
| "/" => focusSearch(e)
78+
| "k" when e.ctrlKey || e.metaKey => focusSearch(e)
79+
| _ => ()
80+
}
81+
}
82+
83+
addKeyboardEventListener("keydown", handleGlobalKeyDown)
84+
Some(() => removeKeyboardEventListener("keydown", handleGlobalKeyDown))
85+
}, [setState])
3686

3787
let focusInput = () =>
3888
inputRef.current->Js.Nullable.toOption->Belt.Option.forEach(el => el->focus)

0 commit comments

Comments
 (0)