Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(ui5-daterange-picker): keyboard handling improvement #2179

Merged
merged 22 commits into from
Oct 9, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
507d9a4
fix(ui5-daterange-picker): keyboard handling improvement
unazko Sep 4, 2020
d830138
feat(ui5-daterange-picker): documentation added for keyboard handling
unazko Sep 4, 2020
f470914
Merge branch 'master' of https://github.com/unazko/ui5-webcomponents …
unazko Sep 8, 2020
2af45ff
feat(ui5-daterange-picker): documentation added for keyboard handling
unazko Sep 8, 2020
8f8291c
Merge branch 'master' of https://github.com/unazko/ui5-webcomponents …
unazko Sep 18, 2020
dc96cde
JS documentation is now accurate and helper function,
unazko Sep 18, 2020
b133650
Merge branch 'master' of https://github.com/unazko/ui5-webcomponents …
unazko Sep 21, 2020
b2c777f
Merge branch 'master' of https://github.com/unazko/ui5-webcomponents …
unazko Sep 23, 2020
3ac69b2
Merge branch 'master' of https://github.com/unazko/ui5-webcomponents …
unazko Sep 23, 2020
8e5eec9
fix(ui5-daterange-picker): keyboard handling improvement
unazko Sep 23, 2020
bfdfdf2
feat(ui5-daterange-picker): keyboard handling improvement
unazko Sep 24, 2020
064c017
Merge branch 'master' of https://github.com/unazko/ui5-webcomponents …
unazko Sep 30, 2020
05e9c79
Merge branch 'master' of https://github.com/unazko/ui5-webcomponents …
unazko Sep 30, 2020
c0df5ef
fix(ui5-daterange-picker): keyboard handling improvement
unazko Oct 1, 2020
db3da35
feat(ui5-daterange-picker): keyboard handling improvement
unazko Oct 1, 2020
29d0021
Merge branch 'master' of https://github.com/unazko/ui5-webcomponents …
unazko Oct 1, 2020
be5b7ff
feat(ui5-daterange-picker): keyboard handling improvement
unazko Oct 1, 2020
5980803
Merge branch 'master' of https://github.com/unazko/ui5-webcomponents …
unazko Oct 1, 2020
f813c41
Merge branch 'master' of https://github.com/unazko/ui5-webcomponents …
unazko Oct 2, 2020
366545d
Merge branch 'master' of https://github.com/unazko/ui5-webcomponents …
unazko Oct 5, 2020
c8bb02f
Unnecessary calls to the CalendarDate instances are now removed.
unazko Oct 5, 2020
ed2f49c
CalendarDate intances created with "CalendarDate.fromTimestamp" method
unazko Oct 5, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions packages/main/src/Calendar.js
Original file line number Diff line number Diff line change
Expand Up @@ -353,9 +353,7 @@ class Calendar extends UI5Element {
_getTimeStampFromString(value) {
const jsDate = this.getFormat().parse(value);
if (jsDate) {
const jsDateTimeNow = Date.UTC(jsDate.getFullYear(), jsDate.getMonth(), jsDate.getDate());
const calDate = CalendarDate.fromTimestamp(jsDateTimeNow, this._primaryCalendarType);
return calDate.valueOf();
return CalendarDate.fromLocalJSDate(jsDate, this._primaryCalendarType).toUTCJSDate().valueOf();
}
return undefined;
}
Expand Down
1 change: 1 addition & 0 deletions packages/main/src/DatePicker.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
class="ui5-date-picker-root"
style="{{styles.main}}"
@keydown={{_onkeydown}}
@focusout="{{_onfocusout}}"
>
<!-- INPUT -->
<ui5-input
Expand Down
85 changes: 55 additions & 30 deletions packages/main/src/DatePicker.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import CalendarDate from "@ui5/webcomponents-localization/dist/dates/CalendarDat
import ValueState from "@ui5/webcomponents-base/dist/types/ValueState.js";
import { getEffectiveAriaLabelText } from "@ui5/webcomponents-base/dist/util/AriaLabelHelper.js";
import {
isEnter,
isPageUp,
isPageDown,
isPageUpShift,
Expand Down Expand Up @@ -503,9 +504,7 @@ class DatePicker extends UI5Element {
_getTimeStampFromString(value) {
const jsDate = this.getFormat().parse(value);
if (jsDate) {
const jsDateTimeNow = Date.UTC(jsDate.getFullYear(), jsDate.getMonth(), jsDate.getDate());
const calDate = CalendarDate.fromTimestamp(jsDateTimeNow, this._primaryCalendarType);
return calDate.valueOf();
return CalendarDate.fromLocalJSDate(jsDate, this._primaryCalendarType).toUTCJSDate().valueOf();
}
return undefined;
}
Expand All @@ -530,82 +529,108 @@ class DatePicker extends UI5Element {
return;
}

if (isEnter(event)) {
this._handleEnterPressed();
}

if (isPageUpShiftCtrl(event)) {
event.preventDefault();
this._changeDateValue(true, true, false, false);
this._changeDateValueWrapper(true, true, false, false);
} else if (isPageUpShift(event)) {
event.preventDefault();
this._changeDateValue(true, false, true, false);
this._changeDateValueWrapper(true, false, true, false);
} else if (isPageUp(event)) {
event.preventDefault();
this._changeDateValue(true, false, false, true);
this._changeDateValueWrapper(true, false, false, true);
}

if (isPageDownShiftCtrl(event)) {
event.preventDefault();
this._changeDateValue(false, true, false, false);
this._changeDateValueWrapper(false, true, false, false);
} else if (isPageDownShift(event)) {
event.preventDefault();
this._changeDateValue(false, false, true, false);
this._changeDateValueWrapper(false, false, true, false);
} else if (isPageDown(event)) {
event.preventDefault();
this._changeDateValue(false, false, false, true);
this._changeDateValueWrapper(false, false, false, true);
}
}

/**
* This method is used in the derived classes
*/
_handleEnterPressed() {}

/**
* This method is used in the derived classes
*/
_onfocusout() {}

/**
* Adds or extracts a given number of measuring units from the "dateValue" property value
*
* @param {boolean} forward if true indicates addition
* @param {boolean} years indicates that the measuring unit is in years
* @param {boolean} months indicates that the measuring unit is in months
* @param {boolean} days indicates that the measuring unit is in days
* @param {boolean} forward if true indicates addition
* @param {int} step number of measuring units to substract or add defaults to 1
*/
_changeDateValue(forward, years, months, days, step = 1) {
_changeDateValueWrapper(forward, years, months, days, step = 1) {
let date = this.dateValue;
date = this._changeDateValue(date, forward, years, months, days, step);
this.value = this.formatValue(date);
}

/**
* Adds or extracts a given number of measuring units from the "dateValue" property value
*
* @param {boolean} date js date object to be changed
* @param {boolean} years indicates that the measuring unit is in years
* @param {boolean} months indicates that the measuring unit is in months
* @param {boolean} days indicates that the measuring unit is in days
* @param {boolean} forward if true indicates addition
unazko marked this conversation as resolved.
Show resolved Hide resolved
* @param {int} step number of measuring units to substract or add defaults ot 1
* @returns {Object} JS date object
*/
_changeDateValue(date, forward, years, months, days, step = 1) {
if (!date) {
return;
}

const oldDate = new Date(date.getTime());
let calDate = CalendarDate.fromLocalJSDate(date, this._primaryCalendarType);
const oldCalDate = new CalendarDate(calDate, this._primaryCalendarType);
const incrementStep = forward ? step : -step;

if (incrementStep === 0) {
if (incrementStep === 0 || (!days && !months && !years)) {
return;
}

if (days) {
date.setDate(date.getDate() + incrementStep);
calDate.setDate(calDate.getDate() + incrementStep);
} else if (months) {
date.setMonth(date.getMonth() + incrementStep);
const monthDiff = (date.getFullYear() - oldDate.getFullYear()) * 12 + (date.getMonth() - oldDate.getMonth());
calDate.setMonth(calDate.getMonth() + incrementStep);
const monthDiff = (calDate.getYear() - oldCalDate.getYear()) * 12 + (calDate.getMonth() - oldCalDate.getMonth());

if (date.getMonth() === oldDate.getMonth() || monthDiff !== incrementStep) {
if (calDate.getMonth() === oldCalDate.getMonth() || monthDiff !== incrementStep) {
// first condition example: 31th of March increment month with -1 results in 2th of March
// second condition example: 31th of January increment month with +1 results in 2th of March
date.setDate(0);
calDate.setDate(0);
}
} else if (years) {
date.setFullYear(date.getFullYear() + incrementStep);
calDate.setYear(calDate.getYear() + incrementStep);

if (date.getMonth() !== oldDate.getMonth()) {
if (calDate.getMonth() !== oldCalDate.getMonth()) {
// day doesn't exist in this month (February 29th)
date.setDate(0);
calDate.setDate(0);
}
} else {
return;
}

if (date.valueOf() < this._minDate) {
date = new Date(this._minDate);
} else if (date.valueOf() > this._maxDate) {
date = new Date(this._maxDate);
if (calDate.valueOf() < this._minDate) {
calDate = CalendarDate.fromTimestamp(this._minDate, this._primaryCalendarType);
} else if (calDate.valueOf() > this._maxDate) {
calDate = CalendarDate.fromTimestamp(this._maxDate, this._primaryCalendarType);
}

this.value = this.formatValue(date);
this.fireEvent("change", { value: this.value, valid: true });
return calDate.toLocalJSDate();
}

_toggleAndFocusInput() {
Expand Down
162 changes: 155 additions & 7 deletions packages/main/src/DateRangePicker.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import Integer from "@ui5/webcomponents-base/dist/types/Integer.js";
import ValueState from "@ui5/webcomponents-base/dist/types/ValueState.js";
import CalendarDate from "@ui5/webcomponents-localization/dist/dates/CalendarDate.js";
import DateRangePickerTemplate from "./generated/templates/DateRangePickerTemplate.lit.js";
import RenderScheduler from "../../base/src/RenderScheduler.js";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This breaks several builds, please use the "@ui5/webcomponents-base/dist/RenderScheduler.js" form

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will for the future. Thanks.


// Styles
import DateRangePickerCss from "./generated/themes/DateRangePicker.css.js";
Expand Down Expand Up @@ -64,6 +65,25 @@ const metadata = {
*
* <code>import @ui5/webcomponents/dist/DateRangePicker.js";</code>
*
* <h3>Keyboard Handling</h3>
* The <code>ui5-daterange-picker</code> provides advanced keyboard handling.
* <br>
*
* When the <code>ui5-daterange-picker</code> input field is focused the user can
* increment or decrement the corresponding field of the JS date object referenced by <code>_firstDateTimestamp</code> propery
* if the caret symbol is before the delimiter character or <code>_lastDateTimestamp</code> property if the caret symbol is
* after the delimiter character.
* The following shortcuts are enabled:
* <br>
* <ul>
* <li>[PAGEDOWN] - Decrements the corresponding day of the month by one</li>
* <li>[SHIFT] + [PAGEDOWN] - Decrements the corresponding month by one</li>
* <li>[SHIFT] + [CTRL] + [PAGEDOWN] - Decrements the corresponding year by one</li>
* <li>[PAGEUP] - Increments the corresponding day of the month by one</li>
* <li>[SHIFT] + [PAGEUP] - Increments the corresponding month by one</li>
* <li>[SHIFT] + [CTRL] + [PAGEUP] - Increments the corresponding year by one</li>
* </ul>
*
* @constructor
* @author SAP SE
* @alias sap.ui.webcomponents.main.DateRangePicker
Expand Down Expand Up @@ -204,7 +224,8 @@ class DateRangePicker extends DatePicker {
}

this._calendar.selectedDates = this.dateIntervalArrayBuilder(this._firstDateTimestamp * 1000, this._lastDateTimestamp * 1000);
this.value = this._formatValue(this._firstDateTimestamp, this._lastDateTimestamp);

this.value = this._formatValue(firstDate.valueOf() / 1000, secondDate.valueOf() / 1000);
this.realValue = this.value;
this._prevValue = this.realValue;
}
Expand Down Expand Up @@ -378,6 +399,125 @@ class DateRangePicker extends DatePicker {
}
}

/**
* Adds or extracts a given number of measuring units from the "dateValue" property value
*
* @param {boolean} forward if true indicates addition
* @param {boolean} years indicates that the measuring unit is in years
* @param {boolean} months indicates that the measuring unit is in months
* @param {boolean} days indicates that the measuring unit is in days
* @param {int} step number of measuring units to substract or add defaults ot 1
*/
async _changeDateValueWrapper(forward, years, months, days, step = 1) {
const emptyValue = this.value === "";
const isValid = emptyValue || this._checkValueValidity(this.value);

if (!isValid) {
return;
}

const dates = this._splitValueByDelimiter(this.value);
const innerInput = this.shadowRoot.querySelector("ui5-input").shadowRoot.querySelector(".ui5-input-inner");
const caretPos = this._getCaretPosition(innerInput);
const first = dates[0] && caretPos <= dates[0].trim().length + 1;
const last = dates[1] && (caretPos >= this.value.length - dates[1].trim().length - 1 && caretPos <= this.value.length);
let firstDate = this.getFormat().parse(dates[0]);
let lastDate = this.getFormat().parse(dates[1]);

if (first && firstDate) {
firstDate = this._changeDateValue(firstDate, forward, years, months, days, step);
} else if (last && lastDate) {
lastDate = this._changeDateValue(lastDate, forward, years, months, days, step);
}

this.value = this._formatValue(firstDate.valueOf() / 1000, lastDate.valueOf() / 1000);

await RenderScheduler.whenFinished();
// Return the caret on the previous position after rendering
this._setCaretPosition(innerInput, caretPos);
}

/**
* This method is used in the derived classes
*/
async _handleEnterPressed() {
const innerInput = this.shadowRoot.querySelector("ui5-input").shadowRoot.querySelector(".ui5-input-inner");
const caretPos = this._getCaretPosition(innerInput);

this._confirmInput();

await RenderScheduler.whenFinished();
// Return the caret on the previous position after rendering
this._setCaretPosition(innerInput, caretPos);
}

_onfocusout() {
this._confirmInput();
}

_confirmInput() {
const emptyValue = this.value === "";

if (emptyValue) {
return;
}

const dates = this._splitValueByDelimiter(this.value);
let firstDate = this.getFormat().parse(dates[0]);
let lastDate = this.getFormat().parse(dates[1]);

if (firstDate > lastDate) {
const temp = firstDate;
firstDate = lastDate;
lastDate = temp;
}

const newValue = this._formatValue(firstDate.valueOf() / 1000, lastDate.valueOf() / 1000);

this._setValue(newValue);
}

/**
* Returns the caret (cursor) position of the specified text field (field).
* Return value range is 0-field.value.length.
*/
_getCaretPosition(field) {
// Initialize
let caretPos = 0;

// IE Support
if (document.selection) {
// Set focus on the element
field.focus();

// To get cursor position, get empty selection range
const selection = document.selection.createRange();

// Move selection start to 0 position
selection.moveStart("character", -field.value.length);

// The caret position is selection length
caretPos = selection.text.length;
} else if (field.selectionStart || field.selectionStart === "0") { // Firefox support
caretPos = field.selectionDirection === "backward" ? field.selectionStart : field.selectionEnd;
}

return caretPos;
}

_setCaretPosition(field, caretPos) {
if (field.createTextRange) {
const range = field.createTextRange();
range.move("character", caretPos);
range.select();
} else if (field.selectionStart) {
field.focus();
field.setSelectionRange(caretPos, caretPos);
} else {
field.focus();
}
}

_handleCalendarSelectedDatesChange() {
this._updateValueCalendarSelectedDatesChange();
this._cleanHoveredAttributeFromVisibleItems();
Expand Down Expand Up @@ -409,23 +549,31 @@ class DateRangePicker extends DatePicker {
}

_updateValueCalendarSelectedDatesChange() {
const calStartDate = CalendarDate.fromTimestamp(this._firstDateTimestamp * 1000, this._primaryCalendarType);
const calEndDate = CalendarDate.fromTimestamp(this._lastDateTimestamp * 1000, this._primaryCalendarType);

// Collect both dates and merge them into one
if (this._firstDateTimestamp !== this._lastDateTimestamp || this._oneTimeStampSelected) {
this.value = this._formatValue(this._firstDateTimestamp, this._lastDateTimestamp);
this.value = this._formatValue(calStartDate.toLocalJSDate().valueOf() / 1000, calEndDate.toLocalJSDate().valueOf() / 1000);
}

this.realValue = this._formatValue(this._firstDateTimestamp, this._lastDateTimestamp);
this.realValue = this._formatValue(calStartDate.toLocalJSDate().valueOf() / 1000, calEndDate.toLocalJSDate().valueOf() / 1000);
this._prevValue = this.realValue;
}

/**
* Combines the start and end dates of a range into a formated string
*
* @param {int} firstDateValue locale start date timestamp
* @param {int} lastDateValue locale end date timestamp
* @returns {string} formated start to end date range
*/
_formatValue(firstDateValue, lastDateValue) {
let value = "";
const delimiter = this.delimiter,
format = this.getFormat(),
firstDate = new Date(firstDateValue * 1000),
lastDate = new Date(lastDateValue * 1000),
firstDateString = format.format(new Date(firstDate.getUTCFullYear(), firstDate.getUTCMonth(), firstDate.getUTCDate(), firstDate.getUTCHours())),
lastDateString = format.format(new Date(lastDate.getUTCFullYear(), lastDate.getUTCMonth(), lastDate.getUTCDate(), lastDate.getUTCHours()));
firstDateString = format.format(new Date(firstDateValue * 1000)),
lastDateString = format.format(new Date(lastDateValue * 1000));

if (firstDateValue) {
if (delimiter && delimiter !== "" && lastDateString) {
Expand Down
Loading