chore(git): update feature-8.5 from main (#30238)
2
.github/workflows/assign-issues.yml
vendored
@ -13,6 +13,6 @@ jobs:
|
|||||||
- name: 'Auto-assign issue'
|
- name: 'Auto-assign issue'
|
||||||
uses: pozil/auto-assign-issue@39c06395cbac76e79afc4ad4e5c5c6db6ecfdd2e # v2.2.0
|
uses: pozil/auto-assign-issue@39c06395cbac76e79afc4ad4e5c5c6db6ecfdd2e # v2.2.0
|
||||||
with:
|
with:
|
||||||
assignees: brandyscarney, thetaPC, joselrio, rugoncalves, BenOsodrac, JoaoFerreira-FrontEnd, OS-giulianasilva, tanner-reits
|
assignees: brandyscarney, thetaPC, ShaneK
|
||||||
numOfAssignee: 1
|
numOfAssignee: 1
|
||||||
allowSelfAssign: false
|
allowSelfAssign: false
|
||||||
|
66
core/package-lock.json
generated
@ -15,10 +15,10 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@axe-core/playwright": "^4.10.0",
|
"@axe-core/playwright": "^4.10.0",
|
||||||
"@capacitor/core": "^6.0.0",
|
"@capacitor/core": "^7.0.0",
|
||||||
"@capacitor/haptics": "^6.0.0",
|
"@capacitor/haptics": "^7.0.0",
|
||||||
"@capacitor/keyboard": "^6.0.0",
|
"@capacitor/keyboard": "^7.0.0",
|
||||||
"@capacitor/status-bar": "^6.0.0",
|
"@capacitor/status-bar": "^7.0.0",
|
||||||
"@clack/prompts": "^0.10.0",
|
"@clack/prompts": "^0.10.0",
|
||||||
"@ionic/eslint-config": "^0.3.0",
|
"@ionic/eslint-config": "^0.3.0",
|
||||||
"@ionic/prettier-config": "^2.0.0",
|
"@ionic/prettier-config": "^2.0.0",
|
||||||
@ -663,39 +663,43 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/@capacitor/core": {
|
"node_modules/@capacitor/core": {
|
||||||
"version": "6.2.0",
|
"version": "7.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/@capacitor/core/-/core-6.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/@capacitor/core/-/core-7.0.1.tgz",
|
||||||
"integrity": "sha512-B9IlJtDpUqhhYb+T8+cp2Db/3RETX36STgjeU2kQZBs/SLAcFiMama227o+msRjLeo3DO+7HJjWVA1+XlyyPEg==",
|
"integrity": "sha512-1Ob9bvA/p8g8aNwK6VesxEekGXowLVf6APjkW4LRnr05H+7z/bke+Q5pn9zqh/GgTbIxAQ/rwZrAZvvxkdm1UA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"tslib": "^2.1.0"
|
"tslib": "^2.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@capacitor/haptics": {
|
"node_modules/@capacitor/haptics": {
|
||||||
"version": "6.0.2",
|
"version": "7.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/@capacitor/haptics/-/haptics-6.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/@capacitor/haptics/-/haptics-7.0.0.tgz",
|
||||||
"integrity": "sha512-xcFdIH4iIIeW2+1lzmlYMVicqB9ytaiuZ9NE3a9laKFPvMGC7hdj6i6tHFezwPJ/96xkHOwXT2b0F8Mh9xtTWg==",
|
"integrity": "sha512-8uI8rWyAbq8EzkjS+sHZSncyzujHzVbuLKgj8J5H0yUL6+r26F16gVA2iuQuIBvzbSMy7Y0/pUuWlwZr/H8AKg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@capacitor/core": "^6.0.0"
|
"@capacitor/core": ">=7.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@capacitor/keyboard": {
|
"node_modules/@capacitor/keyboard": {
|
||||||
"version": "6.0.3",
|
"version": "7.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/@capacitor/keyboard/-/keyboard-6.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/@capacitor/keyboard/-/keyboard-7.0.0.tgz",
|
||||||
"integrity": "sha512-V/mURxBI68HvClYjrGBlOriWkwYN7r+cWid/igJz/3scNc/V81DgQ9fpoLr4W0I5NY7YxOesjIJLuLO+LT18mQ==",
|
"integrity": "sha512-Tqwy8wG+sx4UqiFCX4Q+bFw6uKgG7BiHKAPpeefoIgoEB8H8Jf3xZNZoVPnJIMuPsCdSvuyHXZbJXH9IEEirGA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@capacitor/core": "^6.0.0"
|
"@capacitor/core": ">=7.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@capacitor/status-bar": {
|
"node_modules/@capacitor/status-bar": {
|
||||||
"version": "6.0.2",
|
"version": "7.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/@capacitor/status-bar/-/status-bar-6.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/@capacitor/status-bar/-/status-bar-7.0.0.tgz",
|
||||||
"integrity": "sha512-AmRIX6QvFemItlY7/69ARkIAqitRQqJ2qwgZmD1KqgFb78pH+XFXm1guvS/a8CuOOm/IqZ4ddDbl20yxtBqzGA==",
|
"integrity": "sha512-wsvPkWkoSOXMIgVHu4c6P1sOuDSZ1ClUo5OpLRwj7u8DYzlV4jlmNzztQn2Lvsiqx1z4kfukSaqe40k1Lo4c9g==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@capacitor/core": "^6.0.0"
|
"@capacitor/core": ">=7.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@clack/core": {
|
"node_modules/@clack/core": {
|
||||||
@ -10977,32 +10981,32 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"@capacitor/core": {
|
"@capacitor/core": {
|
||||||
"version": "6.2.0",
|
"version": "7.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/@capacitor/core/-/core-6.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/@capacitor/core/-/core-7.0.1.tgz",
|
||||||
"integrity": "sha512-B9IlJtDpUqhhYb+T8+cp2Db/3RETX36STgjeU2kQZBs/SLAcFiMama227o+msRjLeo3DO+7HJjWVA1+XlyyPEg==",
|
"integrity": "sha512-1Ob9bvA/p8g8aNwK6VesxEekGXowLVf6APjkW4LRnr05H+7z/bke+Q5pn9zqh/GgTbIxAQ/rwZrAZvvxkdm1UA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"tslib": "^2.1.0"
|
"tslib": "^2.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@capacitor/haptics": {
|
"@capacitor/haptics": {
|
||||||
"version": "6.0.2",
|
"version": "7.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/@capacitor/haptics/-/haptics-6.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/@capacitor/haptics/-/haptics-7.0.0.tgz",
|
||||||
"integrity": "sha512-xcFdIH4iIIeW2+1lzmlYMVicqB9ytaiuZ9NE3a9laKFPvMGC7hdj6i6tHFezwPJ/96xkHOwXT2b0F8Mh9xtTWg==",
|
"integrity": "sha512-8uI8rWyAbq8EzkjS+sHZSncyzujHzVbuLKgj8J5H0yUL6+r26F16gVA2iuQuIBvzbSMy7Y0/pUuWlwZr/H8AKg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {}
|
"requires": {}
|
||||||
},
|
},
|
||||||
"@capacitor/keyboard": {
|
"@capacitor/keyboard": {
|
||||||
"version": "6.0.3",
|
"version": "7.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/@capacitor/keyboard/-/keyboard-6.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/@capacitor/keyboard/-/keyboard-7.0.0.tgz",
|
||||||
"integrity": "sha512-V/mURxBI68HvClYjrGBlOriWkwYN7r+cWid/igJz/3scNc/V81DgQ9fpoLr4W0I5NY7YxOesjIJLuLO+LT18mQ==",
|
"integrity": "sha512-Tqwy8wG+sx4UqiFCX4Q+bFw6uKgG7BiHKAPpeefoIgoEB8H8Jf3xZNZoVPnJIMuPsCdSvuyHXZbJXH9IEEirGA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {}
|
"requires": {}
|
||||||
},
|
},
|
||||||
"@capacitor/status-bar": {
|
"@capacitor/status-bar": {
|
||||||
"version": "6.0.2",
|
"version": "7.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/@capacitor/status-bar/-/status-bar-6.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/@capacitor/status-bar/-/status-bar-7.0.0.tgz",
|
||||||
"integrity": "sha512-AmRIX6QvFemItlY7/69ARkIAqitRQqJ2qwgZmD1KqgFb78pH+XFXm1guvS/a8CuOOm/IqZ4ddDbl20yxtBqzGA==",
|
"integrity": "sha512-wsvPkWkoSOXMIgVHu4c6P1sOuDSZ1ClUo5OpLRwj7u8DYzlV4jlmNzztQn2Lvsiqx1z4kfukSaqe40k1Lo4c9g==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {}
|
"requires": {}
|
||||||
},
|
},
|
||||||
|
@ -37,10 +37,10 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@axe-core/playwright": "^4.10.0",
|
"@axe-core/playwright": "^4.10.0",
|
||||||
"@capacitor/core": "^6.0.0",
|
"@capacitor/core": "^7.0.0",
|
||||||
"@capacitor/haptics": "^6.0.0",
|
"@capacitor/haptics": "^7.0.0",
|
||||||
"@capacitor/keyboard": "^6.0.0",
|
"@capacitor/keyboard": "^7.0.0",
|
||||||
"@capacitor/status-bar": "^6.0.0",
|
"@capacitor/status-bar": "^7.0.0",
|
||||||
"@clack/prompts": "^0.10.0",
|
"@clack/prompts": "^0.10.0",
|
||||||
"@ionic/eslint-config": "^0.3.0",
|
"@ionic/eslint-config": "^0.3.0",
|
||||||
"@ionic/prettier-config": "^2.0.0",
|
"@ionic/prettier-config": "^2.0.0",
|
||||||
|
@ -237,6 +237,18 @@ export class Alert implements ComponentInterface, OverlayInterface {
|
|||||||
return;
|
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
|
// 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
|
// ignore the keydown event if it is not on a radio button
|
||||||
if (
|
if (
|
||||||
@ -400,7 +412,19 @@ export class Alert implements ComponentInterface, OverlayInterface {
|
|||||||
|
|
||||||
await this.delegateController.attachViewToDom();
|
await this.delegateController.attachViewToDom();
|
||||||
|
|
||||||
await present(this, 'alertEnter', iosEnterAnimation, mdEnterAnimation);
|
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();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
unlock();
|
unlock();
|
||||||
}
|
}
|
||||||
@ -725,8 +749,8 @@ export class Alert implements ComponentInterface, OverlayInterface {
|
|||||||
const { overlayIndex, header, subHeader, message, htmlAttributes } = this;
|
const { overlayIndex, header, subHeader, message, htmlAttributes } = this;
|
||||||
const mode = getIonMode(this);
|
const mode = getIonMode(this);
|
||||||
const hdrId = `alert-${overlayIndex}-hdr`;
|
const hdrId = `alert-${overlayIndex}-hdr`;
|
||||||
const subHdrId = `alert-${overlayIndex}-sub-hdr`;
|
|
||||||
const msgId = `alert-${overlayIndex}-msg`;
|
const msgId = `alert-${overlayIndex}-msg`;
|
||||||
|
const subHdrId = `alert-${overlayIndex}-sub-hdr`;
|
||||||
const role = this.inputs.length > 0 || this.buttons.length > 0 ? 'alertdialog' : 'alert';
|
const role = this.inputs.length > 0 || this.buttons.length > 0 ? 'alertdialog' : 'alert';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -739,12 +763,7 @@ export class Alert implements ComponentInterface, OverlayInterface {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Host
|
<Host
|
||||||
role={role}
|
|
||||||
aria-modal="true"
|
|
||||||
aria-labelledby={ariaLabelledBy}
|
|
||||||
aria-describedby={message !== undefined ? msgId : null}
|
|
||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
{...(htmlAttributes as any)}
|
|
||||||
style={{
|
style={{
|
||||||
zIndex: `${20000 + overlayIndex}`,
|
zIndex: `${20000 + overlayIndex}`,
|
||||||
}}
|
}}
|
||||||
@ -761,7 +780,16 @@ export class Alert implements ComponentInterface, OverlayInterface {
|
|||||||
|
|
||||||
<div tabindex="0" aria-hidden="true"></div>
|
<div tabindex="0" aria-hidden="true"></div>
|
||||||
|
|
||||||
<div class="alert-wrapper ion-overlay-wrapper" ref={(el) => (this.wrapperEl = el)}>
|
<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-head">
|
<div class="alert-head">
|
||||||
{header && (
|
{header && (
|
||||||
<h2 id={hdrId} class="alert-title">
|
<h2 id={hdrId} class="alert-title">
|
||||||
|
@ -16,6 +16,7 @@ const testAria = async (
|
|||||||
await didPresent.next();
|
await didPresent.next();
|
||||||
|
|
||||||
const alert = page.locator('ion-alert');
|
const alert = page.locator('ion-alert');
|
||||||
|
const alertwrapper = alert.locator('.alert-wrapper');
|
||||||
|
|
||||||
const header = alert.locator('.alert-title');
|
const header = alert.locator('.alert-title');
|
||||||
const subHeader = alert.locator('.alert-sub-title');
|
const subHeader = alert.locator('.alert-sub-title');
|
||||||
@ -42,8 +43,8 @@ const testAria = async (
|
|||||||
* expect().toHaveAttribute() can't check for a null value, so grab and check
|
* expect().toHaveAttribute() can't check for a null value, so grab and check
|
||||||
* the values manually instead.
|
* the values manually instead.
|
||||||
*/
|
*/
|
||||||
const ariaLabelledBy = await alert.getAttribute('aria-labelledby');
|
const ariaLabelledBy = await alertwrapper.getAttribute('aria-labelledby');
|
||||||
const ariaDescribedBy = await alert.getAttribute('aria-describedby');
|
const ariaDescribedBy = await alertwrapper.getAttribute('aria-describedby');
|
||||||
|
|
||||||
expect(ariaLabelledBy).toBe(expectedAriaLabelledBy);
|
expect(ariaLabelledBy).toBe(expectedAriaLabelledBy);
|
||||||
expect(ariaDescribedBy).toBe(expectedAriaDescribedBy);
|
expect(ariaDescribedBy).toBe(expectedAriaDescribedBy);
|
||||||
|
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 35 KiB |
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 44 KiB |
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 34 KiB |
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 32 KiB |
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 42 KiB |
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 32 KiB |
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 22 KiB |
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 27 KiB |
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 36 KiB |
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 27 KiB |
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 34 KiB |
Before Width: | Height: | Size: 43 KiB After Width: | Height: | Size: 44 KiB |
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 33 KiB |
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 21 KiB |
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 24 KiB |
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 17 KiB |
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 30 KiB |
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 19 KiB |
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 32 KiB |
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 23 KiB |
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 35 KiB |
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 24 KiB |
@ -1,7 +1,7 @@
|
|||||||
|
import { h } from '@stencil/core';
|
||||||
import { newSpecPage } from '@stencil/core/testing';
|
import { newSpecPage } from '@stencil/core/testing';
|
||||||
|
|
||||||
import { Alert } from '../alert';
|
import { Alert } from '../alert';
|
||||||
import { h } from '@stencil/core';
|
|
||||||
|
|
||||||
describe('alert: id', () => {
|
describe('alert: id', () => {
|
||||||
it('alert should be assigned an incrementing id', async () => {
|
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>,
|
template: () => <ion-alert htmlAttributes={{ id }} overlayIndex={-1}></ion-alert>,
|
||||||
});
|
});
|
||||||
|
|
||||||
const alert = page.body.querySelector('ion-alert')!;
|
const alertwrapper = page.body.querySelector('.alert-wrapper')!;
|
||||||
expect(alert.id).toBe(id);
|
expect(alertwrapper.id).toBe(id);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -19,6 +19,7 @@ configs({ directions: ['ltr'] }).forEach(({ config, screenshot, title }) => {
|
|||||||
await page.keyboard.press(tabKey);
|
await page.keyboard.press(tabKey);
|
||||||
await expect(alertBtns.nth(0)).toBeFocused();
|
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 page.keyboard.press(`Shift+${tabKey}`);
|
||||||
await expect(alertBtns.nth(2)).toBeFocused();
|
await expect(alertBtns.nth(2)).toBeFocused();
|
||||||
|
|
||||||
@ -30,7 +31,7 @@ configs({ directions: ['ltr'] }).forEach(({ config, screenshot, title }) => {
|
|||||||
const alertFixture = new AlertFixture(page, screenshot);
|
const alertFixture = new AlertFixture(page, screenshot);
|
||||||
|
|
||||||
const alert = await alertFixture.open('#basic');
|
const alert = await alertFixture.open('#basic');
|
||||||
await expect(alert).toHaveAttribute('data-testid', 'basic-alert');
|
await expect(alert.locator('.alert-wrapper')).toHaveAttribute('data-testid', 'basic-alert');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should dismiss when async handler resolves', async ({ page }) => {
|
test('should dismiss when async handler resolves', async ({ page }) => {
|
||||||
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 9.4 KiB After Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 9.4 KiB After Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 25 KiB |
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 19 KiB |
Before Width: | Height: | Size: 9.8 KiB After Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 25 KiB |
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 19 KiB |
Before Width: | Height: | Size: 9.8 KiB After Width: | Height: | Size: 10 KiB |
@ -2,7 +2,7 @@ import type { ComponentInterface, EventEmitter } from '@stencil/core';
|
|||||||
import { Component, Element, Event, Host, Prop, State, Watch, h } from '@stencil/core';
|
import { Component, Element, Event, Host, Prop, State, Watch, h } from '@stencil/core';
|
||||||
import { findClosestIonContent, disableContentScrollY, resetContentScrollY } from '@utils/content';
|
import { findClosestIonContent, disableContentScrollY, resetContentScrollY } from '@utils/content';
|
||||||
import type { Attributes } from '@utils/helpers';
|
import type { Attributes } from '@utils/helpers';
|
||||||
import { inheritAriaAttributes, clamp, debounceEvent, renderHiddenInput } from '@utils/helpers';
|
import { inheritAriaAttributes, clamp, debounceEvent, renderHiddenInput, isSafeNumber } from '@utils/helpers';
|
||||||
import { printIonWarning } from '@utils/logging';
|
import { printIonWarning } from '@utils/logging';
|
||||||
import { isRTL } from '@utils/rtl';
|
import { isRTL } from '@utils/rtl';
|
||||||
import { createColorClasses, hostContext } from '@utils/theme';
|
import { createColorClasses, hostContext } from '@utils/theme';
|
||||||
@ -109,7 +109,11 @@ export class Range implements ComponentInterface {
|
|||||||
*/
|
*/
|
||||||
@Prop() min = 0;
|
@Prop() min = 0;
|
||||||
@Watch('min')
|
@Watch('min')
|
||||||
protected minChanged() {
|
protected minChanged(newValue: number) {
|
||||||
|
if (!isSafeNumber(newValue)) {
|
||||||
|
this.min = 0;
|
||||||
|
}
|
||||||
|
|
||||||
if (!this.noUpdate) {
|
if (!this.noUpdate) {
|
||||||
this.updateRatio();
|
this.updateRatio();
|
||||||
}
|
}
|
||||||
@ -120,7 +124,11 @@ export class Range implements ComponentInterface {
|
|||||||
*/
|
*/
|
||||||
@Prop() max = 100;
|
@Prop() max = 100;
|
||||||
@Watch('max')
|
@Watch('max')
|
||||||
protected maxChanged() {
|
protected maxChanged(newValue: number) {
|
||||||
|
if (!isSafeNumber(newValue)) {
|
||||||
|
this.max = 100;
|
||||||
|
}
|
||||||
|
|
||||||
if (!this.noUpdate) {
|
if (!this.noUpdate) {
|
||||||
this.updateRatio();
|
this.updateRatio();
|
||||||
}
|
}
|
||||||
@ -151,6 +159,12 @@ export class Range implements ComponentInterface {
|
|||||||
* Specifies the value granularity.
|
* Specifies the value granularity.
|
||||||
*/
|
*/
|
||||||
@Prop() step = 1;
|
@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.
|
* If `true`, tick marks are displayed based on the step value.
|
||||||
@ -300,6 +314,11 @@ export class Range implements ComponentInterface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.inheritedAttributes = inheritAriaAttributes(this.el);
|
this.inheritedAttributes = inheritAriaAttributes(this.el);
|
||||||
|
// If min, max, or step are not safe, set them to 0, 100, and 1, respectively.
|
||||||
|
// Each 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() {
|
componentDidLoad() {
|
||||||
|
@ -28,6 +28,25 @@ 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', () => {
|
it('should return the clamped value for a range dual knob component', () => {
|
||||||
sharedRange.min = 0;
|
sharedRange.min = 0;
|
||||||
sharedRange.max = 100;
|
sharedRange.max = 100;
|
||||||
|
@ -145,6 +145,7 @@ export class Toggle implements ComponentInterface {
|
|||||||
const isNowChecked = !checked;
|
const isNowChecked = !checked;
|
||||||
this.checked = isNowChecked;
|
this.checked = isNowChecked;
|
||||||
|
|
||||||
|
this.setFocus();
|
||||||
this.ionChange.emit({
|
this.ionChange.emit({
|
||||||
checked: isNowChecked,
|
checked: isNowChecked,
|
||||||
value,
|
value,
|
||||||
|
@ -11,6 +11,12 @@ describe('floating point utils', () => {
|
|||||||
const n = getDecimalPlaces(5);
|
const n = getDecimalPlaces(5);
|
||||||
expect(n).toBe(0);
|
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', () => {
|
describe('roundToMaxDecimalPlaces', () => {
|
||||||
@ -18,5 +24,11 @@ describe('floating point utils', () => {
|
|||||||
const n = roundToMaxDecimalPlaces(5.12345, 1.12, 2.123);
|
const n = roundToMaxDecimalPlaces(5.12345, 1.12, 2.123);
|
||||||
expect(n).toBe(5.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);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -1,4 +1,7 @@
|
|||||||
|
import { isSafeNumber } from '@utils/helpers';
|
||||||
|
|
||||||
export function getDecimalPlaces(n: number) {
|
export function getDecimalPlaces(n: number) {
|
||||||
|
if (!isSafeNumber(n)) return 0;
|
||||||
if (n % 1 === 0) return 0;
|
if (n % 1 === 0) return 0;
|
||||||
return n.toString().split('.')[1].length;
|
return n.toString().split('.')[1].length;
|
||||||
}
|
}
|
||||||
@ -36,6 +39,7 @@ export function getDecimalPlaces(n: number) {
|
|||||||
* be used as a reference for the desired specificity.
|
* be used as a reference for the desired specificity.
|
||||||
*/
|
*/
|
||||||
export function roundToMaxDecimalPlaces(n: number, ...references: number[]) {
|
export function roundToMaxDecimalPlaces(n: number, ...references: number[]) {
|
||||||
|
if (!isSafeNumber(n)) return 0;
|
||||||
const maxPlaces = Math.max(...references.map((r) => getDecimalPlaces(r)));
|
const maxPlaces = Math.max(...references.map((r) => getDecimalPlaces(r)));
|
||||||
return Number(n.toFixed(maxPlaces));
|
return Number(n.toFixed(maxPlaces));
|
||||||
}
|
}
|
||||||
|
@ -424,3 +424,10 @@ export const getNextSiblingOfType = <T extends Element>(element: Element): T | n
|
|||||||
}
|
}
|
||||||
return null;
|
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);
|
||||||
|
};
|
||||||
|
@ -1,9 +1,17 @@
|
|||||||
import type { CapacitorGlobal } from '@capacitor/core';
|
import type { CapacitorGlobal } from '@capacitor/core';
|
||||||
import { win } from '@utils/browser';
|
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 = () => {
|
export const getCapacitor = () => {
|
||||||
if (win !== undefined) {
|
if (win !== undefined) {
|
||||||
return (win as any).Capacitor as CapacitorGlobal;
|
return (win as any).Capacitor as CustomCapacitorGlobal;
|
||||||
}
|
}
|
||||||
return undefined;
|
return undefined;
|
||||||
};
|
};
|
||||||
|
@ -99,7 +99,8 @@ const isCordova = (win: any): boolean => !!(win['cordova'] || win['phonegap'] ||
|
|||||||
|
|
||||||
const isCapacitorNative = (win: any): boolean => {
|
const isCapacitorNative = (win: any): boolean => {
|
||||||
const capacitor = win['Capacitor'];
|
const capacitor = win['Capacitor'];
|
||||||
return !!capacitor?.isNative;
|
// TODO(ROU-11693): Remove when we no longer support Capacitor 2, which does not have isNativePlatform
|
||||||
|
return !!(capacitor?.isNative || (capacitor?.isNativePlatform && !!capacitor.isNativePlatform()));
|
||||||
};
|
};
|
||||||
|
|
||||||
const isElectron = (win: Window): boolean => testUserAgent(win, /electron/i);
|
const isElectron = (win: Window): boolean => testUserAgent(win, /electron/i);
|
||||||
|
@ -29,7 +29,7 @@ export const PlatformConfiguration = {
|
|||||||
},
|
},
|
||||||
Capacitor: {
|
Capacitor: {
|
||||||
Capacitor: {
|
Capacitor: {
|
||||||
isNative: true,
|
isNativePlatform: () => true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PWA: {
|
PWA: {
|
||||||
|