mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2026-03-13 10:22:08 +08:00
Compare commits
4 Commits
ld/icon-te
...
ld/datetim
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
726e4aeae7 | ||
|
|
eee2115fd2 | ||
|
|
d0057352fe | ||
|
|
c70432e693 |
14
core/package-lock.json
generated
14
core/package-lock.json
generated
@@ -9,7 +9,7 @@
|
||||
"version": "7.4.3",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@stencil/core": "^4.4.0",
|
||||
"@stencil/core": "^4.4.1",
|
||||
"ionicons": "7.1.0",
|
||||
"tslib": "^2.1.0"
|
||||
},
|
||||
@@ -1630,9 +1630,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@stencil/core": {
|
||||
"version": "4.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@stencil/core/-/core-4.4.0.tgz",
|
||||
"integrity": "sha512-YlLyCqGBsMEuZb3XTO/STT0TX9eSwjoVhCJgtjVfQOF+ebIMVlojTh40CmDveWiWbth687cbr6S2heeussV8Sg==",
|
||||
"version": "4.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@stencil/core/-/core-4.4.1.tgz",
|
||||
"integrity": "sha512-SirGcrb5yKHCn2BwdM7HGVXuvCdmwiXlVczEj8jJxQIm42CAUQCUECxtZidTzp+oZBZnWLnoAvfanchJsgkQzA==",
|
||||
"bin": {
|
||||
"stencil": "bin/stencil"
|
||||
},
|
||||
@@ -11536,9 +11536,9 @@
|
||||
"requires": {}
|
||||
},
|
||||
"@stencil/core": {
|
||||
"version": "4.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@stencil/core/-/core-4.4.0.tgz",
|
||||
"integrity": "sha512-YlLyCqGBsMEuZb3XTO/STT0TX9eSwjoVhCJgtjVfQOF+ebIMVlojTh40CmDveWiWbth687cbr6S2heeussV8Sg=="
|
||||
"version": "4.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@stencil/core/-/core-4.4.1.tgz",
|
||||
"integrity": "sha512-SirGcrb5yKHCn2BwdM7HGVXuvCdmwiXlVczEj8jJxQIm42CAUQCUECxtZidTzp+oZBZnWLnoAvfanchJsgkQzA=="
|
||||
},
|
||||
"@stencil/react-output-target": {
|
||||
"version": "0.5.3",
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
"loader/"
|
||||
],
|
||||
"dependencies": {
|
||||
"@stencil/core": "^4.4.0",
|
||||
"@stencil/core": "^4.4.1",
|
||||
"ionicons": "7.1.0",
|
||||
"tslib": "^2.1.0"
|
||||
},
|
||||
|
||||
@@ -211,6 +211,10 @@ export class Checkbox implements ComponentInterface {
|
||||
};
|
||||
|
||||
private onClick = (ev: MouseEvent) => {
|
||||
if (this.disabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.toggleChecked(ev);
|
||||
};
|
||||
|
||||
|
||||
24
core/src/components/checkbox/test/checkbox.spec.ts
Normal file
24
core/src/components/checkbox/test/checkbox.spec.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import { newSpecPage } from '@stencil/core/testing';
|
||||
|
||||
import { Checkbox } from '../checkbox';
|
||||
|
||||
describe('ion-checkbox: disabled', () => {
|
||||
it('clicking disabled checkbox should not toggle checked state', async () => {
|
||||
const page = await newSpecPage({
|
||||
components: [Checkbox],
|
||||
html: `
|
||||
<ion-checkbox disabled="true">Checkbox</ion-checkbox>
|
||||
`,
|
||||
});
|
||||
|
||||
const checkbox = page.body.querySelector('ion-checkbox');
|
||||
|
||||
expect(checkbox.checked).toBe(false);
|
||||
|
||||
checkbox.click();
|
||||
|
||||
await page.waitForChanges();
|
||||
|
||||
expect(checkbox.checked).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -414,9 +414,11 @@ export class PickerColumnInternal implements ComponentInterface {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { items, color, isActive, numericInput } = this;
|
||||
const { items, color, isActive, numericInput, value } = this;
|
||||
const mode = getIonMode(this);
|
||||
|
||||
const activeItem = items.find(item => item.value === value);
|
||||
|
||||
/**
|
||||
* exportparts is needed so ion-datetime can expose the parts
|
||||
* from two layers of shadow nesting. If this causes problems,
|
||||
@@ -425,6 +427,11 @@ export class PickerColumnInternal implements ComponentInterface {
|
||||
*/
|
||||
return (
|
||||
<Host
|
||||
role="spinbutton"
|
||||
aria-valuemin="0"
|
||||
aria-valuemax="0"
|
||||
aria-valuenow={activeItem ? activeItem.value : null}
|
||||
aria-valuetext={activeItem ? activeItem.text : null}
|
||||
exportparts={`${PICKER_ITEM_PART}, ${PICKER_ITEM_ACTIVE_PART}`}
|
||||
tabindex={0}
|
||||
class={createColorClasses(color, {
|
||||
|
||||
@@ -113,7 +113,7 @@ export class RadioGroup implements ComponentInterface {
|
||||
* using the `name` attribute.
|
||||
*/
|
||||
const selectedRadio = ev.target && (ev.target as HTMLElement).closest('ion-radio');
|
||||
if (selectedRadio) {
|
||||
if (selectedRadio && selectedRadio.disabled === false) {
|
||||
const currentValue = this.value;
|
||||
const newValue = selectedRadio.value;
|
||||
if (newValue !== currentValue) {
|
||||
|
||||
@@ -200,7 +200,11 @@ export class Radio implements ComponentInterface {
|
||||
};
|
||||
|
||||
private onClick = () => {
|
||||
const { radioGroup, checked } = this;
|
||||
const { radioGroup, checked, disabled } = this;
|
||||
|
||||
if (disabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* The legacy control uses a native input inside
|
||||
|
||||
@@ -31,3 +31,27 @@ describe('ion-radio', () => {
|
||||
expect(radio.classList.contains('radio-checked')).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('ion-radio: disabled', () => {
|
||||
it('clicking disabled radio should not set checked state', async () => {
|
||||
const page = await newSpecPage({
|
||||
components: [Radio, RadioGroup],
|
||||
html: `
|
||||
<ion-radio-group>
|
||||
<ion-radio disabled="true" value="a">Radio</ion-radio>
|
||||
</ion-radio-group>
|
||||
`,
|
||||
});
|
||||
|
||||
const radio = page.body.querySelector('ion-radio');
|
||||
const radioGroup = page.body.querySelector('ion-radio-group');
|
||||
|
||||
expect(radioGroup.value).toBe(undefined);
|
||||
|
||||
radio.click();
|
||||
|
||||
await page.waitForChanges();
|
||||
|
||||
expect(radioGroup.value).toBe(undefined);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -316,29 +316,46 @@ export class Select implements ComponentInterface {
|
||||
|
||||
// focus selected option for popovers
|
||||
if (this.interface === 'popover') {
|
||||
let indexOfSelected = this.childOpts.map((o) => o.value).indexOf(this.value);
|
||||
indexOfSelected = indexOfSelected > -1 ? indexOfSelected : 0; // default to first option if nothing selected
|
||||
const selectedItem = overlay.querySelector<HTMLElement>(
|
||||
`.select-interface-option:nth-child(${indexOfSelected + 1})`
|
||||
);
|
||||
const indexOfSelected = this.childOpts.map((o) => o.value).indexOf(this.value);
|
||||
|
||||
if (selectedItem) {
|
||||
focusElement(selectedItem);
|
||||
if (indexOfSelected > -1) {
|
||||
const selectedItem = overlay.querySelector<HTMLElement>(
|
||||
`.select-interface-option:nth-child(${indexOfSelected + 1})`
|
||||
);
|
||||
|
||||
if (selectedItem) {
|
||||
focusElement(selectedItem);
|
||||
|
||||
/**
|
||||
* Browsers such as Firefox do not
|
||||
* correctly delegate focus when manually
|
||||
* focusing an element with delegatesFocus.
|
||||
* We work around this by manually focusing
|
||||
* the interactive element.
|
||||
* ion-radio and ion-checkbox are the only
|
||||
* elements that ion-select-popover uses, so
|
||||
* we only need to worry about those two components
|
||||
* when focusing.
|
||||
*/
|
||||
const interactiveEl = selectedItem.querySelector<HTMLElement>('ion-radio, ion-checkbox');
|
||||
if (interactiveEl) {
|
||||
interactiveEl.focus();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
/**
|
||||
* Browsers such as Firefox do not
|
||||
* correctly delegate focus when manually
|
||||
* focusing an element with delegatesFocus.
|
||||
* We work around this by manually focusing
|
||||
* the interactive element.
|
||||
* ion-radio and ion-checkbox are the only
|
||||
* elements that ion-select-popover uses, so
|
||||
* we only need to worry about those two components
|
||||
* when focusing.
|
||||
* If no value is set then focus the first enabled option.
|
||||
*/
|
||||
const interactiveEl = selectedItem.querySelector<HTMLElement>('ion-radio, ion-checkbox');
|
||||
if (interactiveEl) {
|
||||
interactiveEl.focus();
|
||||
const firstEnabledOption = overlay.querySelector<HTMLElement>(
|
||||
'ion-radio:not(.radio-disabled), ion-checkbox:not(.checkbox-disabled)'
|
||||
);
|
||||
if (firstEnabledOption) {
|
||||
focusElement(firstEnabledOption.closest('ion-item')!);
|
||||
|
||||
/**
|
||||
* Focus the option for the same reason as we do above.
|
||||
*/
|
||||
firstEnabledOption.focus();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
36
core/src/components/select/test/disabled/select.e2e.ts
Normal file
36
core/src/components/select/test/disabled/select.e2e.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import { expect } from '@playwright/test';
|
||||
import { configs, test } from '@utils/test/playwright';
|
||||
|
||||
configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) => {
|
||||
test.describe(title('select: disabled options'), () => {
|
||||
test('should not focus a disabled option when no value is set', async ({ page, skip }) => {
|
||||
// TODO (FW-2979)
|
||||
skip.browser('webkit', 'Safari 16 only allows text fields and pop-up menus to be focused.');
|
||||
|
||||
test.info().annotations.push({
|
||||
type: 'issue',
|
||||
description: 'https://github.com/ionic-team/ionic-framework/issues/28284',
|
||||
});
|
||||
|
||||
await page.setContent(
|
||||
`
|
||||
<ion-select interface="popover">
|
||||
<ion-select-option value="a" disabled="true">A</ion-select-option>
|
||||
<ion-select-option value="b">B</ion-select-option>
|
||||
</ion-select>
|
||||
`,
|
||||
config
|
||||
);
|
||||
|
||||
const select = page.locator('ion-select');
|
||||
const popover = page.locator('ion-popover');
|
||||
const ionPopoverDidPresent = await page.spyOnEvent('ionPopoverDidPresent');
|
||||
|
||||
await select.click();
|
||||
await ionPopoverDidPresent.next();
|
||||
|
||||
const popoverOption = popover.locator('.select-interface-option:nth-of-type(2) ion-radio');
|
||||
await expect(popoverOption).toBeFocused();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -41,3 +41,24 @@ describe('toggle', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('ion-toggle: disabled', () => {
|
||||
it('clicking disabled toggle should not toggle checked state', async () => {
|
||||
const page = await newSpecPage({
|
||||
components: [Toggle],
|
||||
html: `
|
||||
<ion-toggle disabled="true">Toggle</ion-toggle>
|
||||
`,
|
||||
});
|
||||
|
||||
const toggle = page.body.querySelector('ion-toggle');
|
||||
|
||||
expect(toggle.checked).toBe(false);
|
||||
|
||||
toggle.click();
|
||||
|
||||
await page.waitForChanges();
|
||||
|
||||
expect(toggle.checked).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -259,6 +259,10 @@ export class Toggle implements ComponentInterface {
|
||||
}
|
||||
|
||||
private onClick = (ev: MouseEvent) => {
|
||||
if (this.disabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
ev.preventDefault();
|
||||
|
||||
if (this.lastDrag + 300 < Date.now()) {
|
||||
|
||||
Reference in New Issue
Block a user