UNPKG

@mui/x-date-pickers

Version:

The community edition of the Date and Time Picker components (MUI X).

357 lines (356 loc) 19.4 kB
import * as React from 'react'; import { FieldSectionType, FieldSection, FieldSelectedSections, MuiPickersAdapter, TimezoneProps, FieldSectionContentType, FieldValueType, PickersTimezone, PickerValidDate, FieldRef } from '../../../models'; import type { PickerValueManager } from '../usePicker'; import { InferError, Validator } from '../useValidation'; import type { UseFieldStateResponse } from './useFieldState'; import type { UseFieldCharacterEditingResponse } from './useFieldCharacterEditing'; import { PickersSectionElement, PickersSectionListRef } from '../../../PickersSectionList'; import { ExportedUseClearableFieldProps } from '../../../hooks/useClearableField'; export interface UseFieldParams<TValue, TDate extends PickerValidDate, TSection extends FieldSection, TEnableAccessibleFieldDOMStructure extends boolean, TForwardedProps extends UseFieldCommonForwardedProps & UseFieldForwardedProps<TEnableAccessibleFieldDOMStructure>, TInternalProps extends UseFieldInternalProps<any, any, any, TEnableAccessibleFieldDOMStructure, any>> { forwardedProps: TForwardedProps; internalProps: TInternalProps; valueManager: PickerValueManager<TValue, TDate, InferError<TInternalProps>>; fieldValueManager: FieldValueManager<TValue, TDate, TSection>; validator: Validator<TValue, TDate, InferError<TInternalProps>, UseFieldValidationProps<TValue, TInternalProps>>; valueType: FieldValueType; } export interface UseFieldInternalProps<TValue, TDate extends PickerValidDate, TSection extends FieldSection, TEnableAccessibleFieldDOMStructure extends boolean, TError> extends TimezoneProps { /** * The selected value. * Used when the component is controlled. */ value?: TValue; /** * The default value. Use when the component is not controlled. */ defaultValue?: TValue; /** * The date used to generate a part of the new value that is not present in the format when both `value` and `defaultValue` are empty. * For example, on time fields it will be used to determine the date to set. * @default The closest valid date using the validation props, except callbacks such as `shouldDisableDate`. Value is rounded to the most granular section used. */ referenceDate?: TDate; /** * Callback fired when the value changes. * @template TValue The value type. Will be either the same type as `value` or `null`. Can be in `[start, end]` format in case of range value. * @template TError The validation error type. Will be either `string` or a `null`. Can be in `[start, end]` format in case of range value. * @param {TValue} value The new value. * @param {FieldChangeHandlerContext<TError>} context The context containing the validation result of the current value. */ onChange?: FieldChangeHandler<TValue, TError>; /** * Callback fired when the error associated to the current value changes. * @template TValue The value type. Will be either the same type as `value` or `null`. Can be in `[start, end]` format in case of range value. * @template TError The validation error type. Will be either `string` or a `null`. Can be in `[start, end]` format in case of range value. * @param {TError} error The new error. * @param {TValue} value The value associated to the error. */ onError?: (error: TError, value: TValue) => void; /** * Format of the date when rendered in the input(s). */ format: string; /** * Density of the format when rendered in the input. * Setting `formatDensity` to `"spacious"` will add a space before and after each `/`, `-` and `.` character. * @default "dense" */ formatDensity?: 'dense' | 'spacious'; /** * If `true`, the format will respect the leading zeroes (e.g: on dayjs, the format `M/D/YYYY` will render `8/16/2018`) * If `false`, the format will always add leading zeroes (e.g: on dayjs, the format `M/D/YYYY` will render `08/16/2018`) * * Warning n°1: Luxon is not able to respect the leading zeroes when using macro tokens (e.g: "DD"), so `shouldRespectLeadingZeros={true}` might lead to inconsistencies when using `AdapterLuxon`. * * Warning n°2: When `shouldRespectLeadingZeros={true}`, the field will add an invisible character on the sections containing a single digit to make sure `onChange` is fired. * If you need to get the clean value from the input, you can remove this character using `input.value.replace(/\u200e/g, '')`. * * Warning n°3: When used in strict mode, dayjs and moment require to respect the leading zeros. * This mean that when using `shouldRespectLeadingZeros={false}`, if you retrieve the value directly from the input (not listening to `onChange`) and your format contains tokens without leading zeros, the value will not be parsed by your library. * * @default false */ shouldRespectLeadingZeros?: boolean; /** * It prevents the user from changing the value of the field * (not from interacting with the field). * @default false */ readOnly?: boolean; /** * The currently selected sections. * This prop accepts four formats: * 1. If a number is provided, the section at this index will be selected. * 2. If a string of type `FieldSectionType` is provided, the first section with that name will be selected. * 3. If `"all"` is provided, all the sections will be selected. * 4. If `null` is provided, no section will be selected. * If not provided, the selected sections will be handled internally. */ selectedSections?: FieldSelectedSections; /** * Callback fired when the selected sections change. * @param {FieldSelectedSections} newValue The new selected sections. */ onSelectedSectionsChange?: (newValue: FieldSelectedSections) => void; /** * The ref object used to imperatively interact with the field. */ unstableFieldRef?: React.Ref<FieldRef<TSection>>; /** * @default false */ enableAccessibleFieldDOMStructure?: TEnableAccessibleFieldDOMStructure; /** * If `true`, the `input` element is focused during the first mount. * @default false */ autoFocus?: boolean; /** * If `true`, the component is disabled. * @default false */ disabled?: boolean; } export interface UseFieldCommonAdditionalProps extends Required<Pick<UseFieldInternalProps<any, any, any, any, any>, 'disabled' | 'readOnly'>> { } export interface UseFieldCommonForwardedProps extends ExportedUseClearableFieldProps { onKeyDown?: React.KeyboardEventHandler; error?: boolean; } export type UseFieldForwardedProps<TEnableAccessibleFieldDOMStructure extends boolean> = UseFieldCommonForwardedProps & (TEnableAccessibleFieldDOMStructure extends false ? UseFieldV6ForwardedProps : UseFieldV7ForwardedProps); export interface UseFieldV6ForwardedProps { inputRef?: React.Ref<HTMLInputElement>; onBlur?: () => void; onClick?: React.MouseEventHandler; onFocus?: () => void; onPaste?: React.ClipboardEventHandler<HTMLDivElement>; placeholder?: string; } interface UseFieldV6AdditionalProps extends Required<Pick<React.InputHTMLAttributes<HTMLInputElement>, 'inputMode' | 'placeholder' | 'value' | 'onChange' | 'autoComplete'>> { enableAccessibleFieldDOMStructure: false; } export interface UseFieldV7ForwardedProps { focused?: boolean; autoFocus?: boolean; sectionListRef?: React.Ref<PickersSectionListRef>; onBlur?: () => void; onClick?: React.MouseEventHandler; onFocus?: () => void; onInput?: React.FormEventHandler<HTMLDivElement>; onPaste?: React.ClipboardEventHandler<HTMLDivElement>; } interface UseFieldV7AdditionalProps { enableAccessibleFieldDOMStructure: true; elements: PickersSectionElement[]; tabIndex: number | undefined; contentEditable: boolean; value: string; onChange: React.ChangeEventHandler<HTMLInputElement>; areAllSectionsEmpty: boolean; } export type UseFieldResponse<TEnableAccessibleFieldDOMStructure extends boolean, TForwardedProps extends UseFieldCommonForwardedProps & { [key: string]: any; }> = Omit<TForwardedProps, keyof UseFieldCommonForwardedProps> & Required<UseFieldCommonForwardedProps> & UseFieldCommonAdditionalProps & (TEnableAccessibleFieldDOMStructure extends false ? UseFieldV6AdditionalProps & Required<UseFieldV6ForwardedProps> : UseFieldV7AdditionalProps & Required<UseFieldV7ForwardedProps>); export type FieldSectionValueBoundaries<TDate extends PickerValidDate, SectionType extends FieldSectionType> = { minimum: number; maximum: number; } & (SectionType extends 'day' ? { longestMonth: TDate; } : {}); export type FieldSectionsValueBoundaries<TDate extends PickerValidDate> = { [SectionType in FieldSectionType]: (params: { currentDate: TDate | null; format: string; contentType: FieldSectionContentType; }) => FieldSectionValueBoundaries<TDate, SectionType>; }; export type FieldSectionsBoundaries = { [SectionType in FieldSectionType]: { minimum: number; maximum: number; }; }; export type FieldChangeHandler<TValue, TError> = (value: TValue, context: FieldChangeHandlerContext<TError>) => void; export interface FieldChangeHandlerContext<TError> { validationError: TError; } /** * Object used to access and update the active date (i.e: the date containing the active section). * Mainly useful in the range fields where we need to update the date containing the active section without impacting the other one. */ interface FieldActiveDateManager<TValue, TDate extends PickerValidDate, TSection extends FieldSection> { /** * Active date from `state.value`. */ date: TDate | null; /** * Active date from the `state.referenceValue`. */ referenceDate: TDate; /** * @template TSection * @param {TSection[]} sections The sections of the full value. * @returns {TSection[]} The sections of the active date. * Get the sections of the active date. */ getSections: (sections: TSection[]) => TSection[]; /** * Creates the new value and reference value based on the new active date and the current state. * @template TValue, TDate * @param {TDate | null} newActiveDate The new value of the date containing the active section. * @returns {Pick<UseFieldState<TValue, any>, 'value' | 'referenceValue'>} The new value and reference value to publish and store in the state. */ getNewValuesFromNewActiveDate: (newActiveDate: TDate | null) => Pick<UseFieldState<TValue, any>, 'value' | 'referenceValue'>; } export type FieldParsedSelectedSections = number | 'all' | null; export interface FieldValueManager<TValue, TDate extends PickerValidDate, TSection extends FieldSection> { /** * Creates the section list from the current value. * The `prevSections` are used on the range fields to avoid losing the sections of a partially filled date when editing the other date. * @template TValue, TDate, TSection * @param {MuiPickersAdapter<TDate>} utils The utils to manipulate the date. * @param {TValue} value The current value to generate sections from. * @param {TSection[] | null} fallbackSections The sections to use as a fallback if a date is null or invalid. * @param {(date: TDate) => FieldSection[]} getSectionsFromDate Returns the sections of the given date. * @returns {TSection[]} The new section list. */ getSectionsFromValue: (utils: MuiPickersAdapter<TDate>, value: TValue, fallbackSections: TSection[] | null, getSectionsFromDate: (date: TDate) => FieldSection[]) => TSection[]; /** * Creates the string value to render in the input based on the current section list. * @template TSection * @param {TSection[]} sections The current section list. * @param {string} localizedDigits The conversion table from localized to 0-9 digits. * @param {boolean} isRTL `true` if the current orientation is "right to left" * @returns {string} The string value to render in the input. */ getV6InputValueFromSections: (sections: TSection[], localizedDigits: string[], isRTL: boolean) => string; /** * Creates the string value to render in the input based on the current section list. * @template TSection * @param {TSection[]} sections The current section list. * @returns {string} The string value to render in the input. */ getV7HiddenInputValueFromSections: (sections: TSection[]) => string; /** * Returns the manager of the active date. * @template TValue, TDate, TSection * @param {MuiPickersAdapter<TDate>} utils The utils to manipulate the date. * @param {UseFieldState<TValue, TSection>} state The current state of the field. * @param {TSection} activeSection The active section. * @returns {FieldActiveDateManager<TValue, TDate, TSection>} The manager of the active date. */ getActiveDateManager: (utils: MuiPickersAdapter<TDate>, state: UseFieldState<TValue, TSection>, activeSection: TSection) => FieldActiveDateManager<TValue, TDate, TSection>; /** * Parses a string version (most of the time coming from the input). * This method should only be used when the change does not come from a single section. * @template TValue, TDate * @param {string} valueStr The string value to parse. * @param {TValue} referenceValue The reference value currently stored in state. * @param {(dateStr: string, referenceDate: TDate) => TDate | null} parseDate A method to convert a string date into a parsed one. * @returns {TValue} The new parsed value. */ parseValueStr: (valueStr: string, referenceValue: TValue, parseDate: (dateStr: string, referenceDate: TDate) => TDate | null) => TValue; /** * Update the reference value with the new value. * This method must make sure that no date inside the returned `referenceValue` is invalid. * @template TValue, TDate * @param {MuiPickersAdapter<TDate>} utils The utils to manipulate the date. * @param {TValue} value The new value from which we want to take all valid dates in the `referenceValue` state. * @param {TValue} prevReferenceValue The previous reference value. It is used as a fallback for invalid dates in the new value. * @returns {TValue} The new reference value with no invalid date. */ updateReferenceValue: (utils: MuiPickersAdapter<TDate>, value: TValue, prevReferenceValue: TValue) => TValue; } export interface UseFieldState<TValue, TSection extends FieldSection> { value: TValue; /** * Non-nullable value used to keep trace of the timezone and the date parts not present in the format. * It is updated whenever we have a valid date (for the range picker we update only the portion of the range that is valid). */ referenceValue: TValue; sections: TSection[]; /** * Android `onChange` behavior when the input selection is not empty is quite different from a desktop behavior. * There are two `onChange` calls: * 1. A call with the selected content removed. * 2. A call with the key pressed added to the value. ** * For instance, if the input value equals `month / day / year` and `day` is selected. * The pressing `1` will have the following behavior: * 1. A call with `month / / year`. * 2. A call with `month / 1 / year`. * * But if you don't update the input with the value passed on the first `onChange`. * Then the second `onChange` will add the key press at the beginning of the selected value. * 1. A call with `month / / year` that we don't set into state. * 2. A call with `month / 1day / year`. * * The property below allows us to set the first `onChange` value into state waiting for the second one. */ tempValueStrAndroid: string | null; } export type UseFieldValidationProps<TValue, TInternalProps extends { value?: TValue; defaultValue?: TValue; timezone?: PickersTimezone; }> = Omit<TInternalProps, 'value' | 'defaultValue' | 'timezone'> & { value: TValue; timezone: PickersTimezone; }; export type AvailableAdjustKeyCode = 'ArrowUp' | 'ArrowDown' | 'PageUp' | 'PageDown' | 'Home' | 'End'; export type SectionNeighbors = { [sectionIndex: number]: { /** * Index of the next section displayed on the left. `null` if it's the leftmost section. */ leftIndex: number | null; /** * Index of the next section displayed on the right. `null` if it's the rightmost section. */ rightIndex: number | null; }; }; export type SectionOrdering = { /** * For each section index provide the index of the section displayed on the left and on the right. */ neighbors: SectionNeighbors; /** * Index of the section displayed on the far left */ startIndex: number; /** * Index of the section displayed on the far right */ endIndex: number; }; export interface UseFieldTextFieldInteractions { /** * Select the correct sections in the DOM according to the sections currently selected in state. */ syncSelectionToDOM: () => void; /** * Returns the index of the active section (the first focused section). * If no section is active, returns `null`. * @returns {number | null} The index of the active section. */ getActiveSectionIndexFromDOM: () => number | null; /** * Focuses the field. * @param {number | FieldSectionType} newSelectedSection The section to select once focused. */ focusField: (newSelectedSection?: number | FieldSectionType) => void; setSelectedSections: (newSelectedSections: FieldSelectedSections) => void; isFieldFocused: () => boolean; } export type UseFieldTextField<TEnableAccessibleFieldDOMStructure extends boolean> = <TValue, TDate extends PickerValidDate, TSection extends FieldSection, TForwardedProps extends TEnableAccessibleFieldDOMStructure extends false ? UseFieldV6ForwardedProps : UseFieldV7ForwardedProps, TInternalProps extends UseFieldInternalProps<any, any, any, TEnableAccessibleFieldDOMStructure, any> & { minutesStep?: number; }>(params: UseFieldTextFieldParams<TValue, TDate, TSection, TEnableAccessibleFieldDOMStructure, TForwardedProps, TInternalProps>) => { interactions: UseFieldTextFieldInteractions; returnedValue: TEnableAccessibleFieldDOMStructure extends false ? UseFieldV6AdditionalProps & Required<UseFieldV6ForwardedProps> : UseFieldV7AdditionalProps & Required<UseFieldV7ForwardedProps>; }; interface UseFieldTextFieldParams<TValue, TDate extends PickerValidDate, TSection extends FieldSection, TEnableAccessibleFieldDOMStructure extends boolean, TForwardedProps extends TEnableAccessibleFieldDOMStructure extends false ? UseFieldV6ForwardedProps : UseFieldV7ForwardedProps, TInternalProps extends UseFieldInternalProps<any, any, any, TEnableAccessibleFieldDOMStructure, any>> extends UseFieldParams<TValue, TDate, TSection, TEnableAccessibleFieldDOMStructure, TForwardedProps, TInternalProps>, UseFieldStateResponse<TValue, TDate, TSection>, UseFieldCharacterEditingResponse { areAllSectionsEmpty: boolean; sectionOrder: SectionOrdering; } export {};