mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-11-09 16:16:41 +08:00
fix(item): emit click event once when clicking padded space on item and emit correct element (#30373)
Issue number: resolves #29758 resolves #29761 --------- ## What is the current behavior? When an `ion-item` has a click event listener, the following issues occur: 1. **Double Click Events**: - Clicking the padding around interactive elements (`ion-checkbox`, `ion-toggle`, `ion-radio`, `ion-textarea`, `ion-input`) triggers the click event twice. 2. **Incorrect Event Targets**: - For `ion-input` and `ion-textarea`, clicking their native inputs reports the wrong element as the event target. - Clicking the padding within the `native-wrapper` of `ion-input` emits a separate click event with an incorrect target element. ## What is the new behavior? - Fires `firstInteractive.click()` in Item for all interactives (no longer excludes input/textarea). - Stops immediate propagation in item when the click event is in the padding of an item, preventing two click events from firing. - Updates input and textarea to always emit from their host elements `ion-input`/`ion-textarea` instead of the native input elements. - Updates input to make the native input take up 100% height. This is necessary to avoid the `native-wrapper` triggering its own click event when clicking on its padding. - Adds e2e tests to check for the above behavior to avoid future regressions. ## Does this introduce a breaking change? - [ ] Yes - [x] No ## Other information **Dev build**: `8.5.6-dev.11745613928.16440384` **Previews**: - [Checkbox Preview](https://ionic-framework-git-fw-6503-ionic1.vercel.app/src/components/checkbox/test/item) - [Input Preview](https://ionic-framework-git-fw-6503-ionic1.vercel.app/src/components/input/test/item) - [Radio Preview](https://ionic-framework-git-fw-6503-ionic1.vercel.app/src/components/radio/test/item) - [Select Preview](https://ionic-framework-git-fw-6503-ionic1.vercel.app/src/components/select/test/item) - [Textarea Preview](https://ionic-framework-git-fw-6503-ionic1.vercel.app/src/components/textarea/test/item) - [Toggle Preview](https://ionic-framework-git-fw-6503-ionic1.vercel.app/src/components/toggle/test/item) --------- Co-authored-by: Brandy Smith <6577830+brandyscarney@users.noreply.github.com>
This commit is contained in:
@ -107,6 +107,10 @@
|
||||
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
|
||||
// Ensure the input fills the full height of the native wrapper.
|
||||
// This prevents the wrapper from being the click event target.
|
||||
height: 100%;
|
||||
max-height: 100%;
|
||||
|
||||
border: 0;
|
||||
|
||||
@ -1,5 +1,18 @@
|
||||
import type { ComponentInterface, EventEmitter } from '@stencil/core';
|
||||
import { Build, Component, Element, Event, Host, Method, Prop, State, Watch, forceUpdate, h } from '@stencil/core';
|
||||
import {
|
||||
Build,
|
||||
Component,
|
||||
Element,
|
||||
Event,
|
||||
Host,
|
||||
Listen,
|
||||
Method,
|
||||
Prop,
|
||||
State,
|
||||
Watch,
|
||||
forceUpdate,
|
||||
h,
|
||||
} from '@stencil/core';
|
||||
import type { NotchController } from '@utils/forms';
|
||||
import { createNotchController } from '@utils/forms';
|
||||
import type { Attributes } from '@utils/helpers';
|
||||
@ -363,6 +376,19 @@ export class Input implements ComponentInterface {
|
||||
forceUpdate(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* This prevents the native input from emitting the click event.
|
||||
* Instead, the click event from the ion-input is emitted.
|
||||
*/
|
||||
@Listen('click', { capture: true })
|
||||
onClickCapture(ev: Event) {
|
||||
const nativeInput = this.nativeInput;
|
||||
if (nativeInput && ev.target === nativeInput) {
|
||||
ev.stopPropagation();
|
||||
this.el.click();
|
||||
}
|
||||
}
|
||||
|
||||
componentWillLoad() {
|
||||
this.inheritedAttributes = {
|
||||
...inheritAriaAttributes(this.el),
|
||||
|
||||
@ -49,6 +49,11 @@ configs().forEach(({ title, screenshot, config }) => {
|
||||
configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) => {
|
||||
test.describe(title('input: item functionality'), () => {
|
||||
test('clicking padded space within item should focus the input', async ({ page }) => {
|
||||
test.info().annotations.push({
|
||||
type: 'issue',
|
||||
description: 'https://github.com/ionic-team/ionic-framework/issues/21982',
|
||||
});
|
||||
|
||||
await page.setContent(
|
||||
`
|
||||
<ion-item>
|
||||
@ -57,11 +62,12 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
|
||||
`,
|
||||
config
|
||||
);
|
||||
const itemNative = page.locator('.item-native');
|
||||
|
||||
const item = page.locator('ion-item');
|
||||
const input = page.locator('ion-input input');
|
||||
|
||||
// Clicks the padded space within the item
|
||||
await itemNative.click({
|
||||
await item.click({
|
||||
position: {
|
||||
x: 5,
|
||||
y: 5,
|
||||
@ -70,5 +76,86 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
|
||||
|
||||
await expect(input).toBeFocused();
|
||||
});
|
||||
|
||||
test('clicking padded space within item should fire one click event', async ({ page }) => {
|
||||
test.info().annotations.push({
|
||||
type: 'issue',
|
||||
description: 'https://github.com/ionic-team/ionic-framework/issues/29761',
|
||||
});
|
||||
|
||||
await page.setContent(
|
||||
`
|
||||
<ion-item>
|
||||
<ion-input label="Input"></ion-input>
|
||||
</ion-item>
|
||||
`,
|
||||
config
|
||||
);
|
||||
|
||||
const item = page.locator('ion-item');
|
||||
const onClick = await page.spyOnEvent('click');
|
||||
|
||||
// Click the padding area (5px from left edge)
|
||||
await item.click({
|
||||
position: {
|
||||
x: 5,
|
||||
y: 5,
|
||||
},
|
||||
});
|
||||
|
||||
expect(onClick).toHaveReceivedEventTimes(1);
|
||||
|
||||
// Verify that the event target is the input and not the item
|
||||
const event = onClick.events[0];
|
||||
expect((event.target as HTMLElement).tagName.toLowerCase()).toBe('ion-input');
|
||||
});
|
||||
|
||||
test('clicking native wrapper should fire one click event', async ({ page }) => {
|
||||
await page.setContent(
|
||||
`
|
||||
<ion-item>
|
||||
<ion-input label="Input"></ion-input>
|
||||
</ion-item>
|
||||
`,
|
||||
config
|
||||
);
|
||||
|
||||
const nativeWrapper = page.locator('.native-wrapper');
|
||||
const onClick = await page.spyOnEvent('click');
|
||||
|
||||
await nativeWrapper.click({
|
||||
position: {
|
||||
x: 5,
|
||||
y: 5,
|
||||
},
|
||||
});
|
||||
|
||||
expect(onClick).toHaveReceivedEventTimes(1);
|
||||
|
||||
// Verify that the event target is the input and not the native wrapper
|
||||
const event = onClick.events[0];
|
||||
expect((event.target as HTMLElement).tagName.toLowerCase()).toBe('ion-input');
|
||||
});
|
||||
|
||||
test('clicking native input within item should fire click event with target as ion-input', async ({ page }) => {
|
||||
await page.setContent(
|
||||
`
|
||||
<ion-item>
|
||||
<ion-input label="Input"></ion-input>
|
||||
</ion-item>
|
||||
`,
|
||||
config
|
||||
);
|
||||
|
||||
const nativeInput = page.locator('.native-input');
|
||||
const onClick = await page.spyOnEvent('click');
|
||||
|
||||
await nativeInput.click();
|
||||
expect(onClick).toHaveReceivedEventTimes(1);
|
||||
|
||||
// Verify that the event target is the ion-input and not the native input
|
||||
const event = onClick.events[0];
|
||||
expect((event.target as HTMLElement).tagName.toLowerCase()).toBe('ion-input');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user