From 7427e02eaad9918ddc06c3d92f7a93feea0a062e Mon Sep 17 00:00:00 2001 From: Steven Spriggs Date: Tue, 19 May 2026 23:52:19 -0400 Subject: [PATCH 01/11] feat(switch): port pf-v5-switch to pf-v6-switch --- docs/framework-integration/react.md | 6 +- elements/pf-v5-accordion/demo/bordered.html | 4 +- .../pf-v5-accordion/test/pf-accordion.spec.ts | 6 +- elements/pf-v5-button/demo/form-control.html | 18 +- elements/pf-v5-card/demo/modifiers.html | 16 +- .../pf-v5-dropdown/demo/custom-toggle.html | 4 +- .../demo/scrollspy-with-subsections.html | 10 +- .../demo/alignment.html | 8 +- .../pf-v5-progress-stepper/demo/compact.html | 8 +- .../pf-v5-progress-stepper/demo/info.html | 4 +- elements/pf-v5-switch/README.md | 88 ---- elements/pf-v5-switch/demo/checked.html | 41 -- elements/pf-v5-switch/demo/disabled.html | 47 -- elements/pf-v5-switch/demo/index.html | 80 ---- .../pf-v5-switch/demo/nested-in-label.html | 56 --- elements/pf-v5-switch/demo/reversed.html | 42 -- elements/pf-v5-switch/demo/without-label.html | 38 -- elements/pf-v5-switch/docs/CHANGELOG.old.md | 20 - elements/pf-v5-switch/docs/pf-v5-switch.md | 70 --- elements/pf-v5-switch/docs/screenshot.png | Bin 25844 -> 0 bytes elements/pf-v5-switch/pf-v5-switch.css | 154 ------- elements/pf-v5-switch/pf-v5-switch.ts | 141 ------ elements/pf-v5-switch/test/pf-switch.spec.ts | 230 ---------- elements/pf-v5-tabs/demo/box.html | 4 +- elements/pf-v5-tabs/demo/inset.html | 2 +- elements/pf-v5-tabs/demo/overflow.html | 4 +- elements/pf-v5-tabs/demo/vertical.html | 4 +- elements/pf-v5-text-input/demo/disabled.html | 4 +- .../pf-v5-text-input/demo/kitchen-sink.html | 4 +- elements/pf-v5-text-input/demo/read-only.html | 4 +- .../pf-v5-text-input/demo/validation.html | 4 +- .../pf-v5-tooltip/demo/block-triggers.html | 4 +- .../pf-v5-tooltip/demo/custom-styles.html | 4 +- elements/pf-v5-tooltip/demo/index.html | 4 +- elements/pf-v5-tooltip/demo/placement.html | 4 +- .../pf-v5-tooltip/demo/slotted-content.html | 4 +- elements/pf-v6-switch/README.md | 52 +++ .../pf-v6-switch/demo/checked-with-label.html | 9 + elements/pf-v6-switch/demo/disabled.html | 26 ++ elements/pf-v6-switch/demo/index.html | 9 + .../pf-v6-switch/demo/nested-in-label.html | 41 ++ .../pf-v6-switch/demo/reversed-layout.html | 9 + elements/pf-v6-switch/demo/without-label.html | 11 + elements/pf-v6-switch/docs/pf-v6-switch.md | 58 +++ elements/pf-v6-switch/docs/screenshot.png | Bin 0 -> 3511 bytes elements/pf-v6-switch/pf-v6-switch.css | 211 +++++++++ elements/pf-v6-switch/pf-v6-switch.ts | 189 ++++++++ .../test/pf-v6-switch.e2e.ts} | 2 +- .../pf-v6-switch/test/pf-v6-switch.spec.ts | 425 ++++++++++++++++++ 49 files changed, 1108 insertions(+), 1075 deletions(-) delete mode 100644 elements/pf-v5-switch/README.md delete mode 100644 elements/pf-v5-switch/demo/checked.html delete mode 100644 elements/pf-v5-switch/demo/disabled.html delete mode 100644 elements/pf-v5-switch/demo/index.html delete mode 100644 elements/pf-v5-switch/demo/nested-in-label.html delete mode 100644 elements/pf-v5-switch/demo/reversed.html delete mode 100644 elements/pf-v5-switch/demo/without-label.html delete mode 100644 elements/pf-v5-switch/docs/CHANGELOG.old.md delete mode 100644 elements/pf-v5-switch/docs/pf-v5-switch.md delete mode 100644 elements/pf-v5-switch/docs/screenshot.png delete mode 100644 elements/pf-v5-switch/pf-v5-switch.css delete mode 100644 elements/pf-v5-switch/pf-v5-switch.ts delete mode 100644 elements/pf-v5-switch/test/pf-switch.spec.ts create mode 100644 elements/pf-v6-switch/README.md create mode 100644 elements/pf-v6-switch/demo/checked-with-label.html create mode 100644 elements/pf-v6-switch/demo/disabled.html create mode 100644 elements/pf-v6-switch/demo/index.html create mode 100644 elements/pf-v6-switch/demo/nested-in-label.html create mode 100644 elements/pf-v6-switch/demo/reversed-layout.html create mode 100644 elements/pf-v6-switch/demo/without-label.html create mode 100644 elements/pf-v6-switch/docs/pf-v6-switch.md create mode 100644 elements/pf-v6-switch/docs/screenshot.png create mode 100644 elements/pf-v6-switch/pf-v6-switch.css create mode 100644 elements/pf-v6-switch/pf-v6-switch.ts rename elements/{pf-v5-switch/test/pf-switch.e2e.ts => pf-v6-switch/test/pf-v6-switch.e2e.ts} (95%) create mode 100644 elements/pf-v6-switch/test/pf-v6-switch.spec.ts diff --git a/docs/framework-integration/react.md b/docs/framework-integration/react.md index c6de196478..20f8185666 100644 --- a/docs/framework-integration/react.md +++ b/docs/framework-integration/react.md @@ -130,7 +130,7 @@ tags: import { Button } from "@patternfly/elements/react/pf-v5-button/pf-v5-button.js"; import { Card } from "@patternfly/elements/react/pf-v5-card/pf-v5-card.js"; - import { Switch } from "@patternfly/elements/react/pf-v5-switch/pf-v5-switch.js"; + import { Switch } from "@patternfly/elements/react/pf-v6-switch/pf-v6-switch.js"; import { Popover } from "@patternfly/elements/react/pf-v5-popover/pf-v5-popover.js"; import { Tooltip } from "@patternfly/elements/react/pf-v5-tooltip/pf-v5-tooltip.js"; @@ -160,7 +160,7 @@ tags: ### Switch - Now we have a card and a button component, let's add [`pf-v5-switch`][pf-v5-switch] + Now we have a card and a button component, let's add [`pf-v6-switch`][pf-v6-switch] web component in our app. We will enable/disable the decrement button by clicking on the Switch button. @@ -355,7 +355,7 @@ tags: [vite]: https://vitejs.dev/guide/#scaffolding-your-first-vite-project [pf-v5-button]: https://patternflyelements.org/components/button/ [pf-v5-card]: https://patternflyelements.org/components/card/ -[pf-v5-switch]: https://patternflyelements.org/components/switch/ +[pf-v6-switch]: https://patternflyelements.org/components/switch/ [pf-v5-tooltip]: https://patternflyelements.org/components/tooltip/ [pf-v5-popover]: https://patternflyelements.org/components/popover/ [inng]: https://medium.com/patternfly-elements/using-patternfly-elements-web-components-in-your-angular-app-4b18b1c9c363 diff --git a/elements/pf-v5-accordion/demo/bordered.html b/elements/pf-v5-accordion/demo/bordered.html index 4b143f510c..47048286ea 100644 --- a/elements/pf-v5-accordion/demo/bordered.html +++ b/elements/pf-v5-accordion/demo/bordered.html @@ -1,7 +1,7 @@

Bordered

- +
@@ -57,7 +57,7 @@

Item five

-``` - -Or, if you are using [NPM](https://npm.im), install it - -```bash -npm install @patternfly/elements -``` - -Then once installed, import it to your application: - -```js -import '@patternfly/elements/pf-v5-switch/pf-v5-switch.js'; -``` - -## Usage -```html - - - -``` - -### Form Associated - -`` is a form-associated custom element. That means that it can -participate in HTML forms just like a native ``. For example, if you add -the `name` attribute, or the `id` attribute, the element will appear in the -FormData object. For example, if you add the `name` attribute, or the `id` -attribute, the element will appear in the `FormData` object. - -```html -
- - -
-``` - - -### Without label - -```html - -``` - -### Checked with label - -```html - - - -``` - -### Disabled Switches - -```html -
-
- Checked and Disabled - - - -
- -
- - - -
-
-``` diff --git a/elements/pf-v5-switch/demo/checked.html b/elements/pf-v5-switch/demo/checked.html deleted file mode 100644 index e8030bdc47..0000000000 --- a/elements/pf-v5-switch/demo/checked.html +++ /dev/null @@ -1,41 +0,0 @@ -
-
-
- Checked with label - - -
-
-
- - - - diff --git a/elements/pf-v5-switch/demo/disabled.html b/elements/pf-v5-switch/demo/disabled.html deleted file mode 100644 index e98fb3150a..0000000000 --- a/elements/pf-v5-switch/demo/disabled.html +++ /dev/null @@ -1,47 +0,0 @@ -
-
-
- Checked and Disabled - - -
-
- - -
-
-
- - - - diff --git a/elements/pf-v5-switch/demo/index.html b/elements/pf-v5-switch/demo/index.html deleted file mode 100644 index 405dcee752..0000000000 --- a/elements/pf-v5-switch/demo/index.html +++ /dev/null @@ -1,80 +0,0 @@ -
-

A switch toggles the state of a setting (between on and off). Switches provide a more explicit, visible representation on a setting.

-
-
- Option A - - -
- -
- Option B (no explicit label) - -
- -
- Form Disabled State - - - - - - - - - - - -
-
-
- - - - diff --git a/elements/pf-v5-switch/demo/nested-in-label.html b/elements/pf-v5-switch/demo/nested-in-label.html deleted file mode 100644 index 82e4754267..0000000000 --- a/elements/pf-v5-switch/demo/nested-in-label.html +++ /dev/null @@ -1,56 +0,0 @@ -
-
-
- Nested in a label - - Submit - Submit to read status -
-
-
- - - - diff --git a/elements/pf-v5-switch/demo/reversed.html b/elements/pf-v5-switch/demo/reversed.html deleted file mode 100644 index 555faa3aa1..0000000000 --- a/elements/pf-v5-switch/demo/reversed.html +++ /dev/null @@ -1,42 +0,0 @@ -
-
-
- Reversed - - -
-
-
- - - - diff --git a/elements/pf-v5-switch/demo/without-label.html b/elements/pf-v5-switch/demo/without-label.html deleted file mode 100644 index ef89b2986b..0000000000 --- a/elements/pf-v5-switch/demo/without-label.html +++ /dev/null @@ -1,38 +0,0 @@ -
-
-
- Without label - -
-
-
- - - - diff --git a/elements/pf-v5-switch/docs/CHANGELOG.old.md b/elements/pf-v5-switch/docs/CHANGELOG.old.md deleted file mode 100644 index a3024c9e08..0000000000 --- a/elements/pf-v5-switch/docs/CHANGELOG.old.md +++ /dev/null @@ -1,20 +0,0 @@ -# @patternfly/pfe-switch - -## 2.0.0-next.0 - -### Major Changes - -- 4400866a: Added ``, a control that toggles the state of a setting between on and off. - Switches provide a more explicit, visible representation on a setting than checkboxes. - - ```html -
- - - -
- ``` - -### Patch Changes - -- dc34cf51: Prevented clicks and other interactions when the switch is disabled diff --git a/elements/pf-v5-switch/docs/pf-v5-switch.md b/elements/pf-v5-switch/docs/pf-v5-switch.md deleted file mode 100644 index b65872d7e3..0000000000 --- a/elements/pf-v5-switch/docs/pf-v5-switch.md +++ /dev/null @@ -1,70 +0,0 @@ -{% renderInstallation %} {% endrenderInstallation %} - -{% renderOverview %} - A switch toggles the state of a setting (between on and off). Switches - provide a more explicit, visible representation on a setting. - - - -{% endrenderOverview %} - -{% band header="Usage" %} - ### Basic - {% htmlexample %} - - - {% endhtmlexample %} - - ### Without label - {% htmlexample %} - - {% endhtmlexample %} - - ### Checked with label - {% htmlexample %} - - - {% endhtmlexample %} - - ### Disabled - {% htmlexample %} -
-
- Checked and Disabled - - -
-
- - -
-
- {% endhtmlexample %} -{% endband %} - -{% renderSlots %}{% endrenderSlots %} - -{% renderAttributes %}{% endrenderAttributes %} - -{% renderMethods %}{% endrenderMethods %} - -{% renderEvents %}{% endrenderEvents %} - -{% renderCssCustomProperties %}{% endrenderCssCustomProperties %} - -{% renderCssParts %}{% endrenderCssParts %} diff --git a/elements/pf-v5-switch/docs/screenshot.png b/elements/pf-v5-switch/docs/screenshot.png deleted file mode 100644 index b38283146dbc1f5e44c9131be44a201076aaeb56..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 25844 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 element. - * Update this value based on the checked state to communicate the meaning of - * each state to assistive technology users, e.g. "Wi-Fi on" / "Wi-Fi off". - */ - @property({ reflect: true, attribute: 'accessible-label' }) accessibleLabel?: string; - - /** Flag to show if the switch has a check icon. */ - @property({ reflect: true, type: Boolean, attribute: 'show-check-icon' }) showCheckIcon = false; - - /** Flag to show if the switch is checked. */ - @property({ reflect: true, type: Boolean }) checked = false; - - /** Flag to show if the switch is disabled. */ - @property({ reflect: true, type: Boolean }) disabled = false; - - get labels(): NodeListOf { - return this.#internals.labels as NodeListOf; - } - - override connectedCallback(): void { - super.connectedCallback(); - this.tabIndex = 0; - this.addEventListener('click', this.#onClick); - this.addEventListener('keyup', this.#onKeyup); - this.addEventListener('keydown', this.#onKeydown); - this.#updateLabels(); - } - - formDisabledCallback(disabled: boolean): void { - this.disabled = disabled; - this.requestUpdate(); - } - - override willUpdate(): void { - this.#internals.ariaChecked = String(!!this.checked); - this.#internals.ariaDisabled = String(!!this.disabled); - this.#internals.ariaLabel = this.accessibleLabel || this.label || null; - } - - override render(): TemplateResult<1> { - return html` -
- - - -
- `; - } - - #onClick(event: Event) { - // @ts-expect-error: firefox workarounds for double-firing in the case of switch nested in label - const { originalTarget, explicitOriginalTarget } = event; - if (explicitOriginalTarget) { - let labels: HTMLLabelElement[]; - if (originalTarget === event.target - && !(labels = Array.from(this.labels)).includes(explicitOriginalTarget) - && labels.includes(this.closest('label') as HTMLLabelElement)) { - return; - } - } - this.#toggle(); - } - - #onKeyup(event: KeyboardEvent) { - switch (event.key) { - case ' ': - case 'Enter': - event.preventDefault(); - this.#toggle(); - } - } - - #onKeydown(event: KeyboardEvent) { - if (event.key === ' ') { - event.preventDefault(); - event.stopPropagation(); - } - } - - #toggle() { - if (!this.disabled) { - this.checked = !this.checked; - this.#updateLabels(); - this.dispatchEvent(new Event('change', { bubbles: true })); - } - } - - #updateLabels() { - const labelState = this.checked ? 'on' : 'off'; - this.labels.forEach(label => { - const states = label.querySelectorAll('[data-state]'); - states.forEach(state => { - if (state) { - state.hidden = state.dataset.state !== labelState; - } - }); - }); - } -} - -declare global { - interface HTMLElementTagNameMap { - 'pf-v5-switch': PfV5Switch; - } -} diff --git a/elements/pf-v5-switch/test/pf-switch.spec.ts b/elements/pf-v5-switch/test/pf-switch.spec.ts deleted file mode 100644 index 176a733b38..0000000000 --- a/elements/pf-v5-switch/test/pf-switch.spec.ts +++ /dev/null @@ -1,230 +0,0 @@ -import type { A11yTreeSnapshot } from '@patternfly/pfe-tools/test/a11y-snapshot.js'; -import { expect, html, nextFrame } from '@open-wc/testing'; -import { createFixture } from '@patternfly/pfe-tools/test/create-fixture.js'; -import { a11ySnapshot } from '@patternfly/pfe-tools/test/a11y-snapshot.js'; - -import { PfV5Switch } from '@patternfly/elements/pf-v5-switch/pf-v5-switch.js'; - -describe('', function() { - it('imperatively instantiates', function() { - expect(document.createElement('pf-v5-switch')).to.be.an.instanceof(PfV5Switch); - }); - - describe('simply instantiating', function() { - let element: PfV5Switch; - let snapshot: A11yTreeSnapshot; - beforeEach(async function() { - const container = await createFixture(html` -
- -
- `); - element = container.querySelector('pf-v5-switch')!; - snapshot = await a11ySnapshot({ selector: 'pf-v5-switch' }); - }); - it('should upgrade', async function() { - const klass = customElements.get('pf-v5-switch'); - expect(element) - .to.be.an.instanceOf(klass) - .and - .to.be.an.instanceOf(PfV5Switch); - }); - it('has accessible role', function() { - expect(snapshot.role).to.equal('switch'); - }); - it('has accessible checked field', function() { - expect(snapshot.role).to.equal('switch'); - }); - it('requires accessible name', function() { - // Double negative - this would fail an accessibility audit, - // but that failure would be correct, because the template instantiated - // in this test's beforeeach hook does not have an accessible name - expect(snapshot.name).to.not.be.ok; - }); - }); - - describe('with accessible-label attribute', function() { - let element: PfV5Switch; - let snapshot: A11yTreeSnapshot; - beforeEach(async function() { - element = await createFixture(html` - - `); - snapshot = await a11ySnapshot({ selector: 'pf-v5-switch' }); - }); - it('has an accessible name from accessible-label', function() { - expect(snapshot.name).to.equal('Dark Mode'); - }); - it('keeps the same accessible name regardless of checked state', async function() { - element.click(); - await element.updateComplete; - await nextFrame(); - snapshot = await a11ySnapshot({ selector: 'pf-v5-switch' }); - expect(snapshot.name).to.equal('Dark Mode'); - }); - }); - - describe('with labels for on and off state', function() { - let element: PfV5Switch; - let snapshot: A11yTreeSnapshot; - beforeEach(async function() { - const container = await createFixture(html` -
- - -
- `); - element = container.querySelector('pf-v5-switch')!; - snapshot = await a11ySnapshot({ selector: '#switch' }); - }); - - it('is accessible', function() { - expect(snapshot.role).to.equal('switch'); - expect(snapshot.name).to.be.ok; - expect(snapshot.checked).to.be.false; - }); - - it('should show the label for the unchecked state', function() { - expect(snapshot.name).to.equal('Message when off'); - }); - - describe('clicking the switch', function() { - beforeEach(async function() { - element.click(); - await element.updateComplete; - await nextFrame(); - snapshot = await a11ySnapshot({ selector: '#switch' }); - }); - it('should be checked', function() { - expect(element.checked).to.be.true; - expect(snapshot.checked).to.be.true; - }); - it('should show the label for the checked state', function() { - expect(snapshot.name).to.equal('Message when on'); - }); - }); - }); - - describe('when checked attr is present', function() { - let element: PfV5Switch; - let snapshot: A11yTreeSnapshot; - beforeEach(async function() { - element = await createFixture(html` - - `); - - await element.updateComplete; - await nextFrame(); - snapshot = await a11ySnapshot({ selector: '#switch' }); - }); - - it('should be checked', function() { - expect(element.checked).to.be.true; - expect(snapshot.checked).to.be.true; - }); - }); - - describe('when checked attr is not present', function() { - let element: PfV5Switch; - let snapshot: A11yTreeSnapshot; - beforeEach(async function() { - element = await createFixture(html` - - `); - - await element.updateComplete; - await nextFrame(); - snapshot = await a11ySnapshot({ selector: '#switch' }); - }); - - it('should be checked', function() { - expect(element.checked).to.be.false; - expect(snapshot.checked).to.be.false; - }); - }); - - - describe('when checked and show-check-icon attrs are present', function() { - let element: PfV5Switch; - beforeEach(async function() { - const container = await createFixture(html` -
- - -
- `); - element = container.querySelector('pf-v5-switch')!; - }); - it('should display a check icon', async function() { - // TODO: can we test this without inspecting the private shadowRoot? - const svg = element.shadowRoot.querySelector('svg'); - expect(svg).to.be.ok; - expect(svg?.hasAttribute('hidden')).to.be.false; - }); - }); - - describe('when checked and show-check-icon attrs are present', function() { - let element: PfV5Switch; - beforeEach(async function() { - element = await createFixture(html` - - - `); - }); - it('should display a check icon', async function() { - // TODO: can we test this without inspecting the private shadowRoot? - const svg = element.shadowRoot.querySelector('svg'); - expect(svg).to.be.ok; - expect(svg?.hasAttribute('hidden')).to.be.false; - }); - }); - - describe('when nested inside a label element', function() { - let label: HTMLLabelElement; - let element: PfV5Switch; - let snapshot: A11yTreeSnapshot; - beforeEach(async function() { - label = await createFixture(html` - - `); - element = label.querySelector('pf-v5-switch')!; - snapshot = await a11ySnapshot({ selector: 'pf-v5-switch' }); - }); - it('does not hide label', function() { - expect(label.hidden).to.be.false; - }); - it('has an accessible name', function() { - expect(snapshot.name).to.equal('Dark Mode'); - }); - describe('clicking the label', function() { - beforeEach(function() { - label.click(); - }); - it('toggles the state', function() { - expect(element.checked).to.be.true; - }); - }); - describe('clicking the switch', function() { - beforeEach(function() { - element.click(); - }); - it('toggles the state', function() { - expect(element.checked).to.be.true; - }); - }); - }); - - // TODO: test keyboard a11y with wtr sendKeys -}); diff --git a/elements/pf-v5-tabs/demo/box.html b/elements/pf-v5-tabs/demo/box.html index 7ab75da55d..3882b770cc 100644 --- a/elements/pf-v5-tabs/demo/box.html +++ b/elements/pf-v5-tabs/demo/box.html @@ -14,13 +14,13 @@ Box Type: - + diff --git a/elements/pf-v5-text-input/demo/validation.html b/elements/pf-v5-text-input/demo/validation.html index a33abdab7c..a710e8ea87 100644 --- a/elements/pf-v5-text-input/demo/validation.html +++ b/elements/pf-v5-text-input/demo/validation.html @@ -1,6 +1,6 @@
Invalid - + import '@patternfly/elements/pf-v5-button/pf-v5-button.js'; - import '@patternfly/elements/pf-v5-switch/pf-v5-switch.js'; + import '@patternfly/elements/pf-v6-switch/pf-v6-switch.js'; import '@patternfly/elements/pf-v5-text-input/pf-v5-text-input.js'; const onblur = document.getElementById('onblur'); const validated = document.getElementById('validated'); diff --git a/elements/pf-v5-tooltip/demo/block-triggers.html b/elements/pf-v5-tooltip/demo/block-triggers.html index 1a728b26b2..763d62f45b 100644 --- a/elements/pf-v5-tooltip/demo/block-triggers.html +++ b/elements/pf-v5-tooltip/demo/block-triggers.html @@ -1,6 +1,6 @@

Toggle Container Width

- +
@@ -27,7 +27,7 @@

Block Triggers

diff --git a/elements/pf-v6-switch/demo/disabled.html b/elements/pf-v6-switch/demo/disabled.html new file mode 100644 index 0000000000..9a569ac21b --- /dev/null +++ b/elements/pf-v6-switch/demo/disabled.html @@ -0,0 +1,26 @@ +--- +name: Disabled +description: A disabled switch cannot be toggled. +--- +
+ Togglable option for disabled checked example +
+
+ Togglable option for disabled unchecked example +
+
+ +
+
+ +
+ + + + diff --git a/elements/pf-v6-switch/demo/index.html b/elements/pf-v6-switch/demo/index.html new file mode 100644 index 0000000000..b3cca80ca2 --- /dev/null +++ b/elements/pf-v6-switch/demo/index.html @@ -0,0 +1,9 @@ +--- +name: Basic +description: A basic switch toggles the state of a setting. +--- +Togglable option for basic example + + diff --git a/elements/pf-v6-switch/demo/nested-in-label.html b/elements/pf-v6-switch/demo/nested-in-label.html new file mode 100644 index 0000000000..0422cdfdc0 --- /dev/null +++ b/elements/pf-v6-switch/demo/nested-in-label.html @@ -0,0 +1,41 @@ +--- +name: Nested in label +description: A switch can be nested inside a label element for form association. +--- +
+
+ Settings + +
+ + +
+ + + + diff --git a/elements/pf-v6-switch/demo/reversed-layout.html b/elements/pf-v6-switch/demo/reversed-layout.html new file mode 100644 index 0000000000..4aff3ec225 --- /dev/null +++ b/elements/pf-v6-switch/demo/reversed-layout.html @@ -0,0 +1,9 @@ +--- +name: Reversed layout +description: A switch with reversed layout places the label before the toggle. +--- +Togglable option for reversed example + + diff --git a/elements/pf-v6-switch/demo/without-label.html b/elements/pf-v6-switch/demo/without-label.html new file mode 100644 index 0000000000..b22ec59487 --- /dev/null +++ b/elements/pf-v6-switch/demo/without-label.html @@ -0,0 +1,11 @@ +--- +name: Without label +description: >- + A switch without a visible label must have an accessible-label attribute + to remain accessible. +--- + + + diff --git a/elements/pf-v6-switch/docs/pf-v6-switch.md b/elements/pf-v6-switch/docs/pf-v6-switch.md new file mode 100644 index 0000000000..149237fe08 --- /dev/null +++ b/elements/pf-v6-switch/docs/pf-v6-switch.md @@ -0,0 +1,58 @@ +{% renderOverview %} + Togglable option +{% endrenderOverview %} + +{% band header="Usage" %} + {% htmlexample %} + Togglable option + {% endhtmlexample %} + + + ### Reversed layout + Use the `reversed` attribute to place the label before the toggle. + + {% htmlexample %} + Togglable option + {% endhtmlexample %} + + + ### Without label + A switch without a visible label must have an `accessible-label` attribute + to remain accessible. + + {% htmlexample %} + + {% endhtmlexample %} + + + ### Checked with label + Use `data-state` children to show different labels for checked and unchecked states. + + {% htmlexample %} + + Message when on + Message when off + + {% endhtmlexample %} + + + ### Disabled + + {% htmlexample %} + Togglable option + Togglable option + {% endhtmlexample %} + +{% endband %} + +{% renderSlots %}{% endrenderSlots %} + +{% renderAttributes %}{% endrenderAttributes %} + +{% renderMethods %}{% endrenderMethods %} + +{% renderEvents %}{% endrenderEvents %} + +{% renderCssCustomProperties %}{% endrenderCssCustomProperties %} + +{% renderCssParts %}{% endrenderCssParts %} diff --git a/elements/pf-v6-switch/docs/screenshot.png b/elements/pf-v6-switch/docs/screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..b8a9d955b23542adef4d4244bdf35f39fe5bcd2f GIT binary patch literal 3511 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 ltfTmC00006Nkl` and `` children + * to show different text for each state. + * + * @fires {Event} change - Fires when the switch is toggled. Uses the native + * `Event` interface with no custom detail payload. + * Cancelable: call `preventDefault()` to reject the state change. + */ +@customElement('pf-v6-switch') +export class PfV6Switch extends LitElement { + static readonly styles: CSSStyleSheet[] = [styles]; + + static readonly formAssociated = true; + + declare shadowRoot: ShadowRoot; + + #internals = InternalsController.of(this, { role: 'switch' }); + + /** + * Accessible label for the switch when there is no visible label text. + * Should describe the checked state, e.g. "Wi-Fi" (not "Wi-Fi on/off"). + */ + @property({ reflect: true, attribute: 'accessible-label' }) accessibleLabel?: string; + + /** Flag to show a check icon on the toggle when checked. */ + @property({ reflect: true, type: Boolean, attribute: 'show-check-icon' }) showCheckIcon = false; + + /** Whether the switch is checked. */ + @property({ reflect: true, type: Boolean }) checked = false; + + /** Whether the switch is disabled. */ + @property({ reflect: true, type: Boolean }) disabled = false; + + /** Reverses the layout so the label appears before the toggle. */ + @property({ reflect: true, type: Boolean }) reversed = false; + + #hasSlottedContent = false; + + #initialChecked = false; + + get labels(): NodeListOf { + return this.#internals.labels as NodeListOf; + } + + override connectedCallback(): void { + super.connectedCallback(); + if (!isServer) { + this.#initialChecked = this.checked; + this.addEventListener('click', this.#onClick); + this.addEventListener('keyup', this.#onKeyup); + this.addEventListener('keydown', this.#onKeydown); + } + } + + override disconnectedCallback(): void { + super.disconnectedCallback(); + this.removeEventListener('click', this.#onClick); + this.removeEventListener('keyup', this.#onKeyup); + this.removeEventListener('keydown', this.#onKeydown); + } + + formDisabledCallback(disabled: boolean): void { + this.disabled = disabled; + this.requestUpdate(); + } + + formResetCallback(): void { + this.checked = this.#initialChecked; + } + + override willUpdate(): void { + this.tabIndex = this.disabled ? -1 : 0; + this.#internals.ariaChecked = String(!!this.checked); + this.#internals.ariaDisabled = String(!!this.disabled); + this.#internals.ariaLabel = this.accessibleLabel || null; + this.#internals.setFormValue(this.checked ? 'on' : null); + } + + override updated(): void { + this.#updateLabels(); + this.#updateSlottedLabels(); + } + + override render(): TemplateResult<1> { + return html` + + + + + + + + + + + + `; + } + + #onClick(event: Event) { + // @ts-expect-error: firefox workaround for double-firing when switch is nested in a label + const { originalTarget, explicitOriginalTarget } = event; + if (explicitOriginalTarget) { + let labels: HTMLLabelElement[]; + if (originalTarget === event.target + && !(labels = Array.from(this.labels)).includes(explicitOriginalTarget) + && labels.includes(this.closest('label') as HTMLLabelElement)) { + return; + } + } + this.#toggle(); + } + + #onKeyup(event: KeyboardEvent) { + switch (event.key) { + case ' ': + case 'Enter': + event.preventDefault(); + this.#toggle(); + } + } + + #onKeydown(event: KeyboardEvent) { + if (event.key === ' ') { + event.preventDefault(); + event.stopPropagation(); + } + } + + #toggle() { + if (!this.disabled) { + this.checked = !this.checked; + if (!this.dispatchEvent(new Event('change', { bubbles: true, cancelable: true }))) { + this.checked = !this.checked; + } + } + } + + #onSlotchange(event: Event) { + const slot = event.target as HTMLSlotElement; + this.#hasSlottedContent = !!slot.assignedNodes({ flatten: true }) + .some(n => n.nodeType === Node.ELEMENT_NODE || n.textContent?.trim()); + this.requestUpdate(); + this.#updateSlottedLabels(); + } + + #updateSlottedLabels() { + const labelState = this.checked ? 'on' : 'off'; + for (const child of this.querySelectorAll('[data-state]')) { + child.hidden = child.dataset.state !== labelState; + } + } + + #updateLabels() { + const labelState = this.checked ? 'on' : 'off'; + this.labels.forEach(label => { + for (const state of label.querySelectorAll('[data-state]')) { + state.hidden = state.dataset.state !== labelState; + } + }); + } +} + +declare global { + interface HTMLElementTagNameMap { + 'pf-v6-switch': PfV6Switch; + } +} diff --git a/elements/pf-v5-switch/test/pf-switch.e2e.ts b/elements/pf-v6-switch/test/pf-v6-switch.e2e.ts similarity index 95% rename from elements/pf-v5-switch/test/pf-switch.e2e.ts rename to elements/pf-v6-switch/test/pf-v6-switch.e2e.ts index 5b41db14f6..c3ba613d2f 100644 --- a/elements/pf-v5-switch/test/pf-switch.e2e.ts +++ b/elements/pf-v6-switch/test/pf-v6-switch.e2e.ts @@ -2,7 +2,7 @@ import { test } from '@playwright/test'; import { PfeDemoPage } from '@patternfly/pfe-tools/test/playwright/PfeDemoPage.js'; import { SSRPage } from '@patternfly/pfe-tools/test/playwright/SSRPage.js'; -const tagName = 'pf-v5-switch'; +const tagName = 'pf-v6-switch'; test.describe(tagName, () => { test('snapshot', async ({ page }) => { diff --git a/elements/pf-v6-switch/test/pf-v6-switch.spec.ts b/elements/pf-v6-switch/test/pf-v6-switch.spec.ts new file mode 100644 index 0000000000..18a90eee2f --- /dev/null +++ b/elements/pf-v6-switch/test/pf-v6-switch.spec.ts @@ -0,0 +1,425 @@ +import type { A11yTreeSnapshot } from '@patternfly/pfe-tools/test/a11y-snapshot.js'; +import { expect, html, nextFrame } from '@open-wc/testing'; +import { createFixture } from '@patternfly/pfe-tools/test/create-fixture.js'; +import { a11ySnapshot } from '@patternfly/pfe-tools/test/a11y-snapshot.js'; +import { sendKeys } from '@web/test-runner-commands'; + +import { PfV6Switch } from '@patternfly/elements/pf-v6-switch/pf-v6-switch.js'; + +describe('', function() { + it('imperatively instantiates', function() { + expect(document.createElement('pf-v6-switch')).to.be.an.instanceof(PfV6Switch); + }); + + describe('simply instantiating', function() { + let element: PfV6Switch; + let snapshot: A11yTreeSnapshot; + beforeEach(async function() { + element = await createFixture(html` + + `); + snapshot = await a11ySnapshot({ selector: 'pf-v6-switch' }); + }); + + it('should upgrade', function() { + const klass = customElements.get('pf-v6-switch'); + expect(element) + .to.be.an.instanceOf(klass) + .and + .to.be.an.instanceOf(PfV6Switch); + }); + + it('has accessible role', function() { + expect(snapshot.role).to.equal('switch'); + }); + + it('has accessible name', function() { + expect(snapshot.name).to.equal('Test'); + }); + + it('is not checked by default', function() { + expect(snapshot.checked).to.be.false; + }); + }); + + describe('with accessible-label attribute', function() { + let element: PfV6Switch; + let snapshot: A11yTreeSnapshot; + beforeEach(async function() { + element = await createFixture(html` + + `); + snapshot = await a11ySnapshot({ selector: 'pf-v6-switch' }); + }); + + it('has an accessible name from accessible-label', function() { + expect(snapshot.name).to.equal('Dark Mode'); + }); + + it('keeps the same accessible name regardless of checked state', async function() { + element.focus(); + await sendKeys({ press: ' ' }); + await element.updateComplete; + await nextFrame(); + snapshot = await a11ySnapshot({ selector: 'pf-v6-switch' }); + expect(snapshot.name).to.equal('Dark Mode'); + }); + }); + + describe('with slotted label text', function() { + let element: PfV6Switch; + beforeEach(async function() { + element = await createFixture(html` + Wi-Fi enabled + `); + await element.updateComplete; + }); + + it('renders the label text', function() { + expect(element.textContent?.trim()).to.equal('Wi-Fi enabled'); + }); + + it('displays the label in the accessibility tree', async function() { + const snapshot = await a11ySnapshot({ selector: 'pf-v6-switch' }); + expect(snapshot.name).to.equal('Wi-Fi enabled'); + }); + }); + + describe('with data-state labels', function() { + let element: PfV6Switch; + beforeEach(async function() { + element = await createFixture(html` + + Enabled + Disabled + + `); + await element.updateComplete; + await nextFrame(); + }); + + it('shows the off label when unchecked', function() { + const on = element.querySelector('[data-state="on"]') as HTMLElement; + const off = element.querySelector('[data-state="off"]') as HTMLElement; + expect(on.hidden).to.be.true; + expect(off.hidden).to.be.false; + }); + + describe('toggling the switch', function() { + beforeEach(async function() { + element.focus(); + await sendKeys({ press: ' ' }); + await element.updateComplete; + await nextFrame(); + }); + + it('shows the on label when checked', function() { + const on = element.querySelector('[data-state="on"]') as HTMLElement; + const off = element.querySelector('[data-state="off"]') as HTMLElement; + expect(on.hidden).to.be.false; + expect(off.hidden).to.be.true; + }); + }); + }); + + describe('with external label for on and off state', function() { + let element: PfV6Switch; + let snapshot: A11yTreeSnapshot; + beforeEach(async function() { + const container = await createFixture(html` +
+ + +
+ `); + element = container.querySelector('pf-v6-switch')!; + snapshot = await a11ySnapshot({ selector: '#switch' }); + }); + + it('is accessible', function() { + expect(snapshot.role).to.equal('switch'); + expect(snapshot.name).to.be.ok; + expect(snapshot.checked).to.be.false; + }); + + it('shows the label for the unchecked state', function() { + expect(snapshot.name).to.equal('Message when off'); + }); + + describe('toggling the switch', function() { + beforeEach(async function() { + element.focus(); + await sendKeys({ press: 'Enter' }); + await element.updateComplete; + await nextFrame(); + snapshot = await a11ySnapshot({ selector: '#switch' }); + }); + + it('should be checked', function() { + expect(element.checked).to.be.true; + expect(snapshot.checked).to.be.true; + }); + + it('shows the label for the checked state', function() { + expect(snapshot.name).to.equal('Message when on'); + }); + }); + }); + + describe('when checked attr is present', function() { + let element: PfV6Switch; + let snapshot: A11yTreeSnapshot; + beforeEach(async function() { + element = await createFixture(html` + + `); + await element.updateComplete; + await nextFrame(); + snapshot = await a11ySnapshot({ selector: '#switch' }); + }); + + it('should be checked', function() { + expect(element.checked).to.be.true; + expect(snapshot.checked).to.be.true; + }); + }); + + describe('when checked attr is not present', function() { + let element: PfV6Switch; + let snapshot: A11yTreeSnapshot; + beforeEach(async function() { + element = await createFixture(html` + + `); + await element.updateComplete; + await nextFrame(); + snapshot = await a11ySnapshot({ selector: '#switch' }); + }); + + it('should not be checked', function() { + expect(element.checked).to.be.false; + expect(snapshot.checked).to.be.false; + }); + }); + + describe('when checked and show-check-icon attrs are present', function() { + let element: PfV6Switch; + beforeEach(async function() { + element = await createFixture(html` + Check icon example + `); + }); + + it('has the show-check-icon attribute', function() { + expect(element.showCheckIcon).to.be.true; + expect(element.hasAttribute('show-check-icon')).to.be.true; + }); + }); + + describe('when disabled', function() { + let element: PfV6Switch; + let snapshot: A11yTreeSnapshot; + beforeEach(async function() { + element = await createFixture(html` + + `); + snapshot = await a11ySnapshot({ selector: 'pf-v6-switch' }); + }); + + it('reports disabled in accessibility tree', function() { + expect(snapshot.disabled).to.be.true; + }); + + it('does not toggle when clicked', function() { + element.click(); + expect(element.checked).to.be.false; + }); + }); + + describe('when reversed', function() { + let element: PfV6Switch; + beforeEach(async function() { + element = await createFixture(html` + Reversed label + `); + }); + + it('has the reversed attribute', function() { + expect(element.reversed).to.be.true; + expect(element.hasAttribute('reversed')).to.be.true; + }); + }); + + describe('when nested inside a label element', function() { + let label: HTMLLabelElement; + let element: PfV6Switch; + let snapshot: A11yTreeSnapshot; + beforeEach(async function() { + label = await createFixture(html` + + `); + element = label.querySelector('pf-v6-switch')!; + snapshot = await a11ySnapshot({ selector: 'pf-v6-switch' }); + }); + + it('does not hide label', function() { + expect(label.hidden).to.be.false; + }); + + it('has an accessible name', function() { + expect(snapshot.name).to.equal('Dark Mode'); + }); + + describe('clicking the label', function() { + beforeEach(function() { + label.click(); + }); + + it('toggles the state', function() { + expect(element.checked).to.be.true; + }); + }); + + describe('pressing Space on the switch', function() { + beforeEach(async function() { + element.focus(); + await sendKeys({ press: ' ' }); + await element.updateComplete; + }); + + it('toggles the state', function() { + expect(element.checked).to.be.true; + }); + }); + }); + + describe('keyboard interaction', function() { + let element: PfV6Switch; + beforeEach(async function() { + element = await createFixture(html` + + `); + element.focus(); + }); + + describe('pressing Space', function() { + beforeEach(async function() { + await sendKeys({ press: ' ' }); + await element.updateComplete; + }); + + it('toggles the state', function() { + expect(element.checked).to.be.true; + }); + }); + + describe('pressing Enter', function() { + beforeEach(async function() { + await sendKeys({ press: 'Enter' }); + await element.updateComplete; + }); + + it('toggles the state', function() { + expect(element.checked).to.be.true; + }); + }); + }); + + describe('change event', function() { + let element: PfV6Switch; + let changeCount: number; + beforeEach(async function() { + changeCount = 0; + element = await createFixture(html` + + `); + element.addEventListener('change', () => changeCount++); + }); + + describe('toggling the switch', function() { + beforeEach(async function() { + element.focus(); + await sendKeys({ press: ' ' }); + await element.updateComplete; + }); + + it('fires change event', function() { + expect(changeCount).to.equal(1); + }); + }); + + describe('clicking a disabled switch', function() { + beforeEach(function() { + element.disabled = true; + element.click(); + }); + + it('does not fire change event', function() { + expect(changeCount).to.equal(0); + }); + }); + + describe('preventing default on the change event', function() { + beforeEach(async function() { + element.addEventListener('change', e => e.preventDefault()); + element.focus(); + await sendKeys({ press: ' ' }); + await element.updateComplete; + }); + + it('reverts the checked state', function() { + expect(element.checked).to.be.false; + }); + }); + }); + + describe('form association', function() { + let form: HTMLFormElement; + let element: PfV6Switch; + let submitData: FormData | null; + beforeEach(async function() { + submitData = null; + const container = await createFixture(html` +
+ + +
+ `); + form = container as HTMLFormElement; + element = form.querySelector('pf-v6-switch')!; + form.addEventListener('submit', function(e) { + e.preventDefault(); + submitData = new FormData(form); + }); + }); + + describe('submitting when unchecked', function() { + beforeEach(function() { + form.requestSubmit(); + }); + + it('does not include value in form data', function() { + expect(submitData!.has('toggle')).to.be.false; + }); + }); + + describe('submitting when checked', function() { + beforeEach(async function() { + element.focus(); + await sendKeys({ press: ' ' }); + await element.updateComplete; + form.requestSubmit(); + }); + + it('includes "on" value in form data', function() { + expect(submitData!.get('toggle')).to.equal('on'); + }); + }); + }); +}); From e6b990e054fdaa7127894561e6db6da57b9e7120 Mon Sep 17 00:00:00 2001 From: Steven Spriggs Date: Wed, 20 May 2026 11:36:05 -0400 Subject: [PATCH 02/11] fix(switch): use nested css with classes instead of host attribute selectors --- elements/pf-v6-switch/pf-v6-switch.css | 148 +++++++++++-------------- elements/pf-v6-switch/pf-v6-switch.ts | 23 ++-- 2 files changed, 81 insertions(+), 90 deletions(-) diff --git a/elements/pf-v6-switch/pf-v6-switch.css b/elements/pf-v6-switch/pf-v6-switch.css index f5b3e67e29..f4793d78ff 100644 --- a/elements/pf-v6-switch/pf-v6-switch.css +++ b/elements/pf-v6-switch/pf-v6-switch.css @@ -2,6 +2,7 @@ display: inline-grid; grid-template-columns: auto; grid-auto-columns: 1fr; + color-scheme: light dark; /** Gap between toggle and label */ column-gap: var(--pf-v6-c-switch--ColumnGap, 0.5rem); /** Switch container height */ @@ -15,15 +16,13 @@ outline: none; } -:host([reversed]) { - #label, - #toggle { - grid-row: 1; - } +#toggle.reversed, +#label.reversed { + grid-row: 1; +} - #label { - grid-column: 1; - } +#label.reversed { + grid-column: 1; } :host([disabled]) { @@ -33,16 +32,16 @@ #toggle { /** Toggle track background color (unchecked). Overrides the `--pf-t--global--background--color--control--default` design token. */ --_bg: var(--pf-v6-c-switch__toggle--BackgroundColor, - var(--pf-t--global--background--color--control--default)); + var(--pf-t--global--background--color--control--default, light-dark(#ffffff, #383838))); /** Toggle track border color. Overrides the `--pf-t--global--border--color--control--default` design token. */ --_border-color: var(--pf-v6-c-switch__toggle--BorderColor, - var(--pf-t--global--border--color--control--default)); + var(--pf-t--global--border--color--control--default, light-dark(#c7c7c7, #a3a3a3))); /** Toggle track border width. Overrides the `--pf-t--global--border--width--control--default` design token. */ --_border-width: var(--pf-v6-c-switch__toggle--BorderWidth, - var(--pf-t--global--border--width--control--default)); + var(--pf-t--global--border--width--control--default, 1px)); /** Knob background color (unchecked). Overrides the `--pf-t--global--icon--color--subtle` design token. */ --_knob-bg: var(--pf-v6-c-switch__input--not-checked__toggle--before--BackgroundColor, - var(--pf-t--global--icon--color--subtle)); + var(--pf-t--global--icon--color--subtle, light-dark(#707070, #a3a3a3))); position: relative; display: inline-block; @@ -61,7 +60,7 @@ border-radius: var(--pf-v6-c-switch__toggle--BorderRadius, var(--pf-t--global--border--radius--pill, 999px)); - &::before { + &:before { position: absolute; inset-block-start: 50%; /** Knob horizontal offset (unchecked) */ @@ -81,7 +80,6 @@ calc(var(--pf-v6-c-switch--FontSize, 0.875rem) - var(--pf-v6-c-switch__toggle-icon--Offset, 0.125rem)))); content: ""; background-color: var(--_knob-bg); - /** Knob border width (high-contrast mode) */ border: var(--pf-v6-c-switch__toggle--before--BorderWidth, var(--pf-t--global--border--width--high-contrast--regular, 0)) solid transparent; /** Knob border radius. Overrides the `--pf-t--global--border--radius--large` design token. */ @@ -101,7 +99,7 @@ translate: 0 -50%; } - &::after { + &:after { position: absolute; inset: 0; content: ""; @@ -109,6 +107,44 @@ border-radius: var(--pf-v6-c-switch__toggle--BorderRadius, var(--pf-t--global--border--radius--pill, 999px)); } + + &.checked { + /** Toggle track background color (checked). Overrides the `--pf-t--global--color--brand--default` design token. */ + --_bg: var(--pf-v6-c-switch__input--checked__toggle--BackgroundColor, + var(--pf-t--global--color--brand--default, light-dark(#0066cc, #92c5f9))); + /** Toggle track border width (checked) */ + --_border-width: var(--pf-v6-c-switch__input--checked__toggle--BorderWidth, + var(--pf-t--global--border--width--high-contrast--regular, 0)); + /** Toggle track border color (checked) */ + --_border-color: var(--pf-v6-c-switch__input--checked__toggle--BorderColor, transparent); + /** Knob background color (checked). Overrides the `--pf-t--global--icon--color--inverse` design token. */ + --_knob-bg: var(--pf-v6-c-switch__input--checked__toggle--before--BackgroundColor, + var(--pf-t--global--icon--color--inverse, light-dark(#ffffff, #1f1f1f))); + + &::before { + /** Knob translate distance (checked). Base offset for knob positioning. */ + translate: var(--pf-v6-c-switch__input--checked__toggle--before--TranslateX, + calc(100% + var(--pf-v6-c-switch__toggle-icon--Offset, 0.125rem))) -50%; + } + } + + &.disabled { + /** Toggle track background color (disabled). Overrides the `--pf-t--global--background--color--disabled--default` design token. */ + --_bg: var(--pf-v6-c-switch__input--disabled__toggle--BackgroundColor, + var(--pf-t--global--background--color--disabled--default, light-dark(#c7c7c7, #a3a3a3))); + /** Toggle track border color (disabled). Overrides the `--pf-t--global--border--color--high-contrast` design token. */ + --_border-color: var(--pf-v6-c-switch__input--disabled__toggle--BorderColor, + var(--pf-t--global--border--color--high-contrast, transparent)); + /** Knob background color (disabled). Overrides the `--pf-t--global--icon--color--on-disabled` design token. */ + --_knob-bg: var(--pf-v6-c-switch__input--disabled__toggle--before--BackgroundColor, + var(--pf-t--global--icon--color--on-disabled, light-dark(#4d4d4d, #383838))); + + #check-icon { + /** Check icon color (disabled). Overrides the `--pf-t--global--icon--color--disabled` design token. */ + color: var(--pf-v6-c-switch__input--disabled__toggle-icon--Color, + var(--pf-t--global--icon--color--disabled, light-dark(#a3a3a3, #707070))); + } + } } #check-icon { @@ -125,7 +161,7 @@ calc(var(--pf-v6-c-switch--FontSize, 0.875rem) * .625)); /** Check icon color. Overrides the `--pf-t--global--icon--color--on-brand--default` design token. */ color: var(--pf-v6-c-switch__toggle-icon--Color, - var(--pf-t--global--icon--color--on-brand--default)); + var(--pf-t--global--icon--color--on-brand--default, light-dark(#ffffff, #1f1f1f))); } #label { @@ -134,78 +170,28 @@ vertical-align: top; /** Label text color (unchecked). Overrides the `--pf-t--global--text--color--subtle` design token. */ color: var(--pf-v6-c-switch__input--not-checked__label--Color, - var(--pf-t--global--text--color--subtle)); + var(--pf-t--global--text--color--subtle, light-dark(#4d4d4d, #c7c7c7))); + + &.checked { + /** Label text color (checked). Overrides the `--pf-t--global--text--color--regular` design token. */ + color: var(--pf-v6-c-switch__input--checked__label--Color, + var(--pf-t--global--text--color--regular, light-dark(#151515, #ffffff))); + } + + &.disabled { + /** Label text color (disabled). Overrides the `--pf-t--global--text--color--disabled` design token. */ + color: var(--pf-v6-c-switch__input--disabled__label--Color, + var(--pf-t--global--text--color--disabled, light-dark(#a3a3a3, #707070))); + } } -:host(:is(:focus, :focus-within)) #toggle { +:host(:is(:focus, :focus-within)) #toggle:not(.disabled) { /** Focus outline width. Overrides the `--pf-t--global--border--width--strong` design token. */ /** Focus outline color. Overrides the `--pf-t--global--color--brand--default` design token. */ outline: var(--pf-v6-c-switch__input--focus__toggle--OutlineWidth, var(--pf-t--global--border--width--strong, 2px)) solid var(--pf-v6-c-switch__input--focus__toggle--OutlineColor, - var(--pf-t--global--color--brand--default)); + var(--pf-t--global--color--brand--default, light-dark(#0066cc, #92c5f9))); /** Focus outline offset. Overrides the `--pf-t--global--spacer--xs` design token. */ outline-offset: var(--pf-v6-c-switch__input--focus__toggle--OutlineOffset, var(--pf-t--global--spacer--xs, 0.25rem)); } - -:host([checked]) #toggle { - /** Toggle track background color (checked). Overrides the `--pf-t--global--color--brand--default` design token. */ - --_bg: var(--pf-v6-c-switch__input--checked__toggle--BackgroundColor, - var(--pf-t--global--color--brand--default)); - /** Toggle track border width (checked) */ - --_border-width: var(--pf-v6-c-switch__input--checked__toggle--BorderWidth, - var(--pf-t--global--border--width--high-contrast--regular, 0)); - /** Toggle track border color (checked) */ - --_border-color: var(--pf-v6-c-switch__input--checked__toggle--BorderColor, transparent); - /** Knob background color (checked). Overrides the `--pf-t--global--icon--color--inverse` design token. */ - --_knob-bg: var(--pf-v6-c-switch__input--checked__toggle--before--BackgroundColor, - var(--pf-t--global--icon--color--inverse)); - - &::before { - /** Knob translate distance (checked). Base offset for knob positioning. */ - translate: var(--pf-v6-c-switch__input--checked__toggle--before--TranslateX, - calc(100% + var(--pf-v6-c-switch__toggle-icon--Offset, 0.125rem))) -50%; - } -} - -:host([checked]:not([show-check-icon])) #check-icon { - display: none; -} - -:host(:not([checked])) #check-icon { - display: none; -} - -:host([checked]) #label { - /** Label text color (checked). Overrides the `--pf-t--global--text--color--regular` design token. */ - color: var(--pf-v6-c-switch__input--checked__label--Color, - var(--pf-t--global--text--color--regular)); -} - -:host([disabled]) #toggle { - /** Toggle track background color (disabled). Overrides the `--pf-t--global--background--color--disabled--default` design token. */ - --_bg: var(--pf-v6-c-switch__input--disabled__toggle--BackgroundColor, - var(--pf-t--global--background--color--disabled--default)); - /** Toggle track border color (disabled). Overrides the `--pf-t--global--border--color--high-contrast` design token. */ - --_border-color: var(--pf-v6-c-switch__input--disabled__toggle--BorderColor, - var(--pf-t--global--border--color--high-contrast)); - /** Knob background color (disabled). Overrides the `--pf-t--global--icon--color--on-disabled` design token. */ - --_knob-bg: var(--pf-v6-c-switch__input--disabled__toggle--before--BackgroundColor, - var(--pf-t--global--icon--color--on-disabled)); -} - -:host([disabled]) #check-icon { - /** Check icon color (disabled). Overrides the `--pf-t--global--icon--color--disabled` design token. */ - color: var(--pf-v6-c-switch__input--disabled__toggle-icon--Color, - var(--pf-t--global--icon--color--disabled)); -} - -:host([disabled]) #label { - /** Label text color (disabled). Overrides the `--pf-t--global--text--color--disabled` design token. */ - color: var(--pf-v6-c-switch__input--disabled__label--Color, - var(--pf-t--global--text--color--disabled)); -} - -:host([disabled]:focus-within) #toggle { - outline: none; -} diff --git a/elements/pf-v6-switch/pf-v6-switch.ts b/elements/pf-v6-switch/pf-v6-switch.ts index dcd20b354d..2b292eba5c 100644 --- a/elements/pf-v6-switch/pf-v6-switch.ts +++ b/elements/pf-v6-switch/pf-v6-switch.ts @@ -1,7 +1,6 @@ import { LitElement, html, isServer, type TemplateResult } from 'lit'; import { customElement } from 'lit/decorators/custom-element.js'; import { property } from 'lit/decorators/property.js'; - import { InternalsController } from '@patternfly/pfe-core/controllers/internals-controller.js'; import styles from './pf-v6-switch.css'; @@ -15,10 +14,6 @@ import styles from './pf-v6-switch.css'; * * @summary Toggle control for on/off settings * - * @slot - Label text displayed beside the switch toggle. - * Place `` and `` children - * to show different text for each state. - * * @fires {Event} change - Fires when the switch is toggled. Uses the native * `Event` interface with no custom detail payload. * Cancelable: call `preventDefault()` to reject the state change. @@ -55,6 +50,14 @@ export class PfV6Switch extends LitElement { #initialChecked = false; + get #classes() { + return [ + this.checked && 'checked', + this.disabled && 'disabled', + this.reversed && 'reversed', + ].filter(Boolean).join(' '); + } + get labels(): NodeListOf { return this.#internals.labels as NodeListOf; } @@ -100,8 +103,9 @@ export class PfV6Switch extends LitElement { override render(): TemplateResult<1> { return html` - - + + - - + `; From 9ec9b838c14d5d7e69ac24a0268cb2029d4a2248 Mon Sep 17 00:00:00 2001 From: Steven Spriggs Date: Wed, 20 May 2026 14:33:06 -0400 Subject: [PATCH 03/11] refactor(switch): replace imperative label mutation with SlotController --- elements/pf-v6-switch/pf-v6-switch.ts | 39 +++----------- .../pf-v6-switch/test/pf-v6-switch.spec.ts | 54 ++----------------- 2 files changed, 11 insertions(+), 82 deletions(-) diff --git a/elements/pf-v6-switch/pf-v6-switch.ts b/elements/pf-v6-switch/pf-v6-switch.ts index 2b292eba5c..7338a9f7a6 100644 --- a/elements/pf-v6-switch/pf-v6-switch.ts +++ b/elements/pf-v6-switch/pf-v6-switch.ts @@ -2,6 +2,7 @@ import { LitElement, html, isServer, type TemplateResult } from 'lit'; import { customElement } from 'lit/decorators/custom-element.js'; import { property } from 'lit/decorators/property.js'; import { InternalsController } from '@patternfly/pfe-core/controllers/internals-controller.js'; +import { SlotController } from '@patternfly/pfe-core/controllers/slot-controller.js'; import styles from './pf-v6-switch.css'; @@ -28,6 +29,8 @@ export class PfV6Switch extends LitElement { #internals = InternalsController.of(this, { role: 'switch' }); + #slots = new SlotController(this, null); + /** * Accessible label for the switch when there is no visible label text. * Should describe the checked state, e.g. "Wi-Fi" (not "Wi-Fi on/off"). @@ -46,8 +49,6 @@ export class PfV6Switch extends LitElement { /** Reverses the layout so the label appears before the toggle. */ @property({ reflect: true, type: Boolean }) reversed = false; - #hasSlottedContent = false; - #initialChecked = false; get #classes() { @@ -96,11 +97,6 @@ export class PfV6Switch extends LitElement { this.#internals.setFormValue(this.checked ? 'on' : null); } - override updated(): void { - this.#updateLabels(); - this.#updateSlottedLabels(); - } - override render(): TemplateResult<1> { return html` @@ -117,8 +113,9 @@ export class PfV6Switch extends LitElement { - + ?hidden=${this.#slots.isEmpty()}> + + `; } @@ -161,30 +158,6 @@ export class PfV6Switch extends LitElement { } } } - - #onSlotchange(event: Event) { - const slot = event.target as HTMLSlotElement; - this.#hasSlottedContent = !!slot.assignedNodes({ flatten: true }) - .some(n => n.nodeType === Node.ELEMENT_NODE || n.textContent?.trim()); - this.requestUpdate(); - this.#updateSlottedLabels(); - } - - #updateSlottedLabels() { - const labelState = this.checked ? 'on' : 'off'; - for (const child of this.querySelectorAll('[data-state]')) { - child.hidden = child.dataset.state !== labelState; - } - } - - #updateLabels() { - const labelState = this.checked ? 'on' : 'off'; - this.labels.forEach(label => { - for (const state of label.querySelectorAll('[data-state]')) { - state.hidden = state.dataset.state !== labelState; - } - }); - } } declare global { diff --git a/elements/pf-v6-switch/test/pf-v6-switch.spec.ts b/elements/pf-v6-switch/test/pf-v6-switch.spec.ts index 18a90eee2f..532dfa1df7 100644 --- a/elements/pf-v6-switch/test/pf-v6-switch.spec.ts +++ b/elements/pf-v6-switch/test/pf-v6-switch.spec.ts @@ -85,54 +85,14 @@ describe('', function() { }); }); - describe('with data-state labels', function() { - let element: PfV6Switch; - beforeEach(async function() { - element = await createFixture(html` - - Enabled - Disabled - - `); - await element.updateComplete; - await nextFrame(); - }); - - it('shows the off label when unchecked', function() { - const on = element.querySelector('[data-state="on"]') as HTMLElement; - const off = element.querySelector('[data-state="off"]') as HTMLElement; - expect(on.hidden).to.be.true; - expect(off.hidden).to.be.false; - }); - - describe('toggling the switch', function() { - beforeEach(async function() { - element.focus(); - await sendKeys({ press: ' ' }); - await element.updateComplete; - await nextFrame(); - }); - - it('shows the on label when checked', function() { - const on = element.querySelector('[data-state="on"]') as HTMLElement; - const off = element.querySelector('[data-state="off"]') as HTMLElement; - expect(on.hidden).to.be.false; - expect(off.hidden).to.be.true; - }); - }); - }); - - describe('with external label for on and off state', function() { + describe('with external label', function() { let element: PfV6Switch; let snapshot: A11yTreeSnapshot; beforeEach(async function() { const container = await createFixture(html`
- +
`); element = container.querySelector('pf-v6-switch')!; @@ -141,14 +101,10 @@ describe('', function() { it('is accessible', function() { expect(snapshot.role).to.equal('switch'); - expect(snapshot.name).to.be.ok; + expect(snapshot.name).to.equal('Dark Mode'); expect(snapshot.checked).to.be.false; }); - it('shows the label for the unchecked state', function() { - expect(snapshot.name).to.equal('Message when off'); - }); - describe('toggling the switch', function() { beforeEach(async function() { element.focus(); @@ -163,8 +119,8 @@ describe('', function() { expect(snapshot.checked).to.be.true; }); - it('shows the label for the checked state', function() { - expect(snapshot.name).to.equal('Message when on'); + it('keeps the same label', function() { + expect(snapshot.name).to.equal('Dark Mode'); }); }); }); From 9ae672a7935e772b0a38fef3e350bbb4555bdd05 Mon Sep 17 00:00:00 2001 From: Steven Spriggs Date: Wed, 20 May 2026 14:40:31 -0400 Subject: [PATCH 04/11] docs(switch): improve README --- elements/pf-v6-switch/README.md | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/elements/pf-v6-switch/README.md b/elements/pf-v6-switch/README.md index ef2e2d6c3b..94c5dd2dec 100644 --- a/elements/pf-v6-switch/README.md +++ b/elements/pf-v6-switch/README.md @@ -13,13 +13,14 @@ explicit, visible representation on a setting. ### Without visible label ```html - + ``` -### With check icon +### With external label ```html -Notifications + + ``` ## Divergences from React `Switch` @@ -41,12 +42,13 @@ explicit, visible representation on a setting. | `hasCheckIcon` | `show-check-icon` | Attribute name carried over from v5 for clarity | | `aria-label` | `accessible-label` | Abstracted behind custom attribute per PFE convention | | `aria-labelledby` | External `