Skip to content

Commit a8cbad0

Browse files
merge release-8.5.7 (#30397)
v8.5.7
2 parents d21c329 + 54bc6cc commit a8cbad0

36 files changed

+449
-94
lines changed

CHANGELOG.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,17 @@
33
All notable changes to this project will be documented in this file.
44
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
55

6+
## [8.5.7](https://github.com/ionic-team/ionic-framework/compare/v8.5.6...v8.5.7) (2025-05-07)
7+
8+
9+
### Bug Fixes
10+
11+
* **labels:** prevent clicking a label from triggering onClick twice on several components ([#30384](https://github.com/ionic-team/ionic-framework/issues/30384)) ([7d639b0](https://github.com/ionic-team/ionic-framework/commit/7d639b0412120523f758942c855cb69f9a52e9d9)), closes [#30165](https://github.com/ionic-team/ionic-framework/issues/30165)
12+
13+
14+
15+
16+
617
## [8.5.6](https://github.com/ionic-team/ionic-framework/compare/v8.5.5...v8.5.6) (2025-04-30)
718

819

core/CHANGELOG.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,17 @@
33
All notable changes to this project will be documented in this file.
44
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
55

6+
## [8.5.7](https://github.com/ionic-team/ionic-framework/compare/v8.5.6...v8.5.7) (2025-05-07)
7+
8+
9+
### Bug Fixes
10+
11+
* **labels:** prevent clicking a label from triggering onClick twice on several components ([#30384](https://github.com/ionic-team/ionic-framework/issues/30384)) ([7d639b0](https://github.com/ionic-team/ionic-framework/commit/7d639b0412120523f758942c855cb69f9a52e9d9)), closes [#30165](https://github.com/ionic-team/ionic-framework/issues/30165)
12+
13+
14+
15+
16+
617
## [8.5.6](https://github.com/ionic-team/ionic-framework/compare/v8.5.5...v8.5.6) (2025-04-30)
718

819

core/package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

core/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@ionic/core",
3-
"version": "8.5.6",
3+
"version": "8.5.7",
44
"description": "Base components for Ionic",
55
"keywords": [
66
"ionic",

core/src/components/checkbox/checkbox.tsx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,14 @@ export class Checkbox implements ComponentInterface {
199199
this.toggleChecked(ev);
200200
};
201201

202+
/**
203+
* Stops propagation when the display label is clicked,
204+
* otherwise, two clicks will be triggered.
205+
*/
206+
private onDivLabelClick = (ev: MouseEvent) => {
207+
ev.stopPropagation();
208+
};
209+
202210
private getHintTextID(): string | undefined {
203211
const { el, helperText, errorText, helperTextId, errorTextId } = this;
204212

@@ -314,6 +322,7 @@ export class Checkbox implements ComponentInterface {
314322
}}
315323
part="label"
316324
id={this.inputLabelId}
325+
onClick={this.onDivLabelClick}
317326
>
318327
<slot></slot>
319328
{this.renderHintText()}

core/src/components/checkbox/test/basic/checkbox.e2e.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,4 +99,38 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
9999
expect(ionChange).not.toHaveReceivedEvent();
100100
});
101101
});
102+
103+
test.describe(title('checkbox: click'), () => {
104+
test('should trigger onclick only once when clicking the label', async ({ page }, testInfo) => {
105+
testInfo.annotations.push({
106+
type: 'issue',
107+
description: 'https://github.com/ionic-team/ionic-framework/issues/30165',
108+
});
109+
110+
// Create a spy function in page context
111+
await page.setContent(`<ion-checkbox onclick="console.log('click called')">Test Checkbox</ion-checkbox>`, config);
112+
113+
// Track calls to the exposed function
114+
let clickCount = 0;
115+
page.on('console', (msg) => {
116+
if (msg.text().includes('click called')) {
117+
clickCount++;
118+
}
119+
});
120+
121+
const input = page.locator('div.label-text-wrapper');
122+
123+
// Use position to make sure we click into the label enough to trigger
124+
// what would be the double click
125+
await input.click({
126+
position: {
127+
x: 5,
128+
y: 5,
129+
},
130+
});
131+
132+
// Verify the click was triggered exactly once
133+
expect(clickCount).toBe(1);
134+
});
135+
});
102136
});

core/src/components/input/input.tsx

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -720,6 +720,18 @@ export class Input implements ComponentInterface {
720720
return this.label !== undefined || this.labelSlot !== null;
721721
}
722722

723+
/**
724+
* Stops propagation when the label is clicked,
725+
* otherwise, two clicks will be triggered.
726+
*/
727+
private onLabelClick = (ev: MouseEvent) => {
728+
// Only stop propagation if the click was directly on the label
729+
// and not on the input or other child elements
730+
if (ev.target === ev.currentTarget) {
731+
ev.stopPropagation();
732+
}
733+
};
734+
723735
/**
724736
* Renders the border container
725737
* when fill="outline".
@@ -815,9 +827,9 @@ export class Input implements ComponentInterface {
815827
* interactable, clicking the label would focus that instead
816828
* since it comes before the input in the DOM.
817829
*/}
818-
<label class="input-wrapper" htmlFor={inputId}>
830+
<label class="input-wrapper" htmlFor={inputId} onClick={this.onLabelClick}>
819831
{this.renderLabelContainer()}
820-
<div class="native-wrapper">
832+
<div class="native-wrapper" onClick={this.onLabelClick}>
821833
<slot name="start"></slot>
822834
<input
823835
class="native-input"

core/src/components/input/test/basic/input.e2e.ts

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,4 +130,81 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, screenshot, c
130130
await expect(item).toHaveScreenshot(screenshot(`input-with-clear-button-item-color`));
131131
});
132132
});
133+
134+
test.describe(title('input: click'), () => {
135+
test('should trigger onclick only once when clicking the label', async ({ page }, testInfo) => {
136+
testInfo.annotations.push({
137+
type: 'issue',
138+
description: 'https://github.com/ionic-team/ionic-framework/issues/30165',
139+
});
140+
// Create a spy function in page context
141+
await page.setContent(
142+
`
143+
<ion-input
144+
label="Click Me"
145+
value="Test Value"
146+
></ion-input>
147+
`,
148+
config
149+
);
150+
151+
// Track calls to the exposed function
152+
const clickEvent = await page.spyOnEvent('click');
153+
const input = page.locator('label.input-wrapper');
154+
155+
// Use position to make sure we click into the label enough to trigger
156+
// what would be the double click
157+
await input.click({
158+
position: {
159+
x: 5,
160+
y: 5,
161+
},
162+
});
163+
164+
// Verify the click was triggered exactly once
165+
expect(clickEvent).toHaveReceivedEventTimes(1);
166+
167+
// Verify that the event target is the checkbox and not the item
168+
const event = clickEvent.events[0];
169+
expect((event.target as HTMLElement).tagName.toLowerCase()).toBe('ion-input');
170+
});
171+
172+
test('should trigger onclick only once when clicking the wrapper', async ({ page }, testInfo) => {
173+
testInfo.annotations.push({
174+
type: 'issue',
175+
description: 'https://github.com/ionic-team/ionic-framework/issues/30165',
176+
});
177+
// Create a spy function in page context
178+
await page.setContent(
179+
`
180+
<ion-input
181+
label="Click Me"
182+
value="Test Value"
183+
label-placement="floating"
184+
></ion-input>
185+
`,
186+
config
187+
);
188+
189+
// Track calls to the exposed function
190+
const clickEvent = await page.spyOnEvent('click');
191+
const input = page.locator('div.native-wrapper');
192+
193+
// Use position to make sure we click into the label enough to trigger
194+
// what would be the double click
195+
await input.click({
196+
position: {
197+
x: 1,
198+
y: 1,
199+
},
200+
});
201+
202+
// Verify the click was triggered exactly once
203+
expect(clickEvent).toHaveReceivedEventTimes(1);
204+
205+
// Verify that the event target is the checkbox and not the item
206+
const event = clickEvent.events[0];
207+
expect((event.target as HTMLElement).tagName.toLowerCase()).toBe('ion-input');
208+
});
209+
});
133210
});

core/src/components/select/select.tsx

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -911,6 +911,18 @@ export class Select implements ComponentInterface {
911911
return this.label !== undefined || this.labelSlot !== null;
912912
}
913913

914+
/**
915+
* Stops propagation when the label is clicked,
916+
* otherwise, two clicks will be triggered.
917+
*/
918+
private onLabelClick = (ev: MouseEvent) => {
919+
// Only stop propagation if the click was directly on the label
920+
// and not on the input or other child elements
921+
if (ev.target === ev.currentTarget) {
922+
ev.stopPropagation();
923+
}
924+
};
925+
914926
/**
915927
* Renders the border container
916928
* when fill="outline".
@@ -1173,7 +1185,7 @@ export class Select implements ComponentInterface {
11731185
[`select-label-placement-${labelPlacement}`]: true,
11741186
})}
11751187
>
1176-
<label class="select-wrapper" id="select-label">
1188+
<label class="select-wrapper" id="select-label" onClick={this.onLabelClick}>
11771189
{this.renderLabelContainer()}
11781190
<div class="select-wrapper-inner">
11791191
<slot name="start"></slot>

core/src/components/select/test/basic/select.e2e.ts

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { expect } from '@playwright/test';
2-
import { configs, test } from '@utils/test/playwright';
32
import type { E2ELocator } from '@utils/test/playwright';
3+
import { configs, test } from '@utils/test/playwright';
44

55
/**
66
* This checks that certain overlays open correctly. While the
@@ -150,6 +150,45 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
150150
expect(alerts.length).toBe(1);
151151
});
152152
});
153+
154+
test.describe(title('select: click'), () => {
155+
test('should trigger onclick only once when clicking the label', async ({ page }, testInfo) => {
156+
testInfo.annotations.push({
157+
type: 'issue',
158+
description: 'https://github.com/ionic-team/ionic-framework/issues/30165',
159+
});
160+
// Create a spy function in page context
161+
await page.setContent(
162+
`
163+
<ion-select aria-label="Fruit" interface="alert">
164+
<ion-select-option value="apple">Apple</ion-select-option>
165+
<ion-select-option value="banana">Banana</ion-select-option>
166+
</ion-select>
167+
`,
168+
config
169+
);
170+
171+
// Track calls to the exposed function
172+
const clickEvent = await page.spyOnEvent('click');
173+
const input = page.locator('label.select-wrapper');
174+
175+
// Use position to make sure we click into the label enough to trigger
176+
// what would be the double click
177+
await input.click({
178+
position: {
179+
x: 5,
180+
y: 5,
181+
},
182+
});
183+
184+
// Verify the click was triggered exactly once
185+
expect(clickEvent).toHaveReceivedEventTimes(1);
186+
187+
// Verify that the event target is the checkbox and not the item
188+
const event = clickEvent.events[0];
189+
expect((event.target as HTMLElement).tagName.toLowerCase()).toBe('ion-select');
190+
});
191+
});
153192
});
154193

155194
/**

0 commit comments

Comments
 (0)