diff --git a/core/src/components/checkbox/checkbox.scss b/core/src/components/checkbox/checkbox.scss index 438add273d0..b99260dd8ed 100644 --- a/core/src/components/checkbox/checkbox.scss +++ b/core/src/components/checkbox/checkbox.scss @@ -1,4 +1,3 @@ -@import "../../themes/ionic.globals"; @import "./checkbox.vars.scss"; // Checkbox @@ -119,9 +118,16 @@ input { display: none; } +// Hide the native focus ring since we draw our own focus indicator. +:host(:focus) { + outline: none; +} + .native-wrapper { display: flex; + position: relative; + align-items: center; } @@ -152,6 +158,28 @@ input { opacity: 0; } +// Checkbox: Keyboard Focus +// --------------------------------------------- + +:host(.ion-focused) .native-wrapper::after { + @include border-radius(50%); + + display: block; + position: absolute; + + width: $checkbox-focus-ring-size; + height: $checkbox-focus-ring-size; + + background: $checkbox-background-color-focused; + + content: ""; + opacity: 0.2; + + // Centered with insets because `transform` is physical and breaks in RTL. + inset-block-start: calc(50% - #{$checkbox-focus-ring-size} / 2); + inset-inline-start: calc(50% - #{$checkbox-focus-ring-size} / 2); +} + // Checkbox Bottom Content // ---------------------------------------------------------------- diff --git a/core/src/components/checkbox/checkbox.tsx b/core/src/components/checkbox/checkbox.tsx index 99cb13c474a..a13bb3580be 100644 --- a/core/src/components/checkbox/checkbox.tsx +++ b/core/src/components/checkbox/checkbox.tsx @@ -1,6 +1,6 @@ import type { ComponentInterface, EventEmitter } from '@stencil/core'; -import { Build, Component, Element, Event, Host, Method, Prop, State, h } from '@stencil/core'; -import { checkInvalidState } from '@utils/forms'; +import { Build, Component, Element, Event, Host, Method, Prop, State, forceUpdate, h } from '@stencil/core'; +import { checkInvalidState, createItemMultipleInputsObserver } from '@utils/forms'; import type { Attributes } from '@utils/helpers'; import { inheritAriaAttributes, renderHiddenInput } from '@utils/helpers'; import { createColorClasses, hostContext } from '@utils/theme'; @@ -37,6 +37,7 @@ export class Checkbox implements ComponentInterface { private errorTextId = `${this.inputId}-error-text`; private inheritedAttributes: Attributes = {}; private validationObserver?: MutationObserver; + private itemFocusObserver?: MutationObserver; @Element() el!: HTMLIonCheckboxElement; @@ -199,6 +200,8 @@ export class Checkbox implements ComponentInterface { // Always set initial state this.isInvalid = checkInvalidState(el); this.hasLabelContent = this.el.textContent !== ''; + + this.itemFocusObserver = createItemMultipleInputsObserver(el, () => forceUpdate(this)); } componentWillLoad() { @@ -210,11 +213,14 @@ export class Checkbox implements ComponentInterface { } disconnectedCallback() { - // Clean up validation observer to prevent memory leaks. if (this.validationObserver) { this.validationObserver.disconnect(); this.validationObserver = undefined; } + if (this.itemFocusObserver) { + this.itemFocusObserver.disconnect(); + this.itemFocusObserver = undefined; + } } /** @internal */ @@ -338,6 +344,8 @@ export class Checkbox implements ComponentInterface { } = this; const mode = getIonMode(this); const path = getSVGPath(mode, indeterminate); + const inItem = hostContext('ion-item', el); + const inMultipleInputsItem = hostContext('ion-item.item-multiple-inputs', el); renderHiddenInput(true, el, name, checked ? value : '', disabled); @@ -360,11 +368,14 @@ export class Checkbox implements ComponentInterface { onClick={this.onClick} class={createColorClasses(color, { [mode]: true, - 'in-item': hostContext('ion-item', el), + 'in-item': inItem, 'checkbox-checked': checked, 'checkbox-disabled': disabled, 'checkbox-indeterminate': indeterminate, interactive: true, + // A single-input item has the input cover and draws the indicator itself. + // A multi-input item has no cover, so each control draws its own. + 'ion-focusable': !inItem || inMultipleInputsItem, [`checkbox-justify-${justify}`]: justify !== undefined, [`checkbox-alignment-${alignment}`]: alignment !== undefined, [`checkbox-label-placement-${labelPlacement}`]: true, diff --git a/core/src/components/checkbox/checkbox.vars.scss b/core/src/components/checkbox/checkbox.vars.scss index 8c73dff6f8f..551eb479b38 100644 --- a/core/src/components/checkbox/checkbox.vars.scss +++ b/core/src/components/checkbox/checkbox.vars.scss @@ -1,5 +1,12 @@ +@import "../../themes/ionic.globals"; + /// @prop - Top margin of checkbox's label when in an item $checkbox-item-label-margin-top: 10px; /// @prop - Bottom margin of checkbox's label when in an item -$checkbox-item-label-margin-bottom: 10px; \ No newline at end of file +$checkbox-item-label-margin-bottom: 10px; + +/// @prop - Background color of focus indicator for checkbox when focused +$checkbox-background-color-focused: ion-color(primary, tint); + +$checkbox-focus-ring-size: 36px; diff --git a/core/src/components/checkbox/test/a11y/checkbox.e2e.ts b/core/src/components/checkbox/test/a11y/checkbox.e2e.ts index 6d0b6645ae5..989a7f97a96 100644 --- a/core/src/components/checkbox/test/a11y/checkbox.e2e.ts +++ b/core/src/components/checkbox/test/a11y/checkbox.e2e.ts @@ -25,6 +25,64 @@ configs({ directions: ['ltr'], palettes: ['light', 'dark'] }).forEach(({ title, }); }); +/** + * These assert `ion-focusable`, not the rendered ring, because `ion-focused` + * relies on keyboard-mode detection that is flaky on WebKit. Gating is mode-independent. + */ +configs({ directions: ['ltr'], modes: ['md'] }).forEach(({ title, config }) => { + test.describe(title('checkbox: focus indicator'), () => { + test('standalone checkbox should be focusable', async ({ page }) => { + await page.setContent( + ` + + Checkbox + + `, + config + ); + + const checkbox = page.locator('ion-checkbox'); + await expect(checkbox).toHaveClass(/ion-focusable/); + }); + + test('checkbox in a single-input item should not show its own focus indicator', async ({ page }) => { + await page.setContent( + ` + + + Checkbox + + + `, + config + ); + + const checkbox = page.locator('ion-checkbox'); + const item = page.locator('ion-item'); + await expect(checkbox).not.toHaveClass(/ion-focusable/); + await expect(item).toHaveClass(/ion-focusable/); + }); + + test('checkbox in a multi-input item should be focusable', async ({ page }) => { + await page.setContent( + ` + + + Checkbox 1 + Checkbox 2 + + + `, + config + ); + + const checkboxes = page.locator('ion-checkbox'); + await expect(checkboxes.nth(0)).toHaveClass(/ion-focusable/); + await expect(checkboxes.nth(1)).toHaveClass(/ion-focusable/); + }); + }); +}); + configs({ directions: ['ltr'] }).forEach(({ title, config, screenshot }) => { test.describe(title('checkbox: a11y'), () => { test.describe(title('checkbox: font scaling'), () => { diff --git a/core/src/components/checkbox/test/basic/checkbox.e2e.ts b/core/src/components/checkbox/test/basic/checkbox.e2e.ts index 733f229e4b6..c9b74fb6d2e 100644 --- a/core/src/components/checkbox/test/basic/checkbox.e2e.ts +++ b/core/src/components/checkbox/test/basic/checkbox.e2e.ts @@ -1,5 +1,5 @@ import { expect } from '@playwright/test'; -import { configs, test } from '@utils/test/playwright'; +import { applyKeyboardFocus, configs, test } from '@utils/test/playwright'; configs().forEach(({ title, screenshot, config }) => { test.describe(title('checkbox: basic visual tests'), () => { @@ -47,7 +47,7 @@ configs().forEach(({ title, screenshot, config }) => { /** * This behavior does not vary across modes/directions */ -configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, screenshot, config }) => { +configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) => { test.describe(title('checkbox: ionChange'), () => { test('should fire ionChange when interacting with checkbox', async ({ page }) => { await page.setContent( @@ -138,48 +138,6 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, screenshot, c }); test.describe(title('checkbox: ionFocus'), () => { - test('should not have visual regressions', async ({ page, pageUtils }) => { - await page.setContent( - ` - - -
- Unchecked -
- `, - config - ); - - await pageUtils.pressKeys('Tab'); - - const container = page.locator('#container'); - - await expect(container).toHaveScreenshot(screenshot(`checkbox-focus`)); - }); - - test('should not have visual regressions when interacting with checkbox in item', async ({ page, pageUtils }) => { - await page.setContent( - ` - - Unchecked - - `, - config - ); - - // Test focus with keyboard navigation - await pageUtils.pressKeys('Tab'); - - const item = page.locator('ion-item'); - - await expect(item).toHaveScreenshot(screenshot(`checkbox-in-item-focus`)); - }); - test('should fire ionFocus when checkbox is focused', async ({ page, pageUtils }) => { await page.setContent( ` @@ -328,3 +286,67 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, screenshot, c }); }); }); + +/** + * This behavior does not vary across directions + */ +configs({ directions: ['ltr'] }).forEach(({ title, screenshot, config }) => { + test.describe(title('checkbox: focus visual'), () => { + test('should render focus indicator when unchecked', async ({ page }) => { + // `ion-app` is required so `startFocusVisible` applies `ion-focused` on keyboard focus. + await page.setContent( + ` + + + +
+ Unchecked +
+
+ `, + config + ); + + const checkbox = page.locator('ion-checkbox'); + + await applyKeyboardFocus(page, checkbox); + + const container = page.locator('#container'); + + await expect(container).toHaveScreenshot(screenshot(`checkbox-focus`)); + }); + + test('should render focus indicator when checked', async ({ page }) => { + await page.setContent( + ` + + + +
+ Checked +
+
+ `, + config + ); + + const checkbox = page.locator('ion-checkbox'); + + await applyKeyboardFocus(page, checkbox); + + const container = page.locator('#container'); + + await expect(container).toHaveScreenshot(screenshot(`checkbox-focus-checked`)); + }); + }); +}); diff --git a/core/src/components/checkbox/test/basic/checkbox.e2e.ts-snapshots/checkbox-focus-checked-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/checkbox/test/basic/checkbox.e2e.ts-snapshots/checkbox-focus-checked-ios-ltr-Mobile-Chrome-linux.png new file mode 100644 index 00000000000..9e8caf8ee43 Binary files /dev/null and b/core/src/components/checkbox/test/basic/checkbox.e2e.ts-snapshots/checkbox-focus-checked-ios-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/checkbox/test/basic/checkbox.e2e.ts-snapshots/checkbox-focus-checked-ios-ltr-Mobile-Firefox-linux.png b/core/src/components/checkbox/test/basic/checkbox.e2e.ts-snapshots/checkbox-focus-checked-ios-ltr-Mobile-Firefox-linux.png new file mode 100644 index 00000000000..a24d128c04b Binary files /dev/null and b/core/src/components/checkbox/test/basic/checkbox.e2e.ts-snapshots/checkbox-focus-checked-ios-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/checkbox/test/basic/checkbox.e2e.ts-snapshots/checkbox-focus-checked-ios-ltr-Mobile-Safari-linux.png b/core/src/components/checkbox/test/basic/checkbox.e2e.ts-snapshots/checkbox-focus-checked-ios-ltr-Mobile-Safari-linux.png new file mode 100644 index 00000000000..5f268bb2df0 Binary files /dev/null and b/core/src/components/checkbox/test/basic/checkbox.e2e.ts-snapshots/checkbox-focus-checked-ios-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/checkbox/test/basic/checkbox.e2e.ts-snapshots/checkbox-focus-checked-md-ltr-Mobile-Chrome-linux.png b/core/src/components/checkbox/test/basic/checkbox.e2e.ts-snapshots/checkbox-focus-checked-md-ltr-Mobile-Chrome-linux.png new file mode 100644 index 00000000000..769ebd955ad Binary files /dev/null and b/core/src/components/checkbox/test/basic/checkbox.e2e.ts-snapshots/checkbox-focus-checked-md-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/checkbox/test/basic/checkbox.e2e.ts-snapshots/checkbox-focus-checked-md-ltr-Mobile-Firefox-linux.png b/core/src/components/checkbox/test/basic/checkbox.e2e.ts-snapshots/checkbox-focus-checked-md-ltr-Mobile-Firefox-linux.png new file mode 100644 index 00000000000..b794065ad98 Binary files /dev/null and b/core/src/components/checkbox/test/basic/checkbox.e2e.ts-snapshots/checkbox-focus-checked-md-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/checkbox/test/basic/checkbox.e2e.ts-snapshots/checkbox-focus-checked-md-ltr-Mobile-Safari-linux.png b/core/src/components/checkbox/test/basic/checkbox.e2e.ts-snapshots/checkbox-focus-checked-md-ltr-Mobile-Safari-linux.png new file mode 100644 index 00000000000..e96f191e15d Binary files /dev/null and b/core/src/components/checkbox/test/basic/checkbox.e2e.ts-snapshots/checkbox-focus-checked-md-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/checkbox/test/basic/checkbox.e2e.ts-snapshots/checkbox-focus-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/checkbox/test/basic/checkbox.e2e.ts-snapshots/checkbox-focus-ios-ltr-Mobile-Chrome-linux.png index ae5bed12337..86c807e630c 100644 Binary files a/core/src/components/checkbox/test/basic/checkbox.e2e.ts-snapshots/checkbox-focus-ios-ltr-Mobile-Chrome-linux.png and b/core/src/components/checkbox/test/basic/checkbox.e2e.ts-snapshots/checkbox-focus-ios-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/checkbox/test/basic/checkbox.e2e.ts-snapshots/checkbox-focus-ios-ltr-Mobile-Firefox-linux.png b/core/src/components/checkbox/test/basic/checkbox.e2e.ts-snapshots/checkbox-focus-ios-ltr-Mobile-Firefox-linux.png index 305f4d89e1b..e328672a417 100644 Binary files a/core/src/components/checkbox/test/basic/checkbox.e2e.ts-snapshots/checkbox-focus-ios-ltr-Mobile-Firefox-linux.png and b/core/src/components/checkbox/test/basic/checkbox.e2e.ts-snapshots/checkbox-focus-ios-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/checkbox/test/basic/checkbox.e2e.ts-snapshots/checkbox-focus-ios-ltr-Mobile-Safari-linux.png b/core/src/components/checkbox/test/basic/checkbox.e2e.ts-snapshots/checkbox-focus-ios-ltr-Mobile-Safari-linux.png index 637fa09bb1e..fdfb755ea67 100644 Binary files a/core/src/components/checkbox/test/basic/checkbox.e2e.ts-snapshots/checkbox-focus-ios-ltr-Mobile-Safari-linux.png and b/core/src/components/checkbox/test/basic/checkbox.e2e.ts-snapshots/checkbox-focus-ios-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/checkbox/test/basic/checkbox.e2e.ts-snapshots/checkbox-focus-md-ltr-Mobile-Chrome-linux.png b/core/src/components/checkbox/test/basic/checkbox.e2e.ts-snapshots/checkbox-focus-md-ltr-Mobile-Chrome-linux.png new file mode 100644 index 00000000000..112e7ada86f Binary files /dev/null and b/core/src/components/checkbox/test/basic/checkbox.e2e.ts-snapshots/checkbox-focus-md-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/checkbox/test/basic/checkbox.e2e.ts-snapshots/checkbox-focus-md-ltr-Mobile-Firefox-linux.png b/core/src/components/checkbox/test/basic/checkbox.e2e.ts-snapshots/checkbox-focus-md-ltr-Mobile-Firefox-linux.png new file mode 100644 index 00000000000..b3cc4169e01 Binary files /dev/null and b/core/src/components/checkbox/test/basic/checkbox.e2e.ts-snapshots/checkbox-focus-md-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/checkbox/test/basic/checkbox.e2e.ts-snapshots/checkbox-focus-md-ltr-Mobile-Safari-linux.png b/core/src/components/checkbox/test/basic/checkbox.e2e.ts-snapshots/checkbox-focus-md-ltr-Mobile-Safari-linux.png new file mode 100644 index 00000000000..5c86d926c0e Binary files /dev/null and b/core/src/components/checkbox/test/basic/checkbox.e2e.ts-snapshots/checkbox-focus-md-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/checkbox/test/basic/checkbox.e2e.ts-snapshots/checkbox-in-item-focus-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/checkbox/test/basic/checkbox.e2e.ts-snapshots/checkbox-in-item-focus-ios-ltr-Mobile-Chrome-linux.png deleted file mode 100644 index 3c134cb37f8..00000000000 Binary files a/core/src/components/checkbox/test/basic/checkbox.e2e.ts-snapshots/checkbox-in-item-focus-ios-ltr-Mobile-Chrome-linux.png and /dev/null differ diff --git a/core/src/components/checkbox/test/basic/checkbox.e2e.ts-snapshots/checkbox-in-item-focus-ios-ltr-Mobile-Safari-linux.png b/core/src/components/checkbox/test/basic/checkbox.e2e.ts-snapshots/checkbox-in-item-focus-ios-ltr-Mobile-Safari-linux.png deleted file mode 100644 index 9dc5aed80d8..00000000000 Binary files a/core/src/components/checkbox/test/basic/checkbox.e2e.ts-snapshots/checkbox-in-item-focus-ios-ltr-Mobile-Safari-linux.png and /dev/null differ diff --git a/core/src/components/checkbox/test/item/checkbox.e2e.ts b/core/src/components/checkbox/test/item/checkbox.e2e.ts index 341839739b1..1070f8a4c3c 100644 --- a/core/src/components/checkbox/test/item/checkbox.e2e.ts +++ b/core/src/components/checkbox/test/item/checkbox.e2e.ts @@ -1,5 +1,5 @@ import { expect } from '@playwright/test'; -import { configs, test } from '@utils/test/playwright'; +import { applyKeyboardFocus, configs, test } from '@utils/test/playwright'; configs().forEach(({ title, screenshot, config }) => { test.describe(title('checkbox: item with list'), () => { @@ -126,6 +126,70 @@ configs({ directions: ['ltr'] }).forEach(({ title, screenshot, config }) => { await expect(list).toHaveScreenshot(screenshot(`checkbox-stacked-label-in-item`)); }); }); + + test.describe(title('checkbox: multiple inputs in item'), () => { + test('should not have visual regressions with multiple checkboxes in an item', async ({ page }) => { + await page.setContent( + ` + + + Checkbox 1 + Checkbox 2 + + + `, + config + ); + const list = page.locator('ion-list'); + await expect(list).toHaveScreenshot(screenshot(`checkbox-multiple-in-item`)); + }); + }); +}); + +/** + * This behavior does not vary across directions + */ +configs({ directions: ['ltr'] }).forEach(({ title, screenshot, config }) => { + test.describe(title('checkbox: focus in item'), () => { + test('should render focus indicator in an item', async ({ page, pageUtils }) => { + await page.setContent( + ` + + Unchecked + + `, + config + ); + + await pageUtils.pressKeys('Tab'); + + const item = page.locator('ion-item'); + + await expect(item).toHaveScreenshot(screenshot(`checkbox-in-item-focus`)); + }); + + test('should render focus indicator for a checkbox in a multi-input item', async ({ page }) => { + await page.setContent( + ` + + + Checkbox 1 + Checkbox 2 + + + `, + config + ); + + const checkbox = page.locator('ion-checkbox').first(); + + await applyKeyboardFocus(page, checkbox); + + const item = page.locator('ion-item'); + + await expect(item).toHaveScreenshot(screenshot(`checkbox-multiple-in-item-focus`)); + }); + }); }); configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) => { diff --git a/core/src/components/checkbox/test/item/checkbox.e2e.ts-snapshots/checkbox-in-item-focus-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/checkbox/test/item/checkbox.e2e.ts-snapshots/checkbox-in-item-focus-ios-ltr-Mobile-Chrome-linux.png new file mode 100644 index 00000000000..b7c449e6f9e Binary files /dev/null and b/core/src/components/checkbox/test/item/checkbox.e2e.ts-snapshots/checkbox-in-item-focus-ios-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/checkbox/test/basic/checkbox.e2e.ts-snapshots/checkbox-in-item-focus-ios-ltr-Mobile-Firefox-linux.png b/core/src/components/checkbox/test/item/checkbox.e2e.ts-snapshots/checkbox-in-item-focus-ios-ltr-Mobile-Firefox-linux.png similarity index 100% rename from core/src/components/checkbox/test/basic/checkbox.e2e.ts-snapshots/checkbox-in-item-focus-ios-ltr-Mobile-Firefox-linux.png rename to core/src/components/checkbox/test/item/checkbox.e2e.ts-snapshots/checkbox-in-item-focus-ios-ltr-Mobile-Firefox-linux.png diff --git a/core/src/components/checkbox/test/item/checkbox.e2e.ts-snapshots/checkbox-in-item-focus-ios-ltr-Mobile-Safari-linux.png b/core/src/components/checkbox/test/item/checkbox.e2e.ts-snapshots/checkbox-in-item-focus-ios-ltr-Mobile-Safari-linux.png new file mode 100644 index 00000000000..b465b40ad93 Binary files /dev/null and b/core/src/components/checkbox/test/item/checkbox.e2e.ts-snapshots/checkbox-in-item-focus-ios-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/checkbox/test/item/checkbox.e2e.ts-snapshots/checkbox-in-item-focus-md-ltr-Mobile-Chrome-linux.png b/core/src/components/checkbox/test/item/checkbox.e2e.ts-snapshots/checkbox-in-item-focus-md-ltr-Mobile-Chrome-linux.png new file mode 100644 index 00000000000..40d72cf339f Binary files /dev/null and b/core/src/components/checkbox/test/item/checkbox.e2e.ts-snapshots/checkbox-in-item-focus-md-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/checkbox/test/item/checkbox.e2e.ts-snapshots/checkbox-in-item-focus-md-ltr-Mobile-Firefox-linux.png b/core/src/components/checkbox/test/item/checkbox.e2e.ts-snapshots/checkbox-in-item-focus-md-ltr-Mobile-Firefox-linux.png new file mode 100644 index 00000000000..3101f22b855 Binary files /dev/null and b/core/src/components/checkbox/test/item/checkbox.e2e.ts-snapshots/checkbox-in-item-focus-md-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/checkbox/test/item/checkbox.e2e.ts-snapshots/checkbox-in-item-focus-md-ltr-Mobile-Safari-linux.png b/core/src/components/checkbox/test/item/checkbox.e2e.ts-snapshots/checkbox-in-item-focus-md-ltr-Mobile-Safari-linux.png new file mode 100644 index 00000000000..247aba9eb13 Binary files /dev/null and b/core/src/components/checkbox/test/item/checkbox.e2e.ts-snapshots/checkbox-in-item-focus-md-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/checkbox/test/item/checkbox.e2e.ts-snapshots/checkbox-multiple-in-item-focus-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/checkbox/test/item/checkbox.e2e.ts-snapshots/checkbox-multiple-in-item-focus-ios-ltr-Mobile-Chrome-linux.png new file mode 100644 index 00000000000..3d9e7f4f7a8 Binary files /dev/null and b/core/src/components/checkbox/test/item/checkbox.e2e.ts-snapshots/checkbox-multiple-in-item-focus-ios-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/checkbox/test/item/checkbox.e2e.ts-snapshots/checkbox-multiple-in-item-focus-ios-ltr-Mobile-Firefox-linux.png b/core/src/components/checkbox/test/item/checkbox.e2e.ts-snapshots/checkbox-multiple-in-item-focus-ios-ltr-Mobile-Firefox-linux.png new file mode 100644 index 00000000000..d7fc1740783 Binary files /dev/null and b/core/src/components/checkbox/test/item/checkbox.e2e.ts-snapshots/checkbox-multiple-in-item-focus-ios-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/checkbox/test/item/checkbox.e2e.ts-snapshots/checkbox-multiple-in-item-focus-ios-ltr-Mobile-Safari-linux.png b/core/src/components/checkbox/test/item/checkbox.e2e.ts-snapshots/checkbox-multiple-in-item-focus-ios-ltr-Mobile-Safari-linux.png new file mode 100644 index 00000000000..987bc5d9b19 Binary files /dev/null and b/core/src/components/checkbox/test/item/checkbox.e2e.ts-snapshots/checkbox-multiple-in-item-focus-ios-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/checkbox/test/item/checkbox.e2e.ts-snapshots/checkbox-multiple-in-item-focus-md-ltr-Mobile-Chrome-linux.png b/core/src/components/checkbox/test/item/checkbox.e2e.ts-snapshots/checkbox-multiple-in-item-focus-md-ltr-Mobile-Chrome-linux.png new file mode 100644 index 00000000000..e919e470843 Binary files /dev/null and b/core/src/components/checkbox/test/item/checkbox.e2e.ts-snapshots/checkbox-multiple-in-item-focus-md-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/checkbox/test/item/checkbox.e2e.ts-snapshots/checkbox-multiple-in-item-focus-md-ltr-Mobile-Firefox-linux.png b/core/src/components/checkbox/test/item/checkbox.e2e.ts-snapshots/checkbox-multiple-in-item-focus-md-ltr-Mobile-Firefox-linux.png new file mode 100644 index 00000000000..d5a636afb4a Binary files /dev/null and b/core/src/components/checkbox/test/item/checkbox.e2e.ts-snapshots/checkbox-multiple-in-item-focus-md-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/checkbox/test/item/checkbox.e2e.ts-snapshots/checkbox-multiple-in-item-focus-md-ltr-Mobile-Safari-linux.png b/core/src/components/checkbox/test/item/checkbox.e2e.ts-snapshots/checkbox-multiple-in-item-focus-md-ltr-Mobile-Safari-linux.png new file mode 100644 index 00000000000..393c0e0bcd6 Binary files /dev/null and b/core/src/components/checkbox/test/item/checkbox.e2e.ts-snapshots/checkbox-multiple-in-item-focus-md-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/checkbox/test/item/checkbox.e2e.ts-snapshots/checkbox-multiple-in-item-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/checkbox/test/item/checkbox.e2e.ts-snapshots/checkbox-multiple-in-item-ios-ltr-Mobile-Chrome-linux.png new file mode 100644 index 00000000000..de660abf936 Binary files /dev/null and b/core/src/components/checkbox/test/item/checkbox.e2e.ts-snapshots/checkbox-multiple-in-item-ios-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/checkbox/test/item/checkbox.e2e.ts-snapshots/checkbox-multiple-in-item-ios-ltr-Mobile-Firefox-linux.png b/core/src/components/checkbox/test/item/checkbox.e2e.ts-snapshots/checkbox-multiple-in-item-ios-ltr-Mobile-Firefox-linux.png new file mode 100644 index 00000000000..f5f925dabe6 Binary files /dev/null and b/core/src/components/checkbox/test/item/checkbox.e2e.ts-snapshots/checkbox-multiple-in-item-ios-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/checkbox/test/item/checkbox.e2e.ts-snapshots/checkbox-multiple-in-item-ios-ltr-Mobile-Safari-linux.png b/core/src/components/checkbox/test/item/checkbox.e2e.ts-snapshots/checkbox-multiple-in-item-ios-ltr-Mobile-Safari-linux.png new file mode 100644 index 00000000000..40be000b83a Binary files /dev/null and b/core/src/components/checkbox/test/item/checkbox.e2e.ts-snapshots/checkbox-multiple-in-item-ios-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/checkbox/test/item/checkbox.e2e.ts-snapshots/checkbox-multiple-in-item-md-ltr-Mobile-Chrome-linux.png b/core/src/components/checkbox/test/item/checkbox.e2e.ts-snapshots/checkbox-multiple-in-item-md-ltr-Mobile-Chrome-linux.png new file mode 100644 index 00000000000..82895bd4516 Binary files /dev/null and b/core/src/components/checkbox/test/item/checkbox.e2e.ts-snapshots/checkbox-multiple-in-item-md-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/checkbox/test/item/checkbox.e2e.ts-snapshots/checkbox-multiple-in-item-md-ltr-Mobile-Firefox-linux.png b/core/src/components/checkbox/test/item/checkbox.e2e.ts-snapshots/checkbox-multiple-in-item-md-ltr-Mobile-Firefox-linux.png new file mode 100644 index 00000000000..723a11e9d84 Binary files /dev/null and b/core/src/components/checkbox/test/item/checkbox.e2e.ts-snapshots/checkbox-multiple-in-item-md-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/checkbox/test/item/checkbox.e2e.ts-snapshots/checkbox-multiple-in-item-md-ltr-Mobile-Safari-linux.png b/core/src/components/checkbox/test/item/checkbox.e2e.ts-snapshots/checkbox-multiple-in-item-md-ltr-Mobile-Safari-linux.png new file mode 100644 index 00000000000..c5297bc2d64 Binary files /dev/null and b/core/src/components/checkbox/test/item/checkbox.e2e.ts-snapshots/checkbox-multiple-in-item-md-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/checkbox/test/item/index.html b/core/src/components/checkbox/test/item/index.html index 2548cbfaad0..60582e73abc 100644 --- a/core/src/components/checkbox/test/item/index.html +++ b/core/src/components/checkbox/test/item/index.html @@ -15,7 +15,7 @@ + + +
+ + Unchecked + +
+
+ `, + config + ); + + const radio = page.locator('ion-radio'); + + await applyKeyboardFocus(page, radio); + + const container = page.locator('#container'); + + await expect(container).toHaveScreenshot(screenshot(`radio-focus`)); + }); + + test('should render focus indicator when checked', async ({ page }) => { + await page.setContent( + ` + + + +
+ + Checked + +
+
+ `, + config + ); + + const radio = page.locator('ion-radio'); + + await applyKeyboardFocus(page, radio); + + const container = page.locator('#container'); + + await expect(container).toHaveScreenshot(screenshot(`radio-focus-checked`)); + }); + }); +}); diff --git a/core/src/components/radio/test/basic/radio.e2e.ts-snapshots/radio-focus-checked-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/radio/test/basic/radio.e2e.ts-snapshots/radio-focus-checked-ios-ltr-Mobile-Chrome-linux.png new file mode 100644 index 00000000000..5ff92f5d5e3 Binary files /dev/null and b/core/src/components/radio/test/basic/radio.e2e.ts-snapshots/radio-focus-checked-ios-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/radio/test/basic/radio.e2e.ts-snapshots/radio-focus-checked-ios-ltr-Mobile-Firefox-linux.png b/core/src/components/radio/test/basic/radio.e2e.ts-snapshots/radio-focus-checked-ios-ltr-Mobile-Firefox-linux.png new file mode 100644 index 00000000000..ab13bbaa13c Binary files /dev/null and b/core/src/components/radio/test/basic/radio.e2e.ts-snapshots/radio-focus-checked-ios-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/radio/test/basic/radio.e2e.ts-snapshots/radio-focus-checked-ios-ltr-Mobile-Safari-linux.png b/core/src/components/radio/test/basic/radio.e2e.ts-snapshots/radio-focus-checked-ios-ltr-Mobile-Safari-linux.png new file mode 100644 index 00000000000..f8c844b5351 Binary files /dev/null and b/core/src/components/radio/test/basic/radio.e2e.ts-snapshots/radio-focus-checked-ios-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/radio/test/basic/radio.e2e.ts-snapshots/radio-focus-checked-md-ltr-Mobile-Chrome-linux.png b/core/src/components/radio/test/basic/radio.e2e.ts-snapshots/radio-focus-checked-md-ltr-Mobile-Chrome-linux.png new file mode 100644 index 00000000000..af756eb1be7 Binary files /dev/null and b/core/src/components/radio/test/basic/radio.e2e.ts-snapshots/radio-focus-checked-md-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/radio/test/basic/radio.e2e.ts-snapshots/radio-focus-checked-md-ltr-Mobile-Firefox-linux.png b/core/src/components/radio/test/basic/radio.e2e.ts-snapshots/radio-focus-checked-md-ltr-Mobile-Firefox-linux.png new file mode 100644 index 00000000000..c0e80459e81 Binary files /dev/null and b/core/src/components/radio/test/basic/radio.e2e.ts-snapshots/radio-focus-checked-md-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/radio/test/basic/radio.e2e.ts-snapshots/radio-focus-checked-md-ltr-Mobile-Safari-linux.png b/core/src/components/radio/test/basic/radio.e2e.ts-snapshots/radio-focus-checked-md-ltr-Mobile-Safari-linux.png new file mode 100644 index 00000000000..e1f652a322a Binary files /dev/null and b/core/src/components/radio/test/basic/radio.e2e.ts-snapshots/radio-focus-checked-md-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/radio/test/basic/radio.e2e.ts-snapshots/radio-focus-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/radio/test/basic/radio.e2e.ts-snapshots/radio-focus-ios-ltr-Mobile-Chrome-linux.png new file mode 100644 index 00000000000..749bbf84999 Binary files /dev/null and b/core/src/components/radio/test/basic/radio.e2e.ts-snapshots/radio-focus-ios-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/radio/test/basic/radio.e2e.ts-snapshots/radio-focus-ios-ltr-Mobile-Firefox-linux.png b/core/src/components/radio/test/basic/radio.e2e.ts-snapshots/radio-focus-ios-ltr-Mobile-Firefox-linux.png new file mode 100644 index 00000000000..6a7c560e590 Binary files /dev/null and b/core/src/components/radio/test/basic/radio.e2e.ts-snapshots/radio-focus-ios-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/radio/test/basic/radio.e2e.ts-snapshots/radio-focus-ios-ltr-Mobile-Safari-linux.png b/core/src/components/radio/test/basic/radio.e2e.ts-snapshots/radio-focus-ios-ltr-Mobile-Safari-linux.png new file mode 100644 index 00000000000..595e0f81d24 Binary files /dev/null and b/core/src/components/radio/test/basic/radio.e2e.ts-snapshots/radio-focus-ios-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/radio/test/basic/radio.e2e.ts-snapshots/radio-focus-md-ltr-Mobile-Chrome-linux.png b/core/src/components/radio/test/basic/radio.e2e.ts-snapshots/radio-focus-md-ltr-Mobile-Chrome-linux.png new file mode 100644 index 00000000000..828d7ce2f16 Binary files /dev/null and b/core/src/components/radio/test/basic/radio.e2e.ts-snapshots/radio-focus-md-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/radio/test/basic/radio.e2e.ts-snapshots/radio-focus-md-ltr-Mobile-Firefox-linux.png b/core/src/components/radio/test/basic/radio.e2e.ts-snapshots/radio-focus-md-ltr-Mobile-Firefox-linux.png new file mode 100644 index 00000000000..ff068041cb7 Binary files /dev/null and b/core/src/components/radio/test/basic/radio.e2e.ts-snapshots/radio-focus-md-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/radio/test/basic/radio.e2e.ts-snapshots/radio-focus-md-ltr-Mobile-Safari-linux.png b/core/src/components/radio/test/basic/radio.e2e.ts-snapshots/radio-focus-md-ltr-Mobile-Safari-linux.png new file mode 100644 index 00000000000..a89aa121456 Binary files /dev/null and b/core/src/components/radio/test/basic/radio.e2e.ts-snapshots/radio-focus-md-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/radio/test/item/index.html b/core/src/components/radio/test/item/index.html index 36e46779fd2..e33e518d156 100644 --- a/core/src/components/radio/test/item/index.html +++ b/core/src/components/radio/test/item/index.html @@ -15,7 +15,7 @@ - -
- Unchecked -
- `, - config - ); - - await pageUtils.pressKeys('Tab'); - - const container = page.locator('#container'); - - await expect(container).toHaveScreenshot(screenshot(`toggle-focus`)); - }); - - test('should not have visual regressions when interacting with toggle in item', async ({ page, pageUtils }) => { - await page.setContent( - ` - - Unchecked - - `, - config - ); - - // Test focus with keyboard navigation - await pageUtils.pressKeys('Tab'); - - const item = page.locator('ion-item'); - - await expect(item).toHaveScreenshot(screenshot(`toggle-in-item-focus`)); - }); - test('should fire ionFocus when toggle is focused', async ({ page, pageUtils }) => { await page.setContent( ` @@ -285,3 +243,67 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, screenshot, c }); }); }); + +/** + * This behavior does not vary across directions + */ +configs({ directions: ['ltr'] }).forEach(({ title, screenshot, config }) => { + test.describe(title('toggle: focus visual'), () => { + test('should render focus indicator when unchecked', async ({ page }) => { + // `ion-app` is required so `startFocusVisible` applies `ion-focused` on keyboard focus. + await page.setContent( + ` + + + +
+ Unchecked +
+
+ `, + config + ); + + const toggle = page.locator('ion-toggle'); + + await applyKeyboardFocus(page, toggle); + + const container = page.locator('#container'); + + await expect(container).toHaveScreenshot(screenshot(`toggle-focus`)); + }); + + test('should render focus indicator when checked', async ({ page }) => { + await page.setContent( + ` + + + +
+ Checked +
+
+ `, + config + ); + + const toggle = page.locator('ion-toggle'); + + await applyKeyboardFocus(page, toggle); + + const container = page.locator('#container'); + + await expect(container).toHaveScreenshot(screenshot(`toggle-focus-checked`)); + }); + }); +}); diff --git a/core/src/components/toggle/test/basic/toggle.e2e.ts-snapshots/toggle-focus-checked-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/toggle/test/basic/toggle.e2e.ts-snapshots/toggle-focus-checked-ios-ltr-Mobile-Chrome-linux.png new file mode 100644 index 00000000000..f5090dcb232 Binary files /dev/null and b/core/src/components/toggle/test/basic/toggle.e2e.ts-snapshots/toggle-focus-checked-ios-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/toggle/test/basic/toggle.e2e.ts-snapshots/toggle-focus-checked-ios-ltr-Mobile-Firefox-linux.png b/core/src/components/toggle/test/basic/toggle.e2e.ts-snapshots/toggle-focus-checked-ios-ltr-Mobile-Firefox-linux.png new file mode 100644 index 00000000000..c2afe2f5941 Binary files /dev/null and b/core/src/components/toggle/test/basic/toggle.e2e.ts-snapshots/toggle-focus-checked-ios-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/toggle/test/basic/toggle.e2e.ts-snapshots/toggle-focus-checked-ios-ltr-Mobile-Safari-linux.png b/core/src/components/toggle/test/basic/toggle.e2e.ts-snapshots/toggle-focus-checked-ios-ltr-Mobile-Safari-linux.png new file mode 100644 index 00000000000..08176dd7004 Binary files /dev/null and b/core/src/components/toggle/test/basic/toggle.e2e.ts-snapshots/toggle-focus-checked-ios-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/toggle/test/basic/toggle.e2e.ts-snapshots/toggle-focus-checked-md-ltr-Mobile-Chrome-linux.png b/core/src/components/toggle/test/basic/toggle.e2e.ts-snapshots/toggle-focus-checked-md-ltr-Mobile-Chrome-linux.png new file mode 100644 index 00000000000..2b0432e046b Binary files /dev/null and b/core/src/components/toggle/test/basic/toggle.e2e.ts-snapshots/toggle-focus-checked-md-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/toggle/test/basic/toggle.e2e.ts-snapshots/toggle-focus-checked-md-ltr-Mobile-Firefox-linux.png b/core/src/components/toggle/test/basic/toggle.e2e.ts-snapshots/toggle-focus-checked-md-ltr-Mobile-Firefox-linux.png new file mode 100644 index 00000000000..800164b9640 Binary files /dev/null and b/core/src/components/toggle/test/basic/toggle.e2e.ts-snapshots/toggle-focus-checked-md-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/toggle/test/basic/toggle.e2e.ts-snapshots/toggle-focus-checked-md-ltr-Mobile-Safari-linux.png b/core/src/components/toggle/test/basic/toggle.e2e.ts-snapshots/toggle-focus-checked-md-ltr-Mobile-Safari-linux.png new file mode 100644 index 00000000000..20b9c1f27e1 Binary files /dev/null and b/core/src/components/toggle/test/basic/toggle.e2e.ts-snapshots/toggle-focus-checked-md-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/toggle/test/basic/toggle.e2e.ts-snapshots/toggle-focus-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/toggle/test/basic/toggle.e2e.ts-snapshots/toggle-focus-ios-ltr-Mobile-Chrome-linux.png index 8cfaef24db3..26dc586af8e 100644 Binary files a/core/src/components/toggle/test/basic/toggle.e2e.ts-snapshots/toggle-focus-ios-ltr-Mobile-Chrome-linux.png and b/core/src/components/toggle/test/basic/toggle.e2e.ts-snapshots/toggle-focus-ios-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/toggle/test/basic/toggle.e2e.ts-snapshots/toggle-focus-ios-ltr-Mobile-Firefox-linux.png b/core/src/components/toggle/test/basic/toggle.e2e.ts-snapshots/toggle-focus-ios-ltr-Mobile-Firefox-linux.png index bb21be2862e..856c3679238 100644 Binary files a/core/src/components/toggle/test/basic/toggle.e2e.ts-snapshots/toggle-focus-ios-ltr-Mobile-Firefox-linux.png and b/core/src/components/toggle/test/basic/toggle.e2e.ts-snapshots/toggle-focus-ios-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/toggle/test/basic/toggle.e2e.ts-snapshots/toggle-focus-ios-ltr-Mobile-Safari-linux.png b/core/src/components/toggle/test/basic/toggle.e2e.ts-snapshots/toggle-focus-ios-ltr-Mobile-Safari-linux.png index c20935c06b8..8153ee087f6 100644 Binary files a/core/src/components/toggle/test/basic/toggle.e2e.ts-snapshots/toggle-focus-ios-ltr-Mobile-Safari-linux.png and b/core/src/components/toggle/test/basic/toggle.e2e.ts-snapshots/toggle-focus-ios-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/toggle/test/basic/toggle.e2e.ts-snapshots/toggle-focus-md-ltr-Mobile-Chrome-linux.png b/core/src/components/toggle/test/basic/toggle.e2e.ts-snapshots/toggle-focus-md-ltr-Mobile-Chrome-linux.png new file mode 100644 index 00000000000..a82ee6c5875 Binary files /dev/null and b/core/src/components/toggle/test/basic/toggle.e2e.ts-snapshots/toggle-focus-md-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/toggle/test/basic/toggle.e2e.ts-snapshots/toggle-focus-md-ltr-Mobile-Firefox-linux.png b/core/src/components/toggle/test/basic/toggle.e2e.ts-snapshots/toggle-focus-md-ltr-Mobile-Firefox-linux.png new file mode 100644 index 00000000000..8bc4ddfa407 Binary files /dev/null and b/core/src/components/toggle/test/basic/toggle.e2e.ts-snapshots/toggle-focus-md-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/toggle/test/basic/toggle.e2e.ts-snapshots/toggle-focus-md-ltr-Mobile-Safari-linux.png b/core/src/components/toggle/test/basic/toggle.e2e.ts-snapshots/toggle-focus-md-ltr-Mobile-Safari-linux.png new file mode 100644 index 00000000000..bc2c99f8353 Binary files /dev/null and b/core/src/components/toggle/test/basic/toggle.e2e.ts-snapshots/toggle-focus-md-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/toggle/test/basic/toggle.e2e.ts-snapshots/toggle-in-item-focus-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/toggle/test/basic/toggle.e2e.ts-snapshots/toggle-in-item-focus-ios-ltr-Mobile-Chrome-linux.png deleted file mode 100644 index 7d5347249a8..00000000000 Binary files a/core/src/components/toggle/test/basic/toggle.e2e.ts-snapshots/toggle-in-item-focus-ios-ltr-Mobile-Chrome-linux.png and /dev/null differ diff --git a/core/src/components/toggle/test/basic/toggle.e2e.ts-snapshots/toggle-in-item-focus-ios-ltr-Mobile-Safari-linux.png b/core/src/components/toggle/test/basic/toggle.e2e.ts-snapshots/toggle-in-item-focus-ios-ltr-Mobile-Safari-linux.png deleted file mode 100644 index 19bd30c2be0..00000000000 Binary files a/core/src/components/toggle/test/basic/toggle.e2e.ts-snapshots/toggle-in-item-focus-ios-ltr-Mobile-Safari-linux.png and /dev/null differ diff --git a/core/src/components/toggle/test/color/toggle.e2e.ts b/core/src/components/toggle/test/color/toggle.e2e.ts index cdebe75ec79..871cc316fb4 100644 --- a/core/src/components/toggle/test/color/toggle.e2e.ts +++ b/core/src/components/toggle/test/color/toggle.e2e.ts @@ -1,5 +1,5 @@ import { expect } from '@playwright/test'; -import { configs, test } from '@utils/test/playwright'; +import { applyKeyboardFocus, configs, test } from '@utils/test/playwright'; configs({ directions: ['ltr'] }).forEach(({ title, screenshot, config }) => { test.describe(title('toggle: color'), () => { @@ -26,5 +26,62 @@ configs({ directions: ['ltr'] }).forEach(({ title, screenshot, config }) => { const toggle = page.locator('ion-toggle'); await expect(toggle).toHaveScreenshot(screenshot(`toggle-color-unchecked`)); }); + + test('should apply color to the focus indicator when checked', async ({ page }) => { + // `ion-app` is required so `startFocusVisible` applies `ion-focused` on keyboard focus. + await page.setContent( + ` + + + +
+ Label +
+
+ `, + config + ); + + const toggle = page.locator('ion-toggle'); + + await applyKeyboardFocus(page, toggle); + + const container = page.locator('#container'); + + await expect(container).toHaveScreenshot(screenshot(`toggle-color-focus-checked`)); + }); + + test('should not apply color to the focus indicator when unchecked', async ({ page }) => { + await page.setContent( + ` + + + +
+ Label +
+
+ `, + config + ); + + const toggle = page.locator('ion-toggle'); + + await applyKeyboardFocus(page, toggle); + + const container = page.locator('#container'); + + await expect(container).toHaveScreenshot(screenshot(`toggle-color-focus-unchecked`)); + }); }); }); diff --git a/core/src/components/toggle/test/color/toggle.e2e.ts-snapshots/toggle-color-focus-checked-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/toggle/test/color/toggle.e2e.ts-snapshots/toggle-color-focus-checked-ios-ltr-Mobile-Chrome-linux.png new file mode 100644 index 00000000000..5649ce582d9 Binary files /dev/null and b/core/src/components/toggle/test/color/toggle.e2e.ts-snapshots/toggle-color-focus-checked-ios-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/toggle/test/color/toggle.e2e.ts-snapshots/toggle-color-focus-checked-ios-ltr-Mobile-Firefox-linux.png b/core/src/components/toggle/test/color/toggle.e2e.ts-snapshots/toggle-color-focus-checked-ios-ltr-Mobile-Firefox-linux.png new file mode 100644 index 00000000000..1c859ee105d Binary files /dev/null and b/core/src/components/toggle/test/color/toggle.e2e.ts-snapshots/toggle-color-focus-checked-ios-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/toggle/test/color/toggle.e2e.ts-snapshots/toggle-color-focus-checked-ios-ltr-Mobile-Safari-linux.png b/core/src/components/toggle/test/color/toggle.e2e.ts-snapshots/toggle-color-focus-checked-ios-ltr-Mobile-Safari-linux.png new file mode 100644 index 00000000000..d2d2651e795 Binary files /dev/null and b/core/src/components/toggle/test/color/toggle.e2e.ts-snapshots/toggle-color-focus-checked-ios-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/toggle/test/color/toggle.e2e.ts-snapshots/toggle-color-focus-checked-md-ltr-Mobile-Chrome-linux.png b/core/src/components/toggle/test/color/toggle.e2e.ts-snapshots/toggle-color-focus-checked-md-ltr-Mobile-Chrome-linux.png new file mode 100644 index 00000000000..de6ed72d4be Binary files /dev/null and b/core/src/components/toggle/test/color/toggle.e2e.ts-snapshots/toggle-color-focus-checked-md-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/toggle/test/color/toggle.e2e.ts-snapshots/toggle-color-focus-checked-md-ltr-Mobile-Firefox-linux.png b/core/src/components/toggle/test/color/toggle.e2e.ts-snapshots/toggle-color-focus-checked-md-ltr-Mobile-Firefox-linux.png new file mode 100644 index 00000000000..f8ef411ff1a Binary files /dev/null and b/core/src/components/toggle/test/color/toggle.e2e.ts-snapshots/toggle-color-focus-checked-md-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/toggle/test/color/toggle.e2e.ts-snapshots/toggle-color-focus-checked-md-ltr-Mobile-Safari-linux.png b/core/src/components/toggle/test/color/toggle.e2e.ts-snapshots/toggle-color-focus-checked-md-ltr-Mobile-Safari-linux.png new file mode 100644 index 00000000000..168808ae7e1 Binary files /dev/null and b/core/src/components/toggle/test/color/toggle.e2e.ts-snapshots/toggle-color-focus-checked-md-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/toggle/test/color/toggle.e2e.ts-snapshots/toggle-color-focus-unchecked-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/toggle/test/color/toggle.e2e.ts-snapshots/toggle-color-focus-unchecked-ios-ltr-Mobile-Chrome-linux.png new file mode 100644 index 00000000000..ed28f85c278 Binary files /dev/null and b/core/src/components/toggle/test/color/toggle.e2e.ts-snapshots/toggle-color-focus-unchecked-ios-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/toggle/test/color/toggle.e2e.ts-snapshots/toggle-color-focus-unchecked-ios-ltr-Mobile-Firefox-linux.png b/core/src/components/toggle/test/color/toggle.e2e.ts-snapshots/toggle-color-focus-unchecked-ios-ltr-Mobile-Firefox-linux.png new file mode 100644 index 00000000000..2058c3d5e69 Binary files /dev/null and b/core/src/components/toggle/test/color/toggle.e2e.ts-snapshots/toggle-color-focus-unchecked-ios-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/toggle/test/color/toggle.e2e.ts-snapshots/toggle-color-focus-unchecked-ios-ltr-Mobile-Safari-linux.png b/core/src/components/toggle/test/color/toggle.e2e.ts-snapshots/toggle-color-focus-unchecked-ios-ltr-Mobile-Safari-linux.png new file mode 100644 index 00000000000..ae7633dd651 Binary files /dev/null and b/core/src/components/toggle/test/color/toggle.e2e.ts-snapshots/toggle-color-focus-unchecked-ios-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/toggle/test/color/toggle.e2e.ts-snapshots/toggle-color-focus-unchecked-md-ltr-Mobile-Chrome-linux.png b/core/src/components/toggle/test/color/toggle.e2e.ts-snapshots/toggle-color-focus-unchecked-md-ltr-Mobile-Chrome-linux.png new file mode 100644 index 00000000000..476ba9d5097 Binary files /dev/null and b/core/src/components/toggle/test/color/toggle.e2e.ts-snapshots/toggle-color-focus-unchecked-md-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/toggle/test/color/toggle.e2e.ts-snapshots/toggle-color-focus-unchecked-md-ltr-Mobile-Firefox-linux.png b/core/src/components/toggle/test/color/toggle.e2e.ts-snapshots/toggle-color-focus-unchecked-md-ltr-Mobile-Firefox-linux.png new file mode 100644 index 00000000000..5ff96d60a1c Binary files /dev/null and b/core/src/components/toggle/test/color/toggle.e2e.ts-snapshots/toggle-color-focus-unchecked-md-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/toggle/test/color/toggle.e2e.ts-snapshots/toggle-color-focus-unchecked-md-ltr-Mobile-Safari-linux.png b/core/src/components/toggle/test/color/toggle.e2e.ts-snapshots/toggle-color-focus-unchecked-md-ltr-Mobile-Safari-linux.png new file mode 100644 index 00000000000..fe6f1b21513 Binary files /dev/null and b/core/src/components/toggle/test/color/toggle.e2e.ts-snapshots/toggle-color-focus-unchecked-md-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/toggle/test/item/toggle.e2e.ts b/core/src/components/toggle/test/item/toggle.e2e.ts index 866a382494f..cc549b9a6bc 100644 --- a/core/src/components/toggle/test/item/toggle.e2e.ts +++ b/core/src/components/toggle/test/item/toggle.e2e.ts @@ -1,5 +1,5 @@ import { expect } from '@playwright/test'; -import { configs, test } from '@utils/test/playwright'; +import { applyKeyboardFocus, configs, test } from '@utils/test/playwright'; configs().forEach(({ title, screenshot, config }) => { test.describe(title('toggle: item'), () => { @@ -110,6 +110,104 @@ configs({ directions: ['ltr'], modes: ['md'] }).forEach(({ title, screenshot, co }); }); +/** + * The focus indicator differs between iOS and MD, so these run in both modes. + * This behavior does not vary across directions. + */ +configs({ directions: ['ltr'] }).forEach(({ title, screenshot, config }) => { + test.describe(title('toggle: focus in item'), () => { + test('should render focus indicator in an item', async ({ page }) => { + // The item gets `ion-focused` too, which is what suppresses the toggle's indicator. + await page.setContent( + ` + + + Unchecked + + + `, + config + ); + + const toggle = page.locator('ion-toggle'); + + await applyKeyboardFocus(page, toggle); + + const item = page.locator('ion-item'); + + await expect(item).toHaveScreenshot(screenshot(`toggle-in-item-focus`)); + }); + + test('should render focus indicator for a checked toggle with a color in an item', async ({ page }) => { + await page.setContent( + ` + + + Checked + + + `, + config + ); + + const toggle = page.locator('ion-toggle'); + + await applyKeyboardFocus(page, toggle); + + const item = page.locator('ion-item'); + + await expect(item).toHaveScreenshot(screenshot(`toggle-color-checked-in-item-focus`)); + }); + + test('should render focus indicator for a toggle in a clickable item', async ({ page }) => { + await page.setContent( + ` + + + Unchecked + + + `, + config + ); + + const toggle = page.locator('ion-toggle'); + + await applyKeyboardFocus(page, toggle); + + const item = page.locator('ion-item'); + + await expect(item).toHaveScreenshot(screenshot(`toggle-in-clickable-item-focus`)); + }); + + test('should render focus indicator for a toggle in a multi-input item', async ({ page }) => { + await page.setContent( + ` + + + Toggle 1 + Toggle 2 + + + `, + config + ); + + const item = page.locator('ion-item'); + + // The item adds this after its controls render, and the toggles re-render off + // it, so waiting avoids capturing the pre-settle state. + await expect(item).toHaveClass(/item-multiple-inputs/); + + const toggle = page.locator('ion-toggle').first(); + + await applyKeyboardFocus(page, toggle); + + await expect(item).toHaveScreenshot(screenshot(`toggle-multiple-in-item-focus`)); + }); + }); +}); + configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) => { test.describe(title('toggle: item functionality'), () => { test('clicking padded space within item should click the toggle', async ({ page }) => { diff --git a/core/src/components/toggle/test/item/toggle.e2e.ts-snapshots/toggle-color-checked-in-item-focus-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/toggle/test/item/toggle.e2e.ts-snapshots/toggle-color-checked-in-item-focus-ios-ltr-Mobile-Chrome-linux.png new file mode 100644 index 00000000000..fc51ac7b706 Binary files /dev/null and b/core/src/components/toggle/test/item/toggle.e2e.ts-snapshots/toggle-color-checked-in-item-focus-ios-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/toggle/test/item/toggle.e2e.ts-snapshots/toggle-color-checked-in-item-focus-ios-ltr-Mobile-Firefox-linux.png b/core/src/components/toggle/test/item/toggle.e2e.ts-snapshots/toggle-color-checked-in-item-focus-ios-ltr-Mobile-Firefox-linux.png new file mode 100644 index 00000000000..e5c55ebc3ca Binary files /dev/null and b/core/src/components/toggle/test/item/toggle.e2e.ts-snapshots/toggle-color-checked-in-item-focus-ios-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/toggle/test/item/toggle.e2e.ts-snapshots/toggle-color-checked-in-item-focus-ios-ltr-Mobile-Safari-linux.png b/core/src/components/toggle/test/item/toggle.e2e.ts-snapshots/toggle-color-checked-in-item-focus-ios-ltr-Mobile-Safari-linux.png new file mode 100644 index 00000000000..83caa41401a Binary files /dev/null and b/core/src/components/toggle/test/item/toggle.e2e.ts-snapshots/toggle-color-checked-in-item-focus-ios-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/toggle/test/item/toggle.e2e.ts-snapshots/toggle-color-checked-in-item-focus-md-ltr-Mobile-Chrome-linux.png b/core/src/components/toggle/test/item/toggle.e2e.ts-snapshots/toggle-color-checked-in-item-focus-md-ltr-Mobile-Chrome-linux.png new file mode 100644 index 00000000000..092815e8974 Binary files /dev/null and b/core/src/components/toggle/test/item/toggle.e2e.ts-snapshots/toggle-color-checked-in-item-focus-md-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/toggle/test/item/toggle.e2e.ts-snapshots/toggle-color-checked-in-item-focus-md-ltr-Mobile-Firefox-linux.png b/core/src/components/toggle/test/item/toggle.e2e.ts-snapshots/toggle-color-checked-in-item-focus-md-ltr-Mobile-Firefox-linux.png new file mode 100644 index 00000000000..ff8aaa150dd Binary files /dev/null and b/core/src/components/toggle/test/item/toggle.e2e.ts-snapshots/toggle-color-checked-in-item-focus-md-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/toggle/test/item/toggle.e2e.ts-snapshots/toggle-color-checked-in-item-focus-md-ltr-Mobile-Safari-linux.png b/core/src/components/toggle/test/item/toggle.e2e.ts-snapshots/toggle-color-checked-in-item-focus-md-ltr-Mobile-Safari-linux.png new file mode 100644 index 00000000000..7f87fd20447 Binary files /dev/null and b/core/src/components/toggle/test/item/toggle.e2e.ts-snapshots/toggle-color-checked-in-item-focus-md-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/toggle/test/item/toggle.e2e.ts-snapshots/toggle-in-clickable-item-focus-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/toggle/test/item/toggle.e2e.ts-snapshots/toggle-in-clickable-item-focus-ios-ltr-Mobile-Chrome-linux.png new file mode 100644 index 00000000000..416897ad19f Binary files /dev/null and b/core/src/components/toggle/test/item/toggle.e2e.ts-snapshots/toggle-in-clickable-item-focus-ios-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/toggle/test/item/toggle.e2e.ts-snapshots/toggle-in-clickable-item-focus-ios-ltr-Mobile-Firefox-linux.png b/core/src/components/toggle/test/item/toggle.e2e.ts-snapshots/toggle-in-clickable-item-focus-ios-ltr-Mobile-Firefox-linux.png new file mode 100644 index 00000000000..d169f66fe58 Binary files /dev/null and b/core/src/components/toggle/test/item/toggle.e2e.ts-snapshots/toggle-in-clickable-item-focus-ios-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/toggle/test/item/toggle.e2e.ts-snapshots/toggle-in-clickable-item-focus-ios-ltr-Mobile-Safari-linux.png b/core/src/components/toggle/test/item/toggle.e2e.ts-snapshots/toggle-in-clickable-item-focus-ios-ltr-Mobile-Safari-linux.png new file mode 100644 index 00000000000..113825e84de Binary files /dev/null and b/core/src/components/toggle/test/item/toggle.e2e.ts-snapshots/toggle-in-clickable-item-focus-ios-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/toggle/test/item/toggle.e2e.ts-snapshots/toggle-in-clickable-item-focus-md-ltr-Mobile-Chrome-linux.png b/core/src/components/toggle/test/item/toggle.e2e.ts-snapshots/toggle-in-clickable-item-focus-md-ltr-Mobile-Chrome-linux.png new file mode 100644 index 00000000000..d431fca3f0d Binary files /dev/null and b/core/src/components/toggle/test/item/toggle.e2e.ts-snapshots/toggle-in-clickable-item-focus-md-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/toggle/test/item/toggle.e2e.ts-snapshots/toggle-in-clickable-item-focus-md-ltr-Mobile-Firefox-linux.png b/core/src/components/toggle/test/item/toggle.e2e.ts-snapshots/toggle-in-clickable-item-focus-md-ltr-Mobile-Firefox-linux.png new file mode 100644 index 00000000000..80da04f99fc Binary files /dev/null and b/core/src/components/toggle/test/item/toggle.e2e.ts-snapshots/toggle-in-clickable-item-focus-md-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/toggle/test/item/toggle.e2e.ts-snapshots/toggle-in-clickable-item-focus-md-ltr-Mobile-Safari-linux.png b/core/src/components/toggle/test/item/toggle.e2e.ts-snapshots/toggle-in-clickable-item-focus-md-ltr-Mobile-Safari-linux.png new file mode 100644 index 00000000000..6fb39e31043 Binary files /dev/null and b/core/src/components/toggle/test/item/toggle.e2e.ts-snapshots/toggle-in-clickable-item-focus-md-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/toggle/test/item/toggle.e2e.ts-snapshots/toggle-in-item-focus-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/toggle/test/item/toggle.e2e.ts-snapshots/toggle-in-item-focus-ios-ltr-Mobile-Chrome-linux.png new file mode 100644 index 00000000000..e356b738007 Binary files /dev/null and b/core/src/components/toggle/test/item/toggle.e2e.ts-snapshots/toggle-in-item-focus-ios-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/toggle/test/basic/toggle.e2e.ts-snapshots/toggle-in-item-focus-ios-ltr-Mobile-Firefox-linux.png b/core/src/components/toggle/test/item/toggle.e2e.ts-snapshots/toggle-in-item-focus-ios-ltr-Mobile-Firefox-linux.png similarity index 100% rename from core/src/components/toggle/test/basic/toggle.e2e.ts-snapshots/toggle-in-item-focus-ios-ltr-Mobile-Firefox-linux.png rename to core/src/components/toggle/test/item/toggle.e2e.ts-snapshots/toggle-in-item-focus-ios-ltr-Mobile-Firefox-linux.png diff --git a/core/src/components/toggle/test/item/toggle.e2e.ts-snapshots/toggle-in-item-focus-ios-ltr-Mobile-Safari-linux.png b/core/src/components/toggle/test/item/toggle.e2e.ts-snapshots/toggle-in-item-focus-ios-ltr-Mobile-Safari-linux.png new file mode 100644 index 00000000000..50bca49651c Binary files /dev/null and b/core/src/components/toggle/test/item/toggle.e2e.ts-snapshots/toggle-in-item-focus-ios-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/toggle/test/item/toggle.e2e.ts-snapshots/toggle-in-item-focus-md-ltr-Mobile-Chrome-linux.png b/core/src/components/toggle/test/item/toggle.e2e.ts-snapshots/toggle-in-item-focus-md-ltr-Mobile-Chrome-linux.png new file mode 100644 index 00000000000..8e16c7a4b8f Binary files /dev/null and b/core/src/components/toggle/test/item/toggle.e2e.ts-snapshots/toggle-in-item-focus-md-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/toggle/test/item/toggle.e2e.ts-snapshots/toggle-in-item-focus-md-ltr-Mobile-Firefox-linux.png b/core/src/components/toggle/test/item/toggle.e2e.ts-snapshots/toggle-in-item-focus-md-ltr-Mobile-Firefox-linux.png new file mode 100644 index 00000000000..0743e8f1314 Binary files /dev/null and b/core/src/components/toggle/test/item/toggle.e2e.ts-snapshots/toggle-in-item-focus-md-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/toggle/test/item/toggle.e2e.ts-snapshots/toggle-in-item-focus-md-ltr-Mobile-Safari-linux.png b/core/src/components/toggle/test/item/toggle.e2e.ts-snapshots/toggle-in-item-focus-md-ltr-Mobile-Safari-linux.png new file mode 100644 index 00000000000..84610944bd1 Binary files /dev/null and b/core/src/components/toggle/test/item/toggle.e2e.ts-snapshots/toggle-in-item-focus-md-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/toggle/test/item/toggle.e2e.ts-snapshots/toggle-multiple-in-item-focus-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/toggle/test/item/toggle.e2e.ts-snapshots/toggle-multiple-in-item-focus-ios-ltr-Mobile-Chrome-linux.png new file mode 100644 index 00000000000..9cac802f1b9 Binary files /dev/null and b/core/src/components/toggle/test/item/toggle.e2e.ts-snapshots/toggle-multiple-in-item-focus-ios-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/toggle/test/item/toggle.e2e.ts-snapshots/toggle-multiple-in-item-focus-ios-ltr-Mobile-Firefox-linux.png b/core/src/components/toggle/test/item/toggle.e2e.ts-snapshots/toggle-multiple-in-item-focus-ios-ltr-Mobile-Firefox-linux.png new file mode 100644 index 00000000000..07bfcfdf0ea Binary files /dev/null and b/core/src/components/toggle/test/item/toggle.e2e.ts-snapshots/toggle-multiple-in-item-focus-ios-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/toggle/test/item/toggle.e2e.ts-snapshots/toggle-multiple-in-item-focus-ios-ltr-Mobile-Safari-linux.png b/core/src/components/toggle/test/item/toggle.e2e.ts-snapshots/toggle-multiple-in-item-focus-ios-ltr-Mobile-Safari-linux.png new file mode 100644 index 00000000000..7cd37793433 Binary files /dev/null and b/core/src/components/toggle/test/item/toggle.e2e.ts-snapshots/toggle-multiple-in-item-focus-ios-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/toggle/test/item/toggle.e2e.ts-snapshots/toggle-multiple-in-item-focus-md-ltr-Mobile-Chrome-linux.png b/core/src/components/toggle/test/item/toggle.e2e.ts-snapshots/toggle-multiple-in-item-focus-md-ltr-Mobile-Chrome-linux.png new file mode 100644 index 00000000000..1342c03b461 Binary files /dev/null and b/core/src/components/toggle/test/item/toggle.e2e.ts-snapshots/toggle-multiple-in-item-focus-md-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/toggle/test/item/toggle.e2e.ts-snapshots/toggle-multiple-in-item-focus-md-ltr-Mobile-Firefox-linux.png b/core/src/components/toggle/test/item/toggle.e2e.ts-snapshots/toggle-multiple-in-item-focus-md-ltr-Mobile-Firefox-linux.png new file mode 100644 index 00000000000..78a2b693fcc Binary files /dev/null and b/core/src/components/toggle/test/item/toggle.e2e.ts-snapshots/toggle-multiple-in-item-focus-md-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/toggle/test/item/toggle.e2e.ts-snapshots/toggle-multiple-in-item-focus-md-ltr-Mobile-Safari-linux.png b/core/src/components/toggle/test/item/toggle.e2e.ts-snapshots/toggle-multiple-in-item-focus-md-ltr-Mobile-Safari-linux.png new file mode 100644 index 00000000000..ae4b62ff354 Binary files /dev/null and b/core/src/components/toggle/test/item/toggle.e2e.ts-snapshots/toggle-multiple-in-item-focus-md-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/toggle/toggle.ios.scss b/core/src/components/toggle/toggle.ios.scss index cfe12f1d5b0..5f9a9cb6b15 100644 --- a/core/src/components/toggle/toggle.ios.scss +++ b/core/src/components/toggle/toggle.ios.scss @@ -171,3 +171,20 @@ :host(.toggle-disabled) { opacity: $toggle-ios-disabled-opacity; } + +// iOS Toggle: Keyboard Focus +// ---------------------------------------------------------- +// `toggle-defers-indicator` is excluded rather than overridden, because an +// override would have to out-specify every rule below, including `.ion-color`. + +:host(.ion-focused:not(.toggle-defers-indicator)) .toggle-icon { + box-shadow: 0 0 0 4px $toggle-focus-ring-color-off; +} + +:host(.ion-focused.toggle-checked:not(.toggle-defers-indicator)) .toggle-icon { + box-shadow: 0 0 0 4px $toggle-focus-ring-color; +} + +:host(.ion-color.ion-focused.toggle-checked:not(.toggle-defers-indicator)) .toggle-icon { + box-shadow: 0 0 0 4px current-color(base, $toggle-focus-ring-color-alpha); +} diff --git a/core/src/components/toggle/toggle.md.scss b/core/src/components/toggle/toggle.md.scss index d0a3bfda360..cd0b5582059 100644 --- a/core/src/components/toggle/toggle.md.scss +++ b/core/src/components/toggle/toggle.md.scss @@ -15,7 +15,8 @@ --handle-width: #{$toggle-md-handle-width}; --handle-height: #{$toggle-md-handle-height}; --handle-max-height: #{$toggle-md-handle-max-height}; - --handle-spacing: 0; + // A length, not a bare 0, so it composes in `calc()`. + --handle-spacing: 0px; --handle-transition: #{$toggle-md-transition}; } @@ -67,6 +68,69 @@ height: 100%; } +// Material Design Toggle: Keyboard Focus +// ---------------------------------------------------------- +// The indicator is drawn on the wrapper because the handle uses +// `contain: strict`, which would clip it. The wrapper travels with the handle. +// +// `toggle-defers-indicator` is excluded rather than overridden so the indicator +// is never created, leaving the rules that position and color it nothing to act on. + +:host(.ion-focused:not(.toggle-defers-indicator)) .toggle-icon-wrapper::after { + @include border-radius(50%); + + display: block; + position: absolute; + + width: $toggle-md-focus-ring-size; + height: $toggle-md-focus-ring-size; + + transform: translateY(-50%); + + background: $toggle-focus-ring-color-off; + + content: ""; + + // Drawn behind the handle so the translucent indicator does not tint it. The + // wrapper is its own stacking context, so it stays above the track. + z-index: -1; + + inset-block-start: 50%; +} + +// Positioned the same way as `.toggle-inner`, offset by half the size difference +// so it centers on the handle. Anchored physically to mirror the handle, which +// uses `left` in LTR and `right` in RTL. + +:host(.toggle-ltr.ion-focused) .toggle-icon-wrapper::after { + // stylelint-disable-next-line property-disallowed-list + left: calc(var(--handle-spacing) + (var(--handle-width) - #{$toggle-md-focus-ring-size}) / 2); +} + +:host(.toggle-rtl.ion-focused) .toggle-icon-wrapper::after { + // stylelint-disable-next-line property-disallowed-list + right: calc(var(--handle-spacing) + (var(--handle-width) - #{$toggle-md-focus-ring-size}) / 2); +} + +// Matches the checked shift `.toggle-inner` applies, so the indicator stays on +// the handle. The Y component repeats the centering from the base rule. + +:host(.toggle-ltr.ion-focused.toggle-checked) .toggle-icon-wrapper::after { + transform: translate(calc(var(--handle-spacing) * -2), -50%); +} + +:host(.toggle-rtl.ion-focused.toggle-checked) .toggle-icon-wrapper::after { + transform: translate(calc(var(--handle-spacing) * 2), -50%); +} + +:host(.ion-focused.toggle-checked) .toggle-icon-wrapper::after { + background: $toggle-focus-ring-color; +} + +:host(.ion-color.ion-focused.toggle-checked) .toggle-icon-wrapper::after { + background: current-color(base, $toggle-focus-ring-color-alpha); +} + // Material Design Toggle: Disabled // ---------------------------------------------------------- // The toggle and label should use the diff --git a/core/src/components/toggle/toggle.md.vars.scss b/core/src/components/toggle/toggle.md.vars.scss index 63f9ef72c9a..b782131b8be 100644 --- a/core/src/components/toggle/toggle.md.vars.scss +++ b/core/src/components/toggle/toggle.md.vars.scss @@ -48,3 +48,5 @@ $toggle-md-on-off-label-color: #000; /// @prop - The text color of the on/off labels when the toggle is checked $toggle-md-on-off-label-checked-color: #fff; + +$toggle-md-focus-ring-size: 36px; diff --git a/core/src/components/toggle/toggle.scss b/core/src/components/toggle/toggle.scss index b64107e66b0..1c239104656 100644 --- a/core/src/components/toggle/toggle.scss +++ b/core/src/components/toggle/toggle.scss @@ -1,4 +1,3 @@ -@import "../../themes/ionic.globals"; @import "./toggle.vars.scss"; // Toggle @@ -59,10 +58,6 @@ width: auto; } -:host(.ion-focused) input { - border: 2px solid #5e9ed6; -} - :host(.toggle-disabled) { pointer-events: none; } @@ -75,6 +70,11 @@ input { display: none; } +// Hide the native focus ring since we draw our own focus indicator. +:host(:focus) { + outline: none; +} + // Toggle Wrapper // -------------------------------------------------- diff --git a/core/src/components/toggle/toggle.tsx b/core/src/components/toggle/toggle.tsx index b46635c8a78..041a200bead 100644 --- a/core/src/components/toggle/toggle.tsx +++ b/core/src/components/toggle/toggle.tsx @@ -1,6 +1,6 @@ import type { ComponentInterface, EventEmitter } from '@stencil/core'; -import { Build, Component, Element, Event, Host, Prop, State, Watch, h } from '@stencil/core'; -import { checkInvalidState } from '@utils/forms'; +import { Build, Component, Element, Event, Host, Prop, State, Watch, forceUpdate, h } from '@stencil/core'; +import { checkInvalidState, createItemMultipleInputsObserver } from '@utils/forms'; import { renderHiddenInput, inheritAriaAttributes } from '@utils/helpers'; import type { Attributes } from '@utils/helpers'; import { hapticSelection } from '@utils/native/haptic'; @@ -46,6 +46,7 @@ export class Toggle implements ComponentInterface { private toggleTrack?: HTMLElement; private didLoad = false; private validationObserver?: MutationObserver; + private itemFocusObserver?: MutationObserver; @Element() el!: HTMLIonToggleElement; @@ -227,6 +228,11 @@ export class Toggle implements ComponentInterface { // Always set initial state this.isInvalid = checkInvalidState(el); + + this.itemFocusObserver = createItemMultipleInputsObserver(el, () => forceUpdate(this), [ + 'item-multiple-inputs', + 'ion-activatable', + ]); } componentDidLoad() { @@ -258,6 +264,11 @@ export class Toggle implements ComponentInterface { this.gesture = undefined; } + if (this.itemFocusObserver) { + this.itemFocusObserver.disconnect(); + this.itemFocusObserver = undefined; + } + // Clean up validation observer to prevent memory leaks. if (this.validationObserver) { this.validationObserver.disconnect(); @@ -456,6 +467,12 @@ export class Toggle implements ComponentInterface { const mode = getIonMode(this); const value = this.getValue(); const rtl = isRTL(el) ? 'rtl' : 'ltr'; + const inItem = hostContext('ion-item', el); + const inMultipleInputsItem = hostContext('ion-item.item-multiple-inputs', el); + // A clickable item is a second tab stop painting the same row indicator, which + // `item-multiple-inputs` misses because it counts cover elements, not toggles. + // The attributes are matched too because the class needs the item to render. + const inClickableItem = hostContext('ion-item.ion-activatable, ion-item[button], ion-item[href]', el); renderHiddenInput(true, el, name, checked ? value : '', disabled); return ( @@ -475,7 +492,11 @@ export class Toggle implements ComponentInterface { onBlur={this.onBlur} class={createColorClasses(color, { [mode]: true, - 'in-item': hostContext('ion-item', el), + 'in-item': inItem, + // `ion-focusable` has to stay on because it is what makes the item focusable. + // When the item draws the row indicator, CSS suppresses ours instead. + 'ion-focusable': true, + 'toggle-defers-indicator': inItem && !inMultipleInputsItem && !inClickableItem, 'toggle-activated': activated, 'toggle-checked': checked, 'toggle-disabled': disabled, diff --git a/core/src/components/toggle/toggle.vars.scss b/core/src/components/toggle/toggle.vars.scss index 4f0d34a764f..173de9c3684 100644 --- a/core/src/components/toggle/toggle.vars.scss +++ b/core/src/components/toggle/toggle.vars.scss @@ -1,5 +1,16 @@ +@import "../../themes/ionic.globals"; + /// @prop - Top margin of toggle's label when in an item $toggle-item-label-margin-top: 10px; /// @prop - Bottom margin of toggle's label when in an item -$toggle-item-label-margin-bottom: 10px; \ No newline at end of file +$toggle-item-label-margin-bottom: 10px; + +/// @prop - Color of the focus indicator for the unchecked toggle +$toggle-focus-ring-color-off: rgba(var(--ion-text-color-rgb, 0, 0, 0), .12); + +/// @prop - Alpha of the focus indicator for the checked toggle +$toggle-focus-ring-color-alpha: .2; + +/// @prop - Color of the focus indicator for the checked toggle +$toggle-focus-ring-color: ion-color(primary, base, $toggle-focus-ring-color-alpha); diff --git a/core/src/utils/forms/index.ts b/core/src/utils/forms/index.ts index 682811ed643..d54258b956b 100644 --- a/core/src/utils/forms/index.ts +++ b/core/src/utils/forms/index.ts @@ -1,3 +1,4 @@ export * from './notch-controller'; export * from './compare-with-utils'; +export * from './item-multiple-inputs'; export * from './validity'; diff --git a/core/src/utils/forms/item-multiple-inputs.ts b/core/src/utils/forms/item-multiple-inputs.ts new file mode 100644 index 00000000000..0678456ebf5 --- /dev/null +++ b/core/src/utils/forms/item-multiple-inputs.ts @@ -0,0 +1,42 @@ +import { Build } from '@stencil/core'; + +/** + * `item-multiple-inputs` is toggled on the item after its form controls render, + * so a control whose focus styling depends on it needs to re-render when it flips. + * + * @internal + * @param el The form control whose closest `ion-item` should be observed. + * @param onChange Called whenever one of `classNames` is added or removed. + * @param classNames The item classes the caller's rendering depends on. A caller + * reading more than one must list them all, or changes to the rest are ignored. + * @returns The observer to disconnect in `disconnectedCallback`, or `undefined` + * if it could not be created. + */ +export const createItemMultipleInputsObserver = ( + el: HTMLElement, + onChange: () => void, + classNames: string[] = ['item-multiple-inputs'] +): MutationObserver | undefined => { + const item = el.closest('ion-item'); + + if (!item || !Build.isBrowser || typeof MutationObserver === 'undefined') { + return undefined; + } + + const readClasses = () => classNames.map((name) => item.classList.contains(name)).join(','); + + let previousClasses = readClasses(); + + const observer = new MutationObserver(() => { + const currentClasses = readClasses(); + + if (currentClasses !== previousClasses) { + previousClasses = currentClasses; + onChange(); + } + }); + + observer.observe(item, { attributes: true, attributeFilter: ['class'] }); + + return observer; +}; diff --git a/core/src/utils/test/playwright/apply-keyboard-focus.ts b/core/src/utils/test/playwright/apply-keyboard-focus.ts new file mode 100644 index 00000000000..7c7ff90dc3e --- /dev/null +++ b/core/src/utils/test/playwright/apply-keyboard-focus.ts @@ -0,0 +1,22 @@ +import type { Locator } from '@playwright/test'; +import { expect } from '@playwright/test'; + +import type { E2EPage } from './playwright-declarations'; + +/** + * Puts an element into the keyboard focus state that draws the focus indicator. + * Requires an `ion-app` so `startFocusVisible` is running. Its listeners attach + * asynchronously, so focus is retried until `ion-focused` sticks. + * + * `Shift` re-enables keyboard mode without moving focus, in case a pointer event + * earlier in the test turned it off. Focus moves programmatically because Firefox + * cannot move it back into the page once blurred, so an early Tab is unretryable. + */ +export const applyKeyboardFocus = async (page: E2EPage, locator: Locator) => { + await expect(async () => { + await page.evaluate(() => (document.activeElement as HTMLElement | null)?.blur()); + await page.keyboard.press('Shift'); + await locator.focus(); + await expect(locator).toHaveClass(/ion-focused/, { timeout: 250 }); + }).toPass({ timeout: 5000 }); +}; diff --git a/core/src/utils/test/playwright/index.ts b/core/src/utils/test/playwright/index.ts index ce51b371266..777d35d4816 100644 --- a/core/src/utils/test/playwright/index.ts +++ b/core/src/utils/test/playwright/index.ts @@ -3,6 +3,7 @@ export * from './playwright-declarations'; export * from './page/event-spy'; export * from './page/utils'; export * from './drag-element'; +export * from './apply-keyboard-focus'; export * from './matchers'; export * from './viewports'; export * from './generator';