diff --git a/packages/@headlessui-react/src/components/switch/switch.test.tsx b/packages/@headlessui-react/src/components/switch/switch.test.tsx
index 06ee361..bd1b052 100644
--- a/packages/@headlessui-react/src/components/switch/switch.test.tsx
+++ b/packages/@headlessui-react/src/components/switch/switch.test.tsx
@@ -156,6 +156,24 @@ describe('Keyboard interactions', () => {
})
})
+ describe('`Enter` key', () => {
+ it('should not be possible to use Enter to toggle the Switch', async () => {
+ const handleChange = jest.fn()
+ render()
+
+ // Ensure checkbox is off
+ assertSwitch({ state: SwitchState.Off })
+
+ // Focus the switch
+ getSwitch()?.focus()
+
+ // Try to toggle
+ await press(Keys.Enter)
+
+ expect(handleChange).not.toHaveBeenCalled()
+ })
+ })
+
describe('`Tab` key', () => {
it('should be possible to tab away from the Switch', async () => {
render(
diff --git a/packages/@headlessui-react/src/components/switch/switch.tsx b/packages/@headlessui-react/src/components/switch/switch.tsx
index 949fecc..4dfcb6a 100644
--- a/packages/@headlessui-react/src/components/switch/switch.tsx
+++ b/packages/@headlessui-react/src/components/switch/switch.tsx
@@ -55,7 +55,14 @@ const DEFAULT_SWITCH_TAG = 'button'
type SwitchRenderPropArg = { checked: boolean }
-type SwitchPropsWeControl = 'id' | 'role' | 'tabIndex' | 'aria-checked' | 'onClick' | 'onKeyUp'
+type SwitchPropsWeControl =
+ | 'id'
+ | 'role'
+ | 'tabIndex'
+ | 'aria-checked'
+ | 'onClick'
+ | 'onKeyUp'
+ | 'onKeyPress'
export function Switch(
props: Props<
@@ -84,14 +91,18 @@ export function Switch) => {
- if (event.key === Keys.Space) {
- event.preventDefault()
- toggle()
- }
+ if (event.key !== Keys.Tab) event.preventDefault()
+ if (event.key === Keys.Space) toggle()
},
[toggle]
)
+ // This is needed so that we can "cancel" the click event when we use the `Enter` key on a button.
+ const handleKeyPress = React.useCallback(
+ (event: React.KeyboardEvent) => event.preventDefault(),
+ []
+ )
+
const propsBag = React.useMemo(() => ({ checked }), [checked])
const propsWeControl = {
id,
@@ -103,6 +114,7 @@ export function Switch> = {
- Space: { key: ' ' },
- Enter: { key: 'Enter' },
- Escape: { key: 'Escape' },
- Backspace: { key: 'Backspace' },
+ Space: { key: ' ', keyCode: 32 },
+ Enter: { key: 'Enter', keyCode: 13 },
+ Escape: { key: 'Escape', keyCode: 27 },
+ Backspace: { key: 'Backspace', keyCode: 8 },
- ArrowUp: { key: 'ArrowUp' },
- ArrowDown: { key: 'ArrowDown' },
+ ArrowUp: { key: 'ArrowUp', keyCode: 38 },
+ ArrowDown: { key: 'ArrowDown', keyCode: 40 },
- Home: { key: 'Home' },
- End: { key: 'End' },
+ Home: { key: 'Home', keyCode: 36 },
+ End: { key: 'End', keyCode: 35 },
- PageUp: { key: 'PageUp' },
- PageDown: { key: 'PageDown' },
+ PageUp: { key: 'PageUp', keyCode: 33 },
+ PageDown: { key: 'PageDown', keyCode: 34 },
- Tab: { key: 'Tab' },
+ Tab: { key: 'Tab', keyCode: 9 },
}
export function shift(event: Partial) {
@@ -38,11 +38,19 @@ export async function type(events: Partial[]) {
let element = document.activeElement
events.forEach(event => {
- const cancelled = !fireEvent.keyDown(element, event)
- if (!cancelled && event.key === Keys.Tab.key) {
+ const cancelled1 = !fireEvent.keyDown(element, event)
+
+ // Special treatment for `Tab` on an element
+ if (!cancelled1 && event.key === Keys.Tab.key) {
element = focusNext(event)
}
- fireEvent.keyPress(element, event)
+
+ const cancelled2 = !fireEvent.keyPress(element, event)
+ // Special treatment for `Enter` on a button element
+ if (!cancelled2 && event.key === Keys.Enter.key && element instanceof HTMLButtonElement) {
+ fireEvent.click(element)
+ }
+
fireEvent.keyUp(element, event)
})
diff --git a/packages/@headlessui-vue/src/components/switch/switch.test.tsx b/packages/@headlessui-vue/src/components/switch/switch.test.tsx
index 485d7fe..210ffff 100644
--- a/packages/@headlessui-vue/src/components/switch/switch.test.tsx
+++ b/packages/@headlessui-vue/src/components/switch/switch.test.tsx
@@ -1,4 +1,4 @@
-import { defineComponent, ref, watchEffect } from 'vue'
+import { defineComponent, ref, watch } from 'vue'
import { render } from '../../test-utils/vue-testing-library'
import { Switch, SwitchLabel, SwitchGroup } from './switch'
@@ -175,7 +175,7 @@ describe('Keyboard interactions', () => {
template: ``,
setup() {
const checked = ref(false)
- watchEffect(() => handleChange(checked.value))
+ watch([checked], () => handleChange(checked.value))
return { checked }
},
})
@@ -200,6 +200,31 @@ describe('Keyboard interactions', () => {
})
})
+ describe('`Enter` key', () => {
+ it('should not be possible to use Enter to toggle the Switch', async () => {
+ const handleChange = jest.fn()
+ renderTemplate({
+ template: ``,
+ setup() {
+ const checked = ref(false)
+ watch([checked], () => handleChange(checked.value))
+ return { checked }
+ },
+ })
+
+ // Ensure checkbox is off
+ assertSwitch({ state: SwitchState.Off })
+
+ // Focus the switch
+ getSwitch()?.focus()
+
+ // Try to toggle
+ await press(Keys.Enter)
+
+ expect(handleChange).not.toHaveBeenCalled()
+ })
+ })
+
describe('`Tab` key', () => {
it('should be possible to tab away from the Switch', async () => {
renderTemplate({
@@ -237,7 +262,7 @@ describe('Mouse interactions', () => {
template: ``,
setup() {
const checked = ref(false)
- watchEffect(() => handleChange(checked.value))
+ watch([checked], () => handleChange(checked.value))
return { checked }
},
})
@@ -269,7 +294,7 @@ describe('Mouse interactions', () => {
`,
setup() {
const checked = ref(false)
- watchEffect(() => handleChange(checked.value))
+ watch([checked], () => handleChange(checked.value))
return { checked }
},
})
diff --git a/packages/@headlessui-vue/src/components/switch/switch.ts b/packages/@headlessui-vue/src/components/switch/switch.ts
index 4f2c3fe..9fb4508 100644
--- a/packages/@headlessui-vue/src/components/switch/switch.ts
+++ b/packages/@headlessui-vue/src/components/switch/switch.ts
@@ -70,6 +70,7 @@ export const Switch = defineComponent({
'aria-labelledby': labelledby.value,
onClick: this.handleClick,
onKeyUp: this.handleKeyUp,
+ onKeyPress: this.handleKeyPress,
}
return render({
@@ -95,10 +96,12 @@ export const Switch = defineComponent({
toggle()
},
handleKeyUp(event: KeyboardEvent) {
- if (event.key === Keys.Space) {
- event.preventDefault()
- toggle()
- }
+ if (event.key !== Keys.Tab) event.preventDefault()
+ if (event.key === Keys.Space) toggle()
+ },
+ // This is needed so that we can "cancel" the click event when we use the `Enter` key on a button.
+ handleKeyPress(event: KeyboardEvent) {
+ event.preventDefault()
},
}
},
diff --git a/packages/@headlessui-vue/src/test-utils/interactions.ts b/packages/@headlessui-vue/src/test-utils/interactions.ts
index 5c347c7..78dfcd6 100644
--- a/packages/@headlessui-vue/src/test-utils/interactions.ts
+++ b/packages/@headlessui-vue/src/test-utils/interactions.ts
@@ -2,21 +2,21 @@ import { nextTick } from 'vue'
import { fireEvent } from '@testing-library/dom'
export const Keys: Record> = {
- Space: { key: ' ' },
- Enter: { key: 'Enter' },
- Escape: { key: 'Escape' },
- Backspace: { key: 'Backspace' },
+ Space: { key: ' ', keyCode: 32 },
+ Enter: { key: 'Enter', keyCode: 13 },
+ Escape: { key: 'Escape', keyCode: 27 },
+ Backspace: { key: 'Backspace', keyCode: 8 },
- ArrowUp: { key: 'ArrowUp' },
- ArrowDown: { key: 'ArrowDown' },
+ ArrowUp: { key: 'ArrowUp', keyCode: 38 },
+ ArrowDown: { key: 'ArrowDown', keyCode: 40 },
- Home: { key: 'Home' },
- End: { key: 'End' },
+ Home: { key: 'Home', keyCode: 36 },
+ End: { key: 'End', keyCode: 35 },
- PageUp: { key: 'PageUp' },
- PageDown: { key: 'PageDown' },
+ PageUp: { key: 'PageUp', keyCode: 33 },
+ PageDown: { key: 'PageDown', keyCode: 34 },
- Tab: { key: 'Tab' },
+ Tab: { key: 'Tab', keyCode: 9 },
}
export function shift(event: Partial) {
@@ -36,11 +36,19 @@ export async function type(events: Partial[]) {
let element = document.activeElement
events.forEach(event => {
- const cancelled = !fireEvent.keyDown(element, event)
- if (!cancelled && event.key === Keys.Tab.key) {
+ const cancelled1 = !fireEvent.keyDown(element, event)
+
+ // Special treatment for `Tab` on an element
+ if (!cancelled1 && event.key === Keys.Tab.key) {
element = focusNext(event)
}
- fireEvent.keyPress(element, event)
+
+ const cancelled2 = !fireEvent.keyPress(element, event)
+ // Special treatment for `Enter` on a button element
+ if (!cancelled2 && event.key === Keys.Enter.key && element instanceof HTMLButtonElement) {
+ fireEvent.click(element)
+ }
+
fireEvent.keyUp(element, event)
})