{
this.scrollEl = el;
}}
- /**
- * When an element has an overlay scroll style and
- * a fixed height, Firefox will focus the scrollable
- * container if the content exceeds the container's
- * dimensions.
- *
- * This causes keyboard navigation to focus to this
- * element instead of going to the next element in
- * the tab order.
- *
- * The desired behavior is for the user to be able to
- * focus the assistive focusable element and tab to
- * the next element in the tab order. Instead of tabbing
- * to this element.
- *
- * To prevent this, we set the tabIndex to -1. This
- * will match the behavior of the other browsers.
- */
- tabIndex={-1}
+ role="slider"
+ tabindex={this.disabled ? undefined : 0}
+ aria-label={this.ariaLabel}
+ aria-valuemin={0}
+ aria-valuemax={0}
+ aria-valuenow={0}
+ aria-valuetext={this.getOptionValueText(this.activeItem)}
+ aria-orientation="vertical"
+ onKeyDown={(ev) => this.onKeyDown(ev)}
>
diff --git a/core/src/components/picker-column/test/picker-column.spec.tsx b/core/src/components/picker-column/test/picker-column.spec.tsx
index 55a9c1c88da..479daba5352 100644
--- a/core/src/components/picker-column/test/picker-column.spec.tsx
+++ b/core/src/components/picker-column/test/picker-column.spec.tsx
@@ -1,10 +1,10 @@
import { h } from '@stencil/core';
import { newSpecPage } from '@stencil/core/testing';
-import { PickerColumn } from '../picker-column';
import { PickerColumnOption } from '../../picker-column-option/picker-column-option';
+import { PickerColumn } from '../picker-column';
-describe('picker-column: assistive element', () => {
+describe('picker-column', () => {
beforeEach(() => {
const mockIntersectionObserver = jest.fn();
mockIntersectionObserver.mockReturnValue({
@@ -22,9 +22,9 @@ describe('picker-column: assistive element', () => {
});
const pickerCol = page.body.querySelector('ion-picker-column')!;
- const assistiveFocusable = pickerCol.shadowRoot!.querySelector('.assistive-focusable')!;
+ const pickerOpts = pickerCol.shadowRoot!.querySelector('.picker-opts')!;
- expect(assistiveFocusable.getAttribute('aria-label')).not.toBe(null);
+ expect(pickerOpts.getAttribute('aria-label')).not.toBe(null);
});
it('should have a custom label', async () => {
@@ -34,9 +34,9 @@ describe('picker-column: assistive element', () => {
});
const pickerCol = page.body.querySelector('ion-picker-column')!;
- const assistiveFocusable = pickerCol.shadowRoot!.querySelector('.assistive-focusable')!;
+ const pickerOpts = pickerCol.shadowRoot!.querySelector('.picker-opts')!;
- expect(assistiveFocusable.getAttribute('aria-label')).toBe('my label');
+ expect(pickerOpts.getAttribute('aria-label')).toBe('my label');
});
it('should update a custom label', async () => {
@@ -46,12 +46,12 @@ describe('picker-column: assistive element', () => {
});
const pickerCol = page.body.querySelector('ion-picker-column')!;
- const assistiveFocusable = pickerCol.shadowRoot!.querySelector('.assistive-focusable')!;
+ const pickerOpts = pickerCol.shadowRoot!.querySelector('.picker-opts')!;
pickerCol.setAttribute('aria-label', 'my label');
await page.waitForChanges();
- expect(assistiveFocusable.getAttribute('aria-label')).toBe('my label');
+ expect(pickerOpts.getAttribute('aria-label')).toBe('my label');
});
it('should receive keyboard focus when enabled', async () => {
@@ -61,9 +61,9 @@ describe('picker-column: assistive element', () => {
});
const pickerCol = page.body.querySelector('ion-picker-column')!;
- const assistiveFocusable = pickerCol.shadowRoot!.querySelector
('.assistive-focusable')!;
+ const pickerOpts = pickerCol.shadowRoot!.querySelector('.picker-opts')!;
- expect(assistiveFocusable.tabIndex).toBe(0);
+ expect(pickerOpts.tabIndex).toBe(0);
});
it('should not receive keyboard focus when disabled', async () => {
@@ -73,9 +73,9 @@ describe('picker-column: assistive element', () => {
});
const pickerCol = page.body.querySelector('ion-picker-column')!;
- const assistiveFocusable = pickerCol.shadowRoot!.querySelector('.assistive-focusable')!;
+ const pickerOpts = pickerCol.shadowRoot!.querySelector('.picker-opts')!;
- expect(assistiveFocusable.tabIndex).toBe(-1);
+ expect(pickerOpts.tabIndex).toBe(-1);
});
it('should use option aria-label as assistive element aria-valuetext', async () => {
@@ -91,9 +91,9 @@ describe('picker-column: assistive element', () => {
});
const pickerCol = page.body.querySelector('ion-picker-column')!;
- const assistiveFocusable = pickerCol.shadowRoot!.querySelector('.assistive-focusable')!;
+ const pickerOpts = pickerCol.shadowRoot!.querySelector('.picker-opts')!;
- expect(assistiveFocusable.getAttribute('aria-valuetext')).toBe('My Label');
+ expect(pickerOpts.getAttribute('aria-valuetext')).toBe('My Label');
});
it('should use option text as assistive element aria-valuetext when no label provided', async () => {
@@ -107,8 +107,8 @@ describe('picker-column: assistive element', () => {
});
const pickerCol = page.body.querySelector('ion-picker-column')!;
- const assistiveFocusable = pickerCol.shadowRoot!.querySelector('.assistive-focusable')!;
+ const pickerOpts = pickerCol.shadowRoot!.querySelector('.picker-opts')!;
- expect(assistiveFocusable.getAttribute('aria-valuetext')).toBe('My Text');
+ expect(pickerOpts.getAttribute('aria-valuetext')).toBe('My Text');
});
});
diff --git a/core/src/components/picker/test/a11y/picker.e2e.ts b/core/src/components/picker/test/a11y/picker.e2e.ts
index 4c5d206cdd2..5ad5989adf2 100644
--- a/core/src/components/picker/test/a11y/picker.e2e.ts
+++ b/core/src/components/picker/test/a11y/picker.e2e.ts
@@ -9,7 +9,14 @@ configs().forEach(({ title, config }) => {
const results = await new AxeBuilder({ page }).analyze();
- expect(results.violations).toEqual([]);
+ const hasKnownViolations = results.violations.filter((violation) => violation.id === 'color-contrast');
+ const violations = results.violations.filter((violation) => !hasKnownViolations.includes(violation));
+
+ if (hasKnownViolations.length > 0) {
+ console.warn('A11Y: Known violation - contrast color.', hasKnownViolations);
+ }
+
+ expect(violations).toEqual([]);
});
});
});
diff --git a/core/src/components/picker/test/basic/index.html b/core/src/components/picker/test/basic/index.html
index b5c7c58f1d1..279b32c38c2 100644
--- a/core/src/components/picker/test/basic/index.html
+++ b/core/src/components/picker/test/basic/index.html
@@ -177,6 +177,10 @@ Modal
'onion'
);
+ const columnDualNumericFirst = document.querySelector('ion-picker-column#dual-numeric-first');
+ columnDualNumericFirst.addEventListener('ionChange', (ev) => {
+ console.log('Column change', ev.detail);
+ });
setPickerColumn(
'#dual-numeric-first',
[
@@ -195,6 +199,10 @@ Modal
],
3
);
+ const columnDualNumericSecond = document.querySelector('ion-picker-column#dual-numeric-second');
+ columnDualNumericSecond.addEventListener('ionChange', (ev) => {
+ console.log('Column change', ev.detail);
+ });
setPickerColumn('#dual-numeric-second', minutes, 3);
setPickerColumn(
diff --git a/core/src/components/picker/test/basic/picker.e2e.ts b/core/src/components/picker/test/basic/picker.e2e.ts
index 5c4a65caf7b..19908fe1106 100644
--- a/core/src/components/picker/test/basic/picker.e2e.ts
+++ b/core/src/components/picker/test/basic/picker.e2e.ts
@@ -106,32 +106,43 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
});
test('tabbing should correctly move focus between columns', async ({ page }) => {
- const firstColumn = page.locator('ion-picker-column#first');
- const secondColumn = page.locator('ion-picker-column#second');
+ const firstColumn = await page.evaluate(() => document.querySelector('ion-picker-column#first'));
+ const secondColumn = await page.evaluate(() => document.querySelector('ion-picker-column#second'));
// Focus first column
await page.keyboard.press('Tab');
- await expect(firstColumn).toBeFocused();
+
+ let activeElement = await page.evaluate(() => document.activeElement);
+ expect(activeElement).toEqual(firstColumn);
await page.waitForChanges();
// Focus second column
await page.keyboard.press('Tab');
- await expect(secondColumn).toBeFocused();
+
+ activeElement = await page.evaluate(() => document.activeElement);
+ expect(activeElement).toEqual(secondColumn);
});
test('tabbing should correctly move focus back', async ({ page }) => {
- const firstColumn = page.locator('ion-picker-column#first');
- const secondColumn = page.locator('ion-picker-column#second');
+ const firstColumn = await page.evaluate(() => document.querySelector('ion-picker-column#first'));
+ const secondColumn = await page.evaluate(() => document.querySelector('ion-picker-column#second'));
- await secondColumn.evaluate((el: HTMLIonPickerColumnElement) => el.setFocus());
- await expect(secondColumn).toBeFocused();
+ await page.evaluate((selector) => {
+ const el = document.querySelector(selector) as HTMLElement | null;
+ el?.focus();
+ }, 'ion-picker-column#second');
+
+ let activeElement = await page.evaluate(() => document.activeElement);
+ expect(activeElement).toEqual(secondColumn);
await page.waitForChanges();
// Focus first column
await page.keyboard.press('Shift+Tab');
- await expect(firstColumn).toBeFocused();
+
+ activeElement = await page.evaluate(() => document.activeElement);
+ expect(activeElement).toEqual(firstColumn);
});
});
});
diff --git a/core/src/components/picker/test/basic/picker.e2e.ts-snapshots/picker-modal-diff-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/picker/test/basic/picker.e2e.ts-snapshots/picker-modal-diff-ios-ltr-Mobile-Chrome-linux.png
index c2dbaa4aedb..4656b449060 100644
Binary files a/core/src/components/picker/test/basic/picker.e2e.ts-snapshots/picker-modal-diff-ios-ltr-Mobile-Chrome-linux.png and b/core/src/components/picker/test/basic/picker.e2e.ts-snapshots/picker-modal-diff-ios-ltr-Mobile-Chrome-linux.png differ
diff --git a/core/src/components/picker/test/basic/picker.e2e.ts-snapshots/picker-modal-diff-md-ltr-Mobile-Chrome-linux.png b/core/src/components/picker/test/basic/picker.e2e.ts-snapshots/picker-modal-diff-md-ltr-Mobile-Chrome-linux.png
index 9483724b697..7d23340c2e1 100644
Binary files a/core/src/components/picker/test/basic/picker.e2e.ts-snapshots/picker-modal-diff-md-ltr-Mobile-Chrome-linux.png and b/core/src/components/picker/test/basic/picker.e2e.ts-snapshots/picker-modal-diff-md-ltr-Mobile-Chrome-linux.png differ
diff --git a/core/src/components/picker/test/keyboard-entry/picker.e2e.ts b/core/src/components/picker/test/keyboard-entry/picker.e2e.ts
index b54cd03585a..35a66caa918 100644
--- a/core/src/components/picker/test/keyboard-entry/picker.e2e.ts
+++ b/core/src/components/picker/test/keyboard-entry/picker.e2e.ts
@@ -38,8 +38,12 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
);
const column = page.locator('ion-picker-column');
+
+ const colShadowRoot = await column.evaluateHandle((el) => el.shadowRoot);
+ const columnPickerOpts = await colShadowRoot.evaluateHandle((root) => root?.querySelector('.picker-opts'));
+
const ionChange = await page.spyOnEvent('ionChange');
- await column.evaluate((el: HTMLIonPickerColumnElement) => el.setFocus());
+ await columnPickerOpts.evaluate((el) => el && (el as HTMLElement).focus());
await page.keyboard.press('Digit2');
@@ -99,23 +103,25 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
);
const firstColumn = page.locator('ion-picker-column#first');
const secondColumn = page.locator('ion-picker-column#second');
- const highlight = page.locator('ion-picker .picker-highlight');
const firstIonChange = await (firstColumn as E2ELocator).spyOnEvent('ionChange');
const secondIonChange = await (secondColumn as E2ELocator).spyOnEvent('ionChange');
- const box = await highlight.boundingBox();
- if (box !== null) {
- await page.mouse.click(box.x + box.width / 2, box.y + box.height / 2);
- }
+ const firstColShadowRoot = await firstColumn.evaluateHandle((el) => el.shadowRoot);
+ const columnPickerOpts = await firstColShadowRoot.evaluateHandle((root) => root?.querySelector('.picker-opts'));
- await expect(firstColumn).toHaveClass(/picker-column-active/);
- await expect(secondColumn).toHaveClass(/picker-column-active/);
+ // Focus first column
+ await columnPickerOpts.evaluate((el) => el && (el as HTMLElement).focus());
await page.keyboard.press('Digit2');
await expect(firstIonChange).toHaveReceivedEventDetail({ value: 2 });
await expect(firstColumn).toHaveJSProperty('value', 2);
+ // Focus second column
+ await page.keyboard.press('Tab');
+
+ await page.waitForChanges();
+
await page.keyboard.press('Digit2+Digit4');
await expect(secondIonChange).toHaveReceivedEventDetail({ value: 24 });
@@ -155,8 +161,12 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
);
const column = page.locator('ion-picker-column');
+
+ const colShadowRoot = await column.evaluateHandle((el) => el.shadowRoot);
+ const columnPickerOpts = await colShadowRoot.evaluateHandle((root) => root?.querySelector('.picker-opts'));
+
const ionChange = await page.spyOnEvent('ionChange');
- await column.evaluate((el: HTMLIonPickerColumnElement) => el.setFocus());
+ await columnPickerOpts.evaluate((el) => el && (el as HTMLElement).focus());
await page.keyboard.press('Digit0');
diff --git a/core/src/utils/overlays.ts b/core/src/utils/overlays.ts
index 9dfc0b63edd..f05762f0ce0 100644
--- a/core/src/utils/overlays.ts
+++ b/core/src/utils/overlays.ts
@@ -495,8 +495,10 @@ export const setRootAriaHidden = (hidden = false) => {
if (hidden) {
viewContainer.setAttribute('aria-hidden', 'true');
+ viewContainer.setAttribute('inert', '');
} else {
viewContainer.removeAttribute('aria-hidden');
+ viewContainer.removeAttribute('inert');
}
};
@@ -511,6 +513,9 @@ export const present = async (
return;
}
+ // Blur the active element to prevent it from being kept focused inside a container that will be set with aria-hidden="true"
+ (document.activeElement as HTMLElement)?.blur();
+
/**
* Due to accessibility guidelines, toasts do not have
* focus traps.
@@ -577,6 +582,7 @@ export const present = async (
* screen readers.
*/
overlay.el.removeAttribute('aria-hidden');
+ overlay.el.removeAttribute('inert');
};
/**
@@ -645,6 +651,9 @@ export const dismiss = async (
return false;
}
+ // Blur the active element to prevent it from being kept focused inside the overlay, since it will be removed
+ (document.activeElement as HTMLElement)?.blur();
+
const presentedOverlays = doc !== undefined ? getPresentedOverlays(doc) : [];
/**
@@ -995,6 +1004,7 @@ const hideAnimatingOverlayFromScreenReaders = (overlay: HTMLIonOverlayElement) =
* This is done at the end of the `present` method.
*/
overlay.setAttribute('aria-hidden', 'true');
+ overlay.setAttribute('inert', '');
}
};
@@ -1024,6 +1034,7 @@ const hideUnderlyingOverlaysFromScreenReaders = (newTopMostOverlay: HTMLIonOverl
*/
if (nextPresentedOverlay.hasAttribute('aria-hidden') || nextPresentedOverlay.tagName !== 'ION-TOAST') {
presentedOverlay.setAttribute('aria-hidden', 'true');
+ presentedOverlay.setAttribute('inert', '');
}
}
};
@@ -1048,6 +1059,7 @@ const revealOverlaysToScreenReaders = () => {
* overlay too so focus can move there since focus is never automatically moved to the Toast.
*/
currentOverlay.removeAttribute('aria-hidden');
+ currentOverlay.removeAttribute('inert');
/**
* If we found a non-Toast element then we can just remove aria-hidden and stop searching entirely
diff --git a/core/src/utils/test/overlays/overlays.spec.ts b/core/src/utils/test/overlays/overlays.spec.ts
deleted file mode 100644
index 29a77c3c268..00000000000
--- a/core/src/utils/test/overlays/overlays.spec.ts
+++ /dev/null
@@ -1,263 +0,0 @@
-import { newSpecPage } from '@stencil/core/testing';
-
-import { Modal } from '../../../components/modal/modal';
-import { Toast } from '../../../components/toast/toast';
-import { Nav } from '../../../components/nav/nav';
-import { RouterOutlet } from '../../../components/router-outlet/router-outlet';
-import { setRootAriaHidden } from '../../overlays';
-
-describe('setRootAriaHidden()', () => {
- it('should correctly remove and re-add router outlet from accessibility tree', async () => {
- const page = await newSpecPage({
- components: [RouterOutlet],
- html: `
-
- `,
- });
-
- const routerOutlet = page.body.querySelector('ion-router-outlet')!;
-
- expect(routerOutlet.hasAttribute('aria-hidden')).toEqual(false);
-
- setRootAriaHidden(true);
- expect(routerOutlet.hasAttribute('aria-hidden')).toEqual(true);
-
- setRootAriaHidden(false);
- expect(routerOutlet.hasAttribute('aria-hidden')).toEqual(false);
- });
-
- it('should correctly remove and re-add nav from accessibility tree', async () => {
- const page = await newSpecPage({
- components: [Nav],
- html: `
-
- `,
- });
-
- const nav = page.body.querySelector('ion-nav')!;
-
- expect(nav.hasAttribute('aria-hidden')).toEqual(false);
-
- setRootAriaHidden(true);
- expect(nav.hasAttribute('aria-hidden')).toEqual(true);
-
- setRootAriaHidden(false);
- expect(nav.hasAttribute('aria-hidden')).toEqual(false);
- });
-
- it('should correctly remove and re-add custom container from accessibility tree', async () => {
- const page = await newSpecPage({
- components: [],
- html: `
-
-
- `,
- });
-
- const containerRoot = page.body.querySelector('#ion-view-container-root')!;
- const notContainerRoot = page.body.querySelector('#not-container-root')!;
-
- expect(containerRoot.hasAttribute('aria-hidden')).toEqual(false);
- expect(notContainerRoot.hasAttribute('aria-hidden')).toEqual(false);
-
- setRootAriaHidden(true);
- expect(containerRoot.hasAttribute('aria-hidden')).toEqual(true);
- expect(notContainerRoot.hasAttribute('aria-hidden')).toEqual(false);
-
- setRootAriaHidden(false);
- expect(containerRoot.hasAttribute('aria-hidden')).toEqual(false);
- expect(notContainerRoot.hasAttribute('aria-hidden')).toEqual(false);
- });
-
- it('should not error if router outlet was not found', async () => {
- await newSpecPage({
- components: [],
- html: `
-
- `,
- });
-
- setRootAriaHidden(true);
- });
-
- it('should remove router-outlet from accessibility tree when overlay is presented', async () => {
- const page = await newSpecPage({
- components: [RouterOutlet, Modal],
- html: `
-
-
-
- `,
- });
-
- const routerOutlet = page.body.querySelector('ion-router-outlet')!;
- const modal = page.body.querySelector('ion-modal')!;
-
- await modal.present();
-
- expect(routerOutlet.hasAttribute('aria-hidden')).toEqual(true);
- });
-
- it('should add router-outlet from accessibility tree when then final overlay is dismissed', async () => {
- const page = await newSpecPage({
- components: [RouterOutlet, Modal],
- html: `
-
-
-
-
- `,
- });
-
- const routerOutlet = page.body.querySelector('ion-router-outlet')!;
- const modalOne = page.body.querySelector('ion-modal#one')!;
- const modalTwo = page.body.querySelector('ion-modal#two')!;
-
- await modalOne.present();
-
- expect(routerOutlet.hasAttribute('aria-hidden')).toEqual(true);
-
- await modalTwo.present();
-
- expect(routerOutlet.hasAttribute('aria-hidden')).toEqual(true);
-
- await modalOne.dismiss();
-
- expect(routerOutlet.hasAttribute('aria-hidden')).toEqual(true);
-
- await modalTwo.dismiss();
-
- expect(routerOutlet.hasAttribute('aria-hidden')).toEqual(false);
- });
-});
-
-describe('aria-hidden on individual overlays', () => {
- it('should hide non-topmost overlays from screen readers', async () => {
- const page = await newSpecPage({
- components: [Modal],
- html: `
-
-
- `,
- });
-
- const modalOne = page.body.querySelector('ion-modal#one')!;
- const modalTwo = page.body.querySelector('ion-modal#two')!;
-
- await modalOne.present();
- await modalTwo.present();
-
- expect(modalOne.hasAttribute('aria-hidden')).toEqual(true);
- expect(modalTwo.hasAttribute('aria-hidden')).toEqual(false);
- });
-
- it('should unhide new topmost overlay from screen readers when topmost is dismissed', async () => {
- const page = await newSpecPage({
- components: [Modal],
- html: `
-
-
- `,
- });
-
- const modalOne = page.body.querySelector('ion-modal#one')!;
- const modalTwo = page.body.querySelector('ion-modal#two')!;
-
- await modalOne.present();
- await modalTwo.present();
-
- // dismiss modalTwo so that modalOne becomes the new topmost overlay
- await modalTwo.dismiss();
-
- expect(modalOne.hasAttribute('aria-hidden')).toEqual(false);
- });
-
- it('should not keep overlays hidden from screen readers if presented after being dismissed while non-topmost', async () => {
- const page = await newSpecPage({
- components: [Modal],
- html: `
-
-
- `,
- });
-
- const modalOne = page.body.querySelector('ion-modal#one')!;
- const modalTwo = page.body.querySelector('ion-modal#two')!;
-
- await modalOne.present();
- await modalTwo.present();
-
- // modalOne is not the topmost overlay at this point and is hidden from screen readers
- await modalOne.dismiss();
-
- // modalOne will become the topmost overlay; ensure it isn't still hidden from screen readers
- await modalOne.present();
- expect(modalOne.hasAttribute('aria-hidden')).toEqual(false);
- });
-
- it('should not hide previous overlay if top-most overlay is toast', async () => {
- const page = await newSpecPage({
- components: [Modal, Toast],
- html: `
-
-
-
-
- `,
- });
-
- const modalOne = page.body.querySelector('ion-modal#m-one')!;
- const modalTwo = page.body.querySelector('ion-modal#m-two')!;
- const toastOne = page.body.querySelector('ion-toast#t-one')!;
- const toastTwo = page.body.querySelector('ion-toast#t-two')!;
-
- await modalOne.present();
- await modalTwo.present();
- await toastOne.present();
- await toastTwo.present();
-
- expect(modalOne.hasAttribute('aria-hidden')).toEqual(true);
- expect(modalTwo.hasAttribute('aria-hidden')).toEqual(false);
- expect(toastOne.hasAttribute('aria-hidden')).toEqual(false);
- expect(toastTwo.hasAttribute('aria-hidden')).toEqual(false);
-
- await toastTwo.dismiss();
-
- expect(modalOne.hasAttribute('aria-hidden')).toEqual(true);
- expect(modalTwo.hasAttribute('aria-hidden')).toEqual(false);
- expect(toastOne.hasAttribute('aria-hidden')).toEqual(false);
-
- await toastOne.dismiss();
-
- expect(modalOne.hasAttribute('aria-hidden')).toEqual(true);
- expect(modalTwo.hasAttribute('aria-hidden')).toEqual(false);
- });
-
- it('should hide previous overlay even with a toast that is not the top-most overlay', async () => {
- const page = await newSpecPage({
- components: [Modal, Toast],
- html: `
-
-
-
- `,
- });
-
- const modalOne = page.body.querySelector('ion-modal#m-one')!;
- const modalTwo = page.body.querySelector('ion-modal#m-two')!;
- const toastOne = page.body.querySelector('ion-toast#t-one')!;
-
- await modalOne.present();
- await toastOne.present();
- await modalTwo.present();
-
- expect(modalOne.hasAttribute('aria-hidden')).toEqual(true);
- expect(toastOne.hasAttribute('aria-hidden')).toEqual(true);
- expect(modalTwo.hasAttribute('aria-hidden')).toEqual(false);
-
- await modalTwo.dismiss();
-
- expect(modalOne.hasAttribute('aria-hidden')).toEqual(false);
- expect(toastOne.hasAttribute('aria-hidden')).toEqual(false);
- });
-});
diff --git a/packages/angular/package.json b/packages/angular/package.json
index 2159739c080..928b33a5eb0 100644
--- a/packages/angular/package.json
+++ b/packages/angular/package.json
@@ -37,6 +37,7 @@
"eslint": "eslint . --ext .ts",
"prerelease": "npm run validate && np prerelease --yolo --any-branch --tag next",
"sync": "./scripts/sync.sh",
+ "local.sync.and.pack": "./scripts/sync-and-pack.sh",
"test": "echo 'angular no tests yet'",
"tsc": "tsc -p .",
"validate": "npm i && npm run lint && npm run test && npm run build"
diff --git a/packages/angular/scripts/sync-and-pack.sh b/packages/angular/scripts/sync-and-pack.sh
new file mode 100755
index 00000000000..7a9ab8aa390
--- /dev/null
+++ b/packages/angular/scripts/sync-and-pack.sh
@@ -0,0 +1,27 @@
+set -e
+
+# Delete old packages
+rm -f *.tgz
+
+# Pack @ionic/core
+echo "\n📦 Packing @ionic/core..."
+npm pack ../../core
+
+# Update package.json with global path for the @ionic/core package
+echo "\n⚙️ Updating package.json with global path for @ionic/core..."
+CORE_PACKAGE=$(ls ionic-core-*.tgz | head -1)
+sed -i "" "s|\"@ionic/core\": \".*\"|\"@ionic/core\": \"file:$(pwd)/$CORE_PACKAGE\"|" package.json
+
+# Install Dependencies
+echo "\n🔧 Installing dependencies..."
+npm install
+
+# Build the project
+echo "\n🔨 Building the project..."
+npm run build
+
+# Pack @ionic/angular
+echo "\n📦 Packing @ionic/angular..."
+npm pack ./dist
+
+echo "\n✅ Packed ionic-angular package!\n $(pwd)/$(ls ionic-angular-*.tgz | head -1)\n"
diff --git a/packages/react/package.json b/packages/react/package.json
index 0305d6cc985..e167fd00bbd 100644
--- a/packages/react/package.json
+++ b/packages/react/package.json
@@ -30,7 +30,8 @@
"lint.fix": "npm run eslint -- --fix && npm run prettier -- --write --cache",
"copy": "node scripts/copy.js",
"test.spec": "jest --ci",
- "sync": "sh ./scripts/sync.sh"
+ "sync": "sh ./scripts/sync.sh",
+ "local.sync.and.pack": "./scripts/sync-and-pack.sh"
},
"main": "dist/index.js",
"types": "dist/types/index.d.ts",
diff --git a/packages/react/scripts/sync-and-pack.sh b/packages/react/scripts/sync-and-pack.sh
new file mode 100755
index 00000000000..ecea6d0f9ab
--- /dev/null
+++ b/packages/react/scripts/sync-and-pack.sh
@@ -0,0 +1,27 @@
+set -e
+
+# Delete old packages
+rm -f *.tgz
+
+# Pack @ionic/core
+echo "\n📦 Packing @ionic/core..."
+npm pack ../../core
+
+# Update package.json with global path for the @ionic/core package
+echo "\n⚙️ Updating package.json with global path for @ionic/core..."
+CORE_PACKAGE=$(ls ionic-core-*.tgz | head -1)
+sed -i "" "s|\"@ionic/core\": \".*\"|\"@ionic/core\": \"file:$(pwd)/$CORE_PACKAGE\"|" package.json
+
+# Install Dependencies
+echo "\n🔧 Installing dependencies..."
+npm install
+
+# Build the project
+echo "\n🔨 Building the project..."
+npm run build
+
+# Pack @ionic/react
+echo "\n📦 Packing @ionic/react..."
+npm pack
+
+echo "\n✅ Packed @ionic/react package!\n $(pwd)/$(ls ionic-react-*.tgz | head -1)\n"
diff --git a/packages/vue/package.json b/packages/vue/package.json
index 756c62aa0e0..43c98938f8d 100644
--- a/packages/vue/package.json
+++ b/packages/vue/package.json
@@ -16,7 +16,8 @@
"build.vetur": "node ./scripts/build-vetur.js",
"copy": "node ./scripts/copy-css.js",
"copy.overlays": "node ./scripts/copy-overlays.js",
- "sync": "sh ./scripts/sync.sh"
+ "sync": "sh ./scripts/sync.sh",
+ "local.sync.and.pack": "./scripts/sync-and-pack.sh"
},
"main": "./dist/index.js",
"types": "./dist/types/index.d.ts",
diff --git a/packages/vue/scripts/sync-and-pack.sh b/packages/vue/scripts/sync-and-pack.sh
new file mode 100755
index 00000000000..a1bc37c6f60
--- /dev/null
+++ b/packages/vue/scripts/sync-and-pack.sh
@@ -0,0 +1,34 @@
+set -e
+
+# Delete old packages
+rm -f *.tgz
+
+# Delete vite cache
+rm -rf node_modules/.vite
+
+# Pack @ionic/core
+echo "\n📦 Packing @ionic/core..."
+npm pack ../../core
+
+# Pack @ionic/vue-router
+echo "\n📦 Packing @ionic/vue-router..."
+npm pack ../vue-router
+
+# Update package.json with global path for the @ionic/core package
+echo "\n⚙️ Updating package.json with global path for @ionic/core..."
+CORE_PACKAGE=$(ls ionic-core-*.tgz | head -1)
+sed -i "" "s|\"@ionic/core\": \".*\"|\"@ionic/core\": \"file:$(pwd)/$CORE_PACKAGE\"|" package.json
+
+# Install Dependencies
+echo "\n🔧 Installing dependencies..."
+npm install
+
+# Build the project
+echo "\n🔨 Building the project..."
+npm run build
+
+# Pack @ionic/vue
+echo "\n📦 Packing @ionic/vue..."
+npm pack
+
+echo "\n✅ Packed @ionic/vue package!\n $(pwd)/$(ls ionic-vue-*.tgz | head -1)\n"