Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
907ac00
fix(switch): implement internals controller
zeroedin Mar 5, 2024
c782b9f
Merge branch 'main' into fix/switch-internals-accessibility
bennypowers Mar 11, 2024
79f91e5
Merge branch 'main' into fix/switch-internals-accessibility
bennypowers Mar 12, 2024
3cd4860
Merge branch 'main' into fix/switch-internals-accessibility
zeroedin Mar 21, 2024
58ccc15
fix(switch): add stopPropagation to stop window scroll
zeroedin Mar 21, 2024
ec93e11
fix(switch): move stopPropagation to keyDown
zeroedin Mar 21, 2024
a45715d
test(switch): update role to switch
zeroedin Mar 21, 2024
9ccd718
fix(switch): update demo to use spans in a single label
zeroedin Mar 21, 2024
cc1de74
fix(switch): updateLabel now updates child spans in the label
zeroedin Mar 21, 2024
f397f54
fix(switch): use data-state attr as selector instead of span
zeroedin Mar 21, 2024
23cc3c2
fix(switch): update all demos to use single label
zeroedin Mar 21, 2024
37bf1f5
fix(switch): add aria-hidden to svg
zeroedin Mar 21, 2024
05b5c91
test(switch): update tests to use single label
zeroedin Mar 21, 2024
5dbdb14
fix(switch): try to improve SR support
bennypowers Mar 24, 2024
ef26ff4
chore: debug demos
bennypowers Mar 27, 2024
ae1e72d
chore(switch): debug tabindex
bennypowers Mar 27, 2024
c3a2aa7
fix(switch): tabindex attr on host
bennypowers Mar 27, 2024
adc92c2
fix(switch): focus outline
bennypowers Mar 27, 2024
24e188d
docs(switch): restore elementinternals polyfill
bennypowers Mar 27, 2024
ced261d
Merge branch 'main' into fix/switch-internals-accessibility
bennypowers Mar 27, 2024
e2db522
chore(switch): add changeset
zeroedin Mar 27, 2024
13f43c3
test(switch): remove duplicate when checked test for show icon, impro…
zeroedin Mar 27, 2024
bc6b804
Merge branch 'main' into fix/switch-internals-accessibility
zeroedin Mar 27, 2024
3b52839
docs(switch): update doc example labels
zeroedin Mar 27, 2024
12a308f
Merge branch 'main' into fix/switch-internals-accessibility
bennypowers Mar 27, 2024
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
17 changes: 17 additions & 0 deletions .changeset/violet-wombats-tell.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
---
"@patternfly/elements": major
---

`<pf-switch>`: Reimplemented label API improving accessibility.

```html
<!-- BEFORE: -->
<pf-switch id="checked" checked show-check-icon></pf-switch>
<label for="checked" data-state="on">Message when on</label>
<label for="checked" data-state="off">Message when off</label>
<!-- AFTER: -->
<pf-switch id="checked" checked show-check-icon></pf-switch>
<label for="checked">
<span data-state="on">Message when on</span>
<span data-state="off" hidden>Message when off</span>
</label>
2 changes: 1 addition & 1 deletion elements/pf-switch/BaseSwitch.css
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ svg {
cursor: not-allowed;
}

:host(:disabled:focus-within) #container {
:host(:disabled:is(:focus,:focus-within)) {
outline: none;
}

Expand Down
63 changes: 38 additions & 25 deletions elements/pf-switch/BaseSwitch.ts
Original file line number Diff line number Diff line change
@@ -1,61 +1,65 @@
import { LitElement, html } from 'lit';
import { property } from 'lit/decorators/property.js';
import styles from './BaseSwitch.css';

import { InternalsController } from '@patternfly/pfe-core/controllers/internals-controller.js';

import styles from './BaseSwitch.css';
/**
* Switch
*/
export abstract class BaseSwitch extends LitElement {
static readonly styles = [styles];

static readonly shadowRootOptions = { ...LitElement.shadowRootOptions, delegatesFocus: true, };

static readonly formAssociated = true;

declare shadowRoot: ShadowRoot;

#internals = this.attachInternals();

#initiallyDisabled = this.hasAttribute('disabled');
#internals = InternalsController.of(this, { role: 'switch' });

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

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

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

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

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

override connectedCallback(): void {
super.connectedCallback();
this.setAttribute('role', 'checkbox');
this.tabIndex = 0;
this.addEventListener('click', this.#onClick);
this.addEventListener('keyup', this.#onKeyup);
this.addEventListener('keydown', this.#onKeyDown);
this.#updateLabels();
}

formDisabledCallback(disabled: boolean) {
this.disabled = disabled;
this.requestUpdate();
}

override render() {
return html`
<div id="container" tabindex="0">
<svg id="toggle" fill="currentColor" height="1em" width="1em" viewBox="0 0 512 512">
<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" />
<div id="container">
<svg id="toggle"
role="presentation"
fill="currentColor"
height="1em"
width="1em"
viewBox="0 0 512 512"
?hidden=${!this.showCheckIcon}>
<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" />
</svg>
</div>
`;
}

override updated() {
this.#internals.ariaChecked = String(this.checked);
this.#internals.ariaDisabled = String(this.disabled);
override willUpdate() {
this.#internals.ariaChecked = String(!!this.checked);
this.#internals.ariaDisabled = String(!!this.disabled);
}

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

#onKeyup(event: KeyboardEvent) {
switch (event.key) {
case ' ':
case 'Enter':
event.preventDefault();
this.#toggle();
if (event.key === ' ' || event.key === 'Enter') {
event.preventDefault();
event.stopPropagation();
this.#toggle();
}
}

#onKeyDown(event: KeyboardEvent) {
if (event.key === ' ') {
event.preventDefault();
event.stopPropagation();
}
}

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

#updateLabels() {
const labelState = this.checked ? 'on' : 'off';
if (this.labels.length > 1) {
for (const label of this.labels) {
label.hidden = label.dataset.state !== labelState;
}
}
this.labels.forEach(label => {
const states = label.querySelectorAll<HTMLElement>('[data-state]');
states.forEach(state => {
if (state) {
state.hidden = state.dataset.state !== labelState;
}
});
});
}
}
6 changes: 4 additions & 2 deletions elements/pf-switch/demo/checked.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@
<fieldset>
<legend>Checked with label</legend>
<pf-switch id="checked" checked show-check-icon></pf-switch>
<label for="checked" data-state="on">Message when on</label>
<label for="checked" data-state="off">Message when off</label>
<label for="checked">
<span data-state="on">Message when on</span>
<span data-state="off" hidden>Message when off</span>
</label>
</fieldset>
</form>
</section>
Expand Down
12 changes: 8 additions & 4 deletions elements/pf-switch/demo/disabled.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,17 @@
<fieldset>
<legend>Checked and Disabled</legend>
<pf-switch id="checked-disabled" checked disabled></pf-switch>
<label for="checked-disabled" data-state="on">Message when on</label>
<label for="checked-disabled" data-state="off">Message when off</label>
<label for="checked-disabled">
<span data-state="on">Message when on</span>
<span data-state="off" hidden>Message when off</span>
</label>
</fieldset>
<fieldset>
<pf-switch id="default-disabled" disabled></pf-switch>
<label for="default-disabled" data-state="on">Message when on</label>
<label for="default-disabled" data-state="off">Message when off</label>
<label for="default-disabled">
<span data-state="on">Message when on</span>
<span data-state="off" hidden>Message when off</span>
</label>
</fieldset>
</form>
</section>
Expand Down
30 changes: 20 additions & 10 deletions elements/pf-switch/demo/pf-switch.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@
<fieldset id="fieldset-a">
<legend>Option A</legend>
<pf-switch id="a" checked></pf-switch>
<label for="a" data-state="on">Message when on</label>
<label for="a" data-state="off" hidden>Message when off</label>
<label for="a">
<span data-state="on">Message when on</span>
<span data-state="off" hidden>Message when off</span>
</label>
</fieldset>

<fieldset id="fieldset-b">
Expand All @@ -16,20 +18,28 @@
<fieldset id="form-disabled">
<legend>Form Disabled State</legend>
<pf-switch id="disable-a" aria-controls="fieldset-a"></pf-switch>
<label for="disable-a" data-state="on">Fieldset A is disabled</label>
<label for="disable-a" data-state="off" hidden>Fieldset A is enabled</label>
<label for="disable-a">
<span data-state="on">Fieldset A is disabled</span>
<span data-state="off" hidden>Fieldset A is enabled</span>
</label>

<pf-switch id="disable-a-switch" aria-controls="a"></pf-switch>
<label for="disable-a-switch" data-state="on">Switch A is disabled</label>
<label for="disable-a-switch" data-state="off" hidden>Switch A is enabled</label>
<label for="disable-a-switch">
<span data-state="on">Fieldset A is disabled</span>
<span data-state="off" hidden>Fieldset A is enabled</span>
</label>

<pf-switch id="disable-b" aria-controls="fieldset-b"></pf-switch>
<label for="disable-b" data-state="on">Fieldset B is disabled</label>
<label for="disable-b" data-state="off" hidden>Fieldset B is enabled</label>
<label for="disable-b">
<span data-state="on">Fieldset B is disabled</span>
<span data-state="off" hidden>Fieldset B is enabled</span>
</label>

<pf-switch id="disable-b-switch" aria-controls="b"></pf-switch>
<label for="disable-b-switch" data-state="on">Switch B is disabled</label>
<label for="disable-b-switch" data-state="off" hidden>Switch B is enabled</label>
<label for="disable-b-switch">
<span data-state="on">Switch B is disabled</span>
<span data-state="off" hidden>Switch B is enabled</span>
</label>
</fieldset>
</form>
</section>
Expand Down
6 changes: 4 additions & 2 deletions elements/pf-switch/demo/reversed.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@
<form>
<fieldset>
<legend>Reversed</legend>
<label for="reversed" data-state="on">Message when on</label>
<label for="reversed" data-state="off">Message when off</label>
<label for="reversed">
<span data-state="on">Message when on</span>
<span data-state="off" hidden>Message when off</span>
</label>
<pf-switch id="reversed" reversed></pf-switch>
</fieldset>
</form>
Expand Down
31 changes: 20 additions & 11 deletions elements/pf-switch/docs/pf-switch.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,20 @@
provide a more explicit, visible representation on a setting.

<pf-switch id="overview-switch" checked></pf-switch>
<label for="overview-switch" data-state="on">Message when on</label>
<label for="overview-switch" data-state="off" hidden>Message when off</label>
<label for="overview-switch">
<span data-state="on">Message when on</span>
<span data-state="off" hidden>Message when off</span>
</label>
{% endrenderOverview %}

{% band header="Usage" %}
### Basic
{% htmlexample %}
<pf-switch id="color-scheme-toggle"></pf-switch>
<label for="color-scheme-toggle" data-state="on">Message when on</label>
<label for="color-scheme-toggle" data-state="off" hidden>Message when off</label>
<label for="color-scheme-toggle">
<span data-state="on">Message when on</span>
<span data-state="off" hidden>Message when off</span>
</label>
{% endhtmlexample %}

### Without label
Expand All @@ -25,8 +29,10 @@
### Checked with label
{% htmlexample %}
<pf-switch id="checked" checked show-check-icon></pf-switch>
<label for="checked" data-state="on">Message when on</label>
<label for="checked" data-state="off">Message when off</label>
<label for="checked">
<span data-state="on">Message when on</span>
<span data-state="off" hidden>Message when off</span>
</label>
{% endhtmlexample %}

### Disabled
Expand All @@ -35,14 +41,17 @@
<fieldset>
<legend>Checked and Disabled</legend>
<pf-switch id="checked-disabled" checked disabled></pf-switch>
<label for="checked-disabled" data-state="on">Message when on</label>
<label for="checked-disabled" data-state="off">Message when off</label>
<label for="checked-disabled">
<span data-state="on">Message when on</span>
<span data-state="off" hidden>Message when off</span>
</label>
</fieldset>

<fieldset>
<pf-switch id="default-disabled" disabled></pf-switch>
<label for="default-disabled" data-state="on">Message when on</label>
<label for="default-disabled" data-state="off">Message when off</label>
<label for="default-disabled">
<span data-state="on">Message when on</span>
<span data-state="off" hidden>Message when off</span>
</label>
</fieldset>
</form>
{% endhtmlexample %}
Expand Down
6 changes: 5 additions & 1 deletion elements/pf-switch/pf-switch.css
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,11 @@
var(--pf-c-switch__toggle--before--Transition, translate .25s ease 0s)); ;
}

:host(:focus-within) #container {
:host {
outline: none;
}

:host(:is(:focus,:focus-within)) #container {
outline: var(--pf-c-switch__input--focus__toggle--OutlineWidth,
var(--pf-global--BorderWidth--md, 2px)) solid var(--pf-c-switch__input--focus__toggle--OutlineColor,
var(--pf-global--primary-color--100, #06c));
Expand Down
Loading