Skip to content

♿ a11y(bal-checkbox, bal-radio): VO keyboard navigation not working as expected #1636

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/tall-ears-leave.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@baloise/ds-core': patch
---

**checkbox**: voice over check action fixed
2 changes: 1 addition & 1 deletion e2e/cypress/component/bal-checkbox-button.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ describe('bal-checkbox-button', () => {
})

it('should select first one and send change event', () => {
cy.get('bal-checkbox-button').eq(0).click().find('input').blur()
cy.get('bal-checkbox-button').eq(0).find('input').check({ force: true }).blur()

cy.get('bal-checkbox-button').eq(0).find('input').should('be.checked')
cy.get('bal-checkbox-button').eq(1).find('input').should('not.be.checked')
Expand Down
2 changes: 1 addition & 1 deletion e2e/cypress/component/bal-checkbox-group.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ describe('bal-checkbox-group', () => {
})

it('should select first one and send change event', () => {
cy.get('bal-checkbox').eq(0).click().find('input').blur()
cy.get('bal-checkbox').eq(0).find('input').check({ force: true }).blur()

cy.get('bal-checkbox').eq(0).find('input').should('be.checked')
cy.get('bal-checkbox').eq(1).find('input').should('not.be.checked')
Expand Down
1 change: 0 additions & 1 deletion e2e/cypress/component/bal-checkbox.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ describe('bal-checkbox', () => {

cy.get('bal-checkbox').click().find('input').blur()

cy.get('@click').should('have.been.calledOnce')
cy.get('@balFocus').should('have.been.calledOnce')
cy.get('@balChange').should('have.been.calledOnce')
cy.get('@balBlur').should('have.been.calledOnce')
Expand Down
74 changes: 74 additions & 0 deletions packages/core/src/components.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -479,6 +479,20 @@ export namespace Components {
*/
"value"?: string | number;
}
interface BalCheck {
/**
* If `true`, the checkbox is selected.
*/
"checked": boolean;
/**
* If `true`, the element is not mutable, focusable, or even submitted with the form. The user can neither edit nor focus on the control, nor its form control descendants.
*/
"disabled"?: boolean;
/**
* If `true` the component gets a invalid red style.
*/
"invalid"?: boolean;
}
interface BalCheckbox {
/**
* If `true`, in Angular reactive forms the control will not be set invalid
Expand Down Expand Up @@ -3137,6 +3151,20 @@ export namespace Components {
*/
"value"?: string;
}
interface BalSwitch {
/**
* If `true`, the checkbox is selected.
*/
"checked": boolean;
/**
* If `true`, the element is not mutable, focusable, or even submitted with the form. The user can neither edit nor focus on the control, nor its form control descendants.
*/
"disabled"?: boolean;
/**
* If `true` the component gets a invalid red style.
*/
"invalid"?: boolean;
}
interface BalTabItem {
/**
* Tells if this route is active and overrides the bal-tabs value property.
Expand Down Expand Up @@ -3886,6 +3914,12 @@ declare global {
prototype: HTMLBalCarouselItemElement;
new (): HTMLBalCarouselItemElement;
};
interface HTMLBalCheckElement extends Components.BalCheck, HTMLStencilElement {
}
var HTMLBalCheckElement: {
prototype: HTMLBalCheckElement;
new (): HTMLBalCheckElement;
};
interface HTMLBalCheckboxElementEventMap {
"balFocus": BalEvents.BalCheckboxFocusDetail;
"balBlur": BalEvents.BalCheckboxBlurDetail;
Expand Down Expand Up @@ -4860,6 +4894,12 @@ declare global {
prototype: HTMLBalStepsElement;
new (): HTMLBalStepsElement;
};
interface HTMLBalSwitchElement extends Components.BalSwitch, HTMLStencilElement {
}
var HTMLBalSwitchElement: {
prototype: HTMLBalSwitchElement;
new (): HTMLBalSwitchElement;
};
interface HTMLBalTabItemElementEventMap {
"balNavigate": BalEvents.BalTabItemNavigateDetail;
}
Expand Down Expand Up @@ -5026,6 +5066,7 @@ declare global {
"bal-card-title": HTMLBalCardTitleElement;
"bal-carousel": HTMLBalCarouselElement;
"bal-carousel-item": HTMLBalCarouselItemElement;
"bal-check": HTMLBalCheckElement;
"bal-checkbox": HTMLBalCheckboxElement;
"bal-checkbox-button": HTMLBalCheckboxButtonElement;
"bal-checkbox-group": HTMLBalCheckboxGroupElement;
Expand Down Expand Up @@ -5117,6 +5158,7 @@ declare global {
"bal-stage-image": HTMLBalStageImageElement;
"bal-step-item": HTMLBalStepItemElement;
"bal-steps": HTMLBalStepsElement;
"bal-switch": HTMLBalSwitchElement;
"bal-tab-item": HTMLBalTabItemElement;
"bal-table": HTMLBalTableElement;
"bal-tabs": HTMLBalTabsElement;
Expand Down Expand Up @@ -5595,6 +5637,20 @@ declare namespace LocalJSX {
*/
"value"?: string | number;
}
interface BalCheck {
/**
* If `true`, the checkbox is selected.
*/
"checked"?: boolean;
/**
* If `true`, the element is not mutable, focusable, or even submitted with the form. The user can neither edit nor focus on the control, nor its form control descendants.
*/
"disabled"?: boolean;
/**
* If `true` the component gets a invalid red style.
*/
"invalid"?: boolean;
}
interface BalCheckbox {
/**
* If `true`, in Angular reactive forms the control will not be set invalid
Expand Down Expand Up @@ -8265,6 +8321,20 @@ declare namespace LocalJSX {
*/
"value"?: string;
}
interface BalSwitch {
/**
* If `true`, the checkbox is selected.
*/
"checked"?: boolean;
/**
* If `true`, the element is not mutable, focusable, or even submitted with the form. The user can neither edit nor focus on the control, nor its form control descendants.
*/
"disabled"?: boolean;
/**
* If `true` the component gets a invalid red style.
*/
"invalid"?: boolean;
}
interface BalTabItem {
/**
* Tells if this route is active and overrides the bal-tabs value property.
Expand Down Expand Up @@ -8728,6 +8798,7 @@ declare namespace LocalJSX {
"bal-card-title": BalCardTitle;
"bal-carousel": BalCarousel;
"bal-carousel-item": BalCarouselItem;
"bal-check": BalCheck;
"bal-checkbox": BalCheckbox;
"bal-checkbox-button": BalCheckboxButton;
"bal-checkbox-group": BalCheckboxGroup;
Expand Down Expand Up @@ -8819,6 +8890,7 @@ declare namespace LocalJSX {
"bal-stage-image": BalStageImage;
"bal-step-item": BalStepItem;
"bal-steps": BalSteps;
"bal-switch": BalSwitch;
"bal-tab-item": BalTabItem;
"bal-table": BalTable;
"bal-tabs": BalTabs;
Expand Down Expand Up @@ -8851,6 +8923,7 @@ declare module "@stencil/core" {
"bal-card-title": LocalJSX.BalCardTitle & JSXBase.HTMLAttributes<HTMLBalCardTitleElement>;
"bal-carousel": LocalJSX.BalCarousel & JSXBase.HTMLAttributes<HTMLBalCarouselElement>;
"bal-carousel-item": LocalJSX.BalCarouselItem & JSXBase.HTMLAttributes<HTMLBalCarouselItemElement>;
"bal-check": LocalJSX.BalCheck & JSXBase.HTMLAttributes<HTMLBalCheckElement>;
"bal-checkbox": LocalJSX.BalCheckbox & JSXBase.HTMLAttributes<HTMLBalCheckboxElement>;
"bal-checkbox-button": LocalJSX.BalCheckboxButton & JSXBase.HTMLAttributes<HTMLBalCheckboxButtonElement>;
"bal-checkbox-group": LocalJSX.BalCheckboxGroup & JSXBase.HTMLAttributes<HTMLBalCheckboxGroupElement>;
Expand Down Expand Up @@ -8942,6 +9015,7 @@ declare module "@stencil/core" {
"bal-stage-image": LocalJSX.BalStageImage & JSXBase.HTMLAttributes<HTMLBalStageImageElement>;
"bal-step-item": LocalJSX.BalStepItem & JSXBase.HTMLAttributes<HTMLBalStepItemElement>;
"bal-steps": LocalJSX.BalSteps & JSXBase.HTMLAttributes<HTMLBalStepsElement>;
"bal-switch": LocalJSX.BalSwitch & JSXBase.HTMLAttributes<HTMLBalSwitchElement>;
"bal-tab-item": LocalJSX.BalTabItem & JSXBase.HTMLAttributes<HTMLBalTabItemElement>;
"bal-table": LocalJSX.BalTable & JSXBase.HTMLAttributes<HTMLBalTableElement>;
"bal-tabs": LocalJSX.BalTabs & JSXBase.HTMLAttributes<HTMLBalTabsElement>;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
@use '@baloise/ds-styles/sass/mixins' as *
@use '../bal-radio-checkbox.vars' as *

.bal-check
display: inline-flex
justify-content: center
align-items: center
cursor: pointer
user-select: none
width: 1.5rem
height: 1.5rem
border-radius: var(--bal-radius-normal)
//
// default color
border: 2px solid var(--bal-color-primary)
background-color: transparent
+hover
&:hover
background: var(--bal-color-grey-2)
border-color: var(--bal-color-border-primary-hovered)
&:active
background: var(--bal-color-grey-1)
border-color: var(--bal-color-border-primary-pressed)
//
// checked
&--checked
background-color: var(--bal-color-primary)
+hover
&:hover
background: var(--bal-color-border-primary-hovered)
border-color: var(--bal-color-border-primary-hovered)
&:active
background: var(--bal-color-border-primary-pressed)
border-color: var(--bal-color-border-primary-pressed)
//
// invalid
&--invalid
border-color: var(--bal-color-border-danger)
background-color: var(--bal-color-danger-1)
+hover
&:hover
background-color: var(--bal-color-danger-1)
border-color: var(--bal-color-border-danger-hovered)
&:active
background-color: var(--bal-color-danger-1)
border-color: var(--bal-color-border-danger-pressed)
&--invalid#{&}--checked
border-color: var(--bal-color-border-danger)
background: var(--bal-color-border-danger)
+hover
&:hover
background-color: var(--bal-color-border-danger-hovered)
border-color: var(--bal-color-border-danger-hovered)
&:active
background-color: var(--bal-color-border-danger-pressed)
border-color: var(--bal-color-border-danger-pressed)
//
// disabled
&--disabled
cursor: default !important
background: var(--bal-color-grey-2) !important
border-color: var(--bal-color-border-grey-dark) !important
&--disabled#{&}--checked
background-color: var(--bal-color-border-grey-dark) !important
border-color: var(--bal-color-border-grey-dark) !important
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { Component, ComponentInterface, h, Host, Prop } from '@stencil/core'
import { BEM } from '../../../utils/bem'

@Component({
tag: 'bal-check',
styleUrl: './bal-check.sass',
})
export class Check implements ComponentInterface {
/**
* PUBLIC PROPERTY API
* ------------------------------------------------------
*/

/**
* If `true`, the checkbox is selected.
*/
@Prop({ mutable: true }) checked = false

/**
* If `true` the component gets a invalid red style.
*/
@Prop() invalid?: boolean = undefined

/**
* If `true`, the element is not mutable, focusable, or even submitted with the form. The user can neither edit nor focus on the control, nor its form control descendants.
*/
@Prop() disabled?: boolean = undefined

/**
* RENDER
* ------------------------------------------------------
*/

render() {
const block = BEM.block('check')
const checked = !!this.checked
const disabled = !!this.disabled
const invalid = !!this.invalid

return (
<Host
class={{
...block.class(),
...block.modifier('checked').class(checked),
...block.modifier('disabled').class(disabled),
...block.modifier('invalid').class(invalid),
}}
>
{checked ? <bal-icon name="check" color="white" size="small" aria-hidden="true"></bal-icon> : ''}
</Host>
)
}
}
Original file line number Diff line number Diff line change
@@ -1,28 +1,28 @@
import { areArraysEqual } from '../../../utils/array'
import {
Component,
h,
Host,
ComponentInterface,
Prop,
Element,
Watch,
Event,
EventEmitter,
h,
Host,
Listen,
Method,
Prop,
State,
Listen,
Watch,
} from '@stencil/core'
import { stopEventBubbling } from '../../../utils/form-input'
import { hasTagName, isDescendant } from '../../../utils/helpers'
import { ariaBooleanToString } from 'packages/core/src/utils/aria'
import { areArraysEqual } from '../../../utils/array'
import { inheritAttributes } from '../../../utils/attributes'
import { BEM } from '../../../utils/bem'
import { BalCheckboxOption } from '../bal-checkbox.type'
import { BalFocusObserver, ListenToFocus } from '../../../utils/focus'
import { BalAriaForm, BalAriaFormLinking, defaultBalAriaForm } from '../../../utils/form'
import { stopEventBubbling } from '../../../utils/form-input'
import { hasTagName, isDescendant } from '../../../utils/helpers'
import { Loggable, Logger, LogInstance } from '../../../utils/log'
import { BalMutationObserver, ListenToMutation } from '../../../utils/mutation'
import { BalAriaForm, BalAriaFormLinking, defaultBalAriaForm } from '../../../utils/form'
import { BalFocusObserver, ListenToFocus } from '../../../utils/focus'
import { ariaBooleanToString } from 'packages/core/src/utils/aria'
import { BalCheckboxOption } from '../bal-checkbox.type'

@Component({
tag: 'bal-checkbox-group',
Expand Down Expand Up @@ -270,10 +270,11 @@ export class CheckboxGroup
}

@Listen('balChange', { capture: true, target: 'document' })
listenOnClick(ev: UIEvent) {
listenOnCheckboxChange(ev: UIEvent) {
if (this.control) {
if (isDescendant(this.el, ev.target as HTMLElement)) {
stopEventBubbling(ev)
this.updateValues()
}
}
}
Expand Down Expand Up @@ -394,7 +395,6 @@ export class CheckboxGroup
if (element.href) {
return
}
ev.preventDefault()

const selectedCheckbox = ev.target && (ev.target as HTMLElement).closest('bal-checkbox')
if (selectedCheckbox) {
Expand All @@ -403,6 +403,10 @@ export class CheckboxGroup
}
}

this.updateValues()
}

private updateValues() {
// generate new value array out of the checked checkboxes
const newValue: any[] = []
this.getCheckboxes().forEach(cb => {
Expand Down
Empty file.
Loading
Loading