Skip to content

Commit e715c81

Browse files
authored
feat(keyboard): select all per {Control}+[KeyA] (#774)
* feat(keyboard): select all per `[Ctrl]+[A]` * add plugin test
1 parent 9273c8a commit e715c81

File tree

6 files changed

+120
-0
lines changed

6 files changed

+120
-0
lines changed

src/keyboard/plugins/combination.ts

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
/**
2+
* Default behavior for key combinations
3+
*/
4+
5+
import { behaviorPlugin } from '../types'
6+
import {
7+
selectAll,
8+
} from '../../utils'
9+
10+
export const keydownBehavior: behaviorPlugin[] = [
11+
{
12+
matches: (keyDef, element, options, state) =>
13+
keyDef.code === 'KeyA' && state.modifiers.ctrl,
14+
handle: (keyDef, element) => selectAll(element)
15+
},
16+
]

src/keyboard/plugins/index.ts

+2
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import * as arrowKeys from './arrow'
44
import * as controlKeys from './control'
55
import * as characterKeys from './character'
66
import * as functionalKeys from './functional'
7+
import * as combination from './combination'
78

89
export const replaceBehavior: behaviorPlugin[] = [
910
{
@@ -28,6 +29,7 @@ export const keydownBehavior: behaviorPlugin[] = [
2829
...arrowKeys.keydownBehavior,
2930
...controlKeys.keydownBehavior,
3031
...functionalKeys.keydownBehavior,
32+
...combination.keydownBehavior,
3133
]
3234

3335
export const keypressBehavior: behaviorPlugin[] = [

src/utils/focus/selectAll.ts

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import {getUIValue} from '../../document'
2+
import {getContentEditable} from '../edit/isContentEditable'
3+
import {editableInputTypes} from '../edit/isEditable'
4+
import {isElementType} from '../misc/isElementType'
5+
import {setSelection} from './selection'
6+
7+
/**
8+
* Expand a selection like the browser does when pressing Ctrl+A.
9+
*/
10+
export function selectAll(target: Element): void {
11+
if (
12+
isElementType(target, 'textarea') ||
13+
(isElementType(target, 'input') && target.type in editableInputTypes)
14+
) {
15+
return setSelection({
16+
focusNode: target,
17+
anchorOffset: 0,
18+
focusOffset: getUIValue(target).length,
19+
})
20+
}
21+
22+
const focusNode = getContentEditable(target) ?? target.ownerDocument.body
23+
setSelection({
24+
focusNode,
25+
anchorOffset: 0,
26+
focusOffset: focusNode.childNodes.length,
27+
})
28+
}

src/utils/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ export * from './focus/focus'
1616
export * from './focus/getActiveElement'
1717
export * from './focus/getTabDestination'
1818
export * from './focus/isFocusable'
19+
export * from './focus/selectAll'
1920
export * from './focus/selection'
2021
export * from './focus/selector'
2122

tests/keyboard/plugin/combination.ts

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import userEvent from '#src'
2+
import {setup} from '#testHelpers/utils'
3+
4+
test('select input per `Control+A`', () => {
5+
const {element} = setup<HTMLInputElement>(`<input value="foo bar baz"/>`)
6+
element.focus()
7+
element.selectionStart = 5
8+
9+
userEvent.keyboard('{Control>}a')
10+
11+
expect(element).toHaveProperty('selectionStart', 0)
12+
expect(element).toHaveProperty('selectionEnd', 11)
13+
})

tests/utils/focus/selectAll.ts

+60
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import {setup} from '#testHelpers/utils'
2+
import {selectAll} from '#src/utils/focus/selectAll'
3+
import {getUISelection} from '#src/document'
4+
5+
test('select all in input', () => {
6+
const {element} = setup<HTMLInputElement>(`<input value="foo bar baz"/>`)
7+
8+
selectAll(element)
9+
10+
expect(getUISelection(element)).toHaveProperty('startOffset', 0)
11+
expect(getUISelection(element)).toHaveProperty('endOffset', 11)
12+
expect(element).toHaveProperty('selectionStart', 0)
13+
expect(element).toHaveProperty('selectionEnd', 11)
14+
})
15+
16+
test('select all in textarea', () => {
17+
const {element} = setup<HTMLTextAreaElement>(
18+
`<textarea>foo\nbar\nbaz</textarea>`,
19+
)
20+
21+
selectAll(element)
22+
23+
expect(getUISelection(element)).toHaveProperty('startOffset', 0)
24+
expect(getUISelection(element)).toHaveProperty('endOffset', 11)
25+
expect(element).toHaveProperty('selectionStart', 0)
26+
expect(element).toHaveProperty('selectionEnd', 11)
27+
})
28+
29+
test('select all in contenteditable', () => {
30+
const {element} = setup(`
31+
<div contenteditable><div>foo</div><div>bar</div></div>
32+
<div>baz</div>
33+
`)
34+
35+
selectAll(element)
36+
37+
const selection = document.getSelection()
38+
expect(selection).toHaveProperty('anchorNode', element)
39+
expect(selection).toHaveProperty('anchorOffset', 0)
40+
expect(selection).toHaveProperty('focusNode', element)
41+
expect(selection).toHaveProperty('focusOffset', 2)
42+
})
43+
44+
test('select all outside of editable', () => {
45+
const {element} = setup(`
46+
<input type="checkbox"/>
47+
<div>foo</div>
48+
`)
49+
50+
selectAll(element)
51+
52+
const selection = document.getSelection()
53+
expect(selection).toHaveProperty('anchorNode', element.ownerDocument.body)
54+
expect(selection).toHaveProperty('anchorOffset', 0)
55+
expect(selection).toHaveProperty('focusNode', element.ownerDocument.body)
56+
expect(selection).toHaveProperty(
57+
'focusOffset',
58+
element.ownerDocument.body.childNodes.length,
59+
)
60+
})

0 commit comments

Comments
 (0)