Skip to content

Commit

Permalink
fix(input, input-number, input-text): no longer set focus when a vali…
Browse files Browse the repository at this point in the history
…dation message is clicked (#10008)

**Related Issue:** #10006

## Summary

- when clicking on a validation message, the component is no longer
being focused
- adds test
  • Loading branch information
driskull authored Aug 13, 2024
1 parent 2c817ae commit 529bb5a
Show file tree
Hide file tree
Showing 6 changed files with 126 additions and 18 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {
renders,
t9n,
} from "../../tests/commonTests";
import { getElementRect, getElementXY, selectText } from "../../tests/utils";
import { getElementRect, getElementXY, isElementFocused, selectText } from "../../tests/utils";
import { letterKeys, numberKeys } from "../../utils/key";
import { locales, numberStringFormatter } from "../../utils/locale";
import {
Expand Down Expand Up @@ -1665,6 +1665,29 @@ describe("calcite-input-number", () => {
});
});

it("should not focus when clicking validation message", async () => {
const page = await newE2EPage();
const componentTag = "calcite-input-number";
await page.setContent(
html` <${componentTag} status="invalid" type="text" validation-message="Info message"></${componentTag}>`,
);
await page.waitForChanges();

expect(await isElementFocused(page, componentTag)).toBe(false);

await page.$eval(`${componentTag} >>> calcite-input-message`, (element: HTMLCalciteInputMessageElement) => {
element.click();
});
await page.waitForChanges();

expect(await isElementFocused(page, componentTag)).toBe(false);

await page.keyboard.press("Tab");
await page.waitForChanges();

expect(await isElementFocused(page, componentTag)).toBe(true);
});

it("allows disabling slotted action", async () => {
const page = await newE2EPage();
await page.setContent(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -405,6 +405,10 @@ export class InputNumber

inlineEditableEl: HTMLCalciteInlineEditableElement;

private inputWrapperEl: HTMLDivElement;

private actionWrapperEl: HTMLDivElement;

/** number text input element for locale */
private childNumberEl?: HTMLInputElement;

Expand Down Expand Up @@ -672,10 +676,16 @@ export class InputNumber
return;
}

const slottedActionEl = getSlotted(this.el, "action");
if (event.target !== slottedActionEl) {
this.setFocus();
const composedPath = event.composedPath();

if (
!composedPath.includes(this.inputWrapperEl) ||
composedPath.includes(this.actionWrapperEl)
) {
return;
}

this.setFocus();
};

private inputNumberFocusHandler = (): void => {
Expand Down Expand Up @@ -1108,7 +1118,10 @@ export class InputNumber
return (
<Host onClick={this.clickHandler} onKeyDown={this.keyDownHandler}>
<InteractiveContainer disabled={this.disabled}>
<div class={{ [CSS.inputWrapper]: true, [CSS_UTILITY.rtl]: dir === "rtl" }}>
<div
class={{ [CSS.inputWrapper]: true, [CSS_UTILITY.rtl]: dir === "rtl" }}
ref={(el) => (this.inputWrapperEl = el)}
>
{this.numberButtonType === "horizontal" && !this.readOnly
? numberButtonsHorizontalDown
: null}
Expand All @@ -1119,7 +1132,7 @@ export class InputNumber
{this.requestedIcon ? iconEl : null}
{this.loading ? loader : null}
</div>
<div class={CSS.actionWrapper}>
<div class={CSS.actionWrapper} ref={(el) => (this.actionWrapperEl = el)}>
<slot name={SLOTS.action} />
</div>
{this.numberButtonType === "vertical" && !this.readOnly ? numberButtonsVertical : null}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
renders,
t9n,
} from "../../tests/commonTests";
import { selectText } from "../../tests/utils";
import { isElementFocused, selectText } from "../../tests/utils";
import {
testHiddenInputSyncing,
testPostValidationFocusing,
Expand Down Expand Up @@ -408,6 +408,29 @@ describe("calcite-input-text", () => {
});
});

it("should not focus when clicking validation message", async () => {
const page = await newE2EPage();
const componentTag = "calcite-input-text";
await page.setContent(
html` <${componentTag} status="invalid" type="text" validation-message="Info message"></${componentTag}>`,
);
await page.waitForChanges();

expect(await isElementFocused(page, componentTag)).toBe(false);

await page.$eval(`${componentTag} >>> calcite-input-message`, (element: HTMLCalciteInputMessageElement) => {
element.click();
});
await page.waitForChanges();

expect(await isElementFocused(page, componentTag)).toBe(false);

await page.keyboard.press("Tab");
await page.waitForChanges();

expect(await isElementFocused(page, componentTag)).toBe(true);
});

it("allows disabling slotted action", async () => {
const page = await newE2EPage();
await page.setContent(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,10 @@ export class InputText

inlineEditableEl: HTMLCalciteInlineEditableElement;

private inputWrapperEl: HTMLDivElement;

private actionWrapperEl: HTMLDivElement;

/** keep track of the rendered child */
private childEl?: HTMLInputElement;

Expand Down Expand Up @@ -505,10 +509,16 @@ export class InputText
return;
}

const slottedActionEl = getSlotted(this.el, "action");
if (event.target !== slottedActionEl) {
this.setFocus();
const composedPath = event.composedPath();

if (
!composedPath.includes(this.inputWrapperEl) ||
composedPath.includes(this.actionWrapperEl)
) {
return;
}

this.setFocus();
};

private inputTextFocusHandler = (): void => {
Expand Down Expand Up @@ -695,15 +705,18 @@ export class InputText
return (
<Host onClick={this.clickHandler} onKeyDown={this.keyDownHandler}>
<InteractiveContainer disabled={this.disabled}>
<div class={{ [CSS.inputWrapper]: true, [CSS_UTILITY.rtl]: dir === "rtl" }}>
<div
class={{ [CSS.inputWrapper]: true, [CSS_UTILITY.rtl]: dir === "rtl" }}
ref={(el) => (this.inputWrapperEl = el)}
>
{this.prefixText ? prefixText : null}
<div class={CSS.wrapper}>
{childEl}
{this.isClearable ? inputClearButton : null}
{this.requestedIcon ? iconEl : null}
{this.loading ? loader : null}
</div>
<div class={CSS.actionWrapper}>
<div class={CSS.actionWrapper} ref={(el) => (this.actionWrapperEl = el)}>
<slot name={SLOTS.action} />
</div>
{this.suffixText ? suffixText : null}
Expand Down
25 changes: 24 additions & 1 deletion packages/calcite-components/src/components/input/input.e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {
import { html } from "../../../support/formatting";
import { letterKeys, numberKeys } from "../../utils/key";
import { locales, numberStringFormatter } from "../../utils/locale";
import { getElementRect, getElementXY, selectText } from "../../tests/utils";
import { getElementRect, getElementXY, isElementFocused, selectText } from "../../tests/utils";
import { DEBOUNCE } from "../../utils/resources";
import { assertCaretPosition } from "../../tests/utils";
import { testHiddenInputSyncing, testPostValidationFocusing, testWorkaroundForGlobalPropRemoval } from "./common/tests";
Expand Down Expand Up @@ -1958,6 +1958,29 @@ describe("calcite-input", () => {
});
});

it("should not focus when clicking validation message", async () => {
const page = await newE2EPage();
const componentTag = "calcite-input";
await page.setContent(
html` <${componentTag} status="invalid" type="text" validation-message="Info message"></${componentTag}>`,
);
await page.waitForChanges();

expect(await isElementFocused(page, componentTag)).toBe(false);

await page.$eval(`${componentTag} >>> calcite-input-message`, (element: HTMLCalciteInputMessageElement) => {
element.click();
});
await page.waitForChanges();

expect(await isElementFocused(page, componentTag)).toBe(false);

await page.keyboard.press("Tab");
await page.waitForChanges();

expect(await isElementFocused(page, componentTag)).toBe(true);
});

it("allows disabling slotted action", async () => {
const page = await newE2EPage();
await page.setContent(
Expand Down
23 changes: 18 additions & 5 deletions packages/calcite-components/src/components/input/input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -452,6 +452,10 @@ export class Input

inlineEditableEl: HTMLCalciteInlineEditableElement;

private inputWrapperEl: HTMLDivElement;

private actionWrapperEl: HTMLDivElement;

/** keep track of the rendered child type */
private childEl?: HTMLInputElement | HTMLTextAreaElement;

Expand Down Expand Up @@ -734,10 +738,16 @@ export class Input
return;
}

const slottedActionEl = getSlotted(this.el, "action");
if (event.target !== slottedActionEl) {
this.setFocus();
const composedPath = event.composedPath();

if (
!composedPath.includes(this.inputWrapperEl) ||
composedPath.includes(this.actionWrapperEl)
) {
return;
}

this.setFocus();
};

private inputFocusHandler = (): void => {
Expand Down Expand Up @@ -1249,7 +1259,10 @@ export class Input
return (
<Host onClick={this.clickHandler} onKeyDown={this.keyDownHandler}>
<InteractiveContainer disabled={this.disabled}>
<div class={{ [CSS.inputWrapper]: true, [CSS_UTILITY.rtl]: dir === "rtl" }}>
<div
class={{ [CSS.inputWrapper]: true, [CSS_UTILITY.rtl]: dir === "rtl" }}
ref={(el) => (this.inputWrapperEl = el)}
>
{this.type === "number" && this.numberButtonType === "horizontal" && !this.readOnly
? numberButtonsHorizontalDown
: null}
Expand All @@ -1261,7 +1274,7 @@ export class Input
{this.requestedIcon ? iconEl : null}
{this.loading ? loader : null}
</div>
<div class={CSS.actionWrapper}>
<div class={CSS.actionWrapper} ref={(el) => (this.actionWrapperEl = el)}>
<slot name={SLOTS.action} />
</div>
{this.type === "number" && this.numberButtonType === "vertical" && !this.readOnly
Expand Down

0 comments on commit 529bb5a

Please sign in to comment.