diff --git a/src/components/radio-button/radio-button-group/radio-button-group.ts b/src/components/radio-button/radio-button-group/radio-button-group.ts index b90c98bce0..5b6e041c19 100644 --- a/src/components/radio-button/radio-button-group/radio-button-group.ts +++ b/src/components/radio-button/radio-button-group/radio-button-group.ts @@ -2,7 +2,7 @@ import { CSSResultGroup, html, LitElement, nothing, PropertyValues, TemplateResu import { customElement, property, state } from 'lit/decorators.js'; import { isArrowKeyPressed, getNextElementIndex, interactivityChecker } from '../../core/a11y'; -import { toggleDatasetEntry, setAttribute } from '../../core/dom'; +import { toggleDatasetEntry, setAttribute, isValidAttribute } from '../../core/dom'; import { createNamedSlotState, HandlerRepository, @@ -241,7 +241,12 @@ export class SbbRadioButtonGroupElement extends LitElement { } private _getRadioTabIndex(radio: SbbRadioButtonElement): number { - return (radio.checked || this._hasSelectionPanel) && !radio.disabled && !this.disabled ? 0 : -1; + const isSelected: boolean = radio.checked && !radio.disabled && !this.disabled; + const isParentPanelWithContent: boolean = + radio.parentElement.nodeName === 'SBB-SELECTION-PANEL' && + isValidAttribute(radio.parentElement, 'data-has-content'); + + return isSelected || (this._hasSelectionPanel && isParentPanelWithContent) ? 0 : -1; } private _handleKeyDown(evt: KeyboardEvent): void { @@ -262,17 +267,14 @@ export class SbbRadioButtonGroupElement extends LitElement { return; } - let current: number; - let nextIndex: number; - - if (this._hasSelectionPanel) { - current = enabledRadios.findIndex((e: SbbRadioButtonElement) => e === evt.target); - nextIndex = getNextElementIndex(evt, current, enabledRadios.length); - } else { - const checked: number = enabledRadios.findIndex( - (radio: SbbRadioButtonElement) => radio.checked, - ); - nextIndex = getNextElementIndex(evt, checked, enabledRadios.length); + const current: number = enabledRadios.findIndex((e: SbbRadioButtonElement) => e === evt.target); + const nextIndex: number = getNextElementIndex(evt, current, enabledRadios.length); + + // Selection on arrow keypress is allowed only if all the selection-panels have no content. + const allPanelsHaveNoContent: boolean = ( + Array.from(this.querySelectorAll?.('sbb-selection-panel')) || [] + ).every((e) => !isValidAttribute(e, 'data-has-content')); + if (!this._hasSelectionPanel || (this._hasSelectionPanel && allPanelsHaveNoContent)) { enabledRadios[nextIndex].select(); } diff --git a/src/components/selection-panel/selection-panel.e2e.ts b/src/components/selection-panel/selection-panel.e2e.ts index 381582680c..8ff7e00370 100644 --- a/src/components/selection-panel/selection-panel.e2e.ts +++ b/src/components/selection-panel/selection-panel.e2e.ts @@ -231,6 +231,64 @@ describe('sbb-selection-panel', () => { }); }); + describe('with radio group with no slotted content', () => { + it('focus selected, the focus and select on keyboard navigation', async () => { + await fixture(html` + + + Value one + + + Value two + + + Value three + + + Value four + + + `); + const wrapperNoContent = document.querySelector('#group-no-content'); + const firstInputNoContent: SbbRadioButtonElement = + document.querySelector('#input-no-content-1'); + const secondInputNoContent: SbbRadioButtonElement = + document.querySelector('#input-no-content-2'); + const fourthInputNoContent: SbbRadioButtonElement = + document.querySelector('#input-no-content-4'); + + await sendKeys({ down: 'Tab' }); + await waitForLitRender(wrapperNoContent); + expect(document.activeElement.id).to.be.equal(secondInputNoContent.id); + + await sendKeys({ down: 'ArrowUp' }); + await waitForLitRender(wrapperNoContent); + expect(document.activeElement.id).to.be.equal(firstInputNoContent.id); + expect(secondInputNoContent).not.to.have.attribute('checked'); + expect(firstInputNoContent).to.have.attribute('checked'); + + await sendKeys({ down: 'ArrowRight' }); + await waitForLitRender(wrapperNoContent); + expect(document.activeElement.id).to.be.equal(secondInputNoContent.id); + expect(firstInputNoContent).not.to.have.attribute('checked'); + expect(secondInputNoContent).to.have.attribute('checked'); + + await sendKeys({ down: 'ArrowDown' }); + await waitForLitRender(wrapperNoContent); + expect(document.activeElement.id).to.be.equal(fourthInputNoContent.id); + expect(secondInputNoContent).not.to.have.attribute('checked'); + expect(fourthInputNoContent).to.have.attribute('checked'); + + await sendKeys({ down: 'ArrowLeft' }); + await waitForLitRender(wrapperNoContent); + expect(document.activeElement.id).to.be.equal(secondInputNoContent.id); + expect(fourthInputNoContent).not.to.have.attribute('checked'); + expect(secondInputNoContent).to.have.attribute('checked'); + }); + }); + describe('with nested radio buttons', () => { let nestedElement: SbbRadioButtonGroupElement; diff --git a/src/components/selection-panel/selection-panel.stories.ts b/src/components/selection-panel/selection-panel.stories.ts index fb0e370572..d9217676dc 100644 --- a/src/components/selection-panel/selection-panel.stories.ts +++ b/src/components/selection-panel/selection-panel.stories.ts @@ -472,6 +472,31 @@ const WithNoContentTemplate = ({ `; +const WithNoContentGroupTemplate = ({ + checkedInput, + disabledInput, + ...args +}: Args): TemplateResult => html` + + + ${cardBadge()} + + Value one ${suffixAndSubtext()} + + + + ${cardBadge()} + + Value two ${suffixAndSubtext()} + + + + ${cardBadge()} + Value three ${suffixAndSubtext()} + + +`; + export const WithCheckbox: StoryObj = { render: WithCheckboxTemplate, argTypes: basicArgTypes, @@ -665,6 +690,12 @@ export const WithNoContentCheckedDisabled: StoryObj = { args: { ...basicArgs, checkedInput: true, disabledInput: true }, }; +export const WithNoContentGroup: StoryObj = { + render: WithNoContentGroupTemplate, + argTypes: basicArgTypes, + args: { ...basicArgs, checkedInput: true }, +}; + export const TicketsOptionsExample: StoryObj = { render: TicketsOptionsExampleTemplate, argTypes: basicArgTypes,