Skip to content

Commit 428338b

Browse files
committed
fix(select): use has-focus class
1 parent c57530b commit 428338b

File tree

12 files changed

+160
-77
lines changed

12 files changed

+160
-77
lines changed

core/src/components/input/input.tsx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,11 +50,20 @@ export class Input implements ComponentInterface {
5050
* Resets when the input loses focus.
5151
*/
5252
private didInputClearOnEdit = false;
53+
5354
/**
5455
* The value of the input when the input is focused.
5556
*/
5657
private focusedValue?: string | number | null;
5758

59+
/**
60+
* The `hasFocus` state ensures the focus class is
61+
* added regardless of how the element is focused.
62+
* The `ion-focused` class only applies when focused
63+
* via tabbing, not by clicking.
64+
* The `has-focus` logic was added to ensure the class
65+
* is applied in both cases.
66+
*/
5867
@State() hasFocus = false;
5968

6069
@Element() el!: HTMLIonInputElement;

core/src/components/select/select.md.outline.scss

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
* If the select has a validity state, the
2323
* border should reflect that as a color.
2424
*/
25-
:host(.ion-focused.select-fill-outline.ion-valid),
25+
:host(.has-focus.select-fill-outline.ion-valid),
2626
:host(.select-fill-outline.ion-touched.ion-invalid) {
2727
--border-color: var(--highlight-color);
2828
}
@@ -43,7 +43,7 @@
4343
* the select is focused.
4444
*/
4545
:host(.select-fill-outline.select-expanded),
46-
:host(.select-fill-outline.ion-focused) {
46+
:host(.select-fill-outline.has-focus) {
4747
--border-width: var(--highlight-height);
4848
--border-color: var(--highlight-color);
4949
}

core/src/components/select/select.md.scss

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,15 @@
2121
* only apply to floating or stacked labels.
2222
*/
2323
:host(.select-label-placement-floating.select-expanded) .label-text-wrapper,
24-
:host(.select-label-placement-floating.ion-focused) .label-text-wrapper,
24+
:host(.select-label-placement-floating.has-focus) .label-text-wrapper,
2525
:host(.select-label-placement-stacked.select-expanded) .label-text-wrapper,
26-
:host(.select-label-placement-stacked.ion-focused) .label-text-wrapper {
26+
:host(.select-label-placement-stacked.has-focus) .label-text-wrapper {
2727
color: var(--highlight-color);
2828
}
2929

30-
:host(.ion-focused.select-label-placement-floating.ion-valid) .label-text-wrapper,
30+
:host(.has-focus.select-label-placement-floating.ion-valid) .label-text-wrapper,
3131
:host(.select-label-placement-floating.ion-touched.ion-invalid) .label-text-wrapper,
32-
:host(.ion-focused.select-label-placement-stacked.ion-valid) .label-text-wrapper,
32+
:host(.has-focus.select-label-placement-stacked.ion-valid) .label-text-wrapper,
3333
:host(.select-label-placement-stacked.ion-touched.ion-invalid) .label-text-wrapper {
3434
color: var(--highlight-color);
3535
}
@@ -53,7 +53,7 @@
5353
}
5454

5555
:host(.select-expanded) .select-highlight,
56-
:host(.ion-focused) .select-highlight {
56+
:host(.has-focus) .select-highlight {
5757
transform: scale(1);
5858
}
5959

@@ -104,9 +104,9 @@
104104
* color if there is a validation state.
105105
*/
106106
:host(.select-expanded) .select-wrapper .select-icon,
107-
:host(.ion-focused.ion-valid) .select-wrapper .select-icon,
107+
:host(.has-focus.ion-valid) .select-wrapper .select-icon,
108108
:host(.ion-touched.ion-invalid) .select-wrapper .select-icon,
109-
:host(.ion-focused) .select-wrapper .select-icon {
109+
:host(.has-focus) .select-wrapper .select-icon {
110110
color: var(--highlight-color);
111111
}
112112

core/src/components/select/select.md.solid.scss

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
* border should reflect that as a color.
2929
*/
3030
:host(.select-expanded.select-fill-solid.ion-valid),
31-
:host(.ion-focused.select-fill-solid.ion-valid),
31+
:host(.has-focus.select-fill-solid.ion-valid),
3232
:host(.select-fill-solid.ion-touched.ion-invalid) {
3333
--border-color: var(--highlight-color);
3434
}
@@ -57,7 +57,7 @@
5757
* much darker on focus.
5858
*/
5959
:host(.select-fill-solid.select-expanded),
60-
:host(.select-fill-solid.ion-focused) {
60+
:host(.select-fill-solid.has-focus) {
6161
--background: #{$background-color-step-150};
6262
--border-color: var(--highlight-color);
6363
}

core/src/components/select/select.scss

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@
8888
pointer-events: none;
8989
}
9090

91-
:host(.ion-focused) button {
91+
:host(.has-focus) button {
9292
border: 2px solid #5e9ed6;
9393
}
9494

@@ -265,7 +265,7 @@ button {
265265
* The component highlight is only shown
266266
* on focus, so we can safely set the valid
267267
* color state when touched/valid. If we
268-
* set it when .ion-focused is present then
268+
* set it when .has-focus is present then
269269
* the highlight color would change
270270
* from the valid color to the component's
271271
* color during the transition after the
@@ -307,7 +307,7 @@ button {
307307
* is currently focused. Do not show the valid
308308
* highlight when the select is blurred.
309309
*/
310-
:host(.ion-focused.ion-valid),
310+
:host(.has-focus.ion-valid),
311311
:host(.select-expanded.ion-valid),
312312
:host(.ion-touched.ion-invalid),
313313
:host(.select-expanded.ion-touched.ion-invalid) {
@@ -599,7 +599,7 @@ button {
599599
* :host(.label-floating.select-label-placement-floating) .native-wrapper .select-placeholder
600600
*/
601601
:host(.select-expanded.select-label-placement-floating) .native-wrapper .select-placeholder,
602-
:host(.ion-focused.select-label-placement-floating) .native-wrapper .select-placeholder,
602+
:host(.has-focus.select-label-placement-floating) .native-wrapper .select-placeholder,
603603
:host(.has-value.select-label-placement-floating) .native-wrapper .select-placeholder {
604604
opacity: 1;
605605
}

core/src/components/select/select.tsx

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,16 @@ export class Select implements ComponentInterface {
7070

7171
@State() isExpanded = false;
7272

73+
/**
74+
* The `hasFocus` state ensures the focus class is
75+
* added regardless of how the element is focused.
76+
* The `ion-focused` class only applies when focused
77+
* via tabbing, not by clicking.
78+
* The `has-focus` logic was added to ensure the class
79+
* is applied in both cases.
80+
*/
81+
@State() hasFocus = false;
82+
7383
/**
7484
* The text to display on the cancel button.
7585
*/
@@ -851,10 +861,14 @@ export class Select implements ComponentInterface {
851861
};
852862

853863
private onFocus = () => {
864+
this.hasFocus = true;
865+
854866
this.ionFocus.emit();
855867
};
856868

857869
private onBlur = () => {
870+
this.hasFocus = false;
871+
858872
this.ionBlur.emit();
859873
};
860874

@@ -1089,8 +1103,20 @@ export class Select implements ComponentInterface {
10891103
}
10901104

10911105
render() {
1092-
const { disabled, el, isExpanded, expandedIcon, labelPlacement, justify, placeholder, fill, shape, name, value } =
1093-
this;
1106+
const {
1107+
disabled,
1108+
el,
1109+
isExpanded,
1110+
expandedIcon,
1111+
labelPlacement,
1112+
justify,
1113+
placeholder,
1114+
fill,
1115+
shape,
1116+
name,
1117+
value,
1118+
hasFocus,
1119+
} = this;
10941120
const mode = getIonMode(this);
10951121
const hasFloatingOrStackedLabel = labelPlacement === 'floating' || labelPlacement === 'stacked';
10961122
const justifyEnabled = !hasFloatingOrStackedLabel && justify !== undefined;
@@ -1136,7 +1162,7 @@ export class Select implements ComponentInterface {
11361162
'has-value': hasValue,
11371163
'label-floating': labelShouldFloat,
11381164
'has-placeholder': placeholder !== undefined,
1139-
'ion-focusable': true,
1165+
'has-focus': hasFocus,
11401166
[`select-${rtl}`]: true,
11411167
[`select-fill-${fill}`]: fill !== undefined,
11421168
[`select-justify-${justify}`]: justifyEnabled,

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -65,13 +65,13 @@ configs({ directions: ['ltr'], palettes: ['light', 'dark'] }).forEach(({ title,
6565
await page.setContent(
6666
`
6767
<main>
68-
<ion-select class="ion-focused" label="Label" value="a">
68+
<ion-select class="has-focus" label="Label" value="a">
6969
<ion-select-option value="a">Apple</ion-select-option>
7070
</ion-select>
71-
<ion-select class="ion-focused" label-placement="floating" label="Label" value="a">
71+
<ion-select class="has-focus" label-placement="floating" label="Label" value="a">
7272
<ion-select-option value="a">Apple</ion-select-option>
7373
</ion-select>
74-
<ion-select class="ion-focused" fill="outline" label-placement="floating" label="Label" value="a">
74+
<ion-select class="has-focus" fill="outline" label-placement="floating" label="Label" value="a">
7575
<ion-select-option value="a">Apple</ion-select-option>
7676
</ion-select>
7777
</main>

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

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -324,3 +324,49 @@ configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, config }) => {
324324
});
325325
});
326326
});
327+
328+
/**
329+
* focus has a consistent behavior across modes
330+
*/
331+
configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, config }) => {
332+
test.describe.only(title('select: focus'), () => {
333+
test('should have the focus class when tabbing', async ({ page, pageUtils }) => {
334+
await page.setContent(
335+
`
336+
<ion-select aria-label="Fruit" interface="alert">
337+
<ion-select-option value="apple">Apple</ion-select-option>
338+
</ion-select>
339+
`,
340+
config
341+
);
342+
343+
const select = page.locator('ion-select');
344+
345+
await pageUtils.pressKeys('Tab');
346+
await expect(select).toHaveClass(/has-focus/);
347+
});
348+
349+
test('should have the focus class after clicking to close', async ({ page }) => {
350+
await page.setContent(
351+
`
352+
<ion-select aria-label="Fruit" interface="alert">
353+
<ion-select-option value="apple">Apple</ion-select-option>
354+
</ion-select>
355+
`,
356+
config
357+
);
358+
359+
const ionAlertDidPresent = await page.spyOnEvent('ionAlertDidPresent');
360+
const select = page.locator('ion-select');
361+
const alert = page.locator('ion-alert');
362+
const confirmButton = alert.locator('.alert-button:not(.alert-button-role-cancel)');
363+
364+
await select.click();
365+
await ionAlertDidPresent.next();
366+
367+
await confirmButton.click();
368+
369+
await expect(select).toHaveClass(/has-focus/);
370+
});
371+
});
372+
});

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, screenshot, co
77
test('should set label and highlight color on expand', async ({ page }) => {
88
await page.setContent(
99
`
10-
<ion-select label="Label" class="select-expanded" value="apple" class="ion-focused" color="danger">
10+
<ion-select label="Label" class="select-expanded" value="apple" class="has-focus" color="danger">
1111
<ion-select-option value="apple">Apple</ion-select-option>
1212
</ion-select>
1313
`,
@@ -22,7 +22,7 @@ configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, screenshot, co
2222
test('should set label and highlight color on expand', async ({ page }) => {
2323
await page.setContent(
2424
`
25-
<ion-select fill="solid" label="Label" class="select-expanded" value="apple" class="ion-focused" color="danger">
25+
<ion-select fill="solid" label="Label" class="select-expanded" value="apple" class="has-focus" color="danger">
2626
<ion-select-option value="apple">Apple</ion-select-option>
2727
</ion-select>
2828
`,
@@ -37,7 +37,7 @@ configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, screenshot, co
3737
test('should set label and highlight color on expand', async ({ page }) => {
3838
await page.setContent(
3939
`
40-
<ion-select fill="outline" label="Label" class="select-expanded" value="apple" class="ion-focused" color="danger">
40+
<ion-select fill="outline" label="Label" class="select-expanded" value="apple" class="has-focus" color="danger">
4141
<ion-select-option value="apple">Apple</ion-select-option>
4242
</ion-select>
4343
`,

0 commit comments

Comments
 (0)