Skip to content

Commit

Permalink
fix(sbb-selection-panel): arrow navigation with radio-button-group wi…
Browse files Browse the repository at this point in the history
…th no content should select the radio-button (#2255)
  • Loading branch information
DavideMininni-Fincons authored Dec 12, 2023
1 parent 0ee530c commit a3d5891
Show file tree
Hide file tree
Showing 3 changed files with 104 additions and 13 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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 {
Expand All @@ -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();
}

Expand Down
58 changes: 58 additions & 0 deletions src/components/selection-panel/selection-panel.e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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`
<sbb-radio-button-group id="group-no-content" name="input-group-name" value="Value 2">
<sbb-selection-panel disable-animation id="no-content-1">
<sbb-radio-button id="input-no-content-1" value="Value 1">Value one</sbb-radio-button>
</sbb-selection-panel>
<sbb-selection-panel disable-animation id="no-content-2">
<sbb-radio-button id="input-no-content-2" value="Value 2">Value two</sbb-radio-button>
</sbb-selection-panel>
<sbb-selection-panel disable-animation id="no-content-3">
<sbb-radio-button id="input-no-content-3" value="Value 3" disabled
>Value three</sbb-radio-button
>
</sbb-selection-panel>
<sbb-selection-panel disable-animation id="no-content-4">
<sbb-radio-button id="input-no-content-4" value="Value 4">Value four</sbb-radio-button>
</sbb-selection-panel>
</sbb-radio-button-group>
`);
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;

Expand Down
31 changes: 31 additions & 0 deletions src/components/selection-panel/selection-panel.stories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -472,6 +472,31 @@ const WithNoContentTemplate = ({
</sbb-selection-panel>
`;

const WithNoContentGroupTemplate = ({
checkedInput,
disabledInput,
...args
}: Args): TemplateResult => html`
<sbb-radio-button-group orientation="vertical" horizontal-from="large">
<sbb-selection-panel ${sbbSpread(args)}>
${cardBadge()}
<sbb-radio-button value="Value one" ?disabled=${disabledInput}>
Value one ${suffixAndSubtext()}
</sbb-radio-button>
</sbb-selection-panel>
<sbb-selection-panel ${sbbSpread(args)}>
${cardBadge()}
<sbb-radio-button value="Value two" ?checked=${checkedInput}>
Value two ${suffixAndSubtext()}
</sbb-radio-button>
</sbb-selection-panel>
<sbb-selection-panel ${sbbSpread(args)}>
${cardBadge()}
<sbb-radio-button value="Value three"> Value three ${suffixAndSubtext()} </sbb-radio-button>
</sbb-selection-panel>
</sbb-radio-button-group>
`;

export const WithCheckbox: StoryObj = {
render: WithCheckboxTemplate,
argTypes: basicArgTypes,
Expand Down Expand Up @@ -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,
Expand Down

0 comments on commit a3d5891

Please sign in to comment.