diff --git a/packages/base/src/sap/ui/webcomponents/base/delegate/ItemNavigation.js b/packages/base/src/sap/ui/webcomponents/base/delegate/ItemNavigation.js index 3ee852c40255..ff4c639370fe 100644 --- a/packages/base/src/sap/ui/webcomponents/base/delegate/ItemNavigation.js +++ b/packages/base/src/sap/ui/webcomponents/base/delegate/ItemNavigation.js @@ -139,6 +139,10 @@ class ItemNavigation extends EventProvider { this.currentIndex -= this.rowSize; } + if (this.currentIndex < 0) { + this.currentIndex = 0; + } + const currentItem = items[this.currentIndex]; if (currentItem instanceof WebComponent) { diff --git a/packages/main/src/Calendar.js b/packages/main/src/Calendar.js index aef967e4329b..4e8f4b38f0f2 100644 --- a/packages/main/src/Calendar.js +++ b/packages/main/src/Calendar.js @@ -303,6 +303,10 @@ class Calendar extends WebComponent { nextMonth.setDate(1); nextMonth.setMonth(nextMonth.getMonth() + 1); + if (nextMonth.getYear() > YearPicker._MAX_YEAR) { + return; + } + this._focusFirstDayOfMonth(nextMonth); this.timestamp = nextMonth.valueOf() / 1000; } @@ -333,14 +337,16 @@ class Calendar extends WebComponent { const weekDaysCount = 7; - // find the DOM for the last day index - const lastDay = dayPicker.shadowRoot.querySelector(".sapWCDayPickerItemsContainer").children[parseInt(lastDayOfMonthIndex / weekDaysCount)].children[(lastDayOfMonthIndex % weekDaysCount)]; + if (lastDayOfMonthIndex !== -1) { + // find the DOM for the last day index + const lastDay = dayPicker.shadowRoot.querySelector(".sapWCDayPickerItemsContainer").children[parseInt(lastDayOfMonthIndex / weekDaysCount)].children[(lastDayOfMonthIndex % weekDaysCount)]; - // update current item in ItemNavigation - dayPicker._itemNav.current = lastDayOfMonthIndex; + // update current item in ItemNavigation + dayPicker._itemNav.current = lastDayOfMonthIndex; - // focus the item - lastDay.focus(); + // focus the item + lastDay.focus(); + } if (iNewMonth > 11) { iNewMonth = 0; @@ -356,10 +362,18 @@ class Calendar extends WebComponent { oNewDate.setYear(iNewYear); oNewDate.setMonth(iNewMonth); + + if (oNewDate.getYear() < YearPicker._MIN_YEAR) { + return; + } this.timestamp = oNewDate.valueOf() / 1000; } _showNextYear() { + if (this._calendarDate.getYear() === YearPicker._MAX_YEAR) { + return; + } + const oNewDate = this._calendarDate; oNewDate.setYear(this._calendarDate.getYear() + 1); @@ -367,6 +381,10 @@ class Calendar extends WebComponent { } _showPrevYear() { + if (this._calendarDate.getYear() === YearPicker._MIN_YEAR) { + return; + } + const oNewDate = this._calendarDate; oNewDate.setYear(this._calendarDate.getYear() - 1); @@ -374,18 +392,30 @@ class Calendar extends WebComponent { } _showNextPageYears() { + if (!this._isYearInRange(this._yearPicker.timestamp, + YearPicker._ITEMS_COUNT - YearPicker._MIDDLE_ITEM_INDEX, + YearPicker._MIN_YEAR, + YearPicker._MAX_YEAR)) { + return; + } + this._yearPicker = Object.assign({}, this._yearPicker, { - // add 20 years to the timestamp of the monthpicker - timestamp: this._yearPicker.timestamp + (31536000 * 20), + timestamp: this._yearPicker.timestamp + (31536000 * YearPicker._ITEMS_COUNT), }); this._isShiftingYears = true; } _showPrevPageYears() { + if (!this._isYearInRange(this._yearPicker.timestamp, + -YearPicker._MIDDLE_ITEM_INDEX - 1, + YearPicker._MIN_YEAR, + YearPicker._MAX_YEAR)) { + return; + } + this._yearPicker = Object.assign({}, this._yearPicker, { - // subtracts 20 years from the timestamp of the monthpicker - timestamp: this._yearPicker.timestamp - (31536000 * 20), + timestamp: this._yearPicker.timestamp - (31536000 * YearPicker._ITEMS_COUNT), }); this._isShiftingYears = true; @@ -436,6 +466,16 @@ class Calendar extends WebComponent { this._oMonth._hidden = false; } + _isYearInRange(timestamp, yearsoffset, min, max) { + if (timestamp) { + const oCalDate = CalendarDate.fromTimestamp(timestamp * 1000, this._primaryCalendarType); + oCalDate.setMonth(0); + oCalDate.setDate(1); + oCalDate.setYear(oCalDate.getYear() + yearsoffset); + return oCalDate.getYear() >= min && oCalDate.getYear() <= max; + } + } + static get calculateTemplateContext() { return CalendarTemplateContext.calculate; } diff --git a/packages/main/src/DatePicker.js b/packages/main/src/DatePicker.js index b06b8bf02d5e..bac1e7aa5be4 100644 --- a/packages/main/src/DatePicker.js +++ b/packages/main/src/DatePicker.js @@ -464,6 +464,11 @@ class DatePicker extends WebComponent { } _changeCalendarSelection() { + if (this._calendarDate.getYear() < 1) { + // 0 is a valid year, but we cannot display it + return; + } + const oCalDate = this._calendarDate; const timestamp = oCalDate.valueOf() / 1000; diff --git a/packages/main/src/DayPicker.hbs b/packages/main/src/DayPicker.hbs index 6f46845d06b9..85620c1b3d9b 100644 --- a/packages/main/src/DayPicker.hbs +++ b/packages/main/src/DayPicker.hbs @@ -22,25 +22,29 @@
{{#each ctr._weeks}} -
- {{#each this}} -
- - {{this.iDay}} - -
- {{/each}} -
+ {{#if this.length}} +
+ {{#each this}} +
+ + {{this.iDay}} + +
+ {{/each}} +
+ {{else}} +
+ {{/if}} {{/each}}
diff --git a/packages/main/src/DayPicker.js b/packages/main/src/DayPicker.js index 4a956c234388..f195ea98af54 100644 --- a/packages/main/src/DayPicker.js +++ b/packages/main/src/DayPicker.js @@ -94,6 +94,9 @@ const metadata = { }, }; +const MAX_YEAR = 9999; +const MIN_YEAR = 1; + /** * @class * @@ -164,7 +167,7 @@ class DayPicker extends WebComponent { return d === timestamp; }), iDay: oCalDate.getDate(), - _index: i, + _index: i.toString(), classes: `sapWCDayPickerItem sapWCDayPickerWDay${weekday}`, }; @@ -206,11 +209,16 @@ class DayPicker extends WebComponent { day.classes += " sapWCDayPickerItemWeekEnd"; } - if (day.classes.indexOf("sapWCDayPickerWDay6") !== -1) { + if (day.classes.indexOf("sapWCDayPickerWDay6") !== -1 + || _aVisibleDays.length - 1 === i) { this._weeks.push(week); week = []; } } + + while (this._weeks.length < 6) { + this._weeks.push([]); + } /* eslint-enable no-loop-func */ if (!isDaySelected && todayIndex && this._itemNav.current === 0) { @@ -339,6 +347,10 @@ class DayPicker extends WebComponent { oNewDate.setYear(iNewYear); oNewDate.setMonth(iNewMonth); + if (oNewDate.getYear() < MIN_YEAR || oNewDate.getYear() > MAX_YEAR) { + return; + } + this.fireEvent("navigate", { timestamp: (oNewDate.valueOf() / 1000) }); } @@ -382,12 +394,12 @@ class DayPicker extends WebComponent { for (let i = 0; i < 42; i++) { iYear = oDay.getYear(); oCalDate = new CalendarDate(oDay, this._primaryCalendarType); - if (bIncludeBCDates && iYear < 1) { + if (bIncludeBCDates && iYear < MIN_YEAR) { // For dates before 0001-01-01 we should render only empty squares to keep // the month square matrix correct. oCalDate._bBeforeFirstYear = true; _aVisibleDays.push(oCalDate); - } else if (iYear > 0 && iYear < 10000) { + } else if (iYear >= MIN_YEAR && iYear <= MAX_YEAR) { // Days before 0001-01-01 or after 9999-12-31 should not be rendered. _aVisibleDays.push(oCalDate); } diff --git a/packages/main/src/YearPicker.js b/packages/main/src/YearPicker.js index 40cab539b06b..a9078753bbf8 100644 --- a/packages/main/src/YearPicker.js +++ b/packages/main/src/YearPicker.js @@ -65,9 +65,6 @@ const metadata = { }, }; -const ITEMS_COUNT = 20; -const MIDDLE_ITEM_INDEX = 7; - /** * @class * @@ -117,7 +114,14 @@ class YearPicker extends WebComponent { const oCalDate = this._calendarDate; oCalDate.setMonth(0); oCalDate.setDate(1); - oCalDate.setYear(oCalDate.getYear() - MIDDLE_ITEM_INDEX - 1); + if (oCalDate.getYear() - YearPicker._MIDDLE_ITEM_INDEX - 1 > YearPicker._MAX_YEAR - YearPicker._ITEMS_COUNT) { + oCalDate.setYear(YearPicker._MAX_YEAR - YearPicker._ITEMS_COUNT); + } else if (oCalDate.getYear() - YearPicker._MIDDLE_ITEM_INDEX - 1 < YearPicker._MIN_YEAR) { + oCalDate.setYear(YearPicker._MIN_YEAR - 1); + } else { + oCalDate.setYear(oCalDate.getYear() - YearPicker._MIDDLE_ITEM_INDEX - 1); + } + const intervals = []; let timestamp; @@ -125,8 +129,14 @@ class YearPicker extends WebComponent { this._selectedYear = this._year; } - for (let i = 0; i < ITEMS_COUNT; i++) { + for (let i = 0; i < YearPicker._ITEMS_COUNT; i++) { + const intervalIndex = parseInt(i / 4); + if (!intervals[intervalIndex]) { + intervals[intervalIndex] = []; + } + oCalDate.setYear(oCalDate.getYear() + 1); + timestamp = oCalDate.valueOf() / 1000; const year = { @@ -140,12 +150,8 @@ class YearPicker extends WebComponent { year.classes += " sapWCYearPickerItemSel"; } - const intervalIndex = parseInt(i / 4); - if (intervals[intervalIndex]) { intervals[intervalIndex].push(year); - } else { - intervals[intervalIndex] = [year]; } } @@ -183,7 +189,7 @@ class YearPicker extends WebComponent { const timestamp = this.getTimestampFromDom(event.ui5target); this.timestamp = timestamp; this._selectedYear = this._year; - this._itemNav.current = MIDDLE_ITEM_INDEX; + this._itemNav.current = YearPicker._MIDDLE_ITEM_INDEX; this.fireEvent("selectedYearChange", { timestamp }); } } @@ -204,7 +210,7 @@ class YearPicker extends WebComponent { this.timestamp = timestamp; this._selectedYear = this._year; - this._itemNav.current = MIDDLE_ITEM_INDEX; + this._itemNav.current = YearPicker._MIDDLE_ITEM_INDEX; this.fireEvent("selectedYearChange", { timestamp }); } } @@ -227,15 +233,27 @@ class YearPicker extends WebComponent { oCalDate.setDate(1); if (event.end) { - oCalDate.setYear(oCalDate.getYear() + ITEMS_COUNT); + oCalDate.setYear(oCalDate.getYear() + YearPicker._ITEMS_COUNT); } else if (event.start) { - oCalDate.setYear(oCalDate.getYear() - ITEMS_COUNT); + if (oCalDate.getYear() - YearPicker._MIDDLE_ITEM_INDEX < YearPicker._MIN_YEAR) { + return; + } + oCalDate.setYear(oCalDate.getYear() - YearPicker._ITEMS_COUNT); + } + + if (oCalDate.getYear() - YearPicker._MIDDLE_ITEM_INDEX > YearPicker._MAX_YEAR) { + return; } this.timestamp = oCalDate.valueOf() / 1000; } } +YearPicker._ITEMS_COUNT = 20; +YearPicker._MIDDLE_ITEM_INDEX = 7; +YearPicker._MAX_YEAR = 9999; +YearPicker._MIN_YEAR = 1; + Bootstrap.boot().then(_ => { YearPicker.define(); }); diff --git a/packages/main/src/themes/base/DayPicker.less b/packages/main/src/themes/base/DayPicker.less index 36ea8fef0638..d197bdc6a3ad 100644 --- a/packages/main/src/themes/base/DayPicker.less +++ b/packages/main/src/themes/base/DayPicker.less @@ -165,6 +165,13 @@ ui5-daypicker { .sapWCDayPickerItemsContainer { outline: none; + & > :first-child { + justify-content: flex-end; + } +} + +.sapWCEmptyWeek { + height: 3rem; } .sapUiSizeCompact { @@ -182,4 +189,8 @@ ui5-daypicker { margin-top: 2px; margin-right: 2px; } + + & .sapWCEmptyWeek { + height: 2.125rem; + } } diff --git a/packages/main/test/pageobjects/DatePickerTestPage.js b/packages/main/test/pageobjects/DatePickerTestPage.js index 17ad9fbb6c15..b571775b043f 100644 --- a/packages/main/test/pageobjects/DatePickerTestPage.js +++ b/packages/main/test/pageobjects/DatePickerTestPage.js @@ -45,10 +45,22 @@ class DatePickerTestPage { return browser.findElementDeep(this._sut + " >>> ui5-icon"); } - get btnNextMonth() { + get btnPrev() { + return browser.findElementDeep(this._sut + " >>> ui5-calendar >>> ui5-calendar-header >>> [data-sap-cal-head-button='Prev']"); + } + + get btnNext() { return browser.findElementDeep(this._sut + " >>> ui5-calendar >>> ui5-calendar-header >>> [data-sap-cal-head-button='Next']"); } + get btnYear() { + return browser.findElementDeep(this._sut + " >>> ui5-calendar >>> ui5-calendar-header >>> [data-sap-show-picker='Year']"); + } + + get btnMonth() { + return browser.findElementDeep(this._sut + " >>> ui5-calendar >>> ui5-calendar-header >>> [data-sap-show-picker='Month']"); + } + getPickerDate(timestamp) { return browser.findElementDeep(`${this._sut} >>> ui5-calendar >>> ui5-daypicker >>> [data-sap-timestamp='${timestamp}']`); } @@ -57,6 +69,16 @@ class DatePickerTestPage { return browser.findElementDeep(`${this._sut} >>> ui5-calendar >>> ui5-daypicker >>> .sapWCDayPickerItem`); } + getFirstDisplayedYear() { + return browser.findElementDeep(`${this._sut} >>> ui5-calendar >>> ui5-yearpicker >>> .sapWCYearPickerItem`); + } + + getDisplayedYear(index) { + return browser + .findElementDeep(`${this._sut} >>> ui5-calendar >>> ui5-yearpicker >>> .sapWCYearPicker`) + .$$(".sapWCYearPickerItem")[index]; + } + isValid(value) { return browser.execute((id, value) => { return document.querySelector(id).isValid(value); diff --git a/packages/main/test/sap/ui/webcomponents/main/pages/DatePicker_test_page.html b/packages/main/test/sap/ui/webcomponents/main/pages/DatePicker_test_page.html index 590f875d1270..7722fb2da0f5 100644 --- a/packages/main/test/sap/ui/webcomponents/main/pages/DatePicker_test_page.html +++ b/packages/main/test/sap/ui/webcomponents/main/pages/DatePicker_test_page.html @@ -31,6 +31,7 @@ + diff --git a/packages/main/test/specs/DatePicker.spec.js b/packages/main/test/specs/DatePicker.spec.js index ac24ce0aec7e..f0d06658ebd3 100644 --- a/packages/main/test/specs/DatePicker.spec.js +++ b/packages/main/test/specs/DatePicker.spec.js @@ -200,7 +200,7 @@ describe("Date Picker Tests", () => { datepicker.innerInput.setValue("Jan 30, 2019"); datepicker.valueHelpIcon.click(); - datepicker.btnNextMonth.click(); + datepicker.btnNext.click(); const firstDisplayedDate = datepicker.getFirstDisplayedDate(); @@ -254,7 +254,6 @@ describe("Date Picker Tests", () => { }); it("does not open, if disabled", () => { - var canClick = true; datepicker.id = "#dp10"; assert.ok(!datepicker.isPickerOpen(), "picker is closed initially."); @@ -306,4 +305,198 @@ describe("Date Picker Tests", () => { browser.pause(1000); assert.ok(datepicker.isPickerOpen(), "picker is open"); }); + + it("daypicker extreme values max", () => { + var _28Nov9999 = "253399363200"; + + datepicker.open(); + datepicker.id = "#dp12"; + + datepicker.innerInput.setValue("Dec 31, 9999"); + datepicker.valueHelpIcon.click(); + + assert.ok(datepicker.getFirstDisplayedDate().getProperty("id").indexOf(_28Nov9999) > -1, "28 Nov, 9999 is the first displayed date"); + }); + + it("daypicker extreme values min", () => { + var _1Jan0001 = "-62135596800"; + + datepicker.open(); + datepicker.id = "#dp12"; + + datepicker.innerInput.setValue("Jan 1, 0001"); + datepicker.valueHelpIcon.click(); + + assert.ok(datepicker.getFirstDisplayedDate().getProperty("id").indexOf(_1Jan0001) > -1, "Jan 1, 0001 is the first displayed date"); + }); + + it("daypicker prev extreme values min", () => { + var _1Jan0001 = "-62135596800"; + + datepicker.open(); + datepicker.id = "#dp12"; + + datepicker.innerInput.setValue("Feb 1, 0001"); + datepicker.valueHelpIcon.click(); + + datepicker.btnPrev.click(); + + assert.ok(datepicker.getFirstDisplayedDate().getProperty("id").indexOf(_1Jan0001) > -1, "Jan 1, 0001 is the first displayed date"); + + datepicker.btnPrev.click(); + + assert.ok(datepicker.getFirstDisplayedDate().getProperty("id").indexOf(_1Jan0001) > -1, "Jan 1, 0001 is the first displayed date"); + }); + + it("daypicker next extreme values max", () => { + var _28Nov9999 = "253399363200"; + + datepicker.open(); + datepicker.id = "#dp12"; + + datepicker.innerInput.setValue("Nov 31, 9999"); + datepicker.valueHelpIcon.click(); + + datepicker.btnNext.click(); + + assert.ok(datepicker.getFirstDisplayedDate().getProperty("id").indexOf(_28Nov9999) > -1, "28 Nov, 9999 is the first displayed date"); + + datepicker.btnNext.click(); + + assert.ok(datepicker.getFirstDisplayedDate().getProperty("id").indexOf(_28Nov9999) > -1, "28 Nov, 9999 is the first displayed date"); + }); + + it("monthpicker next extreme values max", () => { + datepicker.open(); + datepicker.id = "#dp12"; + + datepicker.innerInput.setValue("Dec 31, 9998"); + datepicker.valueHelpIcon.click(); + + datepicker.btnMonth.click(); + datepicker.btnNext.click(); + + assert.ok(datepicker.btnYear.getProperty("innerHTML").indexOf("9999") > -1, "year button's text is correct"); + + datepicker.btnNext.click(); + + assert.ok(datepicker.btnYear.getProperty("innerHTML").indexOf("9999") > -1, "year button's text is correct"); + }); + + it("monthpicker prev extreme values min", () => { + datepicker.open(); + datepicker.id = "#dp12"; + + datepicker.innerInput.setValue("Jan 1, 0002"); + datepicker.valueHelpIcon.click(); + + datepicker.btnMonth.click(); + datepicker.btnPrev.click(); + + assert.ok(datepicker.btnYear.getProperty("innerHTML").indexOf("0001") > -1, "year button's text is correct"); + + datepicker.btnPrev.click(); + + assert.ok(datepicker.btnYear.getProperty("innerHTML").indexOf("0001") > -1, "year button's text is correct"); + }); + + it("yearpicker extreme values max", () => { + datepicker.open(); + datepicker.id = "#dp12"; + + datepicker.innerInput.setValue("Dec 31, 9995"); + datepicker.valueHelpIcon.click(); + + datepicker.btnYear.click(); + + assert.ok(datepicker.getFirstDisplayedYear().getProperty("innerHTML").indexOf("9980") > -1, "First year in the year picker is correct"); + }); + + it("yearpicker extreme values min", () => { + datepicker.open(); + datepicker.id = "#dp12"; + + datepicker.innerInput.setValue("Jan 1, 0003"); + datepicker.valueHelpIcon.click(); + + datepicker.btnYear.click(); + + assert.ok(datepicker.getFirstDisplayedYear().getProperty("innerHTML").indexOf("0001") > -1, "First year in the year picker is correct"); + }); + + it("yearpicker prev page extreme values min", () => { + datepicker.open(); + datepicker.id = "#dp12"; + + datepicker.innerInput.setValue("Jan 1, 0009"); + datepicker.valueHelpIcon.click(); + + datepicker.btnYear.click(); + + assert.ok(datepicker.getFirstDisplayedYear().getProperty("innerHTML").indexOf("0002") > -1, "First year in the year picker is correct"); + + datepicker.btnPrev.click(); + + assert.ok(datepicker.getFirstDisplayedYear().getProperty("innerHTML").indexOf("0001") > -1, "First year in the year picker is correct"); + + datepicker.btnPrev.click(); + + assert.ok(datepicker.getFirstDisplayedYear().getProperty("innerHTML").indexOf("0001") > -1, "First year in the year picker is correct"); + }); + + it("yearpicker next page extreme values max", () => { + datepicker.open(); + datepicker.id = "#dp12"; + + datepicker.innerInput.setValue("Dec 31, 9986"); + datepicker.valueHelpIcon.click(); + + datepicker.btnYear.click(); + + assert.ok(datepicker.getFirstDisplayedYear().getProperty("innerHTML").indexOf("9979") > -1, "First year in the year picker is correct"); + + datepicker.btnNext.click(); + + assert.ok(datepicker.getFirstDisplayedYear().getProperty("innerHTML").indexOf("9980") > -1, "First year in the year picker is correct"); + + datepicker.btnNext.click(); + + assert.ok(datepicker.getFirstDisplayedYear().getProperty("innerHTML").indexOf("9980") > -1, "First year in the year picker is correct"); + }); + + it("yearpicker click extreme values max", () => { + datepicker.open(); + datepicker.id = "#dp12"; + + datepicker.innerInput.setValue("Dec 31, 9986"); + datepicker.valueHelpIcon.click(); + + datepicker.btnYear.click(); + + var tenthYear = datepicker.getDisplayedYear(9); + assert.ok(tenthYear.getProperty("innerHTML").indexOf("9988") > -1, "Tenth year in the year picker is correct"); + + tenthYear.click(); + datepicker.btnYear.click(); + + assert.ok(datepicker.getFirstDisplayedYear().getProperty("innerHTML").indexOf("9980") > -1, "First year in the year picker is correct"); + }); + + it("yearpicker click extreme values min", () => { + datepicker.open(); + datepicker.id = "#dp12"; + + datepicker.innerInput.setValue("Jan 1, 0009"); + datepicker.valueHelpIcon.click(); + + datepicker.btnYear.click(); + + var thirdYear = datepicker.getDisplayedYear(2); + assert.ok(thirdYear.getProperty("innerHTML").indexOf("0004") > -1, "Third year in the year picker is correct"); + + thirdYear.click(); + datepicker.btnYear.click(); + + assert.ok(datepicker.getFirstDisplayedYear().getProperty("innerHTML").indexOf("0001") > -1, "First year in the year picker is correct"); + }); });