Skip to content

Commit bc896e9

Browse files
committed
Do not steal focus from editable elements on the page
1 parent 24b8e21 commit bc896e9

File tree

2 files changed

+89
-25
lines changed

2 files changed

+89
-25
lines changed

src/components/DocSearch.js

Lines changed: 51 additions & 15 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: 38 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,13 @@ 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

10-
type keyboardEventLike = {key: string}
13+
type keyboardEventLike = {key: string, ctrlKey: bool, metaKey: bool}
1114

1215
@bs.val @bs.scope("window")
1316
external addKeyboardEventListener: (string, keyboardEventLike => unit) => unit = "addEventListener"
@@ -16,10 +19,15 @@ external addKeyboardEventListener: (string, keyboardEventLike => unit) => unit =
1619
external removeKeyboardEventListener: (string, keyboardEventLike => unit) => unit =
1720
"addEventListener"
1821

22+
@bs.send
23+
external keyboardEventPreventDefault: keyboardEventLike => unit = "preventDefault"
24+
1925
type state =
2026
| Active
2127
| Inactive
2228

29+
@bs.get external isContentEditable: Dom.element => bool = "isContentEditable"
30+
@bs.get external tagName: Dom.element => string = "tagName"
2331
@bs.send external focus: Dom.element => unit = "focus"
2432
@bs.send external blur: Dom.element => unit = "blur"
2533
@bs.set external value: (Dom.element, string) => unit = "value"
@@ -41,20 +49,40 @@ let make = () => {
4149
| None => ()
4250
}
4351

44-
let handleKeyDown = e => {
45-
if e.key == "/" {
46-
setState(_ => Active)
52+
None
53+
}, [])
54+
55+
React.useEffect1(() => {
56+
let isEditableTag = el =>
57+
switch el->tagName {
58+
| "TEXTAREA" | "SELECT" | "INPUT" => true
59+
| _ => false
60+
}
4761

48-
Js.Global.setTimeout(() => {
49-
// execture focus in the next tick to avoid setting / inside input
62+
let focusSearch = e => {
63+
switch activeElement {
64+
| Some(el) when el->isEditableTag => ()
65+
| Some(el) when el->isContentEditable => ()
66+
| _ => {
67+
setState(_ => Active)
5068
inputRef.current->Js.Nullable.toOption->Belt.Option.forEach(focus)
51-
}, 0)->ignore
69+
70+
e->keyboardEventPreventDefault
71+
}
5272
}
5373
}
5474

55-
addKeyboardEventListener("keydown", handleKeyDown)
56-
Some(() => removeKeyboardEventListener("keydown", handleKeyDown))
57-
}, [])
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])
5886

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

0 commit comments

Comments
 (0)