diff --git a/src/event/behavior/keypress.ts b/src/event/behavior/keypress.ts index dd417ec3..d5e4cd69 100644 --- a/src/event/behavior/keypress.ts +++ b/src/event/behavior/keypress.ts @@ -1,4 +1,9 @@ -import {isContentEditable, isEditable, isElementType} from '../../utils' +import { + isContentEditable, + isEditable, + isElementType, + isSummaryForParentDetails, +} from '../../utils' import {input} from '../input' import {behavior} from './registry' @@ -8,7 +13,8 @@ behavior.keypress = (event, target, instance) => { isElementType(target, 'button') || (isElementType(target, 'input') && ClickInputOnEnter.includes(target.type)) || - (isElementType(target, 'a') && Boolean(target.href)) + (isElementType(target, 'a') && Boolean(target.href)) || + isSummaryForParentDetails(target) ) { return () => { instance.dispatchUIEvent(target, 'click') diff --git a/src/event/behavior/keyup.ts b/src/event/behavior/keyup.ts index 77d1f2ca..b6050529 100644 --- a/src/event/behavior/keyup.ts +++ b/src/event/behavior/keyup.ts @@ -1,4 +1,4 @@ -import {isClickableInput} from '../../utils' +import {isClickableInput, isSummaryForParentDetails} from '../../utils' import {BehaviorPlugin} from '.' import {behavior} from './registry' @@ -10,7 +10,7 @@ const keyupBehavior: { [key: string]: BehaviorPlugin<'keyup'> | undefined } = { ' ': (event, target, instance) => { - if (isClickableInput(target)) { + if (isClickableInput(target) || isSummaryForParentDetails(target)) { return () => instance.dispatchUIEvent(target, 'click') } }, diff --git a/src/utils/click/isSummaryForParentDetails.ts b/src/utils/click/isSummaryForParentDetails.ts new file mode 100644 index 00000000..175cb6d5 --- /dev/null +++ b/src/utils/click/isSummaryForParentDetails.ts @@ -0,0 +1,18 @@ +import {isElementType} from '../misc/isElementType' + +/** + * Check if the element is the summary for its parent `
` element, + * i.e. the first `` child of a `
` element. + * Only this `` is activated per keyboard. + */ +export function isSummaryForParentDetails(element: Element) { + const parent = element.parentElement + return ( + isElementType(element, 'summary') && + !!parent && + isElementType(parent, 'details') && + Array.from(parent.children).find(child => + isElementType(child, 'summary'), + ) === element + ) +} diff --git a/src/utils/index.ts b/src/utils/index.ts index 8ab15cc8..478d7063 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -1,4 +1,5 @@ export * from './click/isClickableInput' +export * from './click/isSummaryForParentDetails' export * from './dataTransfer/Blob' export * from './dataTransfer/DataTransfer' diff --git a/tests/event/behavior/keypress.ts b/tests/event/behavior/keypress.ts index 38d60e05..099ccf75 100644 --- a/tests/event/behavior/keypress.ts +++ b/tests/event/behavior/keypress.ts @@ -125,6 +125,37 @@ cases( }, ) +cases( + 'trigger click on for [Enter]', + ({html, target = 'details/summary', hasClick = true}) => { + const {eventWasFired, getEvents, xpathNode} = render(html) + + setupInstance().dispatchUIEvent(xpathNode(target), 'keypress', { + key: 'Enter', + }) + + expect(eventWasFired('click')).toBe(hasClick) + if (hasClick) { + expect(getEvents('click')[0]).toHaveProperty('detail', 0) + } + }, + { + 'on summary of details': { + html: `
foo
`, + }, + 'omit on summary outside of details': { + html: `
foo
`, + target: 'div/summary', + hasClick: false, + }, + 'omit on second summary of details': { + html: `
foobar
`, + target: 'details/summary[2]', + hasClick: false, + }, + }, +) + cases( 'submit form on [Enter]', async ({html, click, submit}) => { diff --git a/tests/event/behavior/keyup.ts b/tests/event/behavior/keyup.ts index 15796f6b..dd7854b8 100644 --- a/tests/event/behavior/keyup.ts +++ b/tests/event/behavior/keyup.ts @@ -38,3 +38,32 @@ cases( }, }, ) + +cases( + 'trigger click on for [Space]', + ({html, target = 'details/summary', hasClick = true}) => { + const {eventWasFired, getEvents, xpathNode} = render(html) + + setupInstance().dispatchUIEvent(xpathNode(target), 'keyup', {key: ' '}) + + expect(eventWasFired('click')).toBe(hasClick) + if (hasClick) { + expect(getEvents('click')[0]).toHaveProperty('detail', 0) + } + }, + { + 'on summary of details': { + html: `
foo
`, + }, + 'omit on summary outside of details': { + html: `
foo
`, + target: 'div/summary', + hasClick: false, + }, + 'omit on second summary of details': { + html: `
foobar
`, + target: 'details/summary[2]', + hasClick: false, + }, + }, +) diff --git a/tests/keyboard/index.ts b/tests/keyboard/index.ts index 76451277..35fa3d93 100644 --- a/tests/keyboard/index.ts +++ b/tests/keyboard/index.ts @@ -213,3 +213,16 @@ test('typing on focused element with iframe', async () => { iframe.remove() }) + +test('toggle
per [Enter] and [Space] on ', async () => { + const {element, xpathNode, user} = setup( + `
foo

bar

`, + ) + expect(xpathNode('details/summary')).toHaveFocus() + + await user.keyboard('[Enter]') + expect(element.open).toBe(true) + + await user.keyboard('[Space]') + expect(element.open).toBe(false) +})