Skip to content

Commit

Permalink
fix: refactor to use interface based on feedback
Browse files Browse the repository at this point in the history
  • Loading branch information
didip1000 committed Apr 19, 2024
1 parent c57a09d commit ff9f105
Show file tree
Hide file tree
Showing 5 changed files with 82 additions and 69 deletions.
109 changes: 62 additions & 47 deletions packages/main/src/Calendar.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import customElement from "@ui5/webcomponents-base/dist/decorators/customElement.js";
import UI5Element from "@ui5/webcomponents-base/dist/UI5Element.js";
import type { ChangeInfo } from "@ui5/webcomponents-base/dist/UI5Element.js";
import event from "@ui5/webcomponents-base/dist/decorators/event.js";
import property from "@ui5/webcomponents-base/dist/decorators/property.js";
Expand All @@ -16,7 +17,6 @@ import getLocale from "@ui5/webcomponents-base/dist/locale/getLocale.js";
import DateFormat from "@ui5/webcomponents-localization/dist/DateFormat.js";
import UI5Date from "@ui5/webcomponents-localization/dist/dates/UI5Date.js";
import CalendarDate from "./CalendarDate.js";
import CalendarDateRange from "./CalendarDateRange.js";
import CalendarPart from "./CalendarPart.js";
import CalendarHeader from "./CalendarHeader.js";
import DayPicker from "./DayPicker.js";
Expand Down Expand Up @@ -51,6 +51,12 @@ interface ICalendarPicker {
_lastYear?: number,
}

interface ICalendarSelectedDates extends UI5Element {
value?: string,
startValue?: string,
endValue?: string
}

type CalendarSelectionChangeEventDetail = {
selectedValues: Array<string>,
selectedDates: Array<number>,
Expand Down Expand Up @@ -269,7 +275,7 @@ class Calendar extends CalendarPart {
* @public
*/
@slot({ type: HTMLElement, invalidateOnChildChange: true, "default": true })
dates!: Array<CalendarDate | CalendarDateRange>;
dates!: Array<ICalendarSelectedDates>;

/**
* Defines the special dates, visually emphasized in the calendar.
Expand All @@ -286,76 +292,84 @@ class Calendar extends CalendarPart {
@property({ type: CalendarLegendItemType, defaultValue: CalendarLegendItemType.None })
_selectedItemType!: `${CalendarLegendItemType}`;

constructor() {
super();

this._valueIsProcessed = false;
}

/**
* @private
*/
get _selectedDatesTimestamps(): Array<number> {
let isDateRange;
let dates = this.dates.map(date => {
if (date.hasAttribute("ui5-date-range") && this.selectionMode === CalendarSelectionMode.Range) {
isDateRange = true;
}
const value = date.value;
const validValue = value && !!this.getFormat().parse(value);
return validValue ? this._getTimeStampFromString(value)! / 1000 : undefined;
}).filter((date): date is number => !!date);

if (isDateRange) {
const dateRange = this.dates[0] as CalendarDateRange;
const firstDate = dateRange.value ? this.getFormat().parse(dateRange.value, true) as Date : new Date();
const lastDate = dateRange.endValue ? this.getFormat().parse(dateRange.endValue, true) as Date : new Date();
const date = new Date(firstDate);
dates = [];
let selectedDates = [];
const isSelectionModeRange = this._checkSelectionModeRange();

if (isSelectionModeRange) {
const dateRange = this.dates[0];
const startDate = dateRange.startValue ? this.getFormat().parse(dateRange.startValue, true) as Date : UI5Date.getInstance();
const endDate = dateRange.endValue ? this.getFormat().parse(dateRange.endValue, true) as Date : UI5Date.getInstance();
const date = UI5Date.getInstance(startDate);
do {
dates.push(date.getTime() / 1000);
selectedDates.push(date.getTime() / 1000);
date.setDate(date.getDate() + 1);
} while (date.getTime() <= lastDate.getTime());
} while (date.getTime() <= endDate.getTime());
} else {
selectedDates = this.dates.map(date => {
const value = date.value;
const validValue = value && !!this.getFormat().parse(value);
return validValue ? this._getTimeStampFromString(value)! / 1000 : undefined;
}).filter((date): date is number => !!date);
}

return dates;
}

constructor() {
super();

this._valueIsProcessed = false;
return selectedDates;
}

/**
* @private
*/
_setSelectedDates(selectedDates: Array<number>) {
const selectedValues = selectedDates.map(timestamp => this.getFormat().format(UI5Date.getInstance(timestamp * 1000), true)); // Format as UTC
let valuesInDOM = [...this.dates].map(dateElement => dateElement.value);
let isDateRange;
const selectedDatesUTC = selectedDates.map(timestamp => this.getFormat().format(UI5Date.getInstance(timestamp * 1000), true));
const isSelectionModeRange = this._checkSelectionModeRange();

if (this.dates.length && this.dates[0].hasAttribute("ui5-date-range") && this.selectionMode === CalendarSelectionMode.Range) {
isDateRange = true;
}
let valuesInDOM: string[];

if (isDateRange) {
const dateRange = this.dates[0] as CalendarDateRange;
const firstDate = dateRange.value ? this.getFormat().parse(dateRange.value) as Date : new Date();
const lastDate = dateRange.endValue ? this.getFormat().parse(dateRange.endValue) as Date : new Date();
const date = new Date(firstDate);
if (isSelectionModeRange) {
const dateRange = this.dates[0];
const startDate = dateRange.startValue ? this.getFormat().parse(dateRange.startValue) as Date : UI5Date.getInstance();
const endDate = dateRange.endValue ? this.getFormat().parse(dateRange.endValue) as Date : UI5Date.getInstance();
const date = UI5Date.getInstance(startDate);
valuesInDOM = [];
do {
valuesInDOM.push(this.getFormat().format(date));
date.setDate(date.getDate() + 1);
} while (date.getTime() <= lastDate.getTime());
} while (date.getTime() <= endDate.getTime());
} else {
valuesInDOM = [...this.dates].map(dateElement => dateElement.value!);
}

// Remove all elements for dates that are no longer selected
this.dates.filter(dateElement => !selectedValues.includes(dateElement.value)).forEach(dateElement => {
this.removeChild(dateElement);
});
this.dates
.filter(dateElement => !selectedDatesUTC.includes(isSelectionModeRange ? dateElement.startValue! : dateElement.value!))
.forEach(dateElement => {
this.removeChild(dateElement);
});

// Create tags for the selected dates that don't already exist in DOM
selectedValues.filter(value => !valuesInDOM.includes(value)).forEach(value => {
const dateElement = document.createElement(CalendarDate.getMetadata().getTag()) as CalendarDate;
dateElement.value = value;
this.appendChild(dateElement);
});
selectedDatesUTC
.filter(value => !valuesInDOM.includes(value))
.forEach(value => {
const dateElement = document.createElement(CalendarDate.getMetadata().getTag()) as CalendarDate;
dateElement.value = value;
this.appendChild(dateElement);
});
}

_checkSelectionModeRange(): boolean {
if (this.selectionMode === CalendarSelectionMode.Range) {
return this.dates.length > 0 && this.dates.every(date => date.hasAttribute("ui5-date-range"));
}
return false;
}

_isValidCalendarDate(dateString: string): boolean {
Expand Down Expand Up @@ -644,6 +658,7 @@ Calendar.define();
export default Calendar;
export type {
ICalendarPicker,
ICalendarSelectedDates,
CalendarSelectionChangeEventDetail,
SpecialCalendarDateT,
};
3 changes: 2 additions & 1 deletion packages/main/src/CalendarDate.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import customElement from "@ui5/webcomponents-base/dist/decorators/customElement.js";
import property from "@ui5/webcomponents-base/dist/decorators/property.js";
import UI5Element from "@ui5/webcomponents-base/dist/UI5Element.js";
import { ICalendarSelectedDates } from "./Calendar.js";

/**
* @class
Expand All @@ -14,7 +15,7 @@ import UI5Element from "@ui5/webcomponents-base/dist/UI5Element.js";
* @public
*/
@customElement("ui5-date")
class CalendarDate extends UI5Element {
class CalendarDate extends UI5Element implements ICalendarSelectedDates {
/**
* The date formatted according to the `formatPattern` property
* of the `ui5-calendar` that hosts the component.
Expand Down
5 changes: 3 additions & 2 deletions packages/main/src/CalendarDateRange.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import customElement from "@ui5/webcomponents-base/dist/decorators/customElement.js";
import property from "@ui5/webcomponents-base/dist/decorators/property.js";
import UI5Element from "@ui5/webcomponents-base/dist/UI5Element.js";
import { ICalendarSelectedDates } from "./Calendar.js";

/**
* @class
Expand All @@ -14,15 +15,15 @@ import UI5Element from "@ui5/webcomponents-base/dist/UI5Element.js";
* @public
*/
@customElement("ui5-date-range")
class CalendarDateRange extends UI5Element {
class CalendarDateRange extends UI5Element implements ICalendarSelectedDates {
/**
* Start of date range formatted according to the `formatPattern` property
* of the `ui5-calendar` that hosts the component.
* @default ""
* @public
*/
@property()
value!: string;
startValue!: string;

/**
* End of date range formatted according to the `formatPattern` property
Expand Down
4 changes: 2 additions & 2 deletions packages/main/test/pages/Calendar.html
Original file line number Diff line number Diff line change
Expand Up @@ -79,9 +79,9 @@
</section>

<section>
<ui5-title>Calendar with same primary and secondary calendar type</ui5-title>
<ui5-title>Calendar with Selection Mode = Range</ui5-title>
<ui5-calendar id="calendar7" primary-calendar-type='Gregorian' secondary-calendar-type='Gregorian' selection-mode="Range">
<ui5-date-range value="Jan 20, 2021" end-value="Jan 30, 2021"></ui5-date-range>
<ui5-date-range start-value="Jan 20, 2021" end-value="Jan 30, 2021"></ui5-date-range>
</ui5-calendar>
</section>

Expand Down
30 changes: 13 additions & 17 deletions packages/main/test/specs/Calendar.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -370,10 +370,7 @@ describe("Calendar general interaction", () => {
const yearButton = await calendar.shadow$("ui5-calendar-header").shadow$(`div[data-ui5-cal-header-btn-year]`);
await yearButton.click();

const year2025 = await calendar.shadow$("ui5-yearpicker").shadow$$(`div[role="gridcell"] span`).find(async span => {
const text = await span.getText();
return text === "2025";
}).parentElement();
const year2025 = await calendar.shadow$("ui5-yearpicker").shadow$(`div[data-sap-timestamp="1744934400"]`)

assert.strictEqual(await year2025.hasClass("ui5-yp-item--disabled"), true, "Year 2025 is disabled");
});
Expand All @@ -384,22 +381,21 @@ describe("Calendar general interaction", () => {
const calendar = await browser.$("#calendar1");
const yearButton = await calendar.shadow$("ui5-calendar-header").shadow$(`div[data-ui5-cal-header-btn-year]`);
// setting the min and max dates both to a valid format date, but not in the valid ISO format.
await calendar.setAttribute("max-date", `${new Date(Date.UTC(2024, 9, 4, 0, 0, 0))}`);

const newMaxDate = new Date(Date.UTC(2024, 9, 4, 0, 0, 0));
await calendar.setAttribute("max-date", `${newMaxDate}`);
await calendar.setAttribute("min-date", "25.10.2018");
console.log(await calendar.getAttribute("max-date"));

const maxDate = await calendar.getAttribute("max-date");

assert.strictEqual(maxDate, newMaxDate.toString(), "Max date was applied correctly")

await yearButton.click();
const year2016 = await calendar.shadow$("ui5-yearpicker").shadow$$(`div[role="gridcell"] span`).find(async span => {
const text = await span.getText();
return text === "2016";
}).parentElement();
const year2016 = await calendar.shadow$("ui5-yearpicker").shadow$(`div[data-sap-timestamp="1461024000"]`)

assert.strictEqual(await year2016.hasClass("ui5-yp-item--disabled"), false, "Year 2016 is not disabled");

const year2024 = await calendar.shadow$("ui5-yearpicker").shadow$$(`div[role="gridcell"] span`).find(async span => {
const text = await span.getText();
return text === "2024";
}).parentElement();
const year2024 = await calendar.shadow$("ui5-yearpicker").shadow$(`div[data-sap-timestamp="1713484800"]`)

assert.strictEqual(await year2024.hasClass("ui5-yp-item--disabled"), false, "Year 2024 is not disabled");
});
Expand All @@ -422,12 +418,12 @@ describe("Calendar general interaction", () => {
assert.strictEqual(await dayPickerRoot.hasClass("ui5-dp-twocalendartypes"), false, "Secondary Calendar class is applied correctly");
});

it("Focus goes into the selected day item of the day picker", async () => {
it("Focus goes into first selected day of the range selection", async () => {
await browser.url(`test/pages/Calendar.html`);

const calendar = await browser.$("#calendar4");
const calendar = await browser.$("#calendar7");
const dayPicker = await calendar.shadow$("ui5-daypicker");
const currentDayItem = await dayPicker.shadow$(`div[data-sap-timestamp="1594166400"]`);
const currentDayItem = await dayPicker.shadow$(`div[data-sap-timestamp="1611100800"]`);

assert.ok(await currentDayItem.isFocusedDeep(), "Current calendar day item is focused");
});
Expand Down

0 comments on commit ff9f105

Please sign in to comment.