diff --git a/packages/calcite-components/src/components/input-number/input-number.e2e.ts b/packages/calcite-components/src/components/input-number/input-number.e2e.ts index 2a94d4f0ac2..b898d811b0f 100644 --- a/packages/calcite-components/src/components/input-number/input-number.e2e.ts +++ b/packages/calcite-components/src/components/input-number/input-number.e2e.ts @@ -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 { @@ -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">`, + ); + 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( diff --git a/packages/calcite-components/src/components/input-number/input-number.tsx b/packages/calcite-components/src/components/input-number/input-number.tsx index 5597b5ac06b..a768cb77969 100644 --- a/packages/calcite-components/src/components/input-number/input-number.tsx +++ b/packages/calcite-components/src/components/input-number/input-number.tsx @@ -405,6 +405,10 @@ export class InputNumber inlineEditableEl: HTMLCalciteInlineEditableElement; + private inputWrapperEl: HTMLDivElement; + + private actionWrapperEl: HTMLDivElement; + /** number text input element for locale */ private childNumberEl?: HTMLInputElement; @@ -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 => { @@ -1108,7 +1118,10 @@ export class InputNumber return ( -
+
(this.inputWrapperEl = el)} + > {this.numberButtonType === "horizontal" && !this.readOnly ? numberButtonsHorizontalDown : null} @@ -1119,7 +1132,7 @@ export class InputNumber {this.requestedIcon ? iconEl : null} {this.loading ? loader : null}
-
+
(this.actionWrapperEl = el)}>
{this.numberButtonType === "vertical" && !this.readOnly ? numberButtonsVertical : null} diff --git a/packages/calcite-components/src/components/input-text/input-text.e2e.ts b/packages/calcite-components/src/components/input-text/input-text.e2e.ts index 318dcb48d41..e7caced8c1c 100644 --- a/packages/calcite-components/src/components/input-text/input-text.e2e.ts +++ b/packages/calcite-components/src/components/input-text/input-text.e2e.ts @@ -11,7 +11,7 @@ import { renders, t9n, } from "../../tests/commonTests"; -import { selectText } from "../../tests/utils"; +import { isElementFocused, selectText } from "../../tests/utils"; import { testHiddenInputSyncing, testPostValidationFocusing, @@ -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">`, + ); + 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( diff --git a/packages/calcite-components/src/components/input-text/input-text.tsx b/packages/calcite-components/src/components/input-text/input-text.tsx index 0cfc2c8c947..e50f56575bc 100644 --- a/packages/calcite-components/src/components/input-text/input-text.tsx +++ b/packages/calcite-components/src/components/input-text/input-text.tsx @@ -318,6 +318,10 @@ export class InputText inlineEditableEl: HTMLCalciteInlineEditableElement; + private inputWrapperEl: HTMLDivElement; + + private actionWrapperEl: HTMLDivElement; + /** keep track of the rendered child */ private childEl?: HTMLInputElement; @@ -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 => { @@ -695,7 +705,10 @@ export class InputText return ( -
+
(this.inputWrapperEl = el)} + > {this.prefixText ? prefixText : null}
{childEl} @@ -703,7 +716,7 @@ export class InputText {this.requestedIcon ? iconEl : null} {this.loading ? loader : null}
-
+
(this.actionWrapperEl = el)}>
{this.suffixText ? suffixText : null} diff --git a/packages/calcite-components/src/components/input/input.e2e.ts b/packages/calcite-components/src/components/input/input.e2e.ts index 6e70e5c8cf7..94cb3e12e54 100644 --- a/packages/calcite-components/src/components/input/input.e2e.ts +++ b/packages/calcite-components/src/components/input/input.e2e.ts @@ -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"; @@ -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">`, + ); + 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( diff --git a/packages/calcite-components/src/components/input/input.tsx b/packages/calcite-components/src/components/input/input.tsx index 520945097a1..6336141994e 100644 --- a/packages/calcite-components/src/components/input/input.tsx +++ b/packages/calcite-components/src/components/input/input.tsx @@ -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; @@ -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 => { @@ -1249,7 +1259,10 @@ export class Input return ( -
+
(this.inputWrapperEl = el)} + > {this.type === "number" && this.numberButtonType === "horizontal" && !this.readOnly ? numberButtonsHorizontalDown : null} @@ -1261,7 +1274,7 @@ export class Input {this.requestedIcon ? iconEl : null} {this.loading ? loader : null}
-
+
(this.actionWrapperEl = el)}>
{this.type === "number" && this.numberButtonType === "vertical" && !this.readOnly