mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-11-10 00:27:41 +08:00
fix(radio,toggle,checkbox,select): padded space is clickable in items (#28136)
Issue number: Resolves #27169 --------- <!-- Please do not submit updates to dependencies unless it fixes an issue. --> <!-- Please try to limit your pull request to one type (bugfix, feature, etc). Submit multiple pull requests if needed. --> ## What is the current behavior? <!-- Please describe the current behavior that you are modifying. --> Clicking the padded space within an `ion-item` will not pass the click event to the slotted `ion-radio`, `ion-checkbox`, `ion-select` or `ion-toggle`. ## What is the new behavior? <!-- Please describe the behavior or changes that are being added by this PR. --> - The padded space at the start of `.item-native` and at the end of `.item-inner` is clickable to activate a control. - When the item is clicked, we check if the event is a result of clicking the control or clicking the item's padded space. If the click event is on the control, we don't need to do anything and let the default behavior occur. If the click event is on the padded space, we manually call the `.click()` method for the interactive element. - The cursor pointer displays when hovering over the padded space when a slotted interactive control is present. ## Does this introduce a breaking change? - [ ] Yes - [x] No <!-- If this introduces a breaking change, please describe the impact and migration path for existing applications below. --> ## Other information <!-- Any other information that is important to this PR such as screenshots of how the component looks before and after the change. -->
This commit is contained in:
@ -173,6 +173,13 @@
|
||||
}
|
||||
}
|
||||
|
||||
// Item: Interactive
|
||||
// --------------------------------------------------
|
||||
|
||||
:host(.item-has-interactive-control) {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
|
||||
// Item: Disabled
|
||||
// --------------------------------------------------
|
||||
@ -189,6 +196,7 @@
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Native Item
|
||||
// --------------------------------------------------
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import type { ComponentInterface } from '@stencil/core';
|
||||
import { Component, Element, Host, Listen, Prop, State, Watch, forceUpdate, h } from '@stencil/core';
|
||||
import { Build, Component, Element, Host, Listen, Prop, State, Watch, forceUpdate, h } from '@stencil/core';
|
||||
import type { AnchorInterface, ButtonInterface } from '@utils/element-interface';
|
||||
import type { Attributes } from '@utils/helpers';
|
||||
import { inheritAttributes, raf } from '@utils/helpers';
|
||||
@ -350,6 +350,22 @@ export class Item implements ComponentInterface, AnchorInterface, ButtonInterfac
|
||||
}
|
||||
}
|
||||
|
||||
private getFirstInteractive() {
|
||||
if (Build.isTesting) {
|
||||
/**
|
||||
* Pseudo selectors can't be tested in unit tests.
|
||||
* It will cause an error when running the tests.
|
||||
*
|
||||
* TODO: FW-5205 - Remove the build conditional when this is fixed in Stencil
|
||||
*/
|
||||
return undefined;
|
||||
}
|
||||
const controls = this.el.querySelectorAll<HTMLElement>(
|
||||
'ion-toggle:not([disabled]), ion-checkbox:not([disabled]), ion-radio:not([disabled]), ion-select:not([disabled])'
|
||||
);
|
||||
return controls[0];
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
counterString,
|
||||
@ -367,6 +383,7 @@ export class Item implements ComponentInterface, AnchorInterface, ButtonInterfac
|
||||
routerAnimation,
|
||||
routerDirection,
|
||||
inheritedAriaAttributes,
|
||||
multipleInputs,
|
||||
} = this;
|
||||
const childStyles = {} as StyleEventDetail;
|
||||
const mode = getIonMode(this);
|
||||
@ -383,15 +400,41 @@ export class Item implements ComponentInterface, AnchorInterface, ButtonInterfac
|
||||
rel,
|
||||
target,
|
||||
};
|
||||
|
||||
let clickFn = {};
|
||||
|
||||
const firstInteractive = this.getFirstInteractive();
|
||||
|
||||
// Only set onClick if the item is clickable to prevent screen
|
||||
// readers from reading all items as clickable
|
||||
const clickFn = clickable
|
||||
? {
|
||||
onClick: (ev: Event) => {
|
||||
if (clickable || (firstInteractive !== undefined && !multipleInputs)) {
|
||||
clickFn = {
|
||||
onClick: (ev: MouseEvent) => {
|
||||
if (clickable) {
|
||||
openURL(href, ev, routerDirection, routerAnimation);
|
||||
},
|
||||
}
|
||||
: {};
|
||||
}
|
||||
if (firstInteractive !== undefined && !multipleInputs) {
|
||||
const path = ev.composedPath();
|
||||
const target = path[0] as HTMLElement;
|
||||
if (ev.isTrusted) {
|
||||
/**
|
||||
* Dispatches a click event to the first interactive element,
|
||||
* when it is the result of a user clicking on the item.
|
||||
*
|
||||
* We check if the click target is in the shadow root,
|
||||
* which means the user clicked on the .item-native or
|
||||
* .item-inner padding.
|
||||
*/
|
||||
const clickedWithinShadowRoot = this.el.shadowRoot!.contains(target);
|
||||
if (clickedWithinShadowRoot) {
|
||||
firstInteractive.click();
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const showDetail = detail !== undefined ? detail : mode === 'ios' && clickable;
|
||||
this.itemStyles.forEach((value) => {
|
||||
Object.assign(childStyles, value);
|
||||
@ -413,6 +456,7 @@ export class Item implements ComponentInterface, AnchorInterface, ButtonInterfac
|
||||
[`item-lines-${lines}`]: lines !== undefined,
|
||||
[`item-fill-${fillValue}`]: true,
|
||||
[`item-shape-${shape}`]: shape !== undefined,
|
||||
'item-has-interactive-control': firstInteractive !== undefined,
|
||||
'item-disabled': disabled,
|
||||
'in-list': inList,
|
||||
'item-multiple-inputs': this.multipleInputs,
|
||||
|
||||
Reference in New Issue
Block a user