From 72e62fa40df69f12d1cd837e8b2d360ea84a21b5 Mon Sep 17 00:00:00 2001 From: Alexandre Fauquette Date: Wed, 2 Nov 2022 15:15:46 +0100 Subject: [PATCH 01/20] add dependencies --- packages/x-date-pickers/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/x-date-pickers/package.json b/packages/x-date-pickers/package.json index dcb9627b4253a..02cbf09b7eb5e 100644 --- a/packages/x-date-pickers/package.json +++ b/packages/x-date-pickers/package.json @@ -120,4 +120,4 @@ "engines": { "node": ">=12.0.0" } -} +} \ No newline at end of file From e04804459bd2f830b98dcab9a4f1794a8b5e008a Mon Sep 17 00:00:00 2001 From: Alexandre Fauquette Date: Thu, 3 Nov 2022 15:44:39 +0100 Subject: [PATCH 02/20] uninteresting modifications --- packages/x-date-pickers/package.json | 2 +- .../src/DateField/tests/editing.DateField.test.tsx | 11 ++++------- .../DateField/tests/selection.DateField.test.tsx | 14 +++++++------- .../src/TimeField/tests/editing.TimeField.test.tsx | 5 +---- 4 files changed, 13 insertions(+), 19 deletions(-) diff --git a/packages/x-date-pickers/package.json b/packages/x-date-pickers/package.json index 02cbf09b7eb5e..dcb9627b4253a 100644 --- a/packages/x-date-pickers/package.json +++ b/packages/x-date-pickers/package.json @@ -120,4 +120,4 @@ "engines": { "node": ">=12.0.0" } -} \ No newline at end of file +} diff --git a/packages/x-date-pickers/src/DateField/tests/editing.DateField.test.tsx b/packages/x-date-pickers/src/DateField/tests/editing.DateField.test.tsx index acb70b7ab9a49..4d175e90f107b 100644 --- a/packages/x-date-pickers/src/DateField/tests/editing.DateField.test.tsx +++ b/packages/x-date-pickers/src/DateField/tests/editing.DateField.test.tsx @@ -3,10 +3,7 @@ import { expect } from 'chai'; import { spy } from 'sinon'; import { Unstable_DateField as DateField, DateFieldProps } from '@mui/x-date-pickers/DateField'; import { screen, act, userEvent, fireEvent } from '@mui/monorepo/test/utils'; -import { createPickerRenderer, adapterToUse } from 'test/utils/pickers-utils'; - -const expectInputValue = (input: HTMLInputElement, expectedValue: string) => - expect(input.value.replace(/‎/g, '')).to.equal(expectedValue); +import { createPickerRenderer, adapterToUse, expectInputValue } from 'test/utils/pickers-utils'; describe(' - Editing', () => { const { render, clock } = createPickerRenderer({ @@ -126,7 +123,7 @@ describe(' - Editing', () => { key: 'ArrowDown', expectedValue: 'May 31', // To select the date and not the month - cursorPosition: 5, + cursorPosition: 9, }); }); @@ -218,7 +215,7 @@ describe(' - Editing', () => { key: 'ArrowUp', expectedValue: 'July 1', // To select the date and not the month - cursorPosition: 5, + cursorPosition: 9, }); }); @@ -646,7 +643,7 @@ describe(' - Editing', () => { />, ); const input = screen.getByRole('textbox'); - clickOnInput(input, 10); + clickOnInput(input, 11); userEvent.keyPress(input, { key: 'ArrowDown' }); expect(onChange.lastCall.firstArg).toEqualDateTime(new Date(2009, 3, 3, 3, 3, 3)); diff --git a/packages/x-date-pickers/src/DateField/tests/selection.DateField.test.tsx b/packages/x-date-pickers/src/DateField/tests/selection.DateField.test.tsx index 0c9f4fdc8e68e..e17c61220fbfc 100644 --- a/packages/x-date-pickers/src/DateField/tests/selection.DateField.test.tsx +++ b/packages/x-date-pickers/src/DateField/tests/selection.DateField.test.tsx @@ -2,7 +2,7 @@ import * as React from 'react'; import { expect } from 'chai'; import { Unstable_DateField as DateField } from '@mui/x-date-pickers/DateField'; import { screen, act, userEvent, fireEvent } from '@mui/monorepo/test/utils'; -import { createPickerRenderer } from 'test/utils/pickers-utils'; +import { createPickerRenderer, expectInputValue } from 'test/utils/pickers-utils'; const getSelectedContent = (input: HTMLInputElement) => input.value.slice(input.selectionStart ?? 0, input.selectionEnd ?? 0); @@ -34,7 +34,7 @@ describe(' - Selection', () => { input.select(); }); - expect(input.value).to.equal('MM / DD / YYYY'); + expectInputValue(input, 'MM / DD / YYYY'); expect(input.selectionStart).to.equal(0); expect(input.selectionEnd).to.equal(input.value.length); }); @@ -45,13 +45,13 @@ describe(' - Selection', () => { // Simulate a touch focus interaction on mobile act(() => { input.focus(); - input.setSelectionRange(6, 8); + input.setSelectionRange(7, 9); clock.runToLast(); }); - expect(input.value).to.equal('MM / DD / YYYY'); - expect(input.selectionStart).to.equal(5); - expect(input.selectionEnd).to.equal(7); + expectInputValue(input, 'MM / DD / YYYY'); + expect(input.selectionStart).to.equal(6); + expect(input.selectionEnd).to.equal(8); }); it('should select day on desktop', () => { @@ -59,7 +59,7 @@ describe(' - Selection', () => { const input = screen.getByRole('textbox'); clickOnInput(input, 6); - expect(input.value).to.equal('MM / DD / YYYY'); + expectInputValue(input, 'MM / DD / YYYY'); expect(getSelectedContent(input)).to.equal('DD'); }); }); diff --git a/packages/x-date-pickers/src/TimeField/tests/editing.TimeField.test.tsx b/packages/x-date-pickers/src/TimeField/tests/editing.TimeField.test.tsx index 442be218a28bf..86d7f56397f16 100644 --- a/packages/x-date-pickers/src/TimeField/tests/editing.TimeField.test.tsx +++ b/packages/x-date-pickers/src/TimeField/tests/editing.TimeField.test.tsx @@ -3,10 +3,7 @@ import { expect } from 'chai'; import { spy } from 'sinon'; import { Unstable_TimeField as TimeField, TimeFieldProps } from '@mui/x-date-pickers/TimeField'; import { screen, act, userEvent, fireEvent } from '@mui/monorepo/test/utils'; -import { createPickerRenderer, adapterToUse } from 'test/utils/pickers-utils'; - -const expectInputValue = (input: HTMLInputElement, expectedValue: string) => - expect(input.value.replace(/‎/g, '')).to.equal(expectedValue); +import { createPickerRenderer, adapterToUse, expectInputValue } from 'test/utils/pickers-utils'; describe(' - Editing', () => { const { render, clock } = createPickerRenderer({ From f7cf39d3744bcb95e0c49b5235936fb49226bb31 Mon Sep 17 00:00:00 2001 From: Alexandre Fauquette Date: Thu, 3 Nov 2022 15:52:26 +0100 Subject: [PATCH 03/20] comput the section order for keyboard navigation --- .../src/DateField/useDateField.ts | 3 ++ .../hooks/useField/useField.interfaces.ts | 6 +++ .../src/internals/hooks/useField/useField.ts | 21 +++++--- .../hooks/useField/useField.utils.ts | 51 +++++++++++++++++++ .../internals/hooks/useField/useFieldState.ts | 11 ++++ 5 files changed, 86 insertions(+), 6 deletions(-) diff --git a/packages/x-date-pickers/src/DateField/useDateField.ts b/packages/x-date-pickers/src/DateField/useDateField.ts index abf0ccc702a83..8e428af26a363 100644 --- a/packages/x-date-pickers/src/DateField/useDateField.ts +++ b/packages/x-date-pickers/src/DateField/useDateField.ts @@ -19,6 +19,7 @@ import { } from '../internals/hooks/validation/useDateValidation'; import { applyDefaultDate } from '../internals/utils/date-utils'; import { useUtils, useDefaultDates } from '../internals/hooks/useUtils'; +import { getSectionOrder } from '../internals/hooks/useField/useField.utils'; export const dateFieldValueManager: FieldValueManager = { @@ -45,6 +46,8 @@ export const dateFieldValueManager: FieldValueManager error != null, isSameError: isSameDateError, + getSectionOrder: (utils, localeText, format, isRTL) => + getSectionOrder(splitFormatIntoSections(utils, localeText, format, null), isRTL), }; const useDefaultizedDateField = ( diff --git a/packages/x-date-pickers/src/internals/hooks/useField/useField.interfaces.ts b/packages/x-date-pickers/src/internals/hooks/useField/useField.interfaces.ts index e62e1dd27990f..769ba3dc7bde4 100644 --- a/packages/x-date-pickers/src/internals/hooks/useField/useField.interfaces.ts +++ b/packages/x-date-pickers/src/internals/hooks/useField/useField.interfaces.ts @@ -238,6 +238,12 @@ export interface FieldValueManager boolean; + getSectionOrder: ( + utils: MuiPickerFieldAdapter, + localeText: PickersLocaleText, + format: string, + isRTL: boolean, + ) => any; } export interface UseFieldState { diff --git a/packages/x-date-pickers/src/internals/hooks/useField/useField.ts b/packages/x-date-pickers/src/internals/hooks/useField/useField.ts index 1b57ed603686d..2d7417d278e03 100644 --- a/packages/x-date-pickers/src/internals/hooks/useField/useField.ts +++ b/packages/x-date-pickers/src/internals/hooks/useField/useField.ts @@ -51,6 +51,7 @@ export const useField = < updateSectionValue, updateValueFromValueStr, setTempAndroidValueStr, + sectionOrder, } = useFieldState(params); const { @@ -350,11 +351,15 @@ export const useField = < event.preventDefault(); if (selectedSectionIndexes == null) { - setSelectedSections(0); + setSelectedSections(sectionOrder.startIndex); } else if (selectedSectionIndexes.startIndex !== selectedSectionIndexes.endIndex) { setSelectedSections(selectedSectionIndexes.endIndex); - } else if (selectedSectionIndexes.startIndex < state.sections.length - 1) { - setSelectedSections(selectedSectionIndexes.startIndex + 1); + } else { + const nextSectionIndex = + sectionOrder.neighbors[selectedSectionIndexes.startIndex].rightIndex; + if (nextSectionIndex !== null) { + setSelectedSections(nextSectionIndex); + } } break; } @@ -364,11 +369,15 @@ export const useField = < event.preventDefault(); if (selectedSectionIndexes == null) { - setSelectedSections(state.sections.length - 1); + setSelectedSections(sectionOrder.endIndex); } else if (selectedSectionIndexes.startIndex !== selectedSectionIndexes.endIndex) { setSelectedSections(selectedSectionIndexes.startIndex); - } else if (selectedSectionIndexes.startIndex > 0) { - setSelectedSections(selectedSectionIndexes.startIndex - 1); + } else { + const nextSectionIndex = + sectionOrder.neighbors[selectedSectionIndexes.startIndex].leftIndex; + if (nextSectionIndex !== null) { + setSelectedSections(nextSectionIndex); + } } break; } diff --git a/packages/x-date-pickers/src/internals/hooks/useField/useField.utils.ts b/packages/x-date-pickers/src/internals/hooks/useField/useField.utils.ts index 6f4858de58f04..fad8ce931e73a 100644 --- a/packages/x-date-pickers/src/internals/hooks/useField/useField.utils.ts +++ b/packages/x-date-pickers/src/internals/hooks/useField/useField.utils.ts @@ -656,3 +656,54 @@ export const clampDaySection = ( }; }); }; + +export const getSectionOrder = ( + sections: Omit[], + isRTL: boolean, +) => { + if (!isRTL) { + const neighbors = {}; + 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.parsingSeparator ?? section.separator)?.includes(' '), + ); + 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; + } + + const neighbors = {}; + 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] }; +}; diff --git a/packages/x-date-pickers/src/internals/hooks/useField/useFieldState.ts b/packages/x-date-pickers/src/internals/hooks/useField/useFieldState.ts index fbb646efce602..d1e117273acb5 100644 --- a/packages/x-date-pickers/src/internals/hooks/useField/useFieldState.ts +++ b/packages/x-date-pickers/src/internals/hooks/useField/useFieldState.ts @@ -1,4 +1,5 @@ import * as React from 'react'; +import { useTheme } from '@mui/material/styles'; import useControlled from '@mui/utils/useControlled'; import { MuiPickerFieldAdapter } from '../../models/muiPickersAdapter'; import { useUtils, useLocaleText, useLocalizationContext } from '../useUtils'; @@ -44,6 +45,8 @@ export const useFieldState = < const utils = useUtils() as MuiPickerFieldAdapter; const localeText = useLocaleText(); const adapter = useLocalizationContext(); + const theme = useTheme(); + const isRTL = theme.direction === 'rtl'; const { valueManager, @@ -65,6 +68,13 @@ export const useFieldState = < const valueFromTheOutside = valueProp ?? firstDefaultValue.current ?? valueManager.emptyValue; const boundaries = React.useMemo(() => getSectionBoundaries(utils), [utils]); + const [sectionOrder, setSectionOrder] = React.useState(() => + fieldValueManager.getSectionOrder(utils, localeText, format, isRTL), + ); + React.useEffect(() => { + setSectionOrder(fieldValueManager.getSectionOrder(utils, localeText, format, isRTL)); + }, [fieldValueManager, format, isRTL, localeText, utils]); + const [state, setState] = React.useState>(() => { const sections = fieldValueManager.getSectionsFromValue( utils, @@ -324,5 +334,6 @@ export const useFieldState = < updateSectionValue, updateValueFromValueStr, setTempAndroidValueStr, + sectionOrder, }; }; From 59f3c64f7d2cc03b6c82933d4769ff8a22795536 Mon Sep 17 00:00:00 2001 From: Alexandre Fauquette Date: Thu, 3 Nov 2022 15:53:19 +0100 Subject: [PATCH 04/20] isolate elements --- .../hooks/useField/useField.interfaces.ts | 2 + .../src/internals/hooks/useField/useField.ts | 15 ++-- .../hooks/useField/useField.utils.ts | 78 ++++++++++++++----- test/utils/pickers-utils.tsx | 2 +- 4 files changed, 69 insertions(+), 28 deletions(-) diff --git a/packages/x-date-pickers/src/internals/hooks/useField/useField.interfaces.ts b/packages/x-date-pickers/src/internals/hooks/useField/useField.interfaces.ts index 769ba3dc7bde4..a33b5908e048a 100644 --- a/packages/x-date-pickers/src/internals/hooks/useField/useField.interfaces.ts +++ b/packages/x-date-pickers/src/internals/hooks/useField/useField.interfaces.ts @@ -80,6 +80,8 @@ export type UseFieldResponse = O export interface FieldSection { start: number; end: number; + startInInput: number; + endInInput: number; value: string; placeholder: string; /** diff --git a/packages/x-date-pickers/src/internals/hooks/useField/useField.ts b/packages/x-date-pickers/src/internals/hooks/useField/useField.ts index 2d7417d278e03..144964567f85d 100644 --- a/packages/x-date-pickers/src/internals/hooks/useField/useField.ts +++ b/packages/x-date-pickers/src/internals/hooks/useField/useField.ts @@ -69,7 +69,7 @@ export const useField = < const syncSelectionFromDOM = () => { const nextSectionIndex = state.sections.findIndex( - (section) => section.start > (inputRef.current!.selectionStart ?? 0), + (section) => section.startInInput > (inputRef.current!.selectionStart ?? 0), ); const sectionIndex = nextSectionIndex === -1 ? state.sections.length - 1 : nextSectionIndex - 1; setSelectedSections(sectionIndex); @@ -146,12 +146,15 @@ export const useField = < updateValueFromValueStr(pastedValue); }); + const cleanString = (dirtyString: string) => + dirtyString.replace(/\u2066|\u2067|\u2068|\u2069/g, ''); + const handleInputChange = useEventCallback((event: React.ChangeEvent) => { if (readOnly) { return; } - const valueStr = event.target.value; + const valueStr = cleanString(event.target.value); // If no section is selected, we just try to parse the new value // This line is mostly triggered by imperative code / application tests. @@ -160,7 +163,7 @@ export const useField = < return; } - const prevValueStr = fieldValueManager.getValueStrFromSections(state.sections); + const prevValueStr = cleanString(fieldValueManager.getValueStrFromSections(state.sections)); let startOfDiffIndex = -1; let endOfDiffIndex = -1; @@ -193,7 +196,7 @@ export const useField = < valueStr.length - prevValueStr.length + activeSection.end - - (activeSection.separator?.length ?? 0); + cleanString(activeSection.separator || '').length; const keyPressed = valueStr.slice(activeSection.start, activeSectionEndRelativeToNewValue); if (isAndroid() && keyPressed.length === 0) { @@ -450,8 +453,8 @@ export const useField = < const firstSelectedSection = state.sections[selectedSectionIndexes.startIndex]; const lastSelectedSection = state.sections[selectedSectionIndexes.endIndex]; updateSelectionRangeIfChanged( - firstSelectedSection.start, - lastSelectedSection.start + getSectionVisibleValue(lastSelectedSection, true).length, + firstSelectedSection.startInInput, + lastSelectedSection.startInInput + getSectionVisibleValue(lastSelectedSection, true).length, ); }); diff --git a/packages/x-date-pickers/src/internals/hooks/useField/useField.utils.ts b/packages/x-date-pickers/src/internals/hooks/useField/useField.utils.ts index fad8ce931e73a..39f855c38e5d6 100644 --- a/packages/x-date-pickers/src/internals/hooks/useField/useField.utils.ts +++ b/packages/x-date-pickers/src/internals/hooks/useField/useField.utils.ts @@ -234,7 +234,7 @@ export const adjustInvalidDateSectionValue = , + section: Omit, willBeRenderedInInput: boolean, ) => { const value = section.value || section.placeholder; @@ -244,26 +244,46 @@ export const getSectionVisibleValue = ( // 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`. + // Adding the ltr mark is not a problem because it's only for digit (which are always ltr) + // if (willBeRenderedInInput && section.contentType === 'digit' && !section.hasTrailingZeroes) { + // return `${value}\u200e`; + // } + if (willBeRenderedInInput && section.contentType === 'digit' && !section.hasTrailingZeroes) { - return `${value}‎`; + return `\u2068${value}\u2069`; } - return value; }; export const addPositionPropertiesToSections = ( - sections: Omit[], + sections: Omit[], ): TSection[] => { let position = 0; + let positionInInput = 0; const newSections: TSection[] = []; for (let i = 0; i < sections.length; i += 1) { const section = sections[i]; const end = - position + getSectionVisibleValue(section, true).length + (section.separator?.length ?? 0); + position + + getSectionVisibleValue(section, true).length + + (section.separator || '').replace(/\u2066|\u2067|\u2068|\u2069/g, '').length; + + const endInInput = + positionInInput + + getSectionVisibleValue(section, true).length + + (section.separator?.length ?? 0) + + (i === 0 || i === sections.length - 1 ? 1 : 0); // Take into account start and end isolation characters - newSections.push({ ...section, start: position, end } as TSection); + newSections.push({ + ...section, + start: position, + end, + startInInput: positionInInput, + endInInput, + } as TSection); position = end; + positionInInput = endInInput; } return newSections; @@ -321,7 +341,7 @@ export const splitFormatIntoSections = ( date: TDate | null, ) => { let currentTokenValue = ''; - const sections: Omit[] = []; + const sections: Omit[] = []; const expandedFormat = utils.expandFormat(format); const commitCurrentToken = () => { @@ -364,6 +384,13 @@ export const splitFormatIntoSections = ( return sections.map((section) => { if (section.separator !== '/') { + if (section.separator !== null && section.separator.includes(' ')) { + return { + ...section, + separator: section.separator.replace(' ', '\u2069 \u2066'), + parsingSeparator: section.separator, + }; + } return section; } return { @@ -377,22 +404,31 @@ export const splitFormatIntoSections = ( export const createDateStrFromSections = ( sections: FieldSection[], willBeRenderedInInput: boolean, -) => - sections - .map((section) => { - let sectionValueStr = getSectionVisibleValue(section, willBeRenderedInInput); +) => { + const formatedArray = sections.map((section) => { + let sectionValueStr = getSectionVisibleValue(section, willBeRenderedInInput); - const separator = willBeRenderedInInput - ? section.separator - : section.parsingSeparator ?? section.separator; + const separator = willBeRenderedInInput + ? section.separator + : section.parsingSeparator ?? section.separator; - if (separator != null) { - sectionValueStr += separator; - } + if (separator != null) { + sectionValueStr += separator; + } + + return `${sectionValueStr}`; + }); - return sectionValueStr; - }) - .join(''); + if (willBeRenderedInInput) { + // \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${formatedArray.join('')}\u2069`; + } + return formatedArray.join(''); +}; export const getMonthsMatchingQuery = ( utils: MuiPickerFieldAdapter, @@ -577,7 +613,7 @@ export const validateSections = ( export const mergeDateIntoReferenceDate = < TDate, - TSection extends Omit, + TSection extends Omit, >( utils: MuiPickerFieldAdapter, date: TDate, diff --git a/test/utils/pickers-utils.tsx b/test/utils/pickers-utils.tsx index abb72fbb74b4d..b47c1d3f9e2cd 100644 --- a/test/utils/pickers-utils.tsx +++ b/test/utils/pickers-utils.tsx @@ -247,4 +247,4 @@ export const stubMatchMedia = (matches = true) => }); export const expectInputValue = (input: HTMLInputElement, expectedValue: string) => - expect(input.value.replace(/\u200e|\u2068|\u2069/g, '')).to.equal(expectedValue); + expect(input.value.replace(/\u200e|\u2066|\u2067|\u2068|\u2069/g, '')).to.equal(expectedValue); From f546fa0411e05aabcc8401828da489313f04f25d Mon Sep 17 00:00:00 2001 From: Alexandre Fauquette Date: Thu, 3 Nov 2022 16:19:14 +0100 Subject: [PATCH 05/20] fix ts --- .../src/internal/hooks/valueManager/common.ts | 3 +++ packages/x-date-pickers/src/internals-fields/index.ts | 1 + packages/x-date-pickers/src/internals/hooks/useField/index.ts | 1 + 3 files changed, 5 insertions(+) diff --git a/packages/x-date-pickers-pro/src/internal/hooks/valueManager/common.ts b/packages/x-date-pickers-pro/src/internal/hooks/valueManager/common.ts index 2f1297bed6d4b..31527f6480e4f 100644 --- a/packages/x-date-pickers-pro/src/internal/hooks/valueManager/common.ts +++ b/packages/x-date-pickers-pro/src/internal/hooks/valueManager/common.ts @@ -3,6 +3,7 @@ import { splitFormatIntoSections, addPositionPropertiesToSections, createDateStrFromSections, + getSectionOrder, } from '@mui/x-date-pickers/internals-fields'; import { DateRange, DateRangeFieldSection } from '../../models/range'; import { splitDateRangeSections, removeLastSeparator } from '../../utils/date-fields-utils'; @@ -116,4 +117,6 @@ export const rangeFieldValueManager: Omit< }; }, hasError: (error) => error[0] != null || error[1] != null, + getSectionOrder: (utils, localeText, format, isRTL) => + getSectionOrder(splitFormatIntoSections(utils, localeText, format, null), isRTL), }; diff --git a/packages/x-date-pickers/src/internals-fields/index.ts b/packages/x-date-pickers/src/internals-fields/index.ts index d61e43b718bae..a9313cb946858 100644 --- a/packages/x-date-pickers/src/internals-fields/index.ts +++ b/packages/x-date-pickers/src/internals-fields/index.ts @@ -5,6 +5,7 @@ export { createDateStrFromSections, addPositionPropertiesToSections, splitFormatIntoSections, + getSectionOrder, } from '../internals/hooks/useField'; export type { UseFieldInternalProps, diff --git a/packages/x-date-pickers/src/internals/hooks/useField/index.ts b/packages/x-date-pickers/src/internals/hooks/useField/index.ts index fe2b8a45e55cf..0e9d8009b620a 100644 --- a/packages/x-date-pickers/src/internals/hooks/useField/index.ts +++ b/packages/x-date-pickers/src/internals/hooks/useField/index.ts @@ -14,4 +14,5 @@ export { splitFormatIntoSections, addPositionPropertiesToSections, createDateStrFromSections, + getSectionOrder, } from './useField.utils'; From ebff63a2c7075d1cd5e885f5a8c229e64d8c4d57 Mon Sep 17 00:00:00 2001 From: Alexandre Fauquette Date: Thu, 3 Nov 2022 17:10:02 +0100 Subject: [PATCH 06/20] keyboard fixes --- .../src/internals/hooks/useField/useField.ts | 7 +++---- .../src/internals/hooks/useField/useField.utils.ts | 13 +++++++------ 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/x-date-pickers/src/internals/hooks/useField/useField.ts b/packages/x-date-pickers/src/internals/hooks/useField/useField.ts index 144964567f85d..74d5ab9c26e49 100644 --- a/packages/x-date-pickers/src/internals/hooks/useField/useField.ts +++ b/packages/x-date-pickers/src/internals/hooks/useField/useField.ts @@ -22,6 +22,7 @@ import { applySectionValueToDate, cleanTrailingZeroInNumericSectionValue, isAndroid, + cleanString, } from './useField.utils'; import { useFieldState } from './useFieldState'; @@ -68,8 +69,9 @@ export const useField = < const focusTimeoutRef = React.useRef(undefined); const syncSelectionFromDOM = () => { + const browserStartIndex = inputRef.current!.selectionStart ?? 0; const nextSectionIndex = state.sections.findIndex( - (section) => section.startInInput > (inputRef.current!.selectionStart ?? 0), + (section) => section.startInInput > Math.max(browserStartIndex, 1), // If 0, it starts before the first invisible character ); const sectionIndex = nextSectionIndex === -1 ? state.sections.length - 1 : nextSectionIndex - 1; setSelectedSections(sectionIndex); @@ -146,9 +148,6 @@ export const useField = < updateValueFromValueStr(pastedValue); }); - const cleanString = (dirtyString: string) => - dirtyString.replace(/\u2066|\u2067|\u2068|\u2069/g, ''); - const handleInputChange = useEventCallback((event: React.ChangeEvent) => { if (readOnly) { return; diff --git a/packages/x-date-pickers/src/internals/hooks/useField/useField.utils.ts b/packages/x-date-pickers/src/internals/hooks/useField/useField.utils.ts index 39f855c38e5d6..d90d5d910eae7 100644 --- a/packages/x-date-pickers/src/internals/hooks/useField/useField.utils.ts +++ b/packages/x-date-pickers/src/internals/hooks/useField/useField.utils.ts @@ -249,31 +249,32 @@ export const getSectionVisibleValue = ( // return `${value}\u200e`; // } - if (willBeRenderedInInput && section.contentType === 'digit' && !section.hasTrailingZeroes) { + if (willBeRenderedInInput) { return `\u2068${value}\u2069`; } return value; }; +export const cleanString = (dirtyString: string) => + dirtyString.replace(/\u2066|\u2067|\u2068|\u2069/g, ''); + export const addPositionPropertiesToSections = ( sections: Omit[], ): TSection[] => { let position = 0; - let positionInInput = 0; + let positionInInput = 1; const newSections: TSection[] = []; for (let i = 0; i < sections.length; i += 1) { const section = sections[i]; const end = position + - getSectionVisibleValue(section, true).length + - (section.separator || '').replace(/\u2066|\u2067|\u2068|\u2069/g, '').length; + cleanString(`${getSectionVisibleValue(section, true)}${section.separator || ''}`).length; const endInInput = positionInInput + getSectionVisibleValue(section, true).length + - (section.separator?.length ?? 0) + - (i === 0 || i === sections.length - 1 ? 1 : 0); // Take into account start and end isolation characters + (section.separator?.length ?? 0); newSections.push({ ...section, From f5e7dd4a5d62b7a7573abc0c04a5b46521de0d30 Mon Sep 17 00:00:00 2001 From: Alexandre Fauquette Date: Fri, 4 Nov 2022 11:43:32 +0100 Subject: [PATCH 07/20] support digit update --- .../src/internals/hooks/useField/useField.utils.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/x-date-pickers/src/internals/hooks/useField/useField.utils.ts b/packages/x-date-pickers/src/internals/hooks/useField/useField.utils.ts index d90d5d910eae7..64185e950a190 100644 --- a/packages/x-date-pickers/src/internals/hooks/useField/useField.utils.ts +++ b/packages/x-date-pickers/src/internals/hooks/useField/useField.utils.ts @@ -245,9 +245,10 @@ export const getSectionVisibleValue = ( // 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`. // Adding the ltr mark is not a problem because it's only for digit (which are always ltr) - // if (willBeRenderedInInput && section.contentType === 'digit' && !section.hasTrailingZeroes) { - // return `${value}\u200e`; - // } + // The \u2068 and \u2069 are cleaned, but not the \u200e to notice that an update with same digit occures + if (willBeRenderedInInput && section.contentType === 'digit' && !section.hasTrailingZeroes) { + return `\u2068${value}\u200e\u2069`; + } if (willBeRenderedInInput) { return `\u2068${value}\u2069`; From 9b949c4a647cbf2a26b87a10de25bf70e8a92a2a Mon Sep 17 00:00:00 2001 From: Alexandre Fauquette Date: Fri, 4 Nov 2022 14:55:33 +0100 Subject: [PATCH 08/20] fix tests --- .../tests/editing.DateField.test.tsx | 22 ++++-- .../tests/selection.DateField.test.tsx | 77 ++++++++++--------- .../DesktopNextDatePicker.test.tsx | 12 ++- .../NextDatePicker/NextDatePicker.test.tsx | 13 +++- .../tests/editing.TimeField.test.tsx | 34 +++++--- test/utils/pickers-utils.tsx | 7 +- 6 files changed, 106 insertions(+), 59 deletions(-) diff --git a/packages/x-date-pickers/src/DateField/tests/editing.DateField.test.tsx b/packages/x-date-pickers/src/DateField/tests/editing.DateField.test.tsx index 4d175e90f107b..12ed05fd5d478 100644 --- a/packages/x-date-pickers/src/DateField/tests/editing.DateField.test.tsx +++ b/packages/x-date-pickers/src/DateField/tests/editing.DateField.test.tsx @@ -29,11 +29,23 @@ describe(' - Editing', () => { key, expectedValue, cursorPosition = 1, + valueToSelect, ...props - }: DateFieldProps & { key: string; expectedValue: string; cursorPosition?: number }) => { + }: DateFieldProps & { + key: string; + expectedValue: string; + cursorPosition?: number; + valueToSelect?: string; + }) => { render(); const input = screen.getByRole('textbox'); - clickOnInput(input, cursorPosition); + const clickPosition = valueToSelect ? input.value.indexOf(valueToSelect) : cursorPosition; + if (clickPosition === -1) { + throw new Error( + `Failed to find value to select "${valueToSelect}" in input value: ${input.value}`, + ); + } + clickOnInput(input, clickPosition); userEvent.keyPress(input, { key }); expectInputValue(input, expectedValue); }; @@ -123,7 +135,7 @@ describe(' - Editing', () => { key: 'ArrowDown', expectedValue: 'May 31', // To select the date and not the month - cursorPosition: 9, + valueToSelect: '1', }); }); @@ -215,7 +227,7 @@ describe(' - Editing', () => { key: 'ArrowUp', expectedValue: 'July 1', // To select the date and not the month - cursorPosition: 9, + valueToSelect: '30', }); }); @@ -643,7 +655,7 @@ describe(' - Editing', () => { />, ); const input = screen.getByRole('textbox'); - clickOnInput(input, 11); + clickOnInput(input, input.value.indexOf('2010')); userEvent.keyPress(input, { key: 'ArrowDown' }); expect(onChange.lastCall.firstArg).toEqualDateTime(new Date(2009, 3, 3, 3, 3, 3)); diff --git a/packages/x-date-pickers/src/DateField/tests/selection.DateField.test.tsx b/packages/x-date-pickers/src/DateField/tests/selection.DateField.test.tsx index e17c61220fbfc..766445f54c42b 100644 --- a/packages/x-date-pickers/src/DateField/tests/selection.DateField.test.tsx +++ b/packages/x-date-pickers/src/DateField/tests/selection.DateField.test.tsx @@ -2,22 +2,29 @@ import * as React from 'react'; import { expect } from 'chai'; import { Unstable_DateField as DateField } from '@mui/x-date-pickers/DateField'; import { screen, act, userEvent, fireEvent } from '@mui/monorepo/test/utils'; -import { createPickerRenderer, expectInputValue } from 'test/utils/pickers-utils'; - -const getSelectedContent = (input: HTMLInputElement) => - input.value.slice(input.selectionStart ?? 0, input.selectionEnd ?? 0); +import { + createPickerRenderer, + expectInputValue, + getCleanedSelectedContent, +} from 'test/utils/pickers-utils'; describe(' - Selection', () => { const { render, clock } = createPickerRenderer({ clock: 'fake' }); - const clickOnInput = (input: HTMLInputElement, cursorPosition: number) => { + const clickOnInput = (input: HTMLInputElement, position: number | string) => { + const clickPosition = typeof position === 'string' ? input.value.indexOf(position) : position; + if (clickPosition === -1) { + throw new Error( + `Failed to find value to select "${position}" in input value: ${input.value}`, + ); + } act(() => { fireEvent.mouseDown(input); if (document.activeElement !== input) { input.focus(); } fireEvent.mouseUp(input); - input.setSelectionRange(cursorPosition, cursorPosition); + input.setSelectionRange(clickPosition, clickPosition); fireEvent.click(input); clock.runToLast(); @@ -45,22 +52,22 @@ describe(' - Selection', () => { // Simulate a touch focus interaction on mobile act(() => { input.focus(); - input.setSelectionRange(7, 9); + input.setSelectionRange(9, 10); clock.runToLast(); }); expectInputValue(input, 'MM / DD / YYYY'); - expect(input.selectionStart).to.equal(6); - expect(input.selectionEnd).to.equal(8); + expect(input.selectionStart).to.equal(8); + expect(input.selectionEnd).to.equal(12); }); it('should select day on desktop', () => { render(); const input = screen.getByRole('textbox'); - clickOnInput(input, 6); + clickOnInput(input, 'DD'); expectInputValue(input, 'MM / DD / YYYY'); - expect(getSelectedContent(input)).to.equal('DD'); + expect(getCleanedSelectedContent(input)).to.equal('DD'); }); }); @@ -68,21 +75,21 @@ describe(' - Selection', () => { it('should select the clicked selection when the input is already focused', () => { render(); const input = screen.getByRole('textbox'); - clickOnInput(input, 7); - expect(getSelectedContent(input)).to.equal('DD'); + clickOnInput(input, 'DD'); + expect(getCleanedSelectedContent(input)).to.equal('DD'); - clickOnInput(input, 1); - expect(getSelectedContent(input)).to.equal('MM'); + clickOnInput(input, 'MM'); + expect(getCleanedSelectedContent(input)).to.equal('MM'); }); it('should not change the selection when clicking on the only already selected section', () => { render(); const input = screen.getByRole('textbox'); - clickOnInput(input, 7); - expect(getSelectedContent(input)).to.equal('DD'); + clickOnInput(input, 'DD'); + expect(getCleanedSelectedContent(input)).to.equal('DD'); - clickOnInput(input, 8); - expect(getSelectedContent(input)).to.equal('DD'); + clickOnInput(input, input.value.indexOf('DD') + 1); + expect(getCleanedSelectedContent(input)).to.equal('DD'); }); }); @@ -93,7 +100,7 @@ describe(' - Selection', () => { clickOnInput(input, 1); userEvent.keyPress(input, { key: 'a', ctrlKey: true }); - expect(getSelectedContent(input)).to.equal('MM / DD / YYYY'); + expect(getCleanedSelectedContent(input)).to.equal('MM / DD / YYYY'); }); }); @@ -101,19 +108,19 @@ describe(' - Selection', () => { it('should move selection to the next section when one section is selected', () => { render(); const input = screen.getByRole('textbox'); - clickOnInput(input, 7); - expect(getSelectedContent(input)).to.equal('DD'); + clickOnInput(input, 'DD'); + expect(getCleanedSelectedContent(input)).to.equal('DD'); userEvent.keyPress(input, { key: 'ArrowRight' }); - expect(getSelectedContent(input)).to.equal('YYYY'); + expect(getCleanedSelectedContent(input)).to.equal('YYYY'); }); it('should stay on the current section when the last section is selected', () => { render(); const input = screen.getByRole('textbox'); - clickOnInput(input, 11); - expect(getSelectedContent(input)).to.equal('YYYY'); + clickOnInput(input, 'YYYY'); + expect(getCleanedSelectedContent(input)).to.equal('YYYY'); userEvent.keyPress(input, { key: 'ArrowRight' }); - expect(getSelectedContent(input)).to.equal('YYYY'); + expect(getCleanedSelectedContent(input)).to.equal('YYYY'); }); it('should select the last section when all the sections are selected', () => { @@ -123,10 +130,10 @@ describe(' - Selection', () => { // Select all sections userEvent.keyPress(input, { key: 'a', ctrlKey: true }); - expect(getSelectedContent(input)).to.equal('MM / DD / YYYY'); + expect(getCleanedSelectedContent(input)).to.equal('MM / DD / YYYY'); userEvent.keyPress(input, { key: 'ArrowRight' }); - expect(getSelectedContent(input)).to.equal('YYYY'); + expect(getCleanedSelectedContent(input)).to.equal('YYYY'); }); }); @@ -134,19 +141,19 @@ describe(' - Selection', () => { it('should move selection to the previous section when one section is selected', () => { render(); const input = screen.getByRole('textbox'); - clickOnInput(input, 7); - expect(getSelectedContent(input)).to.equal('DD'); + clickOnInput(input, 'DD'); + expect(getCleanedSelectedContent(input)).to.equal('DD'); userEvent.keyPress(input, { key: 'ArrowLeft' }); - expect(getSelectedContent(input)).to.equal('MM'); + expect(getCleanedSelectedContent(input)).to.equal('MM'); }); it('should stay on the current section when the first section is selected', () => { render(); const input = screen.getByRole('textbox'); clickOnInput(input, 1); - expect(getSelectedContent(input)).to.equal('MM'); + expect(getCleanedSelectedContent(input)).to.equal('MM'); userEvent.keyPress(input, { key: 'ArrowLeft' }); - expect(getSelectedContent(input)).to.equal('MM'); + expect(getCleanedSelectedContent(input)).to.equal('MM'); }); it('should select the first section when all the sections are selected', () => { @@ -156,10 +163,10 @@ describe(' - Selection', () => { // Select all sections userEvent.keyPress(input, { key: 'a', ctrlKey: true }); - expect(getSelectedContent(input)).to.equal('MM / DD / YYYY'); + expect(getCleanedSelectedContent(input)).to.equal('MM / DD / YYYY'); userEvent.keyPress(input, { key: 'ArrowLeft' }); - expect(getSelectedContent(input)).to.equal('MM'); + expect(getCleanedSelectedContent(input)).to.equal('MM'); }); }); }); diff --git a/packages/x-date-pickers/src/DesktopNextDatePicker/DesktopNextDatePicker.test.tsx b/packages/x-date-pickers/src/DesktopNextDatePicker/DesktopNextDatePicker.test.tsx index 3c2da9def0e82..6c97284b6df3f 100644 --- a/packages/x-date-pickers/src/DesktopNextDatePicker/DesktopNextDatePicker.test.tsx +++ b/packages/x-date-pickers/src/DesktopNextDatePicker/DesktopNextDatePicker.test.tsx @@ -6,7 +6,12 @@ import { inputBaseClasses } from '@mui/material/InputBase'; import SvgIcon, { SvgIconProps } from '@mui/material/SvgIcon'; import { fireEvent, screen, userEvent } from '@mui/monorepo/test/utils'; import { Unstable_DesktopNextDatePicker as DesktopNextDatePicker } from '@mui/x-date-pickers/DesktopNextDatePicker'; -import { createPickerRenderer, adapterToUse, openPicker } from 'test/utils/pickers-utils'; +import { + createPickerRenderer, + adapterToUse, + openPicker, + expectInputValue, +} from 'test/utils/pickers-utils'; import describeValidation from '@mui/x-date-pickers/tests/describeValidation'; describe('', () => { @@ -89,14 +94,15 @@ describe('', () => { const handleChange = spy(); render(); + const input = screen.getByRole('textbox'); - fireEvent.change(screen.getByRole('textbox'), { + fireEvent.change(input, { target: { value: '10/11/2018', }, }); - expect(screen.getByRole('textbox')).to.have.value('10 / 11 / 2018'); + expectInputValue(input, '10 / 11 / 2018'); expect(handleChange.callCount).to.equal(1); }); diff --git a/packages/x-date-pickers/src/NextDatePicker/NextDatePicker.test.tsx b/packages/x-date-pickers/src/NextDatePicker/NextDatePicker.test.tsx index 2a0ff08e8a0f6..10ff8bb196643 100644 --- a/packages/x-date-pickers/src/NextDatePicker/NextDatePicker.test.tsx +++ b/packages/x-date-pickers/src/NextDatePicker/NextDatePicker.test.tsx @@ -2,7 +2,12 @@ import * as React from 'react'; import { expect } from 'chai'; import { Unstable_NextDatePicker as NextDatePicker } from '@mui/x-date-pickers/NextDatePicker'; import { fireEvent, screen } from '@mui/monorepo/test/utils/createRenderer'; -import { createPickerRenderer, openPicker, stubMatchMedia } from 'test/utils/pickers-utils'; +import { + createPickerRenderer, + expectInputValue, + openPicker, + stubMatchMedia, +} from 'test/utils/pickers-utils'; import describeValidation from '@mui/x-date-pickers/tests/describeValidation'; const isJSDOM = /jsdom/.test(window.navigator.userAgent); @@ -29,10 +34,10 @@ describe('', () => { describe('rendering', () => { it('should handle controlled `onChange` in desktop mode', () => { render(); + const input: HTMLInputElement = screen.getByRole('textbox'); - fireEvent.change(screen.getByRole('textbox'), { target: { value: '02/22/2022' } }); - - expect(screen.getByDisplayValue('02 / 22 / 2022')).not.to.equal(null); + fireEvent.change(input, { target: { value: '02/22/2022' } }); + expectInputValue(input, '02 / 22 / 2022'); }); it('should render in mobile mode when `useMediaQuery` returns `false`', () => { diff --git a/packages/x-date-pickers/src/TimeField/tests/editing.TimeField.test.tsx b/packages/x-date-pickers/src/TimeField/tests/editing.TimeField.test.tsx index 86d7f56397f16..5d34415ed5850 100644 --- a/packages/x-date-pickers/src/TimeField/tests/editing.TimeField.test.tsx +++ b/packages/x-date-pickers/src/TimeField/tests/editing.TimeField.test.tsx @@ -23,11 +23,23 @@ describe(' - Editing', () => { key, expectedValue, cursorPosition = 1, + valueToSelect, ...props - }: TimeFieldProps & { key: string; expectedValue: string; cursorPosition?: number }) => { + }: TimeFieldProps & { + key: string; + expectedValue: string; + cursorPosition?: number; + valueToSelect?: string; + }) => { render(); const input = screen.getByRole('textbox'); - clickOnInput(input, cursorPosition); + const clickPosition = valueToSelect ? input.value.indexOf(valueToSelect) : cursorPosition; + if (clickPosition === -1) { + throw new Error( + `Failed to find value to select "${valueToSelect}" in input value: ${input.value}`, + ); + } + clickOnInput(input, clickPosition); userEvent.keyPress(input, { key }); expectInputValue(input, expectedValue); }; @@ -100,7 +112,7 @@ describe(' - Editing', () => { defaultValue: adapterToUse.date(new Date(2022, 5, 15, 14, 0, 32)), key: 'ArrowDown', expectedValue: '13:59', - cursorPosition: 4, + valueToSelect: '00', }); }); }); @@ -128,7 +140,7 @@ describe(' - Editing', () => { format: adapterToUse.formats.fullTime12h, key: 'ArrowDown', expectedValue: 'hh:mm pm', - cursorPosition: 14, + valueToSelect: 'aa', }); }); @@ -138,7 +150,7 @@ describe(' - Editing', () => { defaultValue: new Date(2022, 5, 15, 2, 25, 32), key: 'ArrowDown', expectedValue: '02:25 pm', - cursorPosition: 14, + valueToSelect: 'am', }); }); @@ -148,7 +160,7 @@ describe(' - Editing', () => { defaultValue: new Date(2022, 5, 15, 14, 25, 32), key: 'ArrowDown', expectedValue: '02:25 am', - cursorPosition: 14, + valueToSelect: 'pm', }); }); @@ -158,7 +170,7 @@ describe(' - Editing', () => { defaultValue: adapterToUse.date(new Date(2022, 5, 15, 0, 0, 32)), key: 'ArrowDown', expectedValue: '11:59 pm', - cursorPosition: 4, + cursorPosition: 6, }); }); @@ -168,7 +180,7 @@ describe(' - Editing', () => { defaultValue: adapterToUse.date(new Date(2022, 5, 15, 12, 0, 32)), key: 'ArrowDown', expectedValue: '11:59 am', - cursorPosition: 4, + cursorPosition: 6, }); }); }); @@ -225,7 +237,7 @@ describe(' - Editing', () => { defaultValue: adapterToUse.date(new Date(2022, 5, 15, 14, 59, 32)), key: 'ArrowUp', expectedValue: '15:00', - cursorPosition: 4, + valueToSelect: '59', }); }); }); @@ -266,7 +278,7 @@ describe(' - Editing', () => { defaultValue: adapterToUse.date(new Date(2022, 5, 15, 11, 59, 32)), key: 'ArrowUp', expectedValue: '12:00 pm', - cursorPosition: 4, + valueToSelect: '59', }); }); @@ -276,7 +288,7 @@ describe(' - Editing', () => { defaultValue: adapterToUse.date(new Date(2022, 5, 15, 23, 59, 32)), key: 'ArrowUp', expectedValue: '12:00 am', - cursorPosition: 4, + valueToSelect: '59', }); }); }); diff --git a/test/utils/pickers-utils.tsx b/test/utils/pickers-utils.tsx index b47c1d3f9e2cd..c3fef42d76dfb 100644 --- a/test/utils/pickers-utils.tsx +++ b/test/utils/pickers-utils.tsx @@ -246,5 +246,10 @@ export const stubMatchMedia = (matches = true) => removeListener: () => {}, }); +export const cleanText = (text) => text.replace(/\u200e|\u2066|\u2067|\u2068|\u2069/g, ''); + +export const getCleanedSelectedContent = (input: HTMLInputElement) => + cleanText(input.value.slice(input.selectionStart ?? 0, input.selectionEnd ?? 0)); + export const expectInputValue = (input: HTMLInputElement, expectedValue: string) => - expect(input.value.replace(/\u200e|\u2066|\u2067|\u2068|\u2069/g, '')).to.equal(expectedValue); + expect(cleanText(input.value)).to.equal(expectedValue); From 945af6adfc875aa93b66e1ea4b762dd85709dbc0 Mon Sep 17 00:00:00 2001 From: Alexandre Date: Mon, 7 Nov 2022 10:19:34 +0100 Subject: [PATCH 09/20] fix ts --- .../hooks/useField/useField.interfaces.ts | 15 ++++++++++++++- .../hooks/useField/useField.utils.ts | 19 +++++++++++++------ 2 files changed, 27 insertions(+), 7 deletions(-) diff --git a/packages/x-date-pickers/src/internals/hooks/useField/useField.interfaces.ts b/packages/x-date-pickers/src/internals/hooks/useField/useField.interfaces.ts index a33b5908e048a..d5bc61bbf40d3 100644 --- a/packages/x-date-pickers/src/internals/hooks/useField/useField.interfaces.ts +++ b/packages/x-date-pickers/src/internals/hooks/useField/useField.interfaces.ts @@ -245,7 +245,7 @@ export interface FieldValueManager, format: string, isRTL: boolean, - ) => any; + ) => SectionOrdering; } export interface UseFieldState { @@ -289,3 +289,16 @@ export type AvailableAdjustKeyCode = | 'PageDown' | 'Home' | 'End'; + +export type SectionNeighbors = { + [sectionIndex: number]: { + leftIndex: number | null; + rightIndex: number | null; + }; +}; + +export type SectionOrdering = { + neighbors: SectionNeighbors; + startIndex: number; + endIndex: number; +}; diff --git a/packages/x-date-pickers/src/internals/hooks/useField/useField.utils.ts b/packages/x-date-pickers/src/internals/hooks/useField/useField.utils.ts index 64185e950a190..6595153931df1 100644 --- a/packages/x-date-pickers/src/internals/hooks/useField/useField.utils.ts +++ b/packages/x-date-pickers/src/internals/hooks/useField/useField.utils.ts @@ -1,4 +1,10 @@ -import { FieldSection, AvailableAdjustKeyCode, FieldBoundaries } from './useField.interfaces'; +import { + FieldSection, + AvailableAdjustKeyCode, + FieldBoundaries, + SectionNeighbors, + SectionOrdering, +} from './useField.interfaces'; import { MuiPickerFieldAdapter, MuiDateSectionName } from '../../models'; import { PickersLocaleText } from '../../../locales/utils/pickersLocaleTextApi'; @@ -698,9 +704,9 @@ export const clampDaySection = ( export const getSectionOrder = ( sections: Omit[], isRTL: boolean, -) => { +): SectionOrdering => { + const neighbors: SectionNeighbors = {}; if (!isRTL) { - const neighbors = {}; sections.forEach((_, index) => { const leftIndex = index === 0 ? null : index - 1; const rightIndex = index === sections.length - 1 ? null : index + 1; @@ -708,8 +714,10 @@ export const getSectionOrder = ( }); return { neighbors, startIndex: 0, endIndex: sections.length - 1 }; } - const rtl2ltr = {}; - const ltr2rtl = {}; + + type PotisionMapping = { [from: number]: number }; + const rtl2ltr: PotisionMapping = {}; + const ltr2rtl: PotisionMapping = {}; let groupedSectionsStart = 0; let groupedSectionsEnd = 0; @@ -734,7 +742,6 @@ export const getSectionOrder = ( groupedSectionsStart = groupedSectionsEnd + 1; } - const neighbors = {}; sections.forEach((_, index) => { const rtlIndex = ltr2rtl[index]; const leftIndex = rtlIndex === 0 ? null : rtl2ltr[rtlIndex - 1]; From 0fbb531aa15637e801d311b4fc93a9dfd6b72ea8 Mon Sep 17 00:00:00 2001 From: alexandre Date: Wed, 30 Nov 2022 11:51:31 +0100 Subject: [PATCH 10/20] documents types --- .../hooks/useField/useField.interfaces.ts | 40 ++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/packages/x-date-pickers/src/internals/hooks/useField/useField.interfaces.ts b/packages/x-date-pickers/src/internals/hooks/useField/useField.interfaces.ts index e96041457283b..b05aecfbc2264 100644 --- a/packages/x-date-pickers/src/internals/hooks/useField/useField.interfaces.ts +++ b/packages/x-date-pickers/src/internals/hooks/useField/useField.interfaces.ts @@ -97,9 +97,23 @@ export type UseFieldResponse = O }; export interface FieldSection { + /** + * 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. + * Consider invisible unicode characters such as \u2069 but does not include them + */ startInInput: number; + /** + * End index of the section value in the input. + * Consider invisible unicode characters such as \u2069 but does not include them + */ endInInput: number; value: string; placeholder: string; @@ -251,8 +265,17 @@ export interface FieldValueManager boolean; + /** + * Return a description of sections display order. This description is usefull in RTL mode. + * @template TDate + * @param {MuiPickersAdapter} utils The utils to manipulate the date. + * @param {PickersLocaleText} localeText The translation object. + * @param {string} format The format from which sectiosn are computed. + * @param {boolean} isRTL Is the field in right-to-left orientation. + * @returns {SectionOrdering} The description of sections order from left to right. + */ getSectionOrder: ( - utils: MuiPickerFieldAdapter, + utils: MuiPickersAdapter, localeText: PickersLocaleText, format: string, isRTL: boolean, @@ -303,13 +326,28 @@ export type AvailableAdjustKeyCode = export type SectionNeighbors = { [sectionIndex: number]: { + /** + * Index of the next sextion displayed on the left. `null` if it's the most on the left section. + */ leftIndex: number | null; + /** + * Index of the next sextion displayed on the right. `null` if it's the most on the right 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; }; From 7de245d08292dc52efb8766954c498202167d489 Mon Sep 17 00:00:00 2001 From: alexandre Date: Wed, 30 Nov 2022 11:54:52 +0100 Subject: [PATCH 11/20] fix alone selection bare --- .../src/internals/hooks/useField/useField.ts | 3 +-- .../hooks/useField/useField.utils.ts | 26 +++++++++++-------- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/packages/x-date-pickers/src/internals/hooks/useField/useField.ts b/packages/x-date-pickers/src/internals/hooks/useField/useField.ts index 356b4219df8c2..2f56195a161b2 100644 --- a/packages/x-date-pickers/src/internals/hooks/useField/useField.ts +++ b/packages/x-date-pickers/src/internals/hooks/useField/useField.ts @@ -16,7 +16,6 @@ import { } from './useField.interfaces'; import { getMonthsMatchingQuery, - getSectionVisibleValue, adjustDateSectionValue, adjustInvalidDateSectionValue, applySectionValueToDate, @@ -458,7 +457,7 @@ export const useField = < const lastSelectedSection = state.sections[selectedSectionIndexes.endIndex]; updateSelectionRangeIfChanged( firstSelectedSection.startInInput, - lastSelectedSection.startInInput + getSectionVisibleValue(lastSelectedSection, true).length, + lastSelectedSection.endInInput, ); }); diff --git a/packages/x-date-pickers/src/internals/hooks/useField/useField.utils.ts b/packages/x-date-pickers/src/internals/hooks/useField/useField.utils.ts index 56170de26f2b7..93b5b3e8727cc 100644 --- a/packages/x-date-pickers/src/internals/hooks/useField/useField.utils.ts +++ b/packages/x-date-pickers/src/internals/hooks/useField/useField.utils.ts @@ -268,7 +268,7 @@ export const getSectionVisibleValue = ( }; export const cleanString = (dirtyString: string) => - dirtyString.replace(/\u2066|\u2067|\u2068|\u2069/g, ''); + dirtyString.replaceAll(/\u2066|\u2067|\u2068|\u2069/g, ''); export const addPositionPropertiesToSections = ( sections: Omit[], @@ -279,24 +279,28 @@ export const addPositionPropertiesToSections = ( for (let i = 0; i < sections.length; i += 1) { const section = sections[i]; - const end = - position + - cleanString(`${getSectionVisibleValue(section, true)}${section.separator || ''}`).length; + const renderedValue = getSectionVisibleValue(section, true); - const endInInput = - positionInInput + - getSectionVisibleValue(section, true).length + - (section.separator?.length ?? 0); + const end = position + cleanString(`${renderedValue}${section.separator || ''}`).length; + + // The ...InInput values consider the unicode characters but do include them in their indexes + const cleanedValue = cleanString(renderedValue); + const startInInput = positionInInput + renderedValue.indexOf(cleanedValue[0]); + const endInInput = startInInput + cleanedValue.length; newSections.push({ ...section, start: position, end, - startInInput: positionInInput, + startInInput, endInInput, } as TSection); position = end; - positionInInput = endInInput; + // Move position to the end of string associated to the current section + positionInInput = + positionInInput + + getSectionVisibleValue(section, true).length + + (section.separator?.length ?? 0); } return newSections; @@ -400,7 +404,7 @@ export const splitFormatIntoSections = ( if (section.separator !== null && section.separator.includes(' ')) { return { ...section, - separator: section.separator.replace(' ', '\u2069 \u2066'), + separator: `\u2069${section.separator}\u2066`, parsingSeparator: section.separator, }; } From 50623f82eaeab17c6ab0c3c36c99cce2852a25e6 Mon Sep 17 00:00:00 2001 From: alexandre Date: Wed, 30 Nov 2022 11:56:19 +0100 Subject: [PATCH 12/20] prettier --- .../src/internals/hooks/useField/useField.interfaces.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/x-date-pickers/src/internals/hooks/useField/useField.interfaces.ts b/packages/x-date-pickers/src/internals/hooks/useField/useField.interfaces.ts index b05aecfbc2264..0974bb3512f4c 100644 --- a/packages/x-date-pickers/src/internals/hooks/useField/useField.interfaces.ts +++ b/packages/x-date-pickers/src/internals/hooks/useField/useField.interfaces.ts @@ -106,7 +106,7 @@ export interface FieldSection { */ end: number; /** - * Start index of the section value in the input. + * Start index of the section value in the input. * Consider invisible unicode characters such as \u2069 but does not include them */ startInInput: number; From b95e470a5385799508cf989e563b089e0ec4defa Mon Sep 17 00:00:00 2001 From: alexandre Date: Wed, 30 Nov 2022 13:49:00 +0100 Subject: [PATCH 13/20] fix tests --- .../src/DateField/tests/selection.DateField.test.tsx | 4 ++-- .../src/TimeField/tests/editing.TimeField.test.tsx | 4 ++-- .../src/internals/hooks/useField/useField.ts | 7 ++++--- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/packages/x-date-pickers/src/DateField/tests/selection.DateField.test.tsx b/packages/x-date-pickers/src/DateField/tests/selection.DateField.test.tsx index 766445f54c42b..0ebfdcb6aa2e0 100644 --- a/packages/x-date-pickers/src/DateField/tests/selection.DateField.test.tsx +++ b/packages/x-date-pickers/src/DateField/tests/selection.DateField.test.tsx @@ -57,8 +57,8 @@ describe(' - Selection', () => { }); expectInputValue(input, 'MM / DD / YYYY'); - expect(input.selectionStart).to.equal(8); - expect(input.selectionEnd).to.equal(12); + expect(input.selectionStart).to.equal(9); + expect(input.selectionEnd).to.equal(11); }); it('should select day on desktop', () => { diff --git a/packages/x-date-pickers/src/TimeField/tests/editing.TimeField.test.tsx b/packages/x-date-pickers/src/TimeField/tests/editing.TimeField.test.tsx index 5d34415ed5850..ed10ecaeb0a41 100644 --- a/packages/x-date-pickers/src/TimeField/tests/editing.TimeField.test.tsx +++ b/packages/x-date-pickers/src/TimeField/tests/editing.TimeField.test.tsx @@ -170,7 +170,7 @@ describe(' - Editing', () => { defaultValue: adapterToUse.date(new Date(2022, 5, 15, 0, 0, 32)), key: 'ArrowDown', expectedValue: '11:59 pm', - cursorPosition: 6, + valueToSelect: '00', }); }); @@ -180,7 +180,7 @@ describe(' - Editing', () => { defaultValue: adapterToUse.date(new Date(2022, 5, 15, 12, 0, 32)), key: 'ArrowDown', expectedValue: '11:59 am', - cursorPosition: 6, + valueToSelect: '00', }); }); }); diff --git a/packages/x-date-pickers/src/internals/hooks/useField/useField.ts b/packages/x-date-pickers/src/internals/hooks/useField/useField.ts index 2f56195a161b2..c0622bcb37e6e 100644 --- a/packages/x-date-pickers/src/internals/hooks/useField/useField.ts +++ b/packages/x-date-pickers/src/internals/hooks/useField/useField.ts @@ -70,9 +70,10 @@ export const useField = < const syncSelectionFromDOM = () => { const browserStartIndex = inputRef.current!.selectionStart ?? 0; - const nextSectionIndex = state.sections.findIndex( - (section) => section.startInInput > Math.max(browserStartIndex, 1), // If 0, it starts before the first invisible character - ); + const nextSectionIndex = + browserStartIndex <= state.sections[0].startInInput + ? 1 // Special case if browser index is in invisible cheracters at the begining. + : state.sections.findIndex((section) => section.startInInput > browserStartIndex); const sectionIndex = nextSectionIndex === -1 ? state.sections.length - 1 : nextSectionIndex - 1; setSelectedSections(sectionIndex); }; From afd01757705d278657efc055b0654eec05dd7d1d Mon Sep 17 00:00:00 2001 From: alexandre Date: Wed, 30 Nov 2022 13:54:20 +0100 Subject: [PATCH 14/20] lukas typo fix --- .../internals/hooks/useField/useField.interfaces.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/x-date-pickers/src/internals/hooks/useField/useField.interfaces.ts b/packages/x-date-pickers/src/internals/hooks/useField/useField.interfaces.ts index 0974bb3512f4c..5e8825f6ad21c 100644 --- a/packages/x-date-pickers/src/internals/hooks/useField/useField.interfaces.ts +++ b/packages/x-date-pickers/src/internals/hooks/useField/useField.interfaces.ts @@ -107,12 +107,12 @@ export interface FieldSection { end: number; /** * Start index of the section value in the input. - * Consider invisible unicode characters such as \u2069 but does not include them + * 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. - * Consider invisible unicode characters such as \u2069 but does not include them + * Takes into account invisible unicode characters such as \u2069 but does not include them */ endInInput: number; value: string; @@ -270,7 +270,7 @@ export interface FieldValueManager} utils The utils to manipulate the date. * @param {PickersLocaleText} localeText The translation object. - * @param {string} format The format from which sectiosn are computed. + * @param {string} format The format from which sections are computed. * @param {boolean} isRTL Is the field in right-to-left orientation. * @returns {SectionOrdering} The description of sections order from left to right. */ @@ -327,11 +327,11 @@ export type AvailableAdjustKeyCode = export type SectionNeighbors = { [sectionIndex: number]: { /** - * Index of the next sextion displayed on the left. `null` if it's the most on the left section. + * Index of the next section displayed on the left. `null` if it's the leftmost section. */ leftIndex: number | null; /** - * Index of the next sextion displayed on the right. `null` if it's the most on the right section. + * Index of the next section displayed on the right. `null` if it's the rightmost section. */ rightIndex: number | null; }; From d325937656dd20c20165a28f585ddf48e0258e26 Mon Sep 17 00:00:00 2001 From: alexandre Date: Wed, 30 Nov 2022 15:01:36 +0100 Subject: [PATCH 15/20] fix replaceALl --- .../src/internals/hooks/useField/useField.utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/x-date-pickers/src/internals/hooks/useField/useField.utils.ts b/packages/x-date-pickers/src/internals/hooks/useField/useField.utils.ts index 93b5b3e8727cc..ae311f8975f12 100644 --- a/packages/x-date-pickers/src/internals/hooks/useField/useField.utils.ts +++ b/packages/x-date-pickers/src/internals/hooks/useField/useField.utils.ts @@ -268,7 +268,7 @@ export const getSectionVisibleValue = ( }; export const cleanString = (dirtyString: string) => - dirtyString.replaceAll(/\u2066|\u2067|\u2068|\u2069/g, ''); + dirtyString.replace(/\u2066|\u2067|\u2068|\u2069/g, ''); export const addPositionPropertiesToSections = ( sections: Omit[], From 43b0f4347be2be3bc451e667b5911989d8cf5927 Mon Sep 17 00:00:00 2001 From: Flavien DELANGLE Date: Thu, 1 Dec 2022 15:18:20 +0100 Subject: [PATCH 16/20] [fields] Fix usage with moment-jalaali (#7) --- .../src/AdapterMomentJalaali/index.ts | 23 +++++++++++++++++++ .../hooks/useField/useField.utils.ts | 6 ++--- 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/packages/x-date-pickers/src/AdapterMomentJalaali/index.ts b/packages/x-date-pickers/src/AdapterMomentJalaali/index.ts index cead29160780f..868b50181f6e1 100644 --- a/packages/x-date-pickers/src/AdapterMomentJalaali/index.ts +++ b/packages/x-date-pickers/src/AdapterMomentJalaali/index.ts @@ -3,6 +3,8 @@ import BaseAdapterMomentJalaali from '@date-io/jalaali'; import defaultMoment, { LongDateFormatKey } from 'moment-jalaali'; import { MuiFormatTokenMap, MuiPickersAdapter } from '../internals/models'; +type Moment = defaultMoment.Moment; + // From https://momentjs.com/docs/#/displaying/format/ const formatTokenMap: MuiFormatTokenMap = { // Month @@ -89,4 +91,25 @@ export class AdapterMomentJalaali public getWeekNumber = (date: defaultMoment.Moment) => { return date.jWeek(); }; + + public addYears = (date: Moment, count: number) => { + return count < 0 + ? date.clone().subtract(Math.abs(count), 'jYear') + : date.clone().add(count, 'jYear'); + }; + + public addMonths = (date: Moment, count: number) => { + return count < 0 + ? date.clone().subtract(Math.abs(count), 'jMonth') + : date.clone().add(count, 'jMonth'); + }; + + public isValid = (value: any) => { + // We can't to `this.moment(value)` because moment-jalaali looses the invalidity information when creating a new moment object from an existing one + if (!this.moment.isMoment(value)) { + return false; + } + + return value.isValid(value); + }; } diff --git a/packages/x-date-pickers/src/internals/hooks/useField/useField.utils.ts b/packages/x-date-pickers/src/internals/hooks/useField/useField.utils.ts index ae311f8975f12..683249266b0f6 100644 --- a/packages/x-date-pickers/src/internals/hooks/useField/useField.utils.ts +++ b/packages/x-date-pickers/src/internals/hooks/useField/useField.utils.ts @@ -422,7 +422,7 @@ export const createDateStrFromSections = ( sections: FieldSection[], willBeRenderedInInput: boolean, ) => { - const formatedArray = sections.map((section) => { + const formattedArray = sections.map((section) => { let sectionValueStr = getSectionVisibleValue(section, willBeRenderedInInput); const separator = willBeRenderedInInput @@ -442,9 +442,9 @@ export const createDateStrFromSections = ( // \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${formatedArray.join('')}\u2069`; + return `\u2066${formattedArray.join('')}\u2069`; } - return formatedArray.join(''); + return formattedArray.join(''); }; export const getMonthsMatchingQuery = ( From 9615e042125d6aa00c109a4158a9091765ff5016 Mon Sep 17 00:00:00 2001 From: alexandre Date: Fri, 2 Dec 2022 13:48:09 +0100 Subject: [PATCH 17/20] fix tests --- .../src/DateField/tests/describes.DateField.test.tsx | 2 +- .../src/DateTimeField/tests/describes.DateTimeField.test.tsx | 4 ++-- .../tests/describes.DesktopNextDatePicker.test.tsx | 2 +- .../tests/describes.DesktopNextDateTimePicker.test.tsx | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/x-date-pickers/src/DateField/tests/describes.DateField.test.tsx b/packages/x-date-pickers/src/DateField/tests/describes.DateField.test.tsx index 88900de3a7358..05c22afb35983 100644 --- a/packages/x-date-pickers/src/DateField/tests/describes.DateField.test.tsx +++ b/packages/x-date-pickers/src/DateField/tests/describes.DateField.test.tsx @@ -54,7 +54,7 @@ describe(' - Describes', () => { setNewValue: (value) => { const newValue = adapterToUse.addDays(value, 1); const input = screen.getByRole('textbox'); - clickOnInput(input, 5); // Update the day + clickOnInput(input, 10); // Update the day userEvent.keyPress(input, { key: 'ArrowUp' }); return newValue; }, diff --git a/packages/x-date-pickers/src/DateTimeField/tests/describes.DateTimeField.test.tsx b/packages/x-date-pickers/src/DateTimeField/tests/describes.DateTimeField.test.tsx index 8a950c4f5c27d..ea7c012f498ec 100644 --- a/packages/x-date-pickers/src/DateTimeField/tests/describes.DateTimeField.test.tsx +++ b/packages/x-date-pickers/src/DateTimeField/tests/describes.DateTimeField.test.tsx @@ -9,7 +9,7 @@ import { } from 'test/utils/pickers-utils'; import { describeValue } from '@mui/x-date-pickers/tests/describeValue'; -describe(' - Describes', () => { +describe.only(' - Describes', () => { const { render, clock } = createPickerRenderer({ clock: 'fake' }); const { clickOnInput } = buildFieldInteractions({ clock }); @@ -43,7 +43,7 @@ describe(' - Describes', () => { setNewValue: (value) => { const newValue = adapterToUse.addDays(value, 1); const input = screen.getByRole('textbox'); - clickOnInput(input, 5); // Update the day + clickOnInput(input, 10); // Update the day userEvent.keyPress(input, { key: 'ArrowUp' }); return newValue; }, diff --git a/packages/x-date-pickers/src/DesktopNextDatePicker/tests/describes.DesktopNextDatePicker.test.tsx b/packages/x-date-pickers/src/DesktopNextDatePicker/tests/describes.DesktopNextDatePicker.test.tsx index 3fe4fc742eed2..dd4831016a340 100644 --- a/packages/x-date-pickers/src/DesktopNextDatePicker/tests/describes.DesktopNextDatePicker.test.tsx +++ b/packages/x-date-pickers/src/DesktopNextDatePicker/tests/describes.DesktopNextDatePicker.test.tsx @@ -42,7 +42,7 @@ describe(' - Describes', () => { ); } else { const input = screen.getByRole('textbox'); - clickOnInput(input, 5); // Update the day + clickOnInput(input, 10); // Update the day userEvent.keyPress(input, { key: 'ArrowUp' }); } diff --git a/packages/x-date-pickers/src/DesktopNextDateTimePicker/tests/describes.DesktopNextDateTimePicker.test.tsx b/packages/x-date-pickers/src/DesktopNextDateTimePicker/tests/describes.DesktopNextDateTimePicker.test.tsx index 1bb79e2cc77f6..29196c5f7f434 100644 --- a/packages/x-date-pickers/src/DesktopNextDateTimePicker/tests/describes.DesktopNextDateTimePicker.test.tsx +++ b/packages/x-date-pickers/src/DesktopNextDateTimePicker/tests/describes.DesktopNextDateTimePicker.test.tsx @@ -55,7 +55,7 @@ describe(' - Describes', () => { ); } else { const input = screen.getByRole('textbox'); - clickOnInput(input, 5); // Update the day + clickOnInput(input, 10); // Update the day userEvent.keyPress(input, { key: 'ArrowUp' }); } From 423c966f4d2509c277952066f1ccc8f00836bf17 Mon Sep 17 00:00:00 2001 From: alexandre Date: Fri, 2 Dec 2022 15:16:51 +0100 Subject: [PATCH 18/20] add testing caveat --- .../getting-started/getting-started.md | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/docs/data/date-pickers/getting-started/getting-started.md b/docs/data/date-pickers/getting-started/getting-started.md index 29b8d784c3f59..8eedb8436fafa 100644 --- a/docs/data/date-pickers/getting-started/getting-started.md +++ b/docs/data/date-pickers/getting-started/getting-started.md @@ -133,9 +133,29 @@ Native date (`type="date"`), time (`type="time"`) and date&time (`type="datetime ## Testing caveats +### Responsive components + :::info Some test environments (i.e. `jsdom`) do not support media query. In such cases, components will be rendered in desktop mode. To modify this behavior you can fake the `window.matchMedia`. ::: Be aware that running tests in headless browsers might not pass the default mediaQuery (`pointer: fine`). In such case you can [force pointer precision](https://github.com/microsoft/playwright/issues/7769#issuecomment-1205106311) via browser flags or preferences. + +### Field components + +:::info +To support RTL and some keyboard interactions, field components add some Unicode character that are invisible, but appears in the input value. +::: + +To add tests about a field value without having to care about those characters, you can remove the specific character before testing the equality. +Here is an example about how to do it. + +```js +// Helper removing specific characters +const cleanText = (string) => + string.replace(/\u200e|\u2066|\u2067|\u2068|\u2069/g, ''); + +// Example of a test using the helper +expect(cleanText(input.value)).to.equal('10-11-2021'); +``` From 1e7f60f6250848be7abb023f88fca3893a27e7cf Mon Sep 17 00:00:00 2001 From: alexandre Date: Fri, 2 Dec 2022 15:41:15 +0100 Subject: [PATCH 19/20] remove only --- .../src/DateTimeField/tests/describes.DateTimeField.test.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/x-date-pickers/src/DateTimeField/tests/describes.DateTimeField.test.tsx b/packages/x-date-pickers/src/DateTimeField/tests/describes.DateTimeField.test.tsx index ea7c012f498ec..ab93850f55a19 100644 --- a/packages/x-date-pickers/src/DateTimeField/tests/describes.DateTimeField.test.tsx +++ b/packages/x-date-pickers/src/DateTimeField/tests/describes.DateTimeField.test.tsx @@ -9,7 +9,7 @@ import { } from 'test/utils/pickers-utils'; import { describeValue } from '@mui/x-date-pickers/tests/describeValue'; -describe.only(' - Describes', () => { +describe(' - Describes', () => { const { render, clock } = createPickerRenderer({ clock: 'fake' }); const { clickOnInput } = buildFieldInteractions({ clock }); From 4040e64cef54d2d9e5ab4f3bc307a11d372f9f52 Mon Sep 17 00:00:00 2001 From: alexandre Date: Wed, 7 Dec 2022 14:26:07 +0100 Subject: [PATCH 20/20] add error on hijri docs --- docs/data/date-pickers/calendar-systems/calendar-systems.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/data/date-pickers/calendar-systems/calendar-systems.md b/docs/data/date-pickers/calendar-systems/calendar-systems.md index ea25175d36bde..063eaa5eba15e 100644 --- a/docs/data/date-pickers/calendar-systems/calendar-systems.md +++ b/docs/data/date-pickers/calendar-systems/calendar-systems.md @@ -21,6 +21,11 @@ The following demo shows how to use the date-fns plugin: ## Hijri +:::error +The adapter with `moment-hijri` does not support the new fields components because the date library seems buggy when parsing a month only. +If you want to help on the support of hijri calendar, please have a look at [this PR](https://github.com/xsoh/moment-hijri/issues/83). +::: + You can use the `AdapterMomentHijri` adapter, which is based on [moment-hijri](https://www.npmjs.com/package/moment-hijri): {{"demo": "AdapterHijri.js"}}