From 6a69a3ded288a0e6da055e619634cea29e26e28c Mon Sep 17 00:00:00 2001 From: "dilan.dogan2" Date: Thu, 22 Aug 2024 14:44:53 +0300 Subject: [PATCH 01/35] feat(datepicker): developing datepicker component --- commitlint.config.cjs | 1 + playground/template.html | 3 + src/baklava.ts | 1 + src/components/calendar/bl-calendar.ts | 178 +++++++------ src/components/datepicker/bl-datepicker.css | 118 +++++++++ .../datepicker/bl-datepicker.stories.mdx | 95 +++++++ src/components/datepicker/bl-datepicker.ts | 235 ++++++++++++++++++ .../datepicker-calendar-mixin.ts | 47 ++++ 8 files changed, 586 insertions(+), 92 deletions(-) create mode 100644 src/components/datepicker/bl-datepicker.css create mode 100644 src/components/datepicker/bl-datepicker.stories.mdx create mode 100644 src/components/datepicker/bl-datepicker.ts create mode 100644 src/mixins/datepicker-calendar-mixin/datepicker-calendar-mixin.ts diff --git a/commitlint.config.cjs b/commitlint.config.cjs index 515a25f7..91021367 100644 --- a/commitlint.config.cjs +++ b/commitlint.config.cjs @@ -36,6 +36,7 @@ module.exports = { "calendar", "table", "split-button", + "datepicker", ], ], }, diff --git a/playground/template.html b/playground/template.html index ed952a0c..da167bba 100644 --- a/playground/template.html +++ b/playground/template.html @@ -33,5 +33,8 @@

Baklava Playground

Baklava is ready + + + sdfghgfdsasdfggfdsasdfghgfdsasdfggfdsaasdfghgfdsasdfggfdsaaasdfghgfdsasdfggfdsaaaaa diff --git a/src/baklava.ts b/src/baklava.ts index 72f1a5f8..bbaddfb5 100644 --- a/src/baklava.ts +++ b/src/baklava.ts @@ -36,4 +36,5 @@ export { default as BlTableHeaderCell } from "./components/table/table-header-ce export { default as BlTableCell } from "./components/table/table-cell/bl-table-cell"; export { default as BlSplitButton } from "./components/split-button/bl-split-button"; export { default as BlCalendar } from "./components/calendar/bl-calendar"; +export { default as BlDatePicker } from "./components/datepicker/bl-datepicker"; export { getIconPath, setIconPath } from "./utilities/asset-paths"; diff --git a/src/components/calendar/bl-calendar.ts b/src/components/calendar/bl-calendar.ts index 74230a3a..8eaf15f0 100644 --- a/src/components/calendar/bl-calendar.ts +++ b/src/components/calendar/bl-calendar.ts @@ -1,8 +1,10 @@ -import { CSSResultGroup, html, LitElement } from "lit"; +import { CSSResultGroup, html } from "lit"; import { customElement, property, state } from "lit/decorators.js"; import { classMap } from "lit/directives/class-map.js"; +import DatepickerCalendarMixin from "../../mixins/datepicker-calendar-mixin/datepicker-calendar-mixin"; import { event, EventDispatcher } from "../../utilities/event"; import "../button/bl-button"; +import { blDatepickerClearSelectedDatesEvent } from "../datepicker/bl-datepicker"; import "../icon/bl-icon"; import { CALENDAR_TYPES, @@ -14,54 +16,25 @@ import style from "./bl-calendar.css"; import { Calendar, CalendarDate, - CalendarType, - CalendarView, CalendarDay, - DayValues, + CalendarView, RangePickerDates, } from "./bl-calendar.types"; +export const blCalendarChangedEvent = "bl-calendar-change"; + /** * @tag bl-calendar * @summary Baklava Calendar component **/ @customElement("bl-calendar") -export default class BlCalendar extends LitElement { - /** - * Defines the calendar types, available types are single, multiple and range - */ - @property() - type: CalendarType = CALENDAR_TYPES.SINGLE; - - /** - * Defines the minimum date value for the calendar - */ - @property({ type: Date, attribute: "min-date", reflect: true }) - minDate: Date; - - /** - * Defines the maximum date value for the calendar - */ - @property({ type: Date, attribute: "max-date", reflect: true }) - maxDate: Date; - - /** - * Defines the start day of the calendar (1 defines monday) - */ - @property({ type: Number, attribute: "start-of-week", reflect: true }) - startOfWeek: DayValues = 0; - - /** - * Defines the unselectable dates for calendar - */ - @property({ type: Array, attribute: "disabled-dates", reflect: true }) - disabledDates: Date[]; - - /** - * Defines the calendar language - */ - @property() - locale: string = document.documentElement.lang; +export default class BlCalendar extends DatepickerCalendarMixin { + constructor() { + super(); + window.addEventListener(blDatepickerClearSelectedDatesEvent, () => + this.handleClearSelectedDates() + ); + } @state() private _selectedDates: CalendarDate[] = []; @@ -87,24 +60,45 @@ export default class BlCalendar extends LitElement { @state() private _calendarDays: CalendarDay[] = []; - private _defaultValue: Date | Date[]; - - /** - * Defines the default selected date value for the calendar - */ - @property({ type: Array, attribute: "default-value", reflect: true }) - get defaultValue(): Date | Date[] { - return this._defaultValue; - } - set defaultValue(defaultValue) { - if (this.type === CALENDAR_TYPES.SINGLE && Array.isArray(defaultValue)) { - console.warn("Invalid prop value for defaultValue"); - } else if (this.defaultValue) { - if (Array.isArray(this.defaultValue)) { - this._selectedDates = { ...this.defaultValue }; - } else this._selectedDates = [this.defaultValue]; + @property({ attribute: "default-value", reflect: true }) + set defaultValue(defaultValue: Date | Date[]) { + if (!this._defaultValue) { + if (this.type === CALENDAR_TYPES.SINGLE && Array.isArray(defaultValue)) { + console.warn("Invalid prop value.Default value should be Date"); + } else if ( + (this.type === CALENDAR_TYPES.MULTIPLE || this.type === CALENDAR_TYPES.RANGE) && + !Array.isArray(defaultValue) + ) { + console.warn("Invalid prop value.Default value should be Date array"); + } else if ( + this.type === CALENDAR_TYPES.RANGE && + Array.isArray(defaultValue) && + (defaultValue.length < 0 || defaultValue.length > 2) + ) { + console.warn("Invalid prop value.Default value should be two Date items"); + } else { + this._defaultValue = defaultValue; + if (this.type === CALENDAR_TYPES.SINGLE && !Array.isArray(this._defaultValue)) { + this._selectedDates = [this._defaultValue]; + } else if (this.type === CALENDAR_TYPES.MULTIPLE && Array.isArray(this._defaultValue)) { + this._selectedDates = this._defaultValue; + } else if (this.type === CALENDAR_TYPES.RANGE && Array.isArray(this._defaultValue)) { + this._selectedDates = this._defaultValue; + this._selectedRangeDates.startDate = this._defaultValue[0]; + this._selectedRangeDates.endDate = this._defaultValue[1]; + this.setHoverClass(); + } + this._onBlCalendarChange(this._selectedDates); + } } } + + public handleClearSelectedDates = () => { + this._selectedDates = []; + this._onBlCalendarChange([]); + this.clearRangePickerStyles(); + }; + get months() { return [...Array(12).keys()].map(month => { return { @@ -126,7 +120,7 @@ export default class BlCalendar extends LitElement { /** * Fires when date selection changes */ - @event("bl-calendar-change") private _onBlCalendarChange: EventDispatcher; + @event(blCalendarChangedEvent) private _onBlCalendarChange: EventDispatcher; static get styles(): CSSResultGroup { return [style]; } @@ -437,41 +431,41 @@ export default class BlCalendar extends LitElement { return html`
${key}
`; })}
- ${[...Array(valuesArray[0].length).keys()].map(key => { - return html`
- ${valuesArray.map(values => { - const date = values[key]; - const isSelectedDay = this.checkIfSelectedDate(date); - const isDayToday = this.checkIfDateIsToday(date); - const isDisabledDay = this.checkIfDateIsDisabled(date); - - const classes = classMap({ - "day": true, - "calendar-text": true, - "today-day": isDayToday, - "selected-day": isSelectedDay, - "other-month-day": values[key].getMonth() !== this._calendarMonth, - "disabled-day": isDisabledDay, - }); - - return html` -
- - ${date.getDate()} - -
- `; - })} -
`; - })} + ${[...Array(valuesArray[0].length).keys()].map(key => { + return html`
+ ${valuesArray.map(values => { + const date = values[key]; + const isSelectedDay = this.checkIfSelectedDate(date); + const isDayToday = this.checkIfDateIsToday(date); + const isDisabledDay = this.checkIfDateIsDisabled(date); + + const classes = classMap({ + "day": true, + "calendar-text": true, + "today-day": isDayToday, + "selected-day": isSelectedDay, + "other-month-day": values[key].getMonth() !== this._calendarMonth, + "disabled-day": isDisabledDay, + }); + + return html` +
+ + ${date.getDate()} + +
+ `; + })} +
`; + })}
`; } else if (calendarView === CALENDAR_VIEWS.MONTHS) { diff --git a/src/components/datepicker/bl-datepicker.css b/src/components/datepicker/bl-datepicker.css new file mode 100644 index 00000000..dd97b663 --- /dev/null +++ b/src/components/datepicker/bl-datepicker.css @@ -0,0 +1,118 @@ + +:host{ + width: fit-content; + display: block; + +} + +.datepicker-content{ + display: flex; + flex-direction: column; + gap: 8px; + width: fit-content; + + --icon-size: var(--line-height); + --icon-color: var(--bl-color-neutral-light); + +} + +.input-container { + position: relative; + display: inline-block; +} + +.input-wrapper { + position: relative; + width: 200px; /* Adjust the width as needed */ + height: 1.5em; + overflow: hidden; +} + +.input-wrapper input { + width: 100%; + height: 100%; + padding-right: 1em; + border: 1px solid #ccc; + box-sizing: border-box; + background: transparent; + position: absolute; + top: 0; + left: 0; + z-index: 1; +} + +.input-wrapper .input-value { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + width: 100%; + height: 100%; + box-sizing: border-box; + padding: 0.5em; + border: 1px solid #ccc; + background: white; + pointer-events: none; /* Prevent interaction */ + z-index: 0; +} + + +.a{ + margin: 0; + list-style: none; + flex: 1; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; +} + +.datepicker-input { + width: 314px; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + flex: 1; +} + +.icon-container{ + display:flex; + gap: 5px; + align-items: center; +} + +.icon { + display: flex; + align-items: center; + gap: var(--icon-gap); + flex-basis: var(--icon-size); + align-self: center; + margin-right: var(--label-padding); + font-size: var(--icon-size); + color: var(--icon-color); + height: var(--icon-size); +} + +.popover { + --left: 0; + --top: 0; + + position: var(--popover-position); + border: solid 1px var(--border-color); + background-color: var(--background-color); + font: var(--bl-font-title-3-regular); + border-radius: var(--bl-border-radius-s); + padding: var(--menu-padding); + outline: none; + box-sizing: border-box; + max-height: var(--menu-height); + overflow-y: auto; + display: none; + flex-direction: column; + z-index: var(--bl-index-popover); + width: 100%; + top: var(--top); + left: var(--left); +} + +.show-popover{ + display: block; +} diff --git a/src/components/datepicker/bl-datepicker.stories.mdx b/src/components/datepicker/bl-datepicker.stories.mdx new file mode 100644 index 00000000..f0e61180 --- /dev/null +++ b/src/components/datepicker/bl-datepicker.stories.mdx @@ -0,0 +1,95 @@ +import { html } from 'lit'; +import { Meta, Canvas, ArgsTable, Story } from '@storybook/addon-docs'; +import { ifDefined } from 'lit/directives/if-defined.js'; +import { unsafeHTML } from 'lit/directives/unsafe-html.js'; + + + +export const DatepickerTemplate = (args) => html`${unsafeHTML(args.content)}` + + +export const Template = (args) => html`${DatepickerTemplate({...args})}`; + + +# Datepicker + +[ADR](https://github.com/Trendyol/baklava/issues/894) +[Figma](https://www.figma.com/file/RrcLH0mWpIUy4vwuTlDeKN/Baklava-Design-Guide?type=design&node-id=1412-8914&mode=design&t=b0kU7tBfJQFvz2at-0) + +Datepicker renders the calendar component within itself and provides the functionality provided by the calendar component. + +### Usage + +* Datepicker has three types such as **single**,**multiple** and **range**.Default datepicker type is `single`.You can set datepicker type by using `type` attribute. +* Datepicker has **min-date** and **max-date** attributes.By entering these values,you can disable all dates before min-date property or will disable all dates after max-date property. +* Another attribute **disabled-dates** is also restrict the dates that can be selected on the datepicker. +* Attribute **start-of-date** defines the days of the week, corresponding to 0 Sundays and 6 Saturdays. By entering this, you can choose from which day the datepicker will create the datepicker view. + + +## Datepicker Types + +### Single Type Datepicker + +Default datepicker type is `single` and you can only select a single day from datepicker. + + + + {Template.bind({})} + + + +### Multiple Type Datepicker + +You can select multiple days from Datepicker. + + + + {Template.bind({})} + + + + +### Range Type Datepicker + +You can select date range from Datepicker. + + + + {Template.bind({})} + + + +### Disabled Dates + +You can set dates which you want to disable from Datepicker. + + + + {Template.bind({})} + + + + +## Reference + + diff --git a/src/components/datepicker/bl-datepicker.ts b/src/components/datepicker/bl-datepicker.ts new file mode 100644 index 00000000..c595ea77 --- /dev/null +++ b/src/components/datepicker/bl-datepicker.ts @@ -0,0 +1,235 @@ +import { CSSResultGroup, html } from "lit"; +import { customElement, property, state, query } from "lit/decorators.js"; +import { classMap } from "lit/directives/class-map.js"; +import { ifDefined } from "lit/directives/if-defined.js"; +import { autoUpdate, computePosition, flip, MiddlewareState, offset, size } from "@floating-ui/dom"; +import DatepickerCalendarMixin from "../../mixins/datepicker-calendar-mixin/datepicker-calendar-mixin"; +import { event, EventDispatcher } from "../../utilities/event"; +import "../calendar/bl-calendar"; +import { CALENDAR_TYPES } from "../calendar/bl-calendar.constant"; +import { CalendarDate } from "../calendar/bl-calendar.types"; +import "../input/bl-input"; +import { CleanUpFunction } from "../select/bl-select"; +import "../tooltip/bl-tooltip"; +import style from "./bl-datepicker.css"; + +export const blDatepickerTag = "bl-datepicker"; +export const blDatepickerClearSelectedDatesEvent = "clear-datepicker-event"; +export const blDatepickerChangedEvent = "bl-datepicker-change"; + +/** + * @tag bl-datepicker + * @summary Baklava DatePicker component + **/ +@customElement(blDatepickerTag) +export default class BlDatepicker extends DatepickerCalendarMixin { + /** + * Defines the datepicker input placeholder + */ + @property() + placeholder: string; + /** + * Defines the datepicker input label + */ + @property() + label: string; + + @state() + private _isPopoverOpen = false; + + @state() + private _value = ""; + + @state() + private _selectedDates: CalendarDate[] = []; + @state() + private _floatingDateCount: number = 0; + @state() + private _fittingDateCount: number = 0; + + private _cleanUpPopover: CleanUpFunction | null = null; + + @query(".popover") + private _popover: HTMLElement; + + @query(".datepicker-input") + private _selectInput: HTMLElement; + static get styles(): CSSResultGroup { + return [style]; + } + /** + * Fires when date selection is cleared + */ + @event(blDatepickerClearSelectedDatesEvent) private _onBlDatepickerCleared: EventDispatcher<[]>; + /** + * Fires when date selection is changed + */ + @event(blDatepickerChangedEvent) private _onBlDatepickerChanged: EventDispatcher; + setDatePickerInput(dates: Date[] | []) { + if (!dates.length) { + this._value = ""; + } else { + this._selectedDates = dates; + if (this.type === CALENDAR_TYPES.SINGLE && this._selectedDates.length === 1) { + this._value = `${this.formatDate(this._selectedDates[0])}`; + } else if (this.type === CALENDAR_TYPES.MULTIPLE) { + const values: string[] = []; + + this._selectedDates.slice(0, this._fittingDateCount).forEach(date => { + values.push(this.formatDate(date)); + }); + this._value = values.join(",") + (this._floatingDateCount >= 0 ? " ,..." : ""); + } else if (this.type === CALENDAR_TYPES.RANGE && dates.length === 2) { + this._value = `${this.formatDate(this._selectedDates[0])} - ${this.formatDate( + this._selectedDates[1] + )}`; + } + } + this._onBlDatepickerChanged(this._selectedDates); + this.requestUpdate(); + } + + formatDate(date: Date) { + const day = String(date.getDate()).padStart(2, "0"); + const month = String(date.getMonth() + 1).padStart(2, "0"); + const year = date.getFullYear(); + + return `${day}/${month}/${year}`; + } + clearDatepicker() { + this._onBlDatepickerCleared([]); + } + + open() { + setTimeout(() => { + document.activeElement?.shadowRoot?.querySelector("bl-input")?.focus(); + }, 100); + + this._isPopoverOpen = true; + this._setupPopover(); + document.addEventListener("click", this._interactOutsideHandler, true); + document.addEventListener("focus", this._interactOutsideHandler, true); + } + + close() { + this._isPopoverOpen = false; + this._cleanUpPopover && this._cleanUpPopover(); + + document.removeEventListener("click", this._interactOutsideHandler, true); + document.removeEventListener("focus", this._interactOutsideHandler, true); + } + private _interactOutsideHandler = (event: MouseEvent | FocusEvent) => { + const eventPath = event.composedPath() as HTMLElement[]; + + if (!eventPath?.find(el => el.tagName === "BL-DATEPICKER")?.contains(this)) { + this.close(); + } + }; + + private _setupPopover() { + this._cleanUpPopover = autoUpdate(this._selectInput, this._popover, () => { + computePosition(this._selectInput, this._popover, { + placement: "bottom", + strategy: "fixed", + middleware: [ + flip(), + offset(8), + size({ + apply(args: MiddlewareState) { + Object.assign(args.elements.floating.style, { + width: `${args.elements.reference.getBoundingClientRect().width}px`, + }); + }, + }), + ], + }).then(({ x, y }) => { + this._popover.style.setProperty("--left", `${x}px`); + this._popover.style.setProperty("--top", `${y}px`); + }); + }); + } + private _togglePopover() { + this._isPopoverOpen ? this.close() : this.open(); + } + + firstUpdated() { + const element = this.shadowRoot?.getElementById("datepicker-input"); + + element?.addEventListener("blur", () => { + element.focus(); + }); + } + render() { + const datepickerInput = this.shadowRoot?.getElementById("datepicker-input"); + const iconsContainer = this.shadowRoot?.getElementById("icon-container"); + const datesTextTotalWidth = + (datepickerInput?.offsetWidth as number) - (iconsContainer?.offsetWidth as number); + + this._fittingDateCount = parseInt(String(datesTextTotalWidth / 90)); + + this._floatingDateCount = this._selectedDates.length - this._fittingDateCount; + + const renderCalendar = html` +
+ +
+ `; + + const additionalDates = this._selectedDates + .slice(this._fittingDateCount) + .map(date => { + return this.formatDate(date); + }) + .join(","); + + const additionalDatesView = + this._floatingDateCount > 0 + ? html` + +${this._floatingDateCount} + ${additionalDates}` + : ""; + + return html` +
+ +
+ ${additionalDatesView} + this.clearDatepicker()} + > + +
+
+ ${renderCalendar} +
+ `; + } +} diff --git a/src/mixins/datepicker-calendar-mixin/datepicker-calendar-mixin.ts b/src/mixins/datepicker-calendar-mixin/datepicker-calendar-mixin.ts new file mode 100644 index 00000000..2e33e628 --- /dev/null +++ b/src/mixins/datepicker-calendar-mixin/datepicker-calendar-mixin.ts @@ -0,0 +1,47 @@ +import { LitElement } from "lit"; +import { property } from "lit/decorators.js"; +import { CalendarType, DayValues } from "../../components/calendar/bl-calendar.types"; + +export default class DatepickerCalendarMixin extends LitElement { + /** + * Defines the calendar types, available types are single, multiple and range + */ + @property() + type: CalendarType; + + /** + * Defines the minimum date value for the calendar + */ + @property({ type: Date, attribute: "min-date", reflect: true }) + minDate: Date; + + /** + * Defines the maximum date value for the calendar + */ + @property({ type: Date, attribute: "max-date", reflect: true }) + maxDate: Date; + + /** + * Defines the start day of the calendar (1 defines monday) + */ + @property({ type: Number, attribute: "start-of-week", reflect: true }) + startOfWeek: DayValues = 0; + + /** + * Defines the unselectable dates for calendar + */ + @property({ type: Array, attribute: "disabled-dates", reflect: true }) + disabledDates: Date[]; + + /** + * Defines the calendar language + */ + @property() + locale: string = document.documentElement.lang; + + _defaultValue: Date | Date[]; + + get defaultValue() { + return this._defaultValue; + } +} From 75d7686e19c55ecd1371a3db766f9582d613d709 Mon Sep 17 00:00:00 2001 From: "dilan.dogan2" Date: Fri, 23 Aug 2024 11:15:32 +0300 Subject: [PATCH 02/35] feat(datepicker): run prettier --- playground/template.html | 1 - src/components/datepicker/bl-datepicker.css | 16 ++++++---------- 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/playground/template.html b/playground/template.html index da167bba..68ed6797 100644 --- a/playground/template.html +++ b/playground/template.html @@ -35,6 +35,5 @@

Baklava Playground

Baklava is ready - sdfghgfdsasdfggfdsasdfghgfdsasdfggfdsaasdfghgfdsasdfggfdsaaasdfghgfdsasdfggfdsaaaaa diff --git a/src/components/datepicker/bl-datepicker.css b/src/components/datepicker/bl-datepicker.css index dd97b663..dd5dd1d4 100644 --- a/src/components/datepicker/bl-datepicker.css +++ b/src/components/datepicker/bl-datepicker.css @@ -1,11 +1,9 @@ - -:host{ +:host { width: fit-content; display: block; - } -.datepicker-content{ +.datepicker-content { display: flex; flex-direction: column; gap: 8px; @@ -13,7 +11,6 @@ --icon-size: var(--line-height); --icon-color: var(--bl-color-neutral-light); - } .input-container { @@ -55,8 +52,7 @@ z-index: 0; } - -.a{ +.a { margin: 0; list-style: none; flex: 1; @@ -73,8 +69,8 @@ flex: 1; } -.icon-container{ - display:flex; +.icon-container { + display: flex; gap: 5px; align-items: center; } @@ -113,6 +109,6 @@ left: var(--left); } -.show-popover{ +.show-popover { display: block; } From 644fd9bfbfe5967b56571662fe96add7e84b8301 Mon Sep 17 00:00:00 2001 From: "dilan.dogan2" Date: Mon, 2 Sep 2024 23:49:49 +0300 Subject: [PATCH 03/35] feat(datepicker): fix storybook bug --- src/components/calendar/bl-calendar.ts | 37 ++++++-- src/components/datepicker/bl-datepicker.css | 51 +---------- .../datepicker/bl-datepicker.stories.mdx | 10 +-- src/components/datepicker/bl-datepicker.ts | 88 ++++++++++++++----- src/components/input/bl-input.css | 1 + 5 files changed, 101 insertions(+), 86 deletions(-) diff --git a/src/components/calendar/bl-calendar.ts b/src/components/calendar/bl-calendar.ts index 8eaf15f0..580b01fe 100644 --- a/src/components/calendar/bl-calendar.ts +++ b/src/components/calendar/bl-calendar.ts @@ -29,11 +29,14 @@ export const blCalendarChangedEvent = "bl-calendar-change"; **/ @customElement("bl-calendar") export default class BlCalendar extends DatepickerCalendarMixin { - constructor() { - super(); - window.addEventListener(blDatepickerClearSelectedDatesEvent, () => - this.handleClearSelectedDates() - ); + connectedCallback() { + super.connectedCallback(); + window.addEventListener(blDatepickerClearSelectedDatesEvent, this.handleClearSelectedDates); + } + + disconnectedCallback() { + super.disconnectedCallback(); + window.removeEventListener(blDatepickerClearSelectedDatesEvent, this.handleClearSelectedDates); } @state() @@ -64,18 +67,24 @@ export default class BlCalendar extends DatepickerCalendarMixin { set defaultValue(defaultValue: Date | Date[]) { if (!this._defaultValue) { if (this.type === CALENDAR_TYPES.SINGLE && Array.isArray(defaultValue)) { - console.warn("Invalid prop value.Default value should be Date"); + console.warn( + "'defaultValue' must be of type Date when the date selection mode is set to single." + ); } else if ( (this.type === CALENDAR_TYPES.MULTIPLE || this.type === CALENDAR_TYPES.RANGE) && !Array.isArray(defaultValue) ) { - console.warn("Invalid prop value.Default value should be Date array"); + console.warn( + "'defaultValue' must be an array of two Date objects when the date selection mode is set to range." + ); } else if ( this.type === CALENDAR_TYPES.RANGE && Array.isArray(defaultValue) && (defaultValue.length < 0 || defaultValue.length > 2) ) { - console.warn("Invalid prop value.Default value should be two Date items"); + console.warn( + "'defaultValue' must be an array of Date objects when the date selection mode is set to multiple." + ); } else { this._defaultValue = defaultValue; if (this.type === CALENDAR_TYPES.SINGLE && !Array.isArray(this._defaultValue)) { @@ -109,6 +118,7 @@ export default class BlCalendar extends DatepickerCalendarMixin { }; }); } + get days() { return [...Array(7).keys()].map(day => { return { @@ -124,12 +134,15 @@ export default class BlCalendar extends DatepickerCalendarMixin { static get styles(): CSSResultGroup { return [style]; } + getDayNumInAMonth(year: number, month: number) { return new Date(year, month + 1, 0).getDate(); } + getWeekDayOfDate(year: number, month: number) { return new Date(year, month, 1).getDay(); } + setPreviousCalendarView() { this.clearRangePickerStyles(); if (this._calendarView === CALENDAR_VIEWS.DAYS) { @@ -151,6 +164,7 @@ export default class BlCalendar extends DatepickerCalendarMixin { this.setHoverClass(); } } + setNextCalendarView() { this.clearRangePickerStyles(); if (this._calendarView === CALENDAR_VIEWS.DAYS) { @@ -203,6 +217,7 @@ export default class BlCalendar extends DatepickerCalendarMixin { ); } } + clearRangePickerStyles() { this.shadowRoot?.querySelectorAll(".range-day").forEach(day => { day.classList.remove("range-day"); @@ -214,6 +229,7 @@ export default class BlCalendar extends DatepickerCalendarMixin { day.classList.remove("range-end-day"); }); } + handleDate(date: CalendarDate) { if (this.type !== CALENDAR_TYPES.RANGE) { if (date.getMonth() < this._calendarMonth) { @@ -230,14 +246,15 @@ export default class BlCalendar extends DatepickerCalendarMixin { } else if (this.type === CALENDAR_TYPES.RANGE) { this.handleRangeSelectCalendar(date); } - this._onBlCalendarChange(this._selectedDates); this.requestUpdate(); } + handleSingleSelectCalendar(calendarDate: CalendarDate) { this._selectedDates.splice(0, 1); this._selectedDates.push(calendarDate); } + handleMultipleSelectCalendar(calendarDate: CalendarDate) { const dateExist = this._selectedDates.find(function (selectedDate) { return selectedDate.getTime() === calendarDate.getTime(); @@ -250,6 +267,7 @@ export default class BlCalendar extends DatepickerCalendarMixin { ); else this._selectedDates.push(calendarDate); } + handleRangeSelectCalendar(calendarDate: CalendarDate) { if (!this._selectedRangeDates.startDate) { this._selectedRangeDates.startDate = calendarDate; @@ -285,6 +303,7 @@ export default class BlCalendar extends DatepickerCalendarMixin { return !!day; } + checkIfDateIsToday(calendarDate: CalendarDate) { const today = new Date(); diff --git a/src/components/datepicker/bl-datepicker.css b/src/components/datepicker/bl-datepicker.css index dd5dd1d4..7634a57a 100644 --- a/src/components/datepicker/bl-datepicker.css +++ b/src/components/datepicker/bl-datepicker.css @@ -1,6 +1,7 @@ :host { width: fit-content; display: block; + --bl-input-cursor: pointer; } .datepicker-content { @@ -13,54 +14,6 @@ --icon-color: var(--bl-color-neutral-light); } -.input-container { - position: relative; - display: inline-block; -} - -.input-wrapper { - position: relative; - width: 200px; /* Adjust the width as needed */ - height: 1.5em; - overflow: hidden; -} - -.input-wrapper input { - width: 100%; - height: 100%; - padding-right: 1em; - border: 1px solid #ccc; - box-sizing: border-box; - background: transparent; - position: absolute; - top: 0; - left: 0; - z-index: 1; -} - -.input-wrapper .input-value { - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - width: 100%; - height: 100%; - box-sizing: border-box; - padding: 0.5em; - border: 1px solid #ccc; - background: white; - pointer-events: none; /* Prevent interaction */ - z-index: 0; -} - -.a { - margin: 0; - list-style: none; - flex: 1; - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; -} - .datepicker-input { width: 314px; overflow: hidden; @@ -75,7 +28,7 @@ align-items: center; } -.icon { +.calendarIcon { display: flex; align-items: center; gap: var(--icon-gap); diff --git a/src/components/datepicker/bl-datepicker.stories.mdx b/src/components/datepicker/bl-datepicker.stories.mdx index f0e61180..0d16ad71 100644 --- a/src/components/datepicker/bl-datepicker.stories.mdx +++ b/src/components/datepicker/bl-datepicker.stories.mdx @@ -53,7 +53,7 @@ Datepicker renders the calendar component within itself and provides the functio Default datepicker type is `single` and you can only select a single day from datepicker. - + {Template.bind({})} @@ -63,7 +63,7 @@ Default datepicker type is `single` and you can only select a single day from da You can select multiple days from Datepicker. - + {Template.bind({})} @@ -74,7 +74,7 @@ You can select multiple days from Datepicker. You can select date range from Datepicker. - + {Template.bind({})} @@ -84,12 +84,10 @@ You can select date range from Datepicker. You can set dates which you want to disable from Datepicker. - + {Template.bind({})} - - ## Reference diff --git a/src/components/datepicker/bl-datepicker.ts b/src/components/datepicker/bl-datepicker.ts index c595ea77..0cc5092e 100644 --- a/src/components/datepicker/bl-datepicker.ts +++ b/src/components/datepicker/bl-datepicker.ts @@ -33,6 +33,11 @@ export default class BlDatepicker extends DatepickerCalendarMixin { */ @property() label: string; + /** + * Defines the custom formatter function + */ + @property({ type: Function }) + valueFormatter: ((dates: CalendarDate[], type: string) => string) | null = null; @state() private _isPopoverOpen = false; @@ -65,26 +70,37 @@ export default class BlDatepicker extends DatepickerCalendarMixin { * Fires when date selection is changed */ @event(blDatepickerChangedEvent) private _onBlDatepickerChanged: EventDispatcher; + + private _defaultValueFormatter(dates: CalendarDate[]) { + if (this.type === CALENDAR_TYPES.SINGLE && this._selectedDates.length === 1) { + this._value = `${this.formatDate(this._selectedDates[0])}`; + } else if (this.type === CALENDAR_TYPES.MULTIPLE) { + const values: string[] = []; + + this._selectedDates.slice(0, this._fittingDateCount).forEach(date => { + values.push(this.formatDate(date)); + }); + this._value = values.join(",") + (this._floatingDateCount >= 0 ? " ,..." : ""); + } else if (this.type === CALENDAR_TYPES.RANGE && dates.length === 2) { + this._value = `${this.formatDate(this._selectedDates[0])} - ${this.formatDate( + this._selectedDates[1] + )}`; + } + } + setDatePickerInput(dates: Date[] | []) { if (!dates.length) { this._value = ""; } else { this._selectedDates = dates; - if (this.type === CALENDAR_TYPES.SINGLE && this._selectedDates.length === 1) { - this._value = `${this.formatDate(this._selectedDates[0])}`; - } else if (this.type === CALENDAR_TYPES.MULTIPLE) { - const values: string[] = []; - - this._selectedDates.slice(0, this._fittingDateCount).forEach(date => { - values.push(this.formatDate(date)); - }); - this._value = values.join(",") + (this._floatingDateCount >= 0 ? " ,..." : ""); - } else if (this.type === CALENDAR_TYPES.RANGE && dates.length === 2) { - this._value = `${this.formatDate(this._selectedDates[0])} - ${this.formatDate( - this._selectedDates[1] - )}`; + + if (this.valueFormatter) { + this._value = this.valueFormatter(this._selectedDates, this.type); + } else { + this._defaultValueFormatter(this._selectedDates); } } + this._onBlDatepickerChanged(this._selectedDates); this.requestUpdate(); } @@ -100,7 +116,7 @@ export default class BlDatepicker extends DatepickerCalendarMixin { this._onBlDatepickerCleared([]); } - open() { + openPopover() { setTimeout(() => { document.activeElement?.shadowRoot?.querySelector("bl-input")?.focus(); }, 100); @@ -111,7 +127,7 @@ export default class BlDatepicker extends DatepickerCalendarMixin { document.addEventListener("focus", this._interactOutsideHandler, true); } - close() { + closePopover() { this._isPopoverOpen = false; this._cleanUpPopover && this._cleanUpPopover(); @@ -122,7 +138,7 @@ export default class BlDatepicker extends DatepickerCalendarMixin { const eventPath = event.composedPath() as HTMLElement[]; if (!eventPath?.find(el => el.tagName === "BL-DATEPICKER")?.contains(this)) { - this.close(); + this.closePopover(); } }; @@ -149,16 +165,40 @@ export default class BlDatepicker extends DatepickerCalendarMixin { }); } private _togglePopover() { - this._isPopoverOpen ? this.close() : this.open(); + this._isPopoverOpen ? this.closePopover() : this.openPopover(); } firstUpdated() { const element = this.shadowRoot?.getElementById("datepicker-input"); - element?.addEventListener("blur", () => { - element.focus(); + element?.addEventListener("mousedown", event => { + event.preventDefault(); // Prevent focus from causing text selection + }); + + // If input loses focus and the calendar is clicked, prevent the focus loss + document.addEventListener("mousedown", event => { + const path = event.composedPath(); + + if (path.includes(this._popover) || (element && path.includes(element))) { + event.preventDefault(); + + element?.focus(); + } }); } + + formatStringWithLineBreaks(str: string) { + const parts = str.split(","); + + // Create an array of elements + return parts.reduce((acc, part, index) => { + if (index > 0 && index % 3 === 0) { + acc.push(html`
`); + } + acc.push(html`${part}, `); + return acc; + }, []); + } render() { const datepickerInput = this.shadowRoot?.getElementById("datepicker-input"); const iconsContainer = this.shadowRoot?.getElementById("icon-container"); @@ -182,6 +222,7 @@ export default class BlDatepicker extends DatepickerCalendarMixin { .minDate=${this.minDate} .maxDate="${this.maxDate}" .startOfWeek="${this.startOfWeek}" + .disabledDates="${this.disabledDates}" @bl-calendar-change="${(event: CustomEvent) => this.setDatePickerInput(event.detail)}" > @@ -194,12 +235,14 @@ export default class BlDatepicker extends DatepickerCalendarMixin { }) .join(","); + const formattedAdditionalDates = this.formatStringWithLineBreaks(additionalDates); + const additionalDatesView = this._floatingDateCount > 0 ? html` +${this._floatingDateCount} - ${additionalDates}` +
${formattedAdditionalDates}
+ ` : ""; return html` @@ -215,6 +258,7 @@ export default class BlDatepicker extends DatepickerCalendarMixin { aria-expanded="${this._isPopoverOpen}" aria-labelledby="label" @click=${this._togglePopover} + readOnly >
${additionalDatesView} @@ -225,7 +269,7 @@ export default class BlDatepicker extends DatepickerCalendarMixin { icon="close" @click=${() => this.clearDatepicker()} > - +
${renderCalendar} diff --git a/src/components/input/bl-input.css b/src/components/input/bl-input.css index c8d9a452..3316be79 100644 --- a/src/components/input/bl-input.css +++ b/src/components/input/bl-input.css @@ -140,6 +140,7 @@ input { color: var(--text-color); -webkit-text-fill-color: var(--text-color); background-color: transparent; + cursor: var(--bl-input-cursor, not-allowed); } input::-webkit-credentials-auto-fill-button { From 04f7c5253cb35cac8c611aaf29e2d0f50145c7b9 Mon Sep 17 00:00:00 2001 From: "dilan.dogan2" Date: Tue, 3 Sep 2024 09:51:23 +0300 Subject: [PATCH 04/35] feat(datepicker): typescript type bug fix --- src/components/datepicker/bl-datepicker.ts | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/components/datepicker/bl-datepicker.ts b/src/components/datepicker/bl-datepicker.ts index 0cc5092e..d726fdbc 100644 --- a/src/components/datepicker/bl-datepicker.ts +++ b/src/components/datepicker/bl-datepicker.ts @@ -1,4 +1,4 @@ -import { CSSResultGroup, html } from "lit"; +import { CSSResultGroup, html, TemplateResult } from "lit"; import { customElement, property, state, query } from "lit/decorators.js"; import { classMap } from "lit/directives/class-map.js"; import { ifDefined } from "lit/directives/if-defined.js"; @@ -187,18 +187,19 @@ export default class BlDatepicker extends DatepickerCalendarMixin { }); } - formatStringWithLineBreaks(str: string) { + formatStringWithLineBreaks(str: string): TemplateResult[] { const parts = str.split(","); - // Create an array of elements - return parts.reduce((acc, part, index) => { + // Explicitly type the accumulator as an array of TemplateResults + return parts.reduce((acc, part, index) => { if (index > 0 && index % 3 === 0) { acc.push(html`
`); } - acc.push(html`${part}, `); + acc.push(html`${part.trim()}, `); return acc; }, []); } + render() { const datepickerInput = this.shadowRoot?.getElementById("datepicker-input"); const iconsContainer = this.shadowRoot?.getElementById("icon-container"); From d02eada0c799e4d579d73734c6fd3b8b1b719072 Mon Sep 17 00:00:00 2001 From: "dilan.dogan2" Date: Tue, 3 Sep 2024 10:51:40 +0300 Subject: [PATCH 05/35] feat(datepicker): ui review updates --- playground/template.html | 2 +- src/components/datepicker/bl-datepicker.css | 2 -- src/components/datepicker/bl-datepicker.ts | 2 +- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/playground/template.html b/playground/template.html index 68ed6797..7b2cc17a 100644 --- a/playground/template.html +++ b/playground/template.html @@ -34,6 +34,6 @@

Baklava Playground

Baklava is ready - + diff --git a/src/components/datepicker/bl-datepicker.css b/src/components/datepicker/bl-datepicker.css index 7634a57a..307920c6 100644 --- a/src/components/datepicker/bl-datepicker.css +++ b/src/components/datepicker/bl-datepicker.css @@ -16,10 +16,8 @@ .datepicker-input { width: 314px; - overflow: hidden; white-space: nowrap; text-overflow: ellipsis; - flex: 1; } .icon-container { diff --git a/src/components/datepicker/bl-datepicker.ts b/src/components/datepicker/bl-datepicker.ts index d726fdbc..c67e1032 100644 --- a/src/components/datepicker/bl-datepicker.ts +++ b/src/components/datepicker/bl-datepicker.ts @@ -267,7 +267,7 @@ export default class BlDatepicker extends DatepickerCalendarMixin { size="small" variant="tertiary" kind="neutral" - icon="close" + icon=${ifDefined(this._selectedDates.length ? "close" : undefined)} @click=${() => this.clearDatepicker()} > From 20a26451267e219f9bc21011e32246572b7f4b99 Mon Sep 17 00:00:00 2001 From: "dilan.dogan2" Date: Wed, 4 Sep 2024 16:18:23 +0300 Subject: [PATCH 06/35] feat(datepicker): showing floating date count fix --- playground/template.html | 3 +- src/components/datepicker/bl-datepicker.css | 8 ++-- src/components/datepicker/bl-datepicker.ts | 50 +++++++++++---------- 3 files changed, 31 insertions(+), 30 deletions(-) diff --git a/playground/template.html b/playground/template.html index 7b2cc17a..823d8883 100644 --- a/playground/template.html +++ b/playground/template.html @@ -32,8 +32,9 @@

Baklava Playground

npm run serve.

- Baklava is ready + Baklava is ready + diff --git a/src/components/datepicker/bl-datepicker.css b/src/components/datepicker/bl-datepicker.css index 307920c6..cda323a0 100644 --- a/src/components/datepicker/bl-datepicker.css +++ b/src/components/datepicker/bl-datepicker.css @@ -1,7 +1,9 @@ :host { width: fit-content; - display: block; --bl-input-cursor: pointer; + --icon-size: var(--line-height); + --icon-color: var(--bl-color-neutral-light); + position: relative; } .datepicker-content { @@ -9,9 +11,6 @@ flex-direction: column; gap: 8px; width: fit-content; - - --icon-size: var(--line-height); - --icon-color: var(--bl-color-neutral-light); } .datepicker-input { @@ -41,7 +40,6 @@ .popover { --left: 0; --top: 0; - position: var(--popover-position); border: solid 1px var(--border-color); background-color: var(--background-color); diff --git a/src/components/datepicker/bl-datepicker.ts b/src/components/datepicker/bl-datepicker.ts index c67e1032..64312668 100644 --- a/src/components/datepicker/bl-datepicker.ts +++ b/src/components/datepicker/bl-datepicker.ts @@ -72,6 +72,8 @@ export default class BlDatepicker extends DatepickerCalendarMixin { @event(blDatepickerChangedEvent) private _onBlDatepickerChanged: EventDispatcher; private _defaultValueFormatter(dates: CalendarDate[]) { + this.setFloatingDates(); + if (this.type === CALENDAR_TYPES.SINGLE && this._selectedDates.length === 1) { this._value = `${this.formatDate(this._selectedDates[0])}`; } else if (this.type === CALENDAR_TYPES.MULTIPLE) { @@ -80,7 +82,7 @@ export default class BlDatepicker extends DatepickerCalendarMixin { this._selectedDates.slice(0, this._fittingDateCount).forEach(date => { values.push(this.formatDate(date)); }); - this._value = values.join(",") + (this._floatingDateCount >= 0 ? " ,..." : ""); + this._value = values.join(",") + (this._floatingDateCount > 0 ? " ,..." : ""); } else if (this.type === CALENDAR_TYPES.RANGE && dates.length === 2) { this._value = `${this.formatDate(this._selectedDates[0])} - ${this.formatDate( this._selectedDates[1] @@ -88,6 +90,17 @@ export default class BlDatepicker extends DatepickerCalendarMixin { } } + setFloatingDates() { + const datepickerInput = this.shadowRoot?.getElementById("datepicker-input"); + const iconsContainer = this.shadowRoot?.getElementById("icon-container"); + const datesTextTotalWidth = + (datepickerInput?.offsetWidth as number) - (iconsContainer?.offsetWidth as number); + + this._fittingDateCount = parseInt(String(datesTextTotalWidth / 90)); + + this._floatingDateCount = this._selectedDates.length - this._fittingDateCount; + } + setDatePickerInput(dates: Date[] | []) { if (!dates.length) { this._value = ""; @@ -114,6 +127,9 @@ export default class BlDatepicker extends DatepickerCalendarMixin { } clearDatepicker() { this._onBlDatepickerCleared([]); + this._selectedDates = []; + this._value = ""; + this._floatingDateCount = 0; } openPopover() { @@ -172,10 +188,9 @@ export default class BlDatepicker extends DatepickerCalendarMixin { const element = this.shadowRoot?.getElementById("datepicker-input"); element?.addEventListener("mousedown", event => { - event.preventDefault(); // Prevent focus from causing text selection + event.preventDefault(); }); - // If input loses focus and the calendar is clicked, prevent the focus loss document.addEventListener("mousedown", event => { const path = event.composedPath(); @@ -190,7 +205,6 @@ export default class BlDatepicker extends DatepickerCalendarMixin { formatStringWithLineBreaks(str: string): TemplateResult[] { const parts = str.split(","); - // Explicitly type the accumulator as an array of TemplateResults return parts.reduce((acc, part, index) => { if (index > 0 && index % 3 === 0) { acc.push(html`
`); @@ -201,32 +215,20 @@ export default class BlDatepicker extends DatepickerCalendarMixin { } render() { - const datepickerInput = this.shadowRoot?.getElementById("datepicker-input"); - const iconsContainer = this.shadowRoot?.getElementById("icon-container"); - const datesTextTotalWidth = - (datepickerInput?.offsetWidth as number) - (iconsContainer?.offsetWidth as number); - - this._fittingDateCount = parseInt(String(datesTextTotalWidth / 90)); - - this._floatingDateCount = this._selectedDates.length - this._fittingDateCount; - const renderCalendar = html` -
- -
+ @bl-calendar-change="${(event: CustomEvent) => this.setDatePickerInput(event.detail)}" + > `; const additionalDates = this._selectedDates From 29eeb96e6f32b85ee72290180ac2e98c11935d03 Mon Sep 17 00:00:00 2001 From: "dilan.dogan2" Date: Wed, 4 Sep 2024 16:49:42 +0300 Subject: [PATCH 07/35] feat(datepicker): clear unnecessary codes --- src/components/datepicker/bl-datepicker.css | 8 +---- src/components/datepicker/bl-datepicker.ts | 34 ++------------------- 2 files changed, 4 insertions(+), 38 deletions(-) diff --git a/src/components/datepicker/bl-datepicker.css b/src/components/datepicker/bl-datepicker.css index cda323a0..4e4fb1c9 100644 --- a/src/components/datepicker/bl-datepicker.css +++ b/src/components/datepicker/bl-datepicker.css @@ -3,7 +3,7 @@ --bl-input-cursor: pointer; --icon-size: var(--line-height); --icon-color: var(--bl-color-neutral-light); - position: relative; + display: block; } .datepicker-content { @@ -38,8 +38,6 @@ } .popover { - --left: 0; - --top: 0; position: var(--popover-position); border: solid 1px var(--border-color); background-color: var(--background-color); @@ -51,11 +49,7 @@ max-height: var(--menu-height); overflow-y: auto; display: none; - flex-direction: column; z-index: var(--bl-index-popover); - width: 100%; - top: var(--top); - left: var(--left); } .show-popover { diff --git a/src/components/datepicker/bl-datepicker.ts b/src/components/datepicker/bl-datepicker.ts index 64312668..59e9e650 100644 --- a/src/components/datepicker/bl-datepicker.ts +++ b/src/components/datepicker/bl-datepicker.ts @@ -2,7 +2,6 @@ import { CSSResultGroup, html, TemplateResult } from "lit"; import { customElement, property, state, query } from "lit/decorators.js"; import { classMap } from "lit/directives/class-map.js"; import { ifDefined } from "lit/directives/if-defined.js"; -import { autoUpdate, computePosition, flip, MiddlewareState, offset, size } from "@floating-ui/dom"; import DatepickerCalendarMixin from "../../mixins/datepicker-calendar-mixin/datepicker-calendar-mixin"; import { event, EventDispatcher } from "../../utilities/event"; import "../calendar/bl-calendar"; @@ -57,8 +56,6 @@ export default class BlDatepicker extends DatepickerCalendarMixin { @query(".popover") private _popover: HTMLElement; - @query(".datepicker-input") - private _selectInput: HTMLElement; static get styles(): CSSResultGroup { return [style]; } @@ -133,12 +130,9 @@ export default class BlDatepicker extends DatepickerCalendarMixin { } openPopover() { - setTimeout(() => { - document.activeElement?.shadowRoot?.querySelector("bl-input")?.focus(); - }, 100); + document.activeElement?.shadowRoot?.querySelector("bl-input")?.focus(); this._isPopoverOpen = true; - this._setupPopover(); document.addEventListener("click", this._interactOutsideHandler, true); document.addEventListener("focus", this._interactOutsideHandler, true); } @@ -158,28 +152,6 @@ export default class BlDatepicker extends DatepickerCalendarMixin { } }; - private _setupPopover() { - this._cleanUpPopover = autoUpdate(this._selectInput, this._popover, () => { - computePosition(this._selectInput, this._popover, { - placement: "bottom", - strategy: "fixed", - middleware: [ - flip(), - offset(8), - size({ - apply(args: MiddlewareState) { - Object.assign(args.elements.floating.style, { - width: `${args.elements.reference.getBoundingClientRect().width}px`, - }); - }, - }), - ], - }).then(({ x, y }) => { - this._popover.style.setProperty("--left", `${x}px`); - this._popover.style.setProperty("--top", `${y}px`); - }); - }); - } private _togglePopover() { this._isPopoverOpen ? this.closePopover() : this.openPopover(); } @@ -202,7 +174,7 @@ export default class BlDatepicker extends DatepickerCalendarMixin { }); } - formatStringWithLineBreaks(str: string): TemplateResult[] { + formatAdditionalDates(str: string): TemplateResult[] { const parts = str.split(","); return parts.reduce((acc, part, index) => { @@ -238,7 +210,7 @@ export default class BlDatepicker extends DatepickerCalendarMixin { }) .join(","); - const formattedAdditionalDates = this.formatStringWithLineBreaks(additionalDates); + const formattedAdditionalDates = this.formatAdditionalDates(additionalDates); const additionalDatesView = this._floatingDateCount > 0 From d76d591893ecb6c39bc033fcab144148e0ae68de Mon Sep 17 00:00:00 2001 From: "dilan.dogan2" Date: Fri, 6 Sep 2024 11:55:00 +0300 Subject: [PATCH 08/35] feat(datepicker): coding improvments on bl-calendar component --- playground/template.html | 4 +- src/components/calendar/bl-calendar.css | 2 +- src/components/calendar/bl-calendar.ts | 372 +++++++++----------- src/components/datepicker/bl-datepicker.css | 8 + src/components/datepicker/bl-datepicker.ts | 77 ++-- 5 files changed, 234 insertions(+), 229 deletions(-) diff --git a/playground/template.html b/playground/template.html index 823d8883..bd0d2abb 100644 --- a/playground/template.html +++ b/playground/template.html @@ -33,8 +33,8 @@

Baklava Playground

- - Baklava is ready + + Baklava is ready diff --git a/src/components/calendar/bl-calendar.css b/src/components/calendar/bl-calendar.css index 8bc5ee7c..fa39978e 100644 --- a/src/components/calendar/bl-calendar.css +++ b/src/components/calendar/bl-calendar.css @@ -20,7 +20,7 @@ justify-content: space-between; width: 100%; align-items: center; - gap: 2px; + padding-bottom: 15px; } .arrow { diff --git a/src/components/calendar/bl-calendar.ts b/src/components/calendar/bl-calendar.ts index 580b01fe..933f65fb 100644 --- a/src/components/calendar/bl-calendar.ts +++ b/src/components/calendar/bl-calendar.ts @@ -63,74 +63,65 @@ export default class BlCalendar extends DatepickerCalendarMixin { @state() private _calendarDays: CalendarDay[] = []; + /** + * Fires when date selection changes + */ + @event(blCalendarChangedEvent) private _onBlCalendarChange: EventDispatcher; + @property({ attribute: "default-value", reflect: true }) set defaultValue(defaultValue: Date | Date[]) { if (!this._defaultValue) { + this._defaultValue = defaultValue; if (this.type === CALENDAR_TYPES.SINGLE && Array.isArray(defaultValue)) { + console.warn("'defaultValue' must be of type Date for single date selection."); + } else if (this.type !== CALENDAR_TYPES.SINGLE && !Array.isArray(defaultValue)) { console.warn( - "'defaultValue' must be of type Date when the date selection mode is set to single." - ); - } else if ( - (this.type === CALENDAR_TYPES.MULTIPLE || this.type === CALENDAR_TYPES.RANGE) && - !Array.isArray(defaultValue) - ) { - console.warn( - "'defaultValue' must be an array of two Date objects when the date selection mode is set to range." + "'defaultValue' must be an array of Date objects for multiple/range selection." ); } else if ( this.type === CALENDAR_TYPES.RANGE && Array.isArray(defaultValue) && - (defaultValue.length < 0 || defaultValue.length > 2) + defaultValue.length != 2 ) { console.warn( - "'defaultValue' must be an array of Date objects when the date selection mode is set to multiple." + "'defaultValue' must be an array of two Date objects when the date selection mode is set to range." ); } else { - this._defaultValue = defaultValue; - if (this.type === CALENDAR_TYPES.SINGLE && !Array.isArray(this._defaultValue)) { - this._selectedDates = [this._defaultValue]; - } else if (this.type === CALENDAR_TYPES.MULTIPLE && Array.isArray(this._defaultValue)) { - this._selectedDates = this._defaultValue; - } else if (this.type === CALENDAR_TYPES.RANGE && Array.isArray(this._defaultValue)) { - this._selectedDates = this._defaultValue; - this._selectedRangeDates.startDate = this._defaultValue[0]; - this._selectedRangeDates.endDate = this._defaultValue[1]; + if (this.type === CALENDAR_TYPES.SINGLE && !Array.isArray(defaultValue)) { + this._selectedDates = [defaultValue]; + } else if (Array.isArray(defaultValue)) { + this._selectedDates = defaultValue; + if (this.type === CALENDAR_TYPES.RANGE) { + this._selectedRangeDates.startDate = defaultValue[0]; + this._selectedRangeDates.endDate = defaultValue[1]; + } this.setHoverClass(); } this._onBlCalendarChange(this._selectedDates); } } } - public handleClearSelectedDates = () => { this._selectedDates = []; + this._selectedRangeDates = { startDate: undefined, endDate: undefined }; this._onBlCalendarChange([]); this.clearRangePickerStyles(); }; get months() { - return [...Array(12).keys()].map(month => { - return { - name: new Date(0, month + 1, 0).toLocaleString(this.locale, { - month: "long", - }), - value: month, - }; - }); + return [...Array(12).keys()].map(month => ({ + name: new Date(0, month + 1, 0).toLocaleString(this.locale, { month: "long" }), + value: month, + })); } get days() { - return [...Array(7).keys()].map(day => { - return { - name: new Date(0, 0, day).toLocaleString(this.locale, { weekday: "short" }), - value: day, - }; - }); + return [...Array(7).keys()].map(day => ({ + name: new Date(0, 0, day).toLocaleString(this.locale, { weekday: "short" }), + value: day, + })); } - /** - * Fires when date selection changes - */ - @event(blCalendarChangedEvent) private _onBlCalendarChange: EventDispatcher; + static get styles(): CSSResultGroup { return [style]; } @@ -146,166 +137,129 @@ export default class BlCalendar extends DatepickerCalendarMixin { setPreviousCalendarView() { this.clearRangePickerStyles(); if (this._calendarView === CALENDAR_VIEWS.DAYS) { - if (this._calendarMonth === FIRST_MONTH_INDEX) { - this._calendarMonth = LAST_MONTH_INDEX; - this._calendarYear -= 1; - } else this._calendarMonth -= 1; + this._calendarMonth === FIRST_MONTH_INDEX + ? ((this._calendarMonth = LAST_MONTH_INDEX), (this._calendarYear -= 1)) + : (this._calendarMonth -= 1); } else if (this._calendarView === CALENDAR_VIEWS.MONTHS) { this._calendarYear -= 1; } else if (this._calendarView === CALENDAR_VIEWS.YEARS) { const fromYear = this._calendarYears[0]; - this._calendarYears = []; - for (let i = 12; i > 0; i--) { - this._calendarYears.push(fromYear - i); - } - } - if (this.type === CALENDAR_TYPES.RANGE) { - this.setHoverClass(); + this._calendarYears = Array.from({ length: 12 }, (_, i) => fromYear - (i + 1)); } + this.type === CALENDAR_TYPES.RANGE && this.setHoverClass(); } setNextCalendarView() { this.clearRangePickerStyles(); if (this._calendarView === CALENDAR_VIEWS.DAYS) { - if (this._calendarMonth === LAST_MONTH_INDEX) { - this._calendarMonth = FIRST_MONTH_INDEX; - this._calendarYear += 1; - } else this._calendarMonth += 1; + this._calendarMonth === LAST_MONTH_INDEX + ? ((this._calendarMonth = FIRST_MONTH_INDEX), (this._calendarYear += 1)) + : (this._calendarMonth += 1); } else if (this._calendarView === CALENDAR_VIEWS.MONTHS) { this._calendarYear += 1; } else if (this._calendarView === CALENDAR_VIEWS.YEARS) { const fromYear = this._calendarYears[11]; - this._calendarYears = []; - for (let i = 1; i <= 12; i++) { - this._calendarYears.push(fromYear + i); - } - } - if (this.type === CALENDAR_TYPES.RANGE) { - this.setHoverClass(); + this._calendarYears = Array.from({ length: 12 }, (_, i) => fromYear + (i + 1)); } + this.type === CALENDAR_TYPES.RANGE && this.setHoverClass(); } setCurrentCalendarView(view: CalendarView) { - if (this._calendarView !== view) { - this._calendarView = view; - } else this._calendarView = CALENDAR_VIEWS.DAYS; + this._calendarView = this._calendarView !== view ? view : CALENDAR_VIEWS.DAYS; this.setHoverClass(); } setMonthAndCalendarView(month: number) { this._calendarMonth = month; this._calendarView = CALENDAR_VIEWS.DAYS; - if (this.type === CALENDAR_TYPES.RANGE) { - this.setHoverClass(); - } + this.type === CALENDAR_TYPES.RANGE && this.setHoverClass(); } + setYearAndCalendarView(year: number) { this._calendarYear = year; this._calendarView = CALENDAR_VIEWS.DAYS; - if (this.type === CALENDAR_TYPES.RANGE) { - this.setHoverClass(); - } + this.type === CALENDAR_TYPES.RANGE && this.setHoverClass(); } generateSurroundingYears() { - if (this._calendarYears.length === 0) { - this._calendarYears = Array.from( - { length: 12 }, - (_, index) => this._calendarYear - 4 + index - ); + if (!this._calendarYears.length) { + this._calendarYears = Array.from({ length: 12 }, (_, i) => this._calendarYear - 4 + i); } } clearRangePickerStyles() { - this.shadowRoot?.querySelectorAll(".range-day").forEach(day => { - day.classList.remove("range-day"); - }); - this.shadowRoot?.querySelectorAll(".range-start-day").forEach(day => { - day.classList.remove("range-start-day"); - }); - this.shadowRoot?.querySelectorAll(".range-end-day").forEach(day => { - day.classList.remove("range-end-day"); - }); + this.shadowRoot + ?.querySelectorAll(".range-day, .range-start-day, .range-end-day") + .forEach(day => day.classList.remove("range-day", "range-start-day", "range-end-day")); } handleDate(date: CalendarDate) { if (this.type !== CALENDAR_TYPES.RANGE) { - if (date.getMonth() < this._calendarMonth) { - this.setPreviousCalendarView(); - } else if (date.getMonth() > this._calendarMonth) { - this.setNextCalendarView(); - } + date.getMonth() < this._calendarMonth + ? this.setPreviousCalendarView() + : date.getMonth() > this._calendarMonth && this.setNextCalendarView(); } - if (this.type === CALENDAR_TYPES.SINGLE) { - this.handleSingleSelectCalendar(date); - } else if (this.type === CALENDAR_TYPES.MULTIPLE) { - this.handleMultipleSelectCalendar(date); - } else if (this.type === CALENDAR_TYPES.RANGE) { - this.handleRangeSelectCalendar(date); + switch (this.type) { + case CALENDAR_TYPES.SINGLE: + this.handleSingleSelectCalendar(date); + break; + case CALENDAR_TYPES.MULTIPLE: + this.handleMultipleSelectCalendar(date); + break; + case CALENDAR_TYPES.RANGE: + this.handleRangeSelectCalendar(date); + break; } + this._onBlCalendarChange(this._selectedDates); this.requestUpdate(); } handleSingleSelectCalendar(calendarDate: CalendarDate) { - this._selectedDates.splice(0, 1); - this._selectedDates.push(calendarDate); + this._selectedDates = [calendarDate]; } handleMultipleSelectCalendar(calendarDate: CalendarDate) { - const dateExist = this._selectedDates.find(function (selectedDate) { - return selectedDate.getTime() === calendarDate.getTime(); - }); - - if (dateExist) - this._selectedDates.splice( - this._selectedDates.findIndex(date => date.getTime() === calendarDate.getTime()), - 1 - ); - else this._selectedDates.push(calendarDate); + const dateExist = this._selectedDates.find(d => d.getTime() === calendarDate.getTime()); + + dateExist + ? this._selectedDates.splice( + this._selectedDates.findIndex(d => d.getTime() === calendarDate.getTime()), + 1 + ) + : this._selectedDates.push(calendarDate); } handleRangeSelectCalendar(calendarDate: CalendarDate) { - if (!this._selectedRangeDates.startDate) { + const { startDate, endDate } = this._selectedRangeDates; + + if (!startDate) { this._selectedRangeDates.startDate = calendarDate; this._selectedDates.push(calendarDate); - } else if (this._selectedRangeDates.startDate && !this._selectedRangeDates.endDate) { - if (calendarDate.getTime() > this._selectedRangeDates.startDate.getTime()) { + } else if (!endDate) { + if (calendarDate.getTime() > startDate.getTime()) { this._selectedRangeDates.endDate = calendarDate; this._selectedDates.push(calendarDate); - } else if (calendarDate.getTime() < this._selectedRangeDates.startDate.getTime()) { - const temp = this._selectedRangeDates.startDate; - - this._selectedRangeDates.startDate = calendarDate; - this._selectedRangeDates.endDate = temp; - this._selectedDates.splice( - 0, - this._selectedDates.length, - this._selectedRangeDates.startDate, - this._selectedRangeDates.endDate - ); + } else { + this._selectedRangeDates = { startDate: calendarDate, endDate: startDate }; + this._selectedDates = [calendarDate, startDate]; } - } else if (this._selectedRangeDates.startDate && this._selectedRangeDates.endDate) { - this._selectedRangeDates.startDate = calendarDate; - this._selectedRangeDates.endDate = undefined; - this._selectedDates.splice(0, this._selectedDates.length, this._selectedRangeDates.startDate); + } else { + this._selectedRangeDates = { startDate: calendarDate, endDate: undefined }; + this._selectedDates = [calendarDate]; } this.setHoverClass(); } checkIfSelectedDate(calendarDate: CalendarDate) { - const day = this._selectedDates.find(selectedDate => { - return calendarDate.getTime() === selectedDate.getTime(); - }); - - return !!day; + return !!this._selectedDates.find(d => d.getTime() === calendarDate.getTime()); } checkIfDateIsToday(calendarDate: CalendarDate) { - const today = new Date(); + const today = this.today; return ( today.getDate() === calendarDate.getDate() && @@ -439,13 +393,51 @@ export default class BlCalendar extends DatepickerCalendarMixin { } return calendar; } - render() { - const getCalendarView = (calendarView: CalendarView) => { - if (calendarView === CALENDAR_VIEWS.DAYS) { - const calendarDays = this.createCalendarDays(); - const valuesArray = Array.from(calendarDays.values()); - return html`
+ renderCalendarHeader() { + const showMonthSelected = + this._calendarView === CALENDAR_VIEWS.MONTHS ? "header-text-hover" : ""; + const showYearSelected = this._calendarView === CALENDAR_VIEWS.YEARS ? "header-text-hover" : ""; + + return html` +
+ + ${this.months[this._calendarMonth].name} + ${this._calendarYear} + +
+ `; + } + + renderCalendarDays() { + const calendarDays = this.createCalendarDays(); + const valuesArray = Array.from(calendarDays.values()); + + return html`
${[...calendarDays.keys()].map(key => { return html`
${key}
`; })}
@@ -487,77 +479,51 @@ export default class BlCalendar extends DatepickerCalendarMixin { })}
`; - } else if (calendarView === CALENDAR_VIEWS.MONTHS) { - return html`
- ${this.months.map((month, index) => { - const variant = month.value === this._calendarMonth ? "primary" : "tertiary"; - const neutral = month.value === this._calendarMonth ? "default" : "neutral"; - - return html`${month.name}`; - })} -
`; - } else { - this.generateSurroundingYears(); - return html`
- ${this._calendarYears.map(year => { - const variant = year === this._calendarYear ? "primary" : "tertiary"; - const neutral = year === this._calendarYear ? "default" : "neutral"; - - return html`${year}`; - })} -
`; - } - }; - const showMonthSelected = - this._calendarView === CALENDAR_VIEWS.MONTHS ? "header-text-hover" : ""; - const showYearSelected = this._calendarView === CALENDAR_VIEWS.YEARS ? "header-text-hover" : ""; + } + renderCalendarMonths() { + return html`
+ ${this.months.map((month, index) => { + const variant = month.value === this._calendarMonth ? "primary" : "tertiary"; + const neutral = month.value === this._calendarMonth ? "default" : "neutral"; + + return html` ${month.name}`; + })} +
`; + } + renderCalendarYears() { + this.generateSurroundingYears(); + return html`
+ ${this._calendarYears.map(year => { + const variant = year === this._calendarYear ? "primary" : "tertiary"; + const neutral = year === this._calendarYear ? "default" : "neutral"; + + return html`${year}`; + })} +
`; + } + render() { return html`
-
- - ${this.months[this._calendarMonth].name} - ${this._calendarYear} - +
+ ${this.renderCalendarHeader()} + ${this._calendarView === CALENDAR_VIEWS.DAYS ? this.renderCalendarDays() : ""} + ${this._calendarView === CALENDAR_VIEWS.MONTHS ? this.renderCalendarMonths() : ""} + ${this._calendarView === CALENDAR_VIEWS.YEARS ? this.renderCalendarYears() : ""}
-
${getCalendarView(this._calendarView)}
`; } diff --git a/src/components/datepicker/bl-datepicker.css b/src/components/datepicker/bl-datepicker.css index 4e4fb1c9..3ee2ed26 100644 --- a/src/components/datepicker/bl-datepicker.css +++ b/src/components/datepicker/bl-datepicker.css @@ -37,6 +37,14 @@ height: var(--icon-size); } +.action-divider { + display: block; + height: 1rem; + width: 1px; + background-color: var(--bl-color-neutral-lighter); + margin-right: 5px; +} + .popover { position: var(--popover-position); border: solid 1px var(--border-color); diff --git a/src/components/datepicker/bl-datepicker.ts b/src/components/datepicker/bl-datepicker.ts index 59e9e650..7b32f6c9 100644 --- a/src/components/datepicker/bl-datepicker.ts +++ b/src/components/datepicker/bl-datepicker.ts @@ -8,7 +8,7 @@ import "../calendar/bl-calendar"; import { CALENDAR_TYPES } from "../calendar/bl-calendar.constant"; import { CalendarDate } from "../calendar/bl-calendar.types"; import "../input/bl-input"; -import { CleanUpFunction } from "../select/bl-select"; +import BlInput from "../input/bl-input"; import "../tooltip/bl-tooltip"; import style from "./bl-datepicker.css"; @@ -37,6 +37,21 @@ export default class BlDatepicker extends DatepickerCalendarMixin { */ @property({ type: Function }) valueFormatter: ((dates: CalendarDate[], type: string) => string) | null = null; + /** + * Sets calendar to disabled + */ + @property({ type: Boolean }) + disabled: boolean; + /** + * Defines invalid text to datepicker input + */ + @property() + invalidText: string; + /** + * Defines help text to datepicker input for users + */ + @property() + helpText: string; @state() private _isPopoverOpen = false; @@ -51,11 +66,11 @@ export default class BlDatepicker extends DatepickerCalendarMixin { @state() private _fittingDateCount: number = 0; - private _cleanUpPopover: CleanUpFunction | null = null; - @query(".popover") private _popover: HTMLElement; + // Query the bl-input component + @query("bl-input") inputElement!: BlInput; static get styles(): CSSResultGroup { return [style]; } @@ -68,11 +83,11 @@ export default class BlDatepicker extends DatepickerCalendarMixin { */ @event(blDatepickerChangedEvent) private _onBlDatepickerChanged: EventDispatcher; - private _defaultValueFormatter(dates: CalendarDate[]) { + private _defaultValueFormatter() { this.setFloatingDates(); - - if (this.type === CALENDAR_TYPES.SINGLE && this._selectedDates.length === 1) { + if (this.type === CALENDAR_TYPES.SINGLE) { this._value = `${this.formatDate(this._selectedDates[0])}`; + this.closePopoverWithTimeout(); } else if (this.type === CALENDAR_TYPES.MULTIPLE) { const values: string[] = []; @@ -80,13 +95,20 @@ export default class BlDatepicker extends DatepickerCalendarMixin { values.push(this.formatDate(date)); }); this._value = values.join(",") + (this._floatingDateCount > 0 ? " ,..." : ""); - } else if (this.type === CALENDAR_TYPES.RANGE && dates.length === 2) { - this._value = `${this.formatDate(this._selectedDates[0])} - ${this.formatDate( - this._selectedDates[1] - )}`; + } else if (this.type === CALENDAR_TYPES.RANGE) { + this._value = `${this._selectedDates[0] && this.formatDate(this._selectedDates[0])}${ + this._selectedDates[1] ? `-${this.formatDate(this._selectedDates[1])}` : "" + }`; + this._selectedDates.length === 2 && this.closePopoverWithTimeout(); } } + closePopoverWithTimeout() { + setTimeout(() => { + this.closePopover(); + }, 400); + } + setFloatingDates() { const datepickerInput = this.shadowRoot?.getElementById("datepicker-input"); const iconsContainer = this.shadowRoot?.getElementById("icon-container"); @@ -103,16 +125,14 @@ export default class BlDatepicker extends DatepickerCalendarMixin { this._value = ""; } else { this._selectedDates = dates; - if (this.valueFormatter) { this._value = this.valueFormatter(this._selectedDates, this.type); } else { - this._defaultValueFormatter(this._selectedDates); + this._defaultValueFormatter(); } } this._onBlDatepickerChanged(this._selectedDates); - this.requestUpdate(); } formatDate(date: Date) { @@ -139,10 +159,12 @@ export default class BlDatepicker extends DatepickerCalendarMixin { closePopover() { this._isPopoverOpen = false; - this._cleanUpPopover && this._cleanUpPopover(); document.removeEventListener("click", this._interactOutsideHandler, true); document.removeEventListener("focus", this._interactOutsideHandler, true); + const element = this.shadowRoot?.getElementById("datepicker-input"); + + element?.blur(); } private _interactOutsideHandler = (event: MouseEvent | FocusEvent) => { const eventPath = event.composedPath() as HTMLElement[]; @@ -189,7 +211,7 @@ export default class BlDatepicker extends DatepickerCalendarMixin { render() { const renderCalendar = html` @@ -220,6 +243,18 @@ export default class BlDatepicker extends DatepickerCalendarMixin { ` : ""; + const clearDatepickerButton = + this._selectedDates.length > 0 + ? html` this.clearDatepicker()} + > +
` + : ""; + return html`
- ${additionalDatesView} - this.clearDatepicker()} - > + ${additionalDatesView} ${clearDatepickerButton}
From dcc03eef21c097aac79838b8606e8bbd1ce6852d Mon Sep 17 00:00:00 2001 From: "dilan.dogan2" Date: Mon, 9 Sep 2024 15:57:01 +0300 Subject: [PATCH 09/35] feat(datepicker): coding improvments on bl-datepicker component --- playground/template.html | 2 +- src/components/datepicker/bl-datepicker.ts | 38 +++++++++------------- 2 files changed, 17 insertions(+), 23 deletions(-) diff --git a/playground/template.html b/playground/template.html index bd0d2abb..b5faae9a 100644 --- a/playground/template.html +++ b/playground/template.html @@ -33,7 +33,7 @@

Baklava Playground

- + Baklava is ready diff --git a/src/components/datepicker/bl-datepicker.ts b/src/components/datepicker/bl-datepicker.ts index 7b32f6c9..cece5540 100644 --- a/src/components/datepicker/bl-datepicker.ts +++ b/src/components/datepicker/bl-datepicker.ts @@ -86,37 +86,33 @@ export default class BlDatepicker extends DatepickerCalendarMixin { private _defaultValueFormatter() { this.setFloatingDates(); if (this.type === CALENDAR_TYPES.SINGLE) { - this._value = `${this.formatDate(this._selectedDates[0])}`; + this._value = this.formatDate(this._selectedDates[0]); this.closePopoverWithTimeout(); } else if (this.type === CALENDAR_TYPES.MULTIPLE) { - const values: string[] = []; + const values = this._selectedDates + .slice(0, this._fittingDateCount) + .map(date => this.formatDate(date)); - this._selectedDates.slice(0, this._fittingDateCount).forEach(date => { - values.push(this.formatDate(date)); - }); this._value = values.join(",") + (this._floatingDateCount > 0 ? " ,..." : ""); } else if (this.type === CALENDAR_TYPES.RANGE) { - this._value = `${this._selectedDates[0] && this.formatDate(this._selectedDates[0])}${ - this._selectedDates[1] ? `-${this.formatDate(this._selectedDates[1])}` : "" + this._value = `${this.formatDate(this._selectedDates[0]) || ""}-${ + this.formatDate(this._selectedDates[1]) || "" }`; - this._selectedDates.length === 2 && this.closePopoverWithTimeout(); + if (this._selectedDates.length === 2) this.closePopoverWithTimeout(); } } closePopoverWithTimeout() { - setTimeout(() => { - this.closePopover(); - }, 400); + setTimeout(() => this.closePopover(), 400); } setFloatingDates() { const datepickerInput = this.shadowRoot?.getElementById("datepicker-input"); const iconsContainer = this.shadowRoot?.getElementById("icon-container"); const datesTextTotalWidth = - (datepickerInput?.offsetWidth as number) - (iconsContainer?.offsetWidth as number); - - this._fittingDateCount = parseInt(String(datesTextTotalWidth / 90)); + (datepickerInput?.offsetWidth ?? 0) - (iconsContainer?.offsetWidth ?? 0); + this._fittingDateCount = Math.floor(datesTextTotalWidth / 90); this._floatingDateCount = this._selectedDates.length - this._fittingDateCount; } @@ -136,11 +132,10 @@ export default class BlDatepicker extends DatepickerCalendarMixin { } formatDate(date: Date) { - const day = String(date.getDate()).padStart(2, "0"); - const month = String(date.getMonth() + 1).padStart(2, "0"); - const year = date.getFullYear(); - - return `${day}/${month}/${year}`; + return `${String(date.getDate()).padStart(2, "0")}/${String(date.getMonth() + 1).padStart( + 2, + "0" + )}/${date.getFullYear()}`; } clearDatepicker() { this._onBlDatepickerCleared([]); @@ -162,10 +157,9 @@ export default class BlDatepicker extends DatepickerCalendarMixin { document.removeEventListener("click", this._interactOutsideHandler, true); document.removeEventListener("focus", this._interactOutsideHandler, true); - const element = this.shadowRoot?.getElementById("datepicker-input"); - - element?.blur(); + this.shadowRoot?.getElementById("datepicker-input")?.blur(); } + private _interactOutsideHandler = (event: MouseEvent | FocusEvent) => { const eventPath = event.composedPath() as HTMLElement[]; From 5066c5dd465489723eaa90b347ec09c3be99b0ed Mon Sep 17 00:00:00 2001 From: "dilan.dogan2" Date: Mon, 9 Sep 2024 17:10:49 +0300 Subject: [PATCH 10/35] feat(datepicker): show calendar on popover --- playground/template.html | 4 +- src/components/datepicker/bl-datepicker.css | 21 ++----- src/components/datepicker/bl-datepicker.ts | 64 +++++++++------------ 3 files changed, 33 insertions(+), 56 deletions(-) diff --git a/playground/template.html b/playground/template.html index b5faae9a..cc96070c 100644 --- a/playground/template.html +++ b/playground/template.html @@ -32,9 +32,9 @@

Baklava Playground

npm run serve.

- - + Baklava is ready + diff --git a/src/components/datepicker/bl-datepicker.css b/src/components/datepicker/bl-datepicker.css index 3ee2ed26..54c1b707 100644 --- a/src/components/datepicker/bl-datepicker.css +++ b/src/components/datepicker/bl-datepicker.css @@ -45,21 +45,10 @@ margin-right: 5px; } -.popover { - position: var(--popover-position); - border: solid 1px var(--border-color); - background-color: var(--background-color); - font: var(--bl-font-title-3-regular); - border-radius: var(--bl-border-radius-s); - padding: var(--menu-padding); - outline: none; - box-sizing: border-box; - max-height: var(--menu-height); - overflow-y: auto; - display: none; - z-index: var(--bl-index-popover); -} +bl-popover{ + --bl-popover-padding:0; + --bl-popover-border-size:0; + --bl-popover-border-radius:0; + --bl-popover-background-color:transparent; -.show-popover { - display: block; } diff --git a/src/components/datepicker/bl-datepicker.ts b/src/components/datepicker/bl-datepicker.ts index cece5540..bc968063 100644 --- a/src/components/datepicker/bl-datepicker.ts +++ b/src/components/datepicker/bl-datepicker.ts @@ -1,7 +1,7 @@ import { CSSResultGroup, html, TemplateResult } from "lit"; import { customElement, property, state, query } from "lit/decorators.js"; -import { classMap } from "lit/directives/class-map.js"; import { ifDefined } from "lit/directives/if-defined.js"; +import { BlCalendar, BlPopover } from "../../baklava"; import DatepickerCalendarMixin from "../../mixins/datepicker-calendar-mixin/datepicker-calendar-mixin"; import { event, EventDispatcher } from "../../utilities/event"; import "../calendar/bl-calendar"; @@ -61,16 +61,22 @@ export default class BlDatepicker extends DatepickerCalendarMixin { @state() private _selectedDates: CalendarDate[] = []; + @state() private _floatingDateCount: number = 0; + @state() private _fittingDateCount: number = 0; - @query(".popover") - private _popover: HTMLElement; + @query("bl-calendar") + private _calendarEl: BlCalendar; + + @query("bl-popover") + private _popoverEl: BlPopover; // Query the bl-input component - @query("bl-input") inputElement!: BlInput; + @query("bl-input") inputEl!: BlInput; + static get styles(): CSSResultGroup { return [style]; } @@ -145,31 +151,16 @@ export default class BlDatepicker extends DatepickerCalendarMixin { } openPopover() { - document.activeElement?.shadowRoot?.querySelector("bl-input")?.focus(); - - this._isPopoverOpen = true; - document.addEventListener("click", this._interactOutsideHandler, true); - document.addEventListener("focus", this._interactOutsideHandler, true); + this._popoverEl.target = this.inputEl; + this._popoverEl.show(); } closePopover() { - this._isPopoverOpen = false; - - document.removeEventListener("click", this._interactOutsideHandler, true); - document.removeEventListener("focus", this._interactOutsideHandler, true); - this.shadowRoot?.getElementById("datepicker-input")?.blur(); + this._popoverEl.hide(); } - private _interactOutsideHandler = (event: MouseEvent | FocusEvent) => { - const eventPath = event.composedPath() as HTMLElement[]; - - if (!eventPath?.find(el => el.tagName === "BL-DATEPICKER")?.contains(this)) { - this.closePopover(); - } - }; - private _togglePopover() { - this._isPopoverOpen ? this.closePopover() : this.openPopover(); + this._popoverEl.visible ? this.closePopover() : this.openPopover(); } firstUpdated() { @@ -182,7 +173,7 @@ export default class BlDatepicker extends DatepickerCalendarMixin { document.addEventListener("mousedown", event => { const path = event.composedPath(); - if (path.includes(this._popover) || (element && path.includes(element))) { + if (path.includes(this._calendarEl) || (element && path.includes(element))) { event.preventDefault(); element?.focus(); @@ -204,20 +195,17 @@ export default class BlDatepicker extends DatepickerCalendarMixin { render() { const renderCalendar = html` - + + + `; const additionalDates = this._selectedDates From ca1d30489a20e90d43c1ad5387900119c36c65ab Mon Sep 17 00:00:00 2001 From: "dilan.dogan2" Date: Sat, 14 Sep 2024 22:52:23 +0300 Subject: [PATCH 11/35] feat(datepicker): default value bug fix --- playground/template.html | 4 +- src/components/calendar/bl-calendar.ts | 52 ++++++----------- .../datepicker/bl-datepicker.stories.mdx | 10 ++-- src/components/datepicker/bl-datepicker.ts | 58 +++++++++++-------- .../datepicker-calendar-mixin.ts | 24 ++++++++ 5 files changed, 83 insertions(+), 65 deletions(-) diff --git a/playground/template.html b/playground/template.html index cc96070c..2f8171fc 100644 --- a/playground/template.html +++ b/playground/template.html @@ -32,8 +32,10 @@

Baklava Playground

npm run serve.

- +
+ Baklava is ready +
diff --git a/src/components/calendar/bl-calendar.ts b/src/components/calendar/bl-calendar.ts index 933f65fb..cf393947 100644 --- a/src/components/calendar/bl-calendar.ts +++ b/src/components/calendar/bl-calendar.ts @@ -1,5 +1,5 @@ import { CSSResultGroup, html } from "lit"; -import { customElement, property, state } from "lit/decorators.js"; +import { customElement, state } from "lit/decorators.js"; import { classMap } from "lit/directives/class-map.js"; import DatepickerCalendarMixin from "../../mixins/datepicker-calendar-mixin/datepicker-calendar-mixin"; import { event, EventDispatcher } from "../../utilities/event"; @@ -68,39 +68,6 @@ export default class BlCalendar extends DatepickerCalendarMixin { */ @event(blCalendarChangedEvent) private _onBlCalendarChange: EventDispatcher; - @property({ attribute: "default-value", reflect: true }) - set defaultValue(defaultValue: Date | Date[]) { - if (!this._defaultValue) { - this._defaultValue = defaultValue; - if (this.type === CALENDAR_TYPES.SINGLE && Array.isArray(defaultValue)) { - console.warn("'defaultValue' must be of type Date for single date selection."); - } else if (this.type !== CALENDAR_TYPES.SINGLE && !Array.isArray(defaultValue)) { - console.warn( - "'defaultValue' must be an array of Date objects for multiple/range selection." - ); - } else if ( - this.type === CALENDAR_TYPES.RANGE && - Array.isArray(defaultValue) && - defaultValue.length != 2 - ) { - console.warn( - "'defaultValue' must be an array of two Date objects when the date selection mode is set to range." - ); - } else { - if (this.type === CALENDAR_TYPES.SINGLE && !Array.isArray(defaultValue)) { - this._selectedDates = [defaultValue]; - } else if (Array.isArray(defaultValue)) { - this._selectedDates = defaultValue; - if (this.type === CALENDAR_TYPES.RANGE) { - this._selectedRangeDates.startDate = defaultValue[0]; - this._selectedRangeDates.endDate = defaultValue[1]; - } - this.setHoverClass(); - } - this._onBlCalendarChange(this._selectedDates); - } - } - } public handleClearSelectedDates = () => { this._selectedDates = []; this._selectedRangeDates = { startDate: undefined, endDate: undefined }; @@ -255,7 +222,7 @@ export default class BlCalendar extends DatepickerCalendarMixin { } checkIfSelectedDate(calendarDate: CalendarDate) { - return !!this._selectedDates.find(d => d.getTime() === calendarDate.getTime()); + return !!this._selectedDates.find(date => date?.getTime() === calendarDate.getTime()); } checkIfDateIsToday(calendarDate: CalendarDate) { @@ -267,6 +234,7 @@ export default class BlCalendar extends DatepickerCalendarMixin { today.getFullYear() === calendarDate.getFullYear() ); } + checkIfDateIsDisabled(calendarDate: CalendarDate) { if ( calendarDate.getTime() < this.minDate?.getTime() || @@ -394,6 +362,20 @@ export default class BlCalendar extends DatepickerCalendarMixin { return calendar; } + async firstUpdated() { + if (this._defaultValue) { + Array.isArray(this._defaultValue) + ? (this._selectedDates = this._defaultValue) + : (this._selectedDates = [new Date(this._defaultValue as Date)]); + + if (this.type === CALENDAR_TYPES.RANGE) { + this._selectedRangeDates.startDate = this._selectedDates[0]; + this._selectedRangeDates.endDate = this._selectedDates[1]; + this.setHoverClass(); + } + } + } + renderCalendarHeader() { const showMonthSelected = this._calendarView === CALENDAR_VIEWS.MONTHS ? "header-text-hover" : ""; diff --git a/src/components/datepicker/bl-datepicker.stories.mdx b/src/components/datepicker/bl-datepicker.stories.mdx index 0d16ad71..2a51b880 100644 --- a/src/components/datepicker/bl-datepicker.stories.mdx +++ b/src/components/datepicker/bl-datepicker.stories.mdx @@ -53,7 +53,7 @@ Datepicker renders the calendar component within itself and provides the functio Default datepicker type is `single` and you can only select a single day from datepicker. - + {Template.bind({})} @@ -63,12 +63,11 @@ Default datepicker type is `single` and you can only select a single day from da You can select multiple days from Datepicker. - + {Template.bind({})} - ### Range Type Datepicker You can select date range from Datepicker. @@ -84,10 +83,13 @@ You can select date range from Datepicker. You can set dates which you want to disable from Datepicker. - + {Template.bind({})} + ## Reference diff --git a/src/components/datepicker/bl-datepicker.ts b/src/components/datepicker/bl-datepicker.ts index bc968063..379e5922 100644 --- a/src/components/datepicker/bl-datepicker.ts +++ b/src/components/datepicker/bl-datepicker.ts @@ -45,7 +45,7 @@ export default class BlDatepicker extends DatepickerCalendarMixin { /** * Defines invalid text to datepicker input */ - @property() + @property({ type: String, attribute: "invalid-text", reflect: true }) invalidText: string; /** * Defines help text to datepicker input for users @@ -53,9 +53,6 @@ export default class BlDatepicker extends DatepickerCalendarMixin { @property() helpText: string; - @state() - private _isPopoverOpen = false; - @state() private _value = ""; @@ -90,26 +87,26 @@ export default class BlDatepicker extends DatepickerCalendarMixin { @event(blDatepickerChangedEvent) private _onBlDatepickerChanged: EventDispatcher; private _defaultValueFormatter() { - this.setFloatingDates(); if (this.type === CALENDAR_TYPES.SINGLE) { this._value = this.formatDate(this._selectedDates[0]); this.closePopoverWithTimeout(); } else if (this.type === CALENDAR_TYPES.MULTIPLE) { + this.setFloatingDates(); const values = this._selectedDates .slice(0, this._fittingDateCount) .map(date => this.formatDate(date)); this._value = values.join(",") + (this._floatingDateCount > 0 ? " ,..." : ""); } else if (this.type === CALENDAR_TYPES.RANGE) { - this._value = `${this.formatDate(this._selectedDates[0]) || ""}-${ - this.formatDate(this._selectedDates[1]) || "" - }`; + this._selectedDates[0] && (this._value = this.formatDate(this._selectedDates[0])); + this._selectedDates[1] && + (this._value = `${this._value}-${this.formatDate(this._selectedDates[1])}`); if (this._selectedDates.length === 2) this.closePopoverWithTimeout(); } } closePopoverWithTimeout() { - setTimeout(() => this.closePopover(), 400); + setTimeout(() => this.closePopover(), 200); } setFloatingDates() { @@ -119,6 +116,7 @@ export default class BlDatepicker extends DatepickerCalendarMixin { (datepickerInput?.offsetWidth ?? 0) - (iconsContainer?.offsetWidth ?? 0); this._fittingDateCount = Math.floor(datesTextTotalWidth / 90); + this._floatingDateCount = this._selectedDates.length - this._fittingDateCount; } @@ -138,11 +136,12 @@ export default class BlDatepicker extends DatepickerCalendarMixin { } formatDate(date: Date) { - return `${String(date.getDate()).padStart(2, "0")}/${String(date.getMonth() + 1).padStart( + return `${String(date?.getDate()).padStart(2, "0")}/${String(date?.getMonth() + 1).padStart( 2, "0" - )}/${date.getFullYear()}`; + )}/${date?.getFullYear()}`; } + clearDatepicker() { this._onBlDatepickerCleared([]); this._selectedDates = []; @@ -163,22 +162,33 @@ export default class BlDatepicker extends DatepickerCalendarMixin { this._popoverEl.visible ? this.closePopover() : this.openPopover(); } - firstUpdated() { - const element = this.shadowRoot?.getElementById("datepicker-input"); - - element?.addEventListener("mousedown", event => { + async firstUpdated() { + this.inputEl?.addEventListener("mousedown", event => { event.preventDefault(); }); document.addEventListener("mousedown", event => { const path = event.composedPath(); - if (path.includes(this._calendarEl) || (element && path.includes(element))) { + if (path.includes(this._calendarEl) || (this.inputEl && path.includes(this.inputEl))) { event.preventDefault(); - element?.focus(); + this.inputEl?.focus(); } }); + if (this._defaultValue) { + Array.isArray(this._defaultValue) + ? (this._selectedDates = this._defaultValue) + : (this._selectedDates = [new Date(this._defaultValue)]); + this.setDatePickerInput(this._selectedDates); + } + } + + async triggerInputError() { + await this.inputEl.updateComplete; + if (this.inputEl) { + await this.inputEl.forceCustomError(); + } } formatAdditionalDates(str: string): TemplateResult[] { @@ -188,7 +198,7 @@ export default class BlDatepicker extends DatepickerCalendarMixin { if (index > 0 && index % 3 === 0) { acc.push(html`
`); } - acc.push(html`${part.trim()}, `); + acc.push(html`${part.trim()}${index < parts.length - 1 ? ", " : ""}`); return acc; }, []); } @@ -199,17 +209,16 @@ export default class BlDatepicker extends DatepickerCalendarMixin { `; - const additionalDates = this._selectedDates - .slice(this._fittingDateCount) + ?.slice(this._fittingDateCount) .map(date => { return this.formatDate(date); }) @@ -247,7 +256,6 @@ export default class BlDatepicker extends DatepickerCalendarMixin { role="button" id="datepicker-input" aria-haspopup="listbox" - aria-expanded="${this._isPopoverOpen}" aria-labelledby="label" @click=${this._togglePopover} help-text=${this.helpText} diff --git a/src/mixins/datepicker-calendar-mixin/datepicker-calendar-mixin.ts b/src/mixins/datepicker-calendar-mixin/datepicker-calendar-mixin.ts index 2e33e628..9fd4e3ac 100644 --- a/src/mixins/datepicker-calendar-mixin/datepicker-calendar-mixin.ts +++ b/src/mixins/datepicker-calendar-mixin/datepicker-calendar-mixin.ts @@ -1,5 +1,6 @@ import { LitElement } from "lit"; import { property } from "lit/decorators.js"; +import { CALENDAR_TYPES } from "../../components/calendar/bl-calendar.constant"; import { CalendarType, DayValues } from "../../components/calendar/bl-calendar.types"; export default class DatepickerCalendarMixin extends LitElement { @@ -44,4 +45,27 @@ export default class DatepickerCalendarMixin extends LitElement { get defaultValue() { return this._defaultValue; } + + @property({ attribute: "default-value", reflect: true }) + set defaultValue(defaultValue: Date | Date[]) { + if (defaultValue) { + if (this.type === CALENDAR_TYPES.SINGLE && Array.isArray(defaultValue)) { + console.warn("'defaultValue' must be of type Date for single date selection."); + } else if (this.type !== CALENDAR_TYPES.SINGLE && !Array.isArray(defaultValue)) { + console.warn( + "'defaultValue' must be an array of Date objects for multiple/range selection." + ); + } else if ( + this.type === CALENDAR_TYPES.RANGE && + Array.isArray(defaultValue) && + defaultValue.length != 2 + ) { + console.warn( + "'defaultValue' must be an array of two Date objects when the date selection mode is set to range." + ); + } else { + this._defaultValue = defaultValue; + } + } + } } From 7ffde5ce7e4ecc1426459b86c3fd30ad989196b4 Mon Sep 17 00:00:00 2001 From: "dilan.dogan2" Date: Mon, 16 Sep 2024 15:50:51 +0300 Subject: [PATCH 12/35] feat(datepicker): fixing datepicker story --- .../calendar/bl-calendar.stories.mdx | 3 ++- src/components/calendar/bl-calendar.ts | 2 +- .../datepicker/bl-datepicker.stories.mdx | 18 +++++++++++++----- 3 files changed, 16 insertions(+), 7 deletions(-) diff --git a/src/components/calendar/bl-calendar.stories.mdx b/src/components/calendar/bl-calendar.stories.mdx index ccfbbb3a..27bd72f4 100644 --- a/src/components/calendar/bl-calendar.stories.mdx +++ b/src/components/calendar/bl-calendar.stories.mdx @@ -82,7 +82,8 @@ You can select date range from calendar. You can set dates which you want to disable from calendar. - + {Template.bind({})} diff --git a/src/components/calendar/bl-calendar.ts b/src/components/calendar/bl-calendar.ts index cf393947..d26244cf 100644 --- a/src/components/calendar/bl-calendar.ts +++ b/src/components/calendar/bl-calendar.ts @@ -243,7 +243,7 @@ export default class BlCalendar extends DatepickerCalendarMixin { return true; } - if (Array.isArray(this.disabledDates)) { + if (this.disabledDates) { const day = this.disabledDates.find(disabledDate => { return calendarDate.getTime() === new Date(disabledDate).getTime(); }); diff --git a/src/components/datepicker/bl-datepicker.stories.mdx b/src/components/datepicker/bl-datepicker.stories.mdx index 2a51b880..0440c2dd 100644 --- a/src/components/datepicker/bl-datepicker.stories.mdx +++ b/src/components/datepicker/bl-datepicker.stories.mdx @@ -53,7 +53,7 @@ Datepicker renders the calendar component within itself and provides the functio Default datepicker type is `single` and you can only select a single day from datepicker. - + {Template.bind({})} @@ -63,7 +63,7 @@ Default datepicker type is `single` and you can only select a single day from da You can select multiple days from Datepicker. - + {Template.bind({})} @@ -78,14 +78,22 @@ You can select date range from Datepicker. +### Default Value + +You can set a default value to datepicker. + + + + {Template.bind({})} + + + ### Disabled Dates You can set dates which you want to disable from Datepicker. - + {Template.bind({})} From adb125b8c742a5f25cf473e275414d366dd05f4a Mon Sep 17 00:00:00 2001 From: "dilan.dogan2" Date: Wed, 18 Sep 2024 10:36:37 +0300 Subject: [PATCH 13/35] feat(datepicker): add bl-calendar test cases --- src/components/calendar/bl-calendar.test.ts | 120 ++++++++++++++++++++ src/components/calendar/bl-calendar.ts | 4 +- 2 files changed, 122 insertions(+), 2 deletions(-) create mode 100644 src/components/calendar/bl-calendar.test.ts diff --git a/src/components/calendar/bl-calendar.test.ts b/src/components/calendar/bl-calendar.test.ts new file mode 100644 index 00000000..cb73b17d --- /dev/null +++ b/src/components/calendar/bl-calendar.test.ts @@ -0,0 +1,120 @@ +import { fixture, expect, html } from "@open-wc/testing"; +import "./bl-calendar"; +import { BlButton, BlCalendar } from "../../baklava"; +import { blCalendarChangedEvent } from "./bl-calendar"; +import { blDatepickerClearSelectedDatesEvent } from "../datepicker/bl-datepicker"; + +describe.only("bl-calendar", () => { + let element: BlCalendar; + + beforeEach(async () => { + element = await fixture(html``); + }); + + it("should instantiate the component", () => { + expect(document.createElement("bl-calendar")).instanceOf(HTMLElement); + }); + + it("should render the calendar header", () => { + const headerButtons = element.shadowRoot?.querySelectorAll(".calendar-header bl-button"); + + expect(headerButtons?.length).to.equal(4); + }); + + it("should navigate to the previous month when clicking the left arrow", () => { + const prevButton = element.shadowRoot?.querySelector(".calendar-header .arrow") as BlButton; + const currentMonth = element._calendarMonth; + + prevButton?.click(); + expect(element._calendarMonth).to.equal( + currentMonth === 0 ? 11 : currentMonth - 1 + ); + }); + + it("should navigate to the next month when clicking the right arrow", () => { + const nextButton = element.shadowRoot?.querySelectorAll(".calendar-header .arrow")[1] as BlButton; + const currentMonth = element._calendarMonth; + + nextButton?.click(); + expect(element._calendarMonth).to.equal( + currentMonth === 11 ? 0 : currentMonth + 1 + ); + }); + + it("should render days of the week", () => { + const weekDays = element.shadowRoot?.querySelectorAll(".calendar-text.weekday-text"); + + expect(weekDays?.length).to.equal(7); + }); + + it("should correctly handle single date selection", async () => { + const singleTypeCalendar= element = await fixture(html``); + + await singleTypeCalendar.updateComplete; + const dayButton = element.shadowRoot?.querySelector(".day-wrapper bl-button") as BlButton; + + dayButton?.click(); + expect(element._selectedDates.length).to.equal(1); + expect(element.checkIfSelectedDate(element._selectedDates[0])).to.be.true; + }); + + it("should correctly handle multiple date selection", async () => { + const multipleTypeCalendar= element = await fixture(html``); + + await multipleTypeCalendar.updateComplete; + const dayButtons = Array.from(element.shadowRoot?.querySelectorAll(".day-wrapper bl-button") || []) as BlButton[]; + + dayButtons[0].click(); + dayButtons[1].click(); + expect(element._selectedDates.length).to.equal(2); + }); + + it("should fire bl-calendar-change event when dates are selected", async () => { + const singleTypeCalendar= await fixture(html``); + + await singleTypeCalendar.updateComplete; + let selectedDates:Date[] = []; + + const onBlCalendarChanged: EventListener = (e: Event) => { + const customEvent = e as CustomEvent; + + selectedDates = customEvent.detail; + }; + + singleTypeCalendar.addEventListener(blCalendarChangedEvent, onBlCalendarChanged); + const daysButtons = Array.from(singleTypeCalendar.shadowRoot?.querySelectorAll(".day-wrapper bl-button") || []) as BlButton[]; + + daysButtons[0].click(); + expect(selectedDates.length).to.equal(1); + expect(selectedDates[0]).to.equal(singleTypeCalendar._selectedDates[0]); + + }); + + it("should clear selected dates on receiving blDatepickerClearSelectedDatesEvent",async () => { + element._selectedDates = [new Date()]; + window.dispatchEvent(new CustomEvent(blDatepickerClearSelectedDatesEvent)); + expect(element._selectedDates.length).to.equal(0); + }); + + it("should disable dates outside min/max date range", async () => { + const minDate = new Date(2023, 0, 1); + const maxDate = new Date(2023, 11, 31); + + element.minDate = minDate; + element.maxDate = maxDate; + + await element.updateComplete; + + const days = Array.from(element.shadowRoot?.querySelectorAll(".day-wrapper bl-button") || []) as BlButton[]; + + days?.forEach((day) => { + const date = new Date(Number(day.id)); + + if (date < minDate || date > maxDate) { + expect(day.disabled).to.be.true; + } else { + expect(day.disabled).to.be.false; + } + }); + }); +}); diff --git a/src/components/calendar/bl-calendar.ts b/src/components/calendar/bl-calendar.ts index d26244cf..e317af81 100644 --- a/src/components/calendar/bl-calendar.ts +++ b/src/components/calendar/bl-calendar.ts @@ -40,7 +40,7 @@ export default class BlCalendar extends DatepickerCalendarMixin { } @state() - private _selectedDates: CalendarDate[] = []; + _selectedDates: CalendarDate[] = []; @state() private _selectedRangeDates: RangePickerDates = { startDate: undefined, endDate: undefined }; @@ -49,7 +49,7 @@ export default class BlCalendar extends DatepickerCalendarMixin { private today = new Date(); @state() - private _calendarMonth: number = this.today.getMonth(); + _calendarMonth: number = this.today.getMonth(); @state() private _calendarYear: number = this.today.getFullYear(); From 116b4c6a572557f1df87a439be327284e810a54a Mon Sep 17 00:00:00 2001 From: "dilan.dogan2" Date: Wed, 18 Sep 2024 23:14:33 +0300 Subject: [PATCH 14/35] feat(datepicker): add test cases for bl-calendar and bl-datepicker components --- playground/template.html | 1 - src/components/calendar/bl-calendar.test.ts | 2 +- src/components/calendar/bl-calendar.ts | 2 +- .../datepicker/bl-datepicker.test.ts | 116 ++++++++++++++++++ src/components/datepicker/bl-datepicker.ts | 43 +++---- 5 files changed, 136 insertions(+), 28 deletions(-) create mode 100644 src/components/datepicker/bl-datepicker.test.ts diff --git a/playground/template.html b/playground/template.html index 2f8171fc..951055f6 100644 --- a/playground/template.html +++ b/playground/template.html @@ -33,7 +33,6 @@

Baklava Playground

- Baklava is ready
diff --git a/src/components/calendar/bl-calendar.test.ts b/src/components/calendar/bl-calendar.test.ts index cb73b17d..e0a3c5fc 100644 --- a/src/components/calendar/bl-calendar.test.ts +++ b/src/components/calendar/bl-calendar.test.ts @@ -4,7 +4,7 @@ import { BlButton, BlCalendar } from "../../baklava"; import { blCalendarChangedEvent } from "./bl-calendar"; import { blDatepickerClearSelectedDatesEvent } from "../datepicker/bl-datepicker"; -describe.only("bl-calendar", () => { +describe("bl-calendar", () => { let element: BlCalendar; beforeEach(async () => { diff --git a/src/components/calendar/bl-calendar.ts b/src/components/calendar/bl-calendar.ts index e317af81..068e9214 100644 --- a/src/components/calendar/bl-calendar.ts +++ b/src/components/calendar/bl-calendar.ts @@ -66,7 +66,7 @@ export default class BlCalendar extends DatepickerCalendarMixin { /** * Fires when date selection changes */ - @event(blCalendarChangedEvent) private _onBlCalendarChange: EventDispatcher; + @event(blCalendarChangedEvent) _onBlCalendarChange: EventDispatcher; public handleClearSelectedDates = () => { this._selectedDates = []; diff --git a/src/components/datepicker/bl-datepicker.test.ts b/src/components/datepicker/bl-datepicker.test.ts new file mode 100644 index 00000000..60731012 --- /dev/null +++ b/src/components/datepicker/bl-datepicker.test.ts @@ -0,0 +1,116 @@ +import { aTimeout, expect, fixture, html } from "@open-wc/testing"; +import BlDatepicker, { blDatepickerChangedEvent } from "./bl-datepicker"; +import { BlButton, BlDatePicker } from "../../baklava"; +import { CALENDAR_TYPES } from "../calendar/bl-calendar.constant"; +import { blCalendarChangedEvent } from "../calendar/bl-calendar"; + +describe("BlDatepicker", () => { + let element : BlDatepicker; + + beforeEach(async () => { + element = await fixture(html``); + await element.updateComplete; + }); + + it("should instantiate the component", () => { + expect(document.createElement("bl-datepicker")).instanceOf(HTMLElement); + }); + + it("should render the datepicker component", () => { + expect(element).to.exist; + expect(element.shadowRoot).to.exist; + }); + + it("should have default empty value", () => { + expect(element._value).to.equal(""); + }); + + it("should set placeholder correctly", async () => { + element.placeholder = "Select a date"; + await element.updateComplete; + + expect(element._inputEl?.placeholder).to.equal("Select a date"); + }); + + it("should open the popover when input is clicked", async () => { + + element._inputEl?.click(); // Simulate clicking the input + await element.updateComplete; + + expect(element._popoverEl).to.exist; + expect(element._popoverEl?.visible).to.be.true; // Assert that the popover is visible + }); + + it("should close the popover after selecting a date", async () => { + + element._inputEl?.click(); + await element.updateComplete; + + element._calendarEl?.dispatchEvent(new CustomEvent(blCalendarChangedEvent, { detail: [new Date()] })); + await element.updateComplete; + await aTimeout(400); + expect(element._selectedDates.length).to.equal(1); + expect(element._popoverEl.visible).to.be.false; + }); + + it("should trigger datepicker change event on date selection", async () => { + const testDate = new Date(2023, 1, 1); + + element.addEventListener(blDatepickerChangedEvent, (event) => { + const customEvent = event as CustomEvent; + + expect(customEvent).to.exist; + expect(customEvent.detail).to.deep.equal([testDate]); + + }); + + // Simulate the calendar change event that triggers the datepicker change + element._calendarEl.dispatchEvent(new CustomEvent(blCalendarChangedEvent, { detail: [testDate] })); + + await element.updateComplete; + }); + + it("should clear selected dates when clear button is clicked", async () => { + element._selectedDates = [new Date(2023, 1, 1)]; + await element.updateComplete; + + const clearButton = element.shadowRoot?.querySelector("bl-button") as BlButton; + + clearButton?.click(); + await element.updateComplete; + + expect(element._selectedDates).to.deep.equal([]); + expect(element._value).to.equal(""); + }); + + it("should show tooltip with extra dates when multiple dates are selected", async () => { + + element._selectedDates = [ + new Date(2023, 1, 1), + new Date(2023, 1, 2), + new Date(2023, 1, 3), + new Date(2023, 1, 4), + ]; + element.type = CALENDAR_TYPES.MULTIPLE; + await element.updateComplete; + + const tooltipTrigger = element.shadowRoot?.querySelector('bl-tooltip [slot="tooltip-trigger"]'); + + await element.updateComplete; + await aTimeout(400); + + expect(tooltipTrigger).to.exist; + expect(tooltipTrigger?.textContent).to.equal("+1"); + }); + + it("should use custom valueFormatter function if provided", async () => { + element.valueFormatter= (dates:Date[]) => `Selected: ${dates.length} dates`; + + element.type = CALENDAR_TYPES.MULTIPLE; + element._selectedDates = [new Date(2023, 1, 1), new Date(2023, 1, 2)]; + await element.updateComplete; + + expect(element._value).to.equal("Selected: 2 dates"); + }); + +}); diff --git a/src/components/datepicker/bl-datepicker.ts b/src/components/datepicker/bl-datepicker.ts index 379e5922..dfad19b8 100644 --- a/src/components/datepicker/bl-datepicker.ts +++ b/src/components/datepicker/bl-datepicker.ts @@ -25,22 +25,22 @@ export default class BlDatepicker extends DatepickerCalendarMixin { /** * Defines the datepicker input placeholder */ - @property() + @property({ type: String, attribute: "placeholder", reflect: true }) placeholder: string; /** * Defines the datepicker input label */ - @property() + @property({ type: String, attribute: "label", reflect: true }) label: string; /** * Defines the custom formatter function */ - @property({ type: Function }) - valueFormatter: ((dates: CalendarDate[], type: string) => string) | null = null; + @property({ type: Function, attribute: "value-formatter", reflect: true }) + valueFormatter: ((dates: CalendarDate[]) => string) | null = null; /** * Sets calendar to disabled */ - @property({ type: Boolean }) + @property({ type: Boolean, reflect: true }) disabled: boolean; /** * Defines invalid text to datepicker input @@ -50,14 +50,14 @@ export default class BlDatepicker extends DatepickerCalendarMixin { /** * Defines help text to datepicker input for users */ - @property() + @property({ type: String, attribute: "help-text", reflect: true }) helpText: string; @state() - private _value = ""; + _value = ""; @state() - private _selectedDates: CalendarDate[] = []; + _selectedDates: CalendarDate[] = []; @state() private _floatingDateCount: number = 0; @@ -66,13 +66,13 @@ export default class BlDatepicker extends DatepickerCalendarMixin { private _fittingDateCount: number = 0; @query("bl-calendar") - private _calendarEl: BlCalendar; + _calendarEl: BlCalendar; @query("bl-popover") - private _popoverEl: BlPopover; + _popoverEl: BlPopover; - // Query the bl-input component - @query("bl-input") inputEl!: BlInput; + @query("bl-input") + _inputEl!: BlInput; static get styles(): CSSResultGroup { return [style]; @@ -126,7 +126,7 @@ export default class BlDatepicker extends DatepickerCalendarMixin { } else { this._selectedDates = dates; if (this.valueFormatter) { - this._value = this.valueFormatter(this._selectedDates, this.type); + this._value = this.valueFormatter(this._selectedDates); } else { this._defaultValueFormatter(); } @@ -150,7 +150,7 @@ export default class BlDatepicker extends DatepickerCalendarMixin { } openPopover() { - this._popoverEl.target = this.inputEl; + this._popoverEl.target = this._inputEl; this._popoverEl.show(); } @@ -163,17 +163,17 @@ export default class BlDatepicker extends DatepickerCalendarMixin { } async firstUpdated() { - this.inputEl?.addEventListener("mousedown", event => { + this._inputEl?.addEventListener("mousedown", event => { event.preventDefault(); }); document.addEventListener("mousedown", event => { const path = event.composedPath(); - if (path.includes(this._calendarEl) || (this.inputEl && path.includes(this.inputEl))) { + if (path.includes(this._calendarEl) || (this._inputEl && path.includes(this._inputEl))) { event.preventDefault(); - this.inputEl?.focus(); + this._inputEl?.focus(); } }); if (this._defaultValue) { @@ -184,13 +184,6 @@ export default class BlDatepicker extends DatepickerCalendarMixin { } } - async triggerInputError() { - await this.inputEl.updateComplete; - if (this.inputEl) { - await this.inputEl.forceCustomError(); - } - } - formatAdditionalDates(str: string): TemplateResult[] { const parts = str.split(","); @@ -213,6 +206,7 @@ export default class BlDatepicker extends DatepickerCalendarMixin { .startOfWeek=${this.startOfWeek} .disabledDates=${this.disabledDates} .defaultValue=${this._defaultValue} + .locale=${this.locale} @bl-calendar-change="${(event: CustomEvent) => this.setDatePickerInput(event.detail)}" > @@ -261,7 +255,6 @@ export default class BlDatepicker extends DatepickerCalendarMixin { help-text=${this.helpText} .customInvalidText=${this.invalidText} ?disabled=${this.disabled} - readOnly >
${additionalDatesView} ${clearDatepickerButton} From 4c40fbafca6d2eda92d5434b81490f1102f7ee20 Mon Sep 17 00:00:00 2001 From: "dilan.dogan2" Date: Thu, 19 Sep 2024 09:09:33 +0300 Subject: [PATCH 15/35] feat(datepicker): run prettier --- src/components/datepicker/bl-datepicker.css | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/components/datepicker/bl-datepicker.css b/src/components/datepicker/bl-datepicker.css index 54c1b707..ae43f0af 100644 --- a/src/components/datepicker/bl-datepicker.css +++ b/src/components/datepicker/bl-datepicker.css @@ -45,10 +45,9 @@ margin-right: 5px; } -bl-popover{ - --bl-popover-padding:0; - --bl-popover-border-size:0; - --bl-popover-border-radius:0; - --bl-popover-background-color:transparent; - +bl-popover { + --bl-popover-padding: 0; + --bl-popover-border-size: 0; + --bl-popover-border-radius: 0; + --bl-popover-background-color: transparent; } From e4720faa0b8e2e596e58744db17a45ee9d96cd8d Mon Sep 17 00:00:00 2001 From: "dilan.dogan2" Date: Thu, 19 Sep 2024 09:18:07 +0300 Subject: [PATCH 16/35] feat(datepicker): remove unnecessary tests --- .../datepicker/bl-datepicker.test.ts | 32 ------------------- 1 file changed, 32 deletions(-) diff --git a/src/components/datepicker/bl-datepicker.test.ts b/src/components/datepicker/bl-datepicker.test.ts index 60731012..e80c840c 100644 --- a/src/components/datepicker/bl-datepicker.test.ts +++ b/src/components/datepicker/bl-datepicker.test.ts @@ -1,7 +1,6 @@ import { aTimeout, expect, fixture, html } from "@open-wc/testing"; import BlDatepicker, { blDatepickerChangedEvent } from "./bl-datepicker"; import { BlButton, BlDatePicker } from "../../baklava"; -import { CALENDAR_TYPES } from "../calendar/bl-calendar.constant"; import { blCalendarChangedEvent } from "../calendar/bl-calendar"; describe("BlDatepicker", () => { @@ -82,35 +81,4 @@ describe("BlDatepicker", () => { expect(element._selectedDates).to.deep.equal([]); expect(element._value).to.equal(""); }); - - it("should show tooltip with extra dates when multiple dates are selected", async () => { - - element._selectedDates = [ - new Date(2023, 1, 1), - new Date(2023, 1, 2), - new Date(2023, 1, 3), - new Date(2023, 1, 4), - ]; - element.type = CALENDAR_TYPES.MULTIPLE; - await element.updateComplete; - - const tooltipTrigger = element.shadowRoot?.querySelector('bl-tooltip [slot="tooltip-trigger"]'); - - await element.updateComplete; - await aTimeout(400); - - expect(tooltipTrigger).to.exist; - expect(tooltipTrigger?.textContent).to.equal("+1"); - }); - - it("should use custom valueFormatter function if provided", async () => { - element.valueFormatter= (dates:Date[]) => `Selected: ${dates.length} dates`; - - element.type = CALENDAR_TYPES.MULTIPLE; - element._selectedDates = [new Date(2023, 1, 1), new Date(2023, 1, 2)]; - await element.updateComplete; - - expect(element._value).to.equal("Selected: 2 dates"); - }); - }); From 878f8e9236ec7b30f767c6ecf6577c4502c450cc Mon Sep 17 00:00:00 2001 From: "dilan.dogan2" Date: Fri, 20 Sep 2024 12:00:46 +0300 Subject: [PATCH 17/35] feat(datepicker): remove calendar border --- src/components/calendar/bl-calendar.css | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/calendar/bl-calendar.css b/src/components/calendar/bl-calendar.css index 8bc5ee7c..a5f4c673 100644 --- a/src/components/calendar/bl-calendar.css +++ b/src/components/calendar/bl-calendar.css @@ -11,7 +11,6 @@ gap: 16px; border-radius: var(--bl-border-radius-s); width: fit-content; - border: 1px solid var(--bl-color-primary); background: var(--bl-color-neutral-full); } From 3125cf01002e6c38fa82186fc3d2a7a9cb4588ab Mon Sep 17 00:00:00 2001 From: "dilan.dogan2" Date: Tue, 8 Oct 2024 00:04:45 +0300 Subject: [PATCH 18/35] feat(datepicker): add all test cases for datepicker and calendar components --- src/components/calendar/bl-calendar.test.ts | 421 +++++++++++++++++- src/components/calendar/bl-calendar.ts | 12 +- src/components/datepicker/bl-datepicker.css | 2 - .../datepicker/bl-datepicker.test.ts | 158 ++++++- src/components/datepicker/bl-datepicker.ts | 3 +- 5 files changed, 582 insertions(+), 14 deletions(-) diff --git a/src/components/calendar/bl-calendar.test.ts b/src/components/calendar/bl-calendar.test.ts index e0a3c5fc..6dfda69e 100644 --- a/src/components/calendar/bl-calendar.test.ts +++ b/src/components/calendar/bl-calendar.test.ts @@ -1,8 +1,10 @@ -import { fixture, expect, html } from "@open-wc/testing"; +import { expect, fixture, html } from "@open-wc/testing"; import "./bl-calendar"; import { BlButton, BlCalendar } from "../../baklava"; import { blCalendarChangedEvent } from "./bl-calendar"; import { blDatepickerClearSelectedDatesEvent } from "../datepicker/bl-datepicker"; +import { CALENDAR_TYPES, CALENDAR_VIEWS, FIRST_MONTH_INDEX, LAST_MONTH_INDEX } from "./bl-calendar.constant"; +import sinon from "sinon"; describe("bl-calendar", () => { let element: BlCalendar; @@ -62,7 +64,7 @@ describe("bl-calendar", () => { const multipleTypeCalendar= element = await fixture(html``); await multipleTypeCalendar.updateComplete; - const dayButtons = Array.from(element.shadowRoot?.querySelectorAll(".day-wrapper bl-button") || []) as BlButton[]; + const dayButtons = Array.from(element.shadowRoot?.querySelectorAll(".day-wrapper bl-button") || []) as BlButton[]; dayButtons[0].click(); dayButtons[1].click(); @@ -117,4 +119,419 @@ describe("bl-calendar", () => { } }); }); + + it("should not allow selection of dates before minDate", async () => { + element.minDate = new Date(2023, 0, 15); + element._calendarMonth = 0; + element._calendarYear = 2023; + + await element.updateComplete; + + const calendarDay = Array.from( + element.shadowRoot?.querySelectorAll("bl-button")|| [] + ).find(button => button?.textContent?.trim() === "10"); + + + expect(calendarDay?.hasAttribute("disabled")).to.be.true; + }); + + it("should switch to month view when the month button is clicked", async () => { + const monthButton = element.shadowRoot?.querySelector(".header-text") as BlButton; + + monthButton?.click(); + + expect(element._calendarView).to.equal(CALENDAR_VIEWS.MONTHS); + }); + + it("should select a date range correctly", async () => { + const startDate = new Date(2023, 1, 10); + const endDate = new Date(2023, 1, 20); + + element.handleRangeSelectCalendar(startDate); + element.handleRangeSelectCalendar(endDate); + + expect(element._selectedRangeDates.startDate).to.deep.equal(startDate); + expect(element._selectedRangeDates.endDate).to.deep.equal(endDate); + }); + + it("should render month names in the correct locale", async () => { + element = await fixture(html``); + + const monthName = new Date().toLocaleString("fr", { month: "long" }); + const firstMonth = element.shadowRoot?.querySelector(".header-text")?.textContent; + + expect(firstMonth).to.equal(monthName); + }); + + it("should apply the today-day class to today's date", async () => { + + const todayElement = Array.from( + element.shadowRoot?.querySelectorAll("bl-button") || [] + ).find(button => button?.textContent?.trim() === `${element.today.getDate()}`); + + expect(todayElement?.classList.contains("today-day")).to.be.true; + }); + + it("should clear selected dates when blDatepickerClearSelectedDatesEvent is triggered", async () => { + const testDate = new Date(2023, 1, 10); + + element._selectedDates = [testDate]; + + window.dispatchEvent(new CustomEvent(blDatepickerClearSelectedDatesEvent)); + + expect(element._selectedDates).to.be.empty; + expect(element._selectedRangeDates.startDate).to.be.undefined; + expect(element._selectedRangeDates.endDate).to.be.undefined; + }); + + it("should switch to the year view and render years", async () => { + const yearButton = (element.shadowRoot?.querySelectorAll(".header-text")[1]) as BlButton; + + yearButton?.click(); + + await element.updateComplete; + + expect(element._calendarView).to.equal(CALENDAR_VIEWS.YEARS); + const yearButtons = element.shadowRoot?.querySelectorAll(".grid-item"); + + expect(yearButtons?.length).to.equal(12); + }); + + it("should update the calendar month and view when setMonthAndCalendarView is called", async () => { + const setHoverClassSpy = sinon.spy(element, "setHoverClass"); + const testMonth = 5; + + element.setMonthAndCalendarView(testMonth); + + expect(element._calendarMonth).to.equal(testMonth); + expect(element._calendarView).to.equal(CALENDAR_VIEWS.DAYS); + expect(setHoverClassSpy.calledOnce).to.be.false; + + element.type = CALENDAR_TYPES.RANGE; + element.setMonthAndCalendarView(testMonth); + + expect(setHoverClassSpy.calledOnce).to.be.true; + }); + + it("should update the calendar year and view when setYearAndCalendarView is called", async () => { + const setHoverClassSpy = sinon.spy(element, "setHoverClass"); + const testYear = 2025; + + element.setYearAndCalendarView(testYear); + + + expect(element._calendarYear).to.equal(testYear); + expect(element._calendarView).to.equal(CALENDAR_VIEWS.DAYS); + expect(setHoverClassSpy.calledOnce).to.be.false; + + + element.type = CALENDAR_TYPES.RANGE; + element.setYearAndCalendarView(testYear); + + expect(setHoverClassSpy.calledOnce).to.be.true; + }); + + it("should return true if calendarDate is in disabledDates", () => { + const calendarDate = new Date(2023,9,18); + + element.disabledDates = [new Date(2023,9,18),new Date(2023,9,20)]; + + const result = element.checkIfDateIsDisabled(calendarDate); + + expect(result).to.be.true; + }); + + it("should return false if calendarDate is not in disabledDates", () => { + const calendarDate = new Date(2023,9,19); + + element.disabledDates = [new Date(2023,9,18),new Date(2023,9,20)]; + + const result = element.checkIfDateIsDisabled(calendarDate); + + expect(result).to.be.false; + }); + + + it("should wrap _defaultValue in an array if it is a single date", async () => { + const calendar = new BlCalendar(); + + calendar._defaultValue = new Date("2023-09-18"); + calendar.type = CALENDAR_TYPES.SINGLE; + + await calendar.firstUpdated(); + + expect(calendar._selectedDates).to.deep.equal([new Date("2023-09-18")],{ }); + }); + + it("should set startDate and endDate in _selectedRangeDates when type is RANGE", async () => { + const defaultDate1=new Date(2023,9,18); + const defaultDate2=new Date(2023,9,19); + + element._defaultValue = [defaultDate1,defaultDate2]; + element.type = CALENDAR_TYPES.RANGE; + + const setHoverClassSpy = sinon.spy(element, "setHoverClass"); + + await element.firstUpdated(); + + expect(element._selectedRangeDates.startDate).to.be.equal(defaultDate1); + expect(element._selectedRangeDates.endDate).to.be.equal(defaultDate2); + expect(setHoverClassSpy).to.be.calledOnce; + }); + + it("should not set _selectedDates or _selectedRangeDates if _defaultValue is undefined", async () => { + + await element.firstUpdated(); + + expect(element._selectedDates).to.deep.equal([]); + expect(element._selectedRangeDates.startDate).to.be.undefined; + expect(element._selectedRangeDates.endDate).to.be.undefined; + }); + + it("should navigate to the previous month in DAYS view", async () => { + element._calendarView = CALENDAR_VIEWS.DAYS; + element._calendarMonth = 5; + element._calendarYear = 2023; + + element.setPreviousCalendarView(); + await element.updateComplete; + + expect(element._calendarMonth).to.equal(4); + expect(element._calendarYear).to.equal(2023); + }); + + it("should navigate to December of the previous year if on January in DAYS view", async () => { + element._calendarView = CALENDAR_VIEWS.DAYS; + element._calendarMonth = FIRST_MONTH_INDEX; + element._calendarYear = 2023; + + element.setPreviousCalendarView(); + await element.updateComplete; + + expect(element._calendarMonth).to.equal(LAST_MONTH_INDEX); + expect(element._calendarYear).to.equal(2022); + }); + + it("should navigate to the previous year in MONTHS view", async () => { + element._calendarView = CALENDAR_VIEWS.MONTHS; + element._calendarYear = 2023; + + element.setPreviousCalendarView(); + await element.updateComplete; + + expect(element._calendarYear).to.equal(2022); + }); + + it("should generate the previous 12 years when in YEARS view", async () => { + element._calendarView = CALENDAR_VIEWS.YEARS; + element._calendarYears = [2023]; + + element.setPreviousCalendarView(); + await element.updateComplete; + + expect(element._calendarYears.length).to.equal(12); + expect(element._calendarYears).to.deep.equal([ + 2022, 2021, 2020, 2019, 2018, 2017, 2016, 2015, 2014, 2013, 2012, 2011 + ]); + }); + + it("should update calendar when in DAYS view and month is December", async () => { + element._calendarView = CALENDAR_VIEWS.DAYS; + element._calendarMonth = 11; + element._calendarYear = 2023; + + element.setNextCalendarView(); + await element.updateComplete; + + + expect(element._calendarMonth).to.equal(0); + expect(element._calendarYear).to.equal(2024); + }); + + it("should update calendar when in DAYS view and month is not December", async () => { + element._calendarView = CALENDAR_VIEWS.DAYS; + element._calendarMonth = 5; + element._calendarYear = 2023; + + element.setNextCalendarView(); + await element.updateComplete; + + + expect(element._calendarMonth).to.equal(6); + expect(element._calendarYear).to.equal(2023); + }); + + it("should update year when in MONTHS view", async () => { + element._calendarView = CALENDAR_VIEWS.MONTHS; + element._calendarYear = 2023; + + element.setNextCalendarView(); + await element.updateComplete; + + + expect(element._calendarYear).to.equal(2024); + }); + + it("should update calendar years when in YEARS view", async () => { + element._calendarView = CALENDAR_VIEWS.YEARS; + element._calendarYears = [2020, 2021, 2022, 2023, 2024, 2025, 2026, 2027, 2028, 2029, 2030, 2031]; + + element.setNextCalendarView(); + await element.updateComplete; + + + expect(element._calendarYears.length).to.equal(12); + expect(element._calendarYears).to.deep.equal([ + 2032, 2033, 2034, 2035, 2036, 2037, 2038, 2039, 2040, 2041, 2042, 2043 + ]); + }); + + it("should set both startDate and endDate when endDate is not set and calendarDate is earlier than startDate", () => { + const startDate = new Date(2023, 0, 5); + const calendarDate = new Date(2023, 0, 1); + + + element._selectedRangeDates.startDate = startDate; + element._selectedDates.push(startDate); + + + element.handleRangeSelectCalendar(calendarDate); + + expect(element._selectedRangeDates).to.deep.equal({ startDate: calendarDate, endDate: startDate }); + expect(element._selectedDates).to.deep.equal([calendarDate, startDate]); + }); + + it("should reset to only startDate when both startDate and endDate are set", () => { + const calendarDate = new Date(2023, 0, 10); + const startDate = new Date(2023, 0, 5); + const endDate = new Date(2023, 0, 15); + + + element._selectedRangeDates = { startDate, endDate }; + element._selectedDates = [startDate, endDate]; + + + element.handleRangeSelectCalendar(calendarDate); + + expect(element._selectedRangeDates).to.deep.equal({ startDate: calendarDate, endDate: undefined }); + expect(element._selectedDates).to.deep.equal([calendarDate]); + }); + + + it("should remove the date if it already exists in _selectedDates", () => { + const calendarDate = new Date(2023, 0, 5); + + element._selectedDates.push(calendarDate); + + element.handleMultipleSelectCalendar(calendarDate); + + expect(element._selectedDates).to.not.include(calendarDate); + expect(element._selectedDates).to.have.lengthOf(0); + }); + + it("should add the date if it does not exist in _selectedDates", () => { + const calendarDate = new Date(2023, 0, 5); + + element.handleMultipleSelectCalendar(calendarDate); + + expect(element._selectedDates).to.include(calendarDate); + expect(element._selectedDates).to.have.lengthOf(1); + }); + + it("should call handleRangeSelectCalendar when type is RANGE", () => { + const calendarDate = new Date(2023, 6, 15); + + element.type = CALENDAR_TYPES.RANGE; + + const handleRangeSelectCalendarStub = sinon.stub(element, "handleRangeSelectCalendar"); + + element.handleDate(calendarDate); + + expect(handleRangeSelectCalendarStub).to.have.been.calledWith(calendarDate); + }); + + it('should apply "range-day" class to elements between startDate and endDate', async () => { + + const startDate = new Date(2024, 9, 5); + const endDate = new Date(2024, 9, 10); + + + element._selectedRangeDates = { startDate, endDate }; + + + const rangeDates = [ + new Date(2024, 9, 6), + new Date(2024, 9, 7), + new Date(2024, 9, 8), + new Date(2024, 9, 9) + ]; + + rangeDates.forEach(date => { + + const fakeElement = document.createElement("div"); + + fakeElement.id = `${date.getTime()}`; + const parentElement = document.createElement("div"); + + parentElement.appendChild(fakeElement); + element.shadowRoot?.appendChild(fakeElement); + }); + + + element.setHoverClass(); + + + await new Promise(resolve => setTimeout(resolve)); + + + rangeDates.forEach(date => { + const elementWithId = element.shadowRoot?.getElementById(`${date.getTime()}`)?.parentElement; + + expect(elementWithId?.classList.contains("range-day")).to.be.true; + }); + }); + + + it("should correctly calculate lastMonthDaysCount when currentMonthStartWeekDay < startOfWeek", () => { + + + element._calendarYear = 2024; + element._calendarMonth = 9; + + element.startOfWeek = 1; + const currentMonthStartWeekDay = 0; + + + element.getWeekDayOfDate = () => currentMonthStartWeekDay; + + + element.getDayNumInAMonth = (_year, month) => (month === 8 ? 30 : 31); + + + element.createCalendarDays(); + + + + const expectedLastMonthDaysCount = 7 - (element.startOfWeek - currentMonthStartWeekDay); + + expect(element.getDayNumInAMonth(2024, 8)).to.equal(30); + expect(element.getWeekDayOfDate(2024, 9)).to.equal(currentMonthStartWeekDay); + expect(expectedLastMonthDaysCount).to.equal(6); + }); + + it("should correctly calculate lastMonthDaysCount when currentMonthStartWeekDay >= startOfWeek", () => { + element.startOfWeek = 1; + const currentMonthStartWeekDay = 2; + + element.getWeekDayOfDate = () => currentMonthStartWeekDay; + + element.getDayNumInAMonth = (_year, month) => (month === 8 ? 30 : 31); + + element.createCalendarDays(); + + const expectedLastMonthDaysCount = currentMonthStartWeekDay - element.startOfWeek; + + expect(expectedLastMonthDaysCount).to.equal(1); + }); + }); diff --git a/src/components/calendar/bl-calendar.ts b/src/components/calendar/bl-calendar.ts index 068e9214..546cb729 100644 --- a/src/components/calendar/bl-calendar.ts +++ b/src/components/calendar/bl-calendar.ts @@ -43,25 +43,25 @@ export default class BlCalendar extends DatepickerCalendarMixin { _selectedDates: CalendarDate[] = []; @state() - private _selectedRangeDates: RangePickerDates = { startDate: undefined, endDate: undefined }; + _selectedRangeDates: RangePickerDates = { startDate: undefined, endDate: undefined }; @state() - private today = new Date(); + today = new Date(); @state() _calendarMonth: number = this.today.getMonth(); @state() - private _calendarYear: number = this.today.getFullYear(); + _calendarYear: number = this.today.getFullYear(); @state() - private _calendarView: CalendarView = CALENDAR_VIEWS.DAYS; + _calendarView: CalendarView = CALENDAR_VIEWS.DAYS; @state() - private _calendarYears: number[] = []; + _calendarYears: number[] = []; @state() - private _calendarDays: CalendarDay[] = []; + _calendarDays: CalendarDay[] = []; /** * Fires when date selection changes diff --git a/src/components/datepicker/bl-datepicker.css b/src/components/datepicker/bl-datepicker.css index ae43f0af..952a6dff 100644 --- a/src/components/datepicker/bl-datepicker.css +++ b/src/components/datepicker/bl-datepicker.css @@ -47,7 +47,5 @@ bl-popover { --bl-popover-padding: 0; - --bl-popover-border-size: 0; - --bl-popover-border-radius: 0; --bl-popover-background-color: transparent; } diff --git a/src/components/datepicker/bl-datepicker.test.ts b/src/components/datepicker/bl-datepicker.test.ts index e80c840c..1bd56bbd 100644 --- a/src/components/datepicker/bl-datepicker.test.ts +++ b/src/components/datepicker/bl-datepicker.test.ts @@ -2,6 +2,8 @@ import { aTimeout, expect, fixture, html } from "@open-wc/testing"; import BlDatepicker, { blDatepickerChangedEvent } from "./bl-datepicker"; import { BlButton, BlDatePicker } from "../../baklava"; import { blCalendarChangedEvent } from "../calendar/bl-calendar"; +import { CALENDAR_TYPES } from "../calendar/bl-calendar.constant"; +import sinon from "sinon"; describe("BlDatepicker", () => { let element : BlDatepicker; @@ -33,11 +35,11 @@ describe("BlDatepicker", () => { it("should open the popover when input is clicked", async () => { - element._inputEl?.click(); // Simulate clicking the input + element._inputEl?.click(); await element.updateComplete; expect(element._popoverEl).to.exist; - expect(element._popoverEl?.visible).to.be.true; // Assert that the popover is visible + expect(element._popoverEl?.visible).to.be.true; }); it("should close the popover after selecting a date", async () => { @@ -63,7 +65,6 @@ describe("BlDatepicker", () => { }); - // Simulate the calendar change event that triggers the datepicker change element._calendarEl.dispatchEvent(new CustomEvent(blCalendarChangedEvent, { detail: [testDate] })); await element.updateComplete; @@ -81,4 +82,155 @@ describe("BlDatepicker", () => { expect(element._selectedDates).to.deep.equal([]); expect(element._value).to.equal(""); }); + + it("should disable the input when 'disabled' is set", async () => { + element.disabled = true; + await element.updateComplete; + + const input = element._inputEl; + + expect(input?.hasAttribute("disabled")).to.be.true; + }); + + it("should use custom value formatter when provided", async () => { + const testDate = new Date(2023, 1, 1); + + element.valueFormatter = (dates: Date[]) => `Custom format: ${dates[0].toDateString()}`; + element.setDatePickerInput([testDate]); + await element.updateComplete; + + expect(element._value).to.equal(`Custom format: ${testDate.toDateString()}`); + }); + + it("should handle multiple date selections", async () => { + const dates = [new Date(2023, 1, 1), new Date(2023, 1, 2)]; + + element.type = CALENDAR_TYPES.MULTIPLE; + await element.updateComplete; + + element._calendarEl?.dispatchEvent(new CustomEvent(blCalendarChangedEvent, { detail: dates })); + await element.updateComplete; + + expect(element._selectedDates.length).to.equal(2); + expect(element._selectedDates).to.deep.equal(dates); + }); + + it("should clear the datepicker even if no dates are selected", async () => { + element.clearDatepicker(); + await element.updateComplete; + + expect(element._selectedDates).to.deep.equal([]); + expect(element._value).to.equal(""); + }); + + it("should handle selecting a range of dates", async () => { + const startDate = new Date(2023, 1, 1); + const endDate = new Date(2023, 1, 7); + + element.type = CALENDAR_TYPES.RANGE; + await element.updateComplete; + + element._calendarEl?.dispatchEvent(new CustomEvent(blCalendarChangedEvent, { detail: [startDate, endDate] })); + await element.updateComplete; + + expect(element._selectedDates.length).to.equal(2); + expect(element._selectedDates).to.deep.equal([startDate, endDate]); + }); + + it("should display help text when provided", async () => { + element.helpText = "Please select a valid date."; + await element.updateComplete; + + const input = element._inputEl; + + expect(input?.getAttribute("help-text")).to.equal("Please select a valid date."); + }); + + it("should close the popover after timeout", async () => { + element._inputEl?.click(); + await element.updateComplete; + + element.closePopoverWithTimeout(); + await aTimeout(300); + + expect(element._popoverEl.visible).to.be.false; + }); + + it("should prevent default mousedown event and focus input element when clicked", async () => { + const focusSpy = sinon.spy(element._inputEl, "focus"); + + const inputAddEventListenerSpy = sinon.spy(element._inputEl, "addEventListener"); + const documentAddEventListenerSpy = sinon.spy(document, "addEventListener"); + + await element.firstUpdated(); + + expect(inputAddEventListenerSpy).to.have.been.calledWith("mousedown"); + expect(documentAddEventListenerSpy).to.have.been.calledWith("mousedown"); + + const mousedownEvent = new MouseEvent("mousedown", { bubbles: true, cancelable: true }); + + element._inputEl.dispatchEvent(mousedownEvent); + + expect(mousedownEvent.defaultPrevented).to.be.true; + + const documentMouseEvent = new MouseEvent("mousedown", { bubbles: true, composed: true }); + + sinon.stub(documentMouseEvent, "composedPath").returns([element._inputEl]); + + document.dispatchEvent(documentMouseEvent); + + expect(focusSpy).to.have.been.called; + }); + + it("should set selected dates and call setDatePickerInput when default value is provided", async () => { + element._defaultValue = new Date(2024,10,10); + + const setDatePickerInputSpy = sinon.spy(element, "setDatePickerInput"); + + await element.firstUpdated(); + + expect(element._selectedDates).to.deep.equal([new Date(2024,10,10)]); + + expect(setDatePickerInputSpy).to.have.been.calledWith(element._selectedDates); + }); + + it("should handle an array of dates for default value", async () => { + element._defaultValue = [new Date(2024,10,10), new Date(2024,10,11)]; + + const setDatePickerInputSpy = sinon.spy(element, "setDatePickerInput"); + + await element.firstUpdated(); + + expect(element._selectedDates).to.deep.equal([ + new Date(2024,10,10), + new Date(2024,10,11) + ]); + + expect(setDatePickerInputSpy).to.have.been.calledWith(element._selectedDates); + }); + + + it("should insert a
after every third item", () => { + const inputString = "Item1, Item2, Item3, Item4"; + const result = element.formatAdditionalDates(inputString); + + expect(result[3].strings).to.include("
"); + }); + + + it("should render the tooltip when floatingDateCount > 0", async () => { + element._floatingDateCount = 2; + element.requestUpdate(); + + await element.updateComplete; + + const tooltip = element.shadowRoot?.querySelector("bl-tooltip"); + + expect(tooltip).to.not.be.null; + + const trigger = tooltip?.querySelector('[slot="tooltip-trigger"]'); + + expect(trigger).to.not.be.null; + expect(trigger?.textContent).to.equal("+2"); + }); }); diff --git a/src/components/datepicker/bl-datepicker.ts b/src/components/datepicker/bl-datepicker.ts index dfad19b8..5a1d52d6 100644 --- a/src/components/datepicker/bl-datepicker.ts +++ b/src/components/datepicker/bl-datepicker.ts @@ -60,7 +60,7 @@ export default class BlDatepicker extends DatepickerCalendarMixin { _selectedDates: CalendarDate[] = []; @state() - private _floatingDateCount: number = 0; + _floatingDateCount: number = 0; @state() private _fittingDateCount: number = 0; @@ -255,6 +255,7 @@ export default class BlDatepicker extends DatepickerCalendarMixin { help-text=${this.helpText} .customInvalidText=${this.invalidText} ?disabled=${this.disabled} + readonly >
${additionalDatesView} ${clearDatepickerButton} From d5de706ad7c136e222a31ef3ae6456da92e2cab7 Mon Sep 17 00:00:00 2001 From: "dilan.dogan2" Date: Tue, 8 Oct 2024 00:12:41 +0300 Subject: [PATCH 19/35] feat(datepicker): fix lint bug --- src/components/calendar/bl-calendar.test.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/calendar/bl-calendar.test.ts b/src/components/calendar/bl-calendar.test.ts index 6dfda69e..2ee53cc4 100644 --- a/src/components/calendar/bl-calendar.test.ts +++ b/src/components/calendar/bl-calendar.test.ts @@ -511,7 +511,6 @@ describe("bl-calendar", () => { element.createCalendarDays(); - const expectedLastMonthDaysCount = 7 - (element.startOfWeek - currentMonthStartWeekDay); expect(element.getDayNumInAMonth(2024, 8)).to.equal(30); From f345e823f5d3ff20039f0aca8ba6a657509d5fa2 Mon Sep 17 00:00:00 2001 From: "dilan.dogan2" Date: Tue, 8 Oct 2024 18:00:01 +0300 Subject: [PATCH 20/35] feat(datepicker): adding missing statement tests --- src/components/calendar/bl-calendar.test.ts | 165 ++++++++++++++++++ src/components/calendar/bl-calendar.ts | 6 +- .../datepicker/bl-datepicker.test.ts | 87 +++++++++ src/components/datepicker/bl-datepicker.ts | 15 +- src/components/popover/bl-popover.ts | 2 +- 5 files changed, 263 insertions(+), 12 deletions(-) diff --git a/src/components/calendar/bl-calendar.test.ts b/src/components/calendar/bl-calendar.test.ts index 2ee53cc4..45f7f663 100644 --- a/src/components/calendar/bl-calendar.test.ts +++ b/src/components/calendar/bl-calendar.test.ts @@ -533,4 +533,169 @@ describe("bl-calendar", () => { expect(expectedLastMonthDaysCount).to.equal(1); }); + it("should call setHoverClass when type is RANGE", () => { + + element.type = CALENDAR_TYPES.RANGE; + + + const setHoverClassSpy = sinon.spy(element, "setHoverClass"); + + + element._calendarView = CALENDAR_VIEWS.DAYS; + element._calendarMonth = FIRST_MONTH_INDEX; + element._calendarYear = 2024; + element._calendarYears = [2023, 2024, 2025]; + + + element.setPreviousCalendarView(); + + + expect(setHoverClassSpy).to.have.been.called; + + + setHoverClassSpy.restore(); + }); + + it("should not call setHoverClass when type is not RANGE", () => { + + element.type = CALENDAR_TYPES.SINGLE; + + + const setHoverClassSpy = sinon.spy(element, "setHoverClass"); + + + element.setPreviousCalendarView(); + + + expect(setHoverClassSpy).to.not.have.been.called; + + + setHoverClassSpy.restore(); + }); + + it("should set _calendarView to CALENDAR_VIEWS.DAYS when current view is CALENDAR_VIEWS.DAYS", () => { + + element._calendarView = CALENDAR_VIEWS.DAYS; + + + const setHoverClassSpy = sinon.spy(element, "setHoverClass"); + + + element.setCurrentCalendarView(CALENDAR_VIEWS.DAYS); + + + expect(element._calendarView).to.equal(CALENDAR_VIEWS.DAYS); + + + expect(setHoverClassSpy).to.have.been.called; + + + setHoverClassSpy.restore(); + }); + + it("should set _calendarView to the provided view if it is different from the current view", () => { + + element._calendarView = CALENDAR_VIEWS.MONTHS; + + + const setHoverClassSpy = sinon.spy(element, "setHoverClass"); + + + element.setCurrentCalendarView(CALENDAR_VIEWS.DAYS); + + + expect(element._calendarView).to.equal(CALENDAR_VIEWS.DAYS); + + + expect(setHoverClassSpy).to.have.been.called; + + + setHoverClassSpy.restore(); + }); + + it("should call setNextCalendarView when date month is greater than _calendarMonth", () => { + element._calendarMonth = 0; + element.type = CALENDAR_TYPES.SINGLE; + + + const setNextCalendarViewSpy = sinon.spy(element, "setNextCalendarView"); + + + element.handleDate(new Date(2024, 2, 15)); + + + expect(setNextCalendarViewSpy).to.have.been.called; + + + setNextCalendarViewSpy.restore(); + }); + + it("should not call setNextCalendarView when calendar type is RANGE", () => { + element._calendarMonth = 0; + element.type = CALENDAR_TYPES.RANGE; + + + const setNextCalendarViewSpy = sinon.spy(element, "setNextCalendarView"); + + + element.handleDate(new Date(2024, 2, 15)); + + + expect(setNextCalendarViewSpy).to.not.have.been.called; + + + setNextCalendarViewSpy.restore(); + }); + + it("should not call setNextCalendarView when date month is less than or equal to _calendarMonth", () => { + element._calendarMonth = 0; + element._calendarMonth = 3; + + + const setNextCalendarViewSpy = sinon.spy(element, "setNextCalendarView"); + + + element.handleDate(new Date(2024, 2, 15)); + + + expect(setNextCalendarViewSpy).to.not.have.been.called; + + + setNextCalendarViewSpy.restore(); + }); + + it("should not call setTimeout or add any class when startDate or endDate is undefined", () => { + element.createCalendarDays = () => new Map(); + element._selectedRangeDates = { + startDate: undefined, + endDate: new Date(2024, 0, 15) + }; + + + const setTimeoutSpy = sinon.spy(window, "setTimeout"); + + + element.setHoverClass(); + + + expect(setTimeoutSpy).to.not.have.been.called; + + + setTimeoutSpy.restore(); + }); + + it("should add classes when both startDate and endDate are defined", () => { + + element._selectedRangeDates = { + startDate: new Date(2024, 0, 10), + endDate: new Date(2024, 0, 15) + }; + + const setTimeoutSpy = sinon.spy(window, "setTimeout"); + + element.setHoverClass(); + + expect(setTimeoutSpy).to.have.been.calledOnce; + + }); }); diff --git a/src/components/calendar/bl-calendar.ts b/src/components/calendar/bl-calendar.ts index 546cb729..dbf634a0 100644 --- a/src/components/calendar/bl-calendar.ts +++ b/src/components/calendar/bl-calendar.ts @@ -130,7 +130,7 @@ export default class BlCalendar extends DatepickerCalendarMixin { this._calendarYears = Array.from({ length: 12 }, (_, i) => fromYear + (i + 1)); } - this.type === CALENDAR_TYPES.RANGE && this.setHoverClass(); + this.setHoverClass(); } setCurrentCalendarView(view: CalendarView) { @@ -273,8 +273,8 @@ export default class BlCalendar extends DatepickerCalendarMixin { .flat() .filter( date => - date.getTime() > (this._selectedRangeDates?.startDate?.getTime() || 0) && - date.getTime() < (this._selectedRangeDates?.endDate?.getTime() || 0) + date.getTime() > this._selectedRangeDates.startDate!.getTime() && + date.getTime() < this._selectedRangeDates?.endDate!.getTime() ); for (let i = 0; i < rangeDays.length; i++) { diff --git a/src/components/datepicker/bl-datepicker.test.ts b/src/components/datepicker/bl-datepicker.test.ts index 1bd56bbd..67bc873f 100644 --- a/src/components/datepicker/bl-datepicker.test.ts +++ b/src/components/datepicker/bl-datepicker.test.ts @@ -7,12 +7,30 @@ import sinon from "sinon"; describe("BlDatepicker", () => { let element : BlDatepicker; + let getElementByIdStub : sinon.SinonStub; beforeEach(async () => { element = await fixture(html``); + + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-expect-error + getElementByIdStub = sinon.stub(element.shadowRoot, "getElementById").callsFake((id) => { + if (id === "datepicker-input") { + return { offsetWidth: 300 }; + } + if (id === "icon-container") { + return { offsetWidth: 60 }; + } + return null; + }); + await element.updateComplete; }); + afterEach(() => { + getElementByIdStub.restore(); + }); + it("should instantiate the component", () => { expect(document.createElement("bl-datepicker")).instanceOf(HTMLElement); }); @@ -233,4 +251,73 @@ describe("BlDatepicker", () => { expect(trigger).to.not.be.null; expect(trigger?.textContent).to.equal("+2"); }); + + + it('should include " ,..." when _floatingDateCount is greater than 0 for MULTIPLE type', () => { + + element.type = CALENDAR_TYPES.MULTIPLE; + element._selectedDates = [new Date("2024-01-01"), new Date("2024-01-02"), new Date("2024-01-03")]; + + + element.setFloatingDates(); + + + element._defaultValueFormatter(); + + + expect(element._value).to.include(" ,..."); + }); + + it('should not include " ,..." when _floatingDateCount is 0 for MULTIPLE type', () => { + + element.type = CALENDAR_TYPES.MULTIPLE; + element._selectedDates = [new Date("2024-01-01")]; + + + element.setFloatingDates(); + + + element._defaultValueFormatter(); + + + expect(element._value).to.not.include(" ,..."); + }); + + it("should format a date correctly", () => { + const testDate = new Date(2024, 9, 8); + const formattedDate = element.formatDate(testDate); + + + expect(formattedDate).to.equal("08/10/2024"); + }); + + it("should handle single-digit days and months correctly", () => { + const testDate = new Date(2024, 0, 5); + const formattedDate = element.formatDate(testDate); + + + expect(formattedDate).to.equal("05/01/2024"); + }); + + it("should call openPopover when _popoverEl is not visible", () => { + const openPopoverSpy = sinon.spy(element, "openPopover"); + + element._togglePopover(); + + expect(openPopoverSpy).to.have.been.calledOnce; + expect(element._popoverEl.visible).to.be.true; + openPopoverSpy.restore(); + }); + + it("should call closePopover when _popoverEl is visible", () => { + element._popoverEl._visible = true; + const closePopoverSpy = sinon.spy(element, "closePopover"); + + element._togglePopover(); + + expect(closePopoverSpy).to.have.been.calledOnce; + expect(element._popoverEl.visible).to.be.false; + closePopoverSpy.restore(); + }); + }); diff --git a/src/components/datepicker/bl-datepicker.ts b/src/components/datepicker/bl-datepicker.ts index 5a1d52d6..2dc12ffa 100644 --- a/src/components/datepicker/bl-datepicker.ts +++ b/src/components/datepicker/bl-datepicker.ts @@ -1,6 +1,5 @@ import { CSSResultGroup, html, TemplateResult } from "lit"; import { customElement, property, state, query } from "lit/decorators.js"; -import { ifDefined } from "lit/directives/if-defined.js"; import { BlCalendar, BlPopover } from "../../baklava"; import DatepickerCalendarMixin from "../../mixins/datepicker-calendar-mixin/datepicker-calendar-mixin"; import { event, EventDispatcher } from "../../utilities/event"; @@ -63,7 +62,7 @@ export default class BlDatepicker extends DatepickerCalendarMixin { _floatingDateCount: number = 0; @state() - private _fittingDateCount: number = 0; + _fittingDateCount: number = 0; @query("bl-calendar") _calendarEl: BlCalendar; @@ -86,7 +85,8 @@ export default class BlDatepicker extends DatepickerCalendarMixin { */ @event(blDatepickerChangedEvent) private _onBlDatepickerChanged: EventDispatcher; - private _defaultValueFormatter() { + _defaultValueFormatter() { + console.log("this._selectedDates[0]", this._selectedDates[0]); if (this.type === CALENDAR_TYPES.SINGLE) { this._value = this.formatDate(this._selectedDates[0]); this.closePopoverWithTimeout(); @@ -112,8 +112,7 @@ export default class BlDatepicker extends DatepickerCalendarMixin { setFloatingDates() { const datepickerInput = this.shadowRoot?.getElementById("datepicker-input"); const iconsContainer = this.shadowRoot?.getElementById("icon-container"); - const datesTextTotalWidth = - (datepickerInput?.offsetWidth ?? 0) - (iconsContainer?.offsetWidth ?? 0); + const datesTextTotalWidth = datepickerInput!.offsetWidth! - iconsContainer!.offsetWidth!; this._fittingDateCount = Math.floor(datesTextTotalWidth / 90); @@ -135,7 +134,7 @@ export default class BlDatepicker extends DatepickerCalendarMixin { this._onBlDatepickerChanged(this._selectedDates); } - formatDate(date: Date) { + formatDate(date: Date): string { return `${String(date?.getDate()).padStart(2, "0")}/${String(date?.getMonth() + 1).padStart( 2, "0" @@ -158,7 +157,7 @@ export default class BlDatepicker extends DatepickerCalendarMixin { this._popoverEl.hide(); } - private _togglePopover() { + _togglePopover() { this._popoverEl.visible ? this.closePopover() : this.openPopover(); } @@ -234,7 +233,7 @@ export default class BlDatepicker extends DatepickerCalendarMixin { size="small" variant="tertiary" kind="neutral" - icon=${ifDefined(this._selectedDates.length ? "close" : undefined)} + icon="close" @click=${() => this.clearDatepicker()} >
` diff --git a/src/components/popover/bl-popover.ts b/src/components/popover/bl-popover.ts index a783e1f2..ca58b8cc 100644 --- a/src/components/popover/bl-popover.ts +++ b/src/components/popover/bl-popover.ts @@ -79,7 +79,7 @@ export default class BlPopover extends LitElement { /** * Visibility state */ - @state() private _visible = false; + @state() _visible = false; /** * Fires when the popover is shown From 63a592b667809705f8631f15af5fd6b5362e986a Mon Sep 17 00:00:00 2001 From: "dilan.dogan2" Date: Wed, 9 Oct 2024 11:53:27 +0300 Subject: [PATCH 21/35] feat(datepicker): adding missing test for mixin --- src/components/calendar/bl-calendar.test.ts | 76 ++------------ .../datepicker/bl-datepicker.test.ts | 98 ++++++++++++++++--- 2 files changed, 95 insertions(+), 79 deletions(-) diff --git a/src/components/calendar/bl-calendar.test.ts b/src/components/calendar/bl-calendar.test.ts index 45f7f663..5877b03e 100644 --- a/src/components/calendar/bl-calendar.test.ts +++ b/src/components/calendar/bl-calendar.test.ts @@ -219,12 +219,10 @@ describe("bl-calendar", () => { element.setYearAndCalendarView(testYear); - expect(element._calendarYear).to.equal(testYear); expect(element._calendarView).to.equal(CALENDAR_VIEWS.DAYS); expect(setHoverClassSpy.calledOnce).to.be.false; - element.type = CALENDAR_TYPES.RANGE; element.setYearAndCalendarView(testYear); @@ -251,8 +249,7 @@ describe("bl-calendar", () => { expect(result).to.be.false; }); - - it("should wrap _defaultValue in an array if it is a single date", async () => { + it("should wrap defaultValue in an array if it is a single date", async () => { const calendar = new BlCalendar(); calendar._defaultValue = new Date("2023-09-18"); @@ -263,7 +260,7 @@ describe("bl-calendar", () => { expect(calendar._selectedDates).to.deep.equal([new Date("2023-09-18")],{ }); }); - it("should set startDate and endDate in _selectedRangeDates when type is RANGE", async () => { + it("should set startDate and endDate in selectedRangeDates when type is range", async () => { const defaultDate1=new Date(2023,9,18); const defaultDate2=new Date(2023,9,19); @@ -279,7 +276,7 @@ describe("bl-calendar", () => { expect(setHoverClassSpy).to.be.calledOnce; }); - it("should not set _selectedDates or _selectedRangeDates if _defaultValue is undefined", async () => { + it("should not set selectedDates or selectedRangeDates if defaultValue is undefined", async () => { await element.firstUpdated(); @@ -343,7 +340,6 @@ describe("bl-calendar", () => { element.setNextCalendarView(); await element.updateComplete; - expect(element._calendarMonth).to.equal(0); expect(element._calendarYear).to.equal(2024); }); @@ -356,7 +352,6 @@ describe("bl-calendar", () => { element.setNextCalendarView(); await element.updateComplete; - expect(element._calendarMonth).to.equal(6); expect(element._calendarYear).to.equal(2023); }); @@ -368,7 +363,6 @@ describe("bl-calendar", () => { element.setNextCalendarView(); await element.updateComplete; - expect(element._calendarYear).to.equal(2024); }); @@ -379,7 +373,6 @@ describe("bl-calendar", () => { element.setNextCalendarView(); await element.updateComplete; - expect(element._calendarYears.length).to.equal(12); expect(element._calendarYears).to.deep.equal([ 2032, 2033, 2034, 2035, 2036, 2037, 2038, 2039, 2040, 2041, 2042, 2043 @@ -390,11 +383,9 @@ describe("bl-calendar", () => { const startDate = new Date(2023, 0, 5); const calendarDate = new Date(2023, 0, 1); - element._selectedRangeDates.startDate = startDate; element._selectedDates.push(startDate); - element.handleRangeSelectCalendar(calendarDate); expect(element._selectedRangeDates).to.deep.equal({ startDate: calendarDate, endDate: startDate }); @@ -406,11 +397,9 @@ describe("bl-calendar", () => { const startDate = new Date(2023, 0, 5); const endDate = new Date(2023, 0, 15); - element._selectedRangeDates = { startDate, endDate }; element._selectedDates = [startDate, endDate]; - element.handleRangeSelectCalendar(calendarDate); expect(element._selectedRangeDates).to.deep.equal({ startDate: calendarDate, endDate: undefined }); @@ -429,7 +418,7 @@ describe("bl-calendar", () => { expect(element._selectedDates).to.have.lengthOf(0); }); - it("should add the date if it does not exist in _selectedDates", () => { + it("should add the date if it does not exist in selectedDates", () => { const calendarDate = new Date(2023, 0, 5); element.handleMultipleSelectCalendar(calendarDate); @@ -455,10 +444,8 @@ describe("bl-calendar", () => { const startDate = new Date(2024, 9, 5); const endDate = new Date(2024, 9, 10); - element._selectedRangeDates = { startDate, endDate }; - const rangeDates = [ new Date(2024, 9, 6), new Date(2024, 9, 7), @@ -477,13 +464,10 @@ describe("bl-calendar", () => { element.shadowRoot?.appendChild(fakeElement); }); - element.setHoverClass(); - await new Promise(resolve => setTimeout(resolve)); - rangeDates.forEach(date => { const elementWithId = element.shadowRoot?.getElementById(`${date.getTime()}`)?.parentElement; @@ -492,8 +476,7 @@ describe("bl-calendar", () => { }); - it("should correctly calculate lastMonthDaysCount when currentMonthStartWeekDay < startOfWeek", () => { - + it("should correctly calculate lastMonthDaysCount when currentMonthStartWeekDay smaller startOfWeek", () => { element._calendarYear = 2024; element._calendarMonth = 9; @@ -501,16 +484,12 @@ describe("bl-calendar", () => { element.startOfWeek = 1; const currentMonthStartWeekDay = 0; - element.getWeekDayOfDate = () => currentMonthStartWeekDay; - element.getDayNumInAMonth = (_year, month) => (month === 8 ? 30 : 31); - element.createCalendarDays(); - const expectedLastMonthDaysCount = 7 - (element.startOfWeek - currentMonthStartWeekDay); expect(element.getDayNumInAMonth(2024, 8)).to.equal(30); @@ -518,7 +497,7 @@ describe("bl-calendar", () => { expect(expectedLastMonthDaysCount).to.equal(6); }); - it("should correctly calculate lastMonthDaysCount when currentMonthStartWeekDay >= startOfWeek", () => { + it("should correctly calculate lastMonthDaysCount when currentMonthStartWeekDay bigger than startOfWeek", () => { element.startOfWeek = 1; const currentMonthStartWeekDay = 2; @@ -537,22 +516,17 @@ describe("bl-calendar", () => { element.type = CALENDAR_TYPES.RANGE; - const setHoverClassSpy = sinon.spy(element, "setHoverClass"); - element._calendarView = CALENDAR_VIEWS.DAYS; element._calendarMonth = FIRST_MONTH_INDEX; element._calendarYear = 2024; element._calendarYears = [2023, 2024, 2025]; - element.setPreviousCalendarView(); - expect(setHoverClassSpy).to.have.been.called; - setHoverClassSpy.restore(); }); @@ -560,73 +534,55 @@ describe("bl-calendar", () => { element.type = CALENDAR_TYPES.SINGLE; - const setHoverClassSpy = sinon.spy(element, "setHoverClass"); - element.setPreviousCalendarView(); - expect(setHoverClassSpy).to.not.have.been.called; - setHoverClassSpy.restore(); }); - it("should set _calendarView to CALENDAR_VIEWS.DAYS when current view is CALENDAR_VIEWS.DAYS", () => { + it("should set calendarView to CALENDAR_VIEWS.DAYS when current view is CALENDAR_VIEWS.DAYS", () => { element._calendarView = CALENDAR_VIEWS.DAYS; - const setHoverClassSpy = sinon.spy(element, "setHoverClass"); - element.setCurrentCalendarView(CALENDAR_VIEWS.DAYS); - expect(element._calendarView).to.equal(CALENDAR_VIEWS.DAYS); - expect(setHoverClassSpy).to.have.been.called; - setHoverClassSpy.restore(); }); - it("should set _calendarView to the provided view if it is different from the current view", () => { + it("should set calendarView to the provided view if it is different from the current view", () => { element._calendarView = CALENDAR_VIEWS.MONTHS; - const setHoverClassSpy = sinon.spy(element, "setHoverClass"); - element.setCurrentCalendarView(CALENDAR_VIEWS.DAYS); - expect(element._calendarView).to.equal(CALENDAR_VIEWS.DAYS); - expect(setHoverClassSpy).to.have.been.called; - setHoverClassSpy.restore(); }); - it("should call setNextCalendarView when date month is greater than _calendarMonth", () => { + it("should call setNextCalendarView when date month is greater than calendarMonth", () => { element._calendarMonth = 0; element.type = CALENDAR_TYPES.SINGLE; - const setNextCalendarViewSpy = sinon.spy(element, "setNextCalendarView"); - element.handleDate(new Date(2024, 2, 15)); - expect(setNextCalendarViewSpy).to.have.been.called; - setNextCalendarViewSpy.restore(); }); @@ -634,33 +590,25 @@ describe("bl-calendar", () => { element._calendarMonth = 0; element.type = CALENDAR_TYPES.RANGE; - const setNextCalendarViewSpy = sinon.spy(element, "setNextCalendarView"); - element.handleDate(new Date(2024, 2, 15)); - expect(setNextCalendarViewSpy).to.not.have.been.called; - setNextCalendarViewSpy.restore(); }); - it("should not call setNextCalendarView when date month is less than or equal to _calendarMonth", () => { + it("should not call setNextCalendarView when date month is less than or equal to calendarMonth", () => { element._calendarMonth = 0; element._calendarMonth = 3; - const setNextCalendarViewSpy = sinon.spy(element, "setNextCalendarView"); - element.handleDate(new Date(2024, 2, 15)); - expect(setNextCalendarViewSpy).to.not.have.been.called; - setNextCalendarViewSpy.restore(); }); @@ -671,16 +619,12 @@ describe("bl-calendar", () => { endDate: new Date(2024, 0, 15) }; - const setTimeoutSpy = sinon.spy(window, "setTimeout"); - element.setHoverClass(); - expect(setTimeoutSpy).to.not.have.been.called; - setTimeoutSpy.restore(); }); diff --git a/src/components/datepicker/bl-datepicker.test.ts b/src/components/datepicker/bl-datepicker.test.ts index 67bc873f..75abcb16 100644 --- a/src/components/datepicker/bl-datepicker.test.ts +++ b/src/components/datepicker/bl-datepicker.test.ts @@ -8,6 +8,7 @@ import sinon from "sinon"; describe("BlDatepicker", () => { let element : BlDatepicker; let getElementByIdStub : sinon.SinonStub; + let consoleWarnSpy: sinon.SinonSpy; beforeEach(async () => { element = await fixture(html``); @@ -23,12 +24,14 @@ describe("BlDatepicker", () => { } return null; }); + consoleWarnSpy = sinon.spy(console, "warn"); await element.updateComplete; }); afterEach(() => { getElementByIdStub.restore(); + consoleWarnSpy.restore(); }); it("should instantiate the component", () => { @@ -227,7 +230,6 @@ describe("BlDatepicker", () => { expect(setDatePickerInputSpy).to.have.been.calledWith(element._selectedDates); }); - it("should insert a
after every third item", () => { const inputString = "Item1, Item2, Item3, Item4"; const result = element.formatAdditionalDates(inputString); @@ -235,7 +237,6 @@ describe("BlDatepicker", () => { expect(result[3].strings).to.include("
"); }); - it("should render the tooltip when floatingDateCount > 0", async () => { element._floatingDateCount = 2; element.requestUpdate(); @@ -252,8 +253,7 @@ describe("BlDatepicker", () => { expect(trigger?.textContent).to.equal("+2"); }); - - it('should include " ,..." when _floatingDateCount is greater than 0 for MULTIPLE type', () => { + it('should include " ,..." when floatingDateCount is greater than 0 for MULTIPLE type', () => { element.type = CALENDAR_TYPES.MULTIPLE; element._selectedDates = [new Date("2024-01-01"), new Date("2024-01-02"), new Date("2024-01-03")]; @@ -268,18 +268,14 @@ describe("BlDatepicker", () => { expect(element._value).to.include(" ,..."); }); - it('should not include " ,..." when _floatingDateCount is 0 for MULTIPLE type', () => { + it('should not include " ,..." when floatingDateCount is 0 for MULTIPLE type', () => { element.type = CALENDAR_TYPES.MULTIPLE; element._selectedDates = [new Date("2024-01-01")]; - element.setFloatingDates(); - element._defaultValueFormatter(); - - expect(element._value).to.not.include(" ,..."); }); @@ -287,7 +283,6 @@ describe("BlDatepicker", () => { const testDate = new Date(2024, 9, 8); const formattedDate = element.formatDate(testDate); - expect(formattedDate).to.equal("08/10/2024"); }); @@ -295,11 +290,10 @@ describe("BlDatepicker", () => { const testDate = new Date(2024, 0, 5); const formattedDate = element.formatDate(testDate); - expect(formattedDate).to.equal("05/01/2024"); }); - it("should call openPopover when _popoverEl is not visible", () => { + it("should call openPopover when popoverEl is not visible", () => { const openPopoverSpy = sinon.spy(element, "openPopover"); element._togglePopover(); @@ -309,7 +303,7 @@ describe("BlDatepicker", () => { openPopoverSpy.restore(); }); - it("should call closePopover when _popoverEl is visible", () => { + it("should call closePopover when popoverEl is visible", () => { element._popoverEl._visible = true; const closePopoverSpy = sinon.spy(element, "closePopover"); @@ -320,4 +314,82 @@ describe("BlDatepicker", () => { closePopoverSpy.restore(); }); + + it("should return a single date when defaultValue is a single Date", () => { + const date = new Date("2024-01-01"); + + element._defaultValue = date; + expect(element.defaultValue).to.equal(date); + }); + + it("should return an array of dates when defaultValue is an array of Dates", () => { + const dates = [new Date("2024-01-01"), new Date("2024-02-01")]; + + element._defaultValue = dates; + expect(element.defaultValue).to.deep.equal(dates); + }); + + it("should return undefined if defaultValue is not set", () => { + expect(element.defaultValue).to.be.undefined; + }); + + it("should warn when 'defaultValue' is not an array for multiple/range selection", async () => { + element = await fixture(html` + `); + element._defaultValue = new Date(); + + element.firstUpdated(); + + expect(consoleWarnSpy.calledOnce).to.be.true; + }); + + it("should not warn when defaultValue is an array for multiple/range selection", () => { + element.type = CALENDAR_TYPES.MULTIPLE; + element._defaultValue = [new Date(), new Date()]; + + element.firstUpdated(); + + expect(consoleWarnSpy.called).to.be.false; + }); + + it("should warn when defaultValue is an array but not exactly two Date objects in RANGE mode", async () => { + element.type = CALENDAR_TYPES.RANGE; + element._defaultValue = []; + element.setDatePickerInput([]); + + await element.updateComplete; + + expect(consoleWarnSpy.calledOnce).to.be.true; + expect(consoleWarnSpy.calledWith("'defaultValue' must be an array of two Date objects when the date selection mode is set to range.")).to.be.true; + + consoleWarnSpy.resetHistory(); + + + element._defaultValue = [new Date()]; + element.setDatePickerInput([new Date()]); + + await element.updateComplete; + + expect(consoleWarnSpy.calledOnce).to.be.true; + expect(consoleWarnSpy.calledWith("'defaultValue' must be an array of two Date objects when the date selection mode is set to range.")).to.be.true; + + consoleWarnSpy.resetHistory(); + + element._defaultValue = [new Date(), new Date(), new Date()]; + element.setDatePickerInput([new Date(), new Date(), new Date()]); + + await element.updateComplete; + + expect(consoleWarnSpy.calledOnce).to.be.true; + expect(consoleWarnSpy.calledWith("'defaultValue' must be an array of two Date objects when the date selection mode is set to range.")).to.be.true; + }); + + it("should not warn when 'defaultValue' is an array of exactly two Date objects in RANGE mode", () => { + element.type = CALENDAR_TYPES.RANGE; + element._defaultValue = [new Date(), new Date()]; + + element.firstUpdated(); + + expect(consoleWarnSpy.called).to.be.false; + }); }); From a183672d392c8aa004cba356c7e3feb68daafb24 Mon Sep 17 00:00:00 2001 From: "dilan.dogan2" Date: Thu, 10 Oct 2024 10:27:03 +0300 Subject: [PATCH 22/35] feat(datepicker): add input width prop to datepicker --- src/components/datepicker/bl-datepicker.ts | 32 ++++++++++++---------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/src/components/datepicker/bl-datepicker.ts b/src/components/datepicker/bl-datepicker.ts index 2dc12ffa..4dd0f7f5 100644 --- a/src/components/datepicker/bl-datepicker.ts +++ b/src/components/datepicker/bl-datepicker.ts @@ -52,6 +52,9 @@ export default class BlDatepicker extends DatepickerCalendarMixin { @property({ type: String, attribute: "help-text", reflect: true }) helpText: string; + @property({ type: Number, attribute: "input-width", reflect: true }) + inputWidth: number; + @state() _value = ""; @@ -86,7 +89,6 @@ export default class BlDatepicker extends DatepickerCalendarMixin { @event(blDatepickerChangedEvent) private _onBlDatepickerChanged: EventDispatcher; _defaultValueFormatter() { - console.log("this._selectedDates[0]", this._selectedDates[0]); if (this.type === CALENDAR_TYPES.SINGLE) { this._value = this.formatDate(this._selectedDates[0]); this.closePopoverWithTimeout(); @@ -161,7 +163,21 @@ export default class BlDatepicker extends DatepickerCalendarMixin { this._popoverEl.visible ? this.closePopover() : this.openPopover(); } + formatAdditionalDates(str: string): TemplateResult[] { + const parts = str.split(","); + + return parts.reduce((acc, part, index) => { + if (index > 0 && index % 3 === 0) { + acc.push(html`
`); + } + acc.push(html`${part.trim()}${index < parts.length - 1 ? ", " : ""}`); + return acc; + }, []); + } + async firstUpdated() { + this._inputEl?.style.setProperty("width", `${this.inputWidth}px`, "important"); + this._inputEl?.addEventListener("mousedown", event => { event.preventDefault(); }); @@ -183,18 +199,6 @@ export default class BlDatepicker extends DatepickerCalendarMixin { } } - formatAdditionalDates(str: string): TemplateResult[] { - const parts = str.split(","); - - return parts.reduce((acc, part, index) => { - if (index > 0 && index % 3 === 0) { - acc.push(html`
`); - } - acc.push(html`${part.trim()}${index < parts.length - 1 ? ", " : ""}`); - return acc; - }, []); - } - render() { const renderCalendar = html` @@ -221,7 +225,7 @@ export default class BlDatepicker extends DatepickerCalendarMixin { const additionalDatesView = this._floatingDateCount > 0 - ? html` + ? html` +${this._floatingDateCount}
${formattedAdditionalDates}
` From c2dcf15ebe20cf8a5161f45da4ecf62ed5c34ead Mon Sep 17 00:00:00 2001 From: "dilan.dogan2" Date: Thu, 10 Oct 2024 12:13:22 +0300 Subject: [PATCH 23/35] feat(datepicker): min date and max date validation added --- playground/template.html | 64 +++++---- src/components/calendar/bl-calendar.test.ts | 127 +++++++++--------- .../datepicker/bl-datepicker.stories.mdx | 58 ++++++-- .../datepicker/bl-datepicker.test.ts | 8 -- src/components/datepicker/bl-datepicker.ts | 20 +-- .../datepicker-calendar-mixin.ts | 50 ++++--- 6 files changed, 181 insertions(+), 146 deletions(-) diff --git a/playground/template.html b/playground/template.html index 951055f6..1b1d23d6 100644 --- a/playground/template.html +++ b/playground/template.html @@ -1,41 +1,37 @@ - - - - Baklava Playground - - - - - - -

Baklava Playground

+ h1 { + font: var(--bl-font-display-1); + } + + + +

Baklava Playground

-

- Copy this file as playground/index.html and try your work here by running - npm run serve. -

+

+ Copy this file as playground/index.html and try your work here by running + npm run serve. +

-
- Baklava is ready -
- - - +Baklava is ready + diff --git a/src/components/calendar/bl-calendar.test.ts b/src/components/calendar/bl-calendar.test.ts index 5877b03e..2a0c6db4 100644 --- a/src/components/calendar/bl-calendar.test.ts +++ b/src/components/calendar/bl-calendar.test.ts @@ -10,14 +10,15 @@ describe("bl-calendar", () => { let element: BlCalendar; beforeEach(async () => { - element = await fixture(html``); + element = await fixture(html` + `); }); it("should instantiate the component", () => { expect(document.createElement("bl-calendar")).instanceOf(HTMLElement); }); - it("should render the calendar header", () => { + it("should render the calendar header", () => { const headerButtons = element.shadowRoot?.querySelectorAll(".calendar-header bl-button"); expect(headerButtons?.length).to.equal(4); @@ -43,14 +44,15 @@ describe("bl-calendar", () => { ); }); - it("should render days of the week", () => { + it("should render days of the week", () => { const weekDays = element.shadowRoot?.querySelectorAll(".calendar-text.weekday-text"); expect(weekDays?.length).to.equal(7); }); it("should correctly handle single date selection", async () => { - const singleTypeCalendar= element = await fixture(html``); + const singleTypeCalendar = element = await fixture(html` + `); await singleTypeCalendar.updateComplete; const dayButton = element.shadowRoot?.querySelector(".day-wrapper bl-button") as BlButton; @@ -60,8 +62,9 @@ describe("bl-calendar", () => { expect(element.checkIfSelectedDate(element._selectedDates[0])).to.be.true; }); - it("should correctly handle multiple date selection", async () => { - const multipleTypeCalendar= element = await fixture(html``); + it("should correctly handle multiple date selection", async () => { + const multipleTypeCalendar = element = await fixture(html` + `); await multipleTypeCalendar.updateComplete; const dayButtons = Array.from(element.shadowRoot?.querySelectorAll(".day-wrapper bl-button") || []) as BlButton[]; @@ -72,10 +75,11 @@ describe("bl-calendar", () => { }); it("should fire bl-calendar-change event when dates are selected", async () => { - const singleTypeCalendar= await fixture(html``); + const singleTypeCalendar = await fixture(html` + `); await singleTypeCalendar.updateComplete; - let selectedDates:Date[] = []; + let selectedDates: Date[] = []; const onBlCalendarChanged: EventListener = (e: Event) => { const customEvent = e as CustomEvent; @@ -92,45 +96,36 @@ describe("bl-calendar", () => { }); - it("should clear selected dates on receiving blDatepickerClearSelectedDatesEvent",async () => { + it("should clear selected dates on receiving blDatepickerClearSelectedDatesEvent", async () => { element._selectedDates = [new Date()]; window.dispatchEvent(new CustomEvent(blDatepickerClearSelectedDatesEvent)); expect(element._selectedDates.length).to.equal(0); }); - it("should disable dates outside min/max date range", async () => { - const minDate = new Date(2023, 0, 1); - const maxDate = new Date(2023, 11, 31); - - element.minDate = minDate; - element.maxDate = maxDate; + it("should not allow selection of dates before minDate", async () => { + element.minDate = new Date(2023, 0, 15); + element._calendarMonth = 0; + element._calendarYear = 2023; await element.updateComplete; - const days = Array.from(element.shadowRoot?.querySelectorAll(".day-wrapper bl-button") || []) as BlButton[]; - - days?.forEach((day) => { - const date = new Date(Number(day.id)); + const calendarDay = Array.from( + element.shadowRoot?.querySelectorAll("bl-button") || [] + ).find(button => button?.textContent?.trim() === "10"); - if (date < minDate || date > maxDate) { - expect(day.disabled).to.be.true; - } else { - expect(day.disabled).to.be.false; - } - }); + expect(calendarDay?.hasAttribute("disabled")).to.be.true; }); - it("should not allow selection of dates before minDate", async () => { - element.minDate = new Date(2023, 0, 15); + it("should not allow selection of dates after maxDate", async () => { + element.maxDate = new Date(2023, 0, 15); element._calendarMonth = 0; element._calendarYear = 2023; await element.updateComplete; const calendarDay = Array.from( - element.shadowRoot?.querySelectorAll("bl-button")|| [] - ).find(button => button?.textContent?.trim() === "10"); - + element.shadowRoot?.querySelectorAll("bl-button") || [] + ).find(button => button?.textContent?.trim() === "20"); expect(calendarDay?.hasAttribute("disabled")).to.be.true; }); @@ -155,7 +150,8 @@ describe("bl-calendar", () => { }); it("should render month names in the correct locale", async () => { - element = await fixture(html``); + element = await fixture(html` + `); const monthName = new Date().toLocaleString("fr", { month: "long" }); const firstMonth = element.shadowRoot?.querySelector(".header-text")?.textContent; @@ -230,9 +226,9 @@ describe("bl-calendar", () => { }); it("should return true if calendarDate is in disabledDates", () => { - const calendarDate = new Date(2023,9,18); + const calendarDate = new Date(2023, 9, 18); - element.disabledDates = [new Date(2023,9,18),new Date(2023,9,20)]; + element.disabledDates = [new Date(2023, 9, 18), new Date(2023, 9, 20)]; const result = element.checkIfDateIsDisabled(calendarDate); @@ -240,9 +236,9 @@ describe("bl-calendar", () => { }); it("should return false if calendarDate is not in disabledDates", () => { - const calendarDate = new Date(2023,9,19); + const calendarDate = new Date(2023, 9, 19); - element.disabledDates = [new Date(2023,9,18),new Date(2023,9,20)]; + element.disabledDates = [new Date(2023, 9, 18), new Date(2023, 9, 20)]; const result = element.checkIfDateIsDisabled(calendarDate); @@ -257,14 +253,14 @@ describe("bl-calendar", () => { await calendar.firstUpdated(); - expect(calendar._selectedDates).to.deep.equal([new Date("2023-09-18")],{ }); + expect(calendar._selectedDates).to.deep.equal([new Date("2023-09-18")], {}); }); it("should set startDate and endDate in selectedRangeDates when type is range", async () => { - const defaultDate1=new Date(2023,9,18); - const defaultDate2=new Date(2023,9,19); + const defaultDate1 = new Date(2023, 9, 18); + const defaultDate2 = new Date(2023, 9, 19); - element._defaultValue = [defaultDate1,defaultDate2]; + element._defaultValue = [defaultDate1, defaultDate2]; element.type = CALENDAR_TYPES.RANGE; const setHoverClassSpy = sinon.spy(element, "setHoverClass"); @@ -406,7 +402,6 @@ describe("bl-calendar", () => { expect(element._selectedDates).to.deep.equal([calendarDate]); }); - it("should remove the date if it already exists in _selectedDates", () => { const calendarDate = new Date(2023, 0, 5); @@ -439,7 +434,7 @@ describe("bl-calendar", () => { expect(handleRangeSelectCalendarStub).to.have.been.calledWith(calendarDate); }); - it('should apply "range-day" class to elements between startDate and endDate', async () => { + it("should apply \"range-day\" class to elements between startDate and endDate", async () => { const startDate = new Date(2024, 9, 5); const endDate = new Date(2024, 9, 10); @@ -475,7 +470,6 @@ describe("bl-calendar", () => { }); }); - it("should correctly calculate lastMonthDaysCount when currentMonthStartWeekDay smaller startOfWeek", () => { element._calendarYear = 2024; @@ -573,44 +567,44 @@ describe("bl-calendar", () => { setHoverClassSpy.restore(); }); - it("should call setNextCalendarView when date month is greater than calendarMonth", () => { - element._calendarMonth = 0; - element.type = CALENDAR_TYPES.SINGLE; + it("should call setNextCalendarView when date month is greater than calendarMonth", () => { + element._calendarMonth = 0; + element.type = CALENDAR_TYPES.SINGLE; - const setNextCalendarViewSpy = sinon.spy(element, "setNextCalendarView"); + const setNextCalendarViewSpy = sinon.spy(element, "setNextCalendarView"); - element.handleDate(new Date(2024, 2, 15)); + element.handleDate(new Date(2024, 2, 15)); - expect(setNextCalendarViewSpy).to.have.been.called; + expect(setNextCalendarViewSpy).to.have.been.called; - setNextCalendarViewSpy.restore(); - }); + setNextCalendarViewSpy.restore(); + }); - it("should not call setNextCalendarView when calendar type is RANGE", () => { - element._calendarMonth = 0; - element.type = CALENDAR_TYPES.RANGE; + it("should not call setNextCalendarView when calendar type is RANGE", () => { + element._calendarMonth = 0; + element.type = CALENDAR_TYPES.RANGE; - const setNextCalendarViewSpy = sinon.spy(element, "setNextCalendarView"); + const setNextCalendarViewSpy = sinon.spy(element, "setNextCalendarView"); - element.handleDate(new Date(2024, 2, 15)); + element.handleDate(new Date(2024, 2, 15)); - expect(setNextCalendarViewSpy).to.not.have.been.called; + expect(setNextCalendarViewSpy).to.not.have.been.called; - setNextCalendarViewSpy.restore(); - }); + setNextCalendarViewSpy.restore(); + }); - it("should not call setNextCalendarView when date month is less than or equal to calendarMonth", () => { - element._calendarMonth = 0; - element._calendarMonth = 3; + it("should not call setNextCalendarView when date month is less than or equal to calendarMonth", () => { + element._calendarMonth = 0; + element._calendarMonth = 3; - const setNextCalendarViewSpy = sinon.spy(element, "setNextCalendarView"); + const setNextCalendarViewSpy = sinon.spy(element, "setNextCalendarView"); - element.handleDate(new Date(2024, 2, 15)); + element.handleDate(new Date(2024, 2, 15)); - expect(setNextCalendarViewSpy).to.not.have.been.called; + expect(setNextCalendarViewSpy).to.not.have.been.called; - setNextCalendarViewSpy.restore(); - }); + setNextCalendarViewSpy.restore(); + }); it("should not call setTimeout or add any class when startDate or endDate is undefined", () => { element.createCalendarDays = () => new Map(); @@ -640,6 +634,5 @@ describe("bl-calendar", () => { element.setHoverClass(); expect(setTimeoutSpy).to.have.been.calledOnce; - }); }); diff --git a/src/components/datepicker/bl-datepicker.stories.mdx b/src/components/datepicker/bl-datepicker.stories.mdx index 0440c2dd..4b76e49a 100644 --- a/src/components/datepicker/bl-datepicker.stories.mdx +++ b/src/components/datepicker/bl-datepicker.stories.mdx @@ -1,7 +1,7 @@ -import { html } from 'lit'; -import { Meta, Canvas, ArgsTable, Story } from '@storybook/addon-docs'; -import { ifDefined } from 'lit/directives/if-defined.js'; -import { unsafeHTML } from 'lit/directives/unsafe-html.js'; +import { html } from "lit"; +import { ArgsTable, Canvas, Meta, Story } from "@storybook/addon-docs"; +import { ifDefined } from "lit/directives/if-defined.js"; +import { unsafeHTML } from "lit/directives/unsafe-html.js"; -export const DatepickerTemplate = (args) => html` html` + html`${unsafeHTML(args.content)}` + input-width=${ifDefined(args.inputWidth)} + disabled-dates=${ifDefined(args.disabledDates)}>${unsafeHTML(args.content)} + ` -export const Template = (args) => html`${DatepickerTemplate({...args})}`; +export const Template = (args) => html`${DatepickerTemplate({ ...args })}`; # Datepicker [ADR](https://github.com/Trendyol/baklava/issues/894) -[Figma](https://www.figma.com/file/RrcLH0mWpIUy4vwuTlDeKN/Baklava-Design-Guide?type=design&node-id=1412-8914&mode=design&t=b0kU7tBfJQFvz2at-0) +[Figma](https://www.figma.com/file/RrcLH0mWpIUy4vwuTlDeKN/Baklava-Design-Guide?type=design&node-id=1412-8914&mode=design&t=b0kU7tBfJQFvz2at-0) Datepicker renders the calendar component within itself and provides the functionality provided by the calendar component. @@ -53,7 +57,8 @@ Datepicker renders the calendar component within itself and provides the functio Default datepicker type is `single` and you can only select a single day from datepicker. - + {Template.bind({})} @@ -63,7 +68,12 @@ Default datepicker type is `single` and you can only select a single day from da You can select multiple days from Datepicker. - + {Template.bind({})} @@ -73,7 +83,8 @@ You can select multiple days from Datepicker. You can select date range from Datepicker. - + {Template.bind({})} @@ -83,7 +94,12 @@ You can select date range from Datepicker. You can set a default value to datepicker. - + {Template.bind({})} @@ -93,7 +109,23 @@ You can set a default value to datepicker. You can set dates which you want to disable from Datepicker. - + + {Template.bind({})} + + + +### İnput Width + +You can set input width of datepicker as you wish. + + + {Template.bind({})} diff --git a/src/components/datepicker/bl-datepicker.test.ts b/src/components/datepicker/bl-datepicker.test.ts index 75abcb16..1f67ca6d 100644 --- a/src/components/datepicker/bl-datepicker.test.ts +++ b/src/components/datepicker/bl-datepicker.test.ts @@ -257,14 +257,8 @@ describe("BlDatepicker", () => { element.type = CALENDAR_TYPES.MULTIPLE; element._selectedDates = [new Date("2024-01-01"), new Date("2024-01-02"), new Date("2024-01-03")]; - - element.setFloatingDates(); - - element._defaultValueFormatter(); - - expect(element._value).to.include(" ,..."); }); @@ -314,7 +308,6 @@ describe("BlDatepicker", () => { closePopoverSpy.restore(); }); - it("should return a single date when defaultValue is a single Date", () => { const date = new Date("2024-01-01"); @@ -364,7 +357,6 @@ describe("BlDatepicker", () => { consoleWarnSpy.resetHistory(); - element._defaultValue = [new Date()]; element.setDatePickerInput([new Date()]); diff --git a/src/components/datepicker/bl-datepicker.ts b/src/components/datepicker/bl-datepicker.ts index 4dd0f7f5..f5476a19 100644 --- a/src/components/datepicker/bl-datepicker.ts +++ b/src/components/datepicker/bl-datepicker.ts @@ -1,5 +1,5 @@ import { CSSResultGroup, html, TemplateResult } from "lit"; -import { customElement, property, state, query } from "lit/decorators.js"; +import { customElement, property, query, state } from "lit/decorators.js"; import { BlCalendar, BlPopover } from "../../baklava"; import DatepickerCalendarMixin from "../../mixins/datepicker-calendar-mixin/datepicker-calendar-mixin"; import { event, EventDispatcher } from "../../utilities/event"; @@ -37,7 +37,7 @@ export default class BlDatepicker extends DatepickerCalendarMixin { @property({ type: Function, attribute: "value-formatter", reflect: true }) valueFormatter: ((dates: CalendarDate[]) => string) | null = null; /** - * Sets calendar to disabled + * Sets datepicker to disabled */ @property({ type: Boolean, reflect: true }) disabled: boolean; @@ -51,7 +51,9 @@ export default class BlDatepicker extends DatepickerCalendarMixin { */ @property({ type: String, attribute: "help-text", reflect: true }) helpText: string; - + /** + * Defines input width of datepicker + */ @property({ type: Number, attribute: "input-width", reflect: true }) inputWidth: number; @@ -75,10 +77,6 @@ export default class BlDatepicker extends DatepickerCalendarMixin { @query("bl-input") _inputEl!: BlInput; - - static get styles(): CSSResultGroup { - return [style]; - } /** * Fires when date selection is cleared */ @@ -88,6 +86,10 @@ export default class BlDatepicker extends DatepickerCalendarMixin { */ @event(blDatepickerChangedEvent) private _onBlDatepickerChanged: EventDispatcher; + static get styles(): CSSResultGroup { + return [style]; + } + _defaultValueFormatter() { if (this.type === CALENDAR_TYPES.SINGLE) { this._value = this.formatDate(this._selectedDates[0]); @@ -225,7 +227,7 @@ export default class BlDatepicker extends DatepickerCalendarMixin { const additionalDatesView = this._floatingDateCount > 0 - ? html` + ? html` +${this._floatingDateCount}
${formattedAdditionalDates}
` @@ -233,7 +235,7 @@ export default class BlDatepicker extends DatepickerCalendarMixin { const clearDatepickerButton = this._selectedDates.length > 0 - ? html`= maxDate) { + console.warn("minDate cannot be greater than maxDate."); + } else { + this._maxDate = maxDate; + } + } + + /** + * Defines the minimum date value for the calendar + */ + _minDate: Date; + + get minDate() { + return this._minDate; + } + + @property({ type: Date, attribute: "min-date", reflect: true }) + set minDate(minDate: Date) { + if (this._maxDate && this._maxDate >= minDate) { + console.warn("minDate cannot be greater than maxDate."); + } else { + this._minDate = minDate; + } + } + _defaultValue: Date | Date[]; get defaultValue() { From f929160f3f8de8bff0a1032a2488894d2be44215 Mon Sep 17 00:00:00 2001 From: "dilan.dogan2" Date: Thu, 10 Oct 2024 13:49:10 +0300 Subject: [PATCH 24/35] feat(datepicker): min date and max date validation test cases added --- .../datepicker/bl-datepicker.test.ts | 48 ++++++++++++++----- .../datepicker-calendar-mixin.ts | 4 +- 2 files changed, 38 insertions(+), 14 deletions(-) diff --git a/src/components/datepicker/bl-datepicker.test.ts b/src/components/datepicker/bl-datepicker.test.ts index 1f67ca6d..b2e3bab7 100644 --- a/src/components/datepicker/bl-datepicker.test.ts +++ b/src/components/datepicker/bl-datepicker.test.ts @@ -6,12 +6,13 @@ import { CALENDAR_TYPES } from "../calendar/bl-calendar.constant"; import sinon from "sinon"; describe("BlDatepicker", () => { - let element : BlDatepicker; - let getElementByIdStub : sinon.SinonStub; + let element: BlDatepicker; + let getElementByIdStub: sinon.SinonStub; let consoleWarnSpy: sinon.SinonSpy; beforeEach(async () => { - element = await fixture(html``); + element = await fixture(html` + `); // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-expect-error @@ -204,27 +205,27 @@ describe("BlDatepicker", () => { }); it("should set selected dates and call setDatePickerInput when default value is provided", async () => { - element._defaultValue = new Date(2024,10,10); + element._defaultValue = new Date(2024, 10, 10); const setDatePickerInputSpy = sinon.spy(element, "setDatePickerInput"); await element.firstUpdated(); - expect(element._selectedDates).to.deep.equal([new Date(2024,10,10)]); + expect(element._selectedDates).to.deep.equal([new Date(2024, 10, 10)]); expect(setDatePickerInputSpy).to.have.been.calledWith(element._selectedDates); }); it("should handle an array of dates for default value", async () => { - element._defaultValue = [new Date(2024,10,10), new Date(2024,10,11)]; + element._defaultValue = [new Date(2024, 10, 10), new Date(2024, 10, 11)]; const setDatePickerInputSpy = sinon.spy(element, "setDatePickerInput"); await element.firstUpdated(); expect(element._selectedDates).to.deep.equal([ - new Date(2024,10,10), - new Date(2024,10,11) + new Date(2024, 10, 10), + new Date(2024, 10, 11) ]); expect(setDatePickerInputSpy).to.have.been.calledWith(element._selectedDates); @@ -247,13 +248,13 @@ describe("BlDatepicker", () => { expect(tooltip).to.not.be.null; - const trigger = tooltip?.querySelector('[slot="tooltip-trigger"]'); + const trigger = tooltip?.querySelector("[slot=\"tooltip-trigger\"]"); expect(trigger).to.not.be.null; expect(trigger?.textContent).to.equal("+2"); }); - it('should include " ,..." when floatingDateCount is greater than 0 for MULTIPLE type', () => { + it("should include \" ,...\" when floatingDateCount is greater than 0 for MULTIPLE type", () => { element.type = CALENDAR_TYPES.MULTIPLE; element._selectedDates = [new Date("2024-01-01"), new Date("2024-01-02"), new Date("2024-01-03")]; @@ -262,7 +263,7 @@ describe("BlDatepicker", () => { expect(element._value).to.include(" ,..."); }); - it('should not include " ,..." when floatingDateCount is 0 for MULTIPLE type', () => { + it("should not include \" ,...\" when floatingDateCount is 0 for MULTIPLE type", () => { element.type = CALENDAR_TYPES.MULTIPLE; element._selectedDates = [new Date("2024-01-01")]; @@ -328,7 +329,7 @@ describe("BlDatepicker", () => { it("should warn when 'defaultValue' is not an array for multiple/range selection", async () => { element = await fixture(html` - `); + `); element._defaultValue = new Date(); element.firstUpdated(); @@ -384,4 +385,27 @@ describe("BlDatepicker", () => { expect(consoleWarnSpy.called).to.be.false; }); + + it("should warn if minDate is greater than or equal to maxDate", async () => { + + element.maxDate = new Date(2023, 0, 1); + await element.updateComplete; + + element.minDate = new Date(2023, 0, 2); + await element.updateComplete; + + expect(consoleWarnSpy.calledWith("minDate cannot be greater than maxDate.")).to.be.true; + }); + + it("should warn if maxDate is less than or equal to minDate", async () => { + + element.minDate = new Date(2023, 0, 2); + await element.updateComplete; + + element.maxDate = new Date(2023, 0, 1); + await element.updateComplete; + + expect(consoleWarnSpy.calledWith("maxDate cannot be smaller than minDate.")).to.be.true; + }); + }); diff --git a/src/mixins/datepicker-calendar-mixin/datepicker-calendar-mixin.ts b/src/mixins/datepicker-calendar-mixin/datepicker-calendar-mixin.ts index 810be815..1aa43396 100644 --- a/src/mixins/datepicker-calendar-mixin/datepicker-calendar-mixin.ts +++ b/src/mixins/datepicker-calendar-mixin/datepicker-calendar-mixin.ts @@ -36,7 +36,7 @@ export default class DatepickerCalendarMixin extends LitElement { @property({ type: Date, attribute: "max-date", reflect: true }) set maxDate(maxDate: Date) { if (this._minDate && this._minDate >= maxDate) { - console.warn("minDate cannot be greater than maxDate."); + console.warn("maxDate cannot be smaller than minDate."); } else { this._maxDate = maxDate; } @@ -53,7 +53,7 @@ export default class DatepickerCalendarMixin extends LitElement { @property({ type: Date, attribute: "min-date", reflect: true }) set minDate(minDate: Date) { - if (this._maxDate && this._maxDate >= minDate) { + if (this._maxDate && this._maxDate <= minDate) { console.warn("minDate cannot be greater than maxDate."); } else { this._minDate = minDate; From f3df7cd1cda39cca66b22384d175aff57e9cbf38 Mon Sep 17 00:00:00 2001 From: "dilan.dogan2" Date: Thu, 10 Oct 2024 16:24:41 +0300 Subject: [PATCH 25/35] feat(datepicker): fix css custom property --- src/components/datepicker/bl-datepicker.css | 3 ++- src/components/datepicker/bl-datepicker.stories.mdx | 11 ++++++++--- src/components/datepicker/bl-datepicker.ts | 13 ++++--------- 3 files changed, 14 insertions(+), 13 deletions(-) diff --git a/src/components/datepicker/bl-datepicker.css b/src/components/datepicker/bl-datepicker.css index 952a6dff..26d79305 100644 --- a/src/components/datepicker/bl-datepicker.css +++ b/src/components/datepicker/bl-datepicker.css @@ -3,6 +3,7 @@ --bl-input-cursor: pointer; --icon-size: var(--line-height); --icon-color: var(--bl-color-neutral-light); + --datepicker-width: 314px; display: block; } @@ -14,7 +15,7 @@ } .datepicker-input { - width: 314px; + width: var(--bl-datepicker-input-width, var(--datepicker-width)); white-space: nowrap; text-overflow: ellipsis; } diff --git a/src/components/datepicker/bl-datepicker.stories.mdx b/src/components/datepicker/bl-datepicker.stories.mdx index 4b76e49a..017599a0 100644 --- a/src/components/datepicker/bl-datepicker.stories.mdx +++ b/src/components/datepicker/bl-datepicker.stories.mdx @@ -26,7 +26,7 @@ export const DatepickerTemplate = (args) => html` start-of-week=${ifDefined(args.startOfWeek)} locale=${ifDefined(args.locale)} default-value=${ifDefined(args.defaultValue)} - input-width=${ifDefined(args.inputWidth)} + style=${ifDefined(args.style)} disabled-dates=${ifDefined(args.disabledDates)}>${unsafeHTML(args.content)}
` @@ -37,7 +37,7 @@ export const Template = (args) => html`${DatepickerTemplate({ ...args })}`; # Datepicker [ADR](https://github.com/Trendyol/baklava/issues/894) -[Figma](https://www.figma.com/file/RrcLH0mWpIUy4vwuTlDeKN/Baklava-Design-Guide?type=design&node-id=1412-8914&mode=design&t=b0kU7tBfJQFvz2at-0) Datepicker renders the calendar component within itself and provides the functionality provided by the calendar component. @@ -125,7 +125,12 @@ You can set input width of datepicker as you wish. + args={{ + type: 'single', + label: 'Change input width', + placeholder: 'Set input width', + style: '--bl-datepicker-input-width: 200px;' + }}> {Template.bind({})} diff --git a/src/components/datepicker/bl-datepicker.ts b/src/components/datepicker/bl-datepicker.ts index f5476a19..5fc8dac1 100644 --- a/src/components/datepicker/bl-datepicker.ts +++ b/src/components/datepicker/bl-datepicker.ts @@ -18,6 +18,8 @@ export const blDatepickerChangedEvent = "bl-datepicker-change"; /** * @tag bl-datepicker * @summary Baklava DatePicker component + * + * @cssproperty [--bl-datepicker-input-width] - Sets the width of datepicker input **/ @customElement(blDatepickerTag) export default class BlDatepicker extends DatepickerCalendarMixin { @@ -51,11 +53,6 @@ export default class BlDatepicker extends DatepickerCalendarMixin { */ @property({ type: String, attribute: "help-text", reflect: true }) helpText: string; - /** - * Defines input width of datepicker - */ - @property({ type: Number, attribute: "input-width", reflect: true }) - inputWidth: number; @state() _value = ""; @@ -178,8 +175,6 @@ export default class BlDatepicker extends DatepickerCalendarMixin { } async firstUpdated() { - this._inputEl?.style.setProperty("width", `${this.inputWidth}px`, "important"); - this._inputEl?.addEventListener("mousedown", event => { event.preventDefault(); }); @@ -203,7 +198,7 @@ export default class BlDatepicker extends DatepickerCalendarMixin { render() { const renderCalendar = html` - + 0 - ? html` + ? html` +${this._floatingDateCount}
${formattedAdditionalDates}
` From ea3a441b335bed1f12ba6f3685cecbb66403dc78 Mon Sep 17 00:00:00 2001 From: "dilan.dogan2" Date: Fri, 11 Oct 2024 16:10:47 +0300 Subject: [PATCH 26/35] feat(datepicker): change story name --- src/components/datepicker/bl-datepicker.stories.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/datepicker/bl-datepicker.stories.mdx b/src/components/datepicker/bl-datepicker.stories.mdx index 017599a0..2637bae8 100644 --- a/src/components/datepicker/bl-datepicker.stories.mdx +++ b/src/components/datepicker/bl-datepicker.stories.mdx @@ -119,7 +119,7 @@ You can set dates which you want to disable from Datepicker. -### İnput Width +### Input Width You can set input width of datepicker as you wish. From 315915fdf49d59caf5f51852986e20933f477096 Mon Sep 17 00:00:00 2001 From: "dilan.dogan2" Date: Thu, 24 Oct 2024 12:16:42 +0300 Subject: [PATCH 27/35] feat(datepicker): add secondary option to locale --- .../datepicker-calendar-mixin/datepicker-calendar-mixin.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mixins/datepicker-calendar-mixin/datepicker-calendar-mixin.ts b/src/mixins/datepicker-calendar-mixin/datepicker-calendar-mixin.ts index 1aa43396..3cb92f0c 100644 --- a/src/mixins/datepicker-calendar-mixin/datepicker-calendar-mixin.ts +++ b/src/mixins/datepicker-calendar-mixin/datepicker-calendar-mixin.ts @@ -23,7 +23,7 @@ export default class DatepickerCalendarMixin extends LitElement { * Defines the calendar language */ @property() - locale: string = document.documentElement.lang; + locale: string = document.documentElement.lang || "en-EN"; /** * Defines the maximum date value for the calendar From da22f12c1223d4c3804e3f98f564a740ef8f720a Mon Sep 17 00:00:00 2001 From: "dilan.dogan2" Date: Thu, 7 Nov 2024 09:57:27 +0300 Subject: [PATCH 28/35] feat(datepicker): self-review updates --- src/components/datepicker/bl-datepicker.css | 2 ++ src/components/datepicker/bl-datepicker.stories.mdx | 1 - src/components/datepicker/bl-datepicker.ts | 6 ------ 3 files changed, 2 insertions(+), 7 deletions(-) diff --git a/src/components/datepicker/bl-datepicker.css b/src/components/datepicker/bl-datepicker.css index 26d79305..e6f7a8b5 100644 --- a/src/components/datepicker/bl-datepicker.css +++ b/src/components/datepicker/bl-datepicker.css @@ -1,9 +1,11 @@ :host { width: fit-content; + --bl-input-cursor: pointer; --icon-size: var(--line-height); --icon-color: var(--bl-color-neutral-light); --datepicker-width: 314px; + display: block; } diff --git a/src/components/datepicker/bl-datepicker.stories.mdx b/src/components/datepicker/bl-datepicker.stories.mdx index 2637bae8..28a14c29 100644 --- a/src/components/datepicker/bl-datepicker.stories.mdx +++ b/src/components/datepicker/bl-datepicker.stories.mdx @@ -72,7 +72,6 @@ You can select multiple days from Datepicker. type: 'multiple', label: 'Multiple Datepicker', placeholder: 'Select multiple dates', - disabledDates: `["${new Date(new Date().getFullYear(), new Date().getMonth(), new Date().getDate() + 2)}"]` }}> {Template.bind({})} diff --git a/src/components/datepicker/bl-datepicker.ts b/src/components/datepicker/bl-datepicker.ts index 5fc8dac1..ebf78fdf 100644 --- a/src/components/datepicker/bl-datepicker.ts +++ b/src/components/datepicker/bl-datepicker.ts @@ -43,11 +43,6 @@ export default class BlDatepicker extends DatepickerCalendarMixin { */ @property({ type: Boolean, reflect: true }) disabled: boolean; - /** - * Defines invalid text to datepicker input - */ - @property({ type: String, attribute: "invalid-text", reflect: true }) - invalidText: string; /** * Defines help text to datepicker input for users */ @@ -253,7 +248,6 @@ export default class BlDatepicker extends DatepickerCalendarMixin { aria-labelledby="label" @click=${this._togglePopover} help-text=${this.helpText} - .customInvalidText=${this.invalidText} ?disabled=${this.disabled} readonly > From e5dd2cdd069f22bed0427b8e2376b4f58bed9b99 Mon Sep 17 00:00:00 2001 From: "dilan.dogan2" Date: Tue, 19 Nov 2024 00:34:56 +0300 Subject: [PATCH 29/35] feat(datepicker): run prettier --- src/components/calendar/bl-calendar.test.ts | 2 +- src/components/calendar/bl-calendar.ts | 174 ++++++++---------- src/components/datepicker/bl-datepicker.css | 2 +- .../datepicker/bl-datepicker.test.ts | 2 +- src/components/datepicker/bl-datepicker.ts | 49 ++--- src/components/popover/bl-popover.ts | 168 ++++++++--------- 6 files changed, 191 insertions(+), 206 deletions(-) diff --git a/src/components/calendar/bl-calendar.test.ts b/src/components/calendar/bl-calendar.test.ts index 2a0c6db4..823a519e 100644 --- a/src/components/calendar/bl-calendar.test.ts +++ b/src/components/calendar/bl-calendar.test.ts @@ -434,7 +434,7 @@ describe("bl-calendar", () => { expect(handleRangeSelectCalendarStub).to.have.been.calledWith(calendarDate); }); - it("should apply \"range-day\" class to elements between startDate and endDate", async () => { + it("should apply range-day class to elements between startDate and endDate", async () => { const startDate = new Date(2024, 9, 5); const endDate = new Date(2024, 9, 10); diff --git a/src/components/calendar/bl-calendar.ts b/src/components/calendar/bl-calendar.ts index dbf634a0..8df1583c 100644 --- a/src/components/calendar/bl-calendar.ts +++ b/src/components/calendar/bl-calendar.ts @@ -4,7 +4,6 @@ import { classMap } from "lit/directives/class-map.js"; import DatepickerCalendarMixin from "../../mixins/datepicker-calendar-mixin/datepicker-calendar-mixin"; import { event, EventDispatcher } from "../../utilities/event"; import "../button/bl-button"; -import { blDatepickerClearSelectedDatesEvent } from "../datepicker/bl-datepicker"; import "../icon/bl-icon"; import { CALENDAR_TYPES, @@ -29,51 +28,30 @@ export const blCalendarChangedEvent = "bl-calendar-change"; **/ @customElement("bl-calendar") export default class BlCalendar extends DatepickerCalendarMixin { - connectedCallback() { - super.connectedCallback(); - window.addEventListener(blDatepickerClearSelectedDatesEvent, this.handleClearSelectedDates); - } - - disconnectedCallback() { - super.disconnectedCallback(); - window.removeEventListener(blDatepickerClearSelectedDatesEvent, this.handleClearSelectedDates); - } - @state() _selectedDates: CalendarDate[] = []; - @state() _selectedRangeDates: RangePickerDates = { startDate: undefined, endDate: undefined }; - @state() today = new Date(); - @state() _calendarMonth: number = this.today.getMonth(); - @state() _calendarYear: number = this.today.getFullYear(); - @state() _calendarView: CalendarView = CALENDAR_VIEWS.DAYS; - @state() _calendarYears: number[] = []; - @state() _calendarDays: CalendarDay[] = []; - /** * Fires when date selection changes */ @event(blCalendarChangedEvent) _onBlCalendarChange: EventDispatcher; - public handleClearSelectedDates = () => { - this._selectedDates = []; - this._selectedRangeDates = { startDate: undefined, endDate: undefined }; - this._onBlCalendarChange([]); - this.clearRangePickerStyles(); - }; + static get styles(): CSSResultGroup { + return [style]; + } get months() { return [...Array(12).keys()].map(month => ({ @@ -89,9 +67,12 @@ export default class BlCalendar extends DatepickerCalendarMixin { })); } - static get styles(): CSSResultGroup { - return [style]; - } + public handleClearSelectedDates = () => { + this._selectedDates = []; + this._selectedRangeDates = { startDate: undefined, endDate: undefined }; + this._onBlCalendarChange([]); + this.clearRangePickerStyles(); + }; getDayNumInAMonth(year: number, month: number) { return new Date(year, month + 1, 0).getDate(); @@ -114,7 +95,7 @@ export default class BlCalendar extends DatepickerCalendarMixin { this._calendarYears = Array.from({ length: 12 }, (_, i) => fromYear - (i + 1)); } - this.type === CALENDAR_TYPES.RANGE && this.setHoverClass(); + if (this.type === CALENDAR_TYPES.RANGE) this.setHoverClass(); } setNextCalendarView() { @@ -141,13 +122,13 @@ export default class BlCalendar extends DatepickerCalendarMixin { setMonthAndCalendarView(month: number) { this._calendarMonth = month; this._calendarView = CALENDAR_VIEWS.DAYS; - this.type === CALENDAR_TYPES.RANGE && this.setHoverClass(); + if (this.type === CALENDAR_TYPES.RANGE) this.setHoverClass(); } setYearAndCalendarView(year: number) { this._calendarYear = year; this._calendarView = CALENDAR_VIEWS.DAYS; - this.type === CALENDAR_TYPES.RANGE && this.setHoverClass(); + if (this.type === CALENDAR_TYPES.RANGE) this.setHoverClass(); } generateSurroundingYears() { @@ -164,9 +145,8 @@ export default class BlCalendar extends DatepickerCalendarMixin { handleDate(date: CalendarDate) { if (this.type !== CALENDAR_TYPES.RANGE) { - date.getMonth() < this._calendarMonth - ? this.setPreviousCalendarView() - : date.getMonth() > this._calendarMonth && this.setNextCalendarView(); + if (date.getMonth() < this._calendarMonth) this.setPreviousCalendarView(); + else if (date.getMonth() > this._calendarMonth) this.setNextCalendarView(); } switch (this.type) { @@ -190,7 +170,7 @@ export default class BlCalendar extends DatepickerCalendarMixin { } handleMultipleSelectCalendar(calendarDate: CalendarDate) { - const dateExist = this._selectedDates.find(d => d.getTime() === calendarDate.getTime()); + const dateExist = this._selectedDates.some(d => d.getTime() === calendarDate.getTime()); dateExist ? this._selectedDates.splice( @@ -222,7 +202,7 @@ export default class BlCalendar extends DatepickerCalendarMixin { } checkIfSelectedDate(calendarDate: CalendarDate) { - return !!this._selectedDates.find(date => date?.getTime() === calendarDate.getTime()); + return this._selectedDates.some(date => date?.getTime() === calendarDate.getTime()); } checkIfDateIsToday(calendarDate: CalendarDate) { @@ -244,11 +224,9 @@ export default class BlCalendar extends DatepickerCalendarMixin { } if (this.disabledDates) { - const day = this.disabledDates.find(disabledDate => { + return this.disabledDates.some(disabledDate => { return calendarDate.getTime() === new Date(disabledDate).getTime(); }); - - return !!day; } return false; } @@ -395,15 +373,15 @@ export default class BlCalendar extends DatepickerCalendarMixin { kind="neutral" class="header-text ${showMonthSelected}" @click="${() => this.setCurrentCalendarView(CALENDAR_VIEWS.MONTHS)}" - >${this.months[this._calendarMonth].name} + >${this.months[this._calendarMonth].name} + ${this._calendarYear} + >${this._calendarYear} + - ${[...calendarDays.keys()].map(key => { - return html`
${key}
`; - })}
-
- ${[...Array(valuesArray[0].length).keys()].map(key => { - return html`
- ${valuesArray.map(values => { - const date = values[key]; - const isSelectedDay = this.checkIfSelectedDate(date); - const isDayToday = this.checkIfDateIsToday(date); - const isDisabledDay = this.checkIfDateIsDisabled(date); - - const classes = classMap({ - "day": true, - "calendar-text": true, - "today-day": isDayToday, - "selected-day": isSelectedDay, - "other-month-day": values[key].getMonth() !== this._calendarMonth, - "disabled-day": isDisabledDay, - }); - - return html` -
- - ${date.getDate()} - -
- `; - })} -
`; - })} -
-
`; + return html` +
+ ${[...calendarDays.keys()].map(key => { + return html`
${key}
`; + })} +
+
+ ${[...Array(valuesArray[0].length).keys()].map(key => { + return html`
+ ${valuesArray.map(values => { + const date = values[key]; + const isSelectedDay = this.checkIfSelectedDate(date); + const isDayToday = this.checkIfDateIsToday(date); + const isDisabledDay = this.checkIfDateIsDisabled(date); + + const classes = classMap({ + "day": true, + "calendar-text": true, + "today-day": isDayToday, + "selected-day": isSelectedDay, + "other-month-day": values[key].getMonth() !== this._calendarMonth, + "disabled-day": isDisabledDay, + }); + + return html` +
+ + ${date.getDate()} + +
+ `; + })} +
`; + })} +
+
`; } + renderCalendarMonths() { return html`
${this.months.map((month, index) => { @@ -479,14 +460,15 @@ export default class BlCalendar extends DatepickerCalendarMixin { })}
`; } + renderCalendarYears() { this.generateSurroundingYears(); - return html`
+ return html`
${this._calendarYears.map(year => { const variant = year === this._calendarYear ? "primary" : "tertiary"; const neutral = year === this._calendarYear ? "default" : "neutral"; - return html` -
-
- ${this.renderCalendarHeader()} - ${this._calendarView === CALENDAR_VIEWS.DAYS ? this.renderCalendarDays() : ""} - ${this._calendarView === CALENDAR_VIEWS.MONTHS ? this.renderCalendarMonths() : ""} - ${this._calendarView === CALENDAR_VIEWS.YEARS ? this.renderCalendarYears() : ""} + return html` +
+
+
+ ${this.renderCalendarHeader()} + ${this._calendarView === CALENDAR_VIEWS.DAYS ? this.renderCalendarDays() : ""} + ${this._calendarView === CALENDAR_VIEWS.MONTHS ? this.renderCalendarMonths() : ""} + ${this._calendarView === CALENDAR_VIEWS.YEARS ? this.renderCalendarYears() : ""} +
-
`; + `; } } diff --git a/src/components/datepicker/bl-datepicker.css b/src/components/datepicker/bl-datepicker.css index e6f7a8b5..d01c8dc0 100644 --- a/src/components/datepicker/bl-datepicker.css +++ b/src/components/datepicker/bl-datepicker.css @@ -28,7 +28,7 @@ align-items: center; } -.calendarIcon { +.calendar-icon { display: flex; align-items: center; gap: var(--icon-gap); diff --git a/src/components/datepicker/bl-datepicker.test.ts b/src/components/datepicker/bl-datepicker.test.ts index b2e3bab7..c0d356f8 100644 --- a/src/components/datepicker/bl-datepicker.test.ts +++ b/src/components/datepicker/bl-datepicker.test.ts @@ -299,7 +299,7 @@ describe("BlDatepicker", () => { }); it("should call closePopover when popoverEl is visible", () => { - element._popoverEl._visible = true; + element._popoverEl.show(); const closePopoverSpy = sinon.spy(element, "closePopover"); element._togglePopover(); diff --git a/src/components/datepicker/bl-datepicker.ts b/src/components/datepicker/bl-datepicker.ts index ebf78fdf..15d5b54a 100644 --- a/src/components/datepicker/bl-datepicker.ts +++ b/src/components/datepicker/bl-datepicker.ts @@ -36,12 +36,12 @@ export default class BlDatepicker extends DatepickerCalendarMixin { /** * Defines the custom formatter function */ - @property({ type: Function, attribute: "value-formatter", reflect: true }) + @property({ type: Function, attribute: "value-formatter" }) valueFormatter: ((dates: CalendarDate[]) => string) | null = null; /** * Sets datepicker to disabled */ - @property({ type: Boolean, reflect: true }) + @property({ type: Boolean }) disabled: boolean; /** * Defines help text to datepicker input for users @@ -69,10 +69,10 @@ export default class BlDatepicker extends DatepickerCalendarMixin { @query("bl-input") _inputEl!: BlInput; - /** - * Fires when date selection is cleared - */ - @event(blDatepickerClearSelectedDatesEvent) private _onBlDatepickerCleared: EventDispatcher<[]>; + + private _onCalendarMouseDown!: (event: MouseEvent) => void; + private _onInputMouseDown!: (event: MouseEvent) => void; + /** * Fires when date selection is changed */ @@ -94,10 +94,10 @@ export default class BlDatepicker extends DatepickerCalendarMixin { this._value = values.join(",") + (this._floatingDateCount > 0 ? " ,..." : ""); } else if (this.type === CALENDAR_TYPES.RANGE) { - this._selectedDates[0] && (this._value = this.formatDate(this._selectedDates[0])); - this._selectedDates[1] && - (this._value = `${this._value}-${this.formatDate(this._selectedDates[1])}`); - if (this._selectedDates.length === 2) this.closePopoverWithTimeout(); + if (this._selectedDates[0]) this._value = this.formatDate(this._selectedDates[0]); + if (this._selectedDates[1]) + this._value = `${this._value}-${this.formatDate(this._selectedDates[1])}`; + if (this._selectedDates[0] && this._selectedDates[1]) this.closePopoverWithTimeout(); } } @@ -138,10 +138,11 @@ export default class BlDatepicker extends DatepickerCalendarMixin { } clearDatepicker() { - this._onBlDatepickerCleared([]); + this._calendarEl.handleClearSelectedDates(); this._selectedDates = []; this._value = ""; this._floatingDateCount = 0; + this._inputEl?.blur(); } openPopover() { @@ -170,19 +171,19 @@ export default class BlDatepicker extends DatepickerCalendarMixin { } async firstUpdated() { - this._inputEl?.addEventListener("mousedown", event => { + this._onCalendarMouseDown = event => { event.preventDefault(); - }); + this._inputEl?.focus(); + }; - document.addEventListener("mousedown", event => { - const path = event.composedPath(); + this._onInputMouseDown = event => { + event.preventDefault(); + this._inputEl?.focus(); + }; - if (path.includes(this._calendarEl) || (this._inputEl && path.includes(this._inputEl))) { - event.preventDefault(); + this._calendarEl?.addEventListener("mousedown", this._onCalendarMouseDown); + this._inputEl?.addEventListener("mousedown", this._onInputMouseDown); - this._inputEl?.focus(); - } - }); if (this._defaultValue) { Array.isArray(this._defaultValue) ? (this._selectedDates = this._defaultValue) @@ -191,6 +192,12 @@ export default class BlDatepicker extends DatepickerCalendarMixin { } } + disconnectedCallback() { + super.disconnectedCallback(); + this._calendarEl?.removeEventListener("mousedown", this._onCalendarMouseDown); + this._inputEl?.removeEventListener("mousedown", this._onInputMouseDown); + } + render() { const renderCalendar = html` @@ -253,7 +260,7 @@ export default class BlDatepicker extends DatepickerCalendarMixin { >
${additionalDatesView} ${clearDatepickerButton} - +
${renderCalendar} diff --git a/src/components/popover/bl-popover.ts b/src/components/popover/bl-popover.ts index b2e83a6e..04195b47 100644 --- a/src/components/popover/bl-popover.ts +++ b/src/components/popover/bl-popover.ts @@ -40,56 +40,81 @@ export type Placement = * @cssproperty [--bl-popover-border-color=--bl-color-primary-highlight] - Sets the border color of popover. * @cssproperty [--bl-popover-border-size=1px] - Sets the border size of popover. You can set it to `0px` to not have a border (if you use a custom background color). Always use with a length unit. * @cssproperty [--bl-popover-padding=--bl-size-m] - Sets the padding of popover. - * @cssproperty [--bl-popover-border-radius=--bl-size-3xs] - Sets the border radius of popover. + * @cssproperty [--bl-popbover-border-radius=--bl-size-3xs] - Sets the border radius of popover. * @cssproperty [--bl-popover-max-width=100vw] - Sets the maximum width of the popover (including border and padding). * @cssproperty [--bl-popover-position=fixed] - Sets the position of popover. You can set it to `absolute` if parent element is a fixed positioned element like drawer or dialog. */ @customElement("bl-popover") export default class BlPopover extends LitElement { - static get styles(): CSSResultGroup { - return [style]; - } - - @query(".popover") private _popover: HTMLElement; - @query(".arrow") private arrow: HTMLElement; - /** * Sets placement of the popover */ @property({ type: String }) placement: Placement = "bottom"; - - /** - * Target elements state - */ - @state() _target: string | Element; - /** * Sets size of popover same as trigger element */ @property({ type: Boolean, attribute: "fit-size" }) fitSize = false; - /** * Sets the distance between popover and target/trigger element */ @property({ type: Number }) offset = 8; + @query(".popover") private _popover: HTMLElement; + @query(".arrow") private arrow: HTMLElement; + /** + * Fires when the popover is shown + */ + @event("bl-popover-show") private onBlPopoverShow: EventDispatcher; + /** + * Fires when popover becomes hidden + */ + @event("bl-popover-hide") private onBlPopoverHide: EventDispatcher; + private popoverAutoUpdateCleanup: () => void; + + static get styles(): CSSResultGroup { + return [style]; + } /** - * Visibility state + * Target elements state */ - @state() _visible = false; + @state() _target: string | Element; /** - * Fires when the popover is shown + * Sets the target element of the popover to align and trigger. + * It can be a string id of the target element or can be a direct Element reference of it. */ - @event("bl-popover-show") private onBlPopoverShow: EventDispatcher; + @property() + get target(): string | Element { + return this._target; + } + + set target(value: string | Element) { + const target = getTarget(value); + + if (!target) { + console.warn( + "BlPopover target only accepts an Element instance or a string id of a DOM element." + ); + return; + } + + this._target = target; + } /** - * Fires when popover becomes hidden + * Visibility state */ - @event("bl-popover-hide") private onBlPopoverHide: EventDispatcher; + @state() private _visible = false; + + /** + * Gives the visibility status of the popover + */ + get visible(): boolean { + return this._visible; + } connectedCallback() { super.connectedCallback(); @@ -105,6 +130,41 @@ export default class BlPopover extends LitElement { this.popoverAutoUpdateCleanup && this.popoverAutoUpdateCleanup(); } + /** + * Shows popover + */ + show() { + this._visible = true; + this.setPopover(); + this.onBlPopoverShow(""); + document.addEventListener("click", this._handleClickOutside); + document.addEventListener("keydown", this._handleKeydownEvent); + document.addEventListener("bl-popover-show", this._handlePopoverShowEvent); + } + + /** + * Hides popover + */ + hide() { + this._visible = false; + document.removeEventListener("click", this._handleClickOutside); + document.removeEventListener("keydown", this._handleKeydownEvent); + document.removeEventListener("bl-popover-show", this._handlePopoverShowEvent); + this.onBlPopoverHide(""); + } + + render(): TemplateResult { + const classes = classMap({ + popover: true, + visible: this._visible, + }); + + return html`
+ + +
`; + } + private getMiddleware(): Middleware[] { const middlewareParams: Middleware[] = []; @@ -137,8 +197,6 @@ export default class BlPopover extends LitElement { } }; - private popoverAutoUpdateCleanup: () => void; - private setPopover() { if (this.target) { this.popoverAutoUpdateCleanup = autoUpdate(this.target as Element, this._popover, () => { @@ -167,58 +225,6 @@ export default class BlPopover extends LitElement { } } - /** - * Sets the target element of the popover to align and trigger. - * It can be a string id of the target element or can be a direct Element reference of it. - */ - @property() - get target(): string | Element { - return this._target; - } - - set target(value: string | Element) { - const target = getTarget(value); - - if (!target) { - console.warn( - "BlPopover target only accepts an Element instance or a string id of a DOM element." - ); - return; - } - - this._target = target; - } - - /** - * Shows popover - */ - show() { - this._visible = true; - this.setPopover(); - this.onBlPopoverShow(""); - document.addEventListener("click", this._handleClickOutside); - document.addEventListener("keydown", this._handleKeydownEvent); - document.addEventListener("bl-popover-show", this._handlePopoverShowEvent); - } - - /** - * Hides popover - */ - hide() { - this._visible = false; - document.removeEventListener("click", this._handleClickOutside); - document.removeEventListener("keydown", this._handleKeydownEvent); - document.removeEventListener("bl-popover-show", this._handlePopoverShowEvent); - this.onBlPopoverHide(""); - } - - /** - * Gives the visibility status of the popover - */ - get visible(): boolean { - return this._visible; - } - private _handlePopoverShowEvent(event: Event) { if (event.target !== this) { const { parentElement } = event.target as HTMLElement; @@ -236,18 +242,6 @@ export default class BlPopover extends LitElement { this.hide(); } } - - render(): TemplateResult { - const classes = classMap({ - popover: true, - visible: this._visible, - }); - - return html`
- - -
`; - } } declare global { From 3ab285df5a9c339d42c162c01fa05266b21427c4 Mon Sep 17 00:00:00 2001 From: "dilan.dogan2" Date: Thu, 21 Nov 2024 13:41:12 +0300 Subject: [PATCH 30/35] feat(datepicker): get disabled date as string or date array --- .../calendar/bl-calendar.stories.mdx | 25 ++++--- src/components/calendar/bl-calendar.test.ts | 68 +++++++------------ src/components/calendar/bl-calendar.ts | 27 +++----- src/components/calendar/bl-calendar.types.ts | 7 +- .../datepicker/bl-datepicker.test.ts | 26 ------- src/components/datepicker/bl-datepicker.ts | 6 +- .../datepicker-calendar-mixin.ts | 33 +++++++-- 7 files changed, 81 insertions(+), 111 deletions(-) diff --git a/src/components/calendar/bl-calendar.stories.mdx b/src/components/calendar/bl-calendar.stories.mdx index 27bd72f4..fe5a8caa 100644 --- a/src/components/calendar/bl-calendar.stories.mdx +++ b/src/components/calendar/bl-calendar.stories.mdx @@ -1,7 +1,7 @@ -import { html } from 'lit'; -import { Meta, Canvas, ArgsTable, Story } from '@storybook/addon-docs'; -import { ifDefined } from 'lit/directives/if-defined.js'; -import { unsafeHTML } from 'lit/directives/unsafe-html.js'; +import { html } from "lit"; +import { ArgsTable, Canvas, Meta, Story } from "@storybook/addon-docs"; +import { ifDefined } from "lit/directives/if-defined.js"; +import { unsafeHTML } from "lit/directives/unsafe-html.js"; -export const CalendarTemplate = (args) => html` html` + ${unsafeHTML(args.content)}` + disabled-dates=${ifDefined(args.disabledDates)}>${unsafeHTML(args.content)} + ` -export const Template = (args) => html`${CalendarTemplate({...args})}`; +export const Template = (args) => html`${CalendarTemplate({ ...args })}`; # Calendar [ADR](https://github.com/Trendyol/baklava/issues/795) -[Figma](https://www.figma.com/file/RrcLH0mWpIUy4vwuTlDeKN/Baklava-Design-Guide?type=design&node-id=1412-8914&mode=design&t=b0kU7tBfJQFvz2at-0) +[Figma](https://www.figma.com/file/RrcLH0mWpIUy4vwuTlDeKN/Baklava-Design-Guide?type=design&node-id=1412-8914&mode=design&t=b0kU7tBfJQFvz2at-0) Calendar component is an **internal** component for using inside Datepicker component. @@ -82,8 +85,10 @@ You can select date range from calendar. You can set dates which you want to disable from calendar. - + {Template.bind({})} diff --git a/src/components/calendar/bl-calendar.test.ts b/src/components/calendar/bl-calendar.test.ts index 823a519e..d0e01cd9 100644 --- a/src/components/calendar/bl-calendar.test.ts +++ b/src/components/calendar/bl-calendar.test.ts @@ -2,7 +2,6 @@ import { expect, fixture, html } from "@open-wc/testing"; import "./bl-calendar"; import { BlButton, BlCalendar } from "../../baklava"; import { blCalendarChangedEvent } from "./bl-calendar"; -import { blDatepickerClearSelectedDatesEvent } from "../datepicker/bl-datepicker"; import { CALENDAR_TYPES, CALENDAR_VIEWS, FIRST_MONTH_INDEX, LAST_MONTH_INDEX } from "./bl-calendar.constant"; import sinon from "sinon"; @@ -96,12 +95,6 @@ describe("bl-calendar", () => { }); - it("should clear selected dates on receiving blDatepickerClearSelectedDatesEvent", async () => { - element._selectedDates = [new Date()]; - window.dispatchEvent(new CustomEvent(blDatepickerClearSelectedDatesEvent)); - expect(element._selectedDates.length).to.equal(0); - }); - it("should not allow selection of dates before minDate", async () => { element.minDate = new Date(2023, 0, 15); element._calendarMonth = 0; @@ -154,7 +147,7 @@ describe("bl-calendar", () => { `); const monthName = new Date().toLocaleString("fr", { month: "long" }); - const firstMonth = element.shadowRoot?.querySelector(".header-text")?.textContent; + const firstMonth = element.shadowRoot?.querySelector(".header-text")?.textContent?.trim(); expect(firstMonth).to.equal(monthName); }); @@ -168,18 +161,6 @@ describe("bl-calendar", () => { expect(todayElement?.classList.contains("today-day")).to.be.true; }); - it("should clear selected dates when blDatepickerClearSelectedDatesEvent is triggered", async () => { - const testDate = new Date(2023, 1, 10); - - element._selectedDates = [testDate]; - - window.dispatchEvent(new CustomEvent(blDatepickerClearSelectedDatesEvent)); - - expect(element._selectedDates).to.be.empty; - expect(element._selectedRangeDates.startDate).to.be.undefined; - expect(element._selectedRangeDates.endDate).to.be.undefined; - }); - it("should switch to the year view and render years", async () => { const yearButton = (element.shadowRoot?.querySelectorAll(".header-text")[1]) as BlButton; @@ -434,40 +415,37 @@ describe("bl-calendar", () => { expect(handleRangeSelectCalendarStub).to.have.been.calledWith(calendarDate); }); - it("should apply range-day class to elements between startDate and endDate", async () => { - - const startDate = new Date(2024, 9, 5); - const endDate = new Date(2024, 9, 10); - element._selectedRangeDates = { startDate, endDate }; - - const rangeDates = [ - new Date(2024, 9, 6), - new Date(2024, 9, 7), - new Date(2024, 9, 8), - new Date(2024, 9, 9) - ]; + it("should add range-start-day class to the start date element", async () => { + element._selectedRangeDates = { + startDate: new Date(2024, 10, 1), + endDate: new Date(2024, 10, 5) + }; - rangeDates.forEach(date => { + element.setHoverClass(); - const fakeElement = document.createElement("div"); + await new Promise((resolve) => setTimeout(resolve, 100)); + const startDateElement = element.shadowRoot?.getElementById( + `${element._selectedRangeDates.startDate?.getTime()}` + )?.parentElement; - fakeElement.id = `${date.getTime()}`; - const parentElement = document.createElement("div"); + expect(startDateElement?.classList.contains("range-start-day")).to.be.true; + }); - parentElement.appendChild(fakeElement); - element.shadowRoot?.appendChild(fakeElement); - }); + it("should add range-end-day class to the end date element", async () => { + element._selectedRangeDates = { + startDate: new Date(2024, 10, 1), + endDate: new Date(2024, 10, 5) + }; element.setHoverClass(); + await new Promise((resolve) => setTimeout(resolve, 100)); - await new Promise(resolve => setTimeout(resolve)); - - rangeDates.forEach(date => { - const elementWithId = element.shadowRoot?.getElementById(`${date.getTime()}`)?.parentElement; + const endDateElement = element.shadowRoot?.getElementById( + `${element._selectedRangeDates.endDate?.getTime()}` + )?.parentElement; - expect(elementWithId?.classList.contains("range-day")).to.be.true; - }); + expect(endDateElement?.classList.contains("range-end-day")).to.be.true; }); it("should correctly calculate lastMonthDaysCount when currentMonthStartWeekDay smaller startOfWeek", () => { diff --git a/src/components/calendar/bl-calendar.ts b/src/components/calendar/bl-calendar.ts index 8df1583c..a076af8c 100644 --- a/src/components/calendar/bl-calendar.ts +++ b/src/components/calendar/bl-calendar.ts @@ -12,13 +12,7 @@ import { LAST_MONTH_INDEX, } from "./bl-calendar.constant"; import style from "./bl-calendar.css"; -import { - Calendar, - CalendarDate, - CalendarDay, - CalendarView, - RangePickerDates, -} from "./bl-calendar.types"; +import { Calendar, CalendarDay, CalendarView, RangePickerDates } from "./bl-calendar.types"; export const blCalendarChangedEvent = "bl-calendar-change"; @@ -29,7 +23,7 @@ export const blCalendarChangedEvent = "bl-calendar-change"; @customElement("bl-calendar") export default class BlCalendar extends DatepickerCalendarMixin { @state() - _selectedDates: CalendarDate[] = []; + _selectedDates: Date[] = []; @state() _selectedRangeDates: RangePickerDates = { startDate: undefined, endDate: undefined }; @state() @@ -143,7 +137,7 @@ export default class BlCalendar extends DatepickerCalendarMixin { .forEach(day => day.classList.remove("range-day", "range-start-day", "range-end-day")); } - handleDate(date: CalendarDate) { + handleDate(date: Date) { if (this.type !== CALENDAR_TYPES.RANGE) { if (date.getMonth() < this._calendarMonth) this.setPreviousCalendarView(); else if (date.getMonth() > this._calendarMonth) this.setNextCalendarView(); @@ -165,11 +159,11 @@ export default class BlCalendar extends DatepickerCalendarMixin { this.requestUpdate(); } - handleSingleSelectCalendar(calendarDate: CalendarDate) { + handleSingleSelectCalendar(calendarDate: Date) { this._selectedDates = [calendarDate]; } - handleMultipleSelectCalendar(calendarDate: CalendarDate) { + handleMultipleSelectCalendar(calendarDate: Date) { const dateExist = this._selectedDates.some(d => d.getTime() === calendarDate.getTime()); dateExist @@ -180,7 +174,7 @@ export default class BlCalendar extends DatepickerCalendarMixin { : this._selectedDates.push(calendarDate); } - handleRangeSelectCalendar(calendarDate: CalendarDate) { + handleRangeSelectCalendar(calendarDate: Date) { const { startDate, endDate } = this._selectedRangeDates; if (!startDate) { @@ -201,11 +195,11 @@ export default class BlCalendar extends DatepickerCalendarMixin { this.setHoverClass(); } - checkIfSelectedDate(calendarDate: CalendarDate) { + checkIfSelectedDate(calendarDate: Date) { return this._selectedDates.some(date => date?.getTime() === calendarDate.getTime()); } - checkIfDateIsToday(calendarDate: CalendarDate) { + checkIfDateIsToday(calendarDate: Date) { const today = this.today; return ( @@ -215,17 +209,16 @@ export default class BlCalendar extends DatepickerCalendarMixin { ); } - checkIfDateIsDisabled(calendarDate: CalendarDate) { + checkIfDateIsDisabled(calendarDate: Date) { if ( calendarDate.getTime() < this.minDate?.getTime() || calendarDate.getTime() > this.maxDate?.getTime() ) { return true; } - if (this.disabledDates) { return this.disabledDates.some(disabledDate => { - return calendarDate.getTime() === new Date(disabledDate).getTime(); + return calendarDate.getTime() === disabledDate.getTime(); }); } return false; diff --git a/src/components/calendar/bl-calendar.types.ts b/src/components/calendar/bl-calendar.types.ts index de556270..487f4c37 100644 --- a/src/components/calendar/bl-calendar.types.ts +++ b/src/components/calendar/bl-calendar.types.ts @@ -6,11 +6,10 @@ export type CalendarView = CALENDAR_VIEWS; export type CalendarType = CALENDAR_TYPES; export type CalendarDay = { value: number; name: string }; -export type CalendarDate = Date; export type RangePickerDates = { - startDate?: CalendarDate; - endDate?: CalendarDate; + startDate?: Date; + endDate?: Date; }; -export type Calendar = Map; +export type Calendar = Map; diff --git a/src/components/datepicker/bl-datepicker.test.ts b/src/components/datepicker/bl-datepicker.test.ts index c0d356f8..04a95bac 100644 --- a/src/components/datepicker/bl-datepicker.test.ts +++ b/src/components/datepicker/bl-datepicker.test.ts @@ -178,32 +178,6 @@ describe("BlDatepicker", () => { expect(element._popoverEl.visible).to.be.false; }); - it("should prevent default mousedown event and focus input element when clicked", async () => { - const focusSpy = sinon.spy(element._inputEl, "focus"); - - const inputAddEventListenerSpy = sinon.spy(element._inputEl, "addEventListener"); - const documentAddEventListenerSpy = sinon.spy(document, "addEventListener"); - - await element.firstUpdated(); - - expect(inputAddEventListenerSpy).to.have.been.calledWith("mousedown"); - expect(documentAddEventListenerSpy).to.have.been.calledWith("mousedown"); - - const mousedownEvent = new MouseEvent("mousedown", { bubbles: true, cancelable: true }); - - element._inputEl.dispatchEvent(mousedownEvent); - - expect(mousedownEvent.defaultPrevented).to.be.true; - - const documentMouseEvent = new MouseEvent("mousedown", { bubbles: true, composed: true }); - - sinon.stub(documentMouseEvent, "composedPath").returns([element._inputEl]); - - document.dispatchEvent(documentMouseEvent); - - expect(focusSpy).to.have.been.called; - }); - it("should set selected dates and call setDatePickerInput when default value is provided", async () => { element._defaultValue = new Date(2024, 10, 10); diff --git a/src/components/datepicker/bl-datepicker.ts b/src/components/datepicker/bl-datepicker.ts index 15d5b54a..6a8059d0 100644 --- a/src/components/datepicker/bl-datepicker.ts +++ b/src/components/datepicker/bl-datepicker.ts @@ -5,14 +5,12 @@ import DatepickerCalendarMixin from "../../mixins/datepicker-calendar-mixin/date import { event, EventDispatcher } from "../../utilities/event"; import "../calendar/bl-calendar"; import { CALENDAR_TYPES } from "../calendar/bl-calendar.constant"; -import { CalendarDate } from "../calendar/bl-calendar.types"; import "../input/bl-input"; import BlInput from "../input/bl-input"; import "../tooltip/bl-tooltip"; import style from "./bl-datepicker.css"; export const blDatepickerTag = "bl-datepicker"; -export const blDatepickerClearSelectedDatesEvent = "clear-datepicker-event"; export const blDatepickerChangedEvent = "bl-datepicker-change"; /** @@ -37,7 +35,7 @@ export default class BlDatepicker extends DatepickerCalendarMixin { * Defines the custom formatter function */ @property({ type: Function, attribute: "value-formatter" }) - valueFormatter: ((dates: CalendarDate[]) => string) | null = null; + valueFormatter: ((dates: Date[]) => string) | null = null; /** * Sets datepicker to disabled */ @@ -53,7 +51,7 @@ export default class BlDatepicker extends DatepickerCalendarMixin { _value = ""; @state() - _selectedDates: CalendarDate[] = []; + _selectedDates: Date[] = []; @state() _floatingDateCount: number = 0; diff --git a/src/mixins/datepicker-calendar-mixin/datepicker-calendar-mixin.ts b/src/mixins/datepicker-calendar-mixin/datepicker-calendar-mixin.ts index 3cb92f0c..7eb46f16 100644 --- a/src/mixins/datepicker-calendar-mixin/datepicker-calendar-mixin.ts +++ b/src/mixins/datepicker-calendar-mixin/datepicker-calendar-mixin.ts @@ -14,17 +14,39 @@ export default class DatepickerCalendarMixin extends LitElement { */ @property({ type: Number, attribute: "start-of-week", reflect: true }) startOfWeek: DayValues = 0; - /** - * Defines the unselectable dates for calendar - */ - @property({ type: Array, attribute: "disabled-dates", reflect: true }) - disabledDates: Date[]; /** * Defines the calendar language */ @property() locale: string = document.documentElement.lang || "en-EN"; + /** + * Defines the unselectable dates for calendar + */ + _disabledDates: Date[] = []; + get disabledDates(): Date[] { + return this._disabledDates; + } + + @property({ attribute: "disabled-dates", reflect: true }) + set disabledDates(disabledDates: Date[] | string) { + if (typeof disabledDates === "string") { + const splitDisabledDates = disabledDates.split(","); + + splitDisabledDates?.forEach(disabledDate => { + const date = new Date(`${disabledDate}T00:00:00`); + + if (!isNaN(date.getTime())) this._disabledDates.push(date); + }); + } else if (Array.isArray(disabledDates)) { + disabledDates.forEach(disabledDate => { + this._disabledDates.push(disabledDate); + }); + } else { + console.warn("invalid disabledDate format.DisabledDates should be string or Date array."); + } + } + /** * Defines the maximum date value for the calendar */ @@ -68,6 +90,7 @@ export default class DatepickerCalendarMixin extends LitElement { @property({ attribute: "default-value", reflect: true }) set defaultValue(defaultValue: Date | Date[]) { + //başta bir type controlü yapalım string 23.04.2021,24.04.2021 2024-11-06 gelirse date array paslayalım ,date[] gelirse dümdüz kullanalım if (defaultValue) { if (this.type === CALENDAR_TYPES.SINGLE && Array.isArray(defaultValue)) { console.warn("'defaultValue' must be of type Date for single date selection."); From da3cbe87e52f2985aa62b784c3e787e8c98e5046 Mon Sep 17 00:00:00 2001 From: "dilan.dogan2" Date: Thu, 28 Nov 2024 17:28:21 +0300 Subject: [PATCH 31/35] feat(datepicker): review updates --- src/components/calendar/bl-calendar.css | 12 +- src/components/calendar/bl-calendar.test.ts | 106 +++++------- src/components/calendar/bl-calendar.ts | 62 +++---- src/components/datepicker/bl-datepicker.css | 10 +- .../datepicker/bl-datepicker.test.ts | 157 +++++++++--------- src/components/datepicker/bl-datepicker.ts | 33 ++-- .../datepicker-calendar-mixin.test.ts | 129 ++++++++++++++ .../datepicker-calendar-mixin.ts | 74 ++++++--- 8 files changed, 335 insertions(+), 248 deletions(-) create mode 100644 src/mixins/datepicker-calendar-mixin/datepicker-calendar-mixin.test.ts diff --git a/src/components/calendar/bl-calendar.css b/src/components/calendar/bl-calendar.css index 0b75ad0c..7cb093b9 100644 --- a/src/components/calendar/bl-calendar.css +++ b/src/components/calendar/bl-calendar.css @@ -5,10 +5,10 @@ .calendar-content { display: flex; - padding: 16px; + padding: var(--bl-size-s); flex-direction: column; align-items: center; - gap: 16px; + gap: var(--bl-size-s); border-radius: var(--bl-border-radius-s); width: fit-content; background: var(--bl-color-neutral-full); @@ -19,7 +19,7 @@ justify-content: space-between; width: 100%; align-items: center; - padding-bottom: 15px; + padding-bottom: var(--bl-size-s); } .arrow { @@ -50,7 +50,7 @@ display: flex; align-items: center; flex-direction: row; - padding-bottom: 8px; + padding-bottom: var(--bl-size-2xs); } .day { @@ -115,7 +115,7 @@ .weekday-text { color: var(--bl-color-neutral-dark); text-align: center; - padding: 8px 0; + padding: var(--bl-size-2xs) 0; width: 40px; } @@ -132,7 +132,7 @@ } .grid-item:not(:nth-last-child(-n + 3)) { - padding-bottom: 8px; + padding-bottom: var(--bl-size-2xs); } .calendar-text { diff --git a/src/components/calendar/bl-calendar.test.ts b/src/components/calendar/bl-calendar.test.ts index d0e01cd9..ed8e0e58 100644 --- a/src/components/calendar/bl-calendar.test.ts +++ b/src/components/calendar/bl-calendar.test.ts @@ -7,13 +7,20 @@ import sinon from "sinon"; describe("bl-calendar", () => { let element: BlCalendar; + let consoleWarnSpy: sinon.SinonSpy; beforeEach(async () => { element = await fixture(html` `); + consoleWarnSpy = sinon.spy(console, "warn"); + + }); + + afterEach(() => { + consoleWarnSpy.restore(); }); - it("should instantiate the component", () => { + it("should instantiate the element", () => { expect(document.createElement("bl-calendar")).instanceOf(HTMLElement); }); @@ -138,8 +145,8 @@ describe("bl-calendar", () => { element.handleRangeSelectCalendar(startDate); element.handleRangeSelectCalendar(endDate); - expect(element._selectedRangeDates.startDate).to.deep.equal(startDate); - expect(element._selectedRangeDates.endDate).to.deep.equal(endDate); + expect(element._selectedDates[0]).to.deep.equal(startDate); + expect(element._selectedDates[1]).to.deep.equal(endDate); }); it("should render month names in the correct locale", async () => { @@ -152,15 +159,6 @@ describe("bl-calendar", () => { expect(firstMonth).to.equal(monthName); }); - it("should apply the today-day class to today's date", async () => { - - const todayElement = Array.from( - element.shadowRoot?.querySelectorAll("bl-button") || [] - ).find(button => button?.textContent?.trim() === `${element.today.getDate()}`); - - expect(todayElement?.classList.contains("today-day")).to.be.true; - }); - it("should switch to the year view and render years", async () => { const yearButton = (element.shadowRoot?.querySelectorAll(".header-text")[1]) as BlButton; @@ -226,40 +224,35 @@ describe("bl-calendar", () => { expect(result).to.be.false; }); - it("should wrap defaultValue in an array if it is a single date", async () => { + it("should return false if calendarDate is not disabled", () => { + const calendarDate = new Date(2023, 9, 19); + + element.minDate = new Date(2023, 8, 19); + element.maxDate = new Date(2024, 10, 19); + + const result = element.checkIfDateIsDisabled(calendarDate); + + expect(result).to.be.false; + }); + + it("should wrap value in an array if it is a single date", async () => { const calendar = new BlCalendar(); - calendar._defaultValue = new Date("2023-09-18"); + calendar.value = new Date("2023-09-18"); calendar.type = CALENDAR_TYPES.SINGLE; - await calendar.firstUpdated(); - expect(calendar._selectedDates).to.deep.equal([new Date("2023-09-18")], {}); }); - it("should set startDate and endDate in selectedRangeDates when type is range", async () => { + it("should set startDate and endDate in selectedDays when type is range", async () => { const defaultDate1 = new Date(2023, 9, 18); const defaultDate2 = new Date(2023, 9, 19); - element._defaultValue = [defaultDate1, defaultDate2]; + element.value = [defaultDate1, defaultDate2]; element.type = CALENDAR_TYPES.RANGE; - const setHoverClassSpy = sinon.spy(element, "setHoverClass"); - - await element.firstUpdated(); - - expect(element._selectedRangeDates.startDate).to.be.equal(defaultDate1); - expect(element._selectedRangeDates.endDate).to.be.equal(defaultDate2); - expect(setHoverClassSpy).to.be.calledOnce; - }); - - it("should not set selectedDates or selectedRangeDates if defaultValue is undefined", async () => { - - await element.firstUpdated(); - - expect(element._selectedDates).to.deep.equal([]); - expect(element._selectedRangeDates.startDate).to.be.undefined; - expect(element._selectedRangeDates.endDate).to.be.undefined; + expect(element._selectedDates[0]).to.be.equal(defaultDate1); + expect(element._selectedDates[1]).to.be.equal(defaultDate2); }); it("should navigate to the previous month in DAYS view", async () => { @@ -360,12 +353,10 @@ describe("bl-calendar", () => { const startDate = new Date(2023, 0, 5); const calendarDate = new Date(2023, 0, 1); - element._selectedRangeDates.startDate = startDate; - element._selectedDates.push(startDate); + element._selectedDates[0] = startDate; element.handleRangeSelectCalendar(calendarDate); - expect(element._selectedRangeDates).to.deep.equal({ startDate: calendarDate, endDate: startDate }); expect(element._selectedDates).to.deep.equal([calendarDate, startDate]); }); @@ -374,12 +365,10 @@ describe("bl-calendar", () => { const startDate = new Date(2023, 0, 5); const endDate = new Date(2023, 0, 15); - element._selectedRangeDates = { startDate, endDate }; element._selectedDates = [startDate, endDate]; element.handleRangeSelectCalendar(calendarDate); - expect(element._selectedRangeDates).to.deep.equal({ startDate: calendarDate, endDate: undefined }); expect(element._selectedDates).to.deep.equal([calendarDate]); }); @@ -417,32 +406,30 @@ describe("bl-calendar", () => { it("should add range-start-day class to the start date element", async () => { - element._selectedRangeDates = { - startDate: new Date(2024, 10, 1), - endDate: new Date(2024, 10, 5) - }; + element._selectedDates = [new Date(2024, 10, 1), + new Date(2024, 10, 5) + ]; element.setHoverClass(); await new Promise((resolve) => setTimeout(resolve, 100)); const startDateElement = element.shadowRoot?.getElementById( - `${element._selectedRangeDates.startDate?.getTime()}` + `${element._selectedDates[0]?.getTime()}` )?.parentElement; expect(startDateElement?.classList.contains("range-start-day")).to.be.true; }); it("should add range-end-day class to the end date element", async () => { - element._selectedRangeDates = { - startDate: new Date(2024, 10, 1), - endDate: new Date(2024, 10, 5) - }; + element._selectedDates = [new Date(2024, 10, 1), + new Date(2024, 10, 5) + ]; element.setHoverClass(); await new Promise((resolve) => setTimeout(resolve, 100)); const endDateElement = element.shadowRoot?.getElementById( - `${element._selectedRangeDates.endDate?.getTime()}` + `${element._selectedDates[1]?.getTime()}` )?.parentElement; expect(endDateElement?.classList.contains("range-end-day")).to.be.true; @@ -584,28 +571,9 @@ describe("bl-calendar", () => { setNextCalendarViewSpy.restore(); }); - it("should not call setTimeout or add any class when startDate or endDate is undefined", () => { - element.createCalendarDays = () => new Map(); - element._selectedRangeDates = { - startDate: undefined, - endDate: new Date(2024, 0, 15) - }; - - const setTimeoutSpy = sinon.spy(window, "setTimeout"); - - element.setHoverClass(); - - expect(setTimeoutSpy).to.not.have.been.called; - - setTimeoutSpy.restore(); - }); - it("should add classes when both startDate and endDate are defined", () => { - element._selectedRangeDates = { - startDate: new Date(2024, 0, 10), - endDate: new Date(2024, 0, 15) - }; + element._selectedDates = [new Date(2024, 0, 10), new Date(2024, 0, 15)]; const setTimeoutSpy = sinon.spy(window, "setTimeout"); diff --git a/src/components/calendar/bl-calendar.ts b/src/components/calendar/bl-calendar.ts index a076af8c..d5420681 100644 --- a/src/components/calendar/bl-calendar.ts +++ b/src/components/calendar/bl-calendar.ts @@ -12,7 +12,7 @@ import { LAST_MONTH_INDEX, } from "./bl-calendar.constant"; import style from "./bl-calendar.css"; -import { Calendar, CalendarDay, CalendarView, RangePickerDates } from "./bl-calendar.types"; +import { Calendar, CalendarDay, CalendarView } from "./bl-calendar.types"; export const blCalendarChangedEvent = "bl-calendar-change"; @@ -22,10 +22,6 @@ export const blCalendarChangedEvent = "bl-calendar-change"; **/ @customElement("bl-calendar") export default class BlCalendar extends DatepickerCalendarMixin { - @state() - _selectedDates: Date[] = []; - @state() - _selectedRangeDates: RangePickerDates = { startDate: undefined, endDate: undefined }; @state() today = new Date(); @state() @@ -63,7 +59,6 @@ export default class BlCalendar extends DatepickerCalendarMixin { public handleClearSelectedDates = () => { this._selectedDates = []; - this._selectedRangeDates = { startDate: undefined, endDate: undefined }; this._onBlCalendarChange([]); this.clearRangePickerStyles(); }; @@ -79,9 +74,10 @@ export default class BlCalendar extends DatepickerCalendarMixin { setPreviousCalendarView() { this.clearRangePickerStyles(); if (this._calendarView === CALENDAR_VIEWS.DAYS) { - this._calendarMonth === FIRST_MONTH_INDEX - ? ((this._calendarMonth = LAST_MONTH_INDEX), (this._calendarYear -= 1)) - : (this._calendarMonth -= 1); + if (this._calendarMonth === FIRST_MONTH_INDEX) { + this._calendarMonth = LAST_MONTH_INDEX; + this._calendarYear -= 1; + } else this._calendarMonth -= 1; } else if (this._calendarView === CALENDAR_VIEWS.MONTHS) { this._calendarYear -= 1; } else if (this._calendarView === CALENDAR_VIEWS.YEARS) { @@ -175,22 +171,20 @@ export default class BlCalendar extends DatepickerCalendarMixin { } handleRangeSelectCalendar(calendarDate: Date) { - const { startDate, endDate } = this._selectedRangeDates; - - if (!startDate) { - this._selectedRangeDates.startDate = calendarDate; - this._selectedDates.push(calendarDate); - } else if (!endDate) { - if (calendarDate.getTime() > startDate.getTime()) { - this._selectedRangeDates.endDate = calendarDate; - this._selectedDates.push(calendarDate); + if (!this._selectedDates[0]) { + this._selectedDates[0] = calendarDate; + } else if (!this._selectedDates[1]) { + if (calendarDate.getTime() > this._selectedDates[0].getTime()) { + this._selectedDates[1] = calendarDate; } else { - this._selectedRangeDates = { startDate: calendarDate, endDate: startDate }; - this._selectedDates = [calendarDate, startDate]; + const tempEndDate = this._selectedDates[0]; + + this._selectedDates[0] = calendarDate; + this._selectedDates[1] = tempEndDate; } } else { - this._selectedRangeDates = { startDate: calendarDate, endDate: undefined }; - this._selectedDates = [calendarDate]; + this._selectedDates = []; + this._selectedDates[0] = calendarDate; } this.setHoverClass(); } @@ -227,16 +221,16 @@ export default class BlCalendar extends DatepickerCalendarMixin { setHoverClass() { this.clearRangePickerStyles(); - if (this._selectedRangeDates.startDate && this._selectedRangeDates.endDate) { + if (this._selectedDates[0] && this._selectedDates[1]) { setTimeout(() => { const startDateParentElement = this.shadowRoot?.getElementById( - `${this._selectedRangeDates.startDate?.getTime()}` + `${this._selectedDates[0]?.getTime()}` )?.parentElement; startDateParentElement?.classList.add("range-start-day"); const endDateParentElement = this.shadowRoot?.getElementById( - `${this._selectedRangeDates.endDate?.getTime()}` + `${this._selectedDates[1]?.getTime()}` )?.parentElement; endDateParentElement?.classList.add("range-end-day"); @@ -244,8 +238,8 @@ export default class BlCalendar extends DatepickerCalendarMixin { .flat() .filter( date => - date.getTime() > this._selectedRangeDates.startDate!.getTime() && - date.getTime() < this._selectedRangeDates?.endDate!.getTime() + date.getTime() > this._selectedDates[0]!.getTime() && + date.getTime() < this._selectedDates[1]!.getTime() ); for (let i = 0; i < rangeDays.length; i++) { @@ -333,20 +327,6 @@ export default class BlCalendar extends DatepickerCalendarMixin { return calendar; } - async firstUpdated() { - if (this._defaultValue) { - Array.isArray(this._defaultValue) - ? (this._selectedDates = this._defaultValue) - : (this._selectedDates = [new Date(this._defaultValue as Date)]); - - if (this.type === CALENDAR_TYPES.RANGE) { - this._selectedRangeDates.startDate = this._selectedDates[0]; - this._selectedRangeDates.endDate = this._selectedDates[1]; - this.setHoverClass(); - } - } - } - renderCalendarHeader() { const showMonthSelected = this._calendarView === CALENDAR_VIEWS.MONTHS ? "header-text-hover" : ""; diff --git a/src/components/datepicker/bl-datepicker.css b/src/components/datepicker/bl-datepicker.css index d01c8dc0..a99cfee4 100644 --- a/src/components/datepicker/bl-datepicker.css +++ b/src/components/datepicker/bl-datepicker.css @@ -12,7 +12,7 @@ .datepicker-content { display: flex; flex-direction: column; - gap: 8px; + gap: var(--bl-size-2xs); width: fit-content; } @@ -24,7 +24,7 @@ .icon-container { display: flex; - gap: 5px; + gap: var(--bl-size-3xs); align-items: center; } @@ -42,10 +42,10 @@ .action-divider { display: block; - height: 1rem; - width: 1px; + height: var(--bl-size-m); + width: var(--bl-size-4xs); background-color: var(--bl-color-neutral-lighter); - margin-right: 5px; + margin-right: var(--bl-size-3xs); } bl-popover { diff --git a/src/components/datepicker/bl-datepicker.test.ts b/src/components/datepicker/bl-datepicker.test.ts index 04a95bac..902a1216 100644 --- a/src/components/datepicker/bl-datepicker.test.ts +++ b/src/components/datepicker/bl-datepicker.test.ts @@ -45,7 +45,7 @@ describe("BlDatepicker", () => { }); it("should have default empty value", () => { - expect(element._value).to.equal(""); + expect(element._inputValue).to.equal(""); }); it("should set placeholder correctly", async () => { @@ -102,7 +102,7 @@ describe("BlDatepicker", () => { await element.updateComplete; expect(element._selectedDates).to.deep.equal([]); - expect(element._value).to.equal(""); + expect(element._inputValue).to.equal(""); }); it("should disable the input when 'disabled' is set", async () => { @@ -117,11 +117,11 @@ describe("BlDatepicker", () => { it("should use custom value formatter when provided", async () => { const testDate = new Date(2023, 1, 1); - element.valueFormatter = (dates: Date[]) => `Custom format: ${dates[0].toDateString()}`; + element.inputValueFormatter = (dates: Date[]) => `Custom format: ${dates[0].toDateString()}`; element.setDatePickerInput([testDate]); await element.updateComplete; - expect(element._value).to.equal(`Custom format: ${testDate.toDateString()}`); + expect(element._inputValue).to.equal(`Custom format: ${testDate.toDateString()}`); }); it("should handle multiple date selections", async () => { @@ -142,7 +142,7 @@ describe("BlDatepicker", () => { await element.updateComplete; expect(element._selectedDates).to.deep.equal([]); - expect(element._value).to.equal(""); + expect(element._inputValue).to.equal(""); }); it("should handle selecting a range of dates", async () => { @@ -178,33 +178,6 @@ describe("BlDatepicker", () => { expect(element._popoverEl.visible).to.be.false; }); - it("should set selected dates and call setDatePickerInput when default value is provided", async () => { - element._defaultValue = new Date(2024, 10, 10); - - const setDatePickerInputSpy = sinon.spy(element, "setDatePickerInput"); - - await element.firstUpdated(); - - expect(element._selectedDates).to.deep.equal([new Date(2024, 10, 10)]); - - expect(setDatePickerInputSpy).to.have.been.calledWith(element._selectedDates); - }); - - it("should handle an array of dates for default value", async () => { - element._defaultValue = [new Date(2024, 10, 10), new Date(2024, 10, 11)]; - - const setDatePickerInputSpy = sinon.spy(element, "setDatePickerInput"); - - await element.firstUpdated(); - - expect(element._selectedDates).to.deep.equal([ - new Date(2024, 10, 10), - new Date(2024, 10, 11) - ]); - - expect(setDatePickerInputSpy).to.have.been.calledWith(element._selectedDates); - }); - it("should insert a
after every third item", () => { const inputString = "Item1, Item2, Item3, Item4"; const result = element.formatAdditionalDates(inputString); @@ -228,13 +201,13 @@ describe("BlDatepicker", () => { expect(trigger?.textContent).to.equal("+2"); }); - it("should include \" ,...\" when floatingDateCount is greater than 0 for MULTIPLE type", () => { + it("should include ',...' when floatingDateCount is greater than 0 for MULTIPLE type", () => { element.type = CALENDAR_TYPES.MULTIPLE; element._selectedDates = [new Date("2024-01-01"), new Date("2024-01-02"), new Date("2024-01-03")]; element.setFloatingDates(); - element._defaultValueFormatter(); - expect(element._value).to.include(" ,..."); + element.defaultInputValueFormatter(); + expect(element._inputValue).to.include(" ,..."); }); it("should not include \" ,...\" when floatingDateCount is 0 for MULTIPLE type", () => { @@ -244,8 +217,8 @@ describe("BlDatepicker", () => { element.setFloatingDates(); - element._defaultValueFormatter(); - expect(element._value).to.not.include(" ,..."); + element.defaultInputValueFormatter(); + expect(element._inputValue).to.not.include(" ,..."); }); it("should format a date correctly", () => { @@ -283,77 +256,46 @@ describe("BlDatepicker", () => { closePopoverSpy.restore(); }); - it("should return a single date when defaultValue is a single Date", () => { + it("should return a single date when value is a single Date", () => { const date = new Date("2024-01-01"); - element._defaultValue = date; - expect(element.defaultValue).to.equal(date); + element._value = date; + expect(element.value).to.equal(date); }); - it("should return an array of dates when defaultValue is an array of Dates", () => { + it("should return an array of dates when value is an array of Dates", () => { const dates = [new Date("2024-01-01"), new Date("2024-02-01")]; - element._defaultValue = dates; - expect(element.defaultValue).to.deep.equal(dates); + element._value = dates; + expect(element.value).to.deep.equal(dates); }); - it("should return undefined if defaultValue is not set", () => { - expect(element.defaultValue).to.be.undefined; + it("should return undefined if value is not set", () => { + expect(element.value).to.be.undefined; }); - it("should warn when 'defaultValue' is not an array for multiple/range selection", async () => { + it("should warn when 'value' is not an array for multiple/range selection", async () => { element = await fixture(html` `); - element._defaultValue = new Date(); + element._value = new Date(); element.firstUpdated(); expect(consoleWarnSpy.calledOnce).to.be.true; }); - it("should not warn when defaultValue is an array for multiple/range selection", () => { + it("should not warn when value is an array for multiple/range selection", () => { element.type = CALENDAR_TYPES.MULTIPLE; - element._defaultValue = [new Date(), new Date()]; + element._value = [new Date(), new Date()]; element.firstUpdated(); expect(consoleWarnSpy.called).to.be.false; }); - it("should warn when defaultValue is an array but not exactly two Date objects in RANGE mode", async () => { + it("should not warn when 'value' is an array of exactly two Date objects in RANGE mode", () => { element.type = CALENDAR_TYPES.RANGE; - element._defaultValue = []; - element.setDatePickerInput([]); - - await element.updateComplete; - - expect(consoleWarnSpy.calledOnce).to.be.true; - expect(consoleWarnSpy.calledWith("'defaultValue' must be an array of two Date objects when the date selection mode is set to range.")).to.be.true; - - consoleWarnSpy.resetHistory(); - - element._defaultValue = [new Date()]; - element.setDatePickerInput([new Date()]); - - await element.updateComplete; - - expect(consoleWarnSpy.calledOnce).to.be.true; - expect(consoleWarnSpy.calledWith("'defaultValue' must be an array of two Date objects when the date selection mode is set to range.")).to.be.true; - - consoleWarnSpy.resetHistory(); - - element._defaultValue = [new Date(), new Date(), new Date()]; - element.setDatePickerInput([new Date(), new Date(), new Date()]); - - await element.updateComplete; - - expect(consoleWarnSpy.calledOnce).to.be.true; - expect(consoleWarnSpy.calledWith("'defaultValue' must be an array of two Date objects when the date selection mode is set to range.")).to.be.true; - }); - - it("should not warn when 'defaultValue' is an array of exactly two Date objects in RANGE mode", () => { - element.type = CALENDAR_TYPES.RANGE; - element._defaultValue = [new Date(), new Date()]; + element._value = [new Date(), new Date()]; element.firstUpdated(); @@ -382,4 +324,55 @@ describe("BlDatepicker", () => { expect(consoleWarnSpy.calledWith("maxDate cannot be smaller than minDate.")).to.be.true; }); + + it("should focus the input element on calendar mouse down", async () => { + const focusSpy = sinon.spy(element._inputEl, "focus"); + const preventDefaultSpy = sinon.spy(); + + element._inputEl.focus = focusSpy; + + const mouseDownEvent = new MouseEvent("mousedown", { bubbles: true, composed: true }); + + mouseDownEvent.preventDefault = preventDefaultSpy; + + element._inputEl.dispatchEvent(mouseDownEvent); + + expect(preventDefaultSpy).to.have.been.calledOnce; + + expect(focusSpy).to.have.been.calledOnce; + }); + + it("should focus the input element on input mouse down", async () => { + // Create spies for the methods we want to check + const focusSpy = sinon.spy(element._inputEl, "focus"); + const preventDefaultSpy = sinon.spy(); + + element._inputEl.focus = focusSpy; + + const mouseDownEvent = new MouseEvent("mousedown", { bubbles: true, composed: true }); + + mouseDownEvent.preventDefault = preventDefaultSpy; + + element._inputEl.dispatchEvent(mouseDownEvent); + + expect(preventDefaultSpy).to.have.been.calledOnce; + + expect(focusSpy).to.have.been.calledOnce; + }); + + it("should focus the input element on calendar mouse down", async () => { + const focusSpy = sinon.spy(element._inputEl, "focus"); + const preventDefaultSpy = sinon.spy(); + + const mouseDownEvent = new MouseEvent("mousedown", { bubbles: true, composed: true }); + + mouseDownEvent.preventDefault = preventDefaultSpy; + + element._calendarEl.dispatchEvent(mouseDownEvent); + + expect(preventDefaultSpy.called).to.be.true; + + expect(focusSpy.called).to.be.true; + }); + }); diff --git a/src/components/datepicker/bl-datepicker.ts b/src/components/datepicker/bl-datepicker.ts index 6a8059d0..5b516c1f 100644 --- a/src/components/datepicker/bl-datepicker.ts +++ b/src/components/datepicker/bl-datepicker.ts @@ -35,7 +35,7 @@ export default class BlDatepicker extends DatepickerCalendarMixin { * Defines the custom formatter function */ @property({ type: Function, attribute: "value-formatter" }) - valueFormatter: ((dates: Date[]) => string) | null = null; + inputValueFormatter: ((dates: Date[]) => string) | null = null; /** * Sets datepicker to disabled */ @@ -48,7 +48,7 @@ export default class BlDatepicker extends DatepickerCalendarMixin { helpText: string; @state() - _value = ""; + _inputValue = ""; @state() _selectedDates: Date[] = []; @@ -80,9 +80,9 @@ export default class BlDatepicker extends DatepickerCalendarMixin { return [style]; } - _defaultValueFormatter() { + defaultInputValueFormatter() { if (this.type === CALENDAR_TYPES.SINGLE) { - this._value = this.formatDate(this._selectedDates[0]); + this._inputValue = this.formatDate(this._selectedDates[0]); this.closePopoverWithTimeout(); } else if (this.type === CALENDAR_TYPES.MULTIPLE) { this.setFloatingDates(); @@ -90,11 +90,11 @@ export default class BlDatepicker extends DatepickerCalendarMixin { .slice(0, this._fittingDateCount) .map(date => this.formatDate(date)); - this._value = values.join(",") + (this._floatingDateCount > 0 ? " ,..." : ""); + this._inputValue = values.join(",") + (this._floatingDateCount > 0 ? " ,..." : ""); } else if (this.type === CALENDAR_TYPES.RANGE) { - if (this._selectedDates[0]) this._value = this.formatDate(this._selectedDates[0]); + if (this._selectedDates[0]) this._inputValue = this.formatDate(this._selectedDates[0]); if (this._selectedDates[1]) - this._value = `${this._value}-${this.formatDate(this._selectedDates[1])}`; + this._inputValue = `${this._inputValue}-${this.formatDate(this._selectedDates[1])}`; if (this._selectedDates[0] && this._selectedDates[1]) this.closePopoverWithTimeout(); } } @@ -115,13 +115,13 @@ export default class BlDatepicker extends DatepickerCalendarMixin { setDatePickerInput(dates: Date[] | []) { if (!dates.length) { - this._value = ""; + this._inputValue = ""; } else { this._selectedDates = dates; - if (this.valueFormatter) { - this._value = this.valueFormatter(this._selectedDates); + if (this.inputValueFormatter) { + this._inputValue = this.inputValueFormatter(this._selectedDates); } else { - this._defaultValueFormatter(); + this.defaultInputValueFormatter(); } } @@ -138,7 +138,7 @@ export default class BlDatepicker extends DatepickerCalendarMixin { clearDatepicker() { this._calendarEl.handleClearSelectedDates(); this._selectedDates = []; - this._value = ""; + this._inputValue = ""; this._floatingDateCount = 0; this._inputEl?.blur(); } @@ -182,10 +182,7 @@ export default class BlDatepicker extends DatepickerCalendarMixin { this._calendarEl?.addEventListener("mousedown", this._onCalendarMouseDown); this._inputEl?.addEventListener("mousedown", this._onInputMouseDown); - if (this._defaultValue) { - Array.isArray(this._defaultValue) - ? (this._selectedDates = this._defaultValue) - : (this._selectedDates = [new Date(this._defaultValue)]); + if (this._selectedDates) { this.setDatePickerInput(this._selectedDates); } } @@ -205,7 +202,7 @@ export default class BlDatepicker extends DatepickerCalendarMixin { .maxDate=${this.maxDate} .startOfWeek=${this.startOfWeek} .disabledDates=${this.disabledDates} - .defaultValue=${this._defaultValue} + .value=${this._value} .locale=${this.locale} @bl-calendar-change="${(event: CustomEvent) => this.setDatePickerInput(event.detail)}" > @@ -243,7 +240,7 @@ export default class BlDatepicker extends DatepickerCalendarMixin { return html`
{ + let element: TestDatepickerCalendar; + + beforeEach(async () => { + element = await fixture( + html` + ` + ); + }); + + it("should correctly set and get disabledDates from a string", () => { + element.disabledDates = "2024-01-01,2024-01-15"; + expect(element.disabledDates).to.have.length(2); + expect(element.disabledDates[0].toISOString()).to.include("2023-12-31T21:00:00.000Z"); + expect(element.disabledDates[1].toISOString()).to.include("2024-01-14T21:00:00.000Z"); + }); + + it("should correctly set and get disabledDates from an array of dates", () => { + const disabledDatesArray = [new Date("2024-01-01"), new Date("2024-01-15")]; + + element.disabledDates = disabledDatesArray; + expect(element.disabledDates).to.deep.equal(disabledDatesArray); + }); + + it("should not add invalid dates to disabledDates", () => { + element.disabledDates = "invalid-date,2024-01-15"; + expect(element.disabledDates).to.have.length(1); + expect(element.disabledDates[0].toISOString()).to.include("2024-01-14T21:00:00.000Z"); + }); + + it("should set and get minDate correctly", () => { + const minDate = new Date("2024-01-01"); + + element.minDate = minDate; + expect(element.minDate).to.equal(minDate); + }); + + it("should log a warning if minDate is greater than maxDate", () => { + const consoleSpy = sinon.spy(console, "warn"); + + element.maxDate = new Date("2024-01-01"); + element.minDate = new Date("2024-02-01"); + expect(consoleSpy.calledWith("minDate cannot be greater than maxDate.")).to.be.true; + consoleSpy.restore(); + }); + + it("should set and get maxDate correctly", () => { + const maxDate = new Date("2024-12-31"); + + element.maxDate = maxDate; + expect(element.maxDate).to.equal(maxDate); + }); + + it("should log a warning if maxDate is smaller than minDate", () => { + const consoleSpy = sinon.spy(console, "warn"); + + element.minDate = new Date("2024-12-31"); + element.maxDate = new Date("2024-01-01"); + expect(consoleSpy.calledWith("maxDate cannot be smaller than minDate.")).to.be.true; + consoleSpy.restore(); + }); + + it("should correctly parse value from a string", () => { + const valueString = "2024-01-01,2024-01-15"; + + element.type = CALENDAR_TYPES.MULTIPLE; + element.value = valueString; + expect(element._selectedDates).to.be.an("array"); + expect(element._selectedDates).to.have.length(2); + expect((element._selectedDates)[0].toISOString()).to.include("2023-12-31T21:00:00.000Z"); + }); + + it("should correctly parse value from a Date object", () => { + const dateValue = new Date("2024-01-01"); + + element.type = CALENDAR_TYPES.SINGLE; + element.value = dateValue; + expect(element.value).to.equal(dateValue); + }); + + it("should log a warning if value type is invalid for CALENDAR_TYPES.SINGLE", () => { + const consoleSpy = sinon.spy(console, "warn"); + + element.type = CALENDAR_TYPES.SINGLE; + element.value = [new Date("2024-01-01"), new Date("2024-01-15")]; + expect(consoleSpy.calledWith("'value' must be of type Date for single date selection.")).to.be + .true; + consoleSpy.restore(); + }); + + it("should log a warning if value type is invalid for CALENDAR_TYPES.RANGE", () => { + const consoleSpy = sinon.spy(console, "warn"); + + element.type = CALENDAR_TYPES.RANGE; + element.value = [new Date("2024-01-01")]; + expect( + consoleSpy.calledWith( + "'value' must be an array of two Date objects when the date selection mode is set to range." + ) + ).to.be.true; + consoleSpy.restore(); + }); + + it("should update selectedDates when value changes", () => { + const dateValue = new Date("2024-01-01"); + + element.type = CALENDAR_TYPES.SINGLE; + element.value = dateValue; + expect(element._selectedDates).to.deep.equal([dateValue]); + }); + + it("should not update selectedDates if value is invalid", () => { + const originalSelectedDates = [...element._selectedDates]; + + element.value = "invalid-date"; + expect(element._selectedDates).to.deep.equal(originalSelectedDates); + }); +}); diff --git a/src/mixins/datepicker-calendar-mixin/datepicker-calendar-mixin.ts b/src/mixins/datepicker-calendar-mixin/datepicker-calendar-mixin.ts index 7eb46f16..f4d8012e 100644 --- a/src/mixins/datepicker-calendar-mixin/datepicker-calendar-mixin.ts +++ b/src/mixins/datepicker-calendar-mixin/datepicker-calendar-mixin.ts @@ -1,5 +1,5 @@ import { LitElement } from "lit"; -import { property } from "lit/decorators.js"; +import { property, state } from "lit/decorators.js"; import { CALENDAR_TYPES } from "../../components/calendar/bl-calendar.constant"; import { CalendarType, DayValues } from "../../components/calendar/bl-calendar.types"; @@ -19,11 +19,14 @@ export default class DatepickerCalendarMixin extends LitElement { */ @property() locale: string = document.documentElement.lang || "en-EN"; + @state() + _selectedDates: Date[] = []; /** * Defines the unselectable dates for calendar */ _disabledDates: Date[] = []; + get disabledDates(): Date[] { return this._disabledDates; } @@ -40,10 +43,8 @@ export default class DatepickerCalendarMixin extends LitElement { }); } else if (Array.isArray(disabledDates)) { disabledDates.forEach(disabledDate => { - this._disabledDates.push(disabledDate); + if (!isNaN(disabledDate.getTime())) this._disabledDates.push(disabledDate); }); - } else { - console.warn("invalid disabledDate format.DisabledDates should be string or Date array."); } } @@ -51,6 +52,7 @@ export default class DatepickerCalendarMixin extends LitElement { * Defines the maximum date value for the calendar */ _maxDate: Date; + get maxDate() { return this._maxDate; } @@ -82,32 +84,50 @@ export default class DatepickerCalendarMixin extends LitElement { } } - _defaultValue: Date | Date[]; + /** + * Target elements state + */ - get defaultValue() { - return this._defaultValue; + @state() _value: Date | Date[] | string; + /** + * Sets the target element of the popover to align and trigger. + * It can be a string id of the target element or can be a direct Element reference of it. + */ + @property() + get value(): string | Date | Date[] { + return this._value; } - @property({ attribute: "default-value", reflect: true }) - set defaultValue(defaultValue: Date | Date[]) { - //başta bir type controlü yapalım string 23.04.2021,24.04.2021 2024-11-06 gelirse date array paslayalım ,date[] gelirse dümdüz kullanalım - if (defaultValue) { - if (this.type === CALENDAR_TYPES.SINGLE && Array.isArray(defaultValue)) { - console.warn("'defaultValue' must be of type Date for single date selection."); - } else if (this.type !== CALENDAR_TYPES.SINGLE && !Array.isArray(defaultValue)) { - console.warn( - "'defaultValue' must be an array of Date objects for multiple/range selection." - ); - } else if ( - this.type === CALENDAR_TYPES.RANGE && - Array.isArray(defaultValue) && - defaultValue.length != 2 - ) { - console.warn( - "'defaultValue' must be an array of two Date objects when the date selection mode is set to range." - ); - } else { - this._defaultValue = defaultValue; + set value(val: string | Date | Date[]) { + if (val) { + let tempVal: Date[] = []; + + if (typeof val === "string") { + const splitDates = val.split(","); + + splitDates?.forEach(date => { + const isDate = new Date(`${date}T00:00:00`); + + if (!isNaN(isDate.getTime())) { + tempVal.push(isDate); + } + }); + } else if (val instanceof Date) { + tempVal.push(val); + } else if (Array.isArray(val)) { + if (this.type === CALENDAR_TYPES.SINGLE && Array.isArray(val)) { + console.warn("'value' must be of type Date for single date selection."); + } else if (this.type === CALENDAR_TYPES.RANGE && Array.isArray(val) && val.length != 2) { + console.warn( + "'value' must be an array of two Date objects when the date selection mode is set to range." + ); + } else { + tempVal = val; + } + } + if (tempVal.length) { + this._value = val; + this._selectedDates.splice(0, this._selectedDates.length, ...tempVal); } } } From ad99f0303021c6368804efb74afa94d3edbee3e2 Mon Sep 17 00:00:00 2001 From: "dilan.dogan2" Date: Fri, 29 Nov 2024 00:43:06 +0300 Subject: [PATCH 32/35] feat(datepicker): string to date converter review update --- .../calendar/bl-calendar.stories.mdx | 17 ++++- src/components/calendar/bl-calendar.test.ts | 3 - src/components/calendar/bl-calendar.ts | 10 +-- .../datepicker/bl-datepicker.stories.mdx | 8 +-- .../datepicker-calendar-mixin.test.ts | 13 ++-- .../datepicker-calendar-mixin.ts | 53 +++++++--------- .../string-to-date-converter.test.ts | 63 +++++++++++++++++++ src/utilities/string-to-date-converter.ts | 13 ++++ 8 files changed, 129 insertions(+), 51 deletions(-) create mode 100644 src/utilities/string-to-date-converter.test.ts create mode 100644 src/utilities/string-to-date-converter.ts diff --git a/src/components/calendar/bl-calendar.stories.mdx b/src/components/calendar/bl-calendar.stories.mdx index fe5a8caa..85efafef 100644 --- a/src/components/calendar/bl-calendar.stories.mdx +++ b/src/components/calendar/bl-calendar.stories.mdx @@ -23,7 +23,7 @@ export const CalendarTemplate = (args) => html` max-date=${ifDefined(args.maxDate)} start-of-week=${ifDefined(args.startOfWeek)} locale=${ifDefined(args.locale)} - default-value=${ifDefined(args.defaultValue)} + value=${ifDefined(args.value)} disabled-dates=${ifDefined(args.disabledDates)}>${unsafeHTML(args.content)} ` @@ -80,6 +80,19 @@ You can select date range from calendar. +### Set Value + +You can set default value to calendar. + + + + {Template.bind({})} + + + ### Disabled Dates You can set dates which you want to disable from calendar. @@ -87,7 +100,7 @@ You can set dates which you want to disable from calendar. {Template.bind({})} diff --git a/src/components/calendar/bl-calendar.test.ts b/src/components/calendar/bl-calendar.test.ts index ed8e0e58..2abba4f4 100644 --- a/src/components/calendar/bl-calendar.test.ts +++ b/src/components/calendar/bl-calendar.test.ts @@ -227,9 +227,6 @@ describe("bl-calendar", () => { it("should return false if calendarDate is not disabled", () => { const calendarDate = new Date(2023, 9, 19); - element.minDate = new Date(2023, 8, 19); - element.maxDate = new Date(2024, 10, 19); - const result = element.checkIfDateIsDisabled(calendarDate); expect(result).to.be.false; diff --git a/src/components/calendar/bl-calendar.ts b/src/components/calendar/bl-calendar.ts index d5420681..1e1dcc1b 100644 --- a/src/components/calendar/bl-calendar.ts +++ b/src/components/calendar/bl-calendar.ts @@ -160,11 +160,11 @@ export default class BlCalendar extends DatepickerCalendarMixin { } handleMultipleSelectCalendar(calendarDate: Date) { - const dateExist = this._selectedDates.some(d => d.getTime() === calendarDate.getTime()); + const dateExist = this._selectedDates?.some(d => d.getTime() === calendarDate.getTime()); dateExist - ? this._selectedDates.splice( - this._selectedDates.findIndex(d => d.getTime() === calendarDate.getTime()), + ? this._selectedDates?.splice( + this._selectedDates?.findIndex(d => d.getTime() === calendarDate.getTime()), 1 ) : this._selectedDates.push(calendarDate); @@ -190,7 +190,7 @@ export default class BlCalendar extends DatepickerCalendarMixin { } checkIfSelectedDate(calendarDate: Date) { - return this._selectedDates.some(date => date?.getTime() === calendarDate.getTime()); + return this._selectedDates?.some(date => date?.getTime() === calendarDate.getTime()); } checkIfDateIsToday(calendarDate: Date) { @@ -210,7 +210,7 @@ export default class BlCalendar extends DatepickerCalendarMixin { ) { return true; } - if (this.disabledDates) { + if (this.disabledDates.length > 0) { return this.disabledDates.some(disabledDate => { return calendarDate.getTime() === disabledDate.getTime(); }); diff --git a/src/components/datepicker/bl-datepicker.stories.mdx b/src/components/datepicker/bl-datepicker.stories.mdx index 28a14c29..126f18f3 100644 --- a/src/components/datepicker/bl-datepicker.stories.mdx +++ b/src/components/datepicker/bl-datepicker.stories.mdx @@ -25,7 +25,7 @@ export const DatepickerTemplate = (args) => html` max-date=${ifDefined(args.maxDate)} start-of-week=${ifDefined(args.startOfWeek)} locale=${ifDefined(args.locale)} - default-value=${ifDefined(args.defaultValue)} + value=${ifDefined(args.value)} style=${ifDefined(args.style)} disabled-dates=${ifDefined(args.disabledDates)}>${unsafeHTML(args.content)} ` @@ -93,11 +93,11 @@ You can select date range from Datepicker. You can set a default value to datepicker. - {Template.bind({})} @@ -112,7 +112,7 @@ You can set dates which you want to disable from Datepicker. type: 'multiple', label: 'Multiple Datepicker', placeholder: 'Set disabled dates', - disabledDates: `["${new Date(new Date().getFullYear(), new Date().getMonth(), new Date().getDate() + 2)}"]` + disabledDates: new Date(new Date().getFullYear(), new Date().getMonth(), new Date().getDate() + 2) }}> {Template.bind({})} diff --git a/src/mixins/datepicker-calendar-mixin/datepicker-calendar-mixin.test.ts b/src/mixins/datepicker-calendar-mixin/datepicker-calendar-mixin.test.ts index fd3dd8b4..71364cc1 100644 --- a/src/mixins/datepicker-calendar-mixin/datepicker-calendar-mixin.test.ts +++ b/src/mixins/datepicker-calendar-mixin/datepicker-calendar-mixin.test.ts @@ -22,8 +22,8 @@ describe("DatepickerCalendarMixin", () => { it("should correctly set and get disabledDates from a string", () => { element.disabledDates = "2024-01-01,2024-01-15"; expect(element.disabledDates).to.have.length(2); - expect(element.disabledDates[0].toISOString()).to.include("2023-12-31T21:00:00.000Z"); - expect(element.disabledDates[1].toISOString()).to.include("2024-01-14T21:00:00.000Z"); + expect(element.disabledDates[0].getTime()).to.equal(new Date("2024-01-01").getTime()); + expect(element.disabledDates[1].getTime()).to.equal(new Date("2024-01-15").getTime()); }); it("should correctly set and get disabledDates from an array of dates", () => { @@ -36,7 +36,7 @@ describe("DatepickerCalendarMixin", () => { it("should not add invalid dates to disabledDates", () => { element.disabledDates = "invalid-date,2024-01-15"; expect(element.disabledDates).to.have.length(1); - expect(element.disabledDates[0].toISOString()).to.include("2024-01-14T21:00:00.000Z"); + expect(element.disabledDates[0].getTime()).to.equal(new Date("2024-01-15").getTime()); }); it("should set and get minDate correctly", () => { @@ -78,7 +78,8 @@ describe("DatepickerCalendarMixin", () => { element.value = valueString; expect(element._selectedDates).to.be.an("array"); expect(element._selectedDates).to.have.length(2); - expect((element._selectedDates)[0].toISOString()).to.include("2023-12-31T21:00:00.000Z"); + expect((element._selectedDates)[0].getTime()).to.equal(new Date("2024-01-01").getTime()); + expect((element._selectedDates)[1].getTime()).to.equal(new Date("2024-01-15").getTime()); }); it("should correctly parse value from a Date object", () => { @@ -94,7 +95,7 @@ describe("DatepickerCalendarMixin", () => { element.type = CALENDAR_TYPES.SINGLE; element.value = [new Date("2024-01-01"), new Date("2024-01-15")]; - expect(consoleSpy.calledWith("'value' must be of type Date for single date selection.")).to.be + expect(consoleSpy.calledWith("'value' must be a single Date for single type selection.")).to.be .true; consoleSpy.restore(); }); @@ -106,7 +107,7 @@ describe("DatepickerCalendarMixin", () => { element.value = [new Date("2024-01-01")]; expect( consoleSpy.calledWith( - "'value' must be an array of two Date objects when the date selection mode is set to range." + "'value' must be an array of two Date objects when the type selection mode is set to range." ) ).to.be.true; consoleSpy.restore(); diff --git a/src/mixins/datepicker-calendar-mixin/datepicker-calendar-mixin.ts b/src/mixins/datepicker-calendar-mixin/datepicker-calendar-mixin.ts index f4d8012e..a89dd7fd 100644 --- a/src/mixins/datepicker-calendar-mixin/datepicker-calendar-mixin.ts +++ b/src/mixins/datepicker-calendar-mixin/datepicker-calendar-mixin.ts @@ -2,6 +2,7 @@ import { LitElement } from "lit"; import { property, state } from "lit/decorators.js"; import { CALENDAR_TYPES } from "../../components/calendar/bl-calendar.constant"; import { CalendarType, DayValues } from "../../components/calendar/bl-calendar.types"; +import { stringToDateArray } from "../../utilities/string-to-date-converter"; export default class DatepickerCalendarMixin extends LitElement { /** @@ -34,13 +35,7 @@ export default class DatepickerCalendarMixin extends LitElement { @property({ attribute: "disabled-dates", reflect: true }) set disabledDates(disabledDates: Date[] | string) { if (typeof disabledDates === "string") { - const splitDisabledDates = disabledDates.split(","); - - splitDisabledDates?.forEach(disabledDate => { - const date = new Date(`${disabledDate}T00:00:00`); - - if (!isNaN(date.getTime())) this._disabledDates.push(date); - }); + this._disabledDates = stringToDateArray(disabledDates); } else if (Array.isArray(disabledDates)) { disabledDates.forEach(disabledDate => { if (!isNaN(disabledDate.getTime())) this._disabledDates.push(disabledDate); @@ -98,37 +93,33 @@ export default class DatepickerCalendarMixin extends LitElement { return this._value; } - set value(val: string | Date | Date[]) { - if (val) { + set value(value: string | Date | Date[]) { + if (value) { let tempVal: Date[] = []; - if (typeof val === "string") { - const splitDates = val.split(","); - - splitDates?.forEach(date => { - const isDate = new Date(`${date}T00:00:00`); - - if (!isNaN(isDate.getTime())) { - tempVal.push(isDate); - } - }); - } else if (val instanceof Date) { - tempVal.push(val); - } else if (Array.isArray(val)) { - if (this.type === CALENDAR_TYPES.SINGLE && Array.isArray(val)) { - console.warn("'value' must be of type Date for single date selection."); - } else if (this.type === CALENDAR_TYPES.RANGE && Array.isArray(val) && val.length != 2) { + if (typeof value === "string") { + tempVal = stringToDateArray(value); + } else if (value instanceof Date) { + tempVal.push(value); + } else if (Array.isArray(value)) { + tempVal = value; + } + if (tempVal.length > 0) { + if (this.type === CALENDAR_TYPES.SINGLE && tempVal.length > 1) { + console.warn("'value' must be a single Date for single type selection."); + } else if ( + this.type === CALENDAR_TYPES.RANGE && + Array.isArray(tempVal) && + tempVal.length != 2 + ) { console.warn( - "'value' must be an array of two Date objects when the date selection mode is set to range." + "'value' must be an array of two Date objects when the type selection mode is set to range." ); } else { - tempVal = val; + this._value = value; + this._selectedDates.splice(0, this._selectedDates.length, ...tempVal); } } - if (tempVal.length) { - this._value = val; - this._selectedDates.splice(0, this._selectedDates.length, ...tempVal); - } } } } diff --git a/src/utilities/string-to-date-converter.test.ts b/src/utilities/string-to-date-converter.test.ts new file mode 100644 index 00000000..4cc773e6 --- /dev/null +++ b/src/utilities/string-to-date-converter.test.ts @@ -0,0 +1,63 @@ +import { expect } from "@open-wc/testing"; +import { stringToDateArray } from "./string-to-date-converter"; + +describe("stringToDateArray", () => { + it("should convert a valid string of dates into an array of Date objects", () => { + const input = "2024-01-01,2024-02-01,2024-03-01"; + const result = stringToDateArray(input); + + expect(result).to.be.an("array").with.length(3); + expect(result[0].getTime()).to.equal(new Date("2024-01-01").getTime()); + expect(result[1].getTime()).to.equal(new Date("2024-02-01").getTime()); + expect(result[2].getTime()).to.equal(new Date("2024-03-01").getTime()); + }); + + it("should handle an empty string and return an empty array", () => { + const input = ""; + const result = stringToDateArray(input); + + expect(result).to.be.an("array").that.is.empty; + }); + + it("should skip invalid date strings", () => { + const input = "2024-01-01,invalid-date,2024-03-01"; + const result = stringToDateArray(input); + + expect(result).to.be.an("array").with.length(2); + expect(result[0].toISOString()).to.include("2024-01-01"); + expect(result[1].toISOString()).to.include("2024-03-01"); + }); + + it("should return an empty array if all dates are invalid", () => { + const input = "invalid-date1,invalid-date2,not-a-date"; + const result = stringToDateArray(input); + + expect(result).to.be.an("array").that.is.empty; + }); + + it("should correctly parse a single valid date", () => { + const input = "2024-01-01"; + const result = stringToDateArray(input); + + expect(result).to.be.an("array").with.length(1); + expect(result[0].toISOString()).to.include("2024-01-01"); + }); + + it("should handle leading and trailing spaces in date strings", () => { + const input = " 2024-01-01 , 2024-02-01 "; + const result = stringToDateArray(input); + + expect(result).to.be.an("array").with.length(2); + expect(result[0].getTime()).to.equal(new Date("2024-01-01").getTime()); + expect(result[1].getTime()).to.equal(new Date("2024-02-01").getTime()); + }); + + it("should handle mixed valid and invalid dates with extra spaces", () => { + const input = " 2024-01-01 , invalid-date , 2024-03-01 "; + const result = stringToDateArray(input); + + expect(result).to.be.an("array").with.length(2); + expect(result[0].getTime()).to.equal(new Date("2024-01-01").getTime()); + expect(result[1].getTime()).to.equal(new Date("2024-03-01").getTime()); + }); +}); diff --git a/src/utilities/string-to-date-converter.ts b/src/utilities/string-to-date-converter.ts new file mode 100644 index 00000000..259f2fa3 --- /dev/null +++ b/src/utilities/string-to-date-converter.ts @@ -0,0 +1,13 @@ +export function stringToDateArray(value: string): Date[] { + const tempValue: Date[] = []; + const splitDates = value.split(","); + + splitDates?.forEach(date => { + const isDate = new Date(date.trim()); + + if (!isNaN(isDate.getTime())) { + tempValue.push(isDate); + } + }); + return tempValue; +} From cb11c421a8e1b3c93ee199f76dcd5a4938a9f5b3 Mon Sep 17 00:00:00 2001 From: "dilan.dogan2" Date: Mon, 2 Dec 2024 10:39:09 +0300 Subject: [PATCH 33/35] feat(datepicker): add css variables to wrapper class --- src/components/calendar/bl-calendar.css | 4 ++-- src/components/calendar/bl-calendar.test.ts | 12 ++++++------ src/components/datepicker/bl-datepicker.css | 7 +++---- src/components/datepicker/bl-datepicker.ts | 6 ++++-- 4 files changed, 15 insertions(+), 14 deletions(-) diff --git a/src/components/calendar/bl-calendar.css b/src/components/calendar/bl-calendar.css index 7cb093b9..8b689341 100644 --- a/src/components/calendar/bl-calendar.css +++ b/src/components/calendar/bl-calendar.css @@ -5,10 +5,10 @@ .calendar-content { display: flex; - padding: var(--bl-size-s); + padding: var(--bl-size-m); flex-direction: column; align-items: center; - gap: var(--bl-size-s); + gap: var(--bl-size-m); border-radius: var(--bl-border-radius-s); width: fit-content; background: var(--bl-color-neutral-full); diff --git a/src/components/calendar/bl-calendar.test.ts b/src/components/calendar/bl-calendar.test.ts index 2abba4f4..01d8951b 100644 --- a/src/components/calendar/bl-calendar.test.ts +++ b/src/components/calendar/bl-calendar.test.ts @@ -403,13 +403,13 @@ describe("bl-calendar", () => { it("should add range-start-day class to the start date element", async () => { - element._selectedDates = [new Date(2024, 10, 1), - new Date(2024, 10, 5) + element._selectedDates = [new Date(element.today.getFullYear(), element.today.getMonth(), 1), + new Date(element.today.getFullYear(), element.today.getMonth(), 5) ]; element.setHoverClass(); - await new Promise((resolve) => setTimeout(resolve, 100)); + await new Promise((resolve) => setTimeout(resolve, 200)); const startDateElement = element.shadowRoot?.getElementById( `${element._selectedDates[0]?.getTime()}` )?.parentElement; @@ -418,12 +418,12 @@ describe("bl-calendar", () => { }); it("should add range-end-day class to the end date element", async () => { - element._selectedDates = [new Date(2024, 10, 1), - new Date(2024, 10, 5) + element._selectedDates = [new Date(element.today.getFullYear(), element.today.getMonth(), 1), + new Date(element.today.getFullYear(), element.today.getMonth(), 5) ]; element.setHoverClass(); - await new Promise((resolve) => setTimeout(resolve, 100)); + await new Promise((resolve) => setTimeout(resolve, 200)); const endDateElement = element.shadowRoot?.getElementById( `${element._selectedDates[1]?.getTime()}` diff --git a/src/components/datepicker/bl-datepicker.css b/src/components/datepicker/bl-datepicker.css index a99cfee4..db493734 100644 --- a/src/components/datepicker/bl-datepicker.css +++ b/src/components/datepicker/bl-datepicker.css @@ -1,15 +1,14 @@ :host { width: fit-content; + display: block; +} +.datepicker-content { --bl-input-cursor: pointer; --icon-size: var(--line-height); --icon-color: var(--bl-color-neutral-light); --datepicker-width: 314px; - display: block; -} - -.datepicker-content { display: flex; flex-direction: column; gap: var(--bl-size-2xs); diff --git a/src/components/datepicker/bl-datepicker.ts b/src/components/datepicker/bl-datepicker.ts index 5b516c1f..5b38cc63 100644 --- a/src/components/datepicker/bl-datepicker.ts +++ b/src/components/datepicker/bl-datepicker.ts @@ -100,7 +100,10 @@ export default class BlDatepicker extends DatepickerCalendarMixin { } closePopoverWithTimeout() { - setTimeout(() => this.closePopover(), 200); + setTimeout(() => { + this.closePopover(); + this._inputEl.blur(); + }, 200); } setFloatingDates() { @@ -140,7 +143,6 @@ export default class BlDatepicker extends DatepickerCalendarMixin { this._selectedDates = []; this._inputValue = ""; this._floatingDateCount = 0; - this._inputEl?.blur(); } openPopover() { From 1fec4ae3f8f06bce1439cd6952baf22d67f9aa2c Mon Sep 17 00:00:00 2001 From: "dilan.dogan2" Date: Tue, 3 Dec 2024 15:04:40 +0300 Subject: [PATCH 34/35] feat(datepicker): add input size and input label type as prop to datepicker --- src/components/datepicker/bl-datepicker.ts | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/components/datepicker/bl-datepicker.ts b/src/components/datepicker/bl-datepicker.ts index 5b38cc63..dcfe562f 100644 --- a/src/components/datepicker/bl-datepicker.ts +++ b/src/components/datepicker/bl-datepicker.ts @@ -6,7 +6,7 @@ import { event, EventDispatcher } from "../../utilities/event"; import "../calendar/bl-calendar"; import { CALENDAR_TYPES } from "../calendar/bl-calendar.constant"; import "../input/bl-input"; -import BlInput from "../input/bl-input"; +import BlInput, { InputSize } from "../input/bl-input"; import "../tooltip/bl-tooltip"; import style from "./bl-datepicker.css"; @@ -26,6 +26,17 @@ export default class BlDatepicker extends DatepickerCalendarMixin { */ @property({ type: String, attribute: "placeholder", reflect: true }) placeholder: string; + /** + * Sets input size. + */ + @property({ type: String, reflect: true }) + inputSize?: InputSize = "medium"; + + /** + * Makes datepicker input label as fixed positioned + */ + @property({ type: Boolean, attribute: "input-label-fixed", reflect: true }) + inputLabelFixed = false; /** * Defines the datepicker input label */ @@ -34,7 +45,7 @@ export default class BlDatepicker extends DatepickerCalendarMixin { /** * Defines the custom formatter function */ - @property({ type: Function, attribute: "value-formatter" }) + @property({ type: Function, attribute: "input-value-formatter" }) inputValueFormatter: ((dates: Date[]) => string) | null = null; /** * Sets datepicker to disabled @@ -253,6 +264,8 @@ export default class BlDatepicker extends DatepickerCalendarMixin { @click=${this._togglePopover} help-text=${this.helpText} ?disabled=${this.disabled} + .size=${this.inputSize} + .labelFixed=${this.inputLabelFixed} readonly >
From 3b17af612b460a7dbcc86b80306d28e87f931b5e Mon Sep 17 00:00:00 2001 From: "dilan.dogan2" Date: Tue, 3 Dec 2024 15:48:08 +0300 Subject: [PATCH 35/35] feat(datepicker): change divider width --- src/components/datepicker/bl-datepicker.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/datepicker/bl-datepicker.css b/src/components/datepicker/bl-datepicker.css index db493734..d5a66ed1 100644 --- a/src/components/datepicker/bl-datepicker.css +++ b/src/components/datepicker/bl-datepicker.css @@ -42,7 +42,7 @@ .action-divider { display: block; height: var(--bl-size-m); - width: var(--bl-size-4xs); + width: 1px; background-color: var(--bl-color-neutral-lighter); margin-right: var(--bl-size-3xs); }