@mui/x-date-pickers 6.0.3 → 6.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (155) hide show
  1. package/AdapterDateFnsJalali/index.js +68 -0
  2. package/AdapterLuxon/index.js +12 -3
  3. package/CHANGELOG.md +61 -0
  4. package/DateCalendar/DateCalendar.types.d.ts +1 -1
  5. package/DateCalendar/PickersCalendarHeader.d.ts +1 -1
  6. package/DateCalendar/index.d.ts +0 -1
  7. package/DateField/DateField.types.d.ts +2 -2
  8. package/DatePicker/DatePickerToolbar.d.ts +1 -1
  9. package/DatePicker/shared.d.ts +2 -2
  10. package/DateTimeField/DateTimeField.types.d.ts +2 -2
  11. package/DateTimePicker/DateTimePickerTabs.d.ts +1 -1
  12. package/DateTimePicker/DateTimePickerToolbar.d.ts +1 -1
  13. package/DateTimePicker/shared.d.ts +1 -2
  14. package/DesktopDatePicker/DesktopDatePicker.types.d.ts +1 -1
  15. package/DesktopDateTimePicker/DesktopDateTimePicker.types.d.ts +1 -1
  16. package/DesktopTimePicker/DesktopTimePicker.types.d.ts +1 -1
  17. package/MobileDatePicker/MobileDatePicker.types.d.ts +1 -1
  18. package/MobileDateTimePicker/MobileDateTimePicker.types.d.ts +1 -1
  19. package/MobileTimePicker/MobileTimePicker.types.d.ts +1 -1
  20. package/PickersLayout/PickersLayout.d.ts +1 -1
  21. package/PickersLayout/PickersLayout.types.d.ts +1 -1
  22. package/PickersLayout/index.d.ts +1 -1
  23. package/PickersLayout/usePickerLayout.d.ts +1 -1
  24. package/README.md +1 -1
  25. package/StaticDatePicker/StaticDatePicker.types.d.ts +2 -1
  26. package/StaticDateTimePicker/StaticDateTimePicker.types.d.ts +2 -1
  27. package/StaticTimePicker/StaticTimePicker.types.d.ts +2 -1
  28. package/TimeClock/Clock.d.ts +1 -1
  29. package/TimeClock/ClockPointer.d.ts +1 -1
  30. package/TimeClock/TimeClock.types.d.ts +1 -1
  31. package/TimeClock/index.d.ts +0 -1
  32. package/TimeField/TimeField.types.d.ts +2 -2
  33. package/TimePicker/TimePickerToolbar.d.ts +1 -1
  34. package/TimePicker/shared.d.ts +1 -2
  35. package/dateViewRenderers/dateViewRenderers.d.ts +1 -1
  36. package/index.js +1 -1
  37. package/internals/components/PickersToolbar.d.ts +1 -1
  38. package/internals/demo/DemoContainer.js +15 -7
  39. package/internals/hooks/useDesktopPicker/useDesktopPicker.d.ts +1 -1
  40. package/internals/hooks/useDesktopPicker/useDesktopPicker.types.d.ts +4 -5
  41. package/internals/hooks/useField/index.d.ts +1 -1
  42. package/internals/hooks/useField/useField.d.ts +2 -1
  43. package/internals/hooks/useField/useField.js +9 -7
  44. package/internals/hooks/useField/useField.types.d.ts +24 -104
  45. package/internals/hooks/useField/useField.utils.d.ts +7 -5
  46. package/internals/hooks/useField/useField.utils.js +68 -50
  47. package/internals/hooks/useField/useFieldCharacterEditing.d.ts +2 -1
  48. package/internals/hooks/useField/useFieldState.d.ts +3 -1
  49. package/internals/hooks/useField/useFieldState.js +88 -62
  50. package/internals/hooks/useIsLandscape.d.ts +1 -1
  51. package/internals/hooks/useMobilePicker/useMobilePicker.d.ts +1 -1
  52. package/internals/hooks/useMobilePicker/useMobilePicker.types.d.ts +4 -5
  53. package/internals/hooks/usePicker/usePicker.d.ts +1 -2
  54. package/internals/hooks/usePicker/usePicker.types.d.ts +1 -2
  55. package/internals/hooks/usePicker/usePickerLayoutProps.d.ts +1 -1
  56. package/internals/hooks/usePicker/usePickerValue.d.ts +2 -1
  57. package/internals/hooks/usePicker/usePickerViews.d.ts +1 -1
  58. package/internals/hooks/useStaticPicker/useStaticPicker.d.ts +1 -1
  59. package/internals/hooks/useStaticPicker/useStaticPicker.types.d.ts +1 -2
  60. package/internals/hooks/useViews.d.ts +1 -1
  61. package/internals/hooks/useViews.js +8 -0
  62. package/internals/hooks/validation/models.d.ts +1 -1
  63. package/internals/hooks/validation/useDateTimeValidation.d.ts +3 -3
  64. package/internals/hooks/validation/useDateValidation.d.ts +2 -2
  65. package/internals/hooks/validation/useTimeValidation.d.ts +2 -2
  66. package/internals/index.d.ts +3 -7
  67. package/internals/index.js +1 -1
  68. package/internals/models/fields.d.ts +2 -20
  69. package/internals/models/index.d.ts +0 -1
  70. package/internals/models/index.js +0 -1
  71. package/internals/models/muiPickersAdapter.d.ts +1 -10
  72. package/internals/models/props/basePickerProps.d.ts +3 -3
  73. package/internals/models/props/tabs.d.ts +1 -1
  74. package/internals/models/props/toolbar.d.ts +1 -1
  75. package/internals/utils/time-utils.d.ts +2 -1
  76. package/internals/utils/utils.d.ts +1 -1
  77. package/internals/utils/utils.js +3 -3
  78. package/internals/utils/validation.d.ts +1 -1
  79. package/internals/utils/valueManagers.d.ts +2 -4
  80. package/internals/utils/valueManagers.js +8 -8
  81. package/internals/utils/views.d.ts +1 -1
  82. package/legacy/AdapterDateFnsJalali/index.js +68 -0
  83. package/legacy/AdapterLuxon/index.js +12 -3
  84. package/legacy/index.js +1 -1
  85. package/legacy/internals/demo/DemoContainer.js +12 -6
  86. package/legacy/internals/hooks/useField/useField.js +10 -8
  87. package/legacy/internals/hooks/useField/useField.utils.js +76 -54
  88. package/legacy/internals/hooks/useField/useFieldState.js +94 -67
  89. package/legacy/internals/hooks/useViews.js +10 -0
  90. package/legacy/internals/index.js +1 -1
  91. package/legacy/internals/models/index.js +0 -1
  92. package/legacy/internals/utils/utils.js +3 -3
  93. package/legacy/internals/utils/valueManagers.js +10 -12
  94. package/legacy/locales/daDK.js +91 -0
  95. package/legacy/locales/nbNO.js +12 -10
  96. package/legacy/locales/svSE.js +12 -10
  97. package/legacy/models/index.js +3 -1
  98. package/locales/daDK.d.ts +51 -0
  99. package/locales/daDK.js +59 -0
  100. package/locales/nbNO.js +8 -10
  101. package/locales/nlNL.d.ts +4 -4
  102. package/locales/svSE.js +8 -10
  103. package/locales/utils/pickersLocaleTextApi.d.ts +2 -1
  104. package/models/fields.d.ts +117 -0
  105. package/models/fields.js +1 -0
  106. package/models/index.d.ts +3 -6
  107. package/models/index.js +3 -1
  108. package/models/validation.d.ts +8 -0
  109. package/models/validation.js +1 -0
  110. package/models/views.js +1 -0
  111. package/modern/AdapterDateFnsJalali/index.js +68 -0
  112. package/modern/AdapterLuxon/index.js +12 -3
  113. package/modern/index.js +1 -1
  114. package/modern/internals/demo/DemoContainer.js +15 -7
  115. package/modern/internals/hooks/useField/useField.js +8 -6
  116. package/modern/internals/hooks/useField/useField.utils.js +68 -50
  117. package/modern/internals/hooks/useField/useFieldState.js +88 -62
  118. package/modern/internals/hooks/useViews.js +8 -0
  119. package/modern/internals/index.js +1 -1
  120. package/modern/internals/models/index.js +0 -1
  121. package/modern/internals/utils/utils.js +3 -3
  122. package/modern/internals/utils/valueManagers.js +8 -8
  123. package/modern/locales/daDK.js +56 -0
  124. package/modern/locales/nbNO.js +8 -10
  125. package/modern/locales/svSE.js +8 -10
  126. package/modern/models/fields.js +1 -0
  127. package/modern/models/index.js +3 -1
  128. package/modern/models/validation.js +1 -0
  129. package/modern/models/views.js +1 -0
  130. package/node/AdapterDateFnsJalali/index.js +68 -0
  131. package/node/AdapterLuxon/index.js +12 -3
  132. package/node/index.js +1 -1
  133. package/node/internals/demo/DemoContainer.js +15 -7
  134. package/node/internals/hooks/useField/useField.js +8 -6
  135. package/node/internals/hooks/useField/useField.utils.js +71 -52
  136. package/node/internals/hooks/useField/useFieldState.js +87 -61
  137. package/node/internals/hooks/useViews.js +8 -0
  138. package/node/internals/index.js +0 -6
  139. package/node/internals/models/index.js +0 -11
  140. package/node/internals/utils/utils.js +3 -3
  141. package/node/internals/utils/valueManagers.js +7 -7
  142. package/node/locales/daDK.js +62 -0
  143. package/node/locales/nbNO.js +8 -10
  144. package/node/locales/svSE.js +8 -10
  145. package/node/models/index.js +33 -0
  146. package/node/models/validation.js +5 -0
  147. package/node/models/views.js +5 -0
  148. package/package.json +1 -1
  149. package/themeAugmentation/props.d.ts +1 -1
  150. package/timeViewRenderers/timeViewRenderers.d.ts +1 -1
  151. /package/{internals/models/views.js → legacy/models/fields.js} +0 -0
  152. /package/legacy/{internals/models/views.js → models/validation.js} +0 -0
  153. /package/{modern/internals → legacy}/models/views.js +0 -0
  154. /package/{internals/models → models}/views.d.ts +0 -0
  155. /package/node/{internals/models/views.js → models/fields.js} +0 -0
@@ -147,32 +147,31 @@ export const adjustSectionValue = (utils, section, keyCode, sectionsValueBoundar
147
147
  }
148
148
  return adjustLetterSection();
149
149
  };
150
- const getSectionVisibleValue = (section, willBeRenderedInInput) => {
151
- const value = section.value || section.placeholder;
150
+ export const getSectionVisibleValue = (section, target) => {
151
+ let value = section.value || section.placeholder;
152
152
 
153
153
  // In the input, we add an empty character at the end of each section without leading zeros.
154
154
  // This makes sure that `onChange` will always be fired.
155
155
  // Otherwise, when your input value equals `1/dd/yyyy` (format `M/DD/YYYY` on DayJs),
156
156
  // If you press `1`, on the first section, the new value is also `1/dd/yyyy`,
157
157
  // So the browser will not fire the input `onChange`.
158
- // Adding the ltr mark is not a problem because it's only for digit (which are always ltr)
159
- // The \u2068 and \u2069 are cleaned, but not the \u200e to notice that an update with same digit occurs
160
- if (willBeRenderedInInput && section.contentType === 'digit' && !section.hasLeadingZeros && value.length === 1) {
161
- return `\u2068${value}\u200e\u2069`;
158
+ const shouldAddInvisibleSpace = ['input-rtl', 'input-ltr'].includes(target) && section.contentType === 'digit' && !section.hasLeadingZeros && value.length === 1;
159
+ if (shouldAddInvisibleSpace) {
160
+ value = `${value}\u200e`;
162
161
  }
163
- if (willBeRenderedInInput) {
164
- return `\u2068${value}\u2069`;
162
+ if (target === 'input-rtl') {
163
+ value = `\u2068${value}\u2069`;
165
164
  }
166
165
  return value;
167
166
  };
168
167
  export const cleanString = dirtyString => dirtyString.replace(/[\u2066\u2067\u2068\u2069]/g, '');
169
- export const addPositionPropertiesToSections = sections => {
168
+ export const addPositionPropertiesToSections = (sections, isRTL) => {
170
169
  let position = 0;
171
- let positionInInput = 1;
170
+ let positionInInput = isRTL ? 1 : 0;
172
171
  const newSections = [];
173
172
  for (let i = 0; i < sections.length; i += 1) {
174
173
  const section = sections[i];
175
- const renderedValue = getSectionVisibleValue(section, true);
174
+ const renderedValue = getSectionVisibleValue(section, isRTL ? 'input-rtl' : 'input-ltr');
176
175
  const sectionStr = `${section.startSeparator}${renderedValue}${section.endSeparator}`;
177
176
  const sectionLength = cleanString(sectionStr).length;
178
177
  const sectionLengthInInput = sectionStr.length;
@@ -317,10 +316,6 @@ export const splitFormatIntoSections = (utils, localeText, format, date) => {
317
316
  if (token === '') {
318
317
  return null;
319
318
  }
320
- const expandedToken = utils.expandFormat(token);
321
- if (expandedToken !== token) {
322
- return expandedToken;
323
- }
324
319
  const sectionConfig = getDateSectionConfigFromFormatToken(utils, token);
325
320
  const sectionValue = date == null || !utils.isValid(date) ? '' : utils.formatByString(date, token);
326
321
  const hasLeadingZeros = doesSectionHaveLeadingZeros(utils, sectionConfig.contentType, sectionConfig.type, token);
@@ -335,39 +330,50 @@ export const splitFormatIntoSections = (utils, localeText, format, date) => {
335
330
  }));
336
331
  return null;
337
332
  };
338
- const splitFormat = token => {
339
- const escapedParts = getEscapedPartsFromFormat(utils, token);
340
- let currentTokenValue = '';
341
- for (let i = 0; i < token.length; i += 1) {
342
- const escapedPartOfCurrentChar = escapedParts.find(escapeIndex => escapeIndex.start <= i && escapeIndex.end >= i);
343
- const char = token[i];
344
- const isEscapedChar = escapedPartOfCurrentChar != null;
345
- if (!isEscapedChar && char.match(/([A-Za-z]+)/)) {
346
- currentTokenValue += char;
347
- } else {
348
- // If we are on the opening or closing character of an escaped part of the format,
349
- // Then we ignore this character.
350
- const isEscapeBoundary = isEscapedChar && escapedPartOfCurrentChar?.start === i || escapedPartOfCurrentChar?.end === i;
351
- if (!isEscapeBoundary) {
352
- const expandedToken = commitToken(currentTokenValue);
353
- if (expandedToken != null) {
354
- splitFormat(expandedToken);
355
- }
356
- currentTokenValue = '';
357
- if (sections.length === 0) {
358
- startSeparator += char;
359
- } else {
360
- sections[sections.length - 1].endSeparator += char;
361
- }
333
+
334
+ // Expand the provided format
335
+ let formatExpansionOverflow = 10;
336
+ let prevFormat = format;
337
+ let nextFormat = utils.expandFormat(format);
338
+ while (nextFormat !== prevFormat) {
339
+ prevFormat = nextFormat;
340
+ nextFormat = utils.expandFormat(prevFormat);
341
+ formatExpansionOverflow -= 1;
342
+ if (formatExpansionOverflow < 0) {
343
+ throw new Error('MUI: The format expansion seems to be enter in an infinite loop. Please open an issue with the format passed to the picker component');
344
+ }
345
+ }
346
+ const expandedFormat = nextFormat;
347
+
348
+ // Get start/end indexes of escaped sections
349
+ const escapedParts = getEscapedPartsFromFormat(utils, expandedFormat);
350
+
351
+ // This RegExp test if the beginning of a string correspond to a supported token
352
+ const isTokenStartRegExp = new RegExp(`^(${Object.keys(utils.formatTokenMap).join('|')})`);
353
+ let currentTokenValue = '';
354
+ for (let i = 0; i < expandedFormat.length; i += 1) {
355
+ const escapedPartOfCurrentChar = escapedParts.find(escapeIndex => escapeIndex.start <= i && escapeIndex.end >= i);
356
+ const char = expandedFormat[i];
357
+ const isEscapedChar = escapedPartOfCurrentChar != null;
358
+ const potentialToken = `${currentTokenValue}${expandedFormat.slice(i)}`;
359
+ if (!isEscapedChar && char.match(/([A-Za-z]+)/) && isTokenStartRegExp.test(potentialToken)) {
360
+ currentTokenValue += char;
361
+ } else {
362
+ // If we are on the opening or closing character of an escaped part of the format,
363
+ // Then we ignore this character.
364
+ const isEscapeBoundary = isEscapedChar && escapedPartOfCurrentChar?.start === i || escapedPartOfCurrentChar?.end === i;
365
+ if (!isEscapeBoundary) {
366
+ commitToken(currentTokenValue);
367
+ currentTokenValue = '';
368
+ if (sections.length === 0) {
369
+ startSeparator += char;
370
+ } else {
371
+ sections[sections.length - 1].endSeparator += char;
362
372
  }
363
373
  }
364
374
  }
365
- const expandedToken = commitToken(currentTokenValue);
366
- if (expandedToken != null) {
367
- splitFormat(expandedToken);
368
- }
369
- };
370
- splitFormat(format);
375
+ }
376
+ commitToken(currentTokenValue);
371
377
  return sections.map(section => {
372
378
  const cleanSeparator = separator => {
373
379
  let cleanedSeparator = separator;
@@ -401,22 +407,26 @@ export const getDateFromDateSections = (utils, sections) => {
401
407
  const shouldSkip = shouldSkipWeekDays && section.type === 'weekDay';
402
408
  if (!shouldSkip) {
403
409
  sectionFormats.push(section.format);
404
- sectionValues.push(getSectionVisibleValue(section, false));
410
+ sectionValues.push(getSectionVisibleValue(section, 'non-input'));
405
411
  }
406
412
  }
407
413
  const formatWithoutSeparator = sectionFormats.join(' ');
408
414
  const dateWithoutSeparatorStr = sectionValues.join(' ');
409
415
  return utils.parse(dateWithoutSeparatorStr, formatWithoutSeparator);
410
416
  };
411
- export const createDateStrForInputFromSections = sections => {
412
- const formattedArray = sections.map(section => `${section.startSeparator}${getSectionVisibleValue(section, true)}${section.endSeparator}`);
417
+ export const createDateStrForInputFromSections = (sections, isRTL) => {
418
+ const formattedSections = sections.map(section => `${section.startSeparator}${getSectionVisibleValue(section, isRTL ? 'input-rtl' : 'input-ltr')}${section.endSeparator}`);
419
+ const dateStr = formattedSections.join('');
420
+ if (!isRTL) {
421
+ return dateStr;
422
+ }
413
423
 
414
424
  // \u2066: start left-to-right isolation
415
425
  // \u2067: start right-to-left isolation
416
426
  // \u2068: start first strong character isolation
417
427
  // \u2069: pop isolation
418
428
  // wrap into an isolated group such that separators can split the string in smaller ones by adding \u2069\u2068
419
- return `\u2066${formattedArray.join('')}\u2069`;
429
+ return `\u2066${dateStr}\u2069`;
420
430
  };
421
431
  export const getSectionsBoundaries = utils => {
422
432
  const today = utils.date();
@@ -583,7 +593,15 @@ export const mergeDateIntoReferenceDate = (utils, dateToTransferFrom, sections,
583
593
  return mergedDate;
584
594
  }, referenceDate);
585
595
  export const isAndroid = () => navigator.userAgent.toLowerCase().indexOf('android') > -1;
586
- export const clampDaySection = (utils, sections, sectionsValueBoundaries) => {
596
+ export const clampDaySectionIfPossible = (utils, sections, sectionsValueBoundaries) => {
597
+ // We can only clamp the day value if:
598
+ // 1. if all the sections are filled (except the week day section which can be empty)
599
+ // 2. there is a day section
600
+ const canClamp = sections.every(section => section.type === 'weekDay' || section.value !== '') && sections.some(section => section.type === 'day');
601
+ if (!canClamp) {
602
+ return null;
603
+ }
604
+
587
605
  // We try to generate a valid date representing the start of the month of the invalid date typed by the user.
588
606
  const sectionsForStartOfMonth = sections.map(section => {
589
607
  if (section.type !== 'day') {
@@ -1,12 +1,15 @@
1
1
  import _extends from "@babel/runtime/helpers/esm/extends";
2
2
  import * as React from 'react';
3
3
  import useControlled from '@mui/utils/useControlled';
4
+ import { useTheme } from '@mui/material/styles';
4
5
  import { useUtils, useLocaleText, useLocalizationContext } from '../useUtils';
5
- import { addPositionPropertiesToSections, splitFormatIntoSections, clampDaySection, mergeDateIntoReferenceDate, getSectionsBoundaries, validateSections, getDateFromDateSections } from './useField.utils';
6
+ import { addPositionPropertiesToSections, splitFormatIntoSections, clampDaySectionIfPossible, mergeDateIntoReferenceDate, getSectionsBoundaries, validateSections, getDateFromDateSections } from './useField.utils';
6
7
  export const useFieldState = params => {
7
8
  const utils = useUtils();
8
9
  const localeText = useLocaleText();
9
10
  const adapter = useLocalizationContext();
11
+ const theme = useTheme();
12
+ const isRTL = theme.direction === 'rtl';
10
13
  const {
11
14
  valueManager,
12
15
  fieldValueManager,
@@ -25,14 +28,14 @@ export const useFieldState = params => {
25
28
  const firstDefaultValue = React.useRef(defaultValue);
26
29
  const valueFromTheOutside = valueProp ?? firstDefaultValue.current ?? valueManager.emptyValue;
27
30
  const sectionsValueBoundaries = React.useMemo(() => getSectionsBoundaries(utils), [utils]);
28
- const placeholder = React.useMemo(() => fieldValueManager.getValueStrFromSections(fieldValueManager.getSectionsFromValue(utils, localeText, null, valueManager.emptyValue, format)), [fieldValueManager, format, localeText, utils, valueManager.emptyValue]);
31
+ const getSectionsFromValue = React.useCallback((value, fallbackSections = null) => fieldValueManager.getSectionsFromValue(utils, value, fallbackSections, isRTL, date => splitFormatIntoSections(utils, localeText, format, date)), [fieldValueManager, format, localeText, isRTL, utils]);
32
+ const placeholder = React.useMemo(() => fieldValueManager.getValueStrFromSections(getSectionsFromValue(valueManager.emptyValue), isRTL), [fieldValueManager, getSectionsFromValue, valueManager.emptyValue, isRTL]);
29
33
  const [state, setState] = React.useState(() => {
30
- const sections = fieldValueManager.getSectionsFromValue(utils, localeText, null, valueFromTheOutside, format);
34
+ const sections = getSectionsFromValue(valueFromTheOutside);
31
35
  validateSections(sections, valueType);
32
36
  return {
33
37
  sections,
34
38
  value: valueFromTheOutside,
35
- placeholder,
36
39
  referenceValue: fieldValueManager.updateReferenceValue(utils, valueFromTheOutside, valueManager.getTodayValue(utils)),
37
40
  tempValueStrAndroid: null
38
41
  };
@@ -78,11 +81,11 @@ export const useFieldState = params => {
78
81
  }, [selectedSections, state.sections]);
79
82
  const publishValue = ({
80
83
  value,
81
- referenceValue
82
- }, sections = state.sections) => {
83
- const newSections = fieldValueManager.getSectionsFromValue(utils, localeText, sections, value, format);
84
+ referenceValue,
85
+ sections
86
+ }) => {
84
87
  setState(prevState => _extends({}, prevState, {
85
- sections: newSections,
88
+ sections,
86
89
  value,
87
90
  referenceValue,
88
91
  tempValueStrAndroid: null
@@ -106,7 +109,7 @@ export const useFieldState = params => {
106
109
  value: newSectionValue,
107
110
  modified: true
108
111
  });
109
- return addPositionPropertiesToSections(newSections);
112
+ return addPositionPropertiesToSections(newSections, isRTL);
110
113
  };
111
114
  const clearValue = () => {
112
115
  if (valueManager.areValuesEqual(utils, state.value, valueManager.emptyValue)) {
@@ -114,8 +117,9 @@ export const useFieldState = params => {
114
117
  }
115
118
  publishValue({
116
119
  value: valueManager.emptyValue,
117
- referenceValue: state.referenceValue
118
- }, null);
120
+ referenceValue: state.referenceValue,
121
+ sections: getSectionsFromValue(valueManager.emptyValue)
122
+ });
119
123
  };
120
124
  const clearActiveSection = () => {
121
125
  if (selectedSectionIndexes == null) {
@@ -126,16 +130,17 @@ export const useFieldState = params => {
126
130
  return;
127
131
  }
128
132
  const activeDateManager = fieldValueManager.getActiveDateManager(utils, state, activeSection);
129
- const activeDateSections = fieldValueManager.getActiveDateSections(state.sections, activeSection);
130
- const nonEmptySectionCountBefore = activeDateSections.filter(section => section.value !== '').length;
133
+ const nonEmptySectionCountBefore = activeDateManager.getSections(state.sections).filter(section => section.value !== '').length;
131
134
  const isTheOnlyNonEmptySection = nonEmptySectionCountBefore === 1;
132
135
  const newSections = setSectionValue(selectedSectionIndexes.startIndex, '');
133
136
  const newActiveDate = isTheOnlyNonEmptySection ? null : utils.date(new Date(''));
134
- const newValue = activeDateManager.getNewValueFromNewActiveDate(newActiveDate);
135
- if ((newActiveDate != null && !utils.isValid(newActiveDate)) !== (activeDateManager.activeDate != null && !utils.isValid(activeDateManager.activeDate))) {
136
- publishValue(newValue, newSections);
137
+ const newValues = activeDateManager.getNewValuesFromNewActiveDate(newActiveDate);
138
+ if ((newActiveDate != null && !utils.isValid(newActiveDate)) !== (activeDateManager.date != null && !utils.isValid(activeDateManager.date))) {
139
+ publishValue(_extends({}, newValues, {
140
+ sections: newSections
141
+ }));
137
142
  } else {
138
- setState(prevState => _extends({}, prevState, newValue, {
143
+ setState(prevState => _extends({}, prevState, newValues, {
139
144
  sections: newSections,
140
145
  tempValueStrAndroid: null
141
146
  }));
@@ -154,7 +159,8 @@ export const useFieldState = params => {
154
159
  const newReferenceValue = fieldValueManager.updateReferenceValue(utils, newValue, state.referenceValue);
155
160
  publishValue({
156
161
  value: newValue,
157
- referenceValue: newReferenceValue
162
+ referenceValue: newReferenceValue,
163
+ sections: getSectionsFromValue(newValue, state.sections)
158
164
  });
159
165
  };
160
166
  const updateSectionValue = ({
@@ -162,73 +168,92 @@ export const useFieldState = params => {
162
168
  newSectionValue,
163
169
  shouldGoToNextSection
164
170
  }) => {
165
- const commit = ({
166
- values,
167
- sections,
168
- shouldPublish
169
- }) => {
170
- if (shouldGoToNextSection && selectedSectionIndexes && selectedSectionIndexes.startIndex < state.sections.length - 1) {
171
- setSelectedSections(selectedSectionIndexes.startIndex + 1);
172
- } else if (selectedSectionIndexes && selectedSectionIndexes.startIndex !== selectedSectionIndexes.endIndex) {
173
- setSelectedSections(selectedSectionIndexes.startIndex);
174
- }
175
- if (shouldPublish) {
176
- publishValue(values, sections);
177
- } else {
178
- setState(prevState => _extends({}, prevState, values, {
179
- sections: sections ?? state.sections,
180
- tempValueStrAndroid: null
181
- }));
182
- }
183
- };
171
+ /**
172
+ * 1. Decide which section should be focused
173
+ */
174
+ if (shouldGoToNextSection && selectedSectionIndexes && selectedSectionIndexes.startIndex < state.sections.length - 1) {
175
+ setSelectedSections(selectedSectionIndexes.startIndex + 1);
176
+ } else if (selectedSectionIndexes && selectedSectionIndexes.startIndex !== selectedSectionIndexes.endIndex) {
177
+ setSelectedSections(selectedSectionIndexes.startIndex);
178
+ }
179
+
180
+ /**
181
+ * 2. Try to build a valid date from the new section value
182
+ */
184
183
  const activeDateManager = fieldValueManager.getActiveDateManager(utils, state, activeSection);
185
184
  const newSections = setSectionValue(selectedSectionIndexes.startIndex, newSectionValue);
186
- const activeDateSections = fieldValueManager.getActiveDateSections(newSections, activeSection);
187
- let newActiveDate = getDateFromDateSections(utils, activeDateSections);
185
+ const newActiveDateSections = activeDateManager.getSections(newSections);
186
+ let newActiveDate = getDateFromDateSections(utils, newActiveDateSections);
187
+ let shouldRegenSections = false;
188
188
 
189
- // When all the sections are filled but the date is invalid, it can be because the month has fewer days than asked.
190
- // We can try to set the day to the maximum boundary.
191
- if (!utils.isValid(newActiveDate) && activeDateSections.every(section => section.type === 'weekDay' || section.value !== '') && activeDateSections.some(section => section.type === 'day')) {
192
- const cleanSections = clampDaySection(utils, activeDateSections, sectionsValueBoundaries);
193
- if (cleanSections != null) {
194
- newActiveDate = getDateFromDateSections(utils, cleanSections);
189
+ /**
190
+ * If the date is invalid,
191
+ * Then we can try to clamp the day section to see if that produces a valid date.
192
+ * This can be useful if the month has fewer days than the day value currently provided.
193
+ */
194
+ if (!utils.isValid(newActiveDate)) {
195
+ const clampedSections = clampDaySectionIfPossible(utils, newActiveDateSections, sectionsValueBoundaries);
196
+ if (clampedSections != null) {
197
+ shouldRegenSections = true;
198
+ newActiveDate = getDateFromDateSections(utils, clampedSections);
195
199
  }
196
200
  }
201
+ let values;
202
+ let shouldPublish;
203
+
204
+ /**
205
+ * If the new date is valid,
206
+ * Then we merge the value of the modified sections into the reference date.
207
+ * This makes sure that we don't lose some information of the initial date (like the time on a date field).
208
+ */
197
209
  if (newActiveDate != null && utils.isValid(newActiveDate)) {
198
- const mergedDate = mergeDateIntoReferenceDate(utils, newActiveDate, activeDateSections, activeDateManager.referenceActiveDate, true);
199
- return commit({
200
- values: activeDateManager.getNewValueFromNewActiveDate(mergedDate),
201
- shouldPublish: true
202
- });
210
+ const mergedDate = mergeDateIntoReferenceDate(utils, newActiveDate, newActiveDateSections, activeDateManager.referenceDate, true);
211
+ values = activeDateManager.getNewValuesFromNewActiveDate(mergedDate);
212
+ shouldPublish = true;
213
+ } else {
214
+ values = activeDateManager.getNewValuesFromNewActiveDate(newActiveDate);
215
+ shouldPublish = (newActiveDate != null && !utils.isValid(newActiveDate)) !== (activeDateManager.date != null && !utils.isValid(activeDateManager.date));
203
216
  }
204
- return commit({
205
- values: activeDateManager.getNewValueFromNewActiveDate(newActiveDate),
206
- sections: newSections,
207
- shouldPublish: (newActiveDate != null && !utils.isValid(newActiveDate)) !== (activeDateManager.activeDate != null && !utils.isValid(activeDateManager.activeDate))
208
- });
217
+
218
+ /**
219
+ * If the value has been modified (to clamp the day).
220
+ * Then we need to re-generate the sections to make sure they also have this change.
221
+ */
222
+ const sections = shouldRegenSections ? getSectionsFromValue(values.value, state.sections) : newSections;
223
+
224
+ /**
225
+ * Publish or update the internal state with the new value and sections.
226
+ */
227
+ if (shouldPublish) {
228
+ return publishValue(_extends({}, values, {
229
+ sections
230
+ }));
231
+ }
232
+ return setState(prevState => _extends({}, prevState, values, {
233
+ sections,
234
+ tempValueStrAndroid: null
235
+ }));
209
236
  };
210
237
  const setTempAndroidValueStr = tempValueStrAndroid => setState(prev => _extends({}, prev, {
211
238
  tempValueStrAndroid
212
239
  }));
213
240
  React.useEffect(() => {
214
241
  if (!valueManager.areValuesEqual(utils, state.value, valueFromTheOutside)) {
215
- const sections = fieldValueManager.getSectionsFromValue(utils, localeText, null, valueFromTheOutside, format);
216
242
  setState(prevState => _extends({}, prevState, {
217
243
  value: valueFromTheOutside,
218
244
  referenceValue: fieldValueManager.updateReferenceValue(utils, valueFromTheOutside, prevState.referenceValue),
219
- sections
245
+ sections: getSectionsFromValue(valueFromTheOutside)
220
246
  }));
221
247
  }
222
248
  }, [valueFromTheOutside]); // eslint-disable-line react-hooks/exhaustive-deps
223
249
 
224
250
  React.useEffect(() => {
225
- const sections = fieldValueManager.getSectionsFromValue(utils, localeText, null, state.value, format);
251
+ const sections = getSectionsFromValue(state.value);
226
252
  validateSections(sections, valueType);
227
253
  setState(prevState => _extends({}, prevState, {
228
- sections,
229
- placeholder
254
+ sections
230
255
  }));
231
- }, [format, utils.locale, placeholder]); // eslint-disable-line react-hooks/exhaustive-deps
256
+ }, [format, utils.locale]); // eslint-disable-line react-hooks/exhaustive-deps
232
257
 
233
258
  return {
234
259
  state,
@@ -239,6 +264,7 @@ export const useFieldState = params => {
239
264
  updateSectionValue,
240
265
  updateValueFromValueStr,
241
266
  setTempAndroidValueStr,
242
- sectionsValueBoundaries
267
+ sectionsValueBoundaries,
268
+ placeholder
243
269
  };
244
270
  };
@@ -24,6 +24,8 @@ export function useViews({
24
24
  }
25
25
  }
26
26
  }
27
+ const previousOpenTo = React.useRef(openTo);
28
+ const previousViews = React.useRef(views);
27
29
  const defaultView = React.useRef(views.includes(openTo) ? openTo : views[0]);
28
30
  const [view, setView] = useControlled({
29
31
  name: 'useViews',
@@ -38,6 +40,12 @@ export function useViews({
38
40
  controlled: inFocusedView,
39
41
  default: defaultFocusedView.current
40
42
  });
43
+ React.useEffect(() => {
44
+ // Update the current view when `openTo` or `views` props change
45
+ if (previousOpenTo.current && previousOpenTo.current !== openTo || previousViews.current && previousViews.current.some(previousView => !views.includes(previousView))) {
46
+ setView(views.includes(openTo) ? openTo : views[0]);
47
+ }
48
+ }, [openTo, setView, view, views]);
41
49
  const viewIndex = views.indexOf(view);
42
50
  const previousView = views[viewIndex - 1] ?? null;
43
51
  const nextView = views[viewIndex + 1] ?? null;
@@ -9,7 +9,7 @@ export { pickersArrowSwitcherClasses } from './components/PickersArrowSwitcher/p
9
9
  export { pickersPopperClasses } from './components/pickersPopperClasses';
10
10
  export { PickersToolbarButton } from './components/PickersToolbarButton';
11
11
  export { DAY_MARGIN, DIALOG_WIDTH } from './constants/dimensions';
12
- export { useField, createDateStrForInputFromSections, addPositionPropertiesToSections, splitFormatIntoSections } from './hooks/useField';
12
+ export { useField, createDateStrForInputFromSections, addPositionPropertiesToSections } from './hooks/useField';
13
13
  export { usePicker } from './hooks/usePicker';
14
14
  export { useStaticPicker } from './hooks/useStaticPicker';
15
15
  export { useLocalizationContext, useDefaultDates, useUtils, useLocaleText, useNow } from './hooks/useUtils';
@@ -1,3 +1,2 @@
1
- export * from './views';
2
1
  export * from './fields';
3
2
  export * from './muiPickersAdapter';
@@ -5,7 +5,7 @@ export function arrayIncludes(array, itemOrItems) {
5
5
  }
6
6
  return array.indexOf(itemOrItems) !== -1;
7
7
  }
8
- export const onSpaceOrEnter = (innerFn, onFocus) => event => {
8
+ export const onSpaceOrEnter = (innerFn, externalEvent) => event => {
9
9
  if (event.key === 'Enter' || event.key === ' ') {
10
10
  innerFn(event);
11
11
 
@@ -13,8 +13,8 @@ export const onSpaceOrEnter = (innerFn, onFocus) => event => {
13
13
  event.preventDefault();
14
14
  event.stopPropagation();
15
15
  }
16
- if (onFocus) {
17
- onFocus(event);
16
+ if (externalEvent) {
17
+ externalEvent(event);
18
18
  }
19
19
  };
20
20
  export const executeInTheNextEventLoopTick = fn => {
@@ -1,5 +1,5 @@
1
1
  import { areDatesEqual, replaceInvalidDateByNull } from './date-utils';
2
- import { addPositionPropertiesToSections, createDateStrForInputFromSections, splitFormatIntoSections } from '../hooks/useField/useField.utils';
2
+ import { addPositionPropertiesToSections, createDateStrForInputFromSections } from '../hooks/useField/useField.utils';
3
3
  export const singleItemValueManager = {
4
4
  emptyValue: null,
5
5
  getTodayValue: utils => utils.date(),
@@ -10,19 +10,19 @@ export const singleItemValueManager = {
10
10
  };
11
11
  export const singleItemFieldValueManager = {
12
12
  updateReferenceValue: (utils, value, prevReferenceValue) => value == null || !utils.isValid(value) ? prevReferenceValue : value,
13
- getSectionsFromValue: (utils, localeText, prevSections, date, format) => {
13
+ getSectionsFromValue: (utils, date, prevSections, isRTL, getSectionsFromDate) => {
14
14
  const shouldReUsePrevDateSections = !utils.isValid(date) && !!prevSections;
15
15
  if (shouldReUsePrevDateSections) {
16
16
  return prevSections;
17
17
  }
18
- return addPositionPropertiesToSections(splitFormatIntoSections(utils, localeText, format, date));
18
+ return addPositionPropertiesToSections(getSectionsFromDate(date), isRTL);
19
19
  },
20
- getValueStrFromSections: sections => createDateStrForInputFromSections(sections),
21
- getActiveDateSections: sections => sections,
20
+ getValueStrFromSections: createDateStrForInputFromSections,
22
21
  getActiveDateManager: (utils, state) => ({
23
- activeDate: state.value,
24
- referenceActiveDate: state.referenceValue,
25
- getNewValueFromNewActiveDate: newActiveDate => ({
22
+ date: state.value,
23
+ referenceDate: state.referenceValue,
24
+ getSections: sections => sections,
25
+ getNewValuesFromNewActiveDate: newActiveDate => ({
26
26
  value: newActiveDate,
27
27
  referenceValue: newActiveDate == null || !utils.isValid(newActiveDate) ? state.referenceValue : newActiveDate
28
28
  })
@@ -0,0 +1,56 @@
1
+ import { getPickersLocalization } from './utils/getPickersLocalization';
2
+
3
+ // maps TimeView to its translation
4
+ const timeViews = {
5
+ hours: 'Timer',
6
+ minutes: 'Minutter',
7
+ seconds: 'Sekunder'
8
+ };
9
+ const daDKPickers = {
10
+ // Calendar navigation
11
+ previousMonth: 'Forrige måned',
12
+ nextMonth: 'Næste måned',
13
+ // View navigation
14
+ openPreviousView: 'åben forrige visning',
15
+ openNextView: 'åben næste visning',
16
+ calendarViewSwitchingButtonAriaLabel: view => view === 'year' ? 'årsvisning er åben, skift til kalendervisning' : 'kalendervisning er åben, skift til årsvisning',
17
+ // DateRange placeholders
18
+ start: 'Start',
19
+ end: 'Slut',
20
+ // Action bar
21
+ cancelButtonLabel: 'Annuller',
22
+ clearButtonLabel: 'Ryd',
23
+ okButtonLabel: 'OK',
24
+ todayButtonLabel: 'I dag',
25
+ // Toolbar titles
26
+ datePickerToolbarTitle: 'Vælg dato',
27
+ dateTimePickerToolbarTitle: 'Vælg dato & tidspunkt',
28
+ timePickerToolbarTitle: 'Vælg tidspunkt',
29
+ dateRangePickerToolbarTitle: 'Vælg datointerval',
30
+ // Clock labels
31
+ clockLabelText: (view, time, adapter) => `Vælg ${timeViews[view] ?? view}. ${time === null ? 'Intet tidspunkt valgt' : `Valgte tidspunkt er ${adapter.format(time, 'fullTime')}`}`,
32
+ hoursClockNumberText: hours => `${hours} timer`,
33
+ minutesClockNumberText: minutes => `${minutes} minutter`,
34
+ secondsClockNumberText: seconds => `${seconds} sekunder`,
35
+ // Calendar labels
36
+ calendarWeekNumberHeaderLabel: 'Ugenummer',
37
+ calendarWeekNumberHeaderText: '#',
38
+ calendarWeekNumberAriaLabelText: weekNumber => `Uge ${weekNumber}`,
39
+ calendarWeekNumberText: weekNumber => `${weekNumber}`,
40
+ // Open picker labels
41
+ openDatePickerDialogue: (value, utils) => value !== null && utils.isValid(value) ? `Vælg dato, valgte dato er ${utils.format(value, 'fullDate')}` : 'Vælg dato',
42
+ openTimePickerDialogue: (value, utils) => value !== null && utils.isValid(value) ? `Vælg tidspunkt, valgte tidspunkt er ${utils.format(value, 'fullTime')}` : 'Vælg tidspunkt',
43
+ // Table labels
44
+ timeTableLabel: 'vælg tidspunkt',
45
+ dateTableLabel: 'vælg dato',
46
+ // Field section placeholders
47
+ fieldYearPlaceholder: params => 'Y'.repeat(params.digitAmount),
48
+ fieldMonthPlaceholder: params => params.contentType === 'letter' ? 'MMMM' : 'MM',
49
+ fieldDayPlaceholder: () => 'DD',
50
+ fieldWeekDayPlaceholder: params => params.contentType === 'letter' ? 'EEEE' : 'EE',
51
+ fieldHoursPlaceholder: () => 'hh',
52
+ fieldMinutesPlaceholder: () => 'mm',
53
+ fieldSecondsPlaceholder: () => 'ss',
54
+ fieldMeridiemPlaceholder: () => 'aa'
55
+ };
56
+ export const daDK = getPickersLocalization(daDKPickers);
@@ -16,22 +16,20 @@ const nbNOPickers = {
16
16
  okButtonLabel: 'OK',
17
17
  todayButtonLabel: 'I dag',
18
18
  // Toolbar titles
19
- // datePickerToolbarTitle: 'Select date',
20
- // dateTimePickerToolbarTitle: 'Select date & time',
21
- // timePickerToolbarTitle: 'Select time',
22
- // dateRangePickerToolbarTitle: 'Select date range',
23
-
19
+ datePickerToolbarTitle: 'Velg dato',
20
+ dateTimePickerToolbarTitle: 'Velg dato & klokkeslett',
21
+ timePickerToolbarTitle: 'Velg klokkeslett',
22
+ dateRangePickerToolbarTitle: 'Velg datoperiode',
24
23
  // Clock labels
25
24
  clockLabelText: (view, time, adapter) => `Velg ${view}. ${time === null ? 'Ingen tid valgt' : `Valgt tid er ${adapter.format(time, 'fullTime')}`}`,
26
25
  hoursClockNumberText: hours => `${hours} timer`,
27
26
  minutesClockNumberText: minutes => `${minutes} minutter`,
28
27
  secondsClockNumberText: seconds => `${seconds} sekunder`,
29
28
  // Calendar labels
30
- // calendarWeekNumberHeaderLabel: 'Week number',
31
- // calendarWeekNumberHeaderText: '#',
32
- // calendarWeekNumberAriaLabelText: weekNumber => `Week ${weekNumber}`,
33
- // calendarWeekNumberText: weekNumber => `${weekNumber}`,
34
-
29
+ calendarWeekNumberHeaderLabel: 'Ukenummer',
30
+ calendarWeekNumberHeaderText: '#',
31
+ calendarWeekNumberAriaLabelText: weekNumber => `Uke ${weekNumber}`,
32
+ calendarWeekNumberText: weekNumber => `${weekNumber}`,
35
33
  // Open picker labels
36
34
  openDatePickerDialogue: (value, utils) => value !== null && utils.isValid(value) ? `Velg dato, valgt dato er ${utils.format(value, 'fullDate')}` : 'Velg dato',
37
35
  openTimePickerDialogue: (value, utils) => value !== null && utils.isValid(value) ? `Velg tid, valgt tid er ${utils.format(value, 'fullTime')}` : 'Velg tid',