Files
sousa-gecko/toolkit/modules/DateTimePickerPanel.sys.mjs
Sam Johnson 762da9af71 Bug 2047046 - Support seconds and milliseconds in the time picker. r=fluent-reviewers,webidl,jules,emilio,bolsson
`<input type="time">` supports seconds and milliseconds and will show fields for them depending on the step and step base. This patch extends the time picker UI to support them as well.

Differential Revision: https://phabricator.services.mozilla.com/D306341
2026-06-15 19:46:42 +00:00

202 lines
5.7 KiB
JavaScript

/* 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/. */
import { InputPickerPanelCommon } from "./InputPickerPanelCommon.sys.mjs";
/** @import {OpenPickerInfo} from "./InputPickerPanelCommon.sys.mjs" */
const TIME_PICKER_WIDTH = "12.8em";
const TIME_PICKER_HEIGHT = "22.8em";
const DATE_PICKER_WIDTH = "24em";
const DATE_PICKER_HEIGHT = "26.9em";
const DATETIME_PICKER_WIDTH = "39.8em";
const DATETIME_PICKER_HEIGHT = "26.9em";
const MS_PER_SECOND = 1000;
const MS_PER_MINUTE = 60 * MS_PER_SECOND;
export class DateTimePickerPanel extends InputPickerPanelCommon {
constructor(element) {
super(element, "chrome://global/content/datetimepicker.xhtml");
}
/**
* Picker window initialization function called when opening the picker
*
* @param {string} type The input element type
* @returns {OpenPickerInfo}
*/
openPickerImpl(type) {
if (
type == "datetime-local" &&
!Services.prefs.getBoolPref("dom.forms.datetime.timepicker")
) {
type = "date";
}
switch (type) {
case "time": {
return {
type,
width: TIME_PICKER_WIDTH,
height: TIME_PICKER_HEIGHT,
};
}
case "date": {
return {
type,
width: DATE_PICKER_WIDTH,
height: DATE_PICKER_HEIGHT,
};
}
case "datetime-local": {
return {
type,
width: DATETIME_PICKER_WIDTH,
height: DATETIME_PICKER_HEIGHT,
};
}
}
throw new Error(`Unexpected type ${type}`);
}
/**
* Popup frame initialization function called when the picker window is loaded
*
* @param {string} type The picker type
* @param {object} detail The argument from the child actor's openPickerImpl
* @returns An argument object to pass to the popup frame
*/
initPickerImpl(type, detail) {
let locale = new Services.intl.Locale(
Services.locale.webExposedLocales[0],
{
calendar: "gregory",
}
).toString();
// Workaround for bug 1418061, while we wait for resolution of
// http://bugs.icu-project.org/trac/ticket/13592: drop the PT region code,
// because it results in "abbreviated" day names that are too long;
// the region-less "pt" locale has shorter forms that are better here.
locale = locale.replace(/^pt-PT/i, "pt");
const dir = Services.locale.isAppLocaleRTL ? "rtl" : "ltr";
const { hour12 } = new Services.intl.DateTimeFormat(locale, {
hour: "numeric",
}).resolvedOptions();
const { year, month, day, hour, minute, second, millisecond } =
detail.value;
const flattenDetail = {
type,
year,
// Month value from input box starts from 1 instead of 0
month: month == undefined ? undefined : month - 1,
day,
hour,
minute,
second,
millisecond,
locale,
dir,
format: hour12 ? "12" : "24",
min: detail.min,
max: detail.max,
step: detail.step,
stepBase: detail.stepBase,
};
if (
type !== "date" &&
Services.prefs.getBoolPref("dom.forms.datetime.timepicker")
) {
flattenDetail.showSeconds =
second != undefined ||
detail.stepBase % MS_PER_MINUTE != 0 ||
detail.step % MS_PER_MINUTE != 0;
flattenDetail.showMilliseconds =
millisecond != undefined ||
detail.stepBase % MS_PER_SECOND != 0 ||
detail.step % MS_PER_SECOND != 0;
}
if (type !== "time") {
const { firstDayOfWeek, weekends } = this.getCalendarInfo(locale);
const monthDisplayNames = new Services.intl.DisplayNames(locale, {
type: "month",
style: "short",
calendar: "gregory",
});
const monthStrings = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12].map(
monthNumber => monthDisplayNames.of(monthNumber)
);
const weekdayDisplayNames = new Services.intl.DisplayNames(locale, {
type: "weekday",
style: "abbreviated",
calendar: "gregory",
});
const weekdayStrings = [
// Weekdays starting Sunday (7) to Saturday (6).
7, 1, 2, 3, 4, 5, 6,
].map(weekday => weekdayDisplayNames.of(weekday));
Object.assign(flattenDetail, {
firstDayOfWeek,
weekends,
monthStrings,
weekdayStrings,
});
}
return flattenDetail;
}
/**
* Input element state updater function called when the picker value is changed
*
* @param {string} type
* @param {object} pickerState
*/
sendPickerValueChangedImpl(type, pickerState) {
let { year, month, day, hour, minute, second, millisecond } = pickerState;
if (month !== undefined) {
// Month value from input box starts from 1 instead of 0
month += 1;
}
switch (type) {
case "time": {
return { hour, minute, second, millisecond };
}
case "date": {
return { year, month, day };
}
case "datetime-local": {
return { year, month, day, hour, minute, second, millisecond };
}
}
throw new Error(`Unexpected type ${type}`);
}
getCalendarInfo(locale) {
const calendarInfo = Services.intl.getCalendarInfo(locale);
// Day of week from calendarInfo starts from 1 as Monday to 7 as Sunday,
// so they need to be mapped to JavaScript convention with 0 as Sunday
// and 6 as Saturday
function toDateWeekday(day) {
return day === 7 ? 0 : day;
}
let firstDayOfWeek = toDateWeekday(calendarInfo.firstDayOfWeek),
weekend = calendarInfo.weekend;
let weekends = weekend.map(toDateWeekday);
return {
firstDayOfWeek,
weekends,
};
}
}