first commit

This commit is contained in:
Stefan Hacker
2026-04-03 09:38:48 +02:00
commit 37ad745546
47450 changed files with 3120798 additions and 0 deletions
@@ -0,0 +1,6 @@
import * as React from 'react';
import type { ITimePickerProps } from './TimePicker.types';
/**
* {@docCategory TimePicker}
*/
export declare const TimePicker: React.FunctionComponent<ITimePickerProps>;
+202
View File
@@ -0,0 +1,202 @@
import { __assign, __rest } from "tslib";
import * as React from 'react';
import { KeyCodes } from '../../Utilities';
import { TimeConstants, addMinutes, formatTimeString, ceilMinuteToIncrement, getDateFromTimeSelection, } from '@fluentui/date-time-utilities';
import { ComboBox } from '../../ComboBox';
import { format } from '../../Utilities';
import { useControllableValue, useConst } from '@fluentui/react-hooks';
var REGEX_SHOW_SECONDS_HOUR_12 = /^((1[0-2]|0?[1-9]):([0-5][0-9]):([0-5][0-9])\s([AaPp][Mm]))$/;
var REGEX_HIDE_SECONDS_HOUR_12 = /^((1[0-2]|0?[1-9]):[0-5][0-9]\s([AaPp][Mm]))$/;
var REGEX_SHOW_SECONDS_HOUR_24 = /^([0-1]?[0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9]$/;
var REGEX_HIDE_SECONDS_HOUR_24 = /^([0-1]?[0-9]|2[0-3]):[0-5][0-9]$/;
var TIME_LOWER_BOUND = 0;
var TIME_UPPER_BOUND = 23;
var getDefaultStrings = function (useHour12, showSeconds) {
var hourUnits = useHour12 ? '12-hour' : '24-hour';
var timeFormat = "hh:mm".concat(showSeconds ? ':ss' : '').concat(useHour12 ? ' AP' : '');
var invalidInputErrorMessage = "Enter a valid time in the ".concat(hourUnits, " format: ").concat(timeFormat);
var timeOutOfBoundsErrorMessage = "Please enter a time within the range of {0} and {1}";
return {
invalidInputErrorMessage: invalidInputErrorMessage,
timeOutOfBoundsErrorMessage: timeOutOfBoundsErrorMessage,
};
};
/**
* {@docCategory TimePicker}
*/
export var TimePicker = function (_a) {
var label = _a.label, _b = _a.increments, increments = _b === void 0 ? 30 : _b, _c = _a.showSeconds, showSeconds = _c === void 0 ? false : _c, _d = _a.allowFreeform, allowFreeform = _d === void 0 ? true : _d, _e = _a.useHour12, useHour12 = _e === void 0 ? false : _e, timeRange = _a.timeRange, _f = _a.strings, strings = _f === void 0 ? getDefaultStrings(useHour12, showSeconds) : _f, defaultValue = _a.defaultValue, value = _a.value, dateAnchor = _a.dateAnchor, onChange = _a.onChange, onFormatDate = _a.onFormatDate, onValidateUserInput = _a.onValidateUserInput, onValidationResult = _a.onValidationResult, rest = __rest(_a, ["label", "increments", "showSeconds", "allowFreeform", "useHour12", "timeRange", "strings", "defaultValue", "value", "dateAnchor", "onChange", "onFormatDate", "onValidateUserInput", "onValidationResult"]);
var _g = React.useState(''), comboBoxText = _g[0], setComboBoxText = _g[1];
var _h = React.useState(), selectedKey = _h[0], setSelectedKey = _h[1];
var _j = React.useState(''), errorMessage = _j[0], setErrorMessage = _j[1];
var fallbackDateAnchor = useConst(new Date());
var _k = useControllableValue(value, defaultValue), selectedTime = _k[0], setSelectedTime = _k[1];
var optionsCount = getDropdownOptionsCount(increments, timeRange);
var internalDateAnchor = dateAnchor || value || defaultValue || fallbackDateAnchor;
var dateStartAnchor = React.useMemo(function () { return getDateAnchor(internalDateAnchor, 'start', increments, timeRange); }, [internalDateAnchor, increments, timeRange]);
var dateEndAnchor = React.useMemo(function () { return getDateAnchor(internalDateAnchor, 'end', increments, timeRange); }, [internalDateAnchor, increments, timeRange]);
var timePickerOptions = React.useMemo(function () {
var optionsList = Array(optionsCount);
for (var i = 0; i < optionsCount; i++) {
optionsList[i] = 0;
}
return optionsList.map(function (_, index) {
var option = addMinutes(dateStartAnchor, increments * index);
option.setSeconds(0);
var formattedTimeString = formatTimeString(option, showSeconds, useHour12);
var optionText = onFormatDate ? onFormatDate(option) : formattedTimeString;
return {
key: formattedTimeString,
text: optionText,
data: option,
};
});
}, [dateStartAnchor, increments, optionsCount, showSeconds, onFormatDate, useHour12]);
React.useEffect(function () {
if (selectedTime && !isNaN(selectedTime.valueOf())) {
var formattedTimeString_1 = formatTimeString(selectedTime, showSeconds, useHour12);
var comboboxOption = timePickerOptions.find(function (option) { return option.key === formattedTimeString_1; });
setSelectedKey(comboboxOption === null || comboboxOption === void 0 ? void 0 : comboboxOption.key);
setComboBoxText(comboboxOption ? comboboxOption.text : formattedTimeString_1);
}
else {
setSelectedKey(null);
setComboBoxText('');
}
}, [selectedTime, timePickerOptions, onFormatDate, showSeconds, useHour12]);
var onInputChange = React.useCallback(function (ev, option, _index, input) {
var validateUserInput = function (userInput) {
var errorMessageToDisplay = '';
var regex;
if (useHour12) {
regex = showSeconds ? REGEX_SHOW_SECONDS_HOUR_12 : REGEX_HIDE_SECONDS_HOUR_12;
}
else {
regex = showSeconds ? REGEX_SHOW_SECONDS_HOUR_24 : REGEX_HIDE_SECONDS_HOUR_24;
}
if (!regex.test(userInput)) {
errorMessageToDisplay = strings.invalidInputErrorMessage;
}
else if (timeRange && strings.timeOutOfBoundsErrorMessage) {
var optionDate = getDateFromTimeSelection(useHour12, dateStartAnchor, userInput);
if (optionDate < dateStartAnchor || optionDate > dateEndAnchor) {
errorMessageToDisplay = format(strings.timeOutOfBoundsErrorMessage, dateStartAnchor.toString(), dateEndAnchor.toString());
}
}
return errorMessageToDisplay;
};
var errorMessageToDisplay = '';
if (input) {
if (allowFreeform && !option) {
if (!onFormatDate) {
// Validate only if user did not add onFormatDate
errorMessageToDisplay = validateUserInput(input);
}
else {
// Use user provided validation if onFormatDate is provided
if (onValidateUserInput) {
errorMessageToDisplay = onValidateUserInput(input);
}
}
}
}
if (onValidationResult && errorMessage !== errorMessageToDisplay) {
// only call onValidationResult if stored errorMessage state value is different from latest error message
onValidationResult(ev, { errorMessage: errorMessageToDisplay });
}
var changedTime;
if (errorMessageToDisplay || (input !== undefined && !input.length)) {
var timeSelection = input || (option === null || option === void 0 ? void 0 : option.text) || '';
setComboBoxText(timeSelection);
setSelectedTime(errorMessageToDisplay ? new Date('invalid') : undefined);
changedTime = new Date('invalid');
}
else {
var updatedTime = void 0;
if ((option === null || option === void 0 ? void 0 : option.data) instanceof Date) {
updatedTime = option.data;
}
else {
var timeSelection = (option === null || option === void 0 ? void 0 : option.key) || input || '';
updatedTime = getDateFromTimeSelection(useHour12, dateStartAnchor, timeSelection);
}
setSelectedTime(updatedTime);
changedTime = updatedTime;
}
onChange === null || onChange === void 0 ? void 0 : onChange(ev, changedTime);
setErrorMessage(errorMessageToDisplay);
}, [
timeRange,
dateStartAnchor,
dateEndAnchor,
allowFreeform,
onFormatDate,
onValidateUserInput,
showSeconds,
useHour12,
strings.invalidInputErrorMessage,
strings.timeOutOfBoundsErrorMessage,
setSelectedTime,
onValidationResult,
onChange,
errorMessage,
]);
var evaluatePressedKey = function (event) {
// eslint-disable-next-line @typescript-eslint/no-deprecated
var charCode = event.charCode;
if (!onFormatDate &&
// Only permit input of digits, space, colon, A/P/M characters
!((charCode >= KeyCodes.zero && charCode <= KeyCodes.colon) ||
charCode === KeyCodes.space ||
charCode === KeyCodes.a ||
charCode === KeyCodes.m ||
charCode === KeyCodes.p)) {
event.preventDefault();
}
};
return (React.createElement(ComboBox, __assign({}, rest, { allowFreeform: allowFreeform, selectedKey: selectedKey, label: label, errorMessage: errorMessage, options: timePickerOptions, onChange: onInputChange, text: comboBoxText,
//eslint-disable-next-line
onKeyPress: evaluatePressedKey, useComboBoxAsMenuWidth: true })));
};
TimePicker.displayName = 'TimePicker';
var getDateAnchor = function (internalDateAnchor, startEnd, increments, timeRange) {
var clampedDateAnchor = new Date(internalDateAnchor.getTime());
if (timeRange) {
var clampedTimeRange = clampTimeRange(timeRange);
var timeRangeHours = startEnd === 'start' ? clampedTimeRange.start : clampedTimeRange.end;
if (clampedDateAnchor.getHours() !== timeRangeHours) {
clampedDateAnchor.setHours(timeRangeHours);
}
}
else if (startEnd === 'end') {
clampedDateAnchor.setDate(clampedDateAnchor.getDate() + 1);
}
clampedDateAnchor.setMinutes(0);
clampedDateAnchor.setSeconds(0);
clampedDateAnchor.setMilliseconds(0);
return ceilMinuteToIncrement(clampedDateAnchor, increments);
};
var clampTimeRange = function (timeRange) {
return {
start: Math.min(Math.max(timeRange.start, TIME_LOWER_BOUND), TIME_UPPER_BOUND),
end: Math.min(Math.max(timeRange.end, TIME_LOWER_BOUND), TIME_UPPER_BOUND),
};
};
var getHoursInRange = function (timeRange) {
var hoursInRange = TimeConstants.HoursInOneDay;
if (timeRange) {
var clampedTimeRange = clampTimeRange(timeRange);
if (clampedTimeRange.start > clampedTimeRange.end) {
hoursInRange = TimeConstants.HoursInOneDay - timeRange.start - timeRange.end;
}
else if (timeRange.end > timeRange.start) {
hoursInRange = timeRange.end - timeRange.start;
}
}
return hoursInRange;
};
var getDropdownOptionsCount = function (increments, timeRange) {
var hoursInRange = getHoursInRange(timeRange);
return Math.floor((TimeConstants.MinutesInOneHour * hoursInRange) / increments);
};
//# sourceMappingURL=TimePicker.js.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1,97 @@
import * as React from 'react';
import type { IComboBox, IComboBoxProps } from '../../ComboBox';
/**
* {@docCategory TimePicker}
* Range of start and end hours to be shown in the TimePicker.
*/
export interface ITimeRange {
/** Start hour (inclusive) for the time range, 0-23 */
start: number;
/** End hour (exclusive) for the time range, 0-23 */
end: number;
}
/**
* {@docCategory TimePicker}
* Localized strings to use in the TimePicker
*/
export interface ITimePickerStrings {
/** Error message to render below the field if input parsing fails. */
invalidInputErrorMessage: string;
/** Error message to render if the user input date is out of bounds. */
timeOutOfBoundsErrorMessage?: string;
}
/**
* {@docCategory TimePicker}
* A type used to represent the TimePicker validation result.
*/
export type TimePickerValidationResultData = {
errorMessage?: string;
};
/**
* {@docCategory TimePicker}
*/
export interface ITimePickerProps extends Omit<IComboBoxProps, 'options' | 'selectedKey' | 'defaultSelectedKey' | 'multiSelect' | 'text' | 'defaultValue' | 'onChange'> {
/**
* Label of the component.
*/
label?: string;
/**
* Time increments, in minutes, of the options in the dropdown.
*/
increments?: number;
/**
* If true, show seconds in the dropdown options and consider seconds for
* default validation purposes.
*/
showSeconds?: boolean;
/**
* If true, use 12-hour time format. Otherwise, use 24-hour format.
*/
useHour12?: boolean;
/**
* If true, the TimePicker allows freeform user input, rather than restricting
* to the default increments.
*
* The input will still be restricted to valid time values.
*/
allowFreeform?: boolean;
/**
* Custom time range to for time options.
*/
timeRange?: ITimeRange;
/**
* Localized strings to use in the TimePicker.
*/
strings?: ITimePickerStrings;
/**
* The uncontrolled default selected time.
* Mutually exclusive with `value`.
*/
defaultValue?: Date;
/**
* A Date representing the selected time. If you provide this, you must maintain selection
* state by observing onChange events and passing a new value in when changed.
* Mutually exclusive with `defaultValue`.
*/
value?: Date;
/**
* The date in which all dropdown options are based off of.
*/
dateAnchor?: Date;
/**
* A callback for receiving a notification when the time has been changed.
*/
onChange?: (event: React.FormEvent<IComboBox>, time: Date) => void;
/**
* Callback to localize the date strings displayed for dropdown options.
*/
onFormatDate?: (date: Date) => string;
/**
* Callback to use custom user-input validation.
*/
onValidateUserInput?: (userInput: string) => string;
/**
* Callback to get validation result.
*/
onValidationResult?: (event: React.FormEvent<IComboBox>, data: TimePickerValidationResultData) => void;
}
@@ -0,0 +1,2 @@
export {};
//# sourceMappingURL=TimePicker.types.js.map
@@ -0,0 +1 @@
{"version":3,"file":"TimePicker.types.js","sourceRoot":"../src/","sources":["components/TimePicker/TimePicker.types.ts"],"names":[],"mappings":"","sourcesContent":["import * as React from 'react';\nimport type { IComboBox, IComboBoxProps } from '../../ComboBox';\n\n/**\n * {@docCategory TimePicker}\n * Range of start and end hours to be shown in the TimePicker.\n */\nexport interface ITimeRange {\n /** Start hour (inclusive) for the time range, 0-23 */\n start: number;\n\n /** End hour (exclusive) for the time range, 0-23 */\n end: number;\n}\n\n/**\n * {@docCategory TimePicker}\n * Localized strings to use in the TimePicker\n */\nexport interface ITimePickerStrings {\n /** Error message to render below the field if input parsing fails. */\n invalidInputErrorMessage: string;\n /** Error message to render if the user input date is out of bounds. */\n timeOutOfBoundsErrorMessage?: string;\n}\n\n/**\n * {@docCategory TimePicker}\n * A type used to represent the TimePicker validation result.\n */\nexport type TimePickerValidationResultData = {\n errorMessage?: string;\n};\n\n/**\n * {@docCategory TimePicker}\n */\nexport interface ITimePickerProps\n extends Omit<\n IComboBoxProps,\n 'options' | 'selectedKey' | 'defaultSelectedKey' | 'multiSelect' | 'text' | 'defaultValue' | 'onChange'\n > {\n /**\n * Label of the component.\n */\n label?: string;\n\n /**\n * Time increments, in minutes, of the options in the dropdown.\n */\n increments?: number;\n\n /**\n * If true, show seconds in the dropdown options and consider seconds for\n * default validation purposes.\n */\n showSeconds?: boolean;\n\n /**\n * If true, use 12-hour time format. Otherwise, use 24-hour format.\n */\n useHour12?: boolean;\n\n /**\n * If true, the TimePicker allows freeform user input, rather than restricting\n * to the default increments.\n *\n * The input will still be restricted to valid time values.\n */\n allowFreeform?: boolean;\n\n /**\n * Custom time range to for time options.\n */\n timeRange?: ITimeRange;\n\n /**\n * Localized strings to use in the TimePicker.\n */\n strings?: ITimePickerStrings;\n\n /**\n * The uncontrolled default selected time.\n * Mutually exclusive with `value`.\n */\n defaultValue?: Date;\n\n /**\n * A Date representing the selected time. If you provide this, you must maintain selection\n * state by observing onChange events and passing a new value in when changed.\n * Mutually exclusive with `defaultValue`.\n */\n value?: Date;\n\n /**\n * The date in which all dropdown options are based off of.\n */\n dateAnchor?: Date;\n\n /**\n * A callback for receiving a notification when the time has been changed.\n */\n onChange?: (event: React.FormEvent<IComboBox>, time: Date) => void;\n\n /**\n * Callback to localize the date strings displayed for dropdown options.\n */\n onFormatDate?: (date: Date) => string;\n\n /**\n * Callback to use custom user-input validation.\n */\n onValidateUserInput?: (userInput: string) => string;\n\n /**\n * Callback to get validation result.\n */\n onValidationResult?: (event: React.FormEvent<IComboBox>, data: TimePickerValidationResultData) => void;\n}\n"]}
+2
View File
@@ -0,0 +1,2 @@
export * from './TimePicker';
export * from './TimePicker.types';
+3
View File
@@ -0,0 +1,3 @@
export * from './TimePicker';
export * from './TimePicker.types';
//# sourceMappingURL=index.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"index.js","sourceRoot":"../src/","sources":["components/TimePicker/index.ts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAC;AAC7B,cAAc,oBAAoB,CAAC","sourcesContent":["export * from './TimePicker';\nexport * from './TimePicker.types';\n"]}