1
0
Fork 0

Added Statistics calculation

Statistics now show calculated values
This commit is contained in:
Techognito 2025-09-04 17:30:00 +02:00
parent fe87374e47
commit fc0f69dacb
2147 changed files with 141321 additions and 39 deletions

View file

@ -0,0 +1,24 @@
import { PickerOnChangeFn } from "./useViews.js";
import { PickerSelectionState } from "./usePicker/index.js";
import { PickersTimezone, PickerValidDate } from "../../models/index.js";
export interface MonthValidationOptions {
disablePast?: boolean;
disableFuture?: boolean;
minDate: PickerValidDate;
maxDate: PickerValidDate;
timezone: PickersTimezone;
}
export declare function useNextMonthDisabled(month: PickerValidDate, {
disableFuture,
maxDate,
timezone
}: Pick<MonthValidationOptions, 'disableFuture' | 'maxDate' | 'timezone'>): boolean;
export declare function usePreviousMonthDisabled(month: PickerValidDate, {
disablePast,
minDate,
timezone
}: Pick<MonthValidationOptions, 'disablePast' | 'minDate' | 'timezone'>): boolean;
export declare function useMeridiemMode(date: PickerValidDate | null, ampm: boolean | undefined, onChange: PickerOnChangeFn, selectionState?: PickerSelectionState): {
meridiemMode: import("../utils/time-utils.js").Meridiem | null;
handleMeridiemChange: (mode: "am" | "pm") => void;
};

View file

@ -0,0 +1,49 @@
"use strict";
var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard").default;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.useMeridiemMode = useMeridiemMode;
exports.useNextMonthDisabled = useNextMonthDisabled;
exports.usePreviousMonthDisabled = usePreviousMonthDisabled;
var React = _interopRequireWildcard(require("react"));
var _timeUtils = require("../utils/time-utils");
var _usePickerAdapter = require("../../hooks/usePickerAdapter");
function useNextMonthDisabled(month, {
disableFuture,
maxDate,
timezone
}) {
const adapter = (0, _usePickerAdapter.usePickerAdapter)();
return React.useMemo(() => {
const now = adapter.date(undefined, timezone);
const lastEnabledMonth = adapter.startOfMonth(disableFuture && adapter.isBefore(now, maxDate) ? now : maxDate);
return !adapter.isAfter(lastEnabledMonth, month);
}, [disableFuture, maxDate, month, adapter, timezone]);
}
function usePreviousMonthDisabled(month, {
disablePast,
minDate,
timezone
}) {
const adapter = (0, _usePickerAdapter.usePickerAdapter)();
return React.useMemo(() => {
const now = adapter.date(undefined, timezone);
const firstEnabledMonth = adapter.startOfMonth(disablePast && adapter.isAfter(now, minDate) ? now : minDate);
return !adapter.isBefore(firstEnabledMonth, month);
}, [disablePast, minDate, month, adapter, timezone]);
}
function useMeridiemMode(date, ampm, onChange, selectionState) {
const adapter = (0, _usePickerAdapter.usePickerAdapter)();
const cleanDate = React.useMemo(() => !adapter.isValid(date) ? null : date, [adapter, date]);
const meridiemMode = (0, _timeUtils.getMeridiem)(cleanDate, adapter);
const handleMeridiemChange = React.useCallback(mode => {
const timeWithMeridiem = cleanDate == null ? null : (0, _timeUtils.convertToMeridiem)(cleanDate, mode, Boolean(ampm), adapter);
onChange(timeWithMeridiem, selectionState ?? 'partial');
}, [ampm, cleanDate, onChange, selectionState, adapter]);
return {
meridiemMode,
handleMeridiemChange
};
}

View file

@ -0,0 +1,15 @@
import { MuiPickersAdapter, PickersTimezone, PickerValidDate } from "../../models/index.js";
import { PickerValue } from "../models/index.js";
export declare const useClockReferenceDate: <TProps extends {}>({
value,
referenceDate: referenceDateProp,
adapter,
props,
timezone
}: {
value: PickerValue;
referenceDate: PickerValidDate | undefined;
adapter: MuiPickersAdapter;
props: TProps;
timezone: PickersTimezone;
}) => PickerValidDate;

View file

@ -0,0 +1,33 @@
"use strict";
var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard").default;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.useClockReferenceDate = void 0;
var React = _interopRequireWildcard(require("react"));
var _valueManagers = require("../utils/valueManagers");
var _dateUtils = require("../utils/date-utils");
var _getDefaultReferenceDate = require("../utils/getDefaultReferenceDate");
const useClockReferenceDate = ({
value,
referenceDate: referenceDateProp,
adapter,
props,
timezone
}) => {
const referenceDate = React.useMemo(() => _valueManagers.singleItemValueManager.getInitialReferenceValue({
value,
adapter,
props,
referenceDate: referenceDateProp,
granularity: _getDefaultReferenceDate.SECTION_TYPE_GRANULARITY.day,
timezone,
getTodayDate: () => (0, _dateUtils.getTodayDate)(adapter, timezone, 'date')
}),
// We want the `referenceDate` to update on prop and `timezone` change (https://github.com/mui/mui-x/issues/10804)
[referenceDateProp, timezone] // eslint-disable-line react-hooks/exhaustive-deps
);
return value ?? referenceDate;
};
exports.useClockReferenceDate = useClockReferenceDate;

View file

@ -0,0 +1,38 @@
import type { PickerRangeValue, PickerValueManager } from "../models/index.js";
import { PickersTimezone, PickerValidDate } from "../../models/index.js";
import { PickerValidValue } from "../models/index.js";
/**
* Hooks controlling the value while making sure that:
* - The value returned by `onChange` always have the timezone of `props.value` or `props.defaultValue` if defined
* - The value rendered is always the one from `props.timezone` if defined
*/
export declare const useControlledValue: <TValue extends PickerValidValue, TChange extends (...params: any[]) => void>({
name,
timezone: timezoneProp,
value: valueProp,
defaultValue,
referenceDate,
onChange: onChangeProp,
valueManager
}: UseControlledValueWithTimezoneParameters<TValue, TChange>) => {
value: TValue;
handleValueChange: TChange;
timezone: string;
};
interface UseValueWithTimezoneParameters<TValue extends PickerValidValue, TChange extends (...params: any[]) => void> {
timezone: PickersTimezone | undefined;
value: TValue | undefined;
defaultValue: TValue | undefined;
/**
* The reference date as passed to `props.referenceDate`.
* It does not need to have its default value.
* This is only used to determine the timezone to use when `props.value` and `props.defaultValue` are not defined.
*/
referenceDate?: TValue extends PickerRangeValue ? TValue | PickerValidDate : PickerValidDate;
onChange: TChange | undefined;
valueManager: PickerValueManager<TValue, any>;
}
interface UseControlledValueWithTimezoneParameters<TValue extends PickerValidValue, TChange extends (...params: any[]) => void> extends UseValueWithTimezoneParameters<TValue, TChange> {
name: string;
}
export {};

View file

@ -0,0 +1,65 @@
"use strict";
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault").default;
var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard").default;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.useControlledValue = void 0;
var React = _interopRequireWildcard(require("react"));
var _useEventCallback = _interopRequireDefault(require("@mui/utils/useEventCallback"));
var _useControlled = _interopRequireDefault(require("@mui/utils/useControlled"));
var _usePickerAdapter = require("../../hooks/usePickerAdapter");
/**
* Hooks controlling the value while making sure that:
* - The value returned by `onChange` always have the timezone of `props.value` or `props.defaultValue` if defined
* - The value rendered is always the one from `props.timezone` if defined
*/
const useControlledValue = ({
name,
timezone: timezoneProp,
value: valueProp,
defaultValue,
referenceDate,
onChange: onChangeProp,
valueManager
}) => {
const adapter = (0, _usePickerAdapter.usePickerAdapter)();
const [valueWithInputTimezone, setValue] = (0, _useControlled.default)({
name,
state: 'value',
controlled: valueProp,
default: defaultValue ?? valueManager.emptyValue
});
const inputTimezone = React.useMemo(() => valueManager.getTimezone(adapter, valueWithInputTimezone), [adapter, valueManager, valueWithInputTimezone]);
const setInputTimezone = (0, _useEventCallback.default)(newValue => {
if (inputTimezone == null) {
return newValue;
}
return valueManager.setTimezone(adapter, inputTimezone, newValue);
});
const timezoneToRender = React.useMemo(() => {
if (timezoneProp) {
return timezoneProp;
}
if (inputTimezone) {
return inputTimezone;
}
if (referenceDate) {
return adapter.getTimezone(Array.isArray(referenceDate) ? referenceDate[0] : referenceDate);
}
return 'default';
}, [timezoneProp, inputTimezone, referenceDate, adapter]);
const valueWithTimezoneToRender = React.useMemo(() => valueManager.setTimezone(adapter, timezoneToRender, valueWithInputTimezone), [valueManager, adapter, timezoneToRender, valueWithInputTimezone]);
const handleValueChange = (0, _useEventCallback.default)((newValue, ...otherParams) => {
const newValueWithInputTimezone = setInputTimezone(newValue);
setValue(newValueWithInputTimezone);
onChangeProp?.(newValueWithInputTimezone, ...otherParams);
});
return {
value: valueWithTimezoneToRender,
handleValueChange,
timezone: timezoneToRender
};
};
exports.useControlledValue = useControlledValue;

View file

@ -0,0 +1,2 @@
export { useDesktopPicker } from "./useDesktopPicker.js";
export type { UseDesktopPickerSlots, UseDesktopPickerSlotProps, ExportedUseDesktopPickerSlotProps, DesktopOnlyPickerProps, UseDesktopPickerProps } from "./useDesktopPicker.types.js";

View file

@ -0,0 +1,12 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
Object.defineProperty(exports, "useDesktopPicker", {
enumerable: true,
get: function () {
return _useDesktopPicker.useDesktopPicker;
}
});
var _useDesktopPicker = require("./useDesktopPicker");

View file

@ -0,0 +1,16 @@
import * as React from 'react';
import { UseDesktopPickerParams, UseDesktopPickerProps } from "./useDesktopPicker.types.js";
import { DateOrTimeViewWithMeridiem } from "../../models/index.js";
/**
* Hook managing all the single-date desktop pickers:
* - DesktopDatePicker
* - DesktopDateTimePicker
* - DesktopTimePicker
*/
export declare const useDesktopPicker: <TView extends DateOrTimeViewWithMeridiem, TEnableAccessibleFieldDOMStructure extends boolean, TExternalProps extends UseDesktopPickerProps<TView, TEnableAccessibleFieldDOMStructure, any, TExternalProps>>({
props,
steps,
...pickerParams
}: UseDesktopPickerParams<TView, TEnableAccessibleFieldDOMStructure, TExternalProps>) => {
renderPicker: () => React.JSX.Element;
};

View file

@ -0,0 +1,106 @@
"use strict";
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault").default;
var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard").default;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.useDesktopPicker = void 0;
var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends"));
var _objectWithoutPropertiesLoose2 = _interopRequireDefault(require("@babel/runtime/helpers/objectWithoutPropertiesLoose"));
var React = _interopRequireWildcard(require("react"));
var _useSlotProps2 = _interopRequireDefault(require("@mui/utils/useSlotProps"));
var _PickerPopper = require("../../components/PickerPopper/PickerPopper");
var _usePicker = require("../usePicker");
var _PickersLayout = require("../../../PickersLayout");
var _PickerProvider = require("../../components/PickerProvider");
var _PickerFieldUI = require("../../components/PickerFieldUI");
var _createNonRangePickerStepNavigation = require("../../utils/createNonRangePickerStepNavigation");
var _jsxRuntime = require("react/jsx-runtime");
const _excluded = ["props", "steps"],
_excluded2 = ["ownerState"];
/**
* Hook managing all the single-date desktop pickers:
* - DesktopDatePicker
* - DesktopDateTimePicker
* - DesktopTimePicker
*/
const useDesktopPicker = _ref => {
let {
props,
steps
} = _ref,
pickerParams = (0, _objectWithoutPropertiesLoose2.default)(_ref, _excluded);
const {
slots,
slotProps: innerSlotProps,
label,
inputRef,
localeText
} = props;
const getStepNavigation = (0, _createNonRangePickerStepNavigation.createNonRangePickerStepNavigation)({
steps
});
const {
providerProps,
renderCurrentView,
ownerState
} = (0, _usePicker.usePicker)((0, _extends2.default)({}, pickerParams, {
props,
localeText,
autoFocusView: true,
viewContainerRole: 'dialog',
variant: 'desktop',
getStepNavigation
}));
const labelId = providerProps.privateContextValue.labelId;
const isToolbarHidden = innerSlotProps?.toolbar?.hidden ?? false;
const Field = slots.field;
const _useSlotProps = (0, _useSlotProps2.default)({
elementType: Field,
externalSlotProps: innerSlotProps?.field,
additionalProps: (0, _extends2.default)({}, isToolbarHidden && {
id: labelId
}),
ownerState
}),
fieldProps = (0, _objectWithoutPropertiesLoose2.default)(_useSlotProps, _excluded2);
const Layout = slots.layout ?? _PickersLayout.PickersLayout;
let labelledById = labelId;
if (isToolbarHidden) {
if (label) {
labelledById = `${labelId}-label`;
} else {
labelledById = undefined;
}
}
const slotProps = (0, _extends2.default)({}, innerSlotProps, {
toolbar: (0, _extends2.default)({}, innerSlotProps?.toolbar, {
titleId: labelId
}),
popper: (0, _extends2.default)({
'aria-labelledby': labelledById
}, innerSlotProps?.popper)
});
const renderPicker = () => /*#__PURE__*/(0, _jsxRuntime.jsx)(_PickerProvider.PickerProvider, (0, _extends2.default)({}, providerProps, {
children: /*#__PURE__*/(0, _jsxRuntime.jsxs)(_PickerFieldUI.PickerFieldUIContextProvider, {
slots: slots,
slotProps: slotProps,
inputRef: inputRef,
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(Field, (0, _extends2.default)({}, fieldProps)), /*#__PURE__*/(0, _jsxRuntime.jsx)(_PickerPopper.PickerPopper, {
slots: slots,
slotProps: slotProps,
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(Layout, (0, _extends2.default)({}, slotProps?.layout, {
slots: slots,
slotProps: slotProps,
children: renderCurrentView()
}))
})]
})
}));
if (process.env.NODE_ENV !== "production") renderPicker.displayName = "renderPicker";
return {
renderPicker
};
};
exports.useDesktopPicker = useDesktopPicker;

View file

@ -0,0 +1,48 @@
import * as React from 'react';
import { MakeRequired, SlotComponentPropsFromProps } from '@mui/x-internals/types';
import { BasePickerProps } from "../../models/props/basePickerProps.js";
import { PickerPopperSlots, PickerPopperSlotProps } from "../../components/PickerPopper/PickerPopper.js";
import { UsePickerParameters, UsePickerNonStaticProps, UsePickerProps } from "../usePicker/index.js";
import { PickerFieldSlotProps, PickerOwnerState } from "../../../models/index.js";
import { ExportedPickersLayoutSlots, ExportedPickersLayoutSlotProps, PickersLayoutSlotProps } from "../../../PickersLayout/PickersLayout.types.js";
import { DateOrTimeViewWithMeridiem, PickerValue } from "../../models/index.js";
import { PickerFieldUISlotsFromContext, PickerFieldUISlotPropsFromContext } from "../../components/PickerFieldUI.js";
import { PickerStep } from "../../utils/createNonRangePickerStepNavigation.js";
export interface UseDesktopPickerSlots extends Pick<PickerPopperSlots, 'desktopPaper' | 'desktopTransition' | 'desktopTrapFocus' | 'popper'>, ExportedPickersLayoutSlots<PickerValue>, PickerFieldUISlotsFromContext {
/**
* Component used to enter the date with the keyboard.
*/
field: React.ElementType;
}
export interface ExportedUseDesktopPickerSlotProps<TEnableAccessibleFieldDOMStructure extends boolean> extends PickerPopperSlotProps, ExportedPickersLayoutSlotProps<PickerValue>, PickerFieldUISlotPropsFromContext {
field?: SlotComponentPropsFromProps<PickerFieldSlotProps<PickerValue, TEnableAccessibleFieldDOMStructure>, {}, PickerOwnerState>;
}
export interface UseDesktopPickerSlotProps<TEnableAccessibleFieldDOMStructure extends boolean> extends ExportedUseDesktopPickerSlotProps<TEnableAccessibleFieldDOMStructure>, Pick<PickersLayoutSlotProps<PickerValue>, 'toolbar'> {}
export interface DesktopOnlyPickerProps extends UsePickerNonStaticProps {
/**
* If `true`, the `input` element is focused during the first mount.
* @default false
*/
autoFocus?: boolean;
}
export interface UseDesktopPickerProps<TView extends DateOrTimeViewWithMeridiem, TEnableAccessibleFieldDOMStructure extends boolean, TError, TExternalProps extends UsePickerProps<PickerValue, TView, TError, any>> extends BasePickerProps<PickerValue, any, TError, TExternalProps>, MakeRequired<DesktopOnlyPickerProps, 'format'> {
/**
* Overridable component slots.
* @default {}
*/
slots: UseDesktopPickerSlots;
/**
* The props used for each component slot.
* @default {}
*/
slotProps?: UseDesktopPickerSlotProps<TEnableAccessibleFieldDOMStructure>;
}
export interface UseDesktopPickerParams<TView extends DateOrTimeViewWithMeridiem, TEnableAccessibleFieldDOMStructure extends boolean, TExternalProps extends UseDesktopPickerProps<TView, TEnableAccessibleFieldDOMStructure, any, TExternalProps>> extends Pick<UsePickerParameters<PickerValue, TView, TExternalProps>, 'valueManager' | 'valueType' | 'validator' | 'rendererInterceptor' | 'ref'> {
props: TExternalProps;
/**
* Steps available for the picker.
* This will be used to define the behavior of navigation actions.
* If null, the picker will not have any step navigation.
*/
steps: PickerStep[] | null;
}

View file

@ -0,0 +1,5 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});

View file

@ -0,0 +1,15 @@
import { FieldSection, MuiPickersAdapter, PickerValidDate } from "../../../models/index.js";
import { PickersLocaleText } from "../../../locales/index.js";
interface BuildSectionsFromFormatParameters {
adapter: MuiPickersAdapter;
format: string;
formatDensity: 'dense' | 'spacious';
isRtl: boolean;
shouldRespectLeadingZeros: boolean;
localeText: PickersLocaleText;
localizedDigits: string[];
date: PickerValidDate | null;
enableAccessibleFieldDOMStructure: boolean;
}
export declare const buildSectionsFromFormat: (parameters: BuildSectionsFromFormatParameters) => FieldSection[];
export {};

View file

@ -0,0 +1,263 @@
"use strict";
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault").default;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.buildSectionsFromFormat = void 0;
var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends"));
var _useField = require("./useField.utils");
const expandFormat = ({
adapter,
format
}) => {
// Expand the provided format
let formatExpansionOverflow = 10;
let prevFormat = format;
let nextFormat = adapter.expandFormat(format);
while (nextFormat !== prevFormat) {
prevFormat = nextFormat;
nextFormat = adapter.expandFormat(prevFormat);
formatExpansionOverflow -= 1;
if (formatExpansionOverflow < 0) {
throw new Error('MUI X: The format expansion seems to be in an infinite loop. Please open an issue with the format passed to the component.');
}
}
return nextFormat;
};
const getEscapedPartsFromFormat = ({
adapter,
expandedFormat
}) => {
const escapedParts = [];
const {
start: startChar,
end: endChar
} = adapter.escapedCharacters;
const regExp = new RegExp(`(\\${startChar}[^\\${endChar}]*\\${endChar})+`, 'g');
let match = null;
// eslint-disable-next-line no-cond-assign
while (match = regExp.exec(expandedFormat)) {
escapedParts.push({
start: match.index,
end: regExp.lastIndex - 1
});
}
return escapedParts;
};
const getSectionPlaceholder = (adapter, localeText, sectionConfig, sectionFormat) => {
switch (sectionConfig.type) {
case 'year':
{
return localeText.fieldYearPlaceholder({
digitAmount: adapter.formatByString(adapter.date(undefined, 'default'), sectionFormat).length,
format: sectionFormat
});
}
case 'month':
{
return localeText.fieldMonthPlaceholder({
contentType: sectionConfig.contentType,
format: sectionFormat
});
}
case 'day':
{
return localeText.fieldDayPlaceholder({
format: sectionFormat
});
}
case 'weekDay':
{
return localeText.fieldWeekDayPlaceholder({
contentType: sectionConfig.contentType,
format: sectionFormat
});
}
case 'hours':
{
return localeText.fieldHoursPlaceholder({
format: sectionFormat
});
}
case 'minutes':
{
return localeText.fieldMinutesPlaceholder({
format: sectionFormat
});
}
case 'seconds':
{
return localeText.fieldSecondsPlaceholder({
format: sectionFormat
});
}
case 'meridiem':
{
return localeText.fieldMeridiemPlaceholder({
format: sectionFormat
});
}
default:
{
return sectionFormat;
}
}
};
const createSection = ({
adapter,
date,
shouldRespectLeadingZeros,
localeText,
localizedDigits,
now,
token,
startSeparator
}) => {
if (token === '') {
throw new Error('MUI X: Should not call `commitToken` with an empty token');
}
const sectionConfig = (0, _useField.getDateSectionConfigFromFormatToken)(adapter, token);
const hasLeadingZerosInFormat = (0, _useField.doesSectionFormatHaveLeadingZeros)(adapter, sectionConfig.contentType, sectionConfig.type, token);
const hasLeadingZerosInInput = shouldRespectLeadingZeros ? hasLeadingZerosInFormat : sectionConfig.contentType === 'digit';
const isValidDate = adapter.isValid(date);
let sectionValue = isValidDate ? adapter.formatByString(date, token) : '';
let maxLength = null;
if (hasLeadingZerosInInput) {
if (hasLeadingZerosInFormat) {
maxLength = sectionValue === '' ? adapter.formatByString(now, token).length : sectionValue.length;
} else {
if (sectionConfig.maxLength == null) {
throw new Error(`MUI X: The token ${token} should have a 'maxLength' property on it's adapter`);
}
maxLength = sectionConfig.maxLength;
if (isValidDate) {
sectionValue = (0, _useField.applyLocalizedDigits)((0, _useField.cleanLeadingZeros)((0, _useField.removeLocalizedDigits)(sectionValue, localizedDigits), maxLength), localizedDigits);
}
}
}
return (0, _extends2.default)({}, sectionConfig, {
format: token,
maxLength,
value: sectionValue,
placeholder: getSectionPlaceholder(adapter, localeText, sectionConfig, token),
hasLeadingZerosInFormat,
hasLeadingZerosInInput,
startSeparator,
endSeparator: '',
modified: false
});
};
const buildSections = parameters => {
const {
adapter,
expandedFormat,
escapedParts
} = parameters;
const now = adapter.date(undefined);
const sections = [];
let startSeparator = '';
// This RegExp tests if the beginning of a string corresponds to a supported token
const validTokens = Object.keys(adapter.formatTokenMap).sort((a, b) => b.length - a.length); // Sort to put longest word first
const regExpFirstWordInFormat = /^([a-zA-Z]+)/;
const regExpWordOnlyComposedOfTokens = new RegExp(`^(${validTokens.join('|')})*$`);
const regExpFirstTokenInWord = new RegExp(`^(${validTokens.join('|')})`);
const getEscapedPartOfCurrentChar = i => escapedParts.find(escapeIndex => escapeIndex.start <= i && escapeIndex.end >= i);
let i = 0;
while (i < expandedFormat.length) {
const escapedPartOfCurrentChar = getEscapedPartOfCurrentChar(i);
const isEscapedChar = escapedPartOfCurrentChar != null;
const firstWordInFormat = regExpFirstWordInFormat.exec(expandedFormat.slice(i))?.[1];
// The first word in the format is only composed of tokens.
// We extract those tokens to create a new sections.
if (!isEscapedChar && firstWordInFormat != null && regExpWordOnlyComposedOfTokens.test(firstWordInFormat)) {
let word = firstWordInFormat;
while (word.length > 0) {
const firstWord = regExpFirstTokenInWord.exec(word)[1];
word = word.slice(firstWord.length);
sections.push(createSection((0, _extends2.default)({}, parameters, {
now,
token: firstWord,
startSeparator
})));
startSeparator = '';
}
i += firstWordInFormat.length;
}
// The remaining format does not start with a token,
// We take the first character and add it to the current section's end separator.
else {
const char = expandedFormat[i];
// If we are on the opening or closing character of an escaped part of the format,
// Then we ignore this character.
const isEscapeBoundary = isEscapedChar && escapedPartOfCurrentChar?.start === i || escapedPartOfCurrentChar?.end === i;
if (!isEscapeBoundary) {
if (sections.length === 0) {
startSeparator += char;
} else {
sections[sections.length - 1].endSeparator += char;
sections[sections.length - 1].isEndFormatSeparator = true;
}
}
i += 1;
}
}
if (sections.length === 0 && startSeparator.length > 0) {
sections.push({
type: 'empty',
contentType: 'letter',
maxLength: null,
format: '',
value: '',
placeholder: '',
hasLeadingZerosInFormat: false,
hasLeadingZerosInInput: false,
startSeparator,
endSeparator: '',
modified: false
});
}
return sections;
};
const postProcessSections = ({
isRtl,
formatDensity,
sections
}) => {
return sections.map(section => {
const cleanSeparator = separator => {
let cleanedSeparator = separator;
if (isRtl && cleanedSeparator !== null && cleanedSeparator.includes(' ')) {
cleanedSeparator = `\u2069${cleanedSeparator}\u2066`;
}
if (formatDensity === 'spacious' && ['/', '.', '-'].includes(cleanedSeparator)) {
cleanedSeparator = ` ${cleanedSeparator} `;
}
return cleanedSeparator;
};
section.startSeparator = cleanSeparator(section.startSeparator);
section.endSeparator = cleanSeparator(section.endSeparator);
return section;
});
};
const buildSectionsFromFormat = parameters => {
let expandedFormat = expandFormat(parameters);
if (parameters.isRtl && parameters.enableAccessibleFieldDOMStructure) {
expandedFormat = expandedFormat.split(' ').reverse().join(' ');
}
const escapedParts = getEscapedPartsFromFormat((0, _extends2.default)({}, parameters, {
expandedFormat
}));
const sections = buildSections((0, _extends2.default)({}, parameters, {
expandedFormat,
escapedParts
}));
return postProcessSections((0, _extends2.default)({}, parameters, {
sections
}));
};
exports.buildSectionsFromFormat = buildSectionsFromFormat;

View file

@ -0,0 +1,4 @@
export { useField } from "./useField.js";
export type { UseFieldInternalProps, UseFieldParameters, UseFieldReturnValue, UseFieldProps, FieldValueManager, FieldChangeHandler, FieldChangeHandlerContext } from "./useField.types.js";
export { createDateStrForV7HiddenInputFromSections, createDateStrForV6InputFromSections } from "./useField.utils.js";
export { useFieldInternalPropsWithDefaults } from "./useFieldInternalPropsWithDefaults.js";

View file

@ -0,0 +1,32 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
Object.defineProperty(exports, "createDateStrForV6InputFromSections", {
enumerable: true,
get: function () {
return _useField2.createDateStrForV6InputFromSections;
}
});
Object.defineProperty(exports, "createDateStrForV7HiddenInputFromSections", {
enumerable: true,
get: function () {
return _useField2.createDateStrForV7HiddenInputFromSections;
}
});
Object.defineProperty(exports, "useField", {
enumerable: true,
get: function () {
return _useField.useField;
}
});
Object.defineProperty(exports, "useFieldInternalPropsWithDefaults", {
enumerable: true,
get: function () {
return _useFieldInternalPropsWithDefaults.useFieldInternalPropsWithDefaults;
}
});
var _useField = require("./useField");
var _useField2 = require("./useField.utils");
var _useFieldInternalPropsWithDefaults = require("./useFieldInternalPropsWithDefaults");

View file

@ -0,0 +1,9 @@
import { PickerValidValue } from "../../models/index.js";
import { UseFieldDOMGetters } from "./useField.types.js";
import { UseFieldStateReturnValue } from "./useFieldState.js";
export declare function syncSelectionToDOM<TValue extends PickerValidValue>(parameters: SyncSelectionToDOMParameters<TValue>): void;
export interface SyncSelectionToDOMParameters<TValue extends PickerValidValue> {
domGetters: UseFieldDOMGetters;
stateResponse: UseFieldStateReturnValue<TValue>;
focused: boolean;
}

View file

@ -0,0 +1,60 @@
"use strict";
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault").default;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.syncSelectionToDOM = syncSelectionToDOM;
var _ownerDocument = _interopRequireDefault(require("@mui/utils/ownerDocument"));
var _utils = require("../../utils/utils");
function syncSelectionToDOM(parameters) {
const {
focused,
domGetters,
stateResponse: {
// States and derived states
parsedSelectedSections,
state
}
} = parameters;
if (!domGetters.isReady()) {
return;
}
const selection = (0, _ownerDocument.default)(domGetters.getRoot()).getSelection();
if (!selection) {
return;
}
if (parsedSelectedSections == null) {
// If the selection contains an element inside the field, we reset it.
if (selection.rangeCount > 0 &&
// Firefox can return a Restricted object here
selection.getRangeAt(0).startContainer instanceof Node && domGetters.getRoot().contains(selection.getRangeAt(0).startContainer)) {
selection.removeAllRanges();
}
if (focused) {
domGetters.getRoot().blur();
}
return;
}
// On multi input range pickers we want to update selection range only for the active input
if (!domGetters.getRoot().contains((0, _utils.getActiveElement)(domGetters.getRoot()))) {
return;
}
const range = new window.Range();
let target;
if (parsedSelectedSections === 'all') {
target = domGetters.getRoot();
} else {
const section = state.sections[parsedSelectedSections];
if (section.type === 'empty') {
target = domGetters.getSectionContainer(parsedSelectedSections);
} else {
target = domGetters.getSectionContent(parsedSelectedSections);
}
}
range.selectNodeContents(target);
target.focus();
selection.removeAllRanges();
selection.addRange(range);
}

View file

@ -0,0 +1,3 @@
import { UseFieldParameters, UseFieldReturnValue, UseFieldProps } from "./useField.types.js";
import { PickerValidValue } from "../../models/index.js";
export declare const useField: <TValue extends PickerValidValue, TEnableAccessibleFieldDOMStructure extends boolean, TError, TValidationProps extends {}, TProps extends UseFieldProps<TEnableAccessibleFieldDOMStructure>>(parameters: UseFieldParameters<TValue, TEnableAccessibleFieldDOMStructure, TError, TValidationProps, TProps>) => UseFieldReturnValue<TEnableAccessibleFieldDOMStructure, TProps>;

View file

@ -0,0 +1,16 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.useField = void 0;
var _useFieldV7TextField = require("./useFieldV7TextField");
var _useFieldV6TextField = require("./useFieldV6TextField");
var _useNullableFieldPrivateContext = require("../useNullableFieldPrivateContext");
const useField = parameters => {
const fieldPrivateContext = (0, _useNullableFieldPrivateContext.useNullableFieldPrivateContext)();
const enableAccessibleFieldDOMStructure = parameters.props.enableAccessibleFieldDOMStructure ?? fieldPrivateContext?.enableAccessibleFieldDOMStructure ?? true;
const useFieldTextField = enableAccessibleFieldDOMStructure ? _useFieldV7TextField.useFieldV7TextField : _useFieldV6TextField.useFieldV6TextField;
return useFieldTextField(parameters);
};
exports.useField = useField;

View file

@ -0,0 +1,329 @@
import * as React from 'react';
import { FieldSectionType, FieldSection, FieldSelectedSections, MuiPickersAdapter, TimezoneProps, FieldSectionContentType, PickerValidDate, FieldRef, OnErrorProps, InferFieldSection, PickerManager, PickerValueType } from "../../../models/index.js";
import { InternalPropNames } from "../../../hooks/useSplitFieldProps.js";
import type { PickersSectionElement, PickersSectionListRef } from "../../../PickersSectionList/index.js";
import { FormProps, InferNonNullablePickerValue, PickerRangeValue, PickerValidValue } from "../../models/index.js";
export interface UseFieldParameters<TValue extends PickerValidValue, TEnableAccessibleFieldDOMStructure extends boolean, TError, TValidationProps extends {}, TProps extends UseFieldProps<TEnableAccessibleFieldDOMStructure>> {
manager: PickerManager<TValue, TEnableAccessibleFieldDOMStructure, TError, TValidationProps, any>;
props: TProps;
skipContextFieldRefAssignment?: boolean;
}
export interface UseFieldInternalProps<TValue extends PickerValidValue, TEnableAccessibleFieldDOMStructure extends boolean, TError> extends TimezoneProps, FormProps, OnErrorProps<TValue, TError> {
/**
* 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?: TValue extends PickerRangeValue ? TValue | PickerValidDate : PickerValidDate;
/**
* Callback fired when the value changes.
* @template TValue The value type. It will be the same type as `value` or `null`. It can be in `[start, end]` format in case of range value.
* @template TError The validation error type. It will be either `string` or a `null`. It 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>;
/**
* 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 (for example on dayjs, the format `M/D/YYYY` will render `8/16/2018`)
* If `false`, the format will always add leading zeroes (for example 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 (for example "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;
/**
* 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<TValue>>;
/**
* @default true
*/
enableAccessibleFieldDOMStructure?: TEnableAccessibleFieldDOMStructure;
/**
* If `true`, the `input` element is focused during the first mount.
* @default false
*/
autoFocus?: boolean;
/**
* If `true`, the component is displayed in focused state.
*/
focused?: boolean;
}
export type UseFieldForwardedProps<TEnableAccessibleFieldDOMStructure extends boolean> = TEnableAccessibleFieldDOMStructure extends false ? {
clearable?: boolean;
error?: boolean;
placeholder?: string;
inputRef?: React.Ref<HTMLInputElement>;
onClick?: React.MouseEventHandler;
onFocus?: React.FocusEventHandler;
onKeyDown?: React.KeyboardEventHandler;
onBlur?: React.FocusEventHandler;
onPaste?: React.ClipboardEventHandler<HTMLDivElement>;
onClear?: React.MouseEventHandler;
} : {
clearable?: boolean;
error?: boolean;
focused?: boolean;
sectionListRef?: React.Ref<PickersSectionListRef>;
onClick?: React.MouseEventHandler;
onKeyDown?: React.KeyboardEventHandler;
onFocus?: React.FocusEventHandler;
onBlur?: React.FocusEventHandler;
onInput?: React.FormEventHandler<HTMLDivElement>;
onPaste?: React.ClipboardEventHandler<HTMLDivElement>;
onClear?: React.MouseEventHandler;
};
type UseFieldAdditionalProps<TEnableAccessibleFieldDOMStructure extends boolean> = TEnableAccessibleFieldDOMStructure extends false ? {
/**
* The aria label to set on the button that opens the Picker.
*/
openPickerAriaLabel: string;
enableAccessibleFieldDOMStructure: false;
focused: boolean | undefined;
inputMode: 'text' | 'numeric';
placeholder: string;
value: string;
onChange: React.ChangeEventHandler<HTMLInputElement>;
autoComplete: 'off';
} : {
/**
* The aria label to set on the button that opens the Picker.
*/
openPickerAriaLabel: string;
enableAccessibleFieldDOMStructure: true;
elements: PickersSectionElement[];
tabIndex: number | undefined;
contentEditable: boolean;
value: string;
onChange: React.ChangeEventHandler<HTMLInputElement>;
areAllSectionsEmpty: boolean;
focused: boolean;
};
export type UseFieldReturnValue<TEnableAccessibleFieldDOMStructure extends boolean, TProps extends UseFieldProps<TEnableAccessibleFieldDOMStructure>> = Required<Pick<UseFieldInternalProps<any, any, any>, 'disabled' | 'readOnly' | 'autoFocus'>> & Required<UseFieldForwardedProps<TEnableAccessibleFieldDOMStructure>> & UseFieldAdditionalProps<TEnableAccessibleFieldDOMStructure> & Omit<TProps, InternalPropNames<PickerValueType>>;
export type FieldSectionValueBoundaries<SectionType extends FieldSectionType> = {
minimum: number;
maximum: number;
} & (SectionType extends 'day' ? {
longestMonth: PickerValidDate;
} : {});
export type FieldSectionsValueBoundaries = { [SectionType in FieldSectionType]: (params: {
currentDate: PickerValidDate | null;
format: string;
contentType: FieldSectionContentType;
}) => FieldSectionValueBoundaries<SectionType> };
export type FieldSectionsBoundaries = { [SectionType in FieldSectionType]: {
minimum: number;
maximum: number;
} };
export type FieldChangeHandler<TValue extends PickerValidValue, TError> = (value: TValue, context: FieldChangeHandlerContext<TError>) => void;
export interface FieldChangeHandlerContext<TError> {
validationError: TError;
}
export type FieldParsedSelectedSections = number | 'all' | null;
export interface FieldValueManager<TValue extends PickerValidValue> {
/**
* 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 The value type. It will be the same type as `value` or `null`. It can be in `[start, end]` format in case of range value.
* @param {TValue} value The current value to generate sections from.
* @param {(date: PickerValidDate | null) => FieldSection[]} getSectionsFromDate Returns the sections of the given date.
* @returns {InferFieldSection<TValue>[]} The new section list.
*/
getSectionsFromValue: (value: TValue, getSectionsFromDate: (date: PickerValidDate | null) => FieldSection[]) => InferFieldSection<TValue>[];
/**
* Creates the string value to render in the input based on the current section list.
* @template TValue The value type. It will be the same type as `value` or `null`. It can be in `[start, end]` format in case of range value.
* @param {InferFieldSection<TValue>[]} 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: InferFieldSection<TValue>[], localizedDigits: string[], isRtl: boolean) => string;
/**
* Creates the string value to render in the input based on the current section list.
* @template TValue The value type. It will be the same type as `value` or `null`. It can be in `[start, end]` format in case of range value.
* @param {InferFieldSection<TValue>[]} sections The current section list.
* @returns {string} The string value to render in the input.
*/
getV7HiddenInputValueFromSections: (sections: InferFieldSection<TValue>[]) => string;
/**
* 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 The value type. It will be the same type as `value` or `null`. It can be in `[start, end]` format in case of range value.
* @param {string} valueStr The string value to parse.
* @param {TValue} referenceValue The reference value currently stored in state.
* @param {(dateStr: string, referenceDate: PickerValidDate) => PickerValidDate | null} parseDate A method to convert a string date into a parsed one.
* @returns {TValue} The new parsed value.
*/
parseValueStr: (valueStr: string, referenceValue: InferNonNullablePickerValue<TValue>, parseDate: (dateStr: string, referenceDate: PickerValidDate) => PickerValidDate | 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 The value type. It will be the same type as `value` or `null`. It can be in `[start, end]` format in case of range value.
* @param {MuiPickersAdapter} adapter The adapter 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: (adapter: MuiPickersAdapter, value: TValue, prevReferenceValue: InferNonNullablePickerValue<TValue>) => InferNonNullablePickerValue<TValue>;
/**
* Extract from the given value the date that contains the given section.
* @param {TValue} value The value to extract the date from.
* @param {InferFieldSection<TValue>} section The section to get the date from.
* @returns {PickerValidDate | null} The date that contains the section.
*/
getDateFromSection: (value: TValue, section: InferFieldSection<TValue>) => PickerValidDate | null;
/**
* Get the sections of the date that contains the given section.
* @template TValue The value type. It will be the same type as `value` or `null`. It can be in `[start, end]` format in case of range value.
* @param {InferFieldSection<TValue>[]} sections The sections of the full value.
* @param {InferFieldSection<TValue>} section A section of the date from which we want to get all the sections.
* @returns {InferFieldSection<TValue>[]} The sections of the date that contains the section.
*/
getDateSectionsFromValue: (sections: InferFieldSection<TValue>[], section: InferFieldSection<TValue>) => InferFieldSection<TValue>[];
/**
* Creates a new value based on the provided date and the current value.
* @template TValue The value type. It will be the same type as `value` or `null`. It can be in `[start, end]` format in case of range value.
* @param {TValue} value The value to update the date in.
* @param {InferFieldSection<TValue>} section A section of the date we want to update in the value.
* @param {PickerValidDate | null} date The date that contains the section.
* @returns {TValue} The updated value.
*/
updateDateInValue: (value: TValue, section: InferFieldSection<TValue>, date: PickerValidDate | null) => TValue;
/**
* @template TValue The value type. It will be the same type as `value` or `null`. It can be in `[start, end]` format in case of range value.
* @param {InferFieldSection<TValue>[]} sections The sections of the full value.
* @param {InferFieldSection<TValue>} section A section of the date from which we want to clear all the sections.
* @returns {InferFieldSection<TValue>[]} The sections of the full value with all the sections of the target date cleared.
*/
clearDateSections: (sections: InferFieldSection<TValue>[], section: InferFieldSection<TValue>) => InferFieldSection<TValue>[];
}
export interface UseFieldState<TValue extends PickerValidValue> {
/**
* Last value returned by `useControlledValue`.
*/
lastExternalValue: TValue;
/**
* Last set of parameters used to generate the sections.
*/
lastSectionsDependencies: {
format: string;
isRtl: boolean;
locale: any;
};
/**
* 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 Pickers we update only the portion of the range that is valid).
*/
referenceValue: InferNonNullablePickerValue<TValue>;
/**
* Sections currently displayed in the field.
*/
sections: InferFieldSection<TValue>[];
/**
* 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;
/**
* The current query when editing the field using letters or digits.
*/
characterQuery: CharacterEditingQuery | null;
}
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 CharacterEditingQuery {
value: string;
sectionIndex: number;
sectionType: FieldSectionType;
}
export type UseFieldProps<TEnableAccessibleFieldDOMStructure extends boolean> = UseFieldForwardedProps<TEnableAccessibleFieldDOMStructure> & {
enableAccessibleFieldDOMStructure?: boolean;
};
export interface UseFieldDOMGetters {
isReady: () => boolean;
getRoot: () => HTMLElement;
getSectionContainer: (sectionIndex: number) => HTMLElement;
getSectionContent: (sectionIndex: number) => HTMLElement;
getSectionIndexFromDOMElement: (element: Element | null | undefined) => number | null;
}
export {};

View file

@ -0,0 +1,5 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});

View file

@ -0,0 +1,36 @@
import { FieldSectionsValueBoundaries, SectionOrdering, FieldSectionValueBoundaries, FieldParsedSelectedSections } from "./useField.types.js";
import { FieldSectionType, FieldSection, MuiPickersAdapter, FieldSectionContentType, PickersTimezone, PickerValidDate, FieldSelectedSections, PickerValueType, InferFieldSection } from "../../../models/index.js";
import { PickerValidValue } from "../../models/index.js";
export declare const getDateSectionConfigFromFormatToken: (adapter: MuiPickersAdapter, formatToken: string) => Pick<FieldSection, "type" | "contentType"> & {
maxLength: number | undefined;
};
export declare const getDaysInWeekStr: (adapter: MuiPickersAdapter, format: string) => string[];
export declare const getLetterEditingOptions: (adapter: MuiPickersAdapter, timezone: PickersTimezone, sectionType: FieldSectionType, format: string) => string[];
export declare const FORMAT_SECONDS_NO_LEADING_ZEROS = "s";
export declare const getLocalizedDigits: (adapter: MuiPickersAdapter) => string[];
export declare const removeLocalizedDigits: (valueStr: string, localizedDigits: string[]) => string;
export declare const applyLocalizedDigits: (valueStr: string, localizedDigits: string[]) => string;
export declare const isStringNumber: (valueStr: string, localizedDigits: string[]) => boolean;
/**
* Make sure the value of a digit section have the right amount of leading zeros.
* E.g.: `03` => `3`
* Warning: Should only be called with non-localized digits. Call `removeLocalizedDigits` with your value if needed.
*/
export declare const cleanLeadingZeros: (valueStr: string, size: number) => string;
export declare const cleanDigitSectionValue: (adapter: MuiPickersAdapter, value: number, sectionBoundaries: FieldSectionValueBoundaries<any>, localizedDigits: string[], section: Pick<FieldSection, "format" | "type" | "contentType" | "hasLeadingZerosInFormat" | "hasLeadingZerosInInput" | "maxLength">) => string;
export declare const getSectionVisibleValue: (section: FieldSection, target: "input-rtl" | "input-ltr" | "non-input", localizedDigits: string[]) => string;
export declare const changeSectionValueFormat: (adapter: MuiPickersAdapter, valueStr: string, currentFormat: string, newFormat: string) => string;
export declare const doesSectionFormatHaveLeadingZeros: (adapter: MuiPickersAdapter, contentType: FieldSectionContentType, sectionType: FieldSectionType, format: string) => boolean;
/**
* Some date libraries like `dayjs` don't support parsing from date with escaped characters.
* To make sure that the parsing works, we are building a format and a date without any separator.
*/
export declare const getDateFromDateSections: (adapter: MuiPickersAdapter, sections: FieldSection[], localizedDigits: string[]) => PickerValidDate;
export declare const createDateStrForV7HiddenInputFromSections: (sections: FieldSection[]) => string;
export declare const createDateStrForV6InputFromSections: (sections: FieldSection[], localizedDigits: string[], isRtl: boolean) => string;
export declare const getSectionsBoundaries: (adapter: MuiPickersAdapter, localizedDigits: string[], timezone: PickersTimezone) => FieldSectionsValueBoundaries;
export declare const validateSections: <TValue extends PickerValidValue>(sections: InferFieldSection<TValue>[], valueType: PickerValueType) => void;
export declare const mergeDateIntoReferenceDate: (adapter: MuiPickersAdapter, dateToTransferFrom: PickerValidDate, sections: FieldSection[], referenceDate: PickerValidDate, shouldLimitToEditedSections: boolean) => PickerValidDate;
export declare const isAndroid: () => boolean;
export declare const getSectionOrder: (sections: FieldSection[], shouldApplyRTL: boolean) => SectionOrdering;
export declare const parseSelectedSections: (selectedSections: FieldSelectedSections, sections: FieldSection[]) => FieldParsedSelectedSections;

View file

@ -0,0 +1,523 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.validateSections = exports.removeLocalizedDigits = exports.parseSelectedSections = exports.mergeDateIntoReferenceDate = exports.isStringNumber = exports.isAndroid = exports.getSectionsBoundaries = exports.getSectionVisibleValue = exports.getSectionOrder = exports.getLocalizedDigits = exports.getLetterEditingOptions = exports.getDaysInWeekStr = exports.getDateSectionConfigFromFormatToken = exports.getDateFromDateSections = exports.doesSectionFormatHaveLeadingZeros = exports.createDateStrForV7HiddenInputFromSections = exports.createDateStrForV6InputFromSections = exports.cleanLeadingZeros = exports.cleanDigitSectionValue = exports.changeSectionValueFormat = exports.applyLocalizedDigits = exports.FORMAT_SECONDS_NO_LEADING_ZEROS = void 0;
var _dateUtils = require("../../utils/date-utils");
const getDateSectionConfigFromFormatToken = (adapter, formatToken) => {
const config = adapter.formatTokenMap[formatToken];
if (config == null) {
throw new Error([`MUI X: The token "${formatToken}" is not supported by the Date and Time Pickers.`, 'Please try using another token or open an issue on https://github.com/mui/mui-x/issues/new/choose if you think it should be supported.'].join('\n'));
}
if (typeof config === 'string') {
return {
type: config,
contentType: config === 'meridiem' ? 'letter' : 'digit',
maxLength: undefined
};
}
return {
type: config.sectionType,
contentType: config.contentType,
maxLength: config.maxLength
};
};
exports.getDateSectionConfigFromFormatToken = getDateSectionConfigFromFormatToken;
const getDaysInWeekStr = (adapter, format) => {
const elements = [];
const now = adapter.date(undefined, 'default');
const startDate = adapter.startOfWeek(now);
const endDate = adapter.endOfWeek(now);
let current = startDate;
while (adapter.isBefore(current, endDate)) {
elements.push(current);
current = adapter.addDays(current, 1);
}
return elements.map(weekDay => adapter.formatByString(weekDay, format));
};
exports.getDaysInWeekStr = getDaysInWeekStr;
const getLetterEditingOptions = (adapter, timezone, sectionType, format) => {
switch (sectionType) {
case 'month':
{
return (0, _dateUtils.getMonthsInYear)(adapter, adapter.date(undefined, timezone)).map(month => adapter.formatByString(month, format));
}
case 'weekDay':
{
return getDaysInWeekStr(adapter, format);
}
case 'meridiem':
{
const now = adapter.date(undefined, timezone);
return [adapter.startOfDay(now), adapter.endOfDay(now)].map(date => adapter.formatByString(date, format));
}
default:
{
return [];
}
}
};
// This format should be the same on all the adapters
// If some adapter does not respect this convention, then we will need to hardcode the format on each adapter.
exports.getLetterEditingOptions = getLetterEditingOptions;
const FORMAT_SECONDS_NO_LEADING_ZEROS = exports.FORMAT_SECONDS_NO_LEADING_ZEROS = 's';
const NON_LOCALIZED_DIGITS = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'];
const getLocalizedDigits = adapter => {
const today = adapter.date(undefined);
const formattedZero = adapter.formatByString(adapter.setSeconds(today, 0), FORMAT_SECONDS_NO_LEADING_ZEROS);
if (formattedZero === '0') {
return NON_LOCALIZED_DIGITS;
}
return Array.from({
length: 10
}).map((_, index) => adapter.formatByString(adapter.setSeconds(today, index), FORMAT_SECONDS_NO_LEADING_ZEROS));
};
exports.getLocalizedDigits = getLocalizedDigits;
const removeLocalizedDigits = (valueStr, localizedDigits) => {
if (localizedDigits[0] === '0') {
return valueStr;
}
const digits = [];
let currentFormattedDigit = '';
for (let i = 0; i < valueStr.length; i += 1) {
currentFormattedDigit += valueStr[i];
const matchingDigitIndex = localizedDigits.indexOf(currentFormattedDigit);
if (matchingDigitIndex > -1) {
digits.push(matchingDigitIndex.toString());
currentFormattedDigit = '';
}
}
return digits.join('');
};
exports.removeLocalizedDigits = removeLocalizedDigits;
const applyLocalizedDigits = (valueStr, localizedDigits) => {
if (localizedDigits[0] === '0') {
return valueStr;
}
return valueStr.split('').map(char => localizedDigits[Number(char)]).join('');
};
exports.applyLocalizedDigits = applyLocalizedDigits;
const isStringNumber = (valueStr, localizedDigits) => {
const nonLocalizedValueStr = removeLocalizedDigits(valueStr, localizedDigits);
// `Number(' ')` returns `0` even if ' ' is not a valid number.
return nonLocalizedValueStr !== ' ' && !Number.isNaN(Number(nonLocalizedValueStr));
};
/**
* Make sure the value of a digit section have the right amount of leading zeros.
* E.g.: `03` => `3`
* Warning: Should only be called with non-localized digits. Call `removeLocalizedDigits` with your value if needed.
*/
exports.isStringNumber = isStringNumber;
const cleanLeadingZeros = (valueStr, size) => {
// Remove the leading zeros and then add back as many as needed.
return Number(valueStr).toString().padStart(size, '0');
};
exports.cleanLeadingZeros = cleanLeadingZeros;
const cleanDigitSectionValue = (adapter, value, sectionBoundaries, localizedDigits, section) => {
if (process.env.NODE_ENV !== 'production') {
if (section.type !== 'day' && section.contentType === 'digit-with-letter') {
throw new Error([`MUI X: The token "${section.format}" is a digit format with letter in it.'
This type of format is only supported for 'day' sections`].join('\n'));
}
}
if (section.type === 'day' && section.contentType === 'digit-with-letter') {
const date = adapter.setDate(sectionBoundaries.longestMonth, value);
return adapter.formatByString(date, section.format);
}
// queryValue without leading `0` (`01` => `1`)
let valueStr = value.toString();
if (section.hasLeadingZerosInInput) {
valueStr = cleanLeadingZeros(valueStr, section.maxLength);
}
return applyLocalizedDigits(valueStr, localizedDigits);
};
exports.cleanDigitSectionValue = cleanDigitSectionValue;
const getSectionVisibleValue = (section, target, localizedDigits) => {
let value = section.value || section.placeholder;
const hasLeadingZeros = target === 'non-input' ? section.hasLeadingZerosInFormat : section.hasLeadingZerosInInput;
if (target === 'non-input' && section.hasLeadingZerosInInput && !section.hasLeadingZerosInFormat) {
value = Number(removeLocalizedDigits(value, localizedDigits)).toString();
}
// In the input, we add an empty character at the end of each section without leading zeros.
// This makes sure that `onChange` will always be fired.
// Otherwise, when your input value equals `1/dd/yyyy` (format `M/DD/YYYY` on DayJs),
// If you press `1`, on the first section, the new value is also `1/dd/yyyy`,
// So the browser will not fire the input `onChange`.
const shouldAddInvisibleSpace = ['input-rtl', 'input-ltr'].includes(target) && section.contentType === 'digit' && !hasLeadingZeros && value.length === 1;
if (shouldAddInvisibleSpace) {
value = `${value}\u200e`;
}
if (target === 'input-rtl') {
value = `\u2068${value}\u2069`;
}
return value;
};
exports.getSectionVisibleValue = getSectionVisibleValue;
const changeSectionValueFormat = (adapter, valueStr, currentFormat, newFormat) => {
if (process.env.NODE_ENV !== 'production') {
if (getDateSectionConfigFromFormatToken(adapter, currentFormat).type === 'weekDay') {
throw new Error("changeSectionValueFormat doesn't support week day formats");
}
}
return adapter.formatByString(adapter.parse(valueStr, currentFormat), newFormat);
};
exports.changeSectionValueFormat = changeSectionValueFormat;
const isFourDigitYearFormat = (adapter, format) => adapter.formatByString(adapter.date(undefined, 'system'), format).length === 4;
const doesSectionFormatHaveLeadingZeros = (adapter, contentType, sectionType, format) => {
if (contentType !== 'digit') {
return false;
}
const now = adapter.date(undefined, 'default');
switch (sectionType) {
// We can't use `changeSectionValueFormat`, because `adapter.parse('1', 'YYYY')` returns `1971` instead of `1`.
case 'year':
{
// Remove once https://github.com/iamkun/dayjs/pull/2847 is merged and bump dayjs version
if (adapter.lib === 'dayjs' && format === 'YY') {
return true;
}
return adapter.formatByString(adapter.setYear(now, 1), format).startsWith('0');
}
case 'month':
{
return adapter.formatByString(adapter.startOfYear(now), format).length > 1;
}
case 'day':
{
return adapter.formatByString(adapter.startOfMonth(now), format).length > 1;
}
case 'weekDay':
{
return adapter.formatByString(adapter.startOfWeek(now), format).length > 1;
}
case 'hours':
{
return adapter.formatByString(adapter.setHours(now, 1), format).length > 1;
}
case 'minutes':
{
return adapter.formatByString(adapter.setMinutes(now, 1), format).length > 1;
}
case 'seconds':
{
return adapter.formatByString(adapter.setSeconds(now, 1), format).length > 1;
}
default:
{
throw new Error('Invalid section type');
}
}
};
/**
* Some date libraries like `dayjs` don't support parsing from date with escaped characters.
* To make sure that the parsing works, we are building a format and a date without any separator.
*/
exports.doesSectionFormatHaveLeadingZeros = doesSectionFormatHaveLeadingZeros;
const getDateFromDateSections = (adapter, sections, localizedDigits) => {
// If we have both a day and a weekDay section,
// Then we skip the weekDay in the parsing because libraries like dayjs can't parse complicated formats containing a weekDay.
// dayjs(dayjs().format('dddd MMMM D YYYY'), 'dddd MMMM D YYYY')) // returns `Invalid Date` even if the format is valid.
const shouldSkipWeekDays = sections.some(section => section.type === 'day');
const sectionFormats = [];
const sectionValues = [];
for (let i = 0; i < sections.length; i += 1) {
const section = sections[i];
const shouldSkip = shouldSkipWeekDays && section.type === 'weekDay';
if (!shouldSkip) {
sectionFormats.push(section.format);
sectionValues.push(getSectionVisibleValue(section, 'non-input', localizedDigits));
}
}
const formatWithoutSeparator = sectionFormats.join(' ');
const dateWithoutSeparatorStr = sectionValues.join(' ');
return adapter.parse(dateWithoutSeparatorStr, formatWithoutSeparator);
};
exports.getDateFromDateSections = getDateFromDateSections;
const createDateStrForV7HiddenInputFromSections = sections => sections.map(section => {
return `${section.startSeparator}${section.value || section.placeholder}${section.endSeparator}`;
}).join('');
exports.createDateStrForV7HiddenInputFromSections = createDateStrForV7HiddenInputFromSections;
const createDateStrForV6InputFromSections = (sections, localizedDigits, isRtl) => {
const formattedSections = sections.map(section => {
const dateValue = getSectionVisibleValue(section, isRtl ? 'input-rtl' : 'input-ltr', localizedDigits);
return `${section.startSeparator}${dateValue}${section.endSeparator}`;
});
const dateStr = formattedSections.join('');
if (!isRtl) {
return dateStr;
}
// \u2066: start left-to-right isolation
// \u2067: start right-to-left isolation
// \u2068: start first strong character isolation
// \u2069: pop isolation
// wrap into an isolated group such that separators can split the string in smaller ones by adding \u2069\u2068
return `\u2066${dateStr}\u2069`;
};
exports.createDateStrForV6InputFromSections = createDateStrForV6InputFromSections;
const getSectionsBoundaries = (adapter, localizedDigits, timezone) => {
const today = adapter.date(undefined, timezone);
const endOfYear = adapter.endOfYear(today);
const endOfDay = adapter.endOfDay(today);
const {
maxDaysInMonth,
longestMonth
} = (0, _dateUtils.getMonthsInYear)(adapter, today).reduce((acc, month) => {
const daysInMonth = adapter.getDaysInMonth(month);
if (daysInMonth > acc.maxDaysInMonth) {
return {
maxDaysInMonth: daysInMonth,
longestMonth: month
};
}
return acc;
}, {
maxDaysInMonth: 0,
longestMonth: null
});
return {
year: ({
format
}) => ({
minimum: 0,
maximum: isFourDigitYearFormat(adapter, format) ? 9999 : 99
}),
month: () => ({
minimum: 1,
// Assumption: All years have the same amount of months
maximum: adapter.getMonth(endOfYear) + 1
}),
day: ({
currentDate
}) => ({
minimum: 1,
maximum: adapter.isValid(currentDate) ? adapter.getDaysInMonth(currentDate) : maxDaysInMonth,
longestMonth: longestMonth
}),
weekDay: ({
format,
contentType
}) => {
if (contentType === 'digit') {
const daysInWeek = getDaysInWeekStr(adapter, format).map(Number);
return {
minimum: Math.min(...daysInWeek),
maximum: Math.max(...daysInWeek)
};
}
return {
minimum: 1,
maximum: 7
};
},
hours: ({
format
}) => {
const lastHourInDay = adapter.getHours(endOfDay);
const hasMeridiem = removeLocalizedDigits(adapter.formatByString(adapter.endOfDay(today), format), localizedDigits) !== lastHourInDay.toString();
if (hasMeridiem) {
return {
minimum: 1,
maximum: Number(removeLocalizedDigits(adapter.formatByString(adapter.startOfDay(today), format), localizedDigits))
};
}
return {
minimum: 0,
maximum: lastHourInDay
};
},
minutes: () => ({
minimum: 0,
// Assumption: All years have the same amount of minutes
maximum: adapter.getMinutes(endOfDay)
}),
seconds: () => ({
minimum: 0,
// Assumption: All years have the same amount of seconds
maximum: adapter.getSeconds(endOfDay)
}),
meridiem: () => ({
minimum: 0,
maximum: 1
}),
empty: () => ({
minimum: 0,
maximum: 0
})
};
};
exports.getSectionsBoundaries = getSectionsBoundaries;
let warnedOnceInvalidSection = false;
const validateSections = (sections, valueType) => {
if (process.env.NODE_ENV !== 'production') {
if (!warnedOnceInvalidSection) {
const supportedSections = ['empty'];
if (['date', 'date-time'].includes(valueType)) {
supportedSections.push('weekDay', 'day', 'month', 'year');
}
if (['time', 'date-time'].includes(valueType)) {
supportedSections.push('hours', 'minutes', 'seconds', 'meridiem');
}
const invalidSection = sections.find(section => !supportedSections.includes(section.type));
if (invalidSection) {
console.warn(`MUI X: The field component you are using is not compatible with the "${invalidSection.type}" date section.`, `The supported date sections are ["${supportedSections.join('", "')}"]\`.`);
warnedOnceInvalidSection = true;
}
}
}
};
exports.validateSections = validateSections;
const transferDateSectionValue = (adapter, section, dateToTransferFrom, dateToTransferTo) => {
switch (section.type) {
case 'year':
{
return adapter.setYear(dateToTransferTo, adapter.getYear(dateToTransferFrom));
}
case 'month':
{
return adapter.setMonth(dateToTransferTo, adapter.getMonth(dateToTransferFrom));
}
case 'weekDay':
{
let dayInWeekStrOfActiveDate = adapter.formatByString(dateToTransferFrom, section.format);
if (section.hasLeadingZerosInInput) {
dayInWeekStrOfActiveDate = cleanLeadingZeros(dayInWeekStrOfActiveDate, section.maxLength);
}
const formattedDaysInWeek = getDaysInWeekStr(adapter, section.format);
const dayInWeekOfActiveDate = formattedDaysInWeek.indexOf(dayInWeekStrOfActiveDate);
const dayInWeekOfNewSectionValue = formattedDaysInWeek.indexOf(section.value);
const diff = dayInWeekOfNewSectionValue - dayInWeekOfActiveDate;
return adapter.addDays(dateToTransferFrom, diff);
}
case 'day':
{
return adapter.setDate(dateToTransferTo, adapter.getDate(dateToTransferFrom));
}
case 'meridiem':
{
const isAM = adapter.getHours(dateToTransferFrom) < 12;
const mergedDateHours = adapter.getHours(dateToTransferTo);
if (isAM && mergedDateHours >= 12) {
return adapter.addHours(dateToTransferTo, -12);
}
if (!isAM && mergedDateHours < 12) {
return adapter.addHours(dateToTransferTo, 12);
}
return dateToTransferTo;
}
case 'hours':
{
return adapter.setHours(dateToTransferTo, adapter.getHours(dateToTransferFrom));
}
case 'minutes':
{
return adapter.setMinutes(dateToTransferTo, adapter.getMinutes(dateToTransferFrom));
}
case 'seconds':
{
return adapter.setSeconds(dateToTransferTo, adapter.getSeconds(dateToTransferFrom));
}
default:
{
return dateToTransferTo;
}
}
};
const reliableSectionModificationOrder = {
year: 1,
month: 2,
day: 3,
weekDay: 4,
hours: 5,
minutes: 6,
seconds: 7,
meridiem: 8,
empty: 9
};
const mergeDateIntoReferenceDate = (adapter, dateToTransferFrom, sections, referenceDate, shouldLimitToEditedSections) =>
// cloning sections before sort to avoid mutating it
[...sections].sort((a, b) => reliableSectionModificationOrder[a.type] - reliableSectionModificationOrder[b.type]).reduce((mergedDate, section) => {
if (!shouldLimitToEditedSections || section.modified) {
return transferDateSectionValue(adapter, section, dateToTransferFrom, mergedDate);
}
return mergedDate;
}, referenceDate);
exports.mergeDateIntoReferenceDate = mergeDateIntoReferenceDate;
const isAndroid = () => navigator.userAgent.toLowerCase().includes('android');
// TODO v9: Remove
exports.isAndroid = isAndroid;
const getSectionOrder = (sections, shouldApplyRTL) => {
const neighbors = {};
if (!shouldApplyRTL) {
sections.forEach((_, index) => {
const leftIndex = index === 0 ? null : index - 1;
const rightIndex = index === sections.length - 1 ? null : index + 1;
neighbors[index] = {
leftIndex,
rightIndex
};
});
return {
neighbors,
startIndex: 0,
endIndex: sections.length - 1
};
}
const rtl2ltr = {};
const ltr2rtl = {};
let groupedSectionsStart = 0;
let groupedSectionsEnd = 0;
let RTLIndex = sections.length - 1;
while (RTLIndex >= 0) {
groupedSectionsEnd = sections.findIndex(
// eslint-disable-next-line @typescript-eslint/no-loop-func
(section, index) => index >= groupedSectionsStart && section.endSeparator?.includes(' ') &&
// Special case where the spaces were not there in the initial input
section.endSeparator !== ' / ');
if (groupedSectionsEnd === -1) {
groupedSectionsEnd = sections.length - 1;
}
for (let i = groupedSectionsEnd; i >= groupedSectionsStart; i -= 1) {
ltr2rtl[i] = RTLIndex;
rtl2ltr[RTLIndex] = i;
RTLIndex -= 1;
}
groupedSectionsStart = groupedSectionsEnd + 1;
}
sections.forEach((_, index) => {
const rtlIndex = ltr2rtl[index];
const leftIndex = rtlIndex === 0 ? null : rtl2ltr[rtlIndex - 1];
const rightIndex = rtlIndex === sections.length - 1 ? null : rtl2ltr[rtlIndex + 1];
neighbors[index] = {
leftIndex,
rightIndex
};
});
return {
neighbors,
startIndex: rtl2ltr[0],
endIndex: rtl2ltr[sections.length - 1]
};
};
exports.getSectionOrder = getSectionOrder;
const parseSelectedSections = (selectedSections, sections) => {
if (selectedSections == null) {
return null;
}
if (selectedSections === 'all') {
return 'all';
}
if (typeof selectedSections === 'string') {
const index = sections.findIndex(section => section.type === selectedSections);
return index === -1 ? null : index;
}
return selectedSections;
};
exports.parseSelectedSections = parseSelectedSections;

View file

@ -0,0 +1,29 @@
import { UseFieldStateReturnValue } from "./useFieldState.js";
import { PickerValidValue } from "../../models/index.js";
/**
* Update the active section value when the user pressed a key that is not a navigation key (arrow key for example).
* This hook has two main editing behaviors
*
* 1. The numeric editing when the user presses a digit
* 2. The letter editing when the user presses another key
*/
export declare const useFieldCharacterEditing: <TValue extends PickerValidValue>({
stateResponse: {
localizedDigits,
sectionsValueBoundaries,
state,
timezone,
setCharacterQuery,
setTempAndroidValueStr,
updateSectionValue
}
}: UseFieldCharacterEditingParameters<TValue>) => UseFieldCharacterEditingReturnValue;
export interface ApplyCharacterEditingParameters {
keyPressed: string;
sectionIndex: number;
}
interface UseFieldCharacterEditingParameters<TValue extends PickerValidValue> {
stateResponse: UseFieldStateReturnValue<TValue>;
}
export type UseFieldCharacterEditingReturnValue = (params: ApplyCharacterEditingParameters) => void;
export {};

View file

@ -0,0 +1,264 @@
"use strict";
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault").default;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.useFieldCharacterEditing = void 0;
var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends"));
var _useEventCallback = _interopRequireDefault(require("@mui/utils/useEventCallback"));
var _useField = require("./useField.utils");
var _usePickerAdapter = require("../../../hooks/usePickerAdapter");
const isQueryResponseWithoutValue = response => response.saveQuery != null;
/**
* Update the active section value when the user pressed a key that is not a navigation key (arrow key for example).
* This hook has two main editing behaviors
*
* 1. The numeric editing when the user presses a digit
* 2. The letter editing when the user presses another key
*/
const useFieldCharacterEditing = ({
stateResponse: {
// States and derived states
localizedDigits,
sectionsValueBoundaries,
state,
timezone,
// Methods to update the states
setCharacterQuery,
setTempAndroidValueStr,
updateSectionValue
}
}) => {
const adapter = (0, _usePickerAdapter.usePickerAdapter)();
const applyQuery = ({
keyPressed,
sectionIndex
}, getFirstSectionValueMatchingWithQuery, isValidQueryValue) => {
const cleanKeyPressed = keyPressed.toLowerCase();
const activeSection = state.sections[sectionIndex];
// The current query targets the section being editing
// We can try to concatenate the value
if (state.characterQuery != null && (!isValidQueryValue || isValidQueryValue(state.characterQuery.value)) && state.characterQuery.sectionIndex === sectionIndex) {
const concatenatedQueryValue = `${state.characterQuery.value}${cleanKeyPressed}`;
const queryResponse = getFirstSectionValueMatchingWithQuery(concatenatedQueryValue, activeSection);
if (!isQueryResponseWithoutValue(queryResponse)) {
setCharacterQuery({
sectionIndex,
value: concatenatedQueryValue,
sectionType: activeSection.type
});
return queryResponse;
}
}
const queryResponse = getFirstSectionValueMatchingWithQuery(cleanKeyPressed, activeSection);
if (isQueryResponseWithoutValue(queryResponse) && !queryResponse.saveQuery) {
setCharacterQuery(null);
return null;
}
setCharacterQuery({
sectionIndex,
value: cleanKeyPressed,
sectionType: activeSection.type
});
if (isQueryResponseWithoutValue(queryResponse)) {
return null;
}
return queryResponse;
};
const applyLetterEditing = params => {
const findMatchingOptions = (format, options, queryValue) => {
const matchingValues = options.filter(option => option.toLowerCase().startsWith(queryValue));
if (matchingValues.length === 0) {
return {
saveQuery: false
};
}
return {
sectionValue: matchingValues[0],
shouldGoToNextSection: matchingValues.length === 1
};
};
const testQueryOnFormatAndFallbackFormat = (queryValue, activeSection, fallbackFormat, formatFallbackValue) => {
const getOptions = format => (0, _useField.getLetterEditingOptions)(adapter, timezone, activeSection.type, format);
if (activeSection.contentType === 'letter') {
return findMatchingOptions(activeSection.format, getOptions(activeSection.format), queryValue);
}
// When editing a digit-format month / weekDay and the user presses a letter,
// We can support the letter editing by using the letter-format month / weekDay and re-formatting the result.
// We just have to make sure that the default month / weekDay format is a letter format,
if (fallbackFormat && formatFallbackValue != null && (0, _useField.getDateSectionConfigFromFormatToken)(adapter, fallbackFormat).contentType === 'letter') {
const fallbackOptions = getOptions(fallbackFormat);
const response = findMatchingOptions(fallbackFormat, fallbackOptions, queryValue);
if (isQueryResponseWithoutValue(response)) {
return {
saveQuery: false
};
}
return (0, _extends2.default)({}, response, {
sectionValue: formatFallbackValue(response.sectionValue, fallbackOptions)
});
}
return {
saveQuery: false
};
};
const getFirstSectionValueMatchingWithQuery = (queryValue, activeSection) => {
switch (activeSection.type) {
case 'month':
{
const formatFallbackValue = fallbackValue => (0, _useField.changeSectionValueFormat)(adapter, fallbackValue, adapter.formats.month, activeSection.format);
return testQueryOnFormatAndFallbackFormat(queryValue, activeSection, adapter.formats.month, formatFallbackValue);
}
case 'weekDay':
{
const formatFallbackValue = (fallbackValue, fallbackOptions) => fallbackOptions.indexOf(fallbackValue).toString();
return testQueryOnFormatAndFallbackFormat(queryValue, activeSection, adapter.formats.weekday, formatFallbackValue);
}
case 'meridiem':
{
return testQueryOnFormatAndFallbackFormat(queryValue, activeSection);
}
default:
{
return {
saveQuery: false
};
}
}
};
return applyQuery(params, getFirstSectionValueMatchingWithQuery);
};
const applyNumericEditing = params => {
const getNewSectionValue = ({
queryValue,
skipIfBelowMinimum,
section
}) => {
const cleanQueryValue = (0, _useField.removeLocalizedDigits)(queryValue, localizedDigits);
const queryValueNumber = Number(cleanQueryValue);
const sectionBoundaries = sectionsValueBoundaries[section.type]({
currentDate: null,
format: section.format,
contentType: section.contentType
});
if (queryValueNumber > sectionBoundaries.maximum) {
return {
saveQuery: false
};
}
// If the user types `0` on a month section,
// It is below the minimum, but we want to store the `0` in the query,
// So that when he pressed `1`, it will store `01` and move to the next section.
if (skipIfBelowMinimum && queryValueNumber < sectionBoundaries.minimum) {
return {
saveQuery: true
};
}
const shouldGoToNextSection = queryValueNumber * 10 > sectionBoundaries.maximum || cleanQueryValue.length === sectionBoundaries.maximum.toString().length;
const newSectionValue = (0, _useField.cleanDigitSectionValue)(adapter, queryValueNumber, sectionBoundaries, localizedDigits, section);
return {
sectionValue: newSectionValue,
shouldGoToNextSection
};
};
const getFirstSectionValueMatchingWithQuery = (queryValue, activeSection) => {
if (activeSection.contentType === 'digit' || activeSection.contentType === 'digit-with-letter') {
return getNewSectionValue({
queryValue,
skipIfBelowMinimum: false,
section: activeSection
});
}
// When editing a letter-format month and the user presses a digit,
// We can support the numeric editing by using the digit-format month and re-formatting the result.
if (activeSection.type === 'month') {
const hasLeadingZerosInFormat = (0, _useField.doesSectionFormatHaveLeadingZeros)(adapter, 'digit', 'month', 'MM');
const response = getNewSectionValue({
queryValue,
skipIfBelowMinimum: true,
section: {
type: activeSection.type,
format: 'MM',
hasLeadingZerosInFormat,
hasLeadingZerosInInput: true,
contentType: 'digit',
maxLength: 2
}
});
if (isQueryResponseWithoutValue(response)) {
return response;
}
const formattedValue = (0, _useField.changeSectionValueFormat)(adapter, response.sectionValue, 'MM', activeSection.format);
return (0, _extends2.default)({}, response, {
sectionValue: formattedValue
});
}
// When editing a letter-format weekDay and the user presses a digit,
// We can support the numeric editing by returning the nth day in the week day array.
if (activeSection.type === 'weekDay') {
const response = getNewSectionValue({
queryValue,
skipIfBelowMinimum: true,
section: activeSection
});
if (isQueryResponseWithoutValue(response)) {
return response;
}
const formattedValue = (0, _useField.getDaysInWeekStr)(adapter, activeSection.format)[Number(response.sectionValue) - 1];
return (0, _extends2.default)({}, response, {
sectionValue: formattedValue
});
}
return {
saveQuery: false
};
};
return applyQuery(params, getFirstSectionValueMatchingWithQuery, queryValue => (0, _useField.isStringNumber)(queryValue, localizedDigits));
};
return (0, _useEventCallback.default)(params => {
const section = state.sections[params.sectionIndex];
const isNumericEditing = (0, _useField.isStringNumber)(params.keyPressed, localizedDigits);
const response = isNumericEditing ? applyNumericEditing((0, _extends2.default)({}, params, {
keyPressed: (0, _useField.applyLocalizedDigits)(params.keyPressed, localizedDigits)
})) : applyLetterEditing(params);
if (response == null) {
setTempAndroidValueStr(null);
return;
}
updateSectionValue({
section,
newSectionValue: response.sectionValue,
shouldGoToNextSection: response.shouldGoToNextSection
});
});
};
/**
* The letter editing and the numeric editing each define a `CharacterEditingApplier`.
* This function decides what the new section value should be and if the focus should switch to the next section.
*
* If it returns `null`, then the section value is not updated and the focus does not move.
*/
/**
* Function called by `applyQuery` which decides:
* - what is the new section value ?
* - should the query used to get this value be stored for the next key press ?
*
* If it returns `{ sectionValue: string; shouldGoToNextSection: boolean }`,
* Then we store the query and update the section with the new value.
*
* If it returns `{ saveQuery: true` },
* Then we store the query and don't update the section.
*
* If it returns `{ saveQuery: false },
* Then we do nothing.
*/
exports.useFieldCharacterEditing = useFieldCharacterEditing;

View file

@ -0,0 +1,20 @@
import * as React from 'react';
import { PickerManager } from "../../../models/index.js";
import { UseFieldStateReturnValue } from "./useFieldState.js";
/**
* Generate the props to pass to the hidden input element of the field.
* It is not used by the non-accessible DOM structure (with an <input /> element for editing).
* It should be used in the MUI accessible DOM structure and the Base UI implementation.
* @param {UseFieldHiddenInputPropsParameters} parameters The parameters of the hook.
* @returns {UseFieldHiddenInputPropsReturnValue} The props to forward to the hidden input element of the field.
*/
export declare function useFieldHiddenInputProps(parameters: UseFieldHiddenInputPropsParameters): UseFieldHiddenInputPropsReturnValue;
interface UseFieldHiddenInputPropsParameters {
manager: PickerManager<any, any, any, any, any>;
stateResponse: UseFieldStateReturnValue<any>;
}
interface UseFieldHiddenInputPropsReturnValue {
value: string;
onChange: React.ChangeEventHandler<HTMLInputElement>;
}
export {};

View file

@ -0,0 +1,39 @@
"use strict";
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault").default;
var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard").default;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.useFieldHiddenInputProps = useFieldHiddenInputProps;
var React = _interopRequireWildcard(require("react"));
var _useEventCallback = _interopRequireDefault(require("@mui/utils/useEventCallback"));
/**
* Generate the props to pass to the hidden input element of the field.
* It is not used by the non-accessible DOM structure (with an <input /> element for editing).
* It should be used in the MUI accessible DOM structure and the Base UI implementation.
* @param {UseFieldHiddenInputPropsParameters} parameters The parameters of the hook.
* @returns {UseFieldHiddenInputPropsReturnValue} The props to forward to the hidden input element of the field.
*/
function useFieldHiddenInputProps(parameters) {
const {
manager: {
internal_fieldValueManager: fieldValueManager
},
stateResponse: {
// States and derived states
areAllSectionsEmpty,
state,
// Methods to update the states
updateValueFromValueStr
}
} = parameters;
const handleChange = (0, _useEventCallback.default)(event => {
updateValueFromValueStr(event.target.value);
});
const valueStr = React.useMemo(() => areAllSectionsEmpty ? '' : fieldValueManager.getV7HiddenInputValueFromSections(state.sections), [areAllSectionsEmpty, state.sections, fieldValueManager]);
return {
value: valueStr,
onChange: handleChange
};
}

View file

@ -0,0 +1,17 @@
import { PickerAnyManager, PickerManagerFieldInternalProps, PickerManagerFieldInternalPropsWithDefaults } from "../../models/index.js";
/**
* Applies the default values to the field internal props.
* This is a temporary hook that will be removed during a follow up when `useField` will receive the internal props without the defaults.
* It is only here to allow the migration to be done in smaller steps.
*/
export declare function useFieldInternalPropsWithDefaults<TManager extends PickerAnyManager>(parameters: UseFieldInternalPropsWithDefaultsParameters<TManager>): PickerManagerFieldInternalPropsWithDefaults<TManager>;
interface UseFieldInternalPropsWithDefaultsParameters<TManager extends PickerAnyManager> {
manager: TManager;
internalProps: PickerManagerFieldInternalProps<TManager>;
/**
* Hack to make sure that on multi input range field, the `useNullableFieldPrivateContext().fieldRef` is only bound to the field matching the range position.
* @default false
*/
skipContextFieldRefAssignment?: boolean;
}
export {};

View file

@ -0,0 +1,60 @@
"use strict";
var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard").default;
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault").default;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.useFieldInternalPropsWithDefaults = useFieldInternalPropsWithDefaults;
var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends"));
var React = _interopRequireWildcard(require("react"));
var _useForkRef = _interopRequireDefault(require("@mui/utils/useForkRef"));
var _useNullablePickerContext = require("../useNullablePickerContext");
var _useNullableFieldPrivateContext = require("../useNullableFieldPrivateContext");
/**
* Applies the default values to the field internal props.
* This is a temporary hook that will be removed during a follow up when `useField` will receive the internal props without the defaults.
* It is only here to allow the migration to be done in smaller steps.
*/
function useFieldInternalPropsWithDefaults(parameters) {
const {
manager: {
internal_useApplyDefaultValuesToFieldInternalProps: useApplyDefaultValuesToFieldInternalProps
},
internalProps,
skipContextFieldRefAssignment
} = parameters;
const pickerContext = (0, _useNullablePickerContext.useNullablePickerContext)();
const fieldPrivateContext = (0, _useNullableFieldPrivateContext.useNullableFieldPrivateContext)();
const handleFieldRef = (0, _useForkRef.default)(internalProps.unstableFieldRef, skipContextFieldRefAssignment ? null : fieldPrivateContext?.fieldRef);
const setValue = pickerContext?.setValue;
const handleChangeFromPicker = React.useCallback((newValue, ctx) => {
return setValue?.(newValue, {
validationError: ctx.validationError,
shouldClose: false
});
}, [setValue]);
const internalPropsWithDefaultsFromContext = React.useMemo(() => {
// If one of the context is null,
// Then the field is used as a standalone component and the other context will be null as well.
if (fieldPrivateContext != null && pickerContext != null) {
return (0, _extends2.default)({
value: pickerContext.value,
onChange: handleChangeFromPicker,
timezone: pickerContext.timezone,
disabled: pickerContext.disabled,
readOnly: pickerContext.readOnly,
autoFocus: pickerContext.autoFocus && !pickerContext.open,
focused: pickerContext.open ? true : undefined,
format: pickerContext.fieldFormat,
formatDensity: fieldPrivateContext.formatDensity,
enableAccessibleFieldDOMStructure: fieldPrivateContext.enableAccessibleFieldDOMStructure,
selectedSections: fieldPrivateContext.selectedSections,
onSelectedSectionsChange: fieldPrivateContext.onSelectedSectionsChange,
unstableFieldRef: handleFieldRef
}, internalProps);
}
return internalProps;
}, [pickerContext, fieldPrivateContext, internalProps, handleChangeFromPicker, handleFieldRef]);
return useApplyDefaultValuesToFieldInternalProps(internalPropsWithDefaultsFromContext);
}

View file

@ -0,0 +1,16 @@
import { PickerManager } from "../../../models/index.js";
import { PickerValidValue } from "../../models/index.js";
import { UseFieldStateReturnValue } from "./useFieldState.js";
import { UseFieldInternalProps } from "./useField.types.js";
/**
* Returns the `onKeyDown` handler to pass to the root element of the field.
*/
export declare function useFieldRootHandleKeyDown<TValue extends PickerValidValue>(parameters: UseFieldRootHandleKeyDownParameters<TValue>): (event: React.KeyboardEvent<HTMLSpanElement>) => void;
interface UseFieldRootHandleKeyDownParameters<TValue extends PickerValidValue> {
manager: PickerManager<TValue, any, any, any, any>;
stateResponse: UseFieldStateReturnValue<TValue>;
internalPropsWithDefaults: UseFieldInternalProps<TValue, any, any> & {
minutesStep?: number;
};
}
export {};

View file

@ -0,0 +1,211 @@
"use strict";
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault").default;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.useFieldRootHandleKeyDown = useFieldRootHandleKeyDown;
var _useEventCallback = _interopRequireDefault(require("@mui/utils/useEventCallback"));
var _useField = require("./useField.utils");
var _usePickerAdapter = require("../../../hooks/usePickerAdapter");
/**
* Returns the `onKeyDown` handler to pass to the root element of the field.
*/
function useFieldRootHandleKeyDown(parameters) {
const adapter = (0, _usePickerAdapter.usePickerAdapter)();
const {
manager: {
internal_fieldValueManager: fieldValueManager
},
internalPropsWithDefaults: {
minutesStep,
disabled,
readOnly
},
stateResponse: {
// States and derived states
state,
value,
activeSectionIndex,
parsedSelectedSections,
sectionsValueBoundaries,
localizedDigits,
timezone,
sectionOrder,
// Methods to update the states
clearValue,
clearActiveSection,
setSelectedSections,
updateSectionValue
}
} = parameters;
return (0, _useEventCallback.default)(event => {
if (disabled) {
return;
}
// eslint-disable-next-line default-case
switch (true) {
// Select all
case (event.ctrlKey || event.metaKey) && String.fromCharCode(event.keyCode) === 'A' && !event.shiftKey && !event.altKey:
{
// prevent default to make sure that the next line "select all" while updating
// the internal state at the same time.
event.preventDefault();
setSelectedSections('all');
break;
}
// Move selection to next section
case event.key === 'ArrowRight':
{
event.preventDefault();
if (parsedSelectedSections == null) {
setSelectedSections(sectionOrder.startIndex);
} else if (parsedSelectedSections === 'all') {
setSelectedSections(sectionOrder.endIndex);
} else {
const nextSectionIndex = sectionOrder.neighbors[parsedSelectedSections].rightIndex;
if (nextSectionIndex !== null) {
setSelectedSections(nextSectionIndex);
}
}
break;
}
// Move selection to previous section
case event.key === 'ArrowLeft':
{
event.preventDefault();
if (parsedSelectedSections == null) {
setSelectedSections(sectionOrder.endIndex);
} else if (parsedSelectedSections === 'all') {
setSelectedSections(sectionOrder.startIndex);
} else {
const nextSectionIndex = sectionOrder.neighbors[parsedSelectedSections].leftIndex;
if (nextSectionIndex !== null) {
setSelectedSections(nextSectionIndex);
}
}
break;
}
// Reset the value of the selected section
case event.key === 'Delete':
{
event.preventDefault();
if (readOnly) {
break;
}
if (parsedSelectedSections == null || parsedSelectedSections === 'all') {
clearValue();
} else {
clearActiveSection();
}
break;
}
// Increment / decrement the selected section value
case ['ArrowUp', 'ArrowDown', 'Home', 'End', 'PageUp', 'PageDown'].includes(event.key):
{
event.preventDefault();
if (readOnly || activeSectionIndex == null) {
break;
}
// if all sections are selected, mark the currently editing one as selected
if (parsedSelectedSections === 'all') {
setSelectedSections(activeSectionIndex);
}
const activeSection = state.sections[activeSectionIndex];
const newSectionValue = adjustSectionValue(adapter, timezone, activeSection, event.key, sectionsValueBoundaries, localizedDigits, fieldValueManager.getDateFromSection(value, activeSection), {
minutesStep
});
updateSectionValue({
section: activeSection,
newSectionValue,
shouldGoToNextSection: false
});
break;
}
}
});
}
function getDeltaFromKeyCode(keyCode) {
switch (keyCode) {
case 'ArrowUp':
return 1;
case 'ArrowDown':
return -1;
case 'PageUp':
return 5;
case 'PageDown':
return -5;
default:
return 0;
}
}
function adjustSectionValue(adapter, timezone, section, keyCode, sectionsValueBoundaries, localizedDigits, activeDate, stepsAttributes) {
const delta = getDeltaFromKeyCode(keyCode);
const isStart = keyCode === 'Home';
const isEnd = keyCode === 'End';
const shouldSetAbsolute = section.value === '' || isStart || isEnd;
const adjustDigitSection = () => {
const sectionBoundaries = sectionsValueBoundaries[section.type]({
currentDate: activeDate,
format: section.format,
contentType: section.contentType
});
const getCleanValue = value => (0, _useField.cleanDigitSectionValue)(adapter, value, sectionBoundaries, localizedDigits, section);
const step = section.type === 'minutes' && stepsAttributes?.minutesStep ? stepsAttributes.minutesStep : 1;
let newSectionValueNumber;
if (shouldSetAbsolute) {
if (section.type === 'year' && !isEnd && !isStart) {
return adapter.formatByString(adapter.date(undefined, timezone), section.format);
}
if (delta > 0 || isStart) {
newSectionValueNumber = sectionBoundaries.minimum;
} else {
newSectionValueNumber = sectionBoundaries.maximum;
}
} else {
const currentSectionValue = parseInt((0, _useField.removeLocalizedDigits)(section.value, localizedDigits), 10);
newSectionValueNumber = currentSectionValue + delta * step;
}
if (newSectionValueNumber % step !== 0) {
if (delta < 0 || isStart) {
newSectionValueNumber += step - (step + newSectionValueNumber) % step; // for JS -3 % 5 = -3 (should be 2)
}
if (delta > 0 || isEnd) {
newSectionValueNumber -= newSectionValueNumber % step;
}
}
if (newSectionValueNumber > sectionBoundaries.maximum) {
return getCleanValue(sectionBoundaries.minimum + (newSectionValueNumber - sectionBoundaries.maximum - 1) % (sectionBoundaries.maximum - sectionBoundaries.minimum + 1));
}
if (newSectionValueNumber < sectionBoundaries.minimum) {
return getCleanValue(sectionBoundaries.maximum - (sectionBoundaries.minimum - newSectionValueNumber - 1) % (sectionBoundaries.maximum - sectionBoundaries.minimum + 1));
}
return getCleanValue(newSectionValueNumber);
};
const adjustLetterSection = () => {
const options = (0, _useField.getLetterEditingOptions)(adapter, timezone, section.type, section.format);
if (options.length === 0) {
return section.value;
}
if (shouldSetAbsolute) {
if (delta > 0 || isStart) {
return options[0];
}
return options[options.length - 1];
}
const currentOptionIndex = options.indexOf(section.value);
const newOptionIndex = (currentOptionIndex + delta) % options.length;
const clampedIndex = (newOptionIndex + options.length) % options.length;
return options[clampedIndex];
};
if (section.contentType === 'digit' || section.contentType === 'digit-with-letter') {
return adjustDigitSection();
}
return adjustLetterSection();
}

View file

@ -0,0 +1,32 @@
import { PickerManager } from "../../../models/index.js";
import { UseFieldDOMGetters, UseFieldInternalProps } from "./useField.types.js";
import { UseFieldStateReturnValue } from "./useFieldState.js";
import { UseFieldCharacterEditingReturnValue } from "./useFieldCharacterEditing.js";
/**
* Generate the props to pass to the root element of the field.
* It is not used by the non-accessible DOM structure (with an <input /> element for editing).
* It should be used in the MUI accessible DOM structure and the Base UI implementation.
* @param {UseFieldRootPropsParameters} parameters The parameters of the hook.
* @returns {UseFieldRootPropsReturnValue} The props to forward to the root element of the field.
*/
export declare function useFieldRootProps(parameters: UseFieldRootPropsParameters): UseFieldRootPropsReturnValue;
interface UseFieldRootPropsParameters {
manager: PickerManager<any, any, any, any, any>;
stateResponse: UseFieldStateReturnValue<any>;
applyCharacterEditing: UseFieldCharacterEditingReturnValue;
internalPropsWithDefaults: UseFieldInternalProps<any, any, any>;
domGetters: UseFieldDOMGetters;
focused: boolean;
setFocused: (focused: boolean) => void;
}
interface UseFieldRootPropsReturnValue {
onKeyDown: React.KeyboardEventHandler<HTMLDivElement>;
onBlur: React.FocusEventHandler<HTMLDivElement>;
onFocus: React.FocusEventHandler<HTMLDivElement>;
onClick: React.MouseEventHandler<HTMLDivElement>;
onPaste: React.ClipboardEventHandler<HTMLDivElement>;
onInput: React.FormEventHandler<HTMLDivElement>;
contentEditable: boolean;
tabIndex: number;
}
export {};

View file

@ -0,0 +1,157 @@
"use strict";
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault").default;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.useFieldRootProps = useFieldRootProps;
var _useEventCallback = _interopRequireDefault(require("@mui/utils/useEventCallback"));
var _useTimeout = _interopRequireDefault(require("@mui/utils/useTimeout"));
var _useFieldRootHandleKeyDown = require("./useFieldRootHandleKeyDown");
var _utils = require("../../utils/utils");
var _syncSelectionToDOM = require("./syncSelectionToDOM");
/**
* Generate the props to pass to the root element of the field.
* It is not used by the non-accessible DOM structure (with an <input /> element for editing).
* It should be used in the MUI accessible DOM structure and the Base UI implementation.
* @param {UseFieldRootPropsParameters} parameters The parameters of the hook.
* @returns {UseFieldRootPropsReturnValue} The props to forward to the root element of the field.
*/
function useFieldRootProps(parameters) {
const {
manager,
focused,
setFocused,
domGetters,
stateResponse,
applyCharacterEditing,
internalPropsWithDefaults,
stateResponse: {
// States and derived states
parsedSelectedSections,
sectionOrder,
state,
// Methods to update the states
clearValue,
setCharacterQuery,
setSelectedSections,
updateValueFromValueStr
},
internalPropsWithDefaults: {
disabled = false,
readOnly = false
}
} = parameters;
// TODO: Inline onContainerKeyDown once the old DOM structure is removed
const handleKeyDown = (0, _useFieldRootHandleKeyDown.useFieldRootHandleKeyDown)({
manager,
internalPropsWithDefaults,
stateResponse
});
const containerClickTimeout = (0, _useTimeout.default)();
const handleClick = (0, _useEventCallback.default)(event => {
if (disabled || !domGetters.isReady()) {
return;
}
setFocused(true);
if (parsedSelectedSections === 'all') {
containerClickTimeout.start(0, () => {
const cursorPosition = document.getSelection().getRangeAt(0).startOffset;
if (cursorPosition === 0) {
setSelectedSections(sectionOrder.startIndex);
return;
}
let sectionIndex = 0;
let cursorOnStartOfSection = 0;
while (cursorOnStartOfSection < cursorPosition && sectionIndex < state.sections.length) {
const section = state.sections[sectionIndex];
sectionIndex += 1;
cursorOnStartOfSection += `${section.startSeparator}${section.value || section.placeholder}${section.endSeparator}`.length;
}
setSelectedSections(sectionIndex - 1);
});
} else if (!focused) {
setFocused(true);
setSelectedSections(sectionOrder.startIndex);
} else {
const hasClickedOnASection = domGetters.getRoot().contains(event.target);
if (!hasClickedOnASection) {
setSelectedSections(sectionOrder.startIndex);
}
}
});
const handleInput = (0, _useEventCallback.default)(event => {
if (!domGetters.isReady() || parsedSelectedSections !== 'all') {
return;
}
const target = event.target;
const keyPressed = target.textContent ?? '';
domGetters.getRoot().innerHTML = state.sections.map(section => `${section.startSeparator}${section.value || section.placeholder}${section.endSeparator}`).join('');
(0, _syncSelectionToDOM.syncSelectionToDOM)({
focused,
domGetters,
stateResponse
});
if (keyPressed.length === 0 || keyPressed.charCodeAt(0) === 10) {
clearValue();
setSelectedSections('all');
} else if (keyPressed.length > 1) {
updateValueFromValueStr(keyPressed);
} else {
if (parsedSelectedSections === 'all') {
setSelectedSections(0);
}
applyCharacterEditing({
keyPressed,
sectionIndex: 0
});
}
});
const handlePaste = (0, _useEventCallback.default)(event => {
if (readOnly || parsedSelectedSections !== 'all') {
event.preventDefault();
return;
}
const pastedValue = event.clipboardData.getData('text');
event.preventDefault();
setCharacterQuery(null);
updateValueFromValueStr(pastedValue);
});
const handleFocus = (0, _useEventCallback.default)(() => {
if (focused || disabled || !domGetters.isReady()) {
return;
}
const activeElement = (0, _utils.getActiveElement)(domGetters.getRoot());
setFocused(true);
const isFocusInsideASection = domGetters.getSectionIndexFromDOMElement(activeElement) != null;
if (!isFocusInsideASection) {
setSelectedSections(sectionOrder.startIndex);
}
});
const handleBlur = (0, _useEventCallback.default)(() => {
setTimeout(() => {
if (!domGetters.isReady()) {
return;
}
const activeElement = (0, _utils.getActiveElement)(domGetters.getRoot());
const shouldBlur = !domGetters.getRoot().contains(activeElement);
if (shouldBlur) {
setFocused(false);
setSelectedSections(null);
}
});
});
return {
// Event handlers
onKeyDown: handleKeyDown,
onBlur: handleBlur,
onFocus: handleFocus,
onClick: handleClick,
onPaste: handlePaste,
onInput: handleInput,
// Other
contentEditable: parsedSelectedSections === 'all',
tabIndex: parsedSelectedSections === 0 ? -1 : 0 // TODO: Try to set to undefined when there is a section selected.
};
}

View file

@ -0,0 +1,17 @@
import * as React from 'react';
import { UseFieldStateReturnValue } from "./useFieldState.js";
import { UseFieldInternalProps } from "./useField.types.js";
/**
* Generate the props to pass to the container element of each section of the field.
* It is not used by the non-accessible DOM structure (with an <input /> element for editing).
* It should be used in the MUI accessible DOM structure and the Base UI implementation.
* @param {UseFieldRootPropsParameters} parameters The parameters of the hook.
* @returns {UseFieldRootPropsReturnValue} The props to forward to the container element of each section of the field.
*/
export declare function useFieldSectionContainerProps(parameters: UseFieldSectionContainerPropsParameters): UseFieldSectionContainerPropsReturnValue;
interface UseFieldSectionContainerPropsParameters {
stateResponse: UseFieldStateReturnValue<any>;
internalPropsWithDefaults: UseFieldInternalProps<any, any, any>;
}
type UseFieldSectionContainerPropsReturnValue = (sectionIndex: number) => React.HTMLAttributes<HTMLSpanElement>;
export {};

View file

@ -0,0 +1,38 @@
"use strict";
var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard").default;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.useFieldSectionContainerProps = useFieldSectionContainerProps;
var React = _interopRequireWildcard(require("react"));
/**
* Generate the props to pass to the container element of each section of the field.
* It is not used by the non-accessible DOM structure (with an <input /> element for editing).
* It should be used in the MUI accessible DOM structure and the Base UI implementation.
* @param {UseFieldRootPropsParameters} parameters The parameters of the hook.
* @returns {UseFieldRootPropsReturnValue} The props to forward to the container element of each section of the field.
*/
function useFieldSectionContainerProps(parameters) {
const {
stateResponse: {
// Methods to update the states
setSelectedSections
},
internalPropsWithDefaults: {
disabled = false
}
} = parameters;
const createHandleClick = React.useCallback(sectionIndex => event => {
// The click event on the clear button would propagate to the input, trigger this handler and result in a wrong section selection.
// We avoid this by checking if the call to this function is actually intended, or a side effect.
if (disabled || event.isDefaultPrevented()) {
return;
}
setSelectedSections(sectionIndex);
}, [disabled, setSelectedSections]);
return React.useCallback(sectionIndex => ({
'data-sectionindex': sectionIndex,
onClick: createHandleClick(sectionIndex)
}), [createHandleClick]);
}

View file

@ -0,0 +1,23 @@
import { UseFieldStateReturnValue } from "./useFieldState.js";
import { FieldSection, PickerManager } from "../../../models/index.js";
import { UseFieldDOMGetters, UseFieldInternalProps } from "./useField.types.js";
import { UseFieldCharacterEditingReturnValue } from "./useFieldCharacterEditing.js";
import { PickersSectionElement } from "../../../PickersSectionList/index.js";
/**
* Generate the props to pass to the content element of each section of the field.
* It is not used by the non-accessible DOM structure (with an <input /> element for editing).
* It should be used in the MUI accessible DOM structure and the Base UI implementation.
* @param {UseFieldRootPropsParameters} parameters The parameters of the hook.
* @returns {UseFieldRootPropsReturnValue} The props to forward to the content element of each section of the field.
*/
export declare function useFieldSectionContentProps(parameters: UseFieldSectionContentPropsParameters): UseFieldSectionContentPropsReturnValue;
interface UseFieldSectionContentPropsParameters {
manager: PickerManager<any, any, any, any, any>;
stateResponse: UseFieldStateReturnValue<any>;
applyCharacterEditing: UseFieldCharacterEditingReturnValue;
internalPropsWithDefaults: UseFieldInternalProps<any, any, any>;
domGetters: UseFieldDOMGetters;
focused: boolean;
}
type UseFieldSectionContentPropsReturnValue = (section: FieldSection, sectionIndex: number) => PickersSectionElement['content'];
export {};

View file

@ -0,0 +1,236 @@
"use strict";
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault").default;
var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard").default;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.useFieldSectionContentProps = useFieldSectionContentProps;
var React = _interopRequireWildcard(require("react"));
var _useEventCallback = _interopRequireDefault(require("@mui/utils/useEventCallback"));
var _useId = _interopRequireDefault(require("@mui/utils/useId"));
var _hooks = require("../../../hooks");
var _syncSelectionToDOM = require("./syncSelectionToDOM");
/**
* Generate the props to pass to the content element of each section of the field.
* It is not used by the non-accessible DOM structure (with an <input /> element for editing).
* It should be used in the MUI accessible DOM structure and the Base UI implementation.
* @param {UseFieldRootPropsParameters} parameters The parameters of the hook.
* @returns {UseFieldRootPropsReturnValue} The props to forward to the content element of each section of the field.
*/
function useFieldSectionContentProps(parameters) {
const adapter = (0, _hooks.usePickerAdapter)();
const translations = (0, _hooks.usePickerTranslations)();
const id = (0, _useId.default)();
const {
focused,
domGetters,
stateResponse,
applyCharacterEditing,
manager: {
internal_fieldValueManager: fieldValueManager
},
stateResponse: {
// States and derived states
parsedSelectedSections,
sectionsValueBoundaries,
state,
value,
// Methods to update the states
clearActiveSection,
setCharacterQuery,
setSelectedSections,
updateSectionValue,
updateValueFromValueStr
},
internalPropsWithDefaults: {
disabled = false,
readOnly = false
}
} = parameters;
const isContainerEditable = parsedSelectedSections === 'all';
const isEditable = !isContainerEditable && !disabled && !readOnly;
/**
* If a section content has been updated with a value we don't want to keep,
* Then we need to imperatively revert it (we can't let React do it because the value did not change in his internal representation).
*/
const revertDOMSectionChange = (0, _useEventCallback.default)(sectionIndex => {
if (!domGetters.isReady()) {
return;
}
const section = state.sections[sectionIndex];
domGetters.getSectionContent(sectionIndex).innerHTML = section.value || section.placeholder;
(0, _syncSelectionToDOM.syncSelectionToDOM)({
focused,
domGetters,
stateResponse
});
});
const handleInput = (0, _useEventCallback.default)(event => {
if (!domGetters.isReady()) {
return;
}
const target = event.target;
const keyPressed = target.textContent ?? '';
const sectionIndex = domGetters.getSectionIndexFromDOMElement(target);
const section = state.sections[sectionIndex];
if (readOnly) {
revertDOMSectionChange(sectionIndex);
return;
}
if (keyPressed.length === 0) {
if (section.value === '') {
revertDOMSectionChange(sectionIndex);
return;
}
const inputType = event.nativeEvent.inputType;
if (inputType === 'insertParagraph' || inputType === 'insertLineBreak') {
revertDOMSectionChange(sectionIndex);
return;
}
revertDOMSectionChange(sectionIndex);
clearActiveSection();
return;
}
applyCharacterEditing({
keyPressed,
sectionIndex
});
// The DOM value needs to remain the one React is expecting.
revertDOMSectionChange(sectionIndex);
});
const handleMouseUp = (0, _useEventCallback.default)(event => {
// Without this, the browser will remove the selected when clicking inside an already-selected section.
event.preventDefault();
});
const handlePaste = (0, _useEventCallback.default)(event => {
// prevent default to avoid the input `onInput` handler being called
event.preventDefault();
if (readOnly || disabled || typeof parsedSelectedSections !== 'number') {
return;
}
const activeSection = state.sections[parsedSelectedSections];
const pastedValue = event.clipboardData.getData('text');
const lettersOnly = /^[a-zA-Z]+$/.test(pastedValue);
const digitsOnly = /^[0-9]+$/.test(pastedValue);
const digitsAndLetterOnly = /^(([a-zA-Z]+)|)([0-9]+)(([a-zA-Z]+)|)$/.test(pastedValue);
const isValidPastedValue = activeSection.contentType === 'letter' && lettersOnly || activeSection.contentType === 'digit' && digitsOnly || activeSection.contentType === 'digit-with-letter' && digitsAndLetterOnly;
if (isValidPastedValue) {
setCharacterQuery(null);
updateSectionValue({
section: activeSection,
newSectionValue: pastedValue,
shouldGoToNextSection: true
});
}
// If the pasted value corresponds to a single section, but not the expected type, we skip the modification
else if (!lettersOnly && !digitsOnly) {
setCharacterQuery(null);
updateValueFromValueStr(pastedValue);
}
});
const handleDragOver = (0, _useEventCallback.default)(event => {
event.preventDefault();
event.dataTransfer.dropEffect = 'none';
});
const createFocusHandler = React.useCallback(sectionIndex => () => {
if (disabled) {
return;
}
setSelectedSections(sectionIndex);
}, [disabled, setSelectedSections]);
return React.useCallback((section, sectionIndex) => {
const sectionBoundaries = sectionsValueBoundaries[section.type]({
currentDate: fieldValueManager.getDateFromSection(value, section),
contentType: section.contentType,
format: section.format
});
return {
// Event handlers
onInput: handleInput,
onPaste: handlePaste,
onMouseUp: handleMouseUp,
onDragOver: handleDragOver,
onFocus: createFocusHandler(sectionIndex),
// Aria attributes
'aria-labelledby': `${id}-${section.type}`,
'aria-readonly': readOnly,
'aria-valuenow': getSectionValueNow(section, adapter),
'aria-valuemin': sectionBoundaries.minimum,
'aria-valuemax': sectionBoundaries.maximum,
'aria-valuetext': section.value ? getSectionValueText(section, adapter) : translations.empty,
'aria-label': translations[section.type],
'aria-disabled': disabled,
// Other
tabIndex: isContainerEditable || sectionIndex > 0 ? -1 : 0,
contentEditable: !isContainerEditable && !disabled && !readOnly,
role: 'spinbutton',
id: `${id}-${section.type}`,
'data-range-position': section.dateName || undefined,
spellCheck: isEditable ? false : undefined,
autoCapitalize: isEditable ? 'off' : undefined,
autoCorrect: isEditable ? 'off' : undefined,
children: section.value || section.placeholder,
inputMode: section.contentType === 'letter' ? 'text' : 'numeric'
};
}, [sectionsValueBoundaries, id, isContainerEditable, disabled, readOnly, isEditable, translations, adapter, handleInput, handlePaste, handleMouseUp, handleDragOver, createFocusHandler, fieldValueManager, value]);
}
function getSectionValueText(section, adapter) {
if (!section.value) {
return undefined;
}
switch (section.type) {
case 'month':
{
if (section.contentType === 'digit') {
return adapter.format(adapter.setMonth(adapter.date(), Number(section.value) - 1), 'month');
}
const parsedDate = adapter.parse(section.value, section.format);
return parsedDate ? adapter.format(parsedDate, 'month') : undefined;
}
case 'day':
return section.contentType === 'digit' ? adapter.format(adapter.setDate(adapter.startOfYear(adapter.date()), Number(section.value)), 'dayOfMonthFull') : section.value;
case 'weekDay':
// TODO: improve by providing the label of the week day
return undefined;
default:
return undefined;
}
}
function getSectionValueNow(section, adapter) {
if (!section.value) {
return undefined;
}
switch (section.type) {
case 'weekDay':
{
if (section.contentType === 'letter') {
// TODO: improve by resolving the week day number from a letter week day
return undefined;
}
return Number(section.value);
}
case 'meridiem':
{
const parsedDate = adapter.parse(`01:00 ${section.value}`, `${adapter.formats.hours12h}:${adapter.formats.minutes} ${section.format}`);
if (parsedDate) {
return adapter.getHours(parsedDate) >= 12 ? 1 : 0;
}
return undefined;
}
case 'day':
return section.contentType === 'digit-with-letter' ? parseInt(section.value, 10) : Number(section.value);
case 'month':
{
if (section.contentType === 'digit') {
return Number(section.value);
}
const parsedDate = adapter.parse(section.value, section.format);
return parsedDate ? adapter.getMonth(parsedDate) + 1 : undefined;
}
default:
return section.contentType !== 'letter' ? Number(section.value) : undefined;
}
}

View file

@ -0,0 +1,44 @@
import { UseFieldInternalProps, UseFieldState, FieldParsedSelectedSections, FieldSectionsValueBoundaries, SectionOrdering, UseFieldForwardedProps, CharacterEditingQuery } from "./useField.types.js";
import { FieldSelectedSections, PickersTimezone, InferFieldSection, PickerManager } from "../../../models/index.js";
import { PickerValidValue } from "../../models/index.js";
export declare const useFieldState: <TValue extends PickerValidValue, TEnableAccessibleFieldDOMStructure extends boolean, TError, TValidationProps extends {}, TForwardedProps extends UseFieldForwardedProps<TEnableAccessibleFieldDOMStructure>>(parameters: UseFieldStateParameters<TValue, TEnableAccessibleFieldDOMStructure, TError, TValidationProps, TForwardedProps>) => UseFieldStateReturnValue<TValue>;
interface UseFieldStateParameters<TValue extends PickerValidValue, TEnableAccessibleFieldDOMStructure extends boolean, TError, TValidationProps extends {}, TForwardedProps extends UseFieldForwardedProps<TEnableAccessibleFieldDOMStructure>> {
manager: PickerManager<TValue, TEnableAccessibleFieldDOMStructure, TError, TValidationProps, any>;
internalPropsWithDefaults: UseFieldInternalProps<TValue, TEnableAccessibleFieldDOMStructure, TError> & TValidationProps;
forwardedProps: TForwardedProps;
}
export interface UpdateSectionValueParameters<TValue extends PickerValidValue> {
/**
* The section on which we want to apply the new value.
*/
section: InferFieldSection<TValue>;
/**
* Value to apply to the active section.
*/
newSectionValue: string;
/**
* If `true`, the focus will move to the next section.
*/
shouldGoToNextSection: boolean;
}
export interface UseFieldStateReturnValue<TValue extends PickerValidValue> {
activeSectionIndex: number | null;
areAllSectionsEmpty: boolean;
error: boolean;
localizedDigits: string[];
parsedSelectedSections: FieldParsedSelectedSections;
sectionOrder: SectionOrdering;
sectionsValueBoundaries: FieldSectionsValueBoundaries;
state: UseFieldState<TValue>;
timezone: PickersTimezone;
value: TValue;
clearValue: () => void;
clearActiveSection: () => void;
setCharacterQuery: (characterQuery: CharacterEditingQuery | null) => void;
setSelectedSections: (sections: FieldSelectedSections) => void;
setTempAndroidValueStr: (tempAndroidValueStr: string | null) => void;
updateSectionValue: (parameters: UpdateSectionValueParameters<TValue>) => void;
updateValueFromValueStr: (valueStr: string) => void;
getSectionsFromValue: (value: TValue, fallbackSections?: InferFieldSection<TValue>[] | null) => InferFieldSection<TValue>[];
}
export {};

View file

@ -0,0 +1,400 @@
"use strict";
'use client';
var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard").default;
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault").default;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.useFieldState = void 0;
var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends"));
var React = _interopRequireWildcard(require("react"));
var _useControlled = _interopRequireDefault(require("@mui/utils/useControlled"));
var _useTimeout = _interopRequireDefault(require("@mui/utils/useTimeout"));
var _useEventCallback = _interopRequireDefault(require("@mui/utils/useEventCallback"));
var _RtlProvider = require("@mui/system/RtlProvider");
var _hooks = require("../../../hooks");
var _useField = require("./useField.utils");
var _buildSectionsFromFormat = require("./buildSectionsFromFormat");
var _validation = require("../../../validation");
var _useControlledValue = require("../useControlledValue");
var _getDefaultReferenceDate = require("../../utils/getDefaultReferenceDate");
const QUERY_LIFE_DURATION_MS = 5000;
const useFieldState = parameters => {
const adapter = (0, _hooks.usePickerAdapter)();
const translations = (0, _hooks.usePickerTranslations)();
const isRtl = (0, _RtlProvider.useRtl)();
const {
manager: {
validator,
valueType,
internal_valueManager: valueManager,
internal_fieldValueManager: fieldValueManager
},
internalPropsWithDefaults,
internalPropsWithDefaults: {
value: valueProp,
defaultValue,
referenceDate: referenceDateProp,
onChange,
format,
formatDensity = 'dense',
selectedSections: selectedSectionsProp,
onSelectedSectionsChange,
shouldRespectLeadingZeros = false,
timezone: timezoneProp,
enableAccessibleFieldDOMStructure = true
},
forwardedProps: {
error: errorProp
}
} = parameters;
const {
value,
handleValueChange,
timezone
} = (0, _useControlledValue.useControlledValue)({
name: 'a field component',
timezone: timezoneProp,
value: valueProp,
defaultValue,
referenceDate: referenceDateProp,
onChange,
valueManager
});
const valueRef = React.useRef(value);
React.useEffect(() => {
valueRef.current = value;
}, [value]);
const {
hasValidationError
} = (0, _validation.useValidation)({
props: internalPropsWithDefaults,
validator,
timezone,
value,
onError: internalPropsWithDefaults.onError
});
const error = React.useMemo(() => {
// only override when `error` is undefined.
// in case of multi input fields, the `error` value is provided externally and will always be defined.
if (errorProp !== undefined) {
return errorProp;
}
return hasValidationError;
}, [hasValidationError, errorProp]);
const localizedDigits = React.useMemo(() => (0, _useField.getLocalizedDigits)(adapter), [adapter]);
const sectionsValueBoundaries = React.useMemo(() => (0, _useField.getSectionsBoundaries)(adapter, localizedDigits, timezone), [adapter, localizedDigits, timezone]);
const getSectionsFromValue = React.useCallback(valueToAnalyze => fieldValueManager.getSectionsFromValue(valueToAnalyze, date => (0, _buildSectionsFromFormat.buildSectionsFromFormat)({
adapter,
localeText: translations,
localizedDigits,
format,
date,
formatDensity,
shouldRespectLeadingZeros,
enableAccessibleFieldDOMStructure,
isRtl
})), [fieldValueManager, format, translations, localizedDigits, isRtl, shouldRespectLeadingZeros, adapter, formatDensity, enableAccessibleFieldDOMStructure]);
const [state, setState] = React.useState(() => {
const sections = getSectionsFromValue(value);
(0, _useField.validateSections)(sections, valueType);
const stateWithoutReferenceDate = {
sections,
lastExternalValue: value,
lastSectionsDependencies: {
format,
isRtl,
locale: adapter.locale
},
tempValueStrAndroid: null,
characterQuery: null
};
const granularity = (0, _getDefaultReferenceDate.getSectionTypeGranularity)(sections);
const referenceValue = valueManager.getInitialReferenceValue({
referenceDate: referenceDateProp,
value,
adapter,
props: internalPropsWithDefaults,
granularity,
timezone
});
return (0, _extends2.default)({}, stateWithoutReferenceDate, {
referenceValue
});
});
const [selectedSections, innerSetSelectedSections] = (0, _useControlled.default)({
controlled: selectedSectionsProp,
default: null,
name: 'useField',
state: 'selectedSections'
});
const setSelectedSections = newSelectedSections => {
innerSetSelectedSections(newSelectedSections);
onSelectedSectionsChange?.(newSelectedSections);
};
const parsedSelectedSections = React.useMemo(() => (0, _useField.parseSelectedSections)(selectedSections, state.sections), [selectedSections, state.sections]);
const activeSectionIndex = parsedSelectedSections === 'all' ? 0 : parsedSelectedSections;
const sectionOrder = React.useMemo(() => (0, _useField.getSectionOrder)(state.sections, isRtl && !enableAccessibleFieldDOMStructure), [state.sections, isRtl, enableAccessibleFieldDOMStructure]);
const areAllSectionsEmpty = React.useMemo(() => state.sections.every(section => section.value === ''), [state.sections]);
const publishValue = newValue => {
const context = {
validationError: validator({
adapter,
value: newValue,
timezone,
props: internalPropsWithDefaults
})
};
handleValueChange(newValue, context);
};
const setSectionValue = (sectionIndex, newSectionValue) => {
const newSections = [...state.sections];
newSections[sectionIndex] = (0, _extends2.default)({}, newSections[sectionIndex], {
value: newSectionValue,
modified: true
});
return newSections;
};
const sectionToUpdateOnNextInvalidDateRef = React.useRef(null);
const updateSectionValueOnNextInvalidDateTimeout = (0, _useTimeout.default)();
const setSectionUpdateToApplyOnNextInvalidDate = newSectionValue => {
if (activeSectionIndex == null) {
return;
}
sectionToUpdateOnNextInvalidDateRef.current = {
sectionIndex: activeSectionIndex,
value: newSectionValue
};
updateSectionValueOnNextInvalidDateTimeout.start(0, () => {
sectionToUpdateOnNextInvalidDateRef.current = null;
});
};
const clearValue = () => {
if (valueManager.areValuesEqual(adapter, value, valueManager.emptyValue)) {
setState(prevState => (0, _extends2.default)({}, prevState, {
sections: prevState.sections.map(section => (0, _extends2.default)({}, section, {
value: ''
})),
tempValueStrAndroid: null,
characterQuery: null
}));
} else {
setState(prevState => (0, _extends2.default)({}, prevState, {
characterQuery: null
}));
publishValue(valueManager.emptyValue);
}
};
const clearActiveSection = () => {
if (activeSectionIndex == null) {
return;
}
const activeSection = state.sections[activeSectionIndex];
if (activeSection.value === '') {
return;
}
setSectionUpdateToApplyOnNextInvalidDate('');
if (fieldValueManager.getDateFromSection(value, activeSection) === null) {
setState(prevState => (0, _extends2.default)({}, prevState, {
sections: setSectionValue(activeSectionIndex, ''),
tempValueStrAndroid: null,
characterQuery: null
}));
} else {
setState(prevState => (0, _extends2.default)({}, prevState, {
characterQuery: null
}));
publishValue(fieldValueManager.updateDateInValue(value, activeSection, null));
}
};
const updateValueFromValueStr = valueStr => {
const parseDateStr = (dateStr, referenceDate) => {
const date = adapter.parse(dateStr, format);
if (!adapter.isValid(date)) {
return null;
}
const sections = (0, _buildSectionsFromFormat.buildSectionsFromFormat)({
adapter,
localeText: translations,
localizedDigits,
format,
date,
formatDensity,
shouldRespectLeadingZeros,
enableAccessibleFieldDOMStructure,
isRtl
});
return (0, _useField.mergeDateIntoReferenceDate)(adapter, date, sections, referenceDate, false);
};
const newValue = fieldValueManager.parseValueStr(valueStr, state.referenceValue, parseDateStr);
publishValue(newValue);
};
const cleanActiveDateSectionsIfValueNullTimeout = (0, _useTimeout.default)();
const updateSectionValue = ({
section,
newSectionValue,
shouldGoToNextSection
}) => {
updateSectionValueOnNextInvalidDateTimeout.clear();
cleanActiveDateSectionsIfValueNullTimeout.clear();
const activeDate = fieldValueManager.getDateFromSection(value, section);
/**
* Decide which section should be focused
*/
if (shouldGoToNextSection && activeSectionIndex < state.sections.length - 1) {
setSelectedSections(activeSectionIndex + 1);
}
/**
* Try to build a valid date from the new section value
*/
const newSections = setSectionValue(activeSectionIndex, newSectionValue);
const newActiveDateSections = fieldValueManager.getDateSectionsFromValue(newSections, section);
const newActiveDate = (0, _useField.getDateFromDateSections)(adapter, newActiveDateSections, localizedDigits);
/**
* If the new date is valid,
* Then we merge the value of the modified sections into the reference date.
* This makes sure that we don't lose some information of the initial date (like the time on a date field).
*/
if (adapter.isValid(newActiveDate)) {
const mergedDate = (0, _useField.mergeDateIntoReferenceDate)(adapter, newActiveDate, newActiveDateSections, fieldValueManager.getDateFromSection(state.referenceValue, section), true);
if (activeDate == null) {
cleanActiveDateSectionsIfValueNullTimeout.start(0, () => {
if (valueRef.current === value) {
setState(prevState => (0, _extends2.default)({}, prevState, {
sections: fieldValueManager.clearDateSections(state.sections, section),
tempValueStrAndroid: null
}));
}
});
}
return publishValue(fieldValueManager.updateDateInValue(value, section, mergedDate));
}
/**
* If all the sections are filled but the date is invalid and the previous date is valid or null,
* Then we publish an invalid date.
*/
if (newActiveDateSections.every(sectionBis => sectionBis.value !== '') && (activeDate == null || adapter.isValid(activeDate))) {
setSectionUpdateToApplyOnNextInvalidDate(newSectionValue);
return publishValue(fieldValueManager.updateDateInValue(value, section, newActiveDate));
}
/**
* If the previous date is not null,
* Then we publish the date as `null`.
*/
if (activeDate != null) {
setSectionUpdateToApplyOnNextInvalidDate(newSectionValue);
return publishValue(fieldValueManager.updateDateInValue(value, section, null));
}
/**
* If the previous date is already null,
* Then we don't publish the date and we update the sections.
*/
return setState(prevState => (0, _extends2.default)({}, prevState, {
sections: newSections,
tempValueStrAndroid: null
}));
};
const setTempAndroidValueStr = tempValueStrAndroid => setState(prevState => (0, _extends2.default)({}, prevState, {
tempValueStrAndroid
}));
const setCharacterQuery = (0, _useEventCallback.default)(newCharacterQuery => {
setState(prevState => (0, _extends2.default)({}, prevState, {
characterQuery: newCharacterQuery
}));
});
// If `prop.value` changes, we update the state to reflect the new value
if (value !== state.lastExternalValue) {
let sections;
if (sectionToUpdateOnNextInvalidDateRef.current != null && !adapter.isValid(fieldValueManager.getDateFromSection(value, state.sections[sectionToUpdateOnNextInvalidDateRef.current.sectionIndex]))) {
sections = setSectionValue(sectionToUpdateOnNextInvalidDateRef.current.sectionIndex, sectionToUpdateOnNextInvalidDateRef.current.value);
} else {
sections = getSectionsFromValue(value);
}
setState(prevState => (0, _extends2.default)({}, prevState, {
lastExternalValue: value,
sections,
sectionsDependencies: {
format,
isRtl,
locale: adapter.locale
},
referenceValue: fieldValueManager.updateReferenceValue(adapter, value, prevState.referenceValue),
tempValueStrAndroid: null
}));
}
if (isRtl !== state.lastSectionsDependencies.isRtl || format !== state.lastSectionsDependencies.format || adapter.locale !== state.lastSectionsDependencies.locale) {
const sections = getSectionsFromValue(value);
(0, _useField.validateSections)(sections, valueType);
setState(prevState => (0, _extends2.default)({}, prevState, {
lastSectionsDependencies: {
format,
isRtl,
locale: adapter.locale
},
sections,
tempValueStrAndroid: null,
characterQuery: null
}));
}
if (state.characterQuery != null && !error && activeSectionIndex == null) {
setCharacterQuery(null);
}
if (state.characterQuery != null && state.sections[state.characterQuery.sectionIndex]?.type !== state.characterQuery.sectionType) {
setCharacterQuery(null);
}
React.useEffect(() => {
if (sectionToUpdateOnNextInvalidDateRef.current != null) {
sectionToUpdateOnNextInvalidDateRef.current = null;
}
});
const cleanCharacterQueryTimeout = (0, _useTimeout.default)();
React.useEffect(() => {
if (state.characterQuery != null) {
cleanCharacterQueryTimeout.start(QUERY_LIFE_DURATION_MS, () => setCharacterQuery(null));
}
return () => {};
}, [state.characterQuery, setCharacterQuery, cleanCharacterQueryTimeout]);
// If `tempValueStrAndroid` is still defined for some section when running `useEffect`,
// Then `onChange` has only been called once, which means the user pressed `Backspace` to reset the section.
// This causes a small flickering on Android,
// But we can't use `useEnhancedEffect` which is always called before the second `onChange` call and then would cause false positives.
React.useEffect(() => {
if (state.tempValueStrAndroid != null && activeSectionIndex != null) {
clearActiveSection();
}
}, [state.sections]); // eslint-disable-line react-hooks/exhaustive-deps
return {
// States and derived states
activeSectionIndex,
areAllSectionsEmpty,
error,
localizedDigits,
parsedSelectedSections,
sectionOrder,
sectionsValueBoundaries,
state,
timezone,
value,
// Methods to update the states
clearValue,
clearActiveSection,
setCharacterQuery,
setSelectedSections,
setTempAndroidValueStr,
updateSectionValue,
updateValueFromValueStr,
// Utilities methods
getSectionsFromValue
};
};
exports.useFieldState = useFieldState;

View file

@ -0,0 +1,26 @@
import { UseFieldParameters, UseFieldProps, UseFieldReturnValue } from "./useField.types.js";
import { InferFieldSection } from "../../../models/index.js";
import { PickerValidValue } from "../../models/index.js";
export declare const addPositionPropertiesToSections: <TValue extends PickerValidValue>(sections: InferFieldSection<TValue>[], localizedDigits: string[], isRtl: boolean) => FieldSectionWithPositions<TValue>[];
export declare const useFieldV6TextField: <TValue extends PickerValidValue, TError, TValidationProps extends {}, TProps extends UseFieldProps<false>>(parameters: UseFieldParameters<TValue, false, TError, TValidationProps, TProps>) => UseFieldReturnValue<false, TProps>;
type FieldSectionWithPositions<TValue extends PickerValidValue> = InferFieldSection<TValue> & {
/**
* Start index of the section in the format
*/
start: number;
/**
* End index of the section in the format
*/
end: number;
/**
* Start index of the section value in the input.
* Takes into account invisible unicode characters such as \u2069 but does not include them
*/
startInInput: number;
/**
* End index of the section value in the input.
* Takes into account invisible unicode characters such as \u2069 but does not include them
*/
endInInput: number;
};
export {};

View file

@ -0,0 +1,420 @@
"use strict";
'use client';
var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard").default;
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault").default;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.useFieldV6TextField = exports.addPositionPropertiesToSections = void 0;
var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends"));
var React = _interopRequireWildcard(require("react"));
var _RtlProvider = require("@mui/system/RtlProvider");
var _useEnhancedEffect = _interopRequireDefault(require("@mui/utils/useEnhancedEffect"));
var _useEventCallback = _interopRequireDefault(require("@mui/utils/useEventCallback"));
var _useTimeout = _interopRequireDefault(require("@mui/utils/useTimeout"));
var _useForkRef = _interopRequireDefault(require("@mui/utils/useForkRef"));
var _hooks = require("../../../hooks");
var _utils = require("../../utils/utils");
var _useField = require("./useField.utils");
var _useFieldCharacterEditing = require("./useFieldCharacterEditing");
var _useFieldRootHandleKeyDown = require("./useFieldRootHandleKeyDown");
var _useFieldState = require("./useFieldState");
var _useFieldInternalPropsWithDefaults = require("./useFieldInternalPropsWithDefaults");
const cleanString = dirtyString => dirtyString.replace(/[\u2066\u2067\u2068\u2069]/g, '');
const addPositionPropertiesToSections = (sections, localizedDigits, isRtl) => {
let position = 0;
let positionInInput = isRtl ? 1 : 0;
const newSections = [];
for (let i = 0; i < sections.length; i += 1) {
const section = sections[i];
const renderedValue = (0, _useField.getSectionVisibleValue)(section, isRtl ? 'input-rtl' : 'input-ltr', localizedDigits);
const sectionStr = `${section.startSeparator}${renderedValue}${section.endSeparator}`;
const sectionLength = cleanString(sectionStr).length;
const sectionLengthInInput = sectionStr.length;
// The ...InInput values consider the unicode characters but do include them in their indexes
const cleanedValue = cleanString(renderedValue);
const startInInput = positionInInput + (cleanedValue === '' ? 0 : renderedValue.indexOf(cleanedValue[0])) + section.startSeparator.length;
const endInInput = startInInput + cleanedValue.length;
newSections.push((0, _extends2.default)({}, section, {
start: position,
end: position + sectionLength,
startInInput,
endInInput
}));
position += sectionLength;
// Move position to the end of string associated to the current section
positionInInput += sectionLengthInInput;
}
return newSections;
};
exports.addPositionPropertiesToSections = addPositionPropertiesToSections;
const useFieldV6TextField = parameters => {
const isRtl = (0, _RtlProvider.useRtl)();
const focusTimeout = (0, _useTimeout.default)();
const selectionSyncTimeout = (0, _useTimeout.default)();
const {
props,
manager,
skipContextFieldRefAssignment,
manager: {
valueType,
internal_valueManager: valueManager,
internal_fieldValueManager: fieldValueManager,
internal_useOpenPickerButtonAriaLabel: useOpenPickerButtonAriaLabel
}
} = parameters;
const {
internalProps,
forwardedProps
} = (0, _hooks.useSplitFieldProps)(props, valueType);
const internalPropsWithDefaults = (0, _useFieldInternalPropsWithDefaults.useFieldInternalPropsWithDefaults)({
manager,
internalProps,
skipContextFieldRefAssignment
});
const {
onFocus,
onClick,
onPaste,
onBlur,
onKeyDown,
onClear,
clearable,
inputRef: inputRefProp,
placeholder: inPlaceholder
} = forwardedProps;
const {
readOnly = false,
disabled = false,
autoFocus = false,
focused,
unstableFieldRef
} = internalPropsWithDefaults;
const inputRef = React.useRef(null);
const handleRef = (0, _useForkRef.default)(inputRefProp, inputRef);
const stateResponse = (0, _useFieldState.useFieldState)({
manager,
internalPropsWithDefaults,
forwardedProps
});
const {
// States and derived states
activeSectionIndex,
areAllSectionsEmpty,
error,
localizedDigits,
parsedSelectedSections,
sectionOrder,
state,
value,
// Methods to update the states
clearValue,
clearActiveSection,
setCharacterQuery,
setSelectedSections,
setTempAndroidValueStr,
updateSectionValue,
updateValueFromValueStr,
// Utilities methods
getSectionsFromValue
} = stateResponse;
const applyCharacterEditing = (0, _useFieldCharacterEditing.useFieldCharacterEditing)({
stateResponse
});
const openPickerAriaLabel = useOpenPickerButtonAriaLabel(value);
const sections = React.useMemo(() => addPositionPropertiesToSections(state.sections, localizedDigits, isRtl), [state.sections, localizedDigits, isRtl]);
function syncSelectionFromDOM() {
const browserStartIndex = inputRef.current.selectionStart ?? 0;
let nextSectionIndex;
if (browserStartIndex <= sections[0].startInInput) {
// Special case if browser index is in invisible characters at the beginning
nextSectionIndex = 1;
} else if (browserStartIndex >= sections[sections.length - 1].endInInput) {
// If the click is after the last character of the input, then we want to select the 1st section.
nextSectionIndex = 1;
} else {
nextSectionIndex = sections.findIndex(section => section.startInInput - section.startSeparator.length > browserStartIndex);
}
const sectionIndex = nextSectionIndex === -1 ? sections.length - 1 : nextSectionIndex - 1;
setSelectedSections(sectionIndex);
}
function focusField(newSelectedSection = 0) {
if ((0, _utils.getActiveElement)(inputRef.current) === inputRef.current) {
return;
}
inputRef.current?.focus();
setSelectedSections(newSelectedSection);
}
const handleInputFocus = (0, _useEventCallback.default)(event => {
onFocus?.(event);
// The ref is guaranteed to be resolved at this point.
const input = inputRef.current;
focusTimeout.start(0, () => {
// The ref changed, the component got remounted, the focus event is no longer relevant.
if (!input || input !== inputRef.current) {
return;
}
if (activeSectionIndex != null) {
return;
}
if (
// avoid selecting all sections when focusing empty field without value
input.value.length && Number(input.selectionEnd) - Number(input.selectionStart) === input.value.length) {
setSelectedSections('all');
} else {
syncSelectionFromDOM();
}
});
});
const handleInputClick = (0, _useEventCallback.default)((event, ...args) => {
// The click event on the clear button would propagate to the input, trigger this handler and result in a wrong section selection.
// We avoid this by checking if the call of `handleInputClick` is actually intended, or a side effect.
if (event.isDefaultPrevented()) {
return;
}
onClick?.(event, ...args);
syncSelectionFromDOM();
});
const handleInputPaste = (0, _useEventCallback.default)(event => {
onPaste?.(event);
// prevent default to avoid the input `onChange` handler being called
event.preventDefault();
if (readOnly || disabled) {
return;
}
const pastedValue = event.clipboardData.getData('text');
if (typeof parsedSelectedSections === 'number') {
const activeSection = state.sections[parsedSelectedSections];
const lettersOnly = /^[a-zA-Z]+$/.test(pastedValue);
const digitsOnly = /^[0-9]+$/.test(pastedValue);
const digitsAndLetterOnly = /^(([a-zA-Z]+)|)([0-9]+)(([a-zA-Z]+)|)$/.test(pastedValue);
const isValidPastedValue = activeSection.contentType === 'letter' && lettersOnly || activeSection.contentType === 'digit' && digitsOnly || activeSection.contentType === 'digit-with-letter' && digitsAndLetterOnly;
if (isValidPastedValue) {
setCharacterQuery(null);
updateSectionValue({
section: activeSection,
newSectionValue: pastedValue,
shouldGoToNextSection: true
});
return;
}
if (lettersOnly || digitsOnly) {
// The pasted value corresponds to a single section, but not the expected type,
// skip the modification
return;
}
}
setCharacterQuery(null);
updateValueFromValueStr(pastedValue);
});
const handleContainerBlur = (0, _useEventCallback.default)(event => {
onBlur?.(event);
setSelectedSections(null);
});
const handleInputChange = (0, _useEventCallback.default)(event => {
if (readOnly) {
return;
}
const targetValue = event.target.value;
if (targetValue === '') {
clearValue();
return;
}
const eventData = event.nativeEvent.data;
// Calling `.fill(04/11/2022)` in playwright will trigger a change event with the requested content to insert in `event.nativeEvent.data`
// usual changes have only the currently typed character in the `event.nativeEvent.data`
const shouldUseEventData = eventData && eventData.length > 1;
const valueStr = shouldUseEventData ? eventData : targetValue;
const cleanValueStr = cleanString(valueStr);
if (parsedSelectedSections === 'all') {
setSelectedSections(activeSectionIndex);
}
// If no section is selected or eventData should be used, we just try to parse the new value
// This line is mostly triggered by imperative code / application tests.
if (activeSectionIndex == null || shouldUseEventData) {
updateValueFromValueStr(shouldUseEventData ? eventData : cleanValueStr);
return;
}
let keyPressed;
if (parsedSelectedSections === 'all' && cleanValueStr.length === 1) {
keyPressed = cleanValueStr;
} else {
const prevValueStr = cleanString(fieldValueManager.getV6InputValueFromSections(sections, localizedDigits, isRtl));
let startOfDiffIndex = -1;
let endOfDiffIndex = -1;
for (let i = 0; i < prevValueStr.length; i += 1) {
if (startOfDiffIndex === -1 && prevValueStr[i] !== cleanValueStr[i]) {
startOfDiffIndex = i;
}
if (endOfDiffIndex === -1 && prevValueStr[prevValueStr.length - i - 1] !== cleanValueStr[cleanValueStr.length - i - 1]) {
endOfDiffIndex = i;
}
}
const activeSection = sections[activeSectionIndex];
const hasDiffOutsideOfActiveSection = startOfDiffIndex < activeSection.start || prevValueStr.length - endOfDiffIndex - 1 > activeSection.end;
if (hasDiffOutsideOfActiveSection) {
// TODO: Support if the new date is valid
return;
}
// The active section being selected, the browser has replaced its value with the key pressed by the user.
const activeSectionEndRelativeToNewValue = cleanValueStr.length - prevValueStr.length + activeSection.end - cleanString(activeSection.endSeparator || '').length;
keyPressed = cleanValueStr.slice(activeSection.start + cleanString(activeSection.startSeparator || '').length, activeSectionEndRelativeToNewValue);
}
if (keyPressed.length === 0) {
if ((0, _useField.isAndroid)()) {
setTempAndroidValueStr(valueStr);
}
clearActiveSection();
return;
}
applyCharacterEditing({
keyPressed,
sectionIndex: activeSectionIndex
});
});
const handleClear = (0, _useEventCallback.default)((event, ...args) => {
event.preventDefault();
onClear?.(event, ...args);
clearValue();
if (!isFieldFocused(inputRef)) {
// setSelectedSections is called internally
focusField(0);
} else {
setSelectedSections(sectionOrder.startIndex);
}
});
const handleContainerKeyDown = (0, _useFieldRootHandleKeyDown.useFieldRootHandleKeyDown)({
manager,
internalPropsWithDefaults,
stateResponse
});
const wrappedHandleContainerKeyDown = (0, _useEventCallback.default)(event => {
onKeyDown?.(event);
handleContainerKeyDown(event);
});
const placeholder = React.useMemo(() => {
if (inPlaceholder !== undefined) {
return inPlaceholder;
}
return fieldValueManager.getV6InputValueFromSections(getSectionsFromValue(valueManager.emptyValue), localizedDigits, isRtl);
}, [inPlaceholder, fieldValueManager, getSectionsFromValue, valueManager.emptyValue, localizedDigits, isRtl]);
const valueStr = React.useMemo(() => state.tempValueStrAndroid ?? fieldValueManager.getV6InputValueFromSections(state.sections, localizedDigits, isRtl), [state.sections, fieldValueManager, state.tempValueStrAndroid, localizedDigits, isRtl]);
React.useEffect(() => {
// Select all the sections when focused on mount (`autoFocus = true` on the input)
if (inputRef.current && inputRef.current === (0, _utils.getActiveElement)(inputRef.current)) {
setSelectedSections('all');
}
}, []); // eslint-disable-line react-hooks/exhaustive-deps
(0, _useEnhancedEffect.default)(() => {
function syncSelectionToDOM() {
if (!inputRef.current) {
return;
}
if (parsedSelectedSections == null) {
if (inputRef.current.scrollLeft) {
// Ensure that input content is not marked as selected.
// setting selection range to 0 causes issues in Safari.
// https://bugs.webkit.org/show_bug.cgi?id=224425
inputRef.current.scrollLeft = 0;
}
return;
}
// On multi input range pickers we want to update selection range only for the active input
// This helps to avoid the focus jumping on Safari https://github.com/mui/mui-x/issues/9003
// because WebKit implements the `setSelectionRange` based on the spec: https://bugs.webkit.org/show_bug.cgi?id=224425
if (inputRef.current !== (0, _utils.getActiveElement)(inputRef.current)) {
return;
}
// Fix scroll jumping on iOS browser: https://github.com/mui/mui-x/issues/8321
const currentScrollTop = inputRef.current.scrollTop;
if (parsedSelectedSections === 'all') {
inputRef.current.select();
} else {
const selectedSection = sections[parsedSelectedSections];
const selectionStart = selectedSection.type === 'empty' ? selectedSection.startInInput - selectedSection.startSeparator.length : selectedSection.startInInput;
const selectionEnd = selectedSection.type === 'empty' ? selectedSection.endInInput + selectedSection.endSeparator.length : selectedSection.endInInput;
if (selectionStart !== inputRef.current.selectionStart || selectionEnd !== inputRef.current.selectionEnd) {
if (inputRef.current === (0, _utils.getActiveElement)(inputRef.current)) {
inputRef.current.setSelectionRange(selectionStart, selectionEnd);
}
}
selectionSyncTimeout.start(0, () => {
// handle case when the selection is not updated correctly
// could happen on Android
if (inputRef.current && inputRef.current === (0, _utils.getActiveElement)(inputRef.current) &&
// The section might loose all selection, where `selectionStart === selectionEnd`
// https://github.com/mui/mui-x/pull/13652
inputRef.current.selectionStart === inputRef.current.selectionEnd && (inputRef.current.selectionStart !== selectionStart || inputRef.current.selectionEnd !== selectionEnd)) {
syncSelectionToDOM();
}
});
}
// Even reading this variable seems to do the trick, but also setting it just to make use of it
inputRef.current.scrollTop = currentScrollTop;
}
syncSelectionToDOM();
});
const inputMode = React.useMemo(() => {
if (activeSectionIndex == null) {
return 'text';
}
if (state.sections[activeSectionIndex].contentType === 'letter') {
return 'text';
}
return 'numeric';
}, [activeSectionIndex, state.sections]);
const inputHasFocus = inputRef.current && inputRef.current === (0, _utils.getActiveElement)(inputRef.current);
const shouldShowPlaceholder = !inputHasFocus && areAllSectionsEmpty;
React.useImperativeHandle(unstableFieldRef, () => ({
getSections: () => state.sections,
getActiveSectionIndex: () => {
const browserStartIndex = inputRef.current.selectionStart ?? 0;
const browserEndIndex = inputRef.current.selectionEnd ?? 0;
if (browserStartIndex === 0 && browserEndIndex === 0) {
return null;
}
const nextSectionIndex = browserStartIndex <= sections[0].startInInput ? 1 // Special case if browser index is in invisible characters at the beginning.
: sections.findIndex(section => section.startInInput - section.startSeparator.length > browserStartIndex);
return nextSectionIndex === -1 ? sections.length - 1 : nextSectionIndex - 1;
},
setSelectedSections: newSelectedSections => setSelectedSections(newSelectedSections),
focusField,
isFieldFocused: () => isFieldFocused(inputRef)
}));
return (0, _extends2.default)({}, forwardedProps, {
error,
clearable: Boolean(clearable && !areAllSectionsEmpty && !readOnly && !disabled),
onBlur: handleContainerBlur,
onClick: handleInputClick,
onFocus: handleInputFocus,
onPaste: handleInputPaste,
onKeyDown: wrappedHandleContainerKeyDown,
onClear: handleClear,
inputRef: handleRef,
// Additional
enableAccessibleFieldDOMStructure: false,
placeholder,
inputMode,
autoComplete: 'off',
value: shouldShowPlaceholder ? '' : valueStr,
onChange: handleInputChange,
focused,
disabled,
readOnly,
autoFocus,
openPickerAriaLabel
});
};
exports.useFieldV6TextField = useFieldV6TextField;
function isFieldFocused(inputRef) {
return inputRef.current === (0, _utils.getActiveElement)(inputRef.current);
}

View file

@ -0,0 +1,3 @@
import { UseFieldParameters, UseFieldProps, UseFieldReturnValue } from "./useField.types.js";
import { PickerValidValue } from "../../models/index.js";
export declare const useFieldV7TextField: <TValue extends PickerValidValue, TError, TValidationProps extends {}, TProps extends UseFieldProps<true>>(parameters: UseFieldParameters<TValue, true, TError, TValidationProps, TProps>) => UseFieldReturnValue<true, TProps>;

View file

@ -0,0 +1,263 @@
"use strict";
'use client';
var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard").default;
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault").default;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.useFieldV7TextField = void 0;
var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends"));
var React = _interopRequireWildcard(require("react"));
var _useForkRef = _interopRequireDefault(require("@mui/utils/useForkRef"));
var _useEventCallback = _interopRequireDefault(require("@mui/utils/useEventCallback"));
var _useEnhancedEffect = _interopRequireDefault(require("@mui/utils/useEnhancedEffect"));
var _useField = require("./useField.utils");
var _utils = require("../../utils/utils");
var _hooks = require("../../../hooks");
var _useFieldCharacterEditing = require("./useFieldCharacterEditing");
var _useFieldState = require("./useFieldState");
var _useFieldInternalPropsWithDefaults = require("./useFieldInternalPropsWithDefaults");
var _syncSelectionToDOM = require("./syncSelectionToDOM");
var _useFieldRootProps = require("./useFieldRootProps");
var _useFieldHiddenInputProps = require("./useFieldHiddenInputProps");
var _useFieldSectionContainerProps = require("./useFieldSectionContainerProps");
var _useFieldSectionContentProps = require("./useFieldSectionContentProps");
const useFieldV7TextField = parameters => {
const {
props,
manager,
skipContextFieldRefAssignment,
manager: {
valueType,
internal_useOpenPickerButtonAriaLabel: useOpenPickerButtonAriaLabel
}
} = parameters;
const {
internalProps,
forwardedProps
} = (0, _hooks.useSplitFieldProps)(props, valueType);
const internalPropsWithDefaults = (0, _useFieldInternalPropsWithDefaults.useFieldInternalPropsWithDefaults)({
manager,
internalProps,
skipContextFieldRefAssignment
});
const {
sectionListRef: sectionListRefProp,
onBlur,
onClick,
onFocus,
onInput,
onPaste,
onKeyDown,
onClear,
clearable
} = forwardedProps;
const {
disabled = false,
readOnly = false,
autoFocus = false,
focused: focusedProp,
unstableFieldRef
} = internalPropsWithDefaults;
const sectionListRef = React.useRef(null);
const handleSectionListRef = (0, _useForkRef.default)(sectionListRefProp, sectionListRef);
const domGetters = React.useMemo(() => ({
isReady: () => sectionListRef.current != null,
getRoot: () => sectionListRef.current.getRoot(),
getSectionContainer: sectionIndex => sectionListRef.current.getSectionContainer(sectionIndex),
getSectionContent: sectionIndex => sectionListRef.current.getSectionContent(sectionIndex),
getSectionIndexFromDOMElement: element => sectionListRef.current.getSectionIndexFromDOMElement(element)
}), [sectionListRef]);
const stateResponse = (0, _useFieldState.useFieldState)({
manager,
internalPropsWithDefaults,
forwardedProps
});
const {
// States and derived states
areAllSectionsEmpty,
error,
parsedSelectedSections,
sectionOrder,
state,
value,
// Methods to update the states
clearValue,
setSelectedSections
} = stateResponse;
const applyCharacterEditing = (0, _useFieldCharacterEditing.useFieldCharacterEditing)({
stateResponse
});
const openPickerAriaLabel = useOpenPickerButtonAriaLabel(value);
const [focused, setFocused] = React.useState(false);
function focusField(newSelectedSections = 0) {
if (disabled || !sectionListRef.current ||
// if the field is already focused, we don't need to focus it again
getActiveSectionIndex(sectionListRef) != null) {
return;
}
const newParsedSelectedSections = (0, _useField.parseSelectedSections)(newSelectedSections, state.sections);
setFocused(true);
sectionListRef.current.getSectionContent(newParsedSelectedSections).focus();
}
const rootProps = (0, _useFieldRootProps.useFieldRootProps)({
manager,
internalPropsWithDefaults,
stateResponse,
applyCharacterEditing,
focused,
setFocused,
domGetters
});
const hiddenInputProps = (0, _useFieldHiddenInputProps.useFieldHiddenInputProps)({
manager,
stateResponse
});
const createSectionContainerProps = (0, _useFieldSectionContainerProps.useFieldSectionContainerProps)({
stateResponse,
internalPropsWithDefaults
});
const createSectionContentProps = (0, _useFieldSectionContentProps.useFieldSectionContentProps)({
manager,
stateResponse,
applyCharacterEditing,
internalPropsWithDefaults,
domGetters,
focused
});
const handleRootKeyDown = (0, _useEventCallback.default)(event => {
onKeyDown?.(event);
rootProps.onKeyDown(event);
});
const handleRootBlur = (0, _useEventCallback.default)(event => {
onBlur?.(event);
rootProps.onBlur(event);
});
const handleRootFocus = (0, _useEventCallback.default)(event => {
onFocus?.(event);
rootProps.onFocus(event);
});
const handleRootClick = (0, _useEventCallback.default)(event => {
// The click event on the clear or open button would propagate to the input, trigger this handler and result in an inadvertent section selection.
// We avoid this by checking if the call of `handleInputClick` is actually intended, or a propagated call, which should be skipped.
if (event.isDefaultPrevented()) {
return;
}
onClick?.(event);
rootProps.onClick(event);
});
const handleRootPaste = (0, _useEventCallback.default)(event => {
onPaste?.(event);
rootProps.onPaste(event);
});
const handleRootInput = (0, _useEventCallback.default)(event => {
onInput?.(event);
rootProps.onInput(event);
});
const handleClear = (0, _useEventCallback.default)((event, ...args) => {
event.preventDefault();
onClear?.(event, ...args);
clearValue();
if (!isFieldFocused(sectionListRef)) {
// setSelectedSections is called internally
focusField(0);
} else {
setSelectedSections(sectionOrder.startIndex);
}
});
const elements = React.useMemo(() => {
return state.sections.map((section, sectionIndex) => {
const content = createSectionContentProps(section, sectionIndex);
return {
container: createSectionContainerProps(sectionIndex),
content: createSectionContentProps(section, sectionIndex),
before: {
children: section.startSeparator
},
after: {
children: section.endSeparator,
'data-range-position': section.isEndFormatSeparator ? content['data-range-position'] : undefined
}
};
});
}, [state.sections, createSectionContainerProps, createSectionContentProps]);
React.useEffect(() => {
if (sectionListRef.current == null) {
throw new Error(['MUI X: The `sectionListRef` prop has not been initialized by `PickersSectionList`', 'You probably tried to pass a component to the `textField` slot that contains an `<input />` element instead of a `PickersSectionList`.', '', 'If you want to keep using an `<input />` HTML element for the editing, please add the `enableAccessibleFieldDOMStructure={false}` prop to your Picker or Field component:', '', '<DatePicker enableAccessibleFieldDOMStructure={false} slots={{ textField: MyCustomTextField }} />', '', 'Learn more about the field accessible DOM structure on the MUI documentation: https://mui.com/x/react-date-pickers/fields/#fields-to-edit-a-single-element'].join('\n'));
}
if (autoFocus && !disabled && sectionListRef.current) {
sectionListRef.current.getSectionContent(sectionOrder.startIndex).focus();
}
}, []); // eslint-disable-line react-hooks/exhaustive-deps
(0, _useEnhancedEffect.default)(() => {
if (!focused || !sectionListRef.current) {
return;
}
if (parsedSelectedSections === 'all') {
sectionListRef.current.getRoot().focus();
} else if (typeof parsedSelectedSections === 'number') {
const domElement = sectionListRef.current.getSectionContent(parsedSelectedSections);
if (domElement) {
domElement.focus();
}
}
}, [parsedSelectedSections, focused]);
(0, _useEnhancedEffect.default)(() => {
(0, _syncSelectionToDOM.syncSelectionToDOM)({
focused,
domGetters,
stateResponse
});
});
React.useImperativeHandle(unstableFieldRef, () => ({
getSections: () => state.sections,
getActiveSectionIndex: () => getActiveSectionIndex(sectionListRef),
setSelectedSections: newSelectedSections => {
if (disabled || !sectionListRef.current) {
return;
}
const newParsedSelectedSections = (0, _useField.parseSelectedSections)(newSelectedSections, state.sections);
const newActiveSectionIndex = newParsedSelectedSections === 'all' ? 0 : newParsedSelectedSections;
setFocused(newActiveSectionIndex !== null);
setSelectedSections(newSelectedSections);
},
focusField,
isFieldFocused: () => isFieldFocused(sectionListRef)
}));
return (0, _extends2.default)({}, forwardedProps, rootProps, {
onBlur: handleRootBlur,
onClick: handleRootClick,
onFocus: handleRootFocus,
onInput: handleRootInput,
onPaste: handleRootPaste,
onKeyDown: handleRootKeyDown,
onClear: handleClear
}, hiddenInputProps, {
error,
clearable: Boolean(clearable && !areAllSectionsEmpty && !readOnly && !disabled),
focused: focusedProp ?? focused,
sectionListRef: handleSectionListRef,
// Additional
enableAccessibleFieldDOMStructure: true,
elements,
areAllSectionsEmpty,
disabled,
readOnly,
autoFocus,
openPickerAriaLabel
});
};
exports.useFieldV7TextField = useFieldV7TextField;
function getActiveSectionIndex(sectionListRef) {
const activeElement = (0, _utils.getActiveElement)(sectionListRef.current?.getRoot());
if (!activeElement || !sectionListRef.current || !sectionListRef.current.getRoot().contains(activeElement)) {
return null;
}
return sectionListRef.current.getSectionIndexFromDOMElement(activeElement);
}
function isFieldFocused(sectionListRef) {
const activeElement = (0, _utils.getActiveElement)(sectionListRef.current?.getRoot());
return !!sectionListRef.current && sectionListRef.current.getRoot().contains(activeElement);
}

View file

@ -0,0 +1,6 @@
import { FieldOwnerState } from "../../models/index.js";
import { FormProps } from "../models/index.js";
export declare function useFieldOwnerState(parameters: UseFieldOwnerStateParameters): FieldOwnerState;
export interface UseFieldOwnerStateParameters extends FormProps {
required?: boolean;
}

View file

@ -0,0 +1,24 @@
"use strict";
var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard").default;
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault").default;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.useFieldOwnerState = useFieldOwnerState;
var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends"));
var React = _interopRequireWildcard(require("react"));
var _RtlProvider = require("@mui/system/RtlProvider");
var _usePickerPrivateContext = require("./usePickerPrivateContext");
function useFieldOwnerState(parameters) {
const {
ownerState: pickerOwnerState
} = (0, _usePickerPrivateContext.usePickerPrivateContext)();
const isRtl = (0, _RtlProvider.useRtl)();
return React.useMemo(() => (0, _extends2.default)({}, pickerOwnerState, {
isFieldDisabled: parameters.disabled ?? false,
isFieldReadOnly: parameters.readOnly ?? false,
isFieldRequired: parameters.required ?? false,
fieldDirection: isRtl ? 'rtl' : 'ltr'
}), [pickerOwnerState, parameters.disabled, parameters.readOnly, parameters.required, isRtl]);
}

View file

@ -0,0 +1,2 @@
export { useMobilePicker } from "./useMobilePicker.js";
export type { UseMobilePickerSlots, UseMobilePickerSlotProps, ExportedUseMobilePickerSlotProps, MobileOnlyPickerProps } from "./useMobilePicker.types.js";

View file

@ -0,0 +1,12 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
Object.defineProperty(exports, "useMobilePicker", {
enumerable: true,
get: function () {
return _useMobilePicker.useMobilePicker;
}
});
var _useMobilePicker = require("./useMobilePicker");

View file

@ -0,0 +1,16 @@
import * as React from 'react';
import { UseMobilePickerParams, UseMobilePickerProps } from "./useMobilePicker.types.js";
import { DateOrTimeViewWithMeridiem } from "../../models/index.js";
/**
* Hook managing all the single-date mobile pickers:
* - MobileDatePicker
* - MobileDateTimePicker
* - MobileTimePicker
*/
export declare const useMobilePicker: <TView extends DateOrTimeViewWithMeridiem, TEnableAccessibleFieldDOMStructure extends boolean, TExternalProps extends UseMobilePickerProps<TView, TEnableAccessibleFieldDOMStructure, any, TExternalProps>>({
props,
steps,
...pickerParams
}: UseMobilePickerParams<TView, TEnableAccessibleFieldDOMStructure, TExternalProps>) => {
renderPicker: () => React.JSX.Element;
};

View file

@ -0,0 +1,106 @@
"use strict";
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault").default;
var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard").default;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.useMobilePicker = void 0;
var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends"));
var _objectWithoutPropertiesLoose2 = _interopRequireDefault(require("@babel/runtime/helpers/objectWithoutPropertiesLoose"));
var React = _interopRequireWildcard(require("react"));
var _useSlotProps2 = _interopRequireDefault(require("@mui/utils/useSlotProps"));
var _PickersModalDialog = require("../../components/PickersModalDialog");
var _usePicker = require("../usePicker");
var _PickersLayout = require("../../../PickersLayout");
var _PickerProvider = require("../../components/PickerProvider");
var _PickerFieldUI = require("../../components/PickerFieldUI");
var _createNonRangePickerStepNavigation = require("../../utils/createNonRangePickerStepNavigation");
var _jsxRuntime = require("react/jsx-runtime");
const _excluded = ["props", "steps"],
_excluded2 = ["ownerState"];
/**
* Hook managing all the single-date mobile pickers:
* - MobileDatePicker
* - MobileDateTimePicker
* - MobileTimePicker
*/
const useMobilePicker = _ref => {
let {
props,
steps
} = _ref,
pickerParams = (0, _objectWithoutPropertiesLoose2.default)(_ref, _excluded);
const {
slots,
slotProps: innerSlotProps,
label,
inputRef,
localeText
} = props;
const getStepNavigation = (0, _createNonRangePickerStepNavigation.createNonRangePickerStepNavigation)({
steps
});
const {
providerProps,
renderCurrentView,
ownerState
} = (0, _usePicker.usePicker)((0, _extends2.default)({}, pickerParams, {
props,
localeText,
autoFocusView: true,
viewContainerRole: 'dialog',
variant: 'mobile',
getStepNavigation
}));
const labelId = providerProps.privateContextValue.labelId;
const isToolbarHidden = innerSlotProps?.toolbar?.hidden ?? false;
const Field = slots.field;
const _useSlotProps = (0, _useSlotProps2.default)({
elementType: Field,
externalSlotProps: innerSlotProps?.field,
additionalProps: (0, _extends2.default)({}, isToolbarHidden && {
id: labelId
}),
ownerState
}),
fieldProps = (0, _objectWithoutPropertiesLoose2.default)(_useSlotProps, _excluded2);
const Layout = slots.layout ?? _PickersLayout.PickersLayout;
let labelledById = labelId;
if (isToolbarHidden) {
if (label) {
labelledById = `${labelId}-label`;
} else {
labelledById = undefined;
}
}
const slotProps = (0, _extends2.default)({}, innerSlotProps, {
toolbar: (0, _extends2.default)({}, innerSlotProps?.toolbar, {
titleId: labelId
}),
mobilePaper: (0, _extends2.default)({
'aria-labelledby': labelledById
}, innerSlotProps?.mobilePaper)
});
const renderPicker = () => /*#__PURE__*/(0, _jsxRuntime.jsx)(_PickerProvider.PickerProvider, (0, _extends2.default)({}, providerProps, {
children: /*#__PURE__*/(0, _jsxRuntime.jsxs)(_PickerFieldUI.PickerFieldUIContextProvider, {
slots: slots,
slotProps: slotProps,
inputRef: inputRef,
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(Field, (0, _extends2.default)({}, fieldProps)), /*#__PURE__*/(0, _jsxRuntime.jsx)(_PickersModalDialog.PickersModalDialog, {
slots: slots,
slotProps: slotProps,
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(Layout, (0, _extends2.default)({}, slotProps?.layout, {
slots: slots,
slotProps: slotProps,
children: renderCurrentView()
}))
})]
})
}));
if (process.env.NODE_ENV !== "production") renderPicker.displayName = "renderPicker";
return {
renderPicker
};
};
exports.useMobilePicker = useMobilePicker;

View file

@ -0,0 +1,42 @@
import * as React from 'react';
import { MakeRequired, SlotComponentPropsFromProps } from '@mui/x-internals/types';
import { BasePickerProps } from "../../models/props/basePickerProps.js";
import { PickersModalDialogSlots, PickersModalDialogSlotProps } from "../../components/PickersModalDialog.js";
import { UsePickerParameters, UsePickerNonStaticProps, UsePickerProps } from "../usePicker/index.js";
import { PickerFieldSlotProps, PickerOwnerState } from "../../../models/index.js";
import { ExportedPickersLayoutSlots, ExportedPickersLayoutSlotProps, PickersLayoutSlotProps } from "../../../PickersLayout/PickersLayout.types.js";
import { DateOrTimeViewWithMeridiem, PickerValue } from "../../models/index.js";
import { PickerFieldUISlotsFromContext, PickerFieldUISlotPropsFromContext } from "../../components/PickerFieldUI.js";
import { PickerStep } from "../../utils/createNonRangePickerStepNavigation.js";
export interface UseMobilePickerSlots extends PickersModalDialogSlots, ExportedPickersLayoutSlots<PickerValue>, PickerFieldUISlotsFromContext {
/**
* Component used to enter the date with the keyboard.
*/
field: React.ElementType;
}
export interface ExportedUseMobilePickerSlotProps<TEnableAccessibleFieldDOMStructure extends boolean> extends PickersModalDialogSlotProps, ExportedPickersLayoutSlotProps<PickerValue>, PickerFieldUISlotPropsFromContext {
field?: SlotComponentPropsFromProps<PickerFieldSlotProps<PickerValue, TEnableAccessibleFieldDOMStructure>, {}, PickerOwnerState>;
}
export interface UseMobilePickerSlotProps<TEnableAccessibleFieldDOMStructure extends boolean> extends ExportedUseMobilePickerSlotProps<TEnableAccessibleFieldDOMStructure>, Pick<PickersLayoutSlotProps<PickerValue>, 'toolbar'> {}
export interface MobileOnlyPickerProps extends UsePickerNonStaticProps {}
export interface UseMobilePickerProps<TView extends DateOrTimeViewWithMeridiem, TEnableAccessibleFieldDOMStructure extends boolean, TError, TExternalProps extends UsePickerProps<PickerValue, TView, TError, any>> extends BasePickerProps<PickerValue, TView, TError, TExternalProps>, MakeRequired<MobileOnlyPickerProps, 'format'> {
/**
* Overridable component slots.
* @default {}
*/
slots: UseMobilePickerSlots;
/**
* The props used for each component slot.
* @default {}
*/
slotProps?: UseMobilePickerSlotProps<TEnableAccessibleFieldDOMStructure>;
}
export interface UseMobilePickerParams<TView extends DateOrTimeViewWithMeridiem, TEnableAccessibleFieldDOMStructure extends boolean, TExternalProps extends UseMobilePickerProps<TView, TEnableAccessibleFieldDOMStructure, any, TExternalProps>> extends Pick<UsePickerParameters<PickerValue, TView, TExternalProps>, 'valueManager' | 'valueType' | 'validator' | 'ref'> {
props: TExternalProps;
/**
* Steps available for the picker.
* This will be used to define the behavior of navigation actions.
* If null, the picker will not have any step navigation.
*/
steps: PickerStep[] | null;
}

View file

@ -0,0 +1,5 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});

View file

@ -0,0 +1,9 @@
import * as React from 'react';
import type { UseFieldInternalProps } from "./useField/index.js";
import { FieldRef } from "../../models/index.js";
import { PickerRangeValue, PickerValue } from "../models/index.js";
export declare const PickerFieldPrivateContext: React.Context<PickerFieldPrivateContextValue | null>;
export declare function useNullableFieldPrivateContext(): PickerFieldPrivateContextValue | null;
export interface PickerFieldPrivateContextValue extends Pick<UseFieldInternalProps<any, any, any>, 'formatDensity' | 'enableAccessibleFieldDOMStructure' | 'selectedSections' | 'onSelectedSectionsChange'> {
fieldRef: React.RefObject<FieldRef<PickerValue> | FieldRef<PickerRangeValue> | null>;
}

View file

@ -0,0 +1,15 @@
"use strict";
'use client';
var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard").default;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.PickerFieldPrivateContext = void 0;
exports.useNullableFieldPrivateContext = useNullableFieldPrivateContext;
var React = _interopRequireWildcard(require("react"));
const PickerFieldPrivateContext = exports.PickerFieldPrivateContext = /*#__PURE__*/React.createContext(null);
if (process.env.NODE_ENV !== "production") PickerFieldPrivateContext.displayName = "PickerFieldPrivateContext";
function useNullableFieldPrivateContext() {
return React.useContext(PickerFieldPrivateContext);
}

View file

@ -0,0 +1,5 @@
/**
* Returns the context passed by the Picker wrapping the current component.
* If the context is not found, returns `null`.
*/
export declare const useNullablePickerContext: () => import("../index.js").PickerContextValue<any, any, any> | null;

View file

@ -0,0 +1,16 @@
"use strict";
'use client';
var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard").default;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.useNullablePickerContext = void 0;
var React = _interopRequireWildcard(require("react"));
var _usePickerContext = require("../../hooks/usePickerContext");
/**
* Returns the context passed by the Picker wrapping the current component.
* If the context is not found, returns `null`.
*/
const useNullablePickerContext = () => React.useContext(_usePickerContext.PickerContext);
exports.useNullablePickerContext = useNullablePickerContext;

View file

@ -0,0 +1,2 @@
import { DateOrTimeViewWithMeridiem, PickerOrientation } from "../../../models/index.js";
export declare function useOrientation(views: readonly DateOrTimeViewWithMeridiem[], customOrientation: PickerOrientation | undefined): PickerOrientation;

View file

@ -0,0 +1,43 @@
"use strict";
'use client';
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault").default;
var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard").default;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.useOrientation = useOrientation;
var React = _interopRequireWildcard(require("react"));
var _useEnhancedEffect = _interopRequireDefault(require("@mui/utils/useEnhancedEffect"));
var _utils = require("../../../utils/utils");
function getOrientation() {
if (typeof window === 'undefined') {
return 'portrait';
}
if (window.screen && window.screen.orientation && window.screen.orientation.angle) {
return Math.abs(window.screen.orientation.angle) === 90 ? 'landscape' : 'portrait';
}
// Support IOS safari
if (window.orientation) {
return Math.abs(Number(window.orientation)) === 90 ? 'landscape' : 'portrait';
}
return 'portrait';
}
function useOrientation(views, customOrientation) {
const [orientation, setOrientation] = React.useState(getOrientation);
(0, _useEnhancedEffect.default)(() => {
const eventHandler = () => {
setOrientation(getOrientation());
};
window.addEventListener('orientationchange', eventHandler);
return () => {
window.removeEventListener('orientationchange', eventHandler);
};
}, []);
if ((0, _utils.arrayIncludes)(views, ['hours', 'minutes', 'seconds'])) {
// could not display 13:34:44 in landscape mode
return 'portrait';
}
return customOrientation ?? orientation;
}

View file

@ -0,0 +1,21 @@
import * as React from 'react';
import { DateOrTimeViewWithMeridiem, PickerValidValue, PickerValueManager } from "../../../models/index.js";
import { PickerSelectionState, UsePickerProps, UsePickerState } from "../usePicker.types.js";
import { InferError } from "../../../../models/index.js";
import { SetValueActionOptions } from "../../../components/PickerProvider.js";
import { Validator } from "../../../../validation/index.js";
export declare function useValueAndOpenStates<TValue extends PickerValidValue, TView extends DateOrTimeViewWithMeridiem, TExternalProps extends UsePickerProps<TValue, TView, any, any>>(parameters: UsePickerDateStateParameters<TValue, TView, TExternalProps>): {
timezone: string;
state: UsePickerState<TValue>;
setValue: (newValue: TValue, options?: SetValueActionOptions<InferError<TExternalProps>>) => void;
setValueFromView: (newValue: TValue, selectionState?: PickerSelectionState) => void;
setOpen: (action: React.SetStateAction<boolean>) => void;
value: TValue;
viewValue: TValue;
};
interface UsePickerDateStateParameters<TValue extends PickerValidValue, TView extends DateOrTimeViewWithMeridiem, TExternalProps extends UsePickerProps<TValue, TView, any, any>> {
props: TExternalProps;
valueManager: PickerValueManager<TValue, InferError<TExternalProps>>;
validator: Validator<TValue, InferError<TExternalProps>, TExternalProps>;
}
export {};

View file

@ -0,0 +1,201 @@
"use strict";
'use client';
var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard").default;
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault").default;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.useValueAndOpenStates = useValueAndOpenStates;
var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends"));
var React = _interopRequireWildcard(require("react"));
var _warning = require("@mui/x-internals/warning");
var _useEventCallback = _interopRequireDefault(require("@mui/utils/useEventCallback"));
var _useControlledValue = require("../../useControlledValue");
var _usePickerAdapter = require("../../../../hooks/usePickerAdapter");
var _validation = require("../../../../validation");
function useValueAndOpenStates(parameters) {
const {
props,
valueManager,
validator
} = parameters;
const {
value: valueProp,
defaultValue: defaultValueProp,
onChange,
referenceDate,
timezone: timezoneProp,
onAccept,
closeOnSelect,
open: openProp,
onOpen,
onClose
} = props;
const {
current: defaultValue
} = React.useRef(defaultValueProp);
const {
current: isValueControlled
} = React.useRef(valueProp !== undefined);
const {
current: isOpenControlled
} = React.useRef(openProp !== undefined);
const adapter = (0, _usePickerAdapter.usePickerAdapter)();
if (process.env.NODE_ENV !== 'production') {
if (props.renderInput != null) {
(0, _warning.warnOnce)(['MUI X: The `renderInput` prop has been removed in version 6.0 of the Date and Time Pickers.', 'You can replace it with the `textField` component slot in most cases.', 'For more information, please have a look at the migration guide (https://mui.com/x/migration/migration-pickers-v5/#input-renderer-required-in-v5).']);
}
}
/* eslint-disable react-hooks/rules-of-hooks, react-hooks/exhaustive-deps */
if (process.env.NODE_ENV !== 'production') {
React.useEffect(() => {
if (isValueControlled !== (valueProp !== undefined)) {
console.error([`MUI X: A component is changing the ${isValueControlled ? '' : 'un'}controlled value of a Picker to be ${isValueControlled ? 'un' : ''}controlled.`, 'Elements should not switch from uncontrolled to controlled (or vice versa).', `Decide between using a controlled or uncontrolled value` + 'for the lifetime of the component.', "The nature of the state is determined during the first render. It's considered controlled if the value is not `undefined`.", 'More info: https://fb.me/react-controlled-components'].join('\n'));
}
}, [valueProp]);
React.useEffect(() => {
if (!isValueControlled && defaultValue !== defaultValueProp) {
console.error([`MUI X: A component is changing the defaultValue of an uncontrolled Picker after being initialized. ` + `To suppress this warning opt to use a controlled value.`].join('\n'));
}
}, [JSON.stringify(defaultValue)]);
}
/* eslint-enable react-hooks/rules-of-hooks, react-hooks/exhaustive-deps */
const {
timezone,
value,
handleValueChange
} = (0, _useControlledValue.useControlledValue)({
name: 'a picker component',
timezone: timezoneProp,
value: valueProp,
defaultValue,
referenceDate,
onChange,
valueManager
});
const [state, setState] = React.useState(() => ({
open: false,
lastExternalValue: value,
clockShallowValue: undefined,
lastCommittedValue: value,
hasBeenModifiedSinceMount: false
}));
const {
getValidationErrorForNewValue
} = (0, _validation.useValidation)({
props,
validator,
timezone,
value,
onError: props.onError
});
const setOpen = (0, _useEventCallback.default)(action => {
const newOpen = typeof action === 'function' ? action(state.open) : action;
if (!isOpenControlled) {
setState(prevState => (0, _extends2.default)({}, prevState, {
open: newOpen
}));
}
if (newOpen && onOpen) {
onOpen();
}
if (!newOpen) {
onClose?.();
}
});
const setValue = (0, _useEventCallback.default)((newValue, options) => {
const {
changeImportance = 'accept',
skipPublicationIfPristine = false,
validationError,
shortcut,
shouldClose = changeImportance === 'accept'
} = options ?? {};
let shouldFireOnChange;
let shouldFireOnAccept;
if (!skipPublicationIfPristine && !isValueControlled && !state.hasBeenModifiedSinceMount) {
// If the value is not controlled and the value has never been modified before,
// Then clicking on any value (including the one equal to `defaultValue`) should call `onChange` and `onAccept`
shouldFireOnChange = true;
shouldFireOnAccept = changeImportance === 'accept';
} else {
shouldFireOnChange = !valueManager.areValuesEqual(adapter, newValue, value);
shouldFireOnAccept = changeImportance === 'accept' && !valueManager.areValuesEqual(adapter, newValue, state.lastCommittedValue);
}
setState(prevState => (0, _extends2.default)({}, prevState, {
// We reset the shallow value whenever we fire onChange.
clockShallowValue: shouldFireOnChange ? undefined : prevState.clockShallowValue,
lastCommittedValue: shouldFireOnAccept ? newValue : prevState.lastCommittedValue,
hasBeenModifiedSinceMount: true
}));
let cachedContext = null;
const getContext = () => {
if (!cachedContext) {
cachedContext = {
validationError: validationError == null ? getValidationErrorForNewValue(newValue) : validationError
};
if (shortcut) {
cachedContext.shortcut = shortcut;
}
}
return cachedContext;
};
if (shouldFireOnChange) {
handleValueChange(newValue, getContext());
}
if (shouldFireOnAccept && onAccept) {
onAccept(newValue, getContext());
}
if (shouldClose) {
setOpen(false);
}
});
// If `prop.value` changes, we update the state to reflect the new value
if (value !== state.lastExternalValue) {
setState(prevState => (0, _extends2.default)({}, prevState, {
lastExternalValue: value,
clockShallowValue: undefined,
hasBeenModifiedSinceMount: true
}));
}
const setValueFromView = (0, _useEventCallback.default)((newValue, selectionState = 'partial') => {
// TODO: Expose a new method (private?) like `setView` that only updates the clock shallow value.
if (selectionState === 'shallow') {
setState(prev => (0, _extends2.default)({}, prev, {
clockShallowValue: newValue,
hasBeenModifiedSinceMount: true
}));
return;
}
setValue(newValue, {
changeImportance: selectionState === 'finish' && closeOnSelect ? 'accept' : 'set'
});
});
// It is required to update inner state in useEffect in order to avoid situation when
// Our component is not mounted yet, but `open` state is set to `true` (for example initially opened)
React.useEffect(() => {
if (isOpenControlled) {
if (openProp === undefined) {
throw new Error('You must not mix controlling and uncontrolled mode for `open` prop');
}
setState(prevState => (0, _extends2.default)({}, prevState, {
open: openProp
}));
}
}, [isOpenControlled, openProp]);
const viewValue = React.useMemo(() => valueManager.cleanValue(adapter, state.clockShallowValue === undefined ? value : state.clockShallowValue), [adapter, valueManager, state.clockShallowValue, value]);
return {
timezone,
state,
setValue,
setValueFromView,
setOpen,
value,
viewValue
};
}

View file

@ -0,0 +1,2 @@
export { usePicker } from "./usePicker.js";
export type { UsePickerProps, UsePickerBaseProps, UsePickerParameters, PickerSelectionState, PickerViewsRendererProps, PickerViewRendererLookup, PickerRendererInterceptorProps, PickerViewRenderer, UsePickerNonStaticProps } from "./usePicker.types.js";

View file

@ -0,0 +1,12 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
Object.defineProperty(exports, "usePicker", {
enumerable: true,
get: function () {
return _usePicker.usePicker;
}
});
var _usePicker = require("./usePicker");

View file

@ -0,0 +1,16 @@
import { UsePickerParameters, UsePickerProps, UsePickerReturnValue } from "./usePicker.types.js";
import { DateOrTimeViewWithMeridiem, PickerValidValue } from "../../models/index.js";
export declare const usePicker: <TValue extends PickerValidValue, TView extends DateOrTimeViewWithMeridiem, TExternalProps extends UsePickerProps<TValue, TView, any, any>>({
ref,
props,
valueManager,
valueType,
variant,
validator,
onPopperExited,
autoFocusView,
rendererInterceptor: RendererInterceptor,
localeText,
viewContainerRole,
getStepNavigation
}: UsePickerParameters<TValue, TView, TExternalProps>) => UsePickerReturnValue<TValue>;

View file

@ -0,0 +1,323 @@
"use strict";
'use client';
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault").default;
var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard").default;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.usePicker = void 0;
var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends"));
var _objectWithoutPropertiesLoose2 = _interopRequireDefault(require("@babel/runtime/helpers/objectWithoutPropertiesLoose"));
var React = _interopRequireWildcard(require("react"));
var _useEnhancedEffect = _interopRequireDefault(require("@mui/utils/useEnhancedEffect"));
var _useEventCallback = _interopRequireDefault(require("@mui/utils/useEventCallback"));
var _useForkRef = _interopRequireDefault(require("@mui/utils/useForkRef"));
var _useId = _interopRequireDefault(require("@mui/utils/useId"));
var _usePickerAdapter = require("../../../hooks/usePickerAdapter");
var _useReduceAnimations = require("../useReduceAnimations");
var _timeUtils = require("../../utils/time-utils");
var _useViews = require("../useViews");
var _useOrientation = require("./hooks/useOrientation");
var _useValueAndOpenStates = require("./hooks/useValueAndOpenStates");
var _jsxRuntime = require("react/jsx-runtime");
const _excluded = ["className", "sx"];
const usePicker = ({
ref,
props,
valueManager,
valueType,
variant,
validator,
onPopperExited,
autoFocusView,
rendererInterceptor: RendererInterceptor,
localeText,
viewContainerRole,
getStepNavigation
}) => {
const {
// View props
views,
view: viewProp,
openTo,
onViewChange,
viewRenderers,
reduceAnimations: reduceAnimationsProp,
orientation: orientationProp,
disableOpenPicker,
closeOnSelect,
// Form props
disabled,
readOnly,
// Field props
formatDensity,
enableAccessibleFieldDOMStructure,
selectedSections,
onSelectedSectionsChange,
format,
label,
// Other props
autoFocus,
name
} = props;
const {
className,
sx
} = props,
propsToForwardToView = (0, _objectWithoutPropertiesLoose2.default)(props, _excluded);
/**
* TODO: Improve how we generate the aria-label and aria-labelledby attributes.
*/
const labelId = (0, _useId.default)();
const adapter = (0, _usePickerAdapter.usePickerAdapter)();
const reduceAnimations = (0, _useReduceAnimations.useReduceAnimations)(reduceAnimationsProp);
const orientation = (0, _useOrientation.useOrientation)(views, orientationProp);
const {
current: initialView
} = React.useRef(openTo ?? null);
/**
* Refs
*/
const [triggerElement, triggerRef] = React.useState(null);
const popupRef = React.useRef(null);
const fieldRef = React.useRef(null);
const rootRefObject = React.useRef(null);
const rootRef = (0, _useForkRef.default)(ref, rootRefObject);
const {
timezone,
state,
setOpen,
setValue,
setValueFromView,
value,
viewValue
} = (0, _useValueAndOpenStates.useValueAndOpenStates)({
props,
valueManager,
validator
});
const {
view,
setView,
defaultView,
focusedView,
setFocusedView,
setValueAndGoToNextView,
goToNextStep,
hasNextStep,
hasSeveralSteps
} = (0, _useViews.useViews)({
view: viewProp,
views,
openTo,
onChange: setValueFromView,
onViewChange,
autoFocus: autoFocusView,
getStepNavigation
});
const clearValue = (0, _useEventCallback.default)(() => setValue(valueManager.emptyValue));
const setValueToToday = (0, _useEventCallback.default)(() => setValue(valueManager.getTodayValue(adapter, timezone, valueType)));
const acceptValueChanges = (0, _useEventCallback.default)(() => setValue(value));
const cancelValueChanges = (0, _useEventCallback.default)(() => setValue(state.lastCommittedValue, {
skipPublicationIfPristine: true
}));
const dismissViews = (0, _useEventCallback.default)(() => {
setValue(value, {
skipPublicationIfPristine: true
});
});
const {
hasUIView,
viewModeLookup,
timeViewsCount
} = React.useMemo(() => views.reduce((acc, viewForReduce) => {
const viewMode = viewRenderers[viewForReduce] == null ? 'field' : 'UI';
acc.viewModeLookup[viewForReduce] = viewMode;
if (viewMode === 'UI') {
acc.hasUIView = true;
if ((0, _timeUtils.isTimeView)(viewForReduce)) {
acc.timeViewsCount += 1;
}
}
return acc;
}, {
hasUIView: false,
viewModeLookup: {},
timeViewsCount: 0
}), [viewRenderers, views]);
const currentViewMode = viewModeLookup[view];
const getCurrentViewMode = (0, _useEventCallback.default)(() => currentViewMode);
const [popperView, setPopperView] = React.useState(currentViewMode === 'UI' ? view : null);
if (popperView !== view && viewModeLookup[view] === 'UI') {
setPopperView(view);
}
(0, _useEnhancedEffect.default)(() => {
// Handle case of Date Time Picker without time renderers
if (currentViewMode === 'field' && state.open) {
setOpen(false);
setTimeout(() => {
fieldRef?.current?.setSelectedSections(view);
// focusing the input before the range selection is done
// calling it outside of timeout results in an inconsistent behavior between Safari And Chrome
fieldRef?.current?.focusField(view);
});
}
}, [view]); // eslint-disable-line react-hooks/exhaustive-deps
(0, _useEnhancedEffect.default)(() => {
if (!state.open) {
return;
}
let newView = view;
// If the current view is a field view, go to the last popper view
if (currentViewMode === 'field' && popperView != null) {
newView = popperView;
}
// If the current view is not the default view and both are UI views
if (newView !== defaultView && viewModeLookup[newView] === 'UI' && viewModeLookup[defaultView] === 'UI') {
newView = defaultView;
}
if (newView !== view) {
setView(newView);
}
setFocusedView(newView, true);
}, [state.open]); // eslint-disable-line react-hooks/exhaustive-deps
const ownerState = React.useMemo(() => ({
isPickerValueEmpty: valueManager.areValuesEqual(adapter, value, valueManager.emptyValue),
isPickerOpen: state.open,
isPickerDisabled: props.disabled ?? false,
isPickerReadOnly: props.readOnly ?? false,
pickerOrientation: orientation,
pickerVariant: variant
}), [adapter, valueManager, value, state.open, orientation, variant, props.disabled, props.readOnly]);
const triggerStatus = React.useMemo(() => {
if (disableOpenPicker || !hasUIView) {
return 'hidden';
}
if (disabled || readOnly) {
return 'disabled';
}
return 'enabled';
}, [disableOpenPicker, hasUIView, disabled, readOnly]);
const wrappedGoToNextStep = (0, _useEventCallback.default)(goToNextStep);
const defaultActionBarActions = React.useMemo(() => {
if (closeOnSelect && !hasSeveralSteps) {
return [];
}
return ['cancel', 'nextOrAccept'];
}, [closeOnSelect, hasSeveralSteps]);
const actionsContextValue = React.useMemo(() => ({
setValue,
setOpen,
clearValue,
setValueToToday,
acceptValueChanges,
cancelValueChanges,
setView,
goToNextStep: wrappedGoToNextStep
}), [setValue, setOpen, clearValue, setValueToToday, acceptValueChanges, cancelValueChanges, setView, wrappedGoToNextStep]);
const contextValue = React.useMemo(() => (0, _extends2.default)({}, actionsContextValue, {
value,
timezone,
open: state.open,
views,
view: popperView,
initialView,
disabled: disabled ?? false,
readOnly: readOnly ?? false,
autoFocus: autoFocus ?? false,
variant,
orientation,
popupRef,
reduceAnimations,
triggerRef,
triggerStatus,
hasNextStep,
fieldFormat: format ?? '',
name,
label,
rootSx: sx,
rootRef,
rootClassName: className
}), [actionsContextValue, value, rootRef, variant, orientation, reduceAnimations, disabled, readOnly, format, className, name, label, sx, triggerStatus, hasNextStep, timezone, state.open, popperView, views, initialView, autoFocus]);
const privateContextValue = React.useMemo(() => ({
dismissViews,
ownerState,
hasUIView,
getCurrentViewMode,
rootRefObject,
labelId,
triggerElement,
viewContainerRole,
defaultActionBarActions,
onPopperExited
}), [dismissViews, ownerState, hasUIView, getCurrentViewMode, labelId, triggerElement, viewContainerRole, defaultActionBarActions, onPopperExited]);
const fieldPrivateContextValue = React.useMemo(() => ({
formatDensity,
enableAccessibleFieldDOMStructure,
selectedSections,
onSelectedSectionsChange,
fieldRef
}), [formatDensity, enableAccessibleFieldDOMStructure, selectedSections, onSelectedSectionsChange, fieldRef]);
const isValidContextValue = testedValue => {
const error = validator({
adapter,
value: testedValue,
timezone,
props
});
return !valueManager.hasError(error);
};
const renderCurrentView = () => {
if (popperView == null) {
return null;
}
const renderer = viewRenderers[popperView];
if (renderer == null) {
return null;
}
const rendererProps = (0, _extends2.default)({}, propsToForwardToView, {
views,
timezone,
value: viewValue,
onChange: setValueAndGoToNextView,
view: popperView,
onViewChange: setView,
showViewSwitcher: timeViewsCount > 1,
timeViewsCount
}, viewContainerRole === 'tooltip' ? {
focusedView: null,
onFocusedViewChange: () => {}
} : {
focusedView,
onFocusedViewChange: setFocusedView
});
if (RendererInterceptor) {
return /*#__PURE__*/(0, _jsxRuntime.jsx)(RendererInterceptor, {
viewRenderers: viewRenderers,
popperView: popperView,
rendererProps: rendererProps
});
}
return renderer(rendererProps);
};
return {
providerProps: {
localeText,
contextValue,
privateContextValue,
actionsContextValue,
fieldPrivateContextValue,
isValidContextValue
},
renderCurrentView,
ownerState
};
};
exports.usePicker = usePicker;

View file

@ -0,0 +1,187 @@
import { SxProps } from '@mui/system';
import { Theme } from '@mui/material/styles';
import { InferError, OnErrorProps, PickerChangeHandlerContext, PickerOwnerState, PickerValidDate, PickerValueType, TimezoneProps } from "../../../models/index.js";
import { DateOrTimeViewWithMeridiem, FormProps, PickerOrientation, PickerRangeValue, PickerValidValue, PickerValueManager, PickerVariant } from "../../models/index.js";
import { Validator } from "../../../validation/index.js";
import { UseViewsOptions } from "../useViews.js";
import { PickerProviderProps } from "../../components/PickerProvider.js";
import { PickersInputLocaleText } from "../../../locales/index.js";
import { PickerFieldPrivateContextValue } from "../useNullableFieldPrivateContext.js";
import { CreateStepNavigationReturnValue } from "../../utils/createStepNavigation.js";
/**
* Props common to all Picker headless implementations.
* Those props are exposed on all the Pickers.
*/
export interface UsePickerBaseProps<TValue extends PickerValidValue, TView extends DateOrTimeViewWithMeridiem, TError, TExternalProps extends UsePickerProps<TValue, TView, TError, any>> extends OnErrorProps<TValue, TError>, Omit<UseViewsOptions<any, TView>, 'onChange' | 'onFocusedViewChange' | 'focusedView' | 'getStepNavigation'>, TimezoneProps, FormProps {
/**
* The selected value.
* Used when the component is controlled.
*/
value?: TValue;
/**
* The default value.
* Used when the component is not controlled.
*/
defaultValue?: TValue;
/**
* Callback fired when the value changes.
* @template TValue The value type. It will be the same type as `value` or `null`. It can be in `[start, end]` format in case of range value.
* @template TError The validation error type. It will be either `string` or a `null`. It 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?: (value: TValue, context: PickerChangeHandlerContext<TError>) => void;
/**
* Callback fired when the value is accepted.
* @template TValue The value type. It will be the same type as `value` or `null`. It can be in `[start, end]` format in case of range value.
* @template TError The validation error type. It will be either `string` or a `null`. It can be in `[start, end]` format in case of range value.
* @param {TValue} value The value that was just accepted.
* @param {FieldChangeHandlerContext<TError>} context The context containing the validation result of the current value.
*/
onAccept?: (value: TValue, context: PickerChangeHandlerContext<TError>) => void;
/**
* If `null`, the section will only have field editing.
* If `undefined`, internally defined view will be used.
*/
viewRenderers: PickerViewRendererLookup<TValue, TView, TExternalProps>;
/**
* The date used to generate the new value when both `value` and `defaultValue` are empty.
* @default The closest valid date-time using the validation props, except callbacks like `shouldDisable<...>`.
*/
referenceDate?: TValue extends PickerRangeValue ? TValue | PickerValidDate : PickerValidDate;
/**
* Force rendering in particular orientation.
*/
orientation?: PickerOrientation;
/**
* If `true`, disable heavy animations.
* @default `@media(prefers-reduced-motion: reduce)` || `navigator.userAgent` matches Android <10 or iOS <13
*/
reduceAnimations?: boolean;
}
/**
* Props used to handle the value of non-static Pickers.
*/
export interface UsePickerNonStaticProps extends Omit<PickerFieldPrivateContextValue, 'fieldRef'> {
/**
* If `true`, the Picker will close after submitting the full date.
* @default false
*/
closeOnSelect?: boolean;
/**
* Control the popup or dialog open state.
* @default false
*/
open?: boolean;
/**
* Callback fired when the popup requests to be closed.
* Use in controlled mode (see `open`).
*/
onClose?: () => void;
/**
* Callback fired when the popup requests to be opened.
* Use in controlled mode (see `open`).
*/
onOpen?: () => void;
/**
* Format of the date when rendered in the input(s).
* Defaults to localized format based on the used `views`.
*/
format?: string;
/**
* If `true`, the button to open the Picker will not be rendered (it will only render the field).
* @deprecated Use the [field component](https://mui.com/x/react-date-pickers/fields/) instead.
* @default false
*/
disableOpenPicker?: boolean;
/**
* The label content.
*/
label?: React.ReactNode;
/**
* Pass a ref to the `input` element.
*/
inputRef?: React.Ref<HTMLInputElement>;
/**
* Name attribute used by the `input` element in the Field.
*/
name?: string;
}
export interface UsePickerProps<TValue extends PickerValidValue, TView extends DateOrTimeViewWithMeridiem, TError, TExternalProps extends UsePickerProps<TValue, TView, TError, any>> extends UsePickerBaseProps<TValue, TView, TError, TExternalProps>, UsePickerNonStaticProps {
referenceDate?: TValue extends PickerRangeValue ? TValue | PickerValidDate : PickerValidDate;
className?: string;
sx?: SxProps<Theme>;
}
export interface UsePickerParameters<TValue extends PickerValidValue, TView extends DateOrTimeViewWithMeridiem, TExternalProps extends UsePickerProps<TValue, TView, any, any>> {
ref: React.ForwardedRef<HTMLDivElement> | undefined;
localeText: PickersInputLocaleText | undefined;
variant: PickerVariant;
valueManager: PickerValueManager<TValue, InferError<TExternalProps>>;
valueType: PickerValueType;
validator: Validator<TValue, InferError<TExternalProps>, TExternalProps>;
autoFocusView: boolean;
viewContainerRole: 'dialog' | 'tooltip' | null;
/**
* A function that intercepts the regular Picker rendering.
* Can be used to consume the provided `viewRenderers` and render a custom component wrapping them.
* @param {PickerViewRendererLookup<TValue, TView, TExternalProps>} viewRenderers The `viewRenderers` provided to the Picker component.
* @param {TView} popperView The current Picker view.
* @param {any} rendererProps All the props being passed down to the renderer.
* @returns {React.ReactNode} A React node that will be rendered instead of the default renderer.
*/
rendererInterceptor?: React.JSXElementConstructor<PickerRendererInterceptorProps<TValue, TView, TExternalProps>>;
props: TExternalProps;
getStepNavigation: CreateStepNavigationReturnValue;
onPopperExited?: () => void;
}
export interface UsePickerReturnValue<TValue extends PickerValidValue> {
ownerState: PickerOwnerState;
renderCurrentView: () => React.ReactNode;
providerProps: Omit<PickerProviderProps<TValue>, 'children'>;
}
export type PickerSelectionState = 'partial' | 'shallow' | 'finish';
export interface UsePickerState<TValue extends PickerValidValue> {
/**
* Whether the Picker is open.
*/
open: boolean;
/**
* Last value returned by `useControlledValue`.
*/
lastExternalValue: TValue;
/**
* Date currently displayed on the views if we are dragging the cursor in the Clock component.
*/
clockShallowValue: TValue | undefined;
/**
* Last value committed (the last value for which `shouldCommitValue` returned `true`).
* If `onAccept` is defined, it's the value that was passed on the last call to this callback.
*/
lastCommittedValue: TValue;
/**
* If we never modified the value since the mount of the component,
* Then we might want to apply some custom logic.
*
* For example, when the component is not controlled and `defaultValue` is defined.
* Then clicking on "Accept", "Today" or "Clear" should fire `onAccept` with `defaultValue`, but clicking on "Cancel" or dismissing the Picker should not.
*/
hasBeenModifiedSinceMount: boolean;
}
export interface PickerViewsRendererBaseExternalProps extends Omit<UsePickerProps<any, any, any, any>, 'openTo' | 'viewRenderers' | 'onChange'> {}
export type PickerViewsRendererProps<TValue extends PickerValidValue, TView extends DateOrTimeViewWithMeridiem, TExternalProps extends PickerViewsRendererBaseExternalProps> = Omit<TExternalProps, 'className' | 'sx'> & {
value: TValue;
onChange: (value: TValue, selectionState?: PickerSelectionState) => void;
view: TView;
views: readonly TView[];
focusedView: TView | null;
onFocusedViewChange: (viewToFocus: TView, hasFocus: boolean) => void;
showViewSwitcher: boolean;
timeViewsCount: number;
};
export type PickerViewRenderer<TValue extends PickerValidValue, TExternalProps extends PickerViewsRendererBaseExternalProps> = (props: PickerViewsRendererProps<TValue, any, TExternalProps>) => React.ReactNode;
export type PickerViewRendererLookup<TValue extends PickerValidValue, TView extends DateOrTimeViewWithMeridiem, TExternalProps extends PickerViewsRendererBaseExternalProps> = Record<TView, PickerViewRenderer<TValue, TExternalProps> | null>;
export interface PickerRendererInterceptorProps<TValue extends PickerValidValue, TView extends DateOrTimeViewWithMeridiem, TExternalProps extends UsePickerProps<TValue, TView, any, TExternalProps>> {
viewRenderers: PickerViewRendererLookup<TValue, TView, TExternalProps>;
popperView: TView;
rendererProps: PickerViewsRendererProps<TValue, TView, TExternalProps>;
}

View file

@ -0,0 +1,5 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});

View file

@ -0,0 +1,4 @@
/**
* Returns the private context passed by the Picker wrapping the current component.
*/
export declare const usePickerPrivateContext: () => import("../components/PickerProvider.js").PickerPrivateContextValue;

View file

@ -0,0 +1,15 @@
"use strict";
'use client';
var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard").default;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.usePickerPrivateContext = void 0;
var React = _interopRequireWildcard(require("react"));
var _PickerProvider = require("../components/PickerProvider");
/**
* Returns the private context passed by the Picker wrapping the current component.
*/
const usePickerPrivateContext = () => React.useContext(_PickerProvider.PickerPrivateContext);
exports.usePickerPrivateContext = usePickerPrivateContext;

View file

@ -0,0 +1,2 @@
export declare const slowAnimationDevices: boolean;
export declare function useReduceAnimations(customReduceAnimations: boolean | undefined): boolean;

View file

@ -0,0 +1,25 @@
"use strict";
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault").default;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.slowAnimationDevices = void 0;
exports.useReduceAnimations = useReduceAnimations;
var _useMediaQuery = _interopRequireDefault(require("@mui/material/useMediaQuery"));
const PREFERS_REDUCED_MOTION = '@media (prefers-reduced-motion: reduce)';
// detect if user agent has Android version < 10 or iOS version < 13
const mobileVersionMatches = typeof navigator !== 'undefined' && navigator.userAgent.match(/android\s(\d+)|OS\s(\d+)/i);
const androidVersion = mobileVersionMatches && mobileVersionMatches[1] ? parseInt(mobileVersionMatches[1], 10) : null;
const iOSVersion = mobileVersionMatches && mobileVersionMatches[2] ? parseInt(mobileVersionMatches[2], 10) : null;
const slowAnimationDevices = exports.slowAnimationDevices = androidVersion && androidVersion < 10 || iOSVersion && iOSVersion < 13 || false;
function useReduceAnimations(customReduceAnimations) {
const prefersReduced = (0, _useMediaQuery.default)(PREFERS_REDUCED_MOTION, {
defaultMatches: false
});
if (customReduceAnimations != null) {
return customReduceAnimations;
}
return prefersReduced || slowAnimationDevices;
}

View file

@ -0,0 +1,2 @@
export { useStaticPicker } from "./useStaticPicker.js";
export type { UseStaticPickerSlots, UseStaticPickerSlotProps, StaticOnlyPickerProps } from "./useStaticPicker.types.js";

View file

@ -0,0 +1,12 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
Object.defineProperty(exports, "useStaticPicker", {
enumerable: true,
get: function () {
return _useStaticPicker.useStaticPicker;
}
});
var _useStaticPicker = require("./useStaticPicker");

View file

@ -0,0 +1,16 @@
import * as React from 'react';
import { UseStaticPickerParams, UseStaticPickerProps } from "./useStaticPicker.types.js";
import { DateOrTimeViewWithMeridiem } from "../../models/index.js";
/**
* Hook managing all the single-date static pickers:
* - StaticDatePicker
* - StaticDateTimePicker
* - StaticTimePicker
*/
export declare const useStaticPicker: <TView extends DateOrTimeViewWithMeridiem, TExternalProps extends UseStaticPickerProps<TView, any, TExternalProps>>({
props,
steps,
...pickerParams
}: UseStaticPickerParams<TView, TExternalProps>) => {
renderPicker: () => React.JSX.Element;
};

View file

@ -0,0 +1,79 @@
"use strict";
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault").default;
var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard").default;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.useStaticPicker = void 0;
var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends"));
var _objectWithoutPropertiesLoose2 = _interopRequireDefault(require("@babel/runtime/helpers/objectWithoutPropertiesLoose"));
var React = _interopRequireWildcard(require("react"));
var _clsx = _interopRequireDefault(require("clsx"));
var _styles = require("@mui/material/styles");
var _usePicker = require("../usePicker");
var _PickerProvider = require("../../components/PickerProvider");
var _PickersLayout = require("../../../PickersLayout");
var _dimensions = require("../../constants/dimensions");
var _utils = require("../../utils/utils");
var _createNonRangePickerStepNavigation = require("../../utils/createNonRangePickerStepNavigation");
var _jsxRuntime = require("react/jsx-runtime");
const _excluded = ["props", "steps"];
const PickerStaticLayout = (0, _styles.styled)(_PickersLayout.PickersLayout)(({
theme
}) => ({
overflow: 'hidden',
minWidth: _dimensions.DIALOG_WIDTH,
backgroundColor: (theme.vars || theme).palette.background.paper
}));
/**
* Hook managing all the single-date static pickers:
* - StaticDatePicker
* - StaticDateTimePicker
* - StaticTimePicker
*/
const useStaticPicker = _ref => {
let {
props,
steps
} = _ref,
pickerParams = (0, _objectWithoutPropertiesLoose2.default)(_ref, _excluded);
const {
localeText,
slots,
slotProps,
displayStaticWrapperAs,
autoFocus
} = props;
const getStepNavigation = (0, _createNonRangePickerStepNavigation.createNonRangePickerStepNavigation)({
steps
});
const {
providerProps,
renderCurrentView
} = (0, _usePicker.usePicker)((0, _extends2.default)({}, pickerParams, {
props,
variant: displayStaticWrapperAs,
autoFocusView: autoFocus ?? false,
viewContainerRole: null,
localeText,
getStepNavigation
}));
const Layout = slots?.layout ?? PickerStaticLayout;
const renderPicker = () => /*#__PURE__*/(0, _jsxRuntime.jsx)(_PickerProvider.PickerProvider, (0, _extends2.default)({}, providerProps, {
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(Layout, (0, _extends2.default)({}, slotProps?.layout, {
slots: slots,
slotProps: slotProps,
sx: (0, _utils.mergeSx)(providerProps.contextValue.rootSx, slotProps?.layout?.sx),
className: (0, _clsx.default)(providerProps.contextValue.rootClassName, slotProps?.layout?.className),
ref: providerProps.contextValue.rootRef,
children: renderCurrentView()
}))
}));
if (process.env.NODE_ENV !== "production") renderPicker.displayName = "renderPicker";
return {
renderPicker
};
};
exports.useStaticPicker = useStaticPicker;

View file

@ -0,0 +1,46 @@
import { ExportedPickersLayoutSlots, ExportedPickersLayoutSlotProps } from "../../../PickersLayout/PickersLayout.types.js";
import { BasePickerProps } from "../../models/props/basePickerProps.js";
import { UsePickerParameters, UsePickerProps } from "../usePicker/index.js";
import { DateOrTimeViewWithMeridiem, PickerValue } from "../../models/index.js";
import { PickerStep } from "../../utils/createNonRangePickerStepNavigation.js";
export interface UseStaticPickerSlots extends ExportedPickersLayoutSlots<PickerValue> {}
export interface UseStaticPickerSlotProps extends ExportedPickersLayoutSlotProps<PickerValue> {}
export interface StaticOnlyPickerProps {
/**
* Force static wrapper inner components to be rendered in mobile or desktop mode.
* @default "mobile"
*/
displayStaticWrapperAs: 'desktop' | 'mobile';
/**
* If `true`, the view is focused during the first mount.
* @default false
*/
autoFocus?: boolean;
/**
* Callback fired when component requests to be closed.
* Can be fired when selecting (by default on `desktop` mode) or clearing a value.
* @deprecated Please avoid using as it will be removed in next major version.
*/
onClose?: () => void;
}
export interface UseStaticPickerProps<TView extends DateOrTimeViewWithMeridiem, TError, TExternalProps extends UsePickerProps<PickerValue, TView, TError, any>> extends BasePickerProps<PickerValue, TView, TError, TExternalProps>, StaticOnlyPickerProps {
/**
* Overridable component slots.
* @default {}
*/
slots?: UseStaticPickerSlots;
/**
* The props used for each component slot.
* @default {}
*/
slotProps?: UseStaticPickerSlotProps;
}
export interface UseStaticPickerParams<TView extends DateOrTimeViewWithMeridiem, TExternalProps extends UseStaticPickerProps<TView, any, TExternalProps>> extends Pick<UsePickerParameters<PickerValue, TView, TExternalProps>, 'valueManager' | 'valueType' | 'validator' | 'ref'> {
props: TExternalProps;
/**
* Steps available for the picker.
* This will be used to define the behavior of navigation actions.
* If null, the picker will not have any step navigation.
*/
steps: PickerStep[] | null;
}

View file

@ -0,0 +1,5 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});

View file

@ -0,0 +1,10 @@
import { PickerOwnerState } from "../../models/index.js";
export declare function useToolbarOwnerState(): PickerToolbarOwnerState;
export interface PickerToolbarOwnerState extends PickerOwnerState {
/**
* The direction of the toolbar.
* Is equal to "ltr" when the toolbar is in left-to-right direction.
* Is equal to "rtl" when the toolbar is in right-to-left direction.
*/
toolbarDirection: 'ltr' | 'rtl';
}

View file

@ -0,0 +1,21 @@
"use strict";
var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard").default;
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault").default;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.useToolbarOwnerState = useToolbarOwnerState;
var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends"));
var React = _interopRequireWildcard(require("react"));
var _RtlProvider = require("@mui/system/RtlProvider");
var _usePickerPrivateContext = require("./usePickerPrivateContext");
function useToolbarOwnerState() {
const {
ownerState: pickerOwnerState
} = (0, _usePickerPrivateContext.usePickerPrivateContext)();
const isRtl = (0, _RtlProvider.useRtl)();
return React.useMemo(() => (0, _extends2.default)({}, pickerOwnerState, {
toolbarDirection: isRtl ? 'rtl' : 'ltr'
}), [pickerOwnerState, isRtl]);
}

View file

@ -0,0 +1,6 @@
import { PickersTimezone, PickerValidDate } from "../../models/index.js";
export declare const useDefaultDates: () => {
minDate: PickerValidDate;
maxDate: PickerValidDate;
};
export declare const useNow: (timezone: PickersTimezone) => PickerValidDate;

View file

@ -0,0 +1,20 @@
"use strict";
var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard").default;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.useNow = exports.useDefaultDates = void 0;
var React = _interopRequireWildcard(require("react"));
var _usePickerAdapter = require("../../hooks/usePickerAdapter");
const useDefaultDates = () => (0, _usePickerAdapter.useLocalizationContext)().defaultDates;
exports.useDefaultDates = useDefaultDates;
const useNow = timezone => {
const adapter = (0, _usePickerAdapter.usePickerAdapter)();
const now = React.useRef(undefined);
if (now.current === undefined) {
now.current = adapter.date(undefined, timezone);
}
return now.current;
};
exports.useNow = useNow;

View file

@ -0,0 +1,85 @@
import { MakeOptional } from '@mui/x-internals/types';
import type { PickerSelectionState } from "./usePicker/index.js";
import { DateOrTimeViewWithMeridiem, PickerValidValue } from "../models/index.js";
import { PickerValidDate } from "../../models/index.js";
import { CreateStepNavigationReturnValue } from "../utils/createStepNavigation.js";
export type PickerOnChangeFn = (date: PickerValidDate | null, selectionState?: PickerSelectionState) => void;
export interface UseViewsOptions<TValue extends PickerValidValue, TView extends DateOrTimeViewWithMeridiem> {
/**
* Callback fired when the value changes.
* @template TValue The value type. It will be the same type as `value` or `null`. It can be in `[start, end]` format in case of range value.
* @template TView The view type. Will be one of date or time views.
* @param {TValue} value The new value.
* @param {PickerSelectionState | undefined} selectionState Indicates if the date selection is complete.
* @param {TView | undefined} selectedView Indicates the view in which the selection has been made.
*/
onChange: (value: TValue, selectionState?: PickerSelectionState, selectedView?: TView) => void;
/**
* Callback fired on view change.
* @template TView Type of the view. It will vary based on the Picker type and the `views` it uses.
* @param {TView} view The new view.
*/
onViewChange?: (view: TView) => void;
/**
* The default visible view.
* Used when the component view is not controlled.
* Must be a valid option from `views` list.
*/
openTo?: TView;
/**
* The visible view.
* Used when the component view is controlled.
* Must be a valid option from `views` list.
*/
view?: TView;
/**
* Available views.
*/
views: readonly TView[];
/**
* If `true`, the main element is focused during the first mount.
* This main element is:
* - the element chosen by the visible view if any (i.e: the selected day on the `day` view).
* - the `input` element if there is a field rendered.
*/
autoFocus?: boolean;
/**
* Controlled focused view.
*/
focusedView?: TView | null;
/**
* Callback fired on focused view change.
* @template TView Type of the view. It will vary based on the Picker type and the `views` it uses.
* @param {TView} view The new view to focus or not.
* @param {boolean} hasFocus `true` if the view should be focused.
*/
onFocusedViewChange?: (view: TView, hasFocus: boolean) => void;
getStepNavigation?: CreateStepNavigationReturnValue;
}
export interface ExportedUseViewsOptions<TValue extends PickerValidValue, TView extends DateOrTimeViewWithMeridiem> extends Omit<MakeOptional<UseViewsOptions<TValue, TView>, 'onChange' | 'openTo' | 'views'>, 'getStepNavigation'> {}
interface UseViewsResponse<TValue extends PickerValidValue, TView extends DateOrTimeViewWithMeridiem> {
view: TView;
setView: (view: TView) => void;
focusedView: TView | null;
setFocusedView: (view: TView, hasFocus: boolean) => void;
nextView: TView | null;
previousView: TView | null;
defaultView: TView;
goToNextView: () => void;
setValueAndGoToNextView: (value: TValue, currentViewSelectionState?: PickerSelectionState, selectedView?: TView) => void;
hasNextStep: boolean;
hasSeveralSteps: boolean;
goToNextStep: () => void;
}
export declare function useViews<TValue extends PickerValidValue, TView extends DateOrTimeViewWithMeridiem>({
onChange,
onViewChange,
openTo,
view: inView,
views,
autoFocus,
focusedView: inFocusedView,
onFocusedViewChange,
getStepNavigation
}: UseViewsOptions<TValue, TView>): UseViewsResponse<TValue, TView>;
export {};

View file

@ -0,0 +1,137 @@
"use strict";
'use client';
var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard").default;
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault").default;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.useViews = useViews;
var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends"));
var React = _interopRequireWildcard(require("react"));
var _useEventCallback = _interopRequireDefault(require("@mui/utils/useEventCallback"));
var _useControlled = _interopRequireDefault(require("@mui/utils/useControlled"));
var _createStepNavigation = require("../utils/createStepNavigation");
let warnedOnceNotValidView = false;
function useViews({
onChange,
onViewChange,
openTo,
view: inView,
views,
autoFocus,
focusedView: inFocusedView,
onFocusedViewChange,
getStepNavigation
}) {
if (process.env.NODE_ENV !== 'production') {
if (!warnedOnceNotValidView) {
if (inView != null && !views.includes(inView)) {
console.warn(`MUI X: \`view="${inView}"\` is not a valid prop.`, `It must be an element of \`views=["${views.join('", "')}"]\`.`);
warnedOnceNotValidView = true;
}
if (inView == null && openTo != null && !views.includes(openTo)) {
console.warn(`MUI X: \`openTo="${openTo}"\` is not a valid prop.`, `It must be an element of \`views=["${views.join('", "')}"]\`.`);
warnedOnceNotValidView = true;
}
}
}
const previousOpenTo = React.useRef(openTo);
const previousViews = React.useRef(views);
const defaultView = React.useRef(views.includes(openTo) ? openTo : views[0]);
const [view, setView] = (0, _useControlled.default)({
name: 'useViews',
state: 'view',
controlled: inView,
default: defaultView.current
});
const defaultFocusedView = React.useRef(autoFocus ? view : null);
const [focusedView, setFocusedView] = (0, _useControlled.default)({
name: 'useViews',
state: 'focusedView',
controlled: inFocusedView,
default: defaultFocusedView.current
});
const stepNavigation = getStepNavigation ? getStepNavigation({
setView,
view,
defaultView: defaultView.current,
views
}) : _createStepNavigation.DEFAULT_STEP_NAVIGATION;
React.useEffect(() => {
// Update the current view when `openTo` or `views` props change
if (previousOpenTo.current && previousOpenTo.current !== openTo || previousViews.current && previousViews.current.some(previousView => !views.includes(previousView))) {
setView(views.includes(openTo) ? openTo : views[0]);
previousViews.current = views;
previousOpenTo.current = openTo;
}
}, [openTo, setView, view, views]);
const viewIndex = views.indexOf(view);
const previousView = views[viewIndex - 1] ?? null;
const nextView = views[viewIndex + 1] ?? null;
const handleFocusedViewChange = (0, _useEventCallback.default)((viewToFocus, hasFocus) => {
if (hasFocus) {
// Focus event
setFocusedView(viewToFocus);
} else {
// Blur event
setFocusedView(prevFocusedView => viewToFocus === prevFocusedView ? null : prevFocusedView // If false the blur is due to view switching
);
}
onFocusedViewChange?.(viewToFocus, hasFocus);
});
const handleChangeView = (0, _useEventCallback.default)(newView => {
// always keep the focused view in sync
handleFocusedViewChange(newView, true);
if (newView === view) {
return;
}
setView(newView);
if (onViewChange) {
onViewChange(newView);
}
});
const goToNextView = (0, _useEventCallback.default)(() => {
if (nextView) {
handleChangeView(nextView);
}
});
const setValueAndGoToNextView = (0, _useEventCallback.default)((value, currentViewSelectionState, selectedView) => {
const isSelectionFinishedOnCurrentView = currentViewSelectionState === 'finish';
const hasMoreViews = selectedView ?
// handles case like `DateTimePicker`, where a view might return a `finish` selection state
// but when it's not the final view given all `views` -> overall selection state should be `partial`.
views.indexOf(selectedView) < views.length - 1 : Boolean(nextView);
const globalSelectionState = isSelectionFinishedOnCurrentView && hasMoreViews ? 'partial' : currentViewSelectionState;
onChange(value, globalSelectionState, selectedView);
// The selected view can be different from the active view,
// This can happen if multiple views are displayed, like in `DesktopDateTimePicker` or `MultiSectionDigitalClock`.
let currentView = null;
if (selectedView != null && selectedView !== view) {
currentView = selectedView;
} else if (isSelectionFinishedOnCurrentView) {
currentView = view;
}
if (currentView == null) {
return;
}
const viewToNavigateTo = views[views.indexOf(currentView) + 1];
if (viewToNavigateTo == null || !stepNavigation.areViewsInSameStep(currentView, viewToNavigateTo)) {
return;
}
handleChangeView(viewToNavigateTo);
});
return (0, _extends2.default)({}, stepNavigation, {
view,
setView: handleChangeView,
focusedView,
setFocusedView: handleFocusedViewChange,
nextView,
previousView,
// Always return up-to-date default view instead of the initial one (i.e. defaultView.current)
defaultView: views.includes(openTo) ? openTo : views[0],
goToNextView,
setValueAndGoToNextView
});
}