Skip to content

Commit 6602fba

Browse files
authored
feat(ui5-calendar): Declarative dates support added (#2648)
1 parent b4f836a commit 6602fba

13 files changed

+240
-50
lines changed

packages/main/src/Calendar.hbs

+3-3
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
id="{{_id}}-daypicker"
88
?hidden="{{_isDayPickerHidden}}"
99
format-pattern="{{_formatPattern}}"
10-
.selectedDates="{{selectedDates}}"
10+
.selectedDates="{{_selectedDatesTimestamps}}"
1111
._hidden="{{_isDayPickerHidden}}"
1212
.primaryCalendarType="{{_primaryCalendarType}}"
1313
.selectionMode="{{selectionMode}}"
@@ -23,7 +23,7 @@
2323
id="{{_id}}-MP"
2424
?hidden="{{_isMonthPickerHidden}}"
2525
format-pattern="{{_formatPattern}}"
26-
.selectedDates="{{selectedDates}}"
26+
.selectedDates="{{_selectedDatesTimestamps}}"
2727
._hidden="{{_isMonthPickerHidden}}"
2828
.primaryCalendarType="{{_primaryCalendarType}}"
2929
.minDate="{{minDate}}"
@@ -37,7 +37,7 @@
3737
id="{{_id}}-YP"
3838
?hidden="{{_isYearPickerHidden}}"
3939
format-pattern="{{_formatPattern}}"
40-
.selectedDates="{{selectedDates}}"
40+
.selectedDates="{{_selectedDatesTimestamps}}"
4141
._hidden="{{_isYearPickerHidden}}"
4242
.primaryCalendarType="{{_primaryCalendarType}}"
4343
.minDate="{{minDate}}"

packages/main/src/Calendar.js

+92-6
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
1+
import CalendarDate from "@ui5/webcomponents-localization/dist/dates/CalendarDate.js";
12
import RenderScheduler from "@ui5/webcomponents-base/dist/RenderScheduler.js";
23
import {
34
isF4,
45
isF4Shift,
56
} from "@ui5/webcomponents-base/dist/Keys.js";
7+
import * as CalendarDateComponent from "./CalendarDate.js";
68
import CalendarPart from "./CalendarPart.js";
79
import CalendarHeader from "./CalendarHeader.js";
810
import DayPicker from "./DayPicker.js";
@@ -73,16 +75,36 @@ const metadata = {
7375
type: Boolean,
7476
},
7577
},
78+
managedSlots: true,
79+
slots: /** @lends sap.ui.webcomponents.main.Calendar.prototype */ {
80+
/**
81+
* Defines the selected date or dates (depending on the <code>selectionMode</code> property) for this calendar as instances of <code>ui5-date</code>
82+
*
83+
* @type {HTMLElement[]}
84+
* @slot
85+
* @public
86+
*/
87+
"default": {
88+
propertyName: "dates",
89+
type: HTMLElement,
90+
invalidateOnChildChange: true,
91+
},
92+
},
7693
events: /** @lends sap.ui.webcomponents.main.Calendar.prototype */ {
7794
/**
78-
* Fired when the selected dates changed.
95+
* Fired when the selected dates change.
96+
* <b>Note:</b> If you call <code>preventDefault()</code> for this event, <code>ui5-calendar</code> will not
97+
* create instances of <code>ui5-date</code> for the newly selected dates. In that case you should do this manually.
98+
*
7999
* @event sap.ui.webcomponents.main.Calendar#selected-dates-change
80-
* @param {Array} dates The selected dates timestamps
100+
* @param {Array} values The selected dates
101+
* @param {Array} dates The selected dates as UTC timestamps
81102
* @public
82103
*/
83104
"selected-dates-change": {
84105
detail: {
85106
dates: { type: Array },
107+
values: { type: Array },
86108
},
87109
},
88110
},
@@ -93,7 +115,14 @@ const metadata = {
93115
*
94116
* <h3 class="comment-api-title">Overview</h3>
95117
*
96-
* The <code>ui5-calendar</code> can be used stand alone to display the years, months, weeks and days
118+
* The <code>ui5-calendar</code> component allows users to select one or more dates.
119+
* <br><br>
120+
* Currently selected dates are represented with instances of <code>ui5-date</code> as
121+
* children of the <code>ui5-calendar</code>. The value property of each <code>ui5-date</code> must be a
122+
* date string, correctly formatted according to the <code>ui5-calendar</code>'s <code>formatPattern</code> property.
123+
* Whenever the user changes the date selection, <code>ui5-calendar</code> will automatically create/remove instances
124+
* of <code>ui5-date</code> in itself, unless you prevent this behavior by calling <code>preventDefault()</code> for the
125+
* <code>selected-dates-change</code> event. This is useful if you want to control the selected dates externally.
97126
* <br><br>
98127
*
99128
* <h3>Usage</h3>
@@ -105,7 +134,7 @@ const metadata = {
105134
* <li>Pressing over an year inside the years view</li>
106135
* </ul>
107136
* <br>
108-
* The user can comfirm a date selection by pressing over a date inside the days view.
137+
* The user can confirm a date selection by pressing over a date inside the days view.
109138
* <br><br>
110139
*
111140
* <h3>Keyboard Handling</h3>
@@ -159,6 +188,7 @@ const metadata = {
159188
* @alias sap.ui.webcomponents.main.Calendar
160189
* @extends CalendarPart
161190
* @tagname ui5-calendar
191+
* @appenddocs CalendarDate
162192
* @public
163193
* @since 1.0.0-rc.11
164194
*/
@@ -175,6 +205,36 @@ class Calendar extends CalendarPart {
175205
return calendarCSS;
176206
}
177207

208+
/**
209+
* @private
210+
*/
211+
get _selectedDatesTimestamps() {
212+
return this.dates.map(date => {
213+
const value = date.value;
214+
return value && !!this.getFormat().parse(value) ? this._getTimeStampFromString(value) / 1000 : undefined;
215+
}).filter(date => !!date);
216+
}
217+
218+
/**
219+
* @private
220+
*/
221+
_setSelectedDates(selectedDates) {
222+
const selectedValues = selectedDates.map(timestamp => this.getFormat().format(new Date(timestamp * 1000), true)); // Format as UTC
223+
const valuesInDOM = [...this.dates].map(dateElement => dateElement.value);
224+
225+
// Remove all elements for dates that are no longer selected
226+
this.dates.filter(dateElement => !selectedValues.includes(dateElement.value)).forEach(dateElement => {
227+
this.removeChild(dateElement);
228+
});
229+
230+
// Create tags for the selected dates that don't already exist in DOM
231+
selectedValues.filter(value => !valuesInDOM.includes(value)).forEach(value => {
232+
const dateElement = document.createElement("ui5-date");
233+
dateElement.value = value;
234+
this.appendChild(dateElement);
235+
});
236+
}
237+
178238
async onAfterRendering() {
179239
await RenderScheduler.whenFinished(); // Await for the current picker to render and then ask if it has previous/next pages
180240
this._previousButtonDisabled = !this._currentPickerDOM._hasPreviousPage();
@@ -237,10 +297,16 @@ class Calendar extends CalendarPart {
237297
onSelectedDatesChange(event) {
238298
const timestamp = event.detail.timestamp;
239299
const selectedDates = event.detail.dates;
300+
const datesValues = selectedDates.map(ts => {
301+
const calendarDate = CalendarDate.fromTimestamp(ts * 1000, this._primaryCalendarType);
302+
return this.getFormat().format(calendarDate.toUTCJSDate(), true);
303+
});
240304

241305
this.timestamp = timestamp;
242-
this.selectedDates = selectedDates;
243-
this.fireEvent("selected-dates-change", { timestamp, dates: [...selectedDates] });
306+
const defaultPrevented = !this.fireEvent("selected-dates-change", { timestamp, dates: [...selectedDates], values: datesValues }, true);
307+
if (!defaultPrevented) {
308+
this._setSelectedDates(selectedDates);
309+
}
244310
}
245311

246312
onSelectedMonthChange(event) {
@@ -267,8 +333,28 @@ class Calendar extends CalendarPart {
267333
}
268334
}
269335

336+
/**
337+
* Returns an array of UTC timestamps, representing the selected dates.
338+
* @protected
339+
* @deprecated
340+
*/
341+
get selectedDates() {
342+
return this._selectedDatesTimestamps;
343+
}
344+
345+
/**
346+
* Creates instances of <code>ui5-date</code> inside this <code>ui5-calendar</code> with values, equal to the provided UTC timestamps
347+
* @protected
348+
* @deprecated
349+
* @param selectedDates Array of UTC timestamps
350+
*/
351+
set selectedDates(selectedDates) {
352+
this._setSelectedDates(selectedDates);
353+
}
354+
270355
static get dependencies() {
271356
return [
357+
CalendarDateComponent.default,
272358
CalendarHeader,
273359
DayPicker,
274360
MonthPicker,

packages/main/src/CalendarDate.js

+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import UI5Element from "@ui5/webcomponents-base/dist/UI5Element.js";
2+
3+
/**
4+
* @public
5+
*/
6+
const metadata = {
7+
tag: "ui5-date",
8+
properties: /** @lends sap.ui.webcomponents.main.CalendarDate.prototype */ {
9+
10+
/**
11+
* The date formatted according to the <code>formatPattern</code> property of the <code>ui5-calendar</code> that hosts the <code>ui5-date</code>
12+
*
13+
* @type {string}
14+
* @public
15+
*/
16+
value: {
17+
type: String,
18+
},
19+
},
20+
};
21+
22+
/**
23+
* @class
24+
*
25+
* <h3 class="comment-api-title">Overview</h3>
26+
*
27+
* The <code>ui5-date</code> component defines a calendar date to be used inside <code>ui5-calendar</code>
28+
*
29+
* @constructor
30+
* @author SAP SE
31+
* @alias sap.ui.webcomponents.main.CalendarDate
32+
* @extends sap.ui.webcomponents.base.UI5Element
33+
* @tagname ui5-date
34+
* @public
35+
*/
36+
class CalendarDate extends UI5Element {
37+
static get metadata() {
38+
return metadata;
39+
}
40+
}
41+
42+
CalendarDate.define();
43+
44+
export default CalendarDate;

packages/main/src/CalendarPart.js

+1-12
Original file line numberDiff line numberDiff line change
@@ -13,22 +13,11 @@ const metadata = {
1313
* The timestamp of the currently focused date. Set this property to move the component's focus to a certain date.
1414
* <b>Node:</b> Timestamp is 10-digit Integer representing the seconds (not milliseconds) since the Unix Epoch.
1515
* @type {Integer}
16-
* @public
16+
* @protected
1717
*/
1818
timestamp: {
1919
type: Integer,
2020
},
21-
22-
/**
23-
* An array of UTC timestamps representing the selected date or dates depending on the capabilities of the picker component.
24-
* @type {Array}
25-
* @public
26-
*/
27-
selectedDates: {
28-
type: Integer,
29-
multiple: true,
30-
compareValues: true,
31-
},
3221
},
3322
};
3423

packages/main/src/DatePicker.js

+6-9
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import Icon from "./Icon.js";
2323
import Button from "./Button.js";
2424
import ResponsivePopover from "./ResponsivePopover.js";
2525
import Calendar from "./Calendar.js";
26+
import * as CalendarDateComponent from "./CalendarDate.js";
2627
import Input from "./Input.js";
2728
import InputType from "./types/InputType.js";
2829
import DatePickerTemplate from "./generated/templates/DatePickerTemplate.lit.js";
@@ -388,12 +389,8 @@ class DatePicker extends DateComponentBase {
388389
* @protected
389390
*/
390391
get _calendarSelectedDates() {
391-
if (!this.value) {
392-
return [];
393-
}
394-
395-
if (this._checkValueValidity(this.value)) {
396-
return [getRoundedTimestamp(this.dateValueUTC.getTime())];
392+
if (this.value && this._checkValueValidity(this.value)) {
393+
return [this.value];
397394
}
398395

399396
return [];
@@ -640,9 +637,8 @@ class DatePicker extends DateComponentBase {
640637
* @protected
641638
*/
642639
onSelectedDatesChange(event) {
643-
const timestamp = event.detail.dates && event.detail.dates[0];
644-
const calendarDate = CalendarDate.fromTimestamp(timestamp * 1000, this._primaryCalendarType);
645-
const newValue = this.getFormat().format(calendarDate.toUTCJSDate(), true);
640+
event.preventDefault();
641+
const newValue = event.detail.values && event.detail.values[0];
646642
this._updateValueAndFireEvents(newValue, true, ["change", "value-changed"]);
647643

648644
this._focusInputAfterClose = true;
@@ -728,6 +724,7 @@ class DatePicker extends DateComponentBase {
728724
Icon,
729725
ResponsivePopover,
730726
Calendar,
727+
CalendarDateComponent.default,
731728
Input,
732729
Button,
733730
];

packages/main/src/DatePickerPopover.hbs

+5-2
Original file line numberDiff line numberDiff line change
@@ -44,14 +44,17 @@
4444
primary-calendar-type="{{_primaryCalendarType}}"
4545
format-pattern="{{_formatPattern}}"
4646
timestamp="{{_calendarTimestamp}}"
47-
.selectedDates={{_calendarSelectedDates}}
4847
.selectionMode="{{_calendarSelectionMode}}"
4948
.minDate="{{minDate}}"
5049
.maxDate="{{maxDate}}"
5150
@ui5-selected-dates-change="{{onSelectedDatesChange}}"
5251
?hide-week-numbers="{{hideWeekNumbers}}"
5352
._currentPicker="{{_calendarCurrentPicker}}"
54-
></ui5-calendar>
53+
>
54+
{{#each _calendarSelectedDates}}
55+
<ui5-date value="{{this}}"></ui5-date>
56+
{{/each}}
57+
</ui5-calendar>
5558
{{/inline}}
5659

5760
{{#*inline "footer"}}{{/inline}}

packages/main/src/DateRangePicker.js

+26-4
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,14 @@ const metadata = {
2424
type: String,
2525
defaultValue: "-",
2626
},
27+
28+
/**
29+
* The first date in the range during selection (this is a temporary value, not the first date in the value range)
30+
* @private
31+
*/
32+
_tempValue: {
33+
type: String,
34+
},
2735
},
2836
};
2937

@@ -104,7 +112,13 @@ class DateRangePicker extends DatePicker {
104112
* @override
105113
*/
106114
get _calendarSelectedDates() {
107-
return [this._firstDateTimestamp, this._lastDateTimestamp].filter(date => !!date);
115+
if (this._tempValue) {
116+
return [this._tempValue];
117+
}
118+
if (this.value && this._checkValueValidity(this.value)) {
119+
return this._splitValueByDelimiter(this.value);
120+
}
121+
return [];
108122
}
109123

110124
/**
@@ -178,13 +192,21 @@ class DateRangePicker extends DatePicker {
178192
* @override
179193
*/
180194
onSelectedDatesChange(event) {
181-
const selectedDates = event.detail.dates;
182-
if (selectedDates.length !== 2) { // Do nothing until the user selects 2 dates, we don't change any state at all for one date
195+
event.preventDefault(); // never let the calendar update its own dates, the parent component controls them
196+
const values = event.detail.values;
197+
198+
if (values.length === 0) {
199+
return;
200+
}
201+
202+
if (values.length === 1) { // Do nothing until the user selects 2 dates, we don't change any state at all for one date
203+
this._tempValue = values[0];
183204
return;
184205
}
185206

186-
const newValue = this._buildValue(...selectedDates); // the value will be normalized so we don't need to order them here
207+
const newValue = this._buildValue(...event.detail.dates); // the value will be normalized so we don't need to order them here
187208
this._updateValueAndFireEvents(newValue, true, ["change", "value-changed"]);
209+
this._tempValue = "";
188210
this._focusInputAfterClose = true;
189211
this.closePicker();
190212
}

0 commit comments

Comments
 (0)