Skip to content

Commit 6023ba3

Browse files
committed
fix(search): activate search-bar buttons from the keyboard
The next/previous/close/replace buttons listened only to pointerdown, while Enter/Space on a focused native button fires click. Handle keyboard-initiated clicks (detail === 0) without changing the pointer path. Fixes #1440
1 parent a37f7a9 commit 6023ba3

3 files changed

Lines changed: 74 additions & 6 deletions

File tree

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,12 @@
99
> - :house: [Internal]
1010
> - :nail_care: [Polish]
1111

12+
## 4.13.20
13+
14+
#### :bug: Bug Fix
15+
16+
- **Search**: the next/previous/close/replace buttons of the search bar could be reached with Tab but did nothing on Enter/Space — the handlers listened only to `pointerdown`, while keyboard activation of a native button fires `click`. Keyboard-initiated clicks (`detail === 0`, which also covers screen readers) are now handled; pointer behaviour is unchanged. Fixes [#1440](https://github.com/xdan/jodit/issues/1440).
17+
1218
## 4.13.19
1319

1420
#### :bug: Bug Fix

src/plugins/search/search.test.js

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -631,6 +631,43 @@ describe('Search plugin', () => {
631631
});
632632
});
633633

634+
describe('Keyboard activation of the search buttons', () => {
635+
// Activating a focused native button with Enter/Space fires a
636+
// `click` with `detail === 0` (there is no `pointerdown`)
637+
// https://github.com/xdan/jodit/issues/1440
638+
it('Should search next and close on a keyboard click', async () => {
639+
const editor = getJodit({
640+
defaultTimeout: 0,
641+
search: {
642+
useCustomHighlightAPI: false
643+
}
644+
});
645+
646+
editor.value = '<p>test</p>';
647+
648+
clickTrigger('find', editor);
649+
clickButton('find', getOpenedPopup(editor));
650+
651+
await editor.async.requestIdlePromise();
652+
const inputs = getSearchInputs(editor);
653+
inputs.query.value = 't';
654+
655+
simulateEvent('click', inputs.nextButton);
656+
await editor.async.requestIdlePromise();
657+
658+
expect(sortAttributes(editor.getNativeEditorValue())).equals(
659+
'<p><span data-jodit-temp="true" jd-tmp-selection="true">t</span>es<span data-jodit-temp="true" jd-tmp-selection="true">t</span></p>'
660+
);
661+
662+
simulateEvent('click', inputs.closeButton);
663+
await editor.async.requestIdlePromise();
664+
665+
expect(editor.getNativeEditorValue()).equals('<p>test</p>');
666+
expect(editor.container.querySelector('.jodit-ui-search')).is
667+
.null;
668+
});
669+
});
670+
634671
describe('After close', () => {
635672
let editor, search, inputs;
636673

src/plugins/search/ui/search.ts

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -106,11 +106,33 @@ export class UISearch extends UIElement<IJodit> {
106106
this.currentBox = current as HTMLSpanElement;
107107
this.countBox = count as HTMLSpanElement;
108108

109+
const closeAction = (): void => this.close();
110+
const replaceAction = (): void =>
111+
void jodit.e.fire(this, 'pressReplaceButton');
112+
const nextAction = (): void => void jodit.e.fire('searchNext');
113+
const prevAction = (): void => void jodit.e.fire('searchPrevious');
114+
115+
// Pointer activation is handled on `pointerdown` (returning `false`
116+
// there keeps the focus and selection intact), but activating a
117+
// native button from the keyboard (Enter/Space) fires only `click`
118+
// with `detail === 0` — handle that path separately. Pointer clicks
119+
// arrive with `detail >= 1`, so the action does not run twice.
120+
// See https://github.com/xdan/jodit/issues/1440
121+
const onKeyboardActivate =
122+
(action: () => void) =>
123+
(e: MouseEvent): void => {
124+
if (!e.detail) {
125+
e.preventDefault();
126+
action();
127+
}
128+
};
129+
109130
jodit.e
110-
.on(this.closeButton, 'pointerdown', () => {
111-
this.close();
131+
.on(this.closeButton, 'pointerdown', (): false => {
132+
closeAction();
112133
return false;
113134
})
135+
.on(this.closeButton, 'click', onKeyboardActivate(closeAction))
114136
.on(this.queryInput, 'input', () => {
115137
this.currentIndex = 0;
116138
})
@@ -120,18 +142,21 @@ export class UISearch extends UIElement<IJodit> {
120142
this.selInfo = jodit.s.save();
121143
}
122144
})
123-
.on(this.replaceButton, 'pointerdown', () => {
124-
jodit.e.fire(this, 'pressReplaceButton');
145+
.on(this.replaceButton, 'pointerdown', (): false => {
146+
replaceAction();
125147
return false;
126148
})
149+
.on(this.replaceButton, 'click', onKeyboardActivate(replaceAction))
127150
.on(next, 'pointerdown', (): false => {
128-
void jodit.e.fire('searchNext');
151+
nextAction();
129152
return false;
130153
})
154+
.on(next, 'click', onKeyboardActivate(nextAction))
131155
.on(prev, 'pointerdown', (): false => {
132-
jodit.e.fire('searchPrevious');
156+
prevAction();
133157
return false;
134158
})
159+
.on(prev, 'click', onKeyboardActivate(prevAction))
135160
.on(this.queryInput, 'input', () => {
136161
this.setMod('empty-query', !trim(this.queryInput.value).length);
137162
})

0 commit comments

Comments
 (0)