Skip to content

Commit

Permalink
feat(keyboard): select all per {Control}+[KeyA] (#774)
Browse files Browse the repository at this point in the history
  • Loading branch information
ph-fritsche committed Nov 28, 2021
1 parent 968c2c4 commit ea9b18a
Show file tree
Hide file tree
Showing 6 changed files with 120 additions and 0 deletions.
16 changes: 16 additions & 0 deletions src/keyboard/plugins/combination.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/**
* Default behavior for key combinations
*/

import { behaviorPlugin } from '../types'
import {
selectAll,
} from '../../utils'

export const keydownBehavior: behaviorPlugin[] = [
{
matches: (keyDef, element, options, state) =>
keyDef.code === 'KeyA' && state.modifiers.ctrl,
handle: (keyDef, element) => selectAll(element)
},
]
2 changes: 2 additions & 0 deletions src/keyboard/plugins/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import * as arrowKeys from './arrow'
import * as controlKeys from './control'
import * as characterKeys from './character'
import * as functionalKeys from './functional'
import * as combination from './combination'

export const replaceBehavior: behaviorPlugin[] = [
{
Expand All @@ -28,6 +29,7 @@ export const keydownBehavior: behaviorPlugin[] = [
...arrowKeys.keydownBehavior,
...controlKeys.keydownBehavior,
...functionalKeys.keydownBehavior,
...combination.keydownBehavior,
]

export const keypressBehavior: behaviorPlugin[] = [
Expand Down
28 changes: 28 additions & 0 deletions src/utils/focus/selectAll.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import {getUIValue} from '../../document'
import {getContentEditable} from '../edit/isContentEditable'
import {editableInputTypes} from '../edit/isEditable'
import {isElementType} from '../misc/isElementType'
import {setSelection} from './selection'

/**
* Expand a selection like the browser does when pressing Ctrl+A.
*/
export function selectAll(target: Element): void {
if (
isElementType(target, 'textarea') ||
(isElementType(target, 'input') && target.type in editableInputTypes)
) {
return setSelection({
focusNode: target,
anchorOffset: 0,
focusOffset: getUIValue(target).length,
})
}

const focusNode = getContentEditable(target) ?? target.ownerDocument.body
setSelection({
focusNode,
anchorOffset: 0,
focusOffset: focusNode.childNodes.length,
})
}
1 change: 1 addition & 0 deletions src/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export * from './focus/focus'
export * from './focus/getActiveElement'
export * from './focus/getTabDestination'
export * from './focus/isFocusable'
export * from './focus/selectAll'
export * from './focus/selection'
export * from './focus/selector'

Expand Down
13 changes: 13 additions & 0 deletions tests/keyboard/plugin/combination.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import userEvent from '#src'
import {setup} from '#testHelpers/utils'

test('select input per `Control+A`', () => {
const {element} = setup<HTMLInputElement>(`<input value="foo bar baz"/>`)
element.focus()
element.selectionStart = 5

userEvent.keyboard('{Control>}a')

expect(element).toHaveProperty('selectionStart', 0)
expect(element).toHaveProperty('selectionEnd', 11)
})
60 changes: 60 additions & 0 deletions tests/utils/focus/selectAll.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import {setup} from '#testHelpers/utils'
import {selectAll} from '#src/utils/focus/selectAll'
import {getUISelection} from '#src/document'

test('select all in input', () => {
const {element} = setup<HTMLInputElement>(`<input value="foo bar baz"/>`)

selectAll(element)

expect(getUISelection(element)).toHaveProperty('startOffset', 0)
expect(getUISelection(element)).toHaveProperty('endOffset', 11)
expect(element).toHaveProperty('selectionStart', 0)
expect(element).toHaveProperty('selectionEnd', 11)
})

test('select all in textarea', () => {
const {element} = setup<HTMLTextAreaElement>(
`<textarea>foo\nbar\nbaz</textarea>`,
)

selectAll(element)

expect(getUISelection(element)).toHaveProperty('startOffset', 0)
expect(getUISelection(element)).toHaveProperty('endOffset', 11)
expect(element).toHaveProperty('selectionStart', 0)
expect(element).toHaveProperty('selectionEnd', 11)
})

test('select all in contenteditable', () => {
const {element} = setup(`
<div contenteditable><div>foo</div><div>bar</div></div>
<div>baz</div>
`)

selectAll(element)

const selection = document.getSelection()
expect(selection).toHaveProperty('anchorNode', element)
expect(selection).toHaveProperty('anchorOffset', 0)
expect(selection).toHaveProperty('focusNode', element)
expect(selection).toHaveProperty('focusOffset', 2)
})

test('select all outside of editable', () => {
const {element} = setup(`
<input type="checkbox"/>
<div>foo</div>
`)

selectAll(element)

const selection = document.getSelection()
expect(selection).toHaveProperty('anchorNode', element.ownerDocument.body)
expect(selection).toHaveProperty('anchorOffset', 0)
expect(selection).toHaveProperty('focusNode', element.ownerDocument.body)
expect(selection).toHaveProperty(
'focusOffset',
element.ownerDocument.body.childNodes.length,
)
})

0 comments on commit ea9b18a

Please sign in to comment.