Skip to content

Commit 67eb59c

Browse files
fix(switch): implement internals controller (#2702)
* fix(switch): implement internals controller * fix(switch): add stopPropagation to stop window scroll * fix(switch): move stopPropagation to keyDown * test(switch): update role to switch * fix(switch): update demo to use spans in a single label * fix(switch): updateLabel now updates child spans in the label * fix(switch): use data-state attr as selector instead of span * fix(switch): update all demos to use single label * fix(switch): add aria-hidden to svg * test(switch): update tests to use single label * fix(switch): try to improve SR support * chore: debug demos * chore(switch): debug tabindex * fix(switch): tabindex attr on host * fix(switch): focus outline * docs(switch): restore elementinternals polyfill * chore(switch): add changeset * test(switch): remove duplicate when checked test for show icon, improve checked/unchecked * docs(switch): update doc example labels --------- Co-authored-by: Benny Powers - עם ישראל חי! <[email protected]> Co-authored-by: Benny Powers <[email protected]>
1 parent 3766961 commit 67eb59c

File tree

10 files changed

+163
-72
lines changed

10 files changed

+163
-72
lines changed

.changeset/violet-wombats-tell.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
---
2+
"@patternfly/elements": major
3+
---
4+
5+
`<pf-switch>`: Reimplemented label API improving accessibility.
6+
7+
```html
8+
<!-- BEFORE: -->
9+
<pf-switch id="checked" checked show-check-icon></pf-switch>
10+
<label for="checked" data-state="on">Message when on</label>
11+
<label for="checked" data-state="off">Message when off</label>
12+
<!-- AFTER: -->
13+
<pf-switch id="checked" checked show-check-icon></pf-switch>
14+
<label for="checked">
15+
<span data-state="on">Message when on</span>
16+
<span data-state="off" hidden>Message when off</span>
17+
</label>

elements/pf-switch/BaseSwitch.css

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ svg {
1919
cursor: not-allowed;
2020
}
2121

22-
:host(:disabled:focus-within) #container {
22+
:host(:disabled:is(:focus,:focus-within)) {
2323
outline: none;
2424
}
2525

elements/pf-switch/BaseSwitch.ts

Lines changed: 38 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,61 +1,65 @@
11
import { LitElement, html } from 'lit';
22
import { property } from 'lit/decorators/property.js';
3-
import styles from './BaseSwitch.css';
43

4+
import { InternalsController } from '@patternfly/pfe-core/controllers/internals-controller.js';
5+
6+
import styles from './BaseSwitch.css';
57
/**
68
* Switch
79
*/
810
export abstract class BaseSwitch extends LitElement {
911
static readonly styles = [styles];
1012

11-
static readonly shadowRootOptions = { ...LitElement.shadowRootOptions, delegatesFocus: true, };
12-
1313
static readonly formAssociated = true;
1414

1515
declare shadowRoot: ShadowRoot;
1616

17-
#internals = this.attachInternals();
18-
19-
#initiallyDisabled = this.hasAttribute('disabled');
17+
#internals = InternalsController.of(this, { role: 'switch' });
2018

2119
@property({ reflect: true }) label?: string;
2220

2321
@property({ reflect: true, type: Boolean, attribute: 'show-check-icon' }) showCheckIcon = false;
2422

2523
@property({ reflect: true, type: Boolean }) checked = false;
2624

27-
disabled = this.#initiallyDisabled;
25+
@property({ reflect: true, type: Boolean }) disabled = false;
2826

2927
get labels(): NodeListOf<HTMLLabelElement> {
3028
return this.#internals.labels as NodeListOf<HTMLLabelElement>;
3129
}
3230

3331
override connectedCallback(): void {
3432
super.connectedCallback();
35-
this.setAttribute('role', 'checkbox');
33+
this.tabIndex = 0;
3634
this.addEventListener('click', this.#onClick);
3735
this.addEventListener('keyup', this.#onKeyup);
36+
this.addEventListener('keydown', this.#onKeyDown);
3837
this.#updateLabels();
3938
}
4039

4140
formDisabledCallback(disabled: boolean) {
4241
this.disabled = disabled;
43-
this.requestUpdate();
4442
}
4543

4644
override render() {
4745
return html`
48-
<div id="container" tabindex="0">
49-
<svg id="toggle" fill="currentColor" height="1em" width="1em" viewBox="0 0 512 512">
50-
<path ?hidden=${!this.showCheckIcon} d="M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z" />
46+
<div id="container">
47+
<svg id="toggle"
48+
role="presentation"
49+
fill="currentColor"
50+
height="1em"
51+
width="1em"
52+
viewBox="0 0 512 512"
53+
?hidden=${!this.showCheckIcon}>
54+
<path d="M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z" />
5155
</svg>
5256
</div>
5357
`;
5458
}
5559

56-
override updated() {
57-
this.#internals.ariaChecked = String(this.checked);
58-
this.#internals.ariaDisabled = String(this.disabled);
60+
override willUpdate() {
61+
this.#internals.ariaChecked = String(!!this.checked);
62+
this.#internals.ariaDisabled = String(!!this.disabled);
5963
}
6064

6165
#onClick(event: Event) {
@@ -75,11 +79,17 @@ export abstract class BaseSwitch extends LitElement {
7579
}
7680

7781
#onKeyup(event: KeyboardEvent) {
78-
switch (event.key) {
79-
case ' ':
80-
case 'Enter':
81-
event.preventDefault();
82-
this.#toggle();
82+
if (event.key === ' ' || event.key === 'Enter') {
83+
event.preventDefault();
84+
event.stopPropagation();
85+
this.#toggle();
86+
}
87+
}
88+
89+
#onKeyDown(event: KeyboardEvent) {
90+
if (event.key === ' ') {
91+
event.preventDefault();
92+
event.stopPropagation();
8393
}
8494
}
8595

@@ -95,10 +105,13 @@ export abstract class BaseSwitch extends LitElement {
95105

96106
#updateLabels() {
97107
const labelState = this.checked ? 'on' : 'off';
98-
if (this.labels.length > 1) {
99-
for (const label of this.labels) {
100-
label.hidden = label.dataset.state !== labelState;
101-
}
102-
}
108+
this.labels.forEach(label => {
109+
const states = label.querySelectorAll<HTMLElement>('[data-state]');
110+
states.forEach(state => {
111+
if (state) {
112+
state.hidden = state.dataset.state !== labelState;
113+
}
114+
});
115+
});
103116
}
104117
}

elements/pf-switch/demo/checked.html

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@
33
<fieldset>
44
<legend>Checked with label</legend>
55
<pf-switch id="checked" checked show-check-icon></pf-switch>
6-
<label for="checked" data-state="on">Message when on</label>
7-
<label for="checked" data-state="off">Message when off</label>
6+
<label for="checked">
7+
<span data-state="on">Message when on</span>
8+
<span data-state="off" hidden>Message when off</span>
9+
</label>
810
</fieldset>
911
</form>
1012
</section>

elements/pf-switch/demo/disabled.html

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,17 @@
33
<fieldset>
44
<legend>Checked and Disabled</legend>
55
<pf-switch id="checked-disabled" checked disabled></pf-switch>
6-
<label for="checked-disabled" data-state="on">Message when on</label>
7-
<label for="checked-disabled" data-state="off">Message when off</label>
6+
<label for="checked-disabled">
7+
<span data-state="on">Message when on</span>
8+
<span data-state="off" hidden>Message when off</span>
9+
</label>
810
</fieldset>
911
<fieldset>
1012
<pf-switch id="default-disabled" disabled></pf-switch>
11-
<label for="default-disabled" data-state="on">Message when on</label>
12-
<label for="default-disabled" data-state="off">Message when off</label>
13+
<label for="default-disabled">
14+
<span data-state="on">Message when on</span>
15+
<span data-state="off" hidden>Message when off</span>
16+
</label>
1317
</fieldset>
1418
</form>
1519
</section>

elements/pf-switch/demo/pf-switch.html

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@
44
<fieldset id="fieldset-a">
55
<legend>Option A</legend>
66
<pf-switch id="a" checked></pf-switch>
7-
<label for="a" data-state="on">Message when on</label>
8-
<label for="a" data-state="off" hidden>Message when off</label>
7+
<label for="a">
8+
<span data-state="on">Message when on</span>
9+
<span data-state="off" hidden>Message when off</span>
10+
</label>
911
</fieldset>
1012

1113
<fieldset id="fieldset-b">
@@ -16,20 +18,28 @@
1618
<fieldset id="form-disabled">
1719
<legend>Form Disabled State</legend>
1820
<pf-switch id="disable-a" aria-controls="fieldset-a"></pf-switch>
19-
<label for="disable-a" data-state="on">Fieldset A is disabled</label>
20-
<label for="disable-a" data-state="off" hidden>Fieldset A is enabled</label>
21+
<label for="disable-a">
22+
<span data-state="on">Fieldset A is disabled</span>
23+
<span data-state="off" hidden>Fieldset A is enabled</span>
24+
</label>
2125

2226
<pf-switch id="disable-a-switch" aria-controls="a"></pf-switch>
23-
<label for="disable-a-switch" data-state="on">Switch A is disabled</label>
24-
<label for="disable-a-switch" data-state="off" hidden>Switch A is enabled</label>
27+
<label for="disable-a-switch">
28+
<span data-state="on">Fieldset A is disabled</span>
29+
<span data-state="off" hidden>Fieldset A is enabled</span>
30+
</label>
2531

2632
<pf-switch id="disable-b" aria-controls="fieldset-b"></pf-switch>
27-
<label for="disable-b" data-state="on">Fieldset B is disabled</label>
28-
<label for="disable-b" data-state="off" hidden>Fieldset B is enabled</label>
33+
<label for="disable-b">
34+
<span data-state="on">Fieldset B is disabled</span>
35+
<span data-state="off" hidden>Fieldset B is enabled</span>
36+
</label>
2937

3038
<pf-switch id="disable-b-switch" aria-controls="b"></pf-switch>
31-
<label for="disable-b-switch" data-state="on">Switch B is disabled</label>
32-
<label for="disable-b-switch" data-state="off" hidden>Switch B is enabled</label>
39+
<label for="disable-b-switch">
40+
<span data-state="on">Switch B is disabled</span>
41+
<span data-state="off" hidden>Switch B is enabled</span>
42+
</label>
3343
</fieldset>
3444
</form>
3545
</section>

elements/pf-switch/demo/reversed.html

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@
22
<form>
33
<fieldset>
44
<legend>Reversed</legend>
5-
<label for="reversed" data-state="on">Message when on</label>
6-
<label for="reversed" data-state="off">Message when off</label>
5+
<label for="reversed">
6+
<span data-state="on">Message when on</span>
7+
<span data-state="off" hidden>Message when off</span>
8+
</label>
79
<pf-switch id="reversed" reversed></pf-switch>
810
</fieldset>
911
</form>

elements/pf-switch/docs/pf-switch.md

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,20 @@
55
provide a more explicit, visible representation on a setting.
66

77
<pf-switch id="overview-switch" checked></pf-switch>
8-
<label for="overview-switch" data-state="on">Message when on</label>
9-
<label for="overview-switch" data-state="off" hidden>Message when off</label>
8+
<label for="overview-switch">
9+
<span data-state="on">Message when on</span>
10+
<span data-state="off" hidden>Message when off</span>
11+
</label>
1012
{% endrenderOverview %}
1113

1214
{% band header="Usage" %}
1315
### Basic
1416
{% htmlexample %}
1517
<pf-switch id="color-scheme-toggle"></pf-switch>
16-
<label for="color-scheme-toggle" data-state="on">Message when on</label>
17-
<label for="color-scheme-toggle" data-state="off" hidden>Message when off</label>
18+
<label for="color-scheme-toggle">
19+
<span data-state="on">Message when on</span>
20+
<span data-state="off" hidden>Message when off</span>
21+
</label>
1822
{% endhtmlexample %}
1923

2024
### Without label
@@ -25,8 +29,10 @@
2529
### Checked with label
2630
{% htmlexample %}
2731
<pf-switch id="checked" checked show-check-icon></pf-switch>
28-
<label for="checked" data-state="on">Message when on</label>
29-
<label for="checked" data-state="off">Message when off</label>
32+
<label for="checked">
33+
<span data-state="on">Message when on</span>
34+
<span data-state="off" hidden>Message when off</span>
35+
</label>
3036
{% endhtmlexample %}
3137

3238
### Disabled
@@ -35,14 +41,17 @@
3541
<fieldset>
3642
<legend>Checked and Disabled</legend>
3743
<pf-switch id="checked-disabled" checked disabled></pf-switch>
38-
<label for="checked-disabled" data-state="on">Message when on</label>
39-
<label for="checked-disabled" data-state="off">Message when off</label>
44+
<label for="checked-disabled">
45+
<span data-state="on">Message when on</span>
46+
<span data-state="off" hidden>Message when off</span>
47+
</label>
4048
</fieldset>
41-
4249
<fieldset>
4350
<pf-switch id="default-disabled" disabled></pf-switch>
44-
<label for="default-disabled" data-state="on">Message when on</label>
45-
<label for="default-disabled" data-state="off">Message when off</label>
51+
<label for="default-disabled">
52+
<span data-state="on">Message when on</span>
53+
<span data-state="off" hidden>Message when off</span>
54+
</label>
4655
</fieldset>
4756
</form>
4857
{% endhtmlexample %}

elements/pf-switch/pf-switch.css

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,11 @@
8282
var(--pf-c-switch__toggle--before--Transition, translate .25s ease 0s)); ;
8383
}
8484

85-
:host(:focus-within) #container {
85+
:host {
86+
outline: none;
87+
}
88+
89+
:host(:is(:focus,:focus-within)) #container {
8690
outline: var(--pf-c-switch__input--focus__toggle--OutlineWidth,
8791
var(--pf-global--BorderWidth--md, 2px)) solid var(--pf-c-switch__input--focus__toggle--OutlineColor,
8892
var(--pf-global--primary-color--100, #06c));

0 commit comments

Comments
 (0)