diff --git a/packages/calcite-components/src/components/input-date-picker/input-date-picker.e2e.ts b/packages/calcite-components/src/components/input-date-picker/input-date-picker.e2e.ts index 0b4e889c1fa..d1cf070c3b6 100644 --- a/packages/calcite-components/src/components/input-date-picker/input-date-picker.e2e.ts +++ b/packages/calcite-components/src/components/input-date-picker/input-date-picker.e2e.ts @@ -133,8 +133,11 @@ describe("calcite-input-date-picker", () => { await input.click(); await page.waitForChanges(); await page.waitForTimeout(animationDurationInMs); - const wrapper = await page.waitForFunction(() => - document.querySelector("calcite-input-date-picker").shadowRoot.querySelector(".calendar-picker-wrapper") + const wrapper = await page.waitForFunction( + (calendarWrapperClass: string) => + document.querySelector("calcite-input-date-picker").shadowRoot.querySelector(`.${calendarWrapperClass}`), + {}, + CSS.calendarWrapper ); expect(await wrapper.isIntersectingViewport()).toBe(true); @@ -202,9 +205,13 @@ describe("calcite-input-date-picker", () => { await page.waitForChanges(); await page.waitForTimeout(animationDurationInMs); - const wrapper = await page.waitForFunction(() => - document.querySelector("calcite-input-date-picker").shadowRoot.querySelector(".calendar-picker-wrapper") + const wrapper = await page.waitForFunction( + (calendarWrapperClass: string) => + document.querySelector("calcite-input-date-picker").shadowRoot.querySelector(`.${calendarWrapperClass}`), + {}, + CSS.calendarWrapper ); + expect(await wrapper.isIntersectingViewport()).toBe(true); const inputtedStartDate = "1/1/2020"; @@ -318,50 +325,267 @@ describe("calcite-input-date-picker", () => { let page: E2EPage; let inputDatePicker: E2EElement; - beforeEach(async () => { - page = await newE2EPage(); - await page.setContent(html` `); - await skipAnimations(page); - await page.waitForChanges(); - inputDatePicker = await page.find("calcite-input-date-picker"); - }); + describe("single value", () => { + beforeEach(async () => { + page = await newE2EPage(); + await page.setContent(html` `); + await skipAnimations(page); + await page.waitForChanges(); + inputDatePicker = await page.find("calcite-input-date-picker"); + }); - it("toggles the date picker when clicked", async () => { - let calendar = await page.find("calcite-input-date-picker >>> .calendar-picker-wrapper"); + it("toggles the date picker when clicked", async () => { + let calendar = await page.find(`calcite-input-date-picker >>> .${CSS.calendarWrapper}`); - expect(await calendar.isVisible()).toBe(false); + expect(await calendar.isVisible()).toBe(false); - await inputDatePicker.click(); - await page.waitForChanges(); - calendar = await page.find("calcite-input-date-picker >>> .calendar-picker-wrapper"); + await inputDatePicker.click(); + await page.waitForChanges(); + calendar = await page.find(`calcite-input-date-picker >>> .${CSS.calendarWrapper}`); - expect(await calendar.isVisible()).toBe(true); + expect(await calendar.isVisible()).toBe(true); - await inputDatePicker.click(); - await page.waitForChanges(); - calendar = await page.find("calcite-input-date-picker >>> .calendar-picker-wrapper"); + await inputDatePicker.click(); + await page.waitForChanges(); + calendar = await page.find(`calcite-input-date-picker >>> .${CSS.calendarWrapper}`); + + expect(await calendar.isVisible()).toBe(false); + }); + + it("toggles the date picker when using arrow down/escape key", async () => { + let calendar = await page.find(`calcite-input-date-picker >>> .${CSS.calendarWrapper}`); + + expect(await calendar.isVisible()).toBe(false); - expect(await calendar.isVisible()).toBe(false); + await inputDatePicker.callMethod("setFocus"); + await page.waitForChanges(); + await page.keyboard.press("ArrowDown"); + await page.waitForChanges(); + calendar = await page.find(`calcite-input-date-picker >>> .${CSS.calendarWrapper}`); + + expect(await calendar.isVisible()).toBe(true); + + await page.keyboard.press("Escape"); + await page.waitForChanges(); + calendar = await page.find(`calcite-input-date-picker >>> .${CSS.calendarWrapper}`); + + expect(await calendar.isVisible()).toBe(false); + }); }); - it("toggles the date picker when using arrow down/escape key", async () => { - let calendar = await page.find("calcite-input-date-picker >>> .calendar-picker-wrapper"); + describe("range", () => { + beforeEach(async () => { + page = await newE2EPage(); + await page.setContent(html` `); + await skipAnimations(page); + await page.waitForChanges(); + inputDatePicker = await page.find("calcite-input-date-picker"); + }); - expect(await calendar.isVisible()).toBe(false); + async function isCalendarVisible(calendar: E2EElement, type: "start" | "end"): Promise { + const calendarPosition = calendar.classList.contains(CSS.calendarWrapperEnd) ? "end" : "start"; + return (await calendar.isVisible()) && calendarPosition === type; + } - await inputDatePicker.callMethod("setFocus"); - await page.waitForChanges(); - await page.keyboard.press("ArrowDown"); - await page.waitForChanges(); - calendar = await page.find("calcite-input-date-picker >>> .calendar-picker-wrapper"); + it("toggles the date picker when clicked", async () => { + const calendar = await page.find(`calcite-input-date-picker >>> .${CSS.calendarWrapper}`); - expect(await calendar.isVisible()).toBe(true); + expect(await isCalendarVisible(calendar, "start")).toBe(false); + expect(await isCalendarVisible(calendar, "end")).toBe(false); - await page.keyboard.press("Escape"); - await page.waitForChanges(); - calendar = await page.find("calcite-input-date-picker >>> .calendar-picker-wrapper"); + const startInput = await page.find( + `calcite-input-date-picker >>> .${CSS.inputWrapper}[data-position="start"] calcite-input-text` + ); + const startInputToggle = await page.find( + `calcite-input-date-picker >>> .${CSS.inputWrapper}[data-position="start"] .${CSS.toggleIcon}` + ); + + const endInput = await page.find( + `calcite-input-date-picker >>> .${CSS.inputWrapper}[data-position="end"] calcite-input-text` + ); + const endInputToggle = await page.find( + `calcite-input-date-picker >>> .${CSS.inputWrapper}[data-position="end"] .${CSS.toggleIcon}` + ); + + // toggling via start date input + + await startInput.click(); + await page.waitForChanges(); + + expect(await isCalendarVisible(calendar, "start")).toBe(true); + + await startInput.click(); + await page.waitForChanges(); + + expect(await isCalendarVisible(calendar, "start")).toBe(false); + + // toggling via start date toggle icon + + await startInputToggle.click(); + await page.waitForChanges(); + + expect(await isCalendarVisible(calendar, "start")).toBe(true); + + await startInputToggle.click(); + await page.waitForChanges(); + + expect(await isCalendarVisible(calendar, "start")).toBe(false); + + // toggling via end date input + + await endInput.click(); + await page.waitForChanges(); + + expect(await isCalendarVisible(calendar, "end")).toBe(true); + + await endInput.click(); + await page.waitForChanges(); + + expect(await isCalendarVisible(calendar, "end")).toBe(false); + + // toggling via end date toggle icon + + await endInputToggle.click(); + await page.waitForChanges(); + + expect(await isCalendarVisible(calendar, "end")).toBe(true); + + await endInputToggle.click(); + await page.waitForChanges(); + + expect(await isCalendarVisible(calendar, "end")).toBe(false); + + // toggling via start date input and toggle icon + + await startInput.click(); + await page.waitForChanges(); + + expect(await isCalendarVisible(calendar, "start")).toBe(true); + + await startInputToggle.click(); + await page.waitForChanges(); + + expect(await isCalendarVisible(calendar, "start")).toBe(false); - expect(await calendar.isVisible()).toBe(false); + // toggling via start toggle icon and date input + + await startInputToggle.click(); + await page.waitForChanges(); + + expect(await isCalendarVisible(calendar, "start")).toBe(true); + + await startInput.click(); + await page.waitForChanges(); + + expect(await isCalendarVisible(calendar, "start")).toBe(false); + + // toggling via end date input and toggle icon + + await endInput.click(); + await page.waitForChanges(); + + expect(await isCalendarVisible(calendar, "end")).toBe(true); + + await endInputToggle.click(); + await page.waitForChanges(); + + expect(await isCalendarVisible(calendar, "end")).toBe(false); + + // toggling via end toggle icon and date input + + await endInputToggle.click(); + await page.waitForChanges(); + + expect(await isCalendarVisible(calendar, "end")).toBe(true); + + await endInput.click(); + await page.waitForChanges(); + + expect(await isCalendarVisible(calendar, "end")).toBe(false); + + // toggling via start date input and end toggle icon + + await startInput.click(); + await page.waitForChanges(); + + expect(await isCalendarVisible(calendar, "start")).toBe(true); + + await endInputToggle.click(); + await page.waitForChanges(); + + expect(await isCalendarVisible(calendar, "end")).toBe(true); + + // toggling via start toggle icon and date input + + await startInputToggle.click(); + await page.waitForChanges(); + + expect(await isCalendarVisible(calendar, "start")).toBe(true); + + await endInput.click(); + await page.waitForChanges(); + + expect(await isCalendarVisible(calendar, "end")).toBe(true); + + // close + await endInput.click(); + await page.waitForChanges(); + + // toggling via end date input and start toggle icon + + await endInput.click(); + await page.waitForChanges(); + + expect(await isCalendarVisible(calendar, "end")).toBe(true); + + await startInputToggle.click(); + await page.waitForChanges(); + + expect(await isCalendarVisible(calendar, "start")).toBe(true); + + // toggling via end toggle icon and start date input + + await endInputToggle.click(); + await page.waitForChanges(); + + expect(await isCalendarVisible(calendar, "end")).toBe(true); + + await startInput.click(); + await page.waitForChanges(); + + expect(await isCalendarVisible(calendar, "start")).toBe(true); + }); + + it("toggles the date picker when using arrow down/escape key", async () => { + const calendar = await page.find(`calcite-input-date-picker >>> .${CSS.calendarWrapper}`); + + expect(await isCalendarVisible(calendar, "start")).toBe(false); + expect(await isCalendarVisible(calendar, "end")).toBe(false); + + await inputDatePicker.callMethod("setFocus"); + await page.waitForChanges(); + await page.keyboard.press("ArrowDown"); + await page.waitForChanges(); + + expect(await isCalendarVisible(calendar, "start")).toBe(true); + + await page.keyboard.press("Escape"); + await page.waitForChanges(); + + expect(await isCalendarVisible(calendar, "start")).toBe(false); + + await page.keyboard.press("Tab"); + + await page.keyboard.press("ArrowDown"); + await page.waitForChanges(); + + expect(await isCalendarVisible(calendar, "end")).toBe(true); + + await page.keyboard.press("Escape"); + await page.waitForChanges(); + + expect(await isCalendarVisible(calendar, "end")).toBe(false); + }); }); }); diff --git a/packages/calcite-components/src/components/input-date-picker/input-date-picker.scss b/packages/calcite-components/src/components/input-date-picker/input-date-picker.scss index b05e82c9474..b844167889f 100644 --- a/packages/calcite-components/src/components/input-date-picker/input-date-picker.scss +++ b/packages/calcite-components/src/components/input-date-picker/input-date-picker.scss @@ -30,7 +30,7 @@ @include disabled(); -.calendar-picker-wrapper { +.calendar-wrapper { @apply shadow-none; transform: translate3d(0, 0, 0); } @@ -79,7 +79,7 @@ items-start; } - .calendar-picker-wrapper--end { + .calendar-wrapper--end { transform: translate3d(0, 0, 0); } diff --git a/packages/calcite-components/src/components/input-date-picker/input-date-picker.tsx b/packages/calcite-components/src/components/input-date-picker/input-date-picker.tsx index 1e4bc5c196b..0785d108309 100644 --- a/packages/calcite-components/src/components/input-date-picker/input-date-picker.tsx +++ b/packages/calcite-components/src/components/input-date-picker/input-date-picker.tsx @@ -519,12 +519,14 @@ export class InputDatePicker }; return ( - + {this.localeData && ( -
+
@@ -534,15 +536,15 @@ export class InputDatePicker aria-describedby={this.placeholderTextId} aria-expanded={toAriaBoolean(this.open)} aria-haspopup="dialog" - class={`input ${ - this.layout === "vertical" && this.range ? `no-bottom-border` : `` - }`} + class={{ + [CSS.input]: true, + [CSS.inputNoBottomBorder]: this.layout === "vertical" && this.range, + }} disabled={disabled} icon="calendar" onCalciteInputTextInput={this.calciteInternalInputInputHandler} onCalciteInternalInputTextBlur={this.calciteInternalInputBlurHandler} onCalciteInternalInputTextFocus={this.startInputFocus} - onFocus={this.startEndInputFocus} placeholder={this.localeData?.placeholder} readOnly={readOnly} role="combobox" @@ -572,8 +574,8 @@ export class InputDatePicker >
{this.range && this.layout === "horizontal" && ( -
+
)} {this.range && this.layout === "vertical" && this.scale !== "s" && ( -
+
)} {this.range && (
@@ -626,15 +630,14 @@ export class InputDatePicker aria-expanded={toAriaBoolean(this.open)} aria-haspopup="dialog" class={{ - input: true, - "border-top-color-one": this.layout === "vertical" && this.range, + [CSS.input]: true, + [CSS.inputBorderTopColorOne]: this.layout === "vertical" && this.range, }} disabled={disabled} icon="calendar" onCalciteInputTextInput={this.calciteInternalInputInputHandler} onCalciteInternalInputTextBlur={this.calciteInternalInputBlurHandler} onCalciteInternalInputTextFocus={this.endInputFocus} - onFocus={this.startEndInputFocus} placeholder={this.localeData?.placeholder} readOnly={readOnly} role="combobox" @@ -672,6 +675,8 @@ export class InputDatePicker @Element() el: HTMLCalciteInputDatePickerElement; + private currentOpenInput: "start" | "end"; + private datePickerEl: HTMLCalciteDatePickerElement; private dialogId = `date-picker-dialog--${guid()}`; @@ -696,8 +701,6 @@ export class InputDatePicker @State() focusedInput: "start" | "end" = "start"; - private lastBlurredInput: "start" | "end" | "none" = "none"; - @State() private localeData: DateLocaleData; private startInput: HTMLCalciteInputElement; @@ -741,14 +744,32 @@ export class InputDatePicker // //-------------------------------------------------------------------------- - private onInputWrapperClick = () => { - if (this.range && this.lastBlurredInput !== "none" && this.open) { - // we keep the date-picker open when moving between inputs - } else { + private onInputWrapperPointerDown = (): void => { + this.currentOpenInput = this.focusedInput; + }; + + private onInputWrapperClick = (event: MouseEvent) => { + const { range, endInput, startInput, currentOpenInput } = this; + if (!range || !this.open) { this.open = !this.open; + return; } - this.lastBlurredInput = "none"; + const currentTarget = event.currentTarget as HTMLDivElement; + const position = currentTarget.getAttribute("data-position") as "start" | "end"; + const path = event.composedPath(); + const wasToggleClicked = path.find((el: HTMLElement) => { + return el.classList?.contains(CSS.toggleIcon); + }); + + if (wasToggleClicked) { + const targetInput = position === "start" ? startInput : endInput; + targetInput.setFocus(); + } + + if (currentOpenInput === position) { + this.open = !this.open; + } }; setFilteredPlacements = (): void => { @@ -803,9 +824,8 @@ export class InputDatePicker this.endInput = el; }; - deactivate = (): void => { + private blurHandler = (): void => { this.open = false; - this.lastBlurredInput = "none"; }; private commitValue(): void { @@ -886,12 +906,6 @@ export class InputDatePicker this.focusedInput = "start"; }; - startEndInputFocus = (event: FocusEvent): void => { - const blurredEl = event.relatedTarget as HTMLElement; - this.lastBlurredInput = - blurredEl === this.startInput ? "start" : blurredEl === this.endInput ? "end" : "none"; - }; - endInputFocus = (): void => { this.focusedInput = "end"; }; diff --git a/packages/calcite-components/src/components/input-date-picker/resources.ts b/packages/calcite-components/src/components/input-date-picker/resources.ts index e79b51c7e74..5c3a881fab2 100644 --- a/packages/calcite-components/src/components/input-date-picker/resources.ts +++ b/packages/calcite-components/src/components/input-date-picker/resources.ts @@ -1,6 +1,15 @@ export const CSS = { assistiveText: "assistive-text", + calendarWrapper: "calendar-wrapper", + calendarWrapperEnd: "calendar-wrapper--end", + horizontalArrowContainer: "horizontal-arrow-container", + inputBorderTopColorOne: "border-top-color-one", + inputContainer: "input-container", + inputNoBottomBorder: "no-bottom-border", + inputWrapper: "input-wrapper", + input: "input", menu: "menu-container", menuActive: "menu-container--active", toggleIcon: "toggle-icon", + verticalArrowContainer: "vertical-arrow-container", };