From 30c683d986ffec16096c39f473feec23aed7b18d Mon Sep 17 00:00:00 2001 From: janekptacijarabaci Date: Wed, 14 Feb 2018 08:26:47 +0100 Subject: Bug 1317600: [DateTimePicker] Spinner arrow buttons shouldn't depend on find bar resources --- toolkit/themes/shared/icons/spinner-arrows.svg | 13 +++++++++++++ toolkit/themes/shared/jar.inc.mn | 1 + toolkit/themes/shared/timepicker.css | 4 ++-- 3 files changed, 16 insertions(+), 2 deletions(-) create mode 100644 toolkit/themes/shared/icons/spinner-arrows.svg diff --git a/toolkit/themes/shared/icons/spinner-arrows.svg b/toolkit/themes/shared/icons/spinner-arrows.svg new file mode 100644 index 0000000000..a8ba72d6b5 --- /dev/null +++ b/toolkit/themes/shared/icons/spinner-arrows.svg @@ -0,0 +1,13 @@ + + + + + + + diff --git a/toolkit/themes/shared/jar.inc.mn b/toolkit/themes/shared/jar.inc.mn index 9c3d86a40d..cfb4e99af5 100644 --- a/toolkit/themes/shared/jar.inc.mn +++ b/toolkit/themes/shared/jar.inc.mn @@ -27,6 +27,7 @@ toolkit.jar: skin/classic/global/icons/input-clear.svg (../../shared/icons/input-clear.svg) skin/classic/global/icons/loading.png (../../shared/icons/loading.png) skin/classic/global/icons/loading@2x.png (../../shared/icons/loading@2x.png) + skin/classic/global/icons/spinner-arrows.svg (../../shared/icons/spinner-arrows.svg) skin/classic/global/icons/warning.svg (../../shared/incontent-icons/warning.svg) skin/classic/global/icons/blocked.svg (../../shared/incontent-icons/blocked.svg) skin/classic/global/alerts/alert-common.css (../../shared/alert-common.css) diff --git a/toolkit/themes/shared/timepicker.css b/toolkit/themes/shared/timepicker.css index e8d081b301..ca49557487 100644 --- a/toolkit/themes/shared/timepicker.css +++ b/toolkit/themes/shared/timepicker.css @@ -68,11 +68,11 @@ body { } .spinner-container > button.up { - mask: url("chrome://global/skin/icons/find-arrows.svg#glyph-find-previous") no-repeat 50% 50%; + background: url("chrome://global/skin/icons/spinner-arrows.svg#up") no-repeat 50% 50%; } .spinner-container > button.down { - mask: url("chrome://global/skin/icons/find-arrows.svg#glyph-find-next") no-repeat 50% 50%; + background: url("chrome://global/skin/icons/spinner-arrows.svg#down") no-repeat 50% 50%; } .spinner-container.hide-buttons > button { -- cgit v1.2.3 From 8fd370c9f39b4bc1d78cc0f763bf4e99dfd0c382 Mon Sep 17 00:00:00 2001 From: janekptacijarabaci Date: Wed, 14 Feb 2018 12:32:46 +0100 Subject: Bug 1283385: Implement UI for --- toolkit/content/datepicker.xhtml | 61 +++++ toolkit/content/jar.mn | 4 + toolkit/content/timepicker.xhtml | 2 +- toolkit/content/widgets/calendar.js | 172 ++++++++++++ toolkit/content/widgets/datekeeper.js | 244 +++++++++++++++++ toolkit/content/widgets/datepicker.js | 354 +++++++++++++++++++++++++ toolkit/content/widgets/spinner.js | 18 +- toolkit/themes/shared/datetimeinputpickers.css | 291 ++++++++++++++++++++ toolkit/themes/shared/jar.inc.mn | 2 +- toolkit/themes/shared/timepicker.css | 153 ----------- 10 files changed, 1138 insertions(+), 163 deletions(-) create mode 100644 toolkit/content/datepicker.xhtml create mode 100644 toolkit/content/widgets/calendar.js create mode 100644 toolkit/content/widgets/datekeeper.js create mode 100644 toolkit/content/widgets/datepicker.js create mode 100644 toolkit/themes/shared/datetimeinputpickers.css delete mode 100644 toolkit/themes/shared/timepicker.css diff --git a/toolkit/content/datepicker.xhtml b/toolkit/content/datepicker.xhtml new file mode 100644 index 0000000000..abc7db3039 --- /dev/null +++ b/toolkit/content/datepicker.xhtml @@ -0,0 +1,61 @@ + + + %htmlDTD; +]> + + + Date Picker + + + + + + + +
+
+ +
+
+
+
+
+
+
+
+
+
+
+ + + + \ No newline at end of file diff --git a/toolkit/content/jar.mn b/toolkit/content/jar.mn index 590356b646..79065e78ac 100644 --- a/toolkit/content/jar.mn +++ b/toolkit/content/jar.mn @@ -45,6 +45,7 @@ toolkit.jar: content/global/customizeToolbar.js content/global/customizeToolbar.xul #endif + content/global/datepicker.xhtml #ifndef MOZ_FENNEC content/global/editMenuOverlay.js * content/global/editMenuOverlay.xul @@ -68,8 +69,11 @@ toolkit.jar: content/global/bindings/autocomplete.xml (widgets/autocomplete.xml) content/global/bindings/browser.xml (widgets/browser.xml) content/global/bindings/button.xml (widgets/button.xml) + content/global/bindings/calendar.js (widgets/calendar.js) content/global/bindings/checkbox.xml (widgets/checkbox.xml) content/global/bindings/colorpicker.xml (widgets/colorpicker.xml) + content/global/bindings/datekeeper.js (widgets/datekeeper.js) + content/global/bindings/datepicker.js (widgets/datepicker.js) content/global/bindings/datetimepicker.xml (widgets/datetimepicker.xml) content/global/bindings/datetimepopup.xml (widgets/datetimepopup.xml) content/global/bindings/datetimebox.xml (widgets/datetimebox.xml) diff --git a/toolkit/content/timepicker.xhtml b/toolkit/content/timepicker.xhtml index 1396223f1b..77b9fba410 100644 --- a/toolkit/content/timepicker.xhtml +++ b/toolkit/content/timepicker.xhtml @@ -6,7 +6,7 @@ Time Picker - + diff --git a/toolkit/content/widgets/calendar.js b/toolkit/content/widgets/calendar.js new file mode 100644 index 0000000000..72e0d9d610 --- /dev/null +++ b/toolkit/content/widgets/calendar.js @@ -0,0 +1,172 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +/** + * Initialize the Calendar and generate nodes for week headers and days, and + * attach event listeners. + * + * @param {Object} options + * { + * {Number} calViewSize: Number of days to appear on a calendar view + * } + * @param {Object} context + * { + * {DOMElement} weekHeader + * {DOMElement} daysView + * } + */ +function Calendar(options, context) { + const DAYS_IN_A_WEEK = 7; + + this.context = context; + this.state = { + days: [], + weekHeaders: [] + }; + this.props = {}; + this.elements = { + weekHeaders: this._generateNodes(DAYS_IN_A_WEEK, context.weekHeader), + daysView: this._generateNodes(options.calViewSize, context.daysView) + }; + + this._attachEventListeners(); +} + +{ + Calendar.prototype = { + + /** + * Set new properties and render them. + * + * @param {Object} props + * { + * {Boolean} isVisible: Whether or not the calendar is in view + * {Array} days: Data for days + * { + * {Number} dateValue: Date in milliseconds + * {Number} textContent + * {Array} classNames + * } + * {Array} weekHeaders: Data for weekHeaders + * { + * {Number} textContent + * {Array} classNames + * } + * {Function} getDayString: Transform day number to string + * {Function} getWeekHeaderString: Transform day of week number to string + * {Function} setValue: Set value for dateKeeper + * {Number} selectionValue: The selection date value + * } + */ + setProps(props) { + if (props.isVisible) { + // Transform the days and weekHeaders array for rendering + const days = props.days.map(({ dateValue, textContent, classNames }) => { + return { + dateValue, + textContent: props.getDayString(textContent), + className: dateValue == props.selectionValue ? + classNames.concat("selection").join(" ") : + classNames.join(" ") + }; + }); + const weekHeaders = props.weekHeaders.map(({ textContent, classNames }) => { + return { + textContent: props.getWeekHeaderString(textContent), + className: classNames.join(" ") + }; + }); + // Update the DOM nodes states + this._render({ + elements: this.elements.daysView, + items: days, + prevState: this.state.days + }); + this._render({ + elements: this.elements.weekHeaders, + items: weekHeaders, + prevState: this.state.weekHeaders, + }); + // Update the state to current + this.state.days = days; + this.state.weekHeaders = weekHeaders; + } + + this.props = Object.assign(this.props, props); + }, + + /** + * Render the items onto the DOM nodes + * @param {Object} + * { + * {Array} elements + * {Array} items + * {Array} prevState: state of items from last render + * } + */ + _render({ elements, items, prevState }) { + for (let i = 0, l = items.length; i < l; i++) { + let el = elements[i]; + + // Check if state from last render has changed, if so, update the elements + if (!prevState[i] || prevState[i].textContent != items[i].textContent) { + el.textContent = items[i].textContent; + } + if (!prevState[i] || prevState[i].className != items[i].className) { + el.className = items[i].className; + } + } + }, + + /** + * Generate DOM nodes + * + * @param {Number} size: Number of nodes to generate + * @param {DOMElement} context: Element to append the nodes to + * @return {Array} + */ + _generateNodes(size, context) { + let frag = document.createDocumentFragment(); + let refs = []; + + for (let i = 0; i < size; i++) { + let el = document.createElement("div"); + el.dataset.id = i; + refs.push(el); + frag.appendChild(el); + } + context.appendChild(frag); + + return refs; + }, + + /** + * Handle events + * @param {DOMEvent} event + */ + handleEvent(event) { + switch (event.type) { + case "click": { + if (event.target.parentNode == this.context.daysView) { + let targetId = event.target.dataset.id; + this.props.setValue({ + selectionValue: this.props.days[targetId].dateValue, + dateValue: this.props.days[targetId].dateValue + }); + } + break; + } + } + }, + + /** + * Attach event listener to daysView + */ + _attachEventListeners() { + this.context.daysView.addEventListener("click", this); + } + }; +} diff --git a/toolkit/content/widgets/datekeeper.js b/toolkit/content/widgets/datekeeper.js new file mode 100644 index 0000000000..62fcfadbc3 --- /dev/null +++ b/toolkit/content/widgets/datekeeper.js @@ -0,0 +1,244 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +/** + * DateKeeper keeps track of the date states. + * + * @param {Object} date parts + * { + * {Number} year + * {Number} month + * {Number} date + * } + * {Object} options + * { + * {Number} firstDayOfWeek [optional] + * {Array} weekends [optional] + * {Number} calViewSize [optional] + * } + */ +function DateKeeper({ year, month, date }, { firstDayOfWeek = 0, weekends = [0], calViewSize = 42 }) { + this.state = { + firstDayOfWeek, weekends, calViewSize, + dateObj: new Date(0), + years: [], + months: [], + days: [] + }; + this.state.weekHeaders = this._getWeekHeaders(firstDayOfWeek); + this._update(year, month, date); +} + +{ + const DAYS_IN_A_WEEK = 7, + MONTHS_IN_A_YEAR = 12, + YEAR_VIEW_SIZE = 200, + YEAR_BUFFER_SIZE = 10; + + DateKeeper.prototype = { + /** + * Set new date + * @param {Object} date parts + * { + * {Number} year [optional] + * {Number} month [optional] + * {Number} date [optional] + * } + */ + set({ year = this.state.year, month = this.state.month, date = this.state.date }) { + this._update(year, month, date); + }, + + /** + * Set date with value + * @param {Number} value: Date value + */ + setValue(value) { + const dateObj = new Date(value); + this._update(dateObj.getUTCFullYear(), dateObj.getUTCMonth(), dateObj.getUTCDate()); + }, + + /** + * Set month. Makes sure the date is <= the last day of the month + * @param {Number} month + */ + setMonth(month) { + const lastDayOfMonth = this._newUTCDate(this.state.year, month + 1, 0).getUTCDate(); + this._update(this.state.year, month, Math.min(this.state.date, lastDayOfMonth)); + }, + + /** + * Set year. Makes sure the date is <= the last day of the month + * @param {Number} year + */ + setYear(year) { + const lastDayOfMonth = this._newUTCDate(year, this.state.month + 1, 0).getUTCDate(); + this._update(year, this.state.month, Math.min(this.state.date, lastDayOfMonth)); + }, + + /** + * Set month by offset. Makes sure the date is <= the last day of the month + * @param {Number} offset + */ + setMonthByOffset(offset) { + const lastDayOfMonth = this._newUTCDate(this.state.year, this.state.month + offset + 1, 0).getUTCDate(); + this._update(this.state.year, this.state.month + offset, Math.min(this.state.date, lastDayOfMonth)); + }, + + /** + * Update the states. + * @param {Number} year [description] + * @param {Number} month [description] + * @param {Number} date [description] + */ + _update(year, month, date) { + // Use setUTCFullYear so that year 99 doesn't get parsed as 1999 + this.state.dateObj.setUTCFullYear(year, month, date); + this.state.year = this.state.dateObj.getUTCFullYear(); + this.state.month = this.state.dateObj.getUTCMonth(); + this.state.date = this.state.dateObj.getUTCDate(); + }, + + /** + * Generate the array of months + * @return {Array} + * { + * {Number} value: Month in int + * {Boolean} enabled + * } + */ + getMonths() { + // TODO: add min/max and step support + let months = []; + + for (let i = 0; i < MONTHS_IN_A_YEAR; i++) { + months.push({ + value: i, + enabled: true + }); + } + + return months; + }, + + /** + * Generate the array of years + * @return {Array} + * { + * {Number} value: Year in int + * {Boolean} enabled + * } + */ + getYears() { + // TODO: add min/max and step support + let years = []; + + const firstItem = this.state.years[0]; + const lastItem = this.state.years[this.state.years.length - 1]; + const currentYear = this.state.dateObj.getUTCFullYear(); + + // Generate new years array when the year is outside of the first & + // last item range. If not, return the cached result. + if (!firstItem || !lastItem || + currentYear <= firstItem.value + YEAR_BUFFER_SIZE || + currentYear >= lastItem.value - YEAR_BUFFER_SIZE) { + // The year is set in the middle with items on both directions + for (let i = -(YEAR_VIEW_SIZE / 2); i < YEAR_VIEW_SIZE / 2; i++) { + years.push({ + value: currentYear + i, + enabled: true + }); + } + this.state.years = years; + } + return this.state.years; + }, + + /** + * Get days for calendar + * @return {Array} + * { + * {Number} dateValue + * {Number} textContent + * {Array} classNames + * } + */ + getDays() { + // TODO: add min/max and step support + let firstDayOfMonth = this._getFirstCalendarDate(this.state.dateObj, this.state.firstDayOfWeek); + let days = []; + let month = this.state.dateObj.getUTCMonth(); + + for (let i = 0; i < this.state.calViewSize; i++) { + let dateObj = this._newUTCDate(firstDayOfMonth.getUTCFullYear(), firstDayOfMonth.getUTCMonth(), firstDayOfMonth.getUTCDate() + i); + let classNames = []; + if (this.state.weekends.includes(dateObj.getUTCDay())) { + classNames.push("weekend"); + } + if (month != dateObj.getUTCMonth()) { + classNames.push("outside"); + } + days.push({ + dateValue: dateObj.getTime(), + textContent: dateObj.getUTCDate(), + classNames + }); + } + return days; + }, + + /** + * Get week headers for calendar + * @param {Number} firstDayOfWeek + * @return {Array} + * { + * {Number} textContent + * {Array} classNames + * } + */ + _getWeekHeaders(firstDayOfWeek) { + let headers = []; + let day = firstDayOfWeek; + + for (let i = 0; i < DAYS_IN_A_WEEK; i++) { + headers.push({ + textContent: day % DAYS_IN_A_WEEK, + classNames: this.state.weekends.includes(day % DAYS_IN_A_WEEK) ? ["weekend"] : [] + }); + day++; + } + return headers; + }, + + /** + * Get the first day on a calendar month + * @param {Date} dateObj + * @param {Number} firstDayOfWeek + * @return {Date} + */ + _getFirstCalendarDate(dateObj, firstDayOfWeek) { + const daysOffset = 1 - DAYS_IN_A_WEEK; + let firstDayOfMonth = this._newUTCDate(dateObj.getUTCFullYear(), dateObj.getUTCMonth()); + let dayOfWeek = firstDayOfMonth.getUTCDay(); + + return this._newUTCDate( + firstDayOfMonth.getUTCFullYear(), + firstDayOfMonth.getUTCMonth(), + // When first calendar date is the same as first day of the week, add + // another row on top of it. + firstDayOfWeek == dayOfWeek ? daysOffset : (firstDayOfWeek - dayOfWeek + daysOffset) % DAYS_IN_A_WEEK); + }, + + /** + * Helper function for creating UTC dates + * @param {...[Number]} parts + * @return {Date} + */ + _newUTCDate(...parts) { + return new Date(Date.UTC(...parts)); + } + }; +} diff --git a/toolkit/content/widgets/datepicker.js b/toolkit/content/widgets/datepicker.js new file mode 100644 index 0000000000..d3f0fd1a43 --- /dev/null +++ b/toolkit/content/widgets/datepicker.js @@ -0,0 +1,354 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +function DatePicker(context) { + this.context = context; + this._attachEventListeners(); +} + +{ + const CAL_VIEW_SIZE = 42; + + DatePicker.prototype = { + /** + * Initializes the date picker. Set the default states and properties. + * @param {Object} props + * { + * {Number} year [optional] + * {Number} month [optional] + * {Number} date [optional] + * {String} locale [optional]: User preferred locale + * } + */ + init(props = {}) { + this.props = props; + this._setDefaultState(); + this._createComponents(); + this._update(); + }, + + /* + * Set initial date picker states. + */ + _setDefaultState() { + const now = new Date(); + const { year = now.getFullYear(), + month = now.getMonth(), + date = now.getDate(), + locale } = this.props; + + // TODO: Use calendar info API to get first day of week & weekends + // (Bug 1287503) + const dateKeeper = new DateKeeper({ + year, month, date + }, { + calViewSize: CAL_VIEW_SIZE, + firstDayOfWeek: 0, + weekends: [0] + }); + + this.state = { + dateKeeper, + locale, + isMonthPickerVisible: false, + isYearSet: false, + isMonthSet: false, + isDateSet: false, + getDayString: new Intl.NumberFormat(locale).format, + // TODO: use calendar terms when available (Bug 1287677) + getWeekHeaderString: weekday => ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"][weekday], + setValue: ({ dateValue, selectionValue }) => { + dateKeeper.setValue(dateValue); + this.state.selectionValue = selectionValue; + this.state.isYearSet = true; + this.state.isMonthSet = true; + this.state.isDateSet = true; + this._update(); + this._dispatchState(); + }, + setYear: year => { + dateKeeper.setYear(year); + this.state.isYearSet = true; + this._update(); + this._dispatchState(); + }, + setMonth: month => { + dateKeeper.setMonth(month); + this.state.isMonthSet = true; + this._update(); + this._dispatchState(); + }, + toggleMonthPicker: () => { + this.state.isMonthPickerVisible = !this.state.isMonthPickerVisible; + this._update(); + } + }; + }, + + /** + * Initalize the date picker components. + */ + _createComponents() { + this.components = { + calendar: new Calendar({ + calViewSize: CAL_VIEW_SIZE, + locale: this.state.locale + }, { + weekHeader: this.context.weekHeader, + daysView: this.context.daysView + }), + monthYear: new MonthYear({ + setYear: this.state.setYear, + setMonth: this.state.setMonth, + locale: this.state.locale + }, { + monthYear: this.context.monthYear, + monthYearView: this.context.monthYearView + }) + }; + }, + + /** + * Update date picker and its components. + */ + _update() { + const { dateKeeper, selectionValue, isMonthPickerVisible } = this.state; + + if (isMonthPickerVisible) { + this.state.months = dateKeeper.getMonths(); + this.state.years = dateKeeper.getYears(); + } else { + this.state.days = dateKeeper.getDays(); + } + + this.components.monthYear.setProps({ + isVisible: isMonthPickerVisible, + dateObj: dateKeeper.state.dateObj, + month: dateKeeper.state.month, + months: this.state.months, + year: dateKeeper.state.year, + years: this.state.years, + toggleMonthPicker: this.state.toggleMonthPicker + }); + this.components.calendar.setProps({ + isVisible: !isMonthPickerVisible, + days: this.state.days, + weekHeaders: dateKeeper.state.weekHeaders, + setValue: this.state.setValue, + getDayString: this.state.getDayString, + getWeekHeaderString: this.state.getWeekHeaderString, + selectionValue + }); + + isMonthPickerVisible ? + this.context.monthYearView.classList.remove("hidden") : + this.context.monthYearView.classList.add("hidden"); + }, + + /** + * Use postMessage to pass the state of picker to the panel. + */ + _dispatchState() { + const { year, month, date } = this.state.dateKeeper.state; + const { isYearSet, isMonthSet, isDateSet } = this.state; + // The panel is listening to window for postMessage event, so we + // do postMessage to itself to send data to input boxes. + window.postMessage({ + name: "DatePickerPopupChanged", + detail: { + year, + month, + date, + isYearSet, + isMonthSet, + isDateSet + } + }, "*"); + }, + + /** + * Attach event listeners + */ + _attachEventListeners() { + window.addEventListener("message", this); + document.addEventListener("click", this); + }, + + /** + * Handle events. + * + * @param {Event} event + */ + handleEvent(event) { + switch (event.type) { + case "message": { + this.handleMessage(event); + break; + } + case "click": { + if (event.target == this.context.buttonLeft) { + this.state.dateKeeper.setMonthByOffset(-1); + this._update(); + } else if (event.target == this.context.buttonRight) { + this.state.dateKeeper.setMonthByOffset(1); + this._update(); + } + break; + } + } + }, + + /** + * Handle postMessage events. + * + * @param {Event} event + */ + handleMessage(event) { + switch (event.data.name) { + case "DatePickerSetValue": { + this.set(event.data.detail); + break; + } + case "DatePickerInit": { + this.init(event.data.detail); + break; + } + } + }, + + /** + * Set the date state and update the components with the new state. + * + * @param {Object} dateState + * { + * {Number} year [optional] + * {Number} month [optional] + * {Number} date [optional] + * } + */ + set(dateState) { + if (dateState.year != undefined) { + this.state.isYearSet = true; + } + if (dateState.month != undefined) { + this.state.isMonthSet = true; + } + if (dateState.date != undefined) { + this.state.isDateSet = true; + } + + this.state.dateKeeper.set(dateState); + this._update(); + } + }; + + /** + * MonthYear is a component that handles the month & year spinners + * + * @param {Object} options + * { + * {String} locale + * {Function} setYear + * {Function} setMonth + * } + * @param {DOMElement} context + */ + function MonthYear(options, context) { + const spinnerSize = 5; + const monthFormat = new Intl.DateTimeFormat(options.locale, { month: "short" }).format; + const yearFormat = new Intl.DateTimeFormat(options.locale, { year: "numeric" }).format; + const dateFormat = new Intl.DateTimeFormat(options.locale, { year: "numeric", month: "long" }).format; + + this.context = context; + this.state = { dateFormat }; + this.props = {}; + this.components = { + month: new Spinner({ + setValue: month => { + this.state.isMonthSet = true; + options.setMonth(month); + }, + getDisplayString: month => monthFormat(new Date(0, month)), + viewportSize: spinnerSize + }, context.monthYearView), + year: new Spinner({ + setValue: year => { + this.state.isYearSet = true; + options.setYear(year); + }, + getDisplayString: year => yearFormat(new Date(new Date(0).setFullYear(year))), + viewportSize: spinnerSize + }, context.monthYearView) + }; + + this._attachEventListeners(); + } + + MonthYear.prototype = { + + /** + * Set new properties and pass them to components + * + * @param {Object} props + * { + * {Boolean} isVisible + * {Date} dateObj + * {Number} month + * {Number} year + * {Array} months + * {Array} years + * {Function} toggleMonthPicker + * } + */ + setProps(props) { + this.context.monthYear.textContent = this.state.dateFormat(props.dateObj); + + if (props.isVisible) { + this.components.month.setState({ + value: props.month, + items: props.months, + isInfiniteScroll: true, + isValueSet: this.state.isMonthSet, + smoothScroll: !this.state.firstOpened + }); + this.components.year.setState({ + value: props.year, + items: props.years, + isInfiniteScroll: false, + isValueSet: this.state.isYearSet, + smoothScroll: !this.state.firstOpened + }); + this.state.firstOpened = false; + } else { + this.state.isMonthSet = false; + this.state.isYearSet = false; + this.state.firstOpened = true; + } + + this.props = Object.assign(this.props, props); + }, + + /** + * Handle events + * @param {DOMEvent} event + */ + handleEvent(event) { + switch (event.type) { + case "click": { + this.props.toggleMonthPicker(); + break; + } + } + }, + + /** + * Attach event listener to monthYear button + */ + _attachEventListeners() { + this.context.monthYear.addEventListener("click", this); + } + }; +} diff --git a/toolkit/content/widgets/spinner.js b/toolkit/content/widgets/spinner.js index 208ab1931b..b3d996fb50 100644 --- a/toolkit/content/widgets/spinner.js +++ b/toolkit/content/widgets/spinner.js @@ -98,7 +98,7 @@ function Spinner(props, context) { setState(newState) { const { spinner } = this.elements; const { value, items } = this.state; - const { value: newValue, items: newItems, isValueSet, isInvalid } = newState; + const { value: newValue, items: newItems, isValueSet, isInvalid, smoothScroll = true } = newState; if (this._isArrayDiff(newItems, items)) { this.state = Object.assign(this.state, newState); @@ -106,16 +106,18 @@ function Spinner(props, context) { this._scrollTo(newValue, true); } else if (newValue != value) { this.state = Object.assign(this.state, newState); - this._smoothScrollTo(newValue); - } - - if (isValueSet) { - if (isInvalid) { - this._removeSelection(); + if (smoothScroll) { + this._smoothScrollTo(newValue, true); } else { - this._updateSelection(); + this._scrollTo(newValue, true); } } + + if (isValueSet && !isInvalid) { + this._updateSelection(); + } else { + this._removeSelection(); + } }, /** diff --git a/toolkit/themes/shared/datetimeinputpickers.css b/toolkit/themes/shared/datetimeinputpickers.css new file mode 100644 index 0000000000..df93bc3a1f --- /dev/null +++ b/toolkit/themes/shared/datetimeinputpickers.css @@ -0,0 +1,291 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +:root { + --font-size-default: 1.1rem; + --spinner-width: 3rem; + --spinner-margin-top-bottom: 0.4rem; + --spinner-item-height: 2.4rem; + --spinner-item-margin-bottom: 0.1rem; + --spinner-button-height: 1.2rem; + --colon-width: 2rem; + --day-period-spacing-width: 1rem; + --calendar-width: 23.1rem; + --date-picker-item-height: 2.4rem; + + --border: 0.1rem solid #D6D6D6; + --border-radius: 0.3rem; + + --font-color: #191919; + --fill-color: #EBEBEB; + + --selected-font-color: #FFFFFF; + --selected-fill-color: #0996F8; + + --button-font-color: #858585; + --button-font-color-hover: #4D4D4D; + --button-font-color-active: #191919; + + --weekday-font-color: #6C6C6C; + --weekday-outside-font-color: #6C6C6C; + --weekend-font-color: #DA4E44; + --weekend-outside-font-color: #FF988F; + + --disabled-opacity: 0.2; +} + +html { + font-size: 10px; +} + +body { + margin: 0; + color: var(--font-color); + font: message-box; + font-size: var(--font-size-default); +} + +.nav { + display: flex; + width: var(--calendar-width); + height: 2.4rem; + margin-bottom: 0.8rem; + justify-content: space-between; +} + +.nav button { + -moz-appearance: none; + background: none; + border: none; + width: 3rem; + height: var(--date-picker-item-height); +} + +.month-year { + position: absolute; + display: flex; + justify-content: center; + align-items: center; + top: 0; + left: 3rem; + width: 17.1rem; + height: var(--date-picker-item-height); + z-index: 10; +} + +.month-year-view { + position: absolute; + z-index: 5; + padding-top: 3.2rem; + top: 0; + left: 0; + bottom: 0; + width: var(--calendar-width); + background: window; + opacity: 1; + transition: opacity 0.15s; +} + +.month-year-view.hidden { + visibility: hidden; + opacity: 0; +} + +.month-year-view > .spinner-container { + width: 5.5rem; + margin: 0 0.5rem; +} + +.month-year-view .spinner { + transform: scaleY(1); + transform-origin: top; + transition: transform 0.15s; +} + +.month-year-view.hidden .spinner { + transform: scaleY(0); + transition: none; +} + +.month-year-view .spinner > div { + transform: scaleY(1); + transition: transform 0.15s; +} + +.month-year-view.hidden .spinner > div { + transform: scaleY(2.5); + transition: none; +} + +.calendar-container { + cursor: default; + display: flex; + flex-direction: column; + width: var(--calendar-width); +} + +.week-header { + display: flex; +} + +.week-header > div { + color: var(--weekday-font-color); +} + +.week-header > div.weekend { + color: var(--weekend-font-color); +} + +.days-viewport { + height: 15rem; + overflow: hidden; + position: relative; +} + +.days-view { + position: absolute; + display: flex; + flex-wrap: wrap; + flex-direction: row; +} + +.week-header > div, +.days-view > div { + align-items: center; + display: flex; + height: var(--date-picker-item-height); + margin: 0.05rem 0.15rem; + position: relative; + justify-content: center; + width: 3rem; +} + +.days-view > div.outside { + color: var(--weekday-outside-font-color); +} + +.days-view > div.weekend { + color: var(--weekend-font-color); +} + +.days-view > div.weekend.outside { + color: var(--weekend-outside-font-color); +} + +#time-picker, +.month-year-view { + display: flex; + flex-direction: row; + justify-content: center; +} + +.spinner-container { + display: flex; + flex-direction: column; + width: var(--spinner-width); +} + +.spinner-container > button { + -moz-appearance: none; + border: none; + background: none; + background-color: var(--button-font-color); + height: var(--spinner-button-height); +} + +.spinner-container > button:hover { + background-color: var(--button-font-color-hover); +} + +.spinner-container > button.active { + background-color: var(--button-font-color-active); +} + +.spinner-container > button.up { + background: url("chrome://global/skin/icons/spinner-arrows.svg#up") no-repeat 50% 50%; +} + +.spinner-container > button.down { + background: url("chrome://global/skin/icons/spinner-arrows.svg#down") no-repeat 50% 50%; +} + +.spinner-container.hide-buttons > button { + visibility: hidden; +} + +.spinner-container > .spinner { + position: relative; + width: 100%; + margin: var(--spinner-margin-top-bottom) 0; + cursor: default; + overflow-y: scroll; + scroll-snap-type: mandatory; + scroll-snap-points-y: repeat(100%); +} + +.spinner-container > .spinner > div { + box-sizing: border-box; + position: relative; + text-align: center; + padding: calc((var(--spinner-item-height) - var(--font-size-default)) / 2) 0; + margin-bottom: var(--spinner-item-margin-bottom); + height: var(--spinner-item-height); + -moz-user-select: none; + scroll-snap-coordinate: 0 0; +} + +.spinner-container > .spinner > div:hover::before, +.calendar-container .days-view > div:hover::before { + background: var(--fill-color); + border: var(--border); + border-radius: var(--border-radius); + content: ""; + position: absolute; + top: 0%; + bottom: 0%; + left: 0%; + right: 0%; + z-index: -10; +} + +.spinner-container > .spinner:not(.scrolling) > div.selection, +.calendar-container .days-view > div.selection { + color: var(--selected-font-color); +} + +.spinner-container > .spinner > div.selection::before, +.calendar-container .days-view > div.selection::before { + background: var(--selected-fill-color); + border: none; + border-radius: var(--border-radius); + content: ""; + position: absolute; + top: 0%; + bottom: 0%; + left: 0%; + right: 0%; + z-index: -10; +} + +.spinner-container > .spinner > div.disabled::before, +.spinner-container > .spinner.scrolling > div.selection::before, +.spinner-container > .spinner.scrolling > div:hover::before { + display: none; +} + +.spinner-container > .spinner > div.disabled { + opacity: var(--disabled-opacity); +} + +.colon { + display: flex; + justify-content: center; + align-items: center; + width: var(--colon-width); + margin-bottom: 0.3rem; +} + +.spacer { + width: var(--day-period-spacing-width); +} \ No newline at end of file diff --git a/toolkit/themes/shared/jar.inc.mn b/toolkit/themes/shared/jar.inc.mn index cfb4e99af5..e7f1f7c676 100644 --- a/toolkit/themes/shared/jar.inc.mn +++ b/toolkit/themes/shared/jar.inc.mn @@ -21,7 +21,7 @@ toolkit.jar: skin/classic/global/aboutSupport.css (../../shared/aboutSupport.css) skin/classic/global/appPicker.css (../../shared/appPicker.css) skin/classic/global/config.css (../../shared/config.css) - skin/classic/global/timepicker.css (../../shared/timepicker.css) + skin/classic/global/datetimeinputpickers.css (../../shared/datetimeinputpickers.css) skin/classic/global/icons/find-arrows.svg (../../shared/icons/find-arrows.svg) skin/classic/global/icons/info.svg (../../shared/incontent-icons/info.svg) skin/classic/global/icons/input-clear.svg (../../shared/icons/input-clear.svg) diff --git a/toolkit/themes/shared/timepicker.css b/toolkit/themes/shared/timepicker.css deleted file mode 100644 index ca49557487..0000000000 --- a/toolkit/themes/shared/timepicker.css +++ /dev/null @@ -1,153 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -:root { - --font-size-default: 1.1rem; - --spinner-width: 3rem; - --spinner-margin-top-bottom: 0.4rem; - --spinner-item-height: 2.4rem; - --spinner-item-margin-bottom: 0.1rem; - --spinner-button-height: 1.2rem; - --colon-width: 2rem; - --day-period-spacing-width: 1rem; - - --border: 0.1rem solid #D6D6D6; - --border-radius: 0.3rem; - - --font-color: #191919; - --fill-color: #EBEBEB; - - --selected-font-color: #FFFFFF; - --selected-fill-color: #0996F8; - - --button-font-color: #858585; - --button-font-color-hover: #4D4D4D; - --button-font-color-active: #191919; - - --disabled-opacity: 0.2; -} - -html { - font-size: 10px; -} - -body { - margin: 0; - color: var(--font-color); - font-size: var(--font-size-default); -} - -#time-picker { - display: flex; - flex-direction: row; - justify-content: space-around; -} - -.spinner-container { - font-family: sans-serif; - display: flex; - flex-direction: column; - width: var(--spinner-width); -} - -.spinner-container > button { - -moz-appearance: none; - border: none; - background: none; - background-color: var(--button-font-color); - height: var(--spinner-button-height); -} - -.spinner-container > button:hover { - background-color: var(--button-font-color-hover); -} - -.spinner-container > button.active { - background-color: var(--button-font-color-active); -} - -.spinner-container > button.up { - background: url("chrome://global/skin/icons/spinner-arrows.svg#up") no-repeat 50% 50%; -} - -.spinner-container > button.down { - background: url("chrome://global/skin/icons/spinner-arrows.svg#down") no-repeat 50% 50%; -} - -.spinner-container.hide-buttons > button { - visibility: hidden; -} - -.spinner-container > .spinner { - position: relative; - width: 100%; - margin: var(--spinner-margin-top-bottom) 0; - cursor: default; - overflow-y: scroll; - scroll-snap-type: mandatory; - scroll-snap-points-y: repeat(100%); -} - -.spinner-container > .spinner > div { - box-sizing: border-box; - position: relative; - text-align: center; - padding: calc((var(--spinner-item-height) - var(--font-size-default)) / 2) 0; - margin-bottom: var(--spinner-item-margin-bottom); - height: var(--spinner-item-height); - -moz-user-select: none; - scroll-snap-coordinate: 0 0; -} - -.spinner-container > .spinner > div:hover::before { - background: var(--fill-color); - border: var(--border); - border-radius: var(--border-radius); - content: ""; - position: absolute; - top: 0%; - bottom: 0%; - left: 0%; - right: 0%; - z-index: -10; -} - -.spinner-container > .spinner:not(.scrolling) > div.selection { - color: var(--selected-font-color); -} - -.spinner-container > .spinner > div.selection::before { - background: var(--selected-fill-color); - border: none; - border-radius: var(--border-radius); - content: ""; - position: absolute; - top: 0%; - bottom: 0%; - left: 0%; - right: 0%; - z-index: -10; -} - -.spinner-container > .spinner > div.disabled::before, -.spinner-container > .spinner.scrolling > div.selection::before, -.spinner-container > .spinner.scrolling > div:hover::before { - display: none; -} - -.spinner-container > .spinner > div.disabled { - opacity: var(--disabled-opacity); -} - -.colon { - display: flex; - justify-content: center; - align-items: center; - width: var(--colon-width); - margin-bottom: 0.3rem; -} - -.spacer { - width: var(--day-period-spacing-width); -} \ No newline at end of file -- cgit v1.2.3 From 260e399a1ff7756cda9b6320a801f1198b27604a Mon Sep 17 00:00:00 2001 From: janekptacijarabaci Date: Wed, 14 Feb 2018 13:24:40 +0100 Subject: Bug 1323109: [DateTimePicker] Date time picker popup does not open near input box --- toolkit/content/widgets/datetimepopup.xml | 3 +++ toolkit/modules/DateTimePickerHelper.jsm | 2 +- toolkit/themes/shared/datetimepopup.css | 11 +++++++++++ toolkit/themes/shared/jar.inc.mn | 1 + 4 files changed, 16 insertions(+), 1 deletion(-) create mode 100644 toolkit/themes/shared/datetimepopup.css diff --git a/toolkit/content/widgets/datetimepopup.xml b/toolkit/content/widgets/datetimepopup.xml index 327f453684..69edffe259 100644 --- a/toolkit/content/widgets/datetimepopup.xml +++ b/toolkit/content/widgets/datetimepopup.xml @@ -11,6 +11,9 @@ xmlns:xbl="http://www.mozilla.org/xbl"> + + + this.querySelector("#dateTimePopupFrame"); diff --git a/toolkit/modules/DateTimePickerHelper.jsm b/toolkit/modules/DateTimePickerHelper.jsm index 3986879886..8ad5204f45 100644 --- a/toolkit/modules/DateTimePickerHelper.jsm +++ b/toolkit/modules/DateTimePickerHelper.jsm @@ -141,7 +141,7 @@ this.DateTimePickerHelper = { this.picker.loadPicker(type, detail); // The arrow panel needs an anchor to work. The popupAnchor (this._anchor) // is a transparent div that the arrow can point to. - this.picker.openPopup(this._anchor, "after_start", rect.left, rect.top); + this.picker.openPopup(this._anchor, "after_start", 0, 0); this.addPickerListeners(); }, diff --git a/toolkit/themes/shared/datetimepopup.css b/toolkit/themes/shared/datetimepopup.css new file mode 100644 index 0000000000..52f6fc7a2d --- /dev/null +++ b/toolkit/themes/shared/datetimepopup.css @@ -0,0 +1,11 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"); + +panel[type="arrow"][side="top"], +panel[type="arrow"][side="bottom"] { + margin-left: 0; + margin-right: 0; +} diff --git a/toolkit/themes/shared/jar.inc.mn b/toolkit/themes/shared/jar.inc.mn index e7f1f7c676..39acff1c64 100644 --- a/toolkit/themes/shared/jar.inc.mn +++ b/toolkit/themes/shared/jar.inc.mn @@ -22,6 +22,7 @@ toolkit.jar: skin/classic/global/appPicker.css (../../shared/appPicker.css) skin/classic/global/config.css (../../shared/config.css) skin/classic/global/datetimeinputpickers.css (../../shared/datetimeinputpickers.css) + skin/classic/global/datetimepopup.css (../../shared/datetimepopup.css) skin/classic/global/icons/find-arrows.svg (../../shared/icons/find-arrows.svg) skin/classic/global/icons/info.svg (../../shared/incontent-icons/info.svg) skin/classic/global/icons/input-clear.svg (../../shared/icons/input-clear.svg) -- cgit v1.2.3 From c87dbe6922ec79f988744f5aab0cde1a166292e6 Mon Sep 17 00:00:00 2001 From: janekptacijarabaci Date: Wed, 14 Feb 2018 13:38:47 +0100 Subject: Bug 1325922: [DateTimePicker] Add arrows svg file and style month-year button for date picker --- toolkit/content/datepicker.xhtml | 8 ++-- toolkit/content/widgets/datepicker.js | 19 +++++++- toolkit/content/widgets/spinner.js | 9 ++-- toolkit/content/widgets/timepicker.js | 7 +++ toolkit/themes/shared/datetimeinputpickers.css | 64 +++++++++++++++++++++---- toolkit/themes/shared/icons/calendar-arrows.svg | 13 +++++ toolkit/themes/shared/jar.inc.mn | 1 + 7 files changed, 101 insertions(+), 20 deletions(-) create mode 100644 toolkit/themes/shared/icons/calendar-arrows.svg diff --git a/toolkit/content/datepicker.xhtml b/toolkit/content/datepicker.xhtml index abc7db3039..d79b531020 100644 --- a/toolkit/content/datepicker.xhtml +++ b/toolkit/content/datepicker.xhtml @@ -16,8 +16,8 @@
@@ -25,8 +25,8 @@
-
-
+
+
diff --git a/toolkit/content/widgets/datepicker.js b/toolkit/content/widgets/datepicker.js index d3f0fd1a43..7453b67eb3 100644 --- a/toolkit/content/widgets/datepicker.js +++ b/toolkit/content/widgets/datepicker.js @@ -174,7 +174,8 @@ function DatePicker(context) { */ _attachEventListeners() { window.addEventListener("message", this); - document.addEventListener("click", this); + document.addEventListener("mouseup", this, { passive: true }); + document.addEventListener("mousedown", this); }, /** @@ -188,16 +189,28 @@ function DatePicker(context) { this.handleMessage(event); break; } - case "click": { + case "mousedown": { + // Use preventDefault to keep focus on input boxes + event.preventDefault(); + event.target.setCapture(); + if (event.target == this.context.buttonLeft) { + event.target.classList.add("active"); this.state.dateKeeper.setMonthByOffset(-1); this._update(); } else if (event.target == this.context.buttonRight) { + event.target.classList.add("active"); this.state.dateKeeper.setMonthByOffset(1); this._update(); } break; } + case "mouseup": { + if (event.target == this.context.buttonLeft || event.target == this.context.buttonRight) { + event.target.classList.remove("active"); + } + + } } }, @@ -307,6 +320,7 @@ function DatePicker(context) { this.context.monthYear.textContent = this.state.dateFormat(props.dateObj); if (props.isVisible) { + this.context.monthYear.classList.add("active"); this.components.month.setState({ value: props.month, items: props.months, @@ -323,6 +337,7 @@ function DatePicker(context) { }); this.state.firstOpened = false; } else { + this.context.monthYear.classList.remove("active"); this.state.isMonthSet = false; this.state.isYearSet = false; this.state.firstOpened = true; diff --git a/toolkit/content/widgets/spinner.js b/toolkit/content/widgets/spinner.js index b3d996fb50..059e151fc0 100644 --- a/toolkit/content/widgets/spinner.js +++ b/toolkit/content/widgets/spinner.js @@ -268,11 +268,11 @@ function Spinner(props, context) { * Attach event listeners to the spinner and buttons. */ _attachEventListeners() { - const { spinner } = this.elements; + const { spinner, container } = this.elements; spinner.addEventListener("scroll", this, { passive: true }); - document.addEventListener("mouseup", this, { passive: true }); - document.addEventListener("mousedown", this); + container.addEventListener("mouseup", this, { passive: true }); + container.addEventListener("mousedown", this, { passive: true }); }, /** @@ -290,9 +290,6 @@ function Spinner(props, context) { break; } case "mousedown": { - // Use preventDefault to keep focus on input boxes - event.preventDefault(); - event.target.setCapture(); this.state.mouseState = { down: true, layerX: event.layerX, diff --git a/toolkit/content/widgets/timepicker.js b/toolkit/content/widgets/timepicker.js index f438e9ec60..544032e9f6 100644 --- a/toolkit/content/widgets/timepicker.js +++ b/toolkit/content/widgets/timepicker.js @@ -218,6 +218,7 @@ function TimePicker(context) { }, _attachEventListeners() { window.addEventListener("message", this); + document.addEventListener("mousedown", this); }, /** @@ -231,6 +232,12 @@ function TimePicker(context) { this.handleMessage(event); break; } + case "mousedown": { + // Use preventDefault to keep focus on input boxes + event.preventDefault(); + event.target.setCapture(); + break; + } } }, diff --git a/toolkit/themes/shared/datetimeinputpickers.css b/toolkit/themes/shared/datetimeinputpickers.css index df93bc3a1f..741f15281f 100644 --- a/toolkit/themes/shared/datetimeinputpickers.css +++ b/toolkit/themes/shared/datetimeinputpickers.css @@ -16,6 +16,7 @@ --border: 0.1rem solid #D6D6D6; --border-radius: 0.3rem; + --border-active-color: #B1B1B1; --font-color: #191919; --fill-color: #EBEBEB; @@ -26,6 +27,7 @@ --button-font-color: #858585; --button-font-color-hover: #4D4D4D; --button-font-color-active: #191919; + --button-fill-color-active: #D4D4D4; --weekday-font-color: #6C6C6C; --weekday-outside-font-color: #6C6C6C; @@ -46,6 +48,12 @@ body { font-size: var(--font-size-default); } +button { + -moz-appearance: none; + background: none; + border: none; +} + .nav { display: flex; width: var(--calendar-width); @@ -54,15 +62,29 @@ body { justify-content: space-between; } -.nav button { - -moz-appearance: none; - background: none; - border: none; +.nav > button { width: 3rem; height: var(--date-picker-item-height); + background-color: var(--button-font-color); +} + +.nav > button:hover { + background-color: var(--button-font-color-hover); +} + +.nav > button.active { + background-color: var(--button-font-color-active); } -.month-year { +.nav > button.left { + background: url("chrome://global/skin/icons/calendar-arrows.svg#left") no-repeat 50% 50%; +} + +.nav > button.right { + background: url("chrome://global/skin/icons/calendar-arrows.svg#right") no-repeat 50% 50%; +} + +.month-year-container { position: absolute; display: flex; justify-content: center; @@ -74,6 +96,35 @@ body { z-index: 10; } +button.month-year { + font-size: 1.3rem; + border: var(--border); + border-radius: 0.3rem; + padding: 0.2rem 2.6rem 0.2rem 1.2rem; +} + +button.month-year:hover { + background: var(--fill-color); +} + +button.month-year.active { + border-color: var(--border-active-color); + background: var(--button-fill-color-active); +} + +button.month-year::after { + position: absolute; + content: ""; + width: 2.6rem; + height: 1.6rem; + background: url("chrome://global/skin/icons/spinner-arrows.svg#down") no-repeat 50% 50%; + background-color: var(--button-font-color); +} + +button.month-year.active::after { + background: url("chrome://global/skin/icons/spinner-arrows.svg#up") no-repeat 50% 50%; +} + .month-year-view { position: absolute; z-index: 5; @@ -187,9 +238,6 @@ body { } .spinner-container > button { - -moz-appearance: none; - border: none; - background: none; background-color: var(--button-font-color); height: var(--spinner-button-height); } diff --git a/toolkit/themes/shared/icons/calendar-arrows.svg b/toolkit/themes/shared/icons/calendar-arrows.svg new file mode 100644 index 0000000000..858676f55b --- /dev/null +++ b/toolkit/themes/shared/icons/calendar-arrows.svg @@ -0,0 +1,13 @@ + + + + + + + diff --git a/toolkit/themes/shared/jar.inc.mn b/toolkit/themes/shared/jar.inc.mn index 39acff1c64..bdfca2a051 100644 --- a/toolkit/themes/shared/jar.inc.mn +++ b/toolkit/themes/shared/jar.inc.mn @@ -23,6 +23,7 @@ toolkit.jar: skin/classic/global/config.css (../../shared/config.css) skin/classic/global/datetimeinputpickers.css (../../shared/datetimeinputpickers.css) skin/classic/global/datetimepopup.css (../../shared/datetimepopup.css) + skin/classic/global/icons/calendar-arrows.svg (../../shared/icons/calendar-arrows.svg) skin/classic/global/icons/find-arrows.svg (../../shared/icons/find-arrows.svg) skin/classic/global/icons/info.svg (../../shared/incontent-icons/info.svg) skin/classic/global/icons/input-clear.svg (../../shared/icons/input-clear.svg) -- cgit v1.2.3 From 631b690ac3fecb1406246237d282390283c77e2c Mon Sep 17 00:00:00 2001 From: janekptacijarabaci Date: Wed, 14 Feb 2018 14:03:55 +0100 Subject: Bug 1320225: [DateTimeInput] Integration of input type=date input box with picker (part 1) --- toolkit/content/widgets/datekeeper.js | 38 +++++++++--------- toolkit/content/widgets/datepicker.js | 44 +++++++++++++------- toolkit/content/widgets/datetimepopup.xml | 67 ++++++++++++++++++++++++++++--- toolkit/content/widgets/timepicker.js | 6 +-- 4 files changed, 113 insertions(+), 42 deletions(-) diff --git a/toolkit/content/widgets/datekeeper.js b/toolkit/content/widgets/datekeeper.js index 62fcfadbc3..de01fdadee 100644 --- a/toolkit/content/widgets/datekeeper.js +++ b/toolkit/content/widgets/datekeeper.js @@ -11,7 +11,7 @@ * { * {Number} year * {Number} month - * {Number} date + * {Number} day * } * {Object} options * { @@ -20,7 +20,7 @@ * {Number} calViewSize [optional] * } */ -function DateKeeper({ year, month, date }, { firstDayOfWeek = 0, weekends = [0], calViewSize = 42 }) { +function DateKeeper({ year, month, day }, { firstDayOfWeek = 0, weekends = [0], calViewSize = 42 }) { this.state = { firstDayOfWeek, weekends, calViewSize, dateObj: new Date(0), @@ -29,7 +29,7 @@ function DateKeeper({ year, month, date }, { firstDayOfWeek = 0, weekends = [0], days: [] }; this.state.weekHeaders = this._getWeekHeaders(firstDayOfWeek); - this._update(year, month, date); + this._update(year, month, day); } { @@ -48,8 +48,8 @@ function DateKeeper({ year, month, date }, { firstDayOfWeek = 0, weekends = [0], * {Number} date [optional] * } */ - set({ year = this.state.year, month = this.state.month, date = this.state.date }) { - this._update(year, month, date); + set({ year = this.state.year, month = this.state.month, day = this.state.day }) { + this._update(year, month, day); }, /** @@ -62,44 +62,44 @@ function DateKeeper({ year, month, date }, { firstDayOfWeek = 0, weekends = [0], }, /** - * Set month. Makes sure the date is <= the last day of the month + * Set month. Makes sure the day is <= the last day of the month * @param {Number} month */ setMonth(month) { const lastDayOfMonth = this._newUTCDate(this.state.year, month + 1, 0).getUTCDate(); - this._update(this.state.year, month, Math.min(this.state.date, lastDayOfMonth)); + this._update(this.state.year, month, Math.min(this.state.day, lastDayOfMonth)); }, /** - * Set year. Makes sure the date is <= the last day of the month + * Set year. Makes sure the day is <= the last day of the month * @param {Number} year */ setYear(year) { const lastDayOfMonth = this._newUTCDate(year, this.state.month + 1, 0).getUTCDate(); - this._update(year, this.state.month, Math.min(this.state.date, lastDayOfMonth)); + this._update(year, this.state.month, Math.min(this.state.day, lastDayOfMonth)); }, /** - * Set month by offset. Makes sure the date is <= the last day of the month + * Set month by offset. Makes sure the day is <= the last day of the month * @param {Number} offset */ setMonthByOffset(offset) { const lastDayOfMonth = this._newUTCDate(this.state.year, this.state.month + offset + 1, 0).getUTCDate(); - this._update(this.state.year, this.state.month + offset, Math.min(this.state.date, lastDayOfMonth)); + this._update(this.state.year, this.state.month + offset, Math.min(this.state.day, lastDayOfMonth)); }, /** * Update the states. * @param {Number} year [description] * @param {Number} month [description] - * @param {Number} date [description] + * @param {Number} day [description] */ - _update(year, month, date) { + _update(year, month, day) { // Use setUTCFullYear so that year 99 doesn't get parsed as 1999 - this.state.dateObj.setUTCFullYear(year, month, date); + this.state.dateObj.setUTCFullYear(year, month, day); this.state.year = this.state.dateObj.getUTCFullYear(); this.state.month = this.state.dateObj.getUTCMonth(); - this.state.date = this.state.dateObj.getUTCDate(); + this.state.day = this.state.dateObj.getUTCDate(); }, /** @@ -201,14 +201,14 @@ function DateKeeper({ year, month, date }, { firstDayOfWeek = 0, weekends = [0], */ _getWeekHeaders(firstDayOfWeek) { let headers = []; - let day = firstDayOfWeek; + let dayOfWeek = firstDayOfWeek; for (let i = 0; i < DAYS_IN_A_WEEK; i++) { headers.push({ - textContent: day % DAYS_IN_A_WEEK, - classNames: this.state.weekends.includes(day % DAYS_IN_A_WEEK) ? ["weekend"] : [] + textContent: dayOfWeek % DAYS_IN_A_WEEK, + classNames: this.state.weekends.includes(dayOfWeek % DAYS_IN_A_WEEK) ? ["weekend"] : [] }); - day++; + dayOfWeek++; } return headers; }, diff --git a/toolkit/content/widgets/datepicker.js b/toolkit/content/widgets/datepicker.js index 7453b67eb3..210ca856cf 100644 --- a/toolkit/content/widgets/datepicker.js +++ b/toolkit/content/widgets/datepicker.js @@ -37,13 +37,13 @@ function DatePicker(context) { const now = new Date(); const { year = now.getFullYear(), month = now.getMonth(), - date = now.getDate(), + day = now.getDate(), locale } = this.props; // TODO: Use calendar info API to get first day of week & weekends // (Bug 1287503) const dateKeeper = new DateKeeper({ - year, month, date + year, month, day }, { calViewSize: CAL_VIEW_SIZE, firstDayOfWeek: 0, @@ -68,6 +68,7 @@ function DatePicker(context) { this.state.isDateSet = true; this._update(); this._dispatchState(); + this._closePopup(); }, setYear: year => { dateKeeper.setYear(year); @@ -148,23 +149,32 @@ function DatePicker(context) { this.context.monthYearView.classList.add("hidden"); }, + /** + * Use postMessage to close the picker. + */ + _closePopup() { + window.postMessage({ + name: "ClosePopup" + }, "*"); + }, + /** * Use postMessage to pass the state of picker to the panel. */ _dispatchState() { - const { year, month, date } = this.state.dateKeeper.state; - const { isYearSet, isMonthSet, isDateSet } = this.state; + const { year, month, day } = this.state.dateKeeper.state; + const { isYearSet, isMonthSet, isDaySet } = this.state; // The panel is listening to window for postMessage event, so we // do postMessage to itself to send data to input boxes. window.postMessage({ - name: "DatePickerPopupChanged", + name: "PickerPopupChanged", detail: { year, month, - date, + day, isYearSet, isMonthSet, - isDateSet + isDaySet } }, "*"); }, @@ -221,11 +231,11 @@ function DatePicker(context) { */ handleMessage(event) { switch (event.data.name) { - case "DatePickerSetValue": { + case "PickerSetValue": { this.set(event.data.detail); break; } - case "DatePickerInit": { + case "PickerInit": { this.init(event.data.detail); break; } @@ -242,18 +252,22 @@ function DatePicker(context) { * {Number} date [optional] * } */ - set(dateState) { - if (dateState.year != undefined) { + set({ year, month, day }) { + const { dateKeeper } = this.state; + + if (year != undefined) { this.state.isYearSet = true; } - if (dateState.month != undefined) { + if (month != undefined) { this.state.isMonthSet = true; } - if (dateState.date != undefined) { - this.state.isDateSet = true; + if (day != undefined) { + this.state.isDaySet = true; } - this.state.dateKeeper.set(dateState); + dateKeeper.set({ + year, month, day + }); this._update(); } }; diff --git a/toolkit/content/widgets/datetimepopup.xml b/toolkit/content/widgets/datetimepopup.xml index 69edffe259..86e8780c15 100644 --- a/toolkit/content/widgets/datetimepopup.xml +++ b/toolkit/content/widgets/datetimepopup.xml @@ -20,6 +20,8 @@ "12em" "21em" + "23.1em" + "20.7em" @@ -38,6 +40,14 @@ this.dateTimePopupFrame.style.height = this.TIME_PICKER_HEIGHT; break; } + case "date": { + this.detail = detail; + this.dateTimePopupFrame.addEventListener("load", this, true); + this.dateTimePopupFrame.setAttribute("src", "chrome://global/content/datepicker.xhtml"); + this.dateTimePopupFrame.style.width = this.DATE_PICKER_WIDTH; + this.dateTimePopupFrame.style.height = this.DATE_PICKER_HEIGHT; + break; + } } ]]> @@ -48,7 +58,7 @@ this.pickerState = {}; this.type = undefined; this.dateTimePopupFrame.removeEventListener("load", this, true); - this.dateTimePopupFrame.contentDocument.removeEventListener("TimePickerPopupChanged", this, false); + this.dateTimePopupFrame.contentDocument.removeEventListener("message", this, false); this.dateTimePopupFrame.setAttribute("src", ""); ]]> @@ -58,25 +68,39 @@ switch (this.type) { case "time": { this.postMessageToPicker({ - name: "TimePickerSetValue", + name: "PickerSetValue", detail: data.value }); break; } + case "date": { + const { year, month, day } = data.value; + this.postMessageToPicker({ + name: "PickerSetValue", + detail: { + year, + // Month value from input box starts from 1 instead of 0 + month: month == undefined ? undefined : month - 1, + day + } + }); + break; + } } ]]> @@ -112,6 +150,10 @@ } break; } + case "date": { + this.sendPickerValueChanged(this.pickerState); + break; + } } ]]> @@ -128,6 +170,17 @@ })); break; } + case "date": { + this.dispatchEvent(new CustomEvent("DateTimePickerValueChanged", { + detail: { + year: value.year, + // Month value from input box starts from 1 instead of 0 + month: value.month == undefined ? undefined : value.month + 1, + day: value.day + } + })); + break; + } } ]]> @@ -155,11 +208,15 @@ } switch (aEvent.data.name) { - case "TimePickerPopupChanged": { + case "PickerPopupChanged": { this.pickerState = aEvent.data.detail; this.setInputBoxValue(); break; } + case "ClosePopup": { + this.closePicker(); + break; + } } ]]> diff --git a/toolkit/content/widgets/timepicker.js b/toolkit/content/widgets/timepicker.js index 544032e9f6..8c2fb89ddb 100644 --- a/toolkit/content/widgets/timepicker.js +++ b/toolkit/content/widgets/timepicker.js @@ -206,7 +206,7 @@ function TimePicker(context) { // The panel is listening to window for postMessage event, so we // do postMessage to itself to send data to input boxes. window.postMessage({ - name: "TimePickerPopupChanged", + name: "PickerPopupChanged", detail: { hour, minute, @@ -248,11 +248,11 @@ function TimePicker(context) { */ handleMessage(event) { switch (event.data.name) { - case "TimePickerSetValue": { + case "PickerSetValue": { this.set(event.data.detail); break; } - case "TimePickerInit": { + case "PickerInit": { this.init(event.data.detail); break; } -- cgit v1.2.3 From 34125a031ed9c7814d2145070294ead44b7504b3 Mon Sep 17 00:00:00 2001 From: janekptacijarabaci Date: Wed, 14 Feb 2018 14:10:13 +0100 Subject: Bug 1314544: [DateTimeInput] browser keyboard shortcut does not work when focus is on input box --- toolkit/content/widgets/datetimebox.css | 2 + toolkit/content/widgets/datetimebox.xml | 208 +++++++++++++++++++++++--------- 2 files changed, 151 insertions(+), 59 deletions(-) diff --git a/toolkit/content/widgets/datetimebox.css b/toolkit/content/widgets/datetimebox.css index 4a9593a697..18ff024c78 100644 --- a/toolkit/content/widgets/datetimebox.css +++ b/toolkit/content/widgets/datetimebox.css @@ -20,6 +20,8 @@ border: 0; margin: 0; ime-mode: disabled; + cursor: default; + -moz-user-select: none; } .datetime-separator { diff --git a/toolkit/content/widgets/datetimebox.xml b/toolkit/content/widgets/datetimebox.xml index 05591e65ad..21cc6c1bdc 100644 --- a/toolkit/content/widgets/datetimebox.xml +++ b/toolkit/content/widgets/datetimebox.xml @@ -282,21 +282,25 @@ if (this.mHourField && !this.mHourField.disabled && !this.mHourField.readOnly) { this.mHourField.value = ""; + this.mHourField.setAttribute("typeBuffer", ""); } if (this.mMinuteField && !this.mMinuteField.disabled && !this.mMinuteField.readOnly) { this.mMinuteField.value = ""; + this.mMinuteField.setAttribute("typeBuffer", ""); } if (this.mSecondField && !this.mSecondField.disabled && !this.mSecondField.readOnly) { this.mSecondField.value = ""; + this.mSecondField.setAttribute("typeBuffer", ""); } if (this.mMillisecField && !this.mMillisecField.disabled && !this.mMillisecField.readOnly) { this.mMillisecField.value = ""; + this.mMillisecField.setAttribute("typeBuffer", ""); } if (this.mDayPeriodField && !this.mDayPeriodField.disabled && @@ -579,9 +583,40 @@ this.mMax = this.mInputElement.max; this.mStep = this.mInputElement.step; this.mIsPickerOpen = false; + + this.EVENTS.forEach((eventName) => { + this.addEventListener(eventName, this, { mozSystemGroup: true }); + }); + // Handle keypress separately since we need to catch it on capturing. + this.addEventListener("keypress", this, { + capture: true, + mozSystemGroup: true + }); ]]> + + { + this.removeEventListener(eventName, this, { mozSystemGroup: true }); + }); + this.removeEventListener("keypress", onKeyPress, { + capture: true, + mozSystemGroup: true + }); + ]]> + + + + + + + + @@ -736,72 +771,127 @@ - - - - - - - - - - + + + + - + + - if (!(event.originalTarget instanceof HTMLButtonElement)) { - this.mInputElement.openDateTimePicker(this.getCurrentValue()); - } - ]]> - + + + + - + + - if (key == "Backspace" || key == "Tab") { - return; - } + + + + + + - if (key == "Enter" || key == " ") { - // Close picker on Enter and Space. - this.mInputElement.closeDateTimePicker(); - } + + + + - - + if (!(aEvent.originalTarget instanceof HTMLButtonElement)) { + this.mInputElement.openDateTimePicker(this.getCurrentValue()); + } + ]]> + + + -- cgit v1.2.3 From 8a15fd8d24e4373f462046b46fbe8558f57f3403 Mon Sep 17 00:00:00 2001 From: janekptacijarabaci Date: Wed, 14 Feb 2018 14:41:19 +0100 Subject: Bug 1286182: Implement the layout for --- accessible/tests/mochitest/jsat/test_output.html | 9 - dom/html/HTMLInputElement.cpp | 23 +- dom/html/nsIFormControl.h | 2 +- dom/html/test/forms/mochitest.ini | 6 +- .../test/forms/test_input_date_key_events.html | 228 +++++++++++ .../test/forms/test_input_datetime_focus_blur.html | 28 +- .../test_input_datetime_focus_blur_events.html | 90 +++++ .../test/forms/test_input_datetime_tabindex.html | 47 ++- .../forms/test_input_time_focus_blur_events.html | 82 ---- .../test/forms/test_input_typing_sanitization.html | 28 ++ layout/base/nsCSSFrameConstructor.cpp | 4 +- layout/forms/nsDateTimeControlFrame.cpp | 3 +- layout/style/res/html.css | 5 + .../satchel/test/test_form_autocomplete.html | 12 +- toolkit/content/widgets/datetimebox.xml | 420 ++++++++++++++++++++- 15 files changed, 846 insertions(+), 141 deletions(-) create mode 100644 dom/html/test/forms/test_input_date_key_events.html create mode 100644 dom/html/test/forms/test_input_datetime_focus_blur_events.html delete mode 100644 dom/html/test/forms/test_input_time_focus_blur_events.html diff --git a/accessible/tests/mochitest/jsat/test_output.html b/accessible/tests/mochitest/jsat/test_output.html index ec2b289bea..5256426070 100644 --- a/accessible/tests/mochitest/jsat/test_output.html +++ b/accessible/tests/mochitest/jsat/test_output.html @@ -124,14 +124,6 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=753984 "Peaches", {"string": "linkAbbr"}, "Plums", {"string": "linkAbbr"}, {"string": "listAbbr"}, {"string": "cellInfoAbbr", "args": [ 1, 1]}]] - }, { - accOrElmOrID: "date", - expectedUtterance: [[{"string": "textInputType_date"}, - {"string": "entry"}, "2011-09-29"], ["2011-09-29", - {"string": "textInputType_date"}, {"string": "entry"}]], - expectedBraille: [[{"string": "textInputType_date"}, - {"string": "entryAbbr"}, "2011-09-29"], ["2011-09-29", - {"string": "textInputType_date"}, {"string": "entryAbbr"}]] }, { accOrElmOrID: "email", expectedUtterance: [[{"string": "textInputType_email"}, @@ -619,7 +611,6 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=753984 - diff --git a/dom/html/HTMLInputElement.cpp b/dom/html/HTMLInputElement.cpp index 78f74ae0c2..dc755cd022 100644 --- a/dom/html/HTMLInputElement.cpp +++ b/dom/html/HTMLInputElement.cpp @@ -2832,7 +2832,8 @@ HTMLInputElement::GetOwnerDateTimeControl() HTMLInputElement::FromContentOrNull( GetParent()->GetParent()->GetParent()->GetParent()); if (ownerDateTimeControl && - ownerDateTimeControl->mType == NS_FORM_INPUT_TIME) { + (ownerDateTimeControl->mType == NS_FORM_INPUT_TIME || + ownerDateTimeControl->mType == NS_FORM_INPUT_DATE)) { return ownerDateTimeControl; } } @@ -3282,7 +3283,8 @@ HTMLInputElement::SetValueInternal(const nsAString& aValue, uint32_t aFlags) if (frame) { frame->UpdateForValueChange(); } - } else if (mType == NS_FORM_INPUT_TIME && + } else if ((mType == NS_FORM_INPUT_TIME || + mType == NS_FORM_INPUT_DATE) && !IsExperimentalMobileType(mType)) { nsDateTimeControlFrame* frame = do_QueryFrame(GetPrimaryFrame()); if (frame) { @@ -3591,7 +3593,8 @@ HTMLInputElement::Blur(ErrorResult& aError) } } - if (mType == NS_FORM_INPUT_TIME && !IsExperimentalMobileType(mType)) { + if ((mType == NS_FORM_INPUT_TIME || mType == NS_FORM_INPUT_DATE) && + !IsExperimentalMobileType(mType)) { nsDateTimeControlFrame* frame = do_QueryFrame(GetPrimaryFrame()); if (frame) { frame->HandleBlurEvent(); @@ -3618,7 +3621,8 @@ HTMLInputElement::Focus(ErrorResult& aError) } } - if (mType == NS_FORM_INPUT_TIME && !IsExperimentalMobileType(mType)) { + if ((mType == NS_FORM_INPUT_TIME || mType == NS_FORM_INPUT_DATE) && + !IsExperimentalMobileType(mType)) { nsDateTimeControlFrame* frame = do_QueryFrame(GetPrimaryFrame()); if (frame) { frame->HandleFocusEvent(); @@ -3956,7 +3960,7 @@ HTMLInputElement::PreHandleEvent(EventChainPreVisitor& aVisitor) } } - if (mType == NS_FORM_INPUT_TIME && + if ((mType == NS_FORM_INPUT_TIME || mType == NS_FORM_INPUT_DATE) && !IsExperimentalMobileType(mType) && aVisitor.mEvent->mMessage == eFocus && aVisitor.mEvent->mOriginalTarget == this) { @@ -4083,7 +4087,8 @@ HTMLInputElement::PreHandleEvent(EventChainPreVisitor& aVisitor) // Stop the event if the related target's first non-native ancestor is the // same as the original target's first non-native ancestor (we are moving // inside of the same element). - if (mType == NS_FORM_INPUT_TIME && !IsExperimentalMobileType(mType) && + if ((mType == NS_FORM_INPUT_TIME || mType == NS_FORM_INPUT_DATE) && + !IsExperimentalMobileType(mType) && (aVisitor.mEvent->mMessage == eFocus || aVisitor.mEvent->mMessage == eFocusIn || aVisitor.mEvent->mMessage == eFocusOut || @@ -7161,13 +7166,15 @@ HTMLInputElement::IsHTMLFocusable(bool aWithMouse, bool* aIsFocusable, int32_t* if (mType == NS_FORM_INPUT_FILE || mType == NS_FORM_INPUT_NUMBER || - mType == NS_FORM_INPUT_TIME) { + mType == NS_FORM_INPUT_TIME || + mType == NS_FORM_INPUT_DATE) { if (aTabIndex) { // We only want our native anonymous child to be tabable to, not ourself. *aTabIndex = -1; } if (mType == NS_FORM_INPUT_NUMBER || - mType == NS_FORM_INPUT_TIME) { + mType == NS_FORM_INPUT_TIME || + mType == NS_FORM_INPUT_DATE) { *aIsFocusable = true; } else { *aIsFocusable = defaultFocusable; diff --git a/dom/html/nsIFormControl.h b/dom/html/nsIFormControl.h index aaa92146ce..e07f7c8293 100644 --- a/dom/html/nsIFormControl.h +++ b/dom/html/nsIFormControl.h @@ -270,8 +270,8 @@ nsIFormControl::IsSingleLineTextControl(bool aExcludePassword, uint32_t aType) #if defined(MOZ_WIDGET_ANDROID) || defined(MOZ_WIDGET_GONK) // On Android/B2G, date/time input appears as a normal text box. aType == NS_FORM_INPUT_TIME || -#endif aType == NS_FORM_INPUT_DATE || +#endif aType == NS_FORM_INPUT_MONTH || aType == NS_FORM_INPUT_WEEK || aType == NS_FORM_INPUT_DATETIME_LOCAL || diff --git a/dom/html/test/forms/mochitest.ini b/dom/html/test/forms/mochitest.ini index 35955b189b..6fceefd981 100644 --- a/dom/html/test/forms/mochitest.ini +++ b/dom/html/test/forms/mochitest.ini @@ -30,8 +30,12 @@ skip-if = os == "android" # up/down arrow keys not supported on android skip-if = android_version == '18' # Android, bug 1147974 [test_input_color_picker_update.html] skip-if = android_version == '18' # Android, bug 1147974 +[test_input_date_key_events.html] +skip-if = os == "android" [test_input_datetime_focus_blur.html] skip-if = os == "android" +[test_input_datetime_focus_blur_events.html] +skip-if = os == "android" [test_input_datetime_tabindex.html] skip-if = os == "android" [test_input_defaultValue.html] @@ -61,8 +65,6 @@ skip-if = os == "android" [test_input_textarea_set_value_no_scroll.html] [test_input_time_key_events.html] skip-if = os == "android" -[test_input_time_focus_blur_events.html] -skip-if = os == "android" [test_input_types_pref.html] [test_input_typing_sanitization.html] [test_input_untrusted_key_events.html] diff --git a/dom/html/test/forms/test_input_date_key_events.html b/dom/html/test/forms/test_input_date_key_events.html new file mode 100644 index 0000000000..cd974e5057 --- /dev/null +++ b/dom/html/test/forms/test_input_date_key_events.html @@ -0,0 +1,228 @@ + + + + + Test key events for time control + + + + + + +Mozilla Bug 1286182 +

+
+ +
+
+
+
+ + diff --git a/dom/html/test/forms/test_input_datetime_focus_blur.html b/dom/html/test/forms/test_input_datetime_focus_blur.html index 5b8d95b25e..85f7b4bb4f 100644 --- a/dom/html/test/forms/test_input_datetime_focus_blur.html +++ b/dom/html/test/forms/test_input_datetime_focus_blur.html @@ -4,7 +4,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=1288591 --> - Test focus/blur behaviour for <input type='time'> + Test focus/blur behaviour for date/time input types @@ -12,7 +12,8 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=1288591 Mozilla Bug 1288591

- + +
 
 
diff --git a/dom/html/test/forms/test_input_datetime_focus_blur_events.html b/dom/html/test/forms/test_input_datetime_focus_blur_events.html new file mode 100644 index 0000000000..873dda627d --- /dev/null +++ b/dom/html/test/forms/test_input_datetime_focus_blur_events.html @@ -0,0 +1,90 @@ + + + + +Test for Bug 1301306 + + + + + +Mozilla Bug 722599 +

+
+ + +
+
+
+
+ + diff --git a/dom/html/test/forms/test_input_datetime_tabindex.html b/dom/html/test/forms/test_input_datetime_tabindex.html index fb7c9b2f19..8023ccf9be 100644 --- a/dom/html/test/forms/test_input_datetime_tabindex.html +++ b/dom/html/test/forms/test_input_datetime_tabindex.html @@ -4,7 +4,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=1288591 --> - Test tabindex attribute for <input type='time'> + Test tabindex attribute for date/time input types @@ -16,13 +16,16 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=1288591 + + +
 
 
diff --git a/dom/html/test/forms/test_input_time_focus_blur_events.html b/dom/html/test/forms/test_input_time_focus_blur_events.html deleted file mode 100644 index 4837414774..0000000000 --- a/dom/html/test/forms/test_input_time_focus_blur_events.html +++ /dev/null @@ -1,82 +0,0 @@ - - - - -Test for Bug 1301306 - - - - - -Mozilla Bug 722599 -

-
- -
-
-
-
- - diff --git a/dom/html/test/forms/test_input_typing_sanitization.html b/dom/html/test/forms/test_input_typing_sanitization.html index 0896f19df8..eee300b33f 100644 --- a/dom/html/test/forms/test_input_typing_sanitization.html +++ b/dom/html/test/forms/test_input_typing_sanitization.html @@ -26,6 +26,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=765772 * This test checks that when a user types in some input types, it will not be * in a state where the value will be un-sanitized and usable (by a script). */ +const isDesktop = !/Mobile|Tablet/.test(navigator.userAgent); var input = document.getElementById('i'); var form = document.getElementById('f'); @@ -143,6 +144,7 @@ function runTest() ] }, { + mobileOnly: true, type: 'date', validData: [ '0001-01-01', @@ -160,6 +162,28 @@ function runTest() '1000-12-99', ] }, + { + mobileOnly: true, + type: 'time', + validData: [ + '00:00', + '09:09:00', + '08:23:23.1', + '21:43:56.12', + '23:12:45.100', + ], + invalidData: [ + '00:', + '00:00:', + '25:00', + '-00:00', + '00:00:00.', + '00:60', + '10:58:99', + ':19:10', + '23:08:09.1012', + ] + }, { type: 'month', validData: [ @@ -218,6 +242,10 @@ function runTest() for (test of data) { gCurrentTest = test; + if (gCurrentTest.mobileOnly && isDesktop) { + continue; + } + input.type = test.type; gValidData = test.validData; gInvalidData = test.invalidData; diff --git a/layout/base/nsCSSFrameConstructor.cpp b/layout/base/nsCSSFrameConstructor.cpp index a118c38f93..f8c7f52a9e 100644 --- a/layout/base/nsCSSFrameConstructor.cpp +++ b/layout/base/nsCSSFrameConstructor.cpp @@ -3658,13 +3658,13 @@ nsCSSFrameConstructor::FindInputData(Element* aElement, nsCSSAnonBoxes::buttonContent) }, // TODO: this is temporary until a frame is written: bug 635240. SIMPLE_INT_CREATE(NS_FORM_INPUT_NUMBER, NS_NewNumberControlFrame), - // TODO: this is temporary until a frame is written: bug 888320. - SIMPLE_INT_CREATE(NS_FORM_INPUT_DATE, NS_NewTextControlFrame), #if defined(MOZ_WIDGET_ANDROID) || defined(MOZ_WIDGET_GONK) // On Android/B2G, date/time input appears as a normal text box. SIMPLE_INT_CREATE(NS_FORM_INPUT_TIME, NS_NewTextControlFrame), + SIMPLE_INT_CREATE(NS_FORM_INPUT_DATE, NS_NewTextControlFrame), #else SIMPLE_INT_CREATE(NS_FORM_INPUT_TIME, NS_NewDateTimeControlFrame), + SIMPLE_INT_CREATE(NS_FORM_INPUT_DATE, NS_NewDateTimeControlFrame), #endif // TODO: this is temporary until a frame is written: bug 888320 SIMPLE_INT_CREATE(NS_FORM_INPUT_MONTH, NS_NewTextControlFrame), diff --git a/layout/forms/nsDateTimeControlFrame.cpp b/layout/forms/nsDateTimeControlFrame.cpp index df2e43986b..fa22dcebac 100644 --- a/layout/forms/nsDateTimeControlFrame.cpp +++ b/layout/forms/nsDateTimeControlFrame.cpp @@ -372,7 +372,8 @@ nsDateTimeControlFrame::AttributeChanged(int32_t aNameSpaceID, auto contentAsInputElem = static_cast(mContent); // If script changed the 's type before setting these attributes // then we don't need to do anything since we are going to be reframed. - if (contentAsInputElem->GetType() == NS_FORM_INPUT_TIME) { + if (contentAsInputElem->GetType() == NS_FORM_INPUT_TIME || + contentAsInputElem->GetType() == NS_FORM_INPUT_DATE) { if (aAttribute == nsGkAtoms::value) { nsCOMPtr inputAreaContent = do_QueryInterface(mInputAreaContent); diff --git a/layout/style/res/html.css b/layout/style/res/html.css index a779461de9..bc3f082100 100644 --- a/layout/style/res/html.css +++ b/layout/style/res/html.css @@ -774,6 +774,11 @@ input[type="time"] > xul|datetimebox { -moz-binding: url("chrome://global/content/bindings/datetimebox.xml#time-input"); } +input[type="date"] > xul|datetimebox { + display: flex; + -moz-binding: url("chrome://global/content/bindings/datetimebox.xml#date-input"); +} + /* details & summary */ /* Need to revert Bug 1259889 Part 2 when removing details preference. */ @supports -moz-bool-pref("dom.details_element.enabled") { diff --git a/toolkit/components/satchel/test/test_form_autocomplete.html b/toolkit/components/satchel/test/test_form_autocomplete.html index 4cf09117a0..d2c22a3db0 100644 --- a/toolkit/components/satchel/test/test_form_autocomplete.html +++ b/toolkit/components/satchel/test/test_form_autocomplete.html @@ -172,7 +172,7 @@ function setupFormHistory(aCallback) { { op : "add", fieldname : "field8", value : "value" }, { op : "add", fieldname : "field9", value : "value" }, { op : "add", fieldname : "field10", value : "42" }, - { op : "add", fieldname : "field11", value : "2010-10-10" }, + { op : "add", fieldname : "field11", value : "2010-10-10" }, // not used, since type=date doesn't have autocomplete currently { op : "add", fieldname : "field12", value : "21:21" }, // not used, since type=time doesn't have autocomplete currently { op : "add", fieldname : "field13", value : "32" }, // not used, since type=range doesn't have a drop down menu { op : "add", fieldname : "field14", value : "#ffffff" }, // not used, since type=color doesn't have autocomplete currently @@ -899,15 +899,13 @@ function runTest() { input = $_(14, "field11"); restoreForm(); - expectPopup(); - doKey("down"); + waitForMenuChange(0); break; case 405: - checkMenuEntries(["2010-10-10"]); - doKey("down"); - doKey("return"); - checkForm("2010-10-10"); + checkMenuEntries([]); // type=date with it's own control frame does not + // have a drop down menu for now + checkForm(""); input = $_(15, "field12"); restoreForm(); diff --git a/toolkit/content/widgets/datetimebox.xml b/toolkit/content/widgets/datetimebox.xml index 21cc6c1bdc..677d3fc21b 100644 --- a/toolkit/content/widgets/datetimebox.xml +++ b/toolkit/content/widgets/datetimebox.xml @@ -10,6 +10,405 @@ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:xbl="http://www.mozilla.org/xbl"> + + + + + + + + + + input box + this.mMonthPlaceHolder = "mm"; + this.mDayPlaceHolder = "dd"; + this.mYearPlaceHolder = "yyyy"; + this.mSeparatorText = "/"; + this.mMinMonth = 1; + this.mMaxMonth = 12; + this.mMinDay = 1; + this.mMaxDay = 31; + this.mMinYear = 1; + // Maximum year limited by ECMAScript date object range, year <= 275760. + this.mMaxYear = 275760; + this.mMonthDayLength = 2; + this.mYearLength = 4; + this.mMonthPageUpDownInterval = 3; + this.mDayPageUpDownInterval = 7; + this.mYearPageUpDownInterval = 10; + + // Default to en-US, month-day-year order. + this.mMonthField = + document.getAnonymousElementByAttribute(this, "anonid", "input-one"); + this.mDayField = + document.getAnonymousElementByAttribute(this, "anonid", "input-two"); + this.mYearField = + document.getAnonymousElementByAttribute(this, "anonid", "input-three"); + this.mYearField.size = this.mYearLength; + this.mYearField.maxLength = this.mMaxYear.toString().length; + + this.mMonthField.placeholder = this.mMonthPlaceHolder; + this.mDayField.placeholder = this.mDayPlaceHolder; + this.mYearField.placeholder = this.mYearPlaceHolder; + + this.mMonthField.setAttribute("min", this.mMinMonth); + this.mMonthField.setAttribute("max", this.mMaxMonth); + this.mMonthField.setAttribute("pginterval", + this.mMonthPageUpDownInterval); + this.mDayField.setAttribute("min", this.mMinDay); + this.mDayField.setAttribute("max", this.mMaxDay); + this.mDayField.setAttribute("pginterval", this.mDayPageUpDownInterval); + this.mYearField.setAttribute("min", this.mMinYear); + this.mYearField.setAttribute("max", this.mMaxYear); + this.mYearField.setAttribute("pginterval", + this.mYearPageUpDownInterval); + + this.mDaySeparator = + document.getAnonymousElementByAttribute(this, "anonid", "sep-first"); + this.mDaySeparator.textContent = this.mSeparatorText; + this.mYearSeparator = + document.getAnonymousElementByAttribute(this, "anonid", "sep-second"); + this.mYearSeparator.textContent = this.mSeparatorText; + + if (this.mInputElement.value) { + this.setFieldsFromInputValue(); + } + ]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + max) { + return true; + } + + return false; + ]]> + + + + + + this.getDaysInMonth(month, year)) { + // Don't set invalid date, otherwise input element's value will be + // set to empty. + return; + } + + let date = [year, month, day].join("-"); + + this.log("setInputValueFromFields: " + date); + this.mInputElement.setUserInput(date); + ]]> + + + + + + + + + + + + + = targetField.maxLength || n * 10 > max) { + buffer = ""; + this.advanceToNextField(); + } + targetField.setAttribute("typeBuffer", buffer); + } + ]]> + + + + + + + + max) { + value -= (max - min + 1); + } else if (value < min) { + value += (max - min + 1); + } + this.setFieldValue(aTargetField, value); + aTargetField.select(); + ]]> + + + + + + + + + + + + + + + + + + + + + max) { + value = max; + } + } + + if (aField == this.mMonthField || + aField == this.mDayField) { + // prepend zero + if (value < 10) { + value = "0" + value; + } + } else { + // prepend zeroes + if (value < 10) { + value = "000" + value; + } else if (value < 100) { + value = "00" + value; + } else if (value < 1000) { + value = "0" + value; + } + + if (value.toString().length > this.mYearLength && + value.toString().length <= this.mMaxYear.toString().length) { + this.mYearField.size = value.toString().length; + } + } + + aField.value = value; + ]]> + + + + + + + + + + + + @@ -745,6 +1144,12 @@ + + + throw Components.results.NS_ERROR_NOT_IMPLEMENTED; + + + + + + + + + + -- cgit v1.2.3 From 73c6f6b3deb95fff270898dfb3e48c116f714d5c Mon Sep 17 00:00:00 2001 From: janekptacijarabaci Date: Wed, 14 Feb 2018 14:45:58 +0100 Subject: Bug 1320225: [DateTimeInput] Integration of input type=date input box with picker (part 2) --- dom/webidl/HTMLInputElement.webidl | 3 +++ toolkit/content/browser-content.js | 1 + toolkit/content/widgets/datetimebox.xml | 18 ++++++++++++++++-- toolkit/modules/DateTimePickerHelper.jsm | 5 +---- 4 files changed, 21 insertions(+), 6 deletions(-) diff --git a/dom/webidl/HTMLInputElement.webidl b/dom/webidl/HTMLInputElement.webidl index d3d537f84c..99da49ca2d 100644 --- a/dom/webidl/HTMLInputElement.webidl +++ b/dom/webidl/HTMLInputElement.webidl @@ -238,6 +238,9 @@ partial interface HTMLInputElement { dictionary DateTimeValue { long hour; long minute; + long year; + long month; + long day; }; partial interface HTMLInputElement { diff --git a/toolkit/content/browser-content.js b/toolkit/content/browser-content.js index 4ae798fbdc..731b551857 100644 --- a/toolkit/content/browser-content.js +++ b/toolkit/content/browser-content.js @@ -1737,6 +1737,7 @@ let DateTimePickerListener = { } case "MozUpdateDateTimePicker": { let value = this._inputElement.getDateTimeInputBoxValue(); + value.type = this._inputElement.type; sendAsyncMessage("FormDateTime:UpdatePicker", { value }); break; } diff --git a/toolkit/content/widgets/datetimebox.xml b/toolkit/content/widgets/datetimebox.xml index 677d3fc21b..5859f80dd1 100644 --- a/toolkit/content/widgets/datetimebox.xml +++ b/toolkit/content/widgets/datetimebox.xml @@ -199,10 +199,24 @@ + diff --git a/toolkit/modules/DateTimePickerHelper.jsm b/toolkit/modules/DateTimePickerHelper.jsm index 8ad5204f45..769ae0094c 100644 --- a/toolkit/modules/DateTimePickerHelper.jsm +++ b/toolkit/modules/DateTimePickerHelper.jsm @@ -97,13 +97,10 @@ this.DateTimePickerHelper = { // Called when picker value has changed, notify input box about it. updateInputBoxValue: function(aEvent) { - // TODO: parse data based on input type. - const { hour, minute } = aEvent.detail; - debug("hour: " + hour + ", minute: " + minute); let browser = this.weakBrowser ? this.weakBrowser.get() : null; if (browser) { browser.messageManager.sendAsyncMessage( - "FormDateTime:PickerValueChanged", { hour, minute }); + "FormDateTime:PickerValueChanged", aEvent.detail); } }, -- cgit v1.2.3 From 9920472970243912edb8c7f55636fc223909d092 Mon Sep 17 00:00:00 2001 From: janekptacijarabaci Date: Wed, 14 Feb 2018 14:47:09 +0100 Subject: Bug 1341190: Date picker panel and email input validation popup close immediately after opening --- browser/base/content/browser.css | 1 - 1 file changed, 1 deletion(-) diff --git a/browser/base/content/browser.css b/browser/base/content/browser.css index a05b031b2c..7557d15a15 100644 --- a/browser/base/content/browser.css +++ b/browser/base/content/browser.css @@ -815,7 +815,6 @@ html|*#fullscreen-exit-button { .popup-anchor { /* should occupy space but not be visible */ opacity: 0; - visibility: hidden; pointer-events: none; -moz-stack-sizing: ignore; } -- cgit v1.2.3 From eddd0de2ae80e176011f41a5400e81522d53f4f3 Mon Sep 17 00:00:00 2001 From: janekptacijarabaci Date: Wed, 14 Feb 2018 16:10:55 +0100 Subject: [follow up] Bug 1325922 - Add arrows svg file and style month-year button for date picker --- toolkit/content/datepicker.xhtml | 1 - 1 file changed, 1 deletion(-) diff --git a/toolkit/content/datepicker.xhtml b/toolkit/content/datepicker.xhtml index d79b531020..4da6e398f8 100644 --- a/toolkit/content/datepicker.xhtml +++ b/toolkit/content/datepicker.xhtml @@ -24,7 +24,6 @@
-
-- cgit v1.2.3 From e25430117a67f5c898e5e9388ebd44b185d469ab Mon Sep 17 00:00:00 2001 From: janekptacijarabaci Date: Fri, 30 Mar 2018 12:17:17 +0200 Subject: moebius#92: HTML - input - datetime + native in moebius: Bug 1317600: https://bugzilla.mozilla.org/show_bug.cgi?id=1317600 A note - not implemented: Bug 1282768: https://bugzilla.mozilla.org/show_bug.cgi?id=1282768 *.css: filter: url("chrome://global/skin/filters.svg#fill");, fill: Bug 1283385: https://bugzilla.mozilla.org/show_bug.cgi?id=1283385 Bug 1323109: https://bugzilla.mozilla.org/show_bug.cgi?id=1323109 Bug 1314544: https://bugzilla.mozilla.org/show_bug.cgi?id=1314544 Bug 1286182: https://bugzilla.mozilla.org/show_bug.cgi?id=1286182 Bug 1325922: https://bugzilla.mozilla.org/show_bug.cgi?id=1325922 A note - not implemented: Bug 1282768: https://bugzilla.mozilla.org/show_bug.cgi?id=1282768 *.css: filter: url("chrome://global/skin/filters.svg#fill");, fill: Bug 1320225: https://bugzilla.mozilla.org/show_bug.cgi?id=1320225 Bug 1341190: https://bugzilla.mozilla.org/show_bug.cgi?id=1341190 --- browser/base/content/browser.css | 2 +- browser/base/content/browser.xul | 4 +- dom/html/HTMLInputElement.cpp | 185 +++++++++++++---- dom/html/HTMLInputElement.h | 66 +++++- dom/html/test/forms/mochitest.ini | 2 + .../test/forms/test_input_date_key_events.html | 8 +- .../test_input_datetime_input_change_events.html | 88 ++++++++ layout/reftests/forms/input/datetime/reftest.list | 11 + .../datetime/time-content-left-aligned-ref.html | 9 + .../input/datetime/time-content-left-aligned.html | 9 + .../time-reset-button-right-aligned-ref.html | 9 + .../datetime/time-reset-button-right-aligned.html | 10 + .../input/datetime/time-small-height-ref.html | 18 ++ .../forms/input/datetime/time-small-height.html | 19 ++ .../datetime/time-small-width-height-ref.html | 18 ++ .../input/datetime/time-small-width-height.html | 19 ++ .../forms/input/datetime/time-small-width-ref.html | 19 ++ .../forms/input/datetime/time-small-width.html | 20 ++ layout/style/res/forms.css | 5 + toolkit/content/tests/browser/browser.ini | 1 + .../tests/browser/browser_datetime_datepicker.js | 222 +++++++++++++++++++++ toolkit/content/tests/browser/head.js | 85 ++++++++ toolkit/content/widgets/calendar.js | 22 +- toolkit/content/widgets/datekeeper.js | 171 ++++++++++------ toolkit/content/widgets/datepicker.js | 94 ++++----- toolkit/content/widgets/datetimebox.css | 10 +- toolkit/content/widgets/datetimebox.xml | 156 ++++++++++++--- toolkit/content/widgets/datetimepicker.xml | 6 +- toolkit/content/widgets/datetimepopup.xml | 101 +++++++++- toolkit/content/widgets/spinner.js | 4 +- .../locales/en-US/chrome/global/datetimebox.dtd | 9 + toolkit/locales/jar.mn | 1 + toolkit/modules/DateTimePickerHelper.jsm | 22 +- toolkit/themes/shared/datetimeinputpickers.css | 90 ++++++--- 34 files changed, 1267 insertions(+), 248 deletions(-) create mode 100644 dom/html/test/forms/test_input_datetime_input_change_events.html create mode 100644 layout/reftests/forms/input/datetime/time-content-left-aligned-ref.html create mode 100644 layout/reftests/forms/input/datetime/time-content-left-aligned.html create mode 100644 layout/reftests/forms/input/datetime/time-reset-button-right-aligned-ref.html create mode 100644 layout/reftests/forms/input/datetime/time-reset-button-right-aligned.html create mode 100644 layout/reftests/forms/input/datetime/time-small-height-ref.html create mode 100644 layout/reftests/forms/input/datetime/time-small-height.html create mode 100644 layout/reftests/forms/input/datetime/time-small-width-height-ref.html create mode 100644 layout/reftests/forms/input/datetime/time-small-width-height.html create mode 100644 layout/reftests/forms/input/datetime/time-small-width-ref.html create mode 100644 layout/reftests/forms/input/datetime/time-small-width.html create mode 100644 toolkit/content/tests/browser/browser_datetime_datepicker.js create mode 100644 toolkit/locales/en-US/chrome/global/datetimebox.dtd diff --git a/browser/base/content/browser.css b/browser/base/content/browser.css index 7557d15a15..f03f21c3f7 100644 --- a/browser/base/content/browser.css +++ b/browser/base/content/browser.css @@ -557,7 +557,7 @@ toolbar:not(#TabsToolbar) > #personal-bookmarks { transition: none; } -#DateTimePickerPanel { +#DateTimePickerPanel[active="true"] { -moz-binding: url("chrome://global/content/bindings/datetimepopup.xml#datetime-popup"); } diff --git a/browser/base/content/browser.xul b/browser/base/content/browser.xul index ae531e1677..62c2b122ff 100644 --- a/browser/base/content/browser.xul +++ b/browser/base/content/browser.xul @@ -160,8 +160,10 @@ hidden="true" orient="vertical" noautofocus="true" + noautohide="true" consumeoutsideclicks="false" - level="parent"> + level="parent" + tabspecific="true">