Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,21 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.

## [8.4.1](https://github.com/ionic-team/ionic-framework/compare/v8.4.0...v8.4.1) (2024-11-27)


### Bug Fixes

* **header:** use aria attributes to hide small title when collapsed ([#30027](https://github.com/ionic-team/ionic-framework/issues/30027)) ([23763ab](https://github.com/ionic-team/ionic-framework/commit/23763abf797f9a4ba8262225760f718e9dcc4782)), closes [#29347](https://github.com/ionic-team/ionic-framework/issues/29347)
* **menu:** hide from screen readers while animating ([#30036](https://github.com/ionic-team/ionic-framework/issues/30036)) ([845071c](https://github.com/ionic-team/ionic-framework/commit/845071c97a856d45eb5e0bb81d9c270bc38bb604))
* **overlays:** announce info after opening based on platform ([#30025](https://github.com/ionic-team/ionic-framework/issues/30025)) ([f6188c4](https://github.com/ionic-team/ionic-framework/commit/f6188c47e9278fe69fd9d250c65156edbe5ef32e))
* **overlays:** focus management with checkbox/radio ([#30026](https://github.com/ionic-team/ionic-framework/issues/30026)) ([8ee42bb](https://github.com/ionic-team/ionic-framework/commit/8ee42bbc1e0bf4731d20040c7853756722f1a4b2))
* **toast:** swipe gesture works with custom container layout ([#29999](https://github.com/ionic-team/ionic-framework/issues/29999)) ([470decc](https://github.com/ionic-team/ionic-framework/commit/470decca7b6b89ef74095ef0bb7909b93640cd78)), closes [#29998](https://github.com/ionic-team/ionic-framework/issues/29998)





# [8.4.0](https://github.com/ionic-team/ionic-framework/compare/v8.3.4...v8.4.0) (2024-11-04)


Expand Down
15 changes: 15 additions & 0 deletions core/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,21 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.

## [8.4.1](https://github.com/ionic-team/ionic-framework/compare/v8.4.0...v8.4.1) (2024-11-27)


### Bug Fixes

* **header:** use aria attributes to hide small title when collapsed ([#30027](https://github.com/ionic-team/ionic-framework/issues/30027)) ([23763ab](https://github.com/ionic-team/ionic-framework/commit/23763abf797f9a4ba8262225760f718e9dcc4782)), closes [#29347](https://github.com/ionic-team/ionic-framework/issues/29347)
* **menu:** hide from screen readers while animating ([#30036](https://github.com/ionic-team/ionic-framework/issues/30036)) ([845071c](https://github.com/ionic-team/ionic-framework/commit/845071c97a856d45eb5e0bb81d9c270bc38bb604))
* **overlays:** announce info after opening based on platform ([#30025](https://github.com/ionic-team/ionic-framework/issues/30025)) ([f6188c4](https://github.com/ionic-team/ionic-framework/commit/f6188c47e9278fe69fd9d250c65156edbe5ef32e))
* **overlays:** focus management with checkbox/radio ([#30026](https://github.com/ionic-team/ionic-framework/issues/30026)) ([8ee42bb](https://github.com/ionic-team/ionic-framework/commit/8ee42bbc1e0bf4731d20040c7853756722f1a4b2))
* **toast:** swipe gesture works with custom container layout ([#29999](https://github.com/ionic-team/ionic-framework/issues/29999)) ([470decc](https://github.com/ionic-team/ionic-framework/commit/470decca7b6b89ef74095ef0bb7909b93640cd78)), closes [#29998](https://github.com/ionic-team/ionic-framework/issues/29998)





# [8.4.0](https://github.com/ionic-team/ionic-framework/compare/v8.3.4...v8.4.0) (2024-11-04)


Expand Down
4 changes: 2 additions & 2 deletions core/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion core/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@ionic/core",
"version": "8.4.0",
"version": "8.4.1",
"description": "Base components for Ionic",
"keywords": [
"ionic",
Expand Down
1 change: 1 addition & 0 deletions core/src/components.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2303,6 +2303,7 @@ export namespace Components {
* The name of the control, which is submitted with the form data.
*/
"name": string;
"setFocus": () => Promise<void>;
/**
* the value of the radio group.
*/
Expand Down
25 changes: 23 additions & 2 deletions core/src/components/header/header.utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -167,13 +167,34 @@ export const handleToolbarIntersection = (

export const setHeaderActive = (headerIndex: HeaderIndex, active = true) => {
const headerEl = headerIndex.el;
const toolbars = headerIndex.toolbars;
const ionTitles = toolbars.map((toolbar) => toolbar.ionTitleEl);

if (active) {
headerEl.classList.remove('header-collapse-condense-inactive');
headerEl.removeAttribute('aria-hidden');

ionTitles.forEach((ionTitle) => {
if (ionTitle) {
ionTitle.removeAttribute('aria-hidden');
}
});
} else {
headerEl.classList.add('header-collapse-condense-inactive');
headerEl.setAttribute('aria-hidden', 'true');

/**
* The small title should only be accessed by screen readers
* when the large title collapses into the small title due
* to scrolling.
*
* Originally, the header was given `aria-hidden="true"`
* but this caused issues with screen readers not being
* able to access any focusable elements within the header.
*/
ionTitles.forEach((ionTitle) => {
if (ionTitle) {
ionTitle.setAttribute('aria-hidden', 'true');
}
});
}
};

Expand Down
14 changes: 10 additions & 4 deletions core/src/components/header/test/condense/header.e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,19 @@ import { configs, test } from '@utils/test/playwright';

configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, screenshot, config }) => {
test.describe(title('header: condense'), () => {
test('should be hidden from screen readers when collapsed', async ({ page }) => {
test('should hide small title from screen readers when collapsed', async ({ page }) => {
test.info().annotations.push({
type: 'issue',
description: 'https://github.com/ionic-team/ionic-framework/issues/29347',
});

await page.goto('/src/components/header/test/condense', config);
const largeTitleHeader = page.locator('#largeTitleHeader');
const smallTitleHeader = page.locator('#smallTitleHeader');
const smallTitle = smallTitleHeader.locator('ion-title');
const content = page.locator('ion-content');

await expect(smallTitleHeader).toHaveAttribute('aria-hidden', 'true');
await expect(smallTitle).toHaveAttribute('aria-hidden', 'true');

await expect(largeTitleHeader).toHaveScreenshot(screenshot(`header-condense-large-title-initial-diff`));

Expand All @@ -24,15 +30,15 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, screenshot, c
* Playwright can't do .not.toHaveAttribute() because a value is expected,
* and toHaveAttribute can't accept a value of type null.
*/
const ariaHidden = await smallTitleHeader.getAttribute('aria-hidden');
const ariaHidden = await smallTitle.getAttribute('aria-hidden');
expect(ariaHidden).toBeNull();

await content.evaluate(async (el: HTMLIonContentElement) => {
await el.scrollToTop();
});
await page.locator('#smallTitleHeader.header-collapse-condense-inactive').waitFor();

await expect(smallTitleHeader).toHaveAttribute('aria-hidden', 'true');
await expect(smallTitle).toHaveAttribute('aria-hidden', 'true');
});
});
});
31 changes: 31 additions & 0 deletions core/src/components/menu/menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import type { Attributes } from '@utils/helpers';
import { inheritAriaAttributes, assert, clamp, isEndSide as isEnd } from '@utils/helpers';
import { menuController } from '@utils/menu-controller';
import { BACKDROP, GESTURE, getPresentedOverlay } from '@utils/overlays';
import { isPlatform } from '@utils/platform';
import { hostContext } from '@utils/theme';

import { config } from '../../global/config';
Expand Down Expand Up @@ -631,6 +632,23 @@ export class Menu implements ComponentInterface, MenuI {
private beforeAnimation(shouldOpen: boolean, role?: string) {
assert(!this.isAnimating, '_before() should not be called while animating');

/**
* When the menu is presented on an Android device, TalkBack's focus rings
* may appear in the wrong position due to the transition (specifically
* `transform` styles). This occurs because the focus rings are initially
* displayed at the starting position of the elements before the transition
* begins. This workaround ensures the focus rings do not appear in the
* incorrect location.
*
* If this solution is applied to iOS devices, then it leads to a bug where
* the overlays cannot be accessed by screen readers. This is due to
* VoiceOver not being able to update the accessibility tree when the
* `aria-hidden` is removed.
*/
if (isPlatform('android')) {
this.el.setAttribute('aria-hidden', 'true');
}

// this places the menu into the correct location before it animates in
// this css class doesn't actually kick off any animations
this.el.classList.add(SHOW_MENU);
Expand Down Expand Up @@ -687,6 +705,17 @@ export class Menu implements ComponentInterface, MenuI {
}

if (isOpen) {
/**
* When the menu is presented on an Android device, TalkBack's focus rings
* may appear in the wrong position due to the transition (specifically
* `transform` styles). The menu is hidden from screen readers during the
* transition to prevent this. Once the transition is complete, the menu
* is shown again.
*/
if (isPlatform('android')) {
this.el.removeAttribute('aria-hidden');
}

// emit open event
this.ionDidOpen.emit();

Expand All @@ -703,6 +732,8 @@ export class Menu implements ComponentInterface, MenuI {
// start focus trapping
document.addEventListener('focus', this.handleFocus, true);
} else {
this.el.removeAttribute('aria-hidden');

// remove css classes and unhide content from screen readers
this.el.classList.remove(SHOW_MENU);

Expand Down
9 changes: 8 additions & 1 deletion core/src/components/radio-group/radio-group.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { ComponentInterface, EventEmitter } from '@stencil/core';
import { Component, Element, Event, Host, Listen, Prop, Watch, h } from '@stencil/core';
import { Component, Element, Event, Host, Listen, Method, Prop, Watch, h } from '@stencil/core';
import { renderHiddenInput } from '@utils/helpers';

import { getIonMode } from '../../global/ionic-global';
Expand Down Expand Up @@ -217,6 +217,13 @@ export class RadioGroup implements ComponentInterface {
}
}

/** @internal */
@Method()
async setFocus() {
const radioToFocus = this.getRadios().find((r) => r.tabIndex !== -1);
radioToFocus?.setFocus();
}

render() {
const { label, labelId, el, name, value } = this;
const mode = getIonMode(this);
Expand Down
23 changes: 15 additions & 8 deletions core/src/components/segment/test/segment-events.e2e.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { expect } from '@playwright/test';
import { configs, test } from '@utils/test/playwright';
import { configs, test, dragElementBy } from '@utils/test/playwright';

/**
* This behavior does not vary across modes/directions.
Expand Down Expand Up @@ -105,8 +105,7 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
});
});

// TODO FW-3021
test.describe.skip('when the pointer is released', () => {
test.describe('when the pointer is released', () => {
test('should emit if the value has changed', async ({ page }) => {
test.info().annotations.push({
type: 'issue',
Expand Down Expand Up @@ -136,14 +135,22 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>

const ionChangeSpy = await page.spyOnEvent('ionChange');

const segment = page.locator('ion-segment');
const firstButton = page.locator('ion-segment-button[value="1"]');
const lastButton = page.locator('ion-segment-button[value="3"]');

await firstButton.hover();
await page.mouse.down();

await lastButton.hover();
await page.mouse.up();
/*
* `dragByX` should represent the total width of all segment buttons,
* excluding the first half of the first button and the second half
* of the last button. This calculation accounts for dragging from
* the center of the first button to the center of the last button.
*/
const segmentWidth = await segment.boundingBox().then((box) => (box ? box.width : 0));
const firstButtonWidth = await firstButton.boundingBox().then((box) => (box ? box.width : 0));
const lastButtonWidth = await lastButton.boundingBox().then((box) => (box ? box.width : 0));
const dragByX = segmentWidth - firstButtonWidth / 2 - lastButtonWidth / 2;

await dragElementBy(firstButton, page, dragByX);

expect(ionChangeSpy).toHaveReceivedEventDetail({ value: '3' });
expect(ionChangeSpy).toHaveReceivedEventTimes(1);
Expand Down
3 changes: 2 additions & 1 deletion core/src/components/toast/toast.scss
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,8 @@
background: var(--background);

box-shadow: var(--box-shadow);

pointer-events: auto;
}

.toast-wrapper.toast-top {
Expand All @@ -115,7 +117,6 @@
display: flex;

align-items: center;
pointer-events: auto;

height: inherit;
min-height: inherit;
Expand Down
10 changes: 8 additions & 2 deletions core/src/utils/focus-trap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { focusVisibleElement } from '@utils/helpers';
* valid usage for the disabled property on ion-button.
*/
export const focusableQueryString =
'[tabindex]:not([tabindex^="-"]):not([hidden]):not([disabled]), input:not([type=hidden]):not([tabindex^="-"]):not([hidden]):not([disabled]), textarea:not([tabindex^="-"]):not([hidden]):not([disabled]), button:not([tabindex^="-"]):not([hidden]):not([disabled]), select:not([tabindex^="-"]):not([hidden]):not([disabled]), .ion-focusable:not([tabindex^="-"]):not([hidden]):not([disabled]), .ion-focusable[disabled="false"]:not([tabindex^="-"]):not([hidden])';
'[tabindex]:not([tabindex^="-"]):not([hidden]):not([disabled]), input:not([type=hidden]):not([tabindex^="-"]):not([hidden]):not([disabled]), textarea:not([tabindex^="-"]):not([hidden]):not([disabled]), button:not([tabindex^="-"]):not([hidden]):not([disabled]), select:not([tabindex^="-"]):not([hidden]):not([disabled]), ion-checkbox:not([tabindex^="-"]):not([hidden]):not([disabled]), ion-radio:not([tabindex^="-"]):not([hidden]):not([disabled]), .ion-focusable:not([tabindex^="-"]):not([hidden]):not([disabled]), .ion-focusable[disabled="false"]:not([tabindex^="-"]):not([hidden])';

/**
* Focuses the first descendant in a context
Expand Down Expand Up @@ -78,7 +78,13 @@ const focusElementInContext = <T extends HTMLElement>(
}

if (elementToFocus) {
focusVisibleElement(elementToFocus);
const radioGroup = elementToFocus.closest('ion-radio-group');

if (radioGroup) {
radioGroup.setFocus();
} else {
focusVisibleElement(elementToFocus);
}
} else {
// Focus fallback element instead of letting focus escape
fallbackElement.focus();
Expand Down
28 changes: 19 additions & 9 deletions core/src/utils/overlays.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import {
removeEventListener,
} from './helpers';
import { printIonWarning } from './logging';
import { isPlatform } from './platform';

let lastOverlayIndex = 0;
let lastId = 0;
Expand Down Expand Up @@ -970,21 +971,30 @@ export const createTriggerController = () => {
* like TalkBack do not announce or interact with the content until the
* animation is complete, avoiding confusion for users.
*
* If the overlay is being presented, it prevents focus rings from appearing
* in incorrect positions due to the transition (specifically `transform`
* styles), ensuring that when aria-hidden is removed, the focus rings are
* correctly displayed in the final location of the elements.
* When the overlay is presented on an Android device, TalkBack's focus rings
* may appear in the wrong position due to the transition (specifically
* `transform` styles). This occurs because the focus rings are initially
* displayed at the starting position of the elements before the transition
* begins. This workaround ensures the focus rings do not appear in the
* incorrect location.
*
* If this solution is applied to iOS devices, then it leads to a bug where
* the overlays cannot be accessed by screen readers. This is due to
* VoiceOver not being able to update the accessibility tree when the
* `aria-hidden` is removed.
*
* @param overlay - The overlay that is being animated.
*/
const hideAnimatingOverlayFromScreenReaders = (overlay: HTMLIonOverlayElement) => {
if (doc === undefined) return;

/**
* Once the animation is complete, this attribute will be removed.
* This is done at the end of the `present` method.
*/
overlay.setAttribute('aria-hidden', 'true');
if (isPlatform('android')) {
/**
* Once the animation is complete, this attribute will be removed.
* This is done at the end of the `present` method.
*/
overlay.setAttribute('aria-hidden', 'true');
}
};

/**
Expand Down
2 changes: 1 addition & 1 deletion lerna.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@
"core",
"packages/*"
],
"version": "8.4.0"
"version": "8.4.1"
}
8 changes: 8 additions & 0 deletions packages/angular-server/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.

## [8.4.1](https://github.com/ionic-team/ionic-framework/compare/v8.4.0...v8.4.1) (2024-11-27)

**Note:** Version bump only for package @ionic/angular-server





# [8.4.0](https://github.com/ionic-team/ionic-framework/compare/v8.3.4...v8.4.0) (2024-11-04)

**Note:** Version bump only for package @ionic/angular-server
Expand Down
Loading
Loading