Compare commits

..

4 Commits

90 changed files with 75 additions and 405 deletions

View File

@@ -11,8 +11,8 @@ jobs:
issues: write
steps:
- name: 'Auto-assign issue'
uses: pozil/auto-assign-issue@39c06395cbac76e79afc4ad4e5c5c6db6ecfdd2e # v2.2.0
uses: pozil/auto-assign-issue@c015a6a3f410f12f58255c3d085fd774312f7a2f # v2.1.2
with:
assignees: brandyscarney, thetaPC, ShaneK
assignees: brandyscarney, thetaPC, joselrio, rugoncalves, BenOsodrac, JoaoFerreira-FrontEnd, OS-giulianasilva, tanner-reits
numOfAssignee: 1
allowSelfAssign: false

80
core/package-lock.json generated
View File

@@ -15,11 +15,11 @@
},
"devDependencies": {
"@axe-core/playwright": "^4.10.0",
"@capacitor/core": "^7.0.0",
"@capacitor/haptics": "^7.0.0",
"@capacitor/keyboard": "^7.0.0",
"@capacitor/status-bar": "^7.0.0",
"@clack/prompts": "^0.10.0",
"@capacitor/core": "^6.0.0",
"@capacitor/haptics": "^6.0.0",
"@capacitor/keyboard": "^6.0.0",
"@capacitor/status-bar": "^6.0.0",
"@clack/prompts": "^0.9.0",
"@ionic/eslint-config": "^0.3.0",
"@ionic/prettier-config": "^2.0.0",
"@playwright/test": "^1.46.1",
@@ -663,43 +663,39 @@
"dev": true
},
"node_modules/@capacitor/core": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/@capacitor/core/-/core-7.0.1.tgz",
"integrity": "sha512-1Ob9bvA/p8g8aNwK6VesxEekGXowLVf6APjkW4LRnr05H+7z/bke+Q5pn9zqh/GgTbIxAQ/rwZrAZvvxkdm1UA==",
"version": "6.2.0",
"resolved": "https://registry.npmjs.org/@capacitor/core/-/core-6.2.0.tgz",
"integrity": "sha512-B9IlJtDpUqhhYb+T8+cp2Db/3RETX36STgjeU2kQZBs/SLAcFiMama227o+msRjLeo3DO+7HJjWVA1+XlyyPEg==",
"dev": true,
"license": "MIT",
"dependencies": {
"tslib": "^2.1.0"
}
},
"node_modules/@capacitor/haptics": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/@capacitor/haptics/-/haptics-7.0.0.tgz",
"integrity": "sha512-8uI8rWyAbq8EzkjS+sHZSncyzujHzVbuLKgj8J5H0yUL6+r26F16gVA2iuQuIBvzbSMy7Y0/pUuWlwZr/H8AKg==",
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/@capacitor/haptics/-/haptics-6.0.2.tgz",
"integrity": "sha512-xcFdIH4iIIeW2+1lzmlYMVicqB9ytaiuZ9NE3a9laKFPvMGC7hdj6i6tHFezwPJ/96xkHOwXT2b0F8Mh9xtTWg==",
"dev": true,
"license": "MIT",
"peerDependencies": {
"@capacitor/core": ">=7.0.0"
"@capacitor/core": "^6.0.0"
}
},
"node_modules/@capacitor/keyboard": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/@capacitor/keyboard/-/keyboard-7.0.0.tgz",
"integrity": "sha512-Tqwy8wG+sx4UqiFCX4Q+bFw6uKgG7BiHKAPpeefoIgoEB8H8Jf3xZNZoVPnJIMuPsCdSvuyHXZbJXH9IEEirGA==",
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/@capacitor/keyboard/-/keyboard-6.0.3.tgz",
"integrity": "sha512-V/mURxBI68HvClYjrGBlOriWkwYN7r+cWid/igJz/3scNc/V81DgQ9fpoLr4W0I5NY7YxOesjIJLuLO+LT18mQ==",
"dev": true,
"license": "MIT",
"peerDependencies": {
"@capacitor/core": ">=7.0.0"
"@capacitor/core": "^6.0.0"
}
},
"node_modules/@capacitor/status-bar": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/@capacitor/status-bar/-/status-bar-7.0.0.tgz",
"integrity": "sha512-wsvPkWkoSOXMIgVHu4c6P1sOuDSZ1ClUo5OpLRwj7u8DYzlV4jlmNzztQn2Lvsiqx1z4kfukSaqe40k1Lo4c9g==",
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/@capacitor/status-bar/-/status-bar-6.0.2.tgz",
"integrity": "sha512-AmRIX6QvFemItlY7/69ARkIAqitRQqJ2qwgZmD1KqgFb78pH+XFXm1guvS/a8CuOOm/IqZ4ddDbl20yxtBqzGA==",
"dev": true,
"license": "MIT",
"peerDependencies": {
"@capacitor/core": ">=7.0.0"
"@capacitor/core": "^6.0.0"
}
},
"node_modules/@clack/core": {
@@ -713,9 +709,9 @@
}
},
"node_modules/@clack/prompts": {
"version": "0.10.0",
"resolved": "https://registry.npmjs.org/@clack/prompts/-/prompts-0.10.0.tgz",
"integrity": "sha512-H3rCl6CwW1NdQt9rE3n373t7o5cthPv7yUoxF2ytZvyvlJv89C5RYMJu83Hed8ODgys5vpBU0GKxIRG83jd8NQ==",
"version": "0.9.1",
"resolved": "https://registry.npmjs.org/@clack/prompts/-/prompts-0.9.1.tgz",
"integrity": "sha512-JIpyaboYZeWYlyP0H+OoPPxd6nqueG/CmN6ixBiNFsIDHREevjIf0n0Ohh5gr5C8pEDknzgvz+pIJ8dMhzWIeg==",
"dev": true,
"dependencies": {
"@clack/core": "0.4.1",
@@ -10981,32 +10977,32 @@
"dev": true
},
"@capacitor/core": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/@capacitor/core/-/core-7.0.1.tgz",
"integrity": "sha512-1Ob9bvA/p8g8aNwK6VesxEekGXowLVf6APjkW4LRnr05H+7z/bke+Q5pn9zqh/GgTbIxAQ/rwZrAZvvxkdm1UA==",
"version": "6.2.0",
"resolved": "https://registry.npmjs.org/@capacitor/core/-/core-6.2.0.tgz",
"integrity": "sha512-B9IlJtDpUqhhYb+T8+cp2Db/3RETX36STgjeU2kQZBs/SLAcFiMama227o+msRjLeo3DO+7HJjWVA1+XlyyPEg==",
"dev": true,
"requires": {
"tslib": "^2.1.0"
}
},
"@capacitor/haptics": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/@capacitor/haptics/-/haptics-7.0.0.tgz",
"integrity": "sha512-8uI8rWyAbq8EzkjS+sHZSncyzujHzVbuLKgj8J5H0yUL6+r26F16gVA2iuQuIBvzbSMy7Y0/pUuWlwZr/H8AKg==",
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/@capacitor/haptics/-/haptics-6.0.2.tgz",
"integrity": "sha512-xcFdIH4iIIeW2+1lzmlYMVicqB9ytaiuZ9NE3a9laKFPvMGC7hdj6i6tHFezwPJ/96xkHOwXT2b0F8Mh9xtTWg==",
"dev": true,
"requires": {}
},
"@capacitor/keyboard": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/@capacitor/keyboard/-/keyboard-7.0.0.tgz",
"integrity": "sha512-Tqwy8wG+sx4UqiFCX4Q+bFw6uKgG7BiHKAPpeefoIgoEB8H8Jf3xZNZoVPnJIMuPsCdSvuyHXZbJXH9IEEirGA==",
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/@capacitor/keyboard/-/keyboard-6.0.3.tgz",
"integrity": "sha512-V/mURxBI68HvClYjrGBlOriWkwYN7r+cWid/igJz/3scNc/V81DgQ9fpoLr4W0I5NY7YxOesjIJLuLO+LT18mQ==",
"dev": true,
"requires": {}
},
"@capacitor/status-bar": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/@capacitor/status-bar/-/status-bar-7.0.0.tgz",
"integrity": "sha512-wsvPkWkoSOXMIgVHu4c6P1sOuDSZ1ClUo5OpLRwj7u8DYzlV4jlmNzztQn2Lvsiqx1z4kfukSaqe40k1Lo4c9g==",
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/@capacitor/status-bar/-/status-bar-6.0.2.tgz",
"integrity": "sha512-AmRIX6QvFemItlY7/69ARkIAqitRQqJ2qwgZmD1KqgFb78pH+XFXm1guvS/a8CuOOm/IqZ4ddDbl20yxtBqzGA==",
"dev": true,
"requires": {}
},
@@ -11021,9 +11017,9 @@
}
},
"@clack/prompts": {
"version": "0.10.0",
"resolved": "https://registry.npmjs.org/@clack/prompts/-/prompts-0.10.0.tgz",
"integrity": "sha512-H3rCl6CwW1NdQt9rE3n373t7o5cthPv7yUoxF2ytZvyvlJv89C5RYMJu83Hed8ODgys5vpBU0GKxIRG83jd8NQ==",
"version": "0.9.1",
"resolved": "https://registry.npmjs.org/@clack/prompts/-/prompts-0.9.1.tgz",
"integrity": "sha512-JIpyaboYZeWYlyP0H+OoPPxd6nqueG/CmN6ixBiNFsIDHREevjIf0n0Ohh5gr5C8pEDknzgvz+pIJ8dMhzWIeg==",
"dev": true,
"requires": {
"@clack/core": "0.4.1",

View File

@@ -37,11 +37,11 @@
},
"devDependencies": {
"@axe-core/playwright": "^4.10.0",
"@capacitor/core": "^7.0.0",
"@capacitor/haptics": "^7.0.0",
"@capacitor/keyboard": "^7.0.0",
"@capacitor/status-bar": "^7.0.0",
"@clack/prompts": "^0.10.0",
"@capacitor/core": "^6.0.0",
"@capacitor/haptics": "^6.0.0",
"@capacitor/keyboard": "^6.0.0",
"@capacitor/status-bar": "^6.0.0",
"@clack/prompts": "^0.9.0",
"@ionic/eslint-config": "^0.3.0",
"@ionic/prettier-config": "^2.0.0",
"@playwright/test": "^1.46.1",

View File

@@ -237,18 +237,6 @@ export class Alert implements ComponentInterface, OverlayInterface {
return;
}
/**
* Ensure when alert container is being focused, and the user presses the tab + shift keys, the focus will be set to the last alert button.
*/
if (ev.target.classList.contains('alert-wrapper')) {
if (ev.key === 'Tab' && ev.shiftKey) {
ev.preventDefault();
const lastChildBtn = this.wrapperEl?.querySelector('.alert-button:last-child') as HTMLButtonElement;
lastChildBtn.focus();
return;
}
}
// The only inputs we want to navigate between using arrow keys are the radios
// ignore the keydown event if it is not on a radio button
if (
@@ -412,19 +400,7 @@ export class Alert implements ComponentInterface, OverlayInterface {
await this.delegateController.attachViewToDom();
await present(this, 'alertEnter', iosEnterAnimation, mdEnterAnimation).then(() => {
/**
* Check if alert has only one button and no inputs.
* If so, then focus on the button. Otherwise, focus the alert wrapper.
* This will map to the default native alert behavior.
*/
if (this.buttons.length === 1 && this.inputs.length === 0) {
const queryBtn = this.wrapperEl?.querySelector('.alert-button') as HTMLButtonElement;
queryBtn.focus();
} else {
this.wrapperEl?.focus();
}
});
await present(this, 'alertEnter', iosEnterAnimation, mdEnterAnimation);
unlock();
}
@@ -749,8 +725,8 @@ export class Alert implements ComponentInterface, OverlayInterface {
const { overlayIndex, header, subHeader, message, htmlAttributes } = this;
const mode = getIonMode(this);
const hdrId = `alert-${overlayIndex}-hdr`;
const msgId = `alert-${overlayIndex}-msg`;
const subHdrId = `alert-${overlayIndex}-sub-hdr`;
const msgId = `alert-${overlayIndex}-msg`;
const role = this.inputs.length > 0 || this.buttons.length > 0 ? 'alertdialog' : 'alert';
/**
@@ -763,7 +739,12 @@ export class Alert implements ComponentInterface, OverlayInterface {
return (
<Host
role={role}
aria-modal="true"
aria-labelledby={ariaLabelledBy}
aria-describedby={message !== undefined ? msgId : null}
tabindex="-1"
{...(htmlAttributes as any)}
style={{
zIndex: `${20000 + overlayIndex}`,
}}
@@ -780,16 +761,7 @@ export class Alert implements ComponentInterface, OverlayInterface {
<div tabindex="0" aria-hidden="true"></div>
<div
class="alert-wrapper ion-overlay-wrapper"
role={role}
aria-modal="true"
aria-labelledby={ariaLabelledBy}
aria-describedby={message !== undefined ? msgId : null}
tabindex="0"
ref={(el) => (this.wrapperEl = el)}
{...(htmlAttributes as any)}
>
<div class="alert-wrapper ion-overlay-wrapper" ref={(el) => (this.wrapperEl = el)}>
<div class="alert-head">
{header && (
<h2 id={hdrId} class="alert-title">

View File

@@ -16,7 +16,6 @@ const testAria = async (
await didPresent.next();
const alert = page.locator('ion-alert');
const alertwrapper = alert.locator('.alert-wrapper');
const header = alert.locator('.alert-title');
const subHeader = alert.locator('.alert-sub-title');
@@ -43,8 +42,8 @@ const testAria = async (
* expect().toHaveAttribute() can't check for a null value, so grab and check
* the values manually instead.
*/
const ariaLabelledBy = await alertwrapper.getAttribute('aria-labelledby');
const ariaDescribedBy = await alertwrapper.getAttribute('aria-describedby');
const ariaLabelledBy = await alert.getAttribute('aria-labelledby');
const ariaDescribedBy = await alert.getAttribute('aria-describedby');
expect(ariaLabelledBy).toBe(expectedAriaLabelledBy);
expect(ariaDescribedBy).toBe(expectedAriaDescribedBy);

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 36 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 40 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 32 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 35 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 42 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 32 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 20 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 14 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 29 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 34 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 25 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 36 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 43 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 33 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 14 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 20 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 23 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 16 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 31 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 19 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 30 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 22 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 36 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 24 KiB

View File

@@ -1,7 +1,7 @@
import { h } from '@stencil/core';
import { newSpecPage } from '@stencil/core/testing';
import { Alert } from '../alert';
import { h } from '@stencil/core';
describe('alert: id', () => {
it('alert should be assigned an incrementing id', async () => {
@@ -49,7 +49,7 @@ describe('alert: id', () => {
template: () => <ion-alert htmlAttributes={{ id }} overlayIndex={-1}></ion-alert>,
});
const alertwrapper = page.body.querySelector('.alert-wrapper')!;
expect(alertwrapper.id).toBe(id);
const alert = page.body.querySelector('ion-alert')!;
expect(alert.id).toBe(id);
});
});

View File

@@ -19,7 +19,6 @@ configs({ directions: ['ltr'] }).forEach(({ config, screenshot, title }) => {
await page.keyboard.press(tabKey);
await expect(alertBtns.nth(0)).toBeFocused();
await page.keyboard.press(`Shift+${tabKey}`); // this will focus the alert-wrapper
await page.keyboard.press(`Shift+${tabKey}`);
await expect(alertBtns.nth(2)).toBeFocused();
@@ -31,7 +30,7 @@ configs({ directions: ['ltr'] }).forEach(({ config, screenshot, title }) => {
const alertFixture = new AlertFixture(page, screenshot);
const alert = await alertFixture.open('#basic');
await expect(alert.locator('.alert-wrapper')).toHaveAttribute('data-testid', 'basic-alert');
await expect(alert).toHaveAttribute('data-testid', 'basic-alert');
});
test('should dismiss when async handler resolves', async ({ page }) => {

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 19 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 10 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 18 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 9.4 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 18 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 10 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 18 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 9.4 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 16 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 26 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 11 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 19 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 9.8 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 16 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 26 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 11 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 19 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 9.8 KiB

View File

@@ -2,7 +2,7 @@ import type { ComponentInterface, EventEmitter } from '@stencil/core';
import { Component, Element, Event, Host, Prop, State, Watch, h } from '@stencil/core';
import { findClosestIonContent, disableContentScrollY, resetContentScrollY } from '@utils/content';
import type { Attributes } from '@utils/helpers';
import { inheritAriaAttributes, clamp, debounceEvent, renderHiddenInput, isSafeNumber } from '@utils/helpers';
import { inheritAriaAttributes, clamp, debounceEvent, renderHiddenInput } from '@utils/helpers';
import { printIonWarning } from '@utils/logging';
import { isRTL } from '@utils/rtl';
import { createColorClasses, hostContext } from '@utils/theme';
@@ -109,11 +109,7 @@ export class Range implements ComponentInterface {
*/
@Prop() min = 0;
@Watch('min')
protected minChanged(newValue: number) {
if (!isSafeNumber(newValue)) {
this.min = 0;
}
protected minChanged() {
if (!this.noUpdate) {
this.updateRatio();
}
@@ -124,11 +120,7 @@ export class Range implements ComponentInterface {
*/
@Prop() max = 100;
@Watch('max')
protected maxChanged(newValue: number) {
if (!isSafeNumber(newValue)) {
this.max = 100;
}
protected maxChanged() {
if (!this.noUpdate) {
this.updateRatio();
}
@@ -159,12 +151,6 @@ export class Range implements ComponentInterface {
* Specifies the value granularity.
*/
@Prop() step = 1;
@Watch('step')
protected stepChanged(newValue: number) {
if (!isSafeNumber(newValue)) {
this.step = 1;
}
}
/**
* If `true`, tick marks are displayed based on the step value.
@@ -314,11 +300,6 @@ export class Range implements ComponentInterface {
}
this.inheritedAttributes = inheritAriaAttributes(this.el);
// If the min or max is not safe, set it to 0 or 100 respectively.
// Our watch does this, but not before the initial load.
this.min = isSafeNumber(this.min) ? this.min : 0;
this.max = isSafeNumber(this.max) ? this.max : 100;
this.step = isSafeNumber(this.step) ? this.step : 1;
}
componentDidLoad() {

View File

@@ -28,25 +28,6 @@ describe('Range', () => {
});
});
it('should handle undefined min and max values by falling back to defaults', async () => {
const page = await newSpecPage({
components: [Range],
html: `<ion-range id="my-custom-range">
<div slot="label">Range</div>
</ion-range>`,
});
const range = page.body.querySelector('ion-range')!;
// Here we have to cast this to any, but in its react wrapper it accepts undefined as a valid value
range.min = undefined as any;
range.max = undefined as any;
range.step = undefined as any;
await page.waitForChanges();
expect(range.min).toBe(0);
expect(range.max).toBe(100);
expect(range.step).toBe(1);
});
it('should return the clamped value for a range dual knob component', () => {
sharedRange.min = 0;
sharedRange.max = 100;

View File

@@ -310,10 +310,19 @@ export class Select implements ComponentInterface {
}
this.isExpanded = true;
const overlay = (this.overlay = await this.createOverlay(event));
overlay.onDidDismiss().then(() => {
this.overlay = undefined;
this.isExpanded = false;
this.ionDismiss.emit();
this.setFocus();
});
// Add logic to scroll selected item into view before presenting
const scrollSelectedIntoView = () => {
await overlay.present();
// focus selected option for popovers and modals
if (this.interface === 'popover' || this.interface === 'modal') {
const indexOfSelected = this.childOpts.findIndex((o) => o.value === this.value);
if (indexOfSelected > -1) {
const selectedItem = overlay.querySelector<HTMLElement>(
`.select-interface-option:nth-child(${indexOfSelected + 1})`
@@ -336,7 +345,6 @@ export class Select implements ComponentInterface {
| HTMLIonCheckboxElement
| null;
if (interactiveEl) {
selectedItem.scrollIntoView({ block: 'nearest' });
// Needs to be called before `focusVisibleElement` to prevent issue with focus event bubbling
// and removing `ion-focused` style
interactiveEl.setFocus();
@@ -364,40 +372,8 @@ export class Select implements ComponentInterface {
focusVisibleElement(firstEnabledOption.closest('ion-item')!);
}
}
};
// For modals and popovers, we can scroll before they're visible
if (this.interface === 'modal') {
overlay.addEventListener('ionModalWillPresent', scrollSelectedIntoView, { once: true });
} else if (this.interface === 'popover') {
overlay.addEventListener('ionPopoverWillPresent', scrollSelectedIntoView, { once: true });
} else {
/**
* For alerts and action sheets, we need to wait a frame after willPresent
* because these overlays don't have their content in the DOM immediately
* when willPresent fires. By waiting a frame, we ensure the content is
* rendered and can be properly scrolled into view.
*/
const scrollAfterRender = () => {
requestAnimationFrame(() => {
scrollSelectedIntoView();
});
};
if (this.interface === 'alert') {
overlay.addEventListener('ionAlertWillPresent', scrollAfterRender, { once: true });
} else if (this.interface === 'action-sheet') {
overlay.addEventListener('ionActionSheetWillPresent', scrollAfterRender, { once: true });
}
}
overlay.onDidDismiss().then(() => {
this.overlay = undefined;
this.isExpanded = false;
this.ionDismiss.emit();
this.setFocus();
});
await overlay.present();
return overlay;
}

View File

@@ -61,169 +61,6 @@
</ion-item>
</ion-list>
<ion-list>
<ion-list-header>
<ion-label>Single Value - Overflowing Options</ion-label>
</ion-list-header>
<ion-item>
<ion-select id="alert-select-scroll-to-selected" label="Alert" interface="alert" value="watermelon">
<ion-select-option value="apple">Apple</ion-select-option>
<ion-select-option value="apricot">Apricot</ion-select-option>
<ion-select-option value="avocado">Avocado</ion-select-option>
<ion-select-option value="banana">Banana</ion-select-option>
<ion-select-option value="blackberry">Blackberry</ion-select-option>
<ion-select-option value="blueberry">Blueberry</ion-select-option>
<ion-select-option value="cantaloupe">Cantaloupe</ion-select-option>
<ion-select-option value="cherry">Cherry</ion-select-option>
<ion-select-option value="coconut">Coconut</ion-select-option>
<ion-select-option value="cranberry">Cranberry</ion-select-option>
<ion-select-option value="dragonfruit">Dragonfruit</ion-select-option>
<ion-select-option value="fig">Fig</ion-select-option>
<ion-select-option value="grape">Grape</ion-select-option>
<ion-select-option value="grapefruit">Grapefruit</ion-select-option>
<ion-select-option value="guava">Guava</ion-select-option>
<ion-select-option value="kiwi">Kiwi</ion-select-option>
<ion-select-option value="lemon">Lemon</ion-select-option>
<ion-select-option value="lime">Lime</ion-select-option>
<ion-select-option value="lychee">Lychee</ion-select-option>
<ion-select-option value="mango">Mango</ion-select-option>
<ion-select-option value="nectarine">Nectarine</ion-select-option>
<ion-select-option value="orange">Orange</ion-select-option>
<ion-select-option value="papaya">Papaya</ion-select-option>
<ion-select-option value="passion-fruit">Passion Fruit</ion-select-option>
<ion-select-option value="peach">Peach</ion-select-option>
<ion-select-option value="pear">Pear</ion-select-option>
<ion-select-option value="pineapple">Pineapple</ion-select-option>
<ion-select-option value="plum">Plum</ion-select-option>
<ion-select-option value="pomegranate">Pomegranate</ion-select-option>
<ion-select-option value="raspberry">Raspberry</ion-select-option>
<ion-select-option value="strawberry">Strawberry</ion-select-option>
<ion-select-option value="tangerine">Tangerine</ion-select-option>
<ion-select-option value="watermelon">Watermelon</ion-select-option>
</ion-select>
</ion-item>
<ion-item>
<ion-select
id="action-sheet-select-scroll-to-selected"
label="Action Sheet"
interface="action-sheet"
value="watermelon"
>
<ion-select-option value="apple">Apple</ion-select-option>
<ion-select-option value="apricot">Apricot</ion-select-option>
<ion-select-option value="avocado">Avocado</ion-select-option>
<ion-select-option value="banana">Banana</ion-select-option>
<ion-select-option value="blackberry">Blackberry</ion-select-option>
<ion-select-option value="blueberry">Blueberry</ion-select-option>
<ion-select-option value="cantaloupe">Cantaloupe</ion-select-option>
<ion-select-option value="cherry">Cherry</ion-select-option>
<ion-select-option value="coconut">Coconut</ion-select-option>
<ion-select-option value="cranberry">Cranberry</ion-select-option>
<ion-select-option value="dragonfruit">Dragonfruit</ion-select-option>
<ion-select-option value="fig">Fig</ion-select-option>
<ion-select-option value="grape">Grape</ion-select-option>
<ion-select-option value="grapefruit">Grapefruit</ion-select-option>
<ion-select-option value="guava">Guava</ion-select-option>
<ion-select-option value="kiwi">Kiwi</ion-select-option>
<ion-select-option value="lemon">Lemon</ion-select-option>
<ion-select-option value="lime">Lime</ion-select-option>
<ion-select-option value="lychee">Lychee</ion-select-option>
<ion-select-option value="mango">Mango</ion-select-option>
<ion-select-option value="nectarine">Nectarine</ion-select-option>
<ion-select-option value="orange">Orange</ion-select-option>
<ion-select-option value="papaya">Papaya</ion-select-option>
<ion-select-option value="passion-fruit">Passion Fruit</ion-select-option>
<ion-select-option value="peach">Peach</ion-select-option>
<ion-select-option value="pear">Pear</ion-select-option>
<ion-select-option value="pineapple">Pineapple</ion-select-option>
<ion-select-option value="plum">Plum</ion-select-option>
<ion-select-option value="pomegranate">Pomegranate</ion-select-option>
<ion-select-option value="raspberry">Raspberry</ion-select-option>
<ion-select-option value="strawberry">Strawberry</ion-select-option>
<ion-select-option value="tangerine">Tangerine</ion-select-option>
<ion-select-option value="watermelon">Watermelon</ion-select-option>
</ion-select>
</ion-item>
<ion-item>
<ion-select id="popover-select-scroll-to-selected" label="Popover" interface="popover" value="watermelon">
<ion-select-option value="apple">Apple</ion-select-option>
<ion-select-option value="apricot">Apricot</ion-select-option>
<ion-select-option value="avocado">Avocado</ion-select-option>
<ion-select-option value="banana">Banana</ion-select-option>
<ion-select-option value="blackberry">Blackberry</ion-select-option>
<ion-select-option value="blueberry">Blueberry</ion-select-option>
<ion-select-option value="cantaloupe">Cantaloupe</ion-select-option>
<ion-select-option value="cherry">Cherry</ion-select-option>
<ion-select-option value="coconut">Coconut</ion-select-option>
<ion-select-option value="cranberry">Cranberry</ion-select-option>
<ion-select-option value="dragonfruit">Dragonfruit</ion-select-option>
<ion-select-option value="fig">Fig</ion-select-option>
<ion-select-option value="grape">Grape</ion-select-option>
<ion-select-option value="grapefruit">Grapefruit</ion-select-option>
<ion-select-option value="guava">Guava</ion-select-option>
<ion-select-option value="kiwi">Kiwi</ion-select-option>
<ion-select-option value="lemon">Lemon</ion-select-option>
<ion-select-option value="lime">Lime</ion-select-option>
<ion-select-option value="lychee">Lychee</ion-select-option>
<ion-select-option value="mango">Mango</ion-select-option>
<ion-select-option value="nectarine">Nectarine</ion-select-option>
<ion-select-option value="orange">Orange</ion-select-option>
<ion-select-option value="papaya">Papaya</ion-select-option>
<ion-select-option value="passion-fruit">Passion Fruit</ion-select-option>
<ion-select-option value="peach">Peach</ion-select-option>
<ion-select-option value="pear">Pear</ion-select-option>
<ion-select-option value="pineapple">Pineapple</ion-select-option>
<ion-select-option value="plum">Plum</ion-select-option>
<ion-select-option value="pomegranate">Pomegranate</ion-select-option>
<ion-select-option value="raspberry">Raspberry</ion-select-option>
<ion-select-option value="strawberry">Strawberry</ion-select-option>
<ion-select-option value="tangerine">Tangerine</ion-select-option>
<ion-select-option value="watermelon">Watermelon</ion-select-option>
</ion-select>
</ion-item>
<ion-item>
<ion-select id="modal-select-scroll-to-selected" label="Modal" interface="modal" value="watermelon">
<ion-select-option value="apple">Apple</ion-select-option>
<ion-select-option value="apricot">Apricot</ion-select-option>
<ion-select-option value="avocado">Avocado</ion-select-option>
<ion-select-option value="banana">Banana</ion-select-option>
<ion-select-option value="blackberry">Blackberry</ion-select-option>
<ion-select-option value="blueberry">Blueberry</ion-select-option>
<ion-select-option value="cantaloupe">Cantaloupe</ion-select-option>
<ion-select-option value="cherry">Cherry</ion-select-option>
<ion-select-option value="coconut">Coconut</ion-select-option>
<ion-select-option value="cranberry">Cranberry</ion-select-option>
<ion-select-option value="dragonfruit">Dragonfruit</ion-select-option>
<ion-select-option value="fig">Fig</ion-select-option>
<ion-select-option value="grape">Grape</ion-select-option>
<ion-select-option value="grapefruit">Grapefruit</ion-select-option>
<ion-select-option value="guava">Guava</ion-select-option>
<ion-select-option value="kiwi">Kiwi</ion-select-option>
<ion-select-option value="lemon">Lemon</ion-select-option>
<ion-select-option value="lime">Lime</ion-select-option>
<ion-select-option value="lychee">Lychee</ion-select-option>
<ion-select-option value="mango">Mango</ion-select-option>
<ion-select-option value="nectarine">Nectarine</ion-select-option>
<ion-select-option value="orange">Orange</ion-select-option>
<ion-select-option value="papaya">Papaya</ion-select-option>
<ion-select-option value="passion-fruit">Passion Fruit</ion-select-option>
<ion-select-option value="peach">Peach</ion-select-option>
<ion-select-option value="pear">Pear</ion-select-option>
<ion-select-option value="pineapple">Pineapple</ion-select-option>
<ion-select-option value="plum">Plum</ion-select-option>
<ion-select-option value="pomegranate">Pomegranate</ion-select-option>
<ion-select-option value="raspberry">Raspberry</ion-select-option>
<ion-select-option value="strawberry">Strawberry</ion-select-option>
<ion-select-option value="tangerine">Tangerine</ion-select-option>
<ion-select-option value="watermelon">Watermelon</ion-select-option>
</ion-select>
</ion-item>
</ion-list>
<ion-list>
<ion-list-header>
<ion-label>Multiple Value Select</ion-label>

View File

@@ -8,7 +8,7 @@ import type { E2ELocator } from '@utils/test/playwright';
* does not. The overlay rendering is already tested in the respective
* test files.
*/
configs({ directions: ['ltr'] }).forEach(({ title, config, screenshot }) => {
configs({ directions: ['ltr'] }).forEach(({ title, config }) => {
test.describe(title('select: basic'), () => {
test.beforeEach(async ({ page }) => {
await page.goto('/src/components/select/test/basic', config);
@@ -24,16 +24,6 @@ configs({ directions: ['ltr'] }).forEach(({ title, config, screenshot }) => {
await expect(page.locator('ion-alert')).toBeVisible();
});
test('it should scroll to selected option when opened', async ({ page }) => {
const ionAlertDidPresent = await page.spyOnEvent('ionAlertDidPresent');
await page.click('#alert-select-scroll-to-selected');
await ionAlertDidPresent.next();
const alert = page.locator('ion-alert');
await expect(alert).toHaveScreenshot(screenshot(`select-basic-alert-scroll-to-selected`));
});
});
test.describe('select: action sheet', () => {
@@ -46,16 +36,6 @@ configs({ directions: ['ltr'] }).forEach(({ title, config, screenshot }) => {
await expect(page.locator('ion-action-sheet')).toBeVisible();
});
test('it should scroll to selected option when opened', async ({ page }) => {
const ionActionSheetDidPresent = await page.spyOnEvent('ionActionSheetDidPresent');
await page.click('#action-sheet-select-scroll-to-selected');
await ionActionSheetDidPresent.next();
const actionSheet = page.locator('ion-action-sheet');
await expect(actionSheet).toHaveScreenshot(screenshot(`select-basic-action-sheet-scroll-to-selected`));
});
});
test.describe('select: popover', () => {
@@ -77,16 +57,6 @@ configs({ directions: ['ltr'] }).forEach(({ title, config, screenshot }) => {
await expect(popover).toBeVisible();
});
test('it should scroll to selected option when opened', async ({ page }) => {
const ionPopoverDidPresent = await page.spyOnEvent('ionPopoverDidPresent');
await page.click('#popover-select-scroll-to-selected');
await ionPopoverDidPresent.next();
const popover = page.locator('ion-popover');
await expect(popover).toHaveScreenshot(screenshot(`select-basic-popover-scroll-to-selected`));
});
});
test.describe('select: modal', () => {
@@ -105,16 +75,6 @@ configs({ directions: ['ltr'] }).forEach(({ title, config, screenshot }) => {
await expect(modal).toBeVisible();
});
test('it should scroll to selected option when opened', async ({ page }) => {
const ionModalDidPresent = await page.spyOnEvent('ionModalDidPresent');
await page.click('#modal-select-scroll-to-selected');
await ionModalDidPresent.next();
const modal = page.locator('ion-modal');
await expect(modal).toHaveScreenshot(screenshot(`select-basic-modal-scroll-to-selected`));
});
});
});
});

View File

@@ -11,12 +11,6 @@ describe('floating point utils', () => {
const n = getDecimalPlaces(5);
expect(n).toBe(0);
});
it('should handle nullish values', () => {
expect(getDecimalPlaces(undefined as any)).toBe(0);
expect(getDecimalPlaces(null as any)).toBe(0);
expect(getDecimalPlaces(NaN as any)).toBe(0);
});
});
describe('roundToMaxDecimalPlaces', () => {
@@ -24,11 +18,5 @@ describe('floating point utils', () => {
const n = roundToMaxDecimalPlaces(5.12345, 1.12, 2.123);
expect(n).toBe(5.123);
});
it('should handle nullish values', () => {
expect(roundToMaxDecimalPlaces(undefined as any)).toBe(0);
expect(roundToMaxDecimalPlaces(null as any)).toBe(0);
expect(roundToMaxDecimalPlaces(NaN as any)).toBe(0);
});
});
});

View File

@@ -1,7 +1,4 @@
import { isSafeNumber } from '@utils/helpers';
export function getDecimalPlaces(n: number) {
if (!isSafeNumber(n)) return 0;
if (n % 1 === 0) return 0;
return n.toString().split('.')[1].length;
}
@@ -39,7 +36,6 @@ export function getDecimalPlaces(n: number) {
* be used as a reference for the desired specificity.
*/
export function roundToMaxDecimalPlaces(n: number, ...references: number[]) {
if (!isSafeNumber(n)) return 0;
const maxPlaces = Math.max(...references.map((r) => getDecimalPlaces(r)));
return Number(n.toFixed(maxPlaces));
}

View File

@@ -424,10 +424,3 @@ export const getNextSiblingOfType = <T extends Element>(element: Element): T | n
}
return null;
};
/**
* Checks input for usable number. Not NaN and not Infinite.
*/
export const isSafeNumber = (input: unknown): input is number => {
return typeof input === 'number' && !isNaN(input) && isFinite(input);
};

View File

@@ -1,17 +1,9 @@
import type { CapacitorGlobal } from '@capacitor/core';
import { win } from '@utils/browser';
type CustomCapacitorGlobal = CapacitorGlobal & {
// Capacitor from @capacitor/core no longer exports Plugins, but we're pulling
// Capacitor from window.Capacitor, which does
Plugins: {
[key: string]: any;
};
};
export const getCapacitor = () => {
if (win !== undefined) {
return (win as any).Capacitor as CustomCapacitorGlobal;
return (win as any).Capacitor as CapacitorGlobal;
}
return undefined;
};