@servicetitan/anvil2 1.42.1 → 1.43.0

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 (58) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/dist/{Combobox-D2aSaDkz.js → Combobox-C7ANSGIu.js} +2 -2
  3. package/dist/{Combobox-D2aSaDkz.js.map → Combobox-C7ANSGIu.js.map} +1 -1
  4. package/dist/Combobox.js +1 -1
  5. package/dist/{DateField-D28_sa7P.js → DateField-_TqCdcYv.js} +4 -3
  6. package/dist/{DateField-D28_sa7P.js.map → DateField-_TqCdcYv.js.map} +1 -1
  7. package/dist/DateField.js +1 -1
  8. package/dist/{DateFieldRange-c4LX8hoE.js → DateFieldRange-Dk8WA52F.js} +2 -2
  9. package/dist/{DateFieldRange-c4LX8hoE.js.map → DateFieldRange-Dk8WA52F.js.map} +1 -1
  10. package/dist/DateFieldRange.js +1 -1
  11. package/dist/{DateFieldSingle-WfPOm0qk.js → DateFieldSingle-D3xD8YZk.js} +2 -2
  12. package/dist/{DateFieldSingle-WfPOm0qk.js.map → DateFieldSingle-D3xD8YZk.js.map} +1 -1
  13. package/dist/DateFieldSingle.js +1 -1
  14. package/dist/{DateFieldYearless-DC50l-ht.js → DateFieldYearless-BxkCSNk5.js} +2 -2
  15. package/dist/{DateFieldYearless-DC50l-ht.js.map → DateFieldYearless-BxkCSNk5.js.map} +1 -1
  16. package/dist/DateFieldYearless-CAUHW6Ow-EUWxJ0OY.js +1431 -0
  17. package/dist/DateFieldYearless-CAUHW6Ow-EUWxJ0OY.js.map +1 -0
  18. package/dist/DateFieldYearless.js +1 -1
  19. package/dist/{Page-KN0DLtcf.js → Page-CxB5N9dR.js} +36 -36
  20. package/dist/{Page-KN0DLtcf.js.map → Page-CxB5N9dR.js.map} +1 -1
  21. package/dist/Page.css +72 -72
  22. package/dist/Page.js +1 -1
  23. package/dist/Pagination-DU_qvFRR.js +430 -0
  24. package/dist/Pagination-DU_qvFRR.js.map +1 -0
  25. package/dist/Pagination.js +1 -429
  26. package/dist/Pagination.js.map +1 -1
  27. package/dist/{Popover-B1HaUjGI.js → Popover-BPiqdyu1.js} +2 -2
  28. package/dist/{Popover-B1HaUjGI.js.map → Popover-BPiqdyu1.js.map} +1 -1
  29. package/dist/{Popover-CU2cGVD8-FWJOuFRj.js → Popover-CU2cGVD8-Casl3vM1.js} +2 -2
  30. package/dist/{Popover-CU2cGVD8-FWJOuFRj.js.map → Popover-CU2cGVD8-Casl3vM1.js.map} +1 -1
  31. package/dist/Popover.js +1 -1
  32. package/dist/TimeField-DRHLRqN3.js +913 -0
  33. package/dist/TimeField-DRHLRqN3.js.map +1 -0
  34. package/dist/TimeField.css +10 -0
  35. package/dist/TimeField.d.ts +2 -0
  36. package/dist/TimeField.js +2 -0
  37. package/dist/TimeField.js.map +1 -0
  38. package/dist/{Toolbar-BznMJKGJ.js → Toolbar-CSWhVSUM.js} +2 -2
  39. package/dist/{Toolbar-BznMJKGJ.js.map → Toolbar-CSWhVSUM.js.map} +1 -1
  40. package/dist/Toolbar.js +1 -1
  41. package/dist/assets/icons/st/gnav_field_pro_active.svg +1 -0
  42. package/dist/assets/icons/st/gnav_field_pro_inactive.svg +1 -0
  43. package/dist/assets/icons/st.ts +2 -0
  44. package/dist/components/TimeField/TimeField.d.ts +53 -0
  45. package/dist/components/TimeField/index.d.ts +1 -0
  46. package/dist/components/index.d.ts +2 -0
  47. package/dist/event-BEJFimi3.js +6 -0
  48. package/dist/event-BEJFimi3.js.map +1 -0
  49. package/dist/index.js +10 -8
  50. package/dist/index.js.map +1 -1
  51. package/dist/usePopoverCloseDelayWorkaround-BZcjPkvT-BZcjPkvT.js +18 -0
  52. package/dist/usePopoverCloseDelayWorkaround-BZcjPkvT-BZcjPkvT.js.map +1 -0
  53. package/dist/{DateFieldYearless-DU0z2fEA-ByR2ixI5.js → usePopoverSupport-B9Lsqryr-DhZHMoNb.js} +470 -1447
  54. package/dist/usePopoverSupport-B9Lsqryr-DhZHMoNb.js.map +1 -0
  55. package/package.json +2 -2
  56. package/dist/DateFieldYearless-DU0z2fEA-ByR2ixI5.js.map +0 -1
  57. package/dist/usePopoverCloseDelayWorkaround-BhhG-xEB-hfJZaXHC.js +0 -21
  58. package/dist/usePopoverCloseDelayWorkaround-BhhG-xEB-hfJZaXHC.js.map +0 -1
@@ -1,14 +1,5 @@
1
- import { jsx, jsxs } from 'react/jsx-runtime';
2
- import { useState, useCallback, useRef, useLayoutEffect, useEffect, useMemo, forwardRef, useImperativeHandle } from 'react';
3
- import { T as TextField } from './TextField-CRTh0gL_-D2CjcYXX.js';
4
- import { u as useMergeRefs$1 } from './floating-ui.react-BFNinq1w.js';
5
- import { D as DateTime, f as Calendar } from './Calendar-DD5kmVd3-CBGTR11R.js';
6
- import { I as Icon } from './Icon-B6HmlQiR-BxQkO3X5.js';
7
- import { u as usePopoverCloseDelayWorkaround, S as SvgEvent } from './usePopoverCloseDelayWorkaround-BhhG-xEB-hfJZaXHC.js';
8
- import { d as Popover } from './Popover-CU2cGVD8-FWJOuFRj.js';
9
- import { u as useOptionallyControlledState } from './useOptionallyControlledState-DAv5LXXh-DAv5LXXh.js';
10
- import { u as useMergeRefs } from './useMergeRefs-Bde85AWI-Bde85AWI.js';
11
- import { s as supportsPopover, u as useKeyboardFocusables } from './ProgressBar-BRvB-bD4-DppwBrFg.js';
1
+ import { useState, useCallback, useRef, useLayoutEffect, useEffect } from 'react';
2
+ import { s as supportsPopover } from './ProgressBar-BRvB-bD4-DppwBrFg.js';
12
3
 
13
4
  function getContentEditableSelection(element) {
14
5
  const { anchorOffset = 0, focusOffset = 0 } = element.ownerDocument.getSelection() || {};
@@ -984,6 +975,19 @@ const DATE_SEGMENTS_MAX_VALUES = {
984
975
  const DEFAULT_MIN_DATE = new Date('0001-01-01T00:00');
985
976
  const DEFAULT_MAX_DATE = new Date('9999-12-31T23:59:59.999');
986
977
 
978
+ const DEFAULT_TIME_SEGMENT_MAX_VALUES = {
979
+ hours: 23,
980
+ minutes: 59,
981
+ seconds: 59,
982
+ milliseconds: 999,
983
+ };
984
+ const DEFAULT_TIME_SEGMENT_MIN_VALUES = {
985
+ hours: 0,
986
+ minutes: 0,
987
+ seconds: 0,
988
+ milliseconds: 0,
989
+ };
990
+
987
991
  /**
988
992
  * {@link https://unicode-table.com/en/00A0/ Non-breaking space}.
989
993
  */
@@ -1022,9 +1026,31 @@ const CHAR_MINUS = '\u2212';
1022
1026
  * is used as prolonged sounds in Japanese.
1023
1027
  */
1024
1028
  const CHAR_JP_HYPHEN = '\u30FC';
1029
+ /**
1030
+ * {@link https://symbl.cc/en/003A/ Colon}
1031
+ * is a punctuation mark that connects parts of a text logically.
1032
+ * ---
1033
+ * is also used as separator in time.
1034
+ */
1035
+ const CHAR_COLON = '\u003A';
1036
+ /**
1037
+ * {@link https://symbl.cc/en/FF1A/ Full-width colon}
1038
+ * is a full-width punctuation mark used to separate parts of a text commonly in Japanese.
1039
+ */
1040
+ const CHAR_JP_COLON = '\uFF1A';
1041
+
1042
+ const ANY_MERIDIEM_CHARACTER_RE = new RegExp(`[${CHAR_NO_BREAK_SPACE}APM]+$`, 'g');
1043
+ const ALL_MERIDIEM_CHARACTERS_RE = new RegExp(`${CHAR_NO_BREAK_SPACE}[AP]M$`, 'g');
1025
1044
 
1026
1045
  const TIME_FIXED_CHARACTERS = [':', '.'];
1027
1046
 
1047
+ const TIME_SEGMENT_VALUE_LENGTHS = {
1048
+ hours: 2,
1049
+ minutes: 2,
1050
+ seconds: 2,
1051
+ milliseconds: 3,
1052
+ };
1053
+
1028
1054
  function validateDateString({ dateString, dateModeTemplate, dateSegmentsSeparator, offset, selection: [from, to], }) {
1029
1055
  var _a, _b;
1030
1056
  const parsedDate = parseDateString(dateString, dateModeTemplate);
@@ -1078,6 +1104,8 @@ function validateDateString({ dateString, dateModeTemplate, dateSegmentsSeparato
1078
1104
  function identity(x) {
1079
1105
  return x;
1080
1106
  }
1107
+ // eslint-disable-next-line @typescript-eslint/no-empty-function
1108
+ function noop() { }
1081
1109
 
1082
1110
  /**
1083
1111
  * Copy-pasted solution from lodash
@@ -1091,6 +1119,17 @@ function escapeRegExp(str) {
1091
1119
  : str;
1092
1120
  }
1093
1121
 
1122
+ function findCommonBeginningSubstr(a, b) {
1123
+ let res = '';
1124
+ for (let i = 0; i < a.length; i++) {
1125
+ if (a[i] !== b[i]) {
1126
+ return res;
1127
+ }
1128
+ res += a[i];
1129
+ }
1130
+ return res;
1131
+ }
1132
+
1094
1133
  function isEmpty(entity) {
1095
1134
  return !entity || (typeof entity === 'object' && Object.keys(entity).length === 0);
1096
1135
  }
@@ -1113,6 +1152,15 @@ function padWithZeroesUntilValid(segmentValue, paddedMaxValue, prefixedZeroesCou
1113
1152
  return padWithZeroesUntilValid(`${valueWithoutLastChar}0`, paddedMaxValue, prefixedZeroesCount);
1114
1153
  }
1115
1154
 
1155
+ /**
1156
+ * Replace fullwidth colon with half width colon
1157
+ * @param fullWidthColon full width colon
1158
+ * @returns processed half width colon
1159
+ */
1160
+ function toHalfWidthColon(fullWidthColon) {
1161
+ return fullWidthColon.replaceAll(new RegExp(CHAR_JP_COLON, 'g'), CHAR_COLON);
1162
+ }
1163
+
1116
1164
  /**
1117
1165
  * Replace fullwidth numbers with half width number
1118
1166
  * @param fullWidthNumber full width number
@@ -1122,6 +1170,22 @@ function toHalfWidthNumber(fullWidthNumber) {
1122
1170
  return fullWidthNumber.replaceAll(/[0-9]/g, (s) => String.fromCharCode(s.charCodeAt(0) - 0xfee0));
1123
1171
  }
1124
1172
 
1173
+ /**
1174
+ * Convert full width colon (:) to half width one (:)
1175
+ */
1176
+ function createColonConvertPreprocessor() {
1177
+ return ({ elementState, data }) => {
1178
+ const { value, selection } = elementState;
1179
+ return {
1180
+ elementState: {
1181
+ selection,
1182
+ value: toHalfWidthColon(value),
1183
+ },
1184
+ data: toHalfWidthColon(data),
1185
+ };
1186
+ };
1187
+ }
1188
+
1125
1189
  function createDateSegmentsZeroPaddingPostprocessor({ dateModeTemplate, dateSegmentSeparator, splitFn, uniteFn, }) {
1126
1190
  return ({ value, selection }) => {
1127
1191
  var _a;
@@ -1205,7 +1269,184 @@ function createFullWidthToHalfWidthPreprocessor() {
1205
1269
  };
1206
1270
  }
1207
1271
 
1208
- new RegExp(`[${TIME_FIXED_CHARACTERS.map(escapeRegExp).join('')}]$`);
1272
+ function createTimeMaskExpression(mode) {
1273
+ return Array.from(mode.replace(' AA', ''))
1274
+ .map((char) => (TIME_FIXED_CHARACTERS.includes(char) ? char : /\d/))
1275
+ .concat(mode.includes('AA') ? [CHAR_NO_BREAK_SPACE, /[AP]/i, /M/i] : []);
1276
+ }
1277
+
1278
+ function padTimeSegments(timeSegments, pad) {
1279
+ return Object.fromEntries(Object.entries(timeSegments).map(([segmentName, segmentValue]) => [
1280
+ segmentName,
1281
+ pad(String(segmentValue), TIME_SEGMENT_VALUE_LENGTHS[segmentName]),
1282
+ ]));
1283
+ }
1284
+
1285
+ function padStartTimeSegments(timeSegments) {
1286
+ return padTimeSegments(timeSegments, (value, length) => value.padStart(length, '0'));
1287
+ }
1288
+
1289
+ const SEGMENT_FULL_NAME = {
1290
+ HH: 'hours',
1291
+ MM: 'minutes',
1292
+ SS: 'seconds',
1293
+ MSS: 'milliseconds',
1294
+ };
1295
+ /**
1296
+ * @param timeString can be with/without fixed characters
1297
+ */
1298
+ function parseTimeString(timeString, timeMode) {
1299
+ const onlyDigits = timeString.replaceAll(/\D+/g, '');
1300
+ let offset = 0;
1301
+ return Object.fromEntries(timeMode
1302
+ .split(/\W/)
1303
+ .filter((segmentAbbr) => SEGMENT_FULL_NAME[segmentAbbr])
1304
+ .map((segmentAbbr) => {
1305
+ const segmentValue = onlyDigits.slice(offset, offset + segmentAbbr.length);
1306
+ offset += segmentAbbr.length;
1307
+ return [SEGMENT_FULL_NAME[segmentAbbr], segmentValue];
1308
+ }));
1309
+ }
1310
+
1311
+ const LEADING_NON_DIGITS = /^\D*/;
1312
+ const TRAILING_NON_DIGITS = /\D*$/;
1313
+ function toTimeString({ hours = '', minutes = '', seconds = '', milliseconds = '', }) {
1314
+ return `${hours}:${minutes}:${seconds}.${milliseconds}`
1315
+ .replace(LEADING_NON_DIGITS, '')
1316
+ .replace(TRAILING_NON_DIGITS, '');
1317
+ }
1318
+
1319
+ const TRAILING_TIME_SEGMENT_SEPARATOR_REG = new RegExp(`[${TIME_FIXED_CHARACTERS.map(escapeRegExp).join('')}]$`);
1320
+ /**
1321
+ * Pads invalid time segment with zero to make it valid.
1322
+ * @example 00:|00 => Type 9 (too much for the first digit of minutes) => 00:09|
1323
+ * @example |19:00 => Type 2 (29 - invalid value for hours) => 2|0:00
1324
+ */
1325
+ function enrichTimeSegmentsWithZeroes({ value, selection }, { mode, timeSegmentMaxValues = DEFAULT_TIME_SEGMENT_MAX_VALUES, }) {
1326
+ const [from, to] = selection;
1327
+ const parsedTime = parseTimeString(value, mode);
1328
+ const possibleTimeSegments = Object.entries(parsedTime);
1329
+ const paddedMaxValues = padStartTimeSegments(timeSegmentMaxValues);
1330
+ const validatedTimeSegments = {};
1331
+ let paddedZeroes = 0;
1332
+ for (const [segmentName, segmentValue] of possibleTimeSegments) {
1333
+ const maxSegmentValue = paddedMaxValues[segmentName];
1334
+ const { validatedSegmentValue, prefixedZeroesCount } = padWithZeroesUntilValid(segmentValue, String(maxSegmentValue));
1335
+ paddedZeroes += prefixedZeroesCount;
1336
+ validatedTimeSegments[segmentName] = validatedSegmentValue;
1337
+ }
1338
+ const [leadingNonDigitCharacters = ''] = value.match(/^\D+(?=\d)/g) || []; // prefix
1339
+ const [trailingNonDigitCharacters = ''] = value.match(/\D+$/g) || []; // trailing segment separators / meridiem characters / postfix
1340
+ const validatedTimeString = leadingNonDigitCharacters +
1341
+ toTimeString(validatedTimeSegments) +
1342
+ trailingNonDigitCharacters;
1343
+ const addedDateSegmentSeparators = Math.max(validatedTimeString.length - value.length - paddedZeroes, 0);
1344
+ let newFrom = from + paddedZeroes + addedDateSegmentSeparators;
1345
+ let newTo = to + paddedZeroes + addedDateSegmentSeparators;
1346
+ if (newFrom === newTo &&
1347
+ paddedZeroes &&
1348
+ // if next character after cursor is time segment separator
1349
+ validatedTimeString.slice(0, newTo + 1).match(TRAILING_TIME_SEGMENT_SEPARATOR_REG)) {
1350
+ newFrom++;
1351
+ newTo++;
1352
+ }
1353
+ return {
1354
+ value: validatedTimeString,
1355
+ selection: [newFrom, newTo],
1356
+ };
1357
+ }
1358
+
1359
+ /**
1360
+ * Prevent insertion if any time segment will become invalid
1361
+ * (and even zero padding won't help with it).
1362
+ * @example 2|0:00 => Type 9 => 2|0:00
1363
+ */
1364
+ function createInvalidTimeSegmentInsertionPreprocessor({ timeMode, timeSegmentMinValues = DEFAULT_TIME_SEGMENT_MIN_VALUES, timeSegmentMaxValues = DEFAULT_TIME_SEGMENT_MAX_VALUES, parseValue = (x) => ({ timeString: x }), }) {
1365
+ const invalidCharsRegExp = new RegExp(`[^\\d${TIME_FIXED_CHARACTERS.map(escapeRegExp).join('')}]+`);
1366
+ return ({ elementState, data }, actionType) => {
1367
+ if (actionType !== 'insert') {
1368
+ return { elementState, data };
1369
+ }
1370
+ const { value, selection } = elementState;
1371
+ const [from, rawTo] = selection;
1372
+ const newCharacters = data.replace(invalidCharsRegExp, '');
1373
+ const to = rawTo + newCharacters.length; // to be conformed with `overwriteMode: replace`
1374
+ const newPossibleValue = value.slice(0, from) + newCharacters + value.slice(to);
1375
+ const { timeString, restValue = '' } = parseValue(newPossibleValue);
1376
+ const timeSegments = Object.entries(parseTimeString(timeString, timeMode));
1377
+ let offset = restValue.length;
1378
+ for (const [segmentName, stringifiedSegmentValue] of timeSegments) {
1379
+ const minSegmentValue = timeSegmentMinValues[segmentName];
1380
+ const maxSegmentValue = timeSegmentMaxValues[segmentName];
1381
+ const segmentValue = Number(stringifiedSegmentValue);
1382
+ const lastSegmentDigitIndex = offset + TIME_SEGMENT_VALUE_LENGTHS[segmentName];
1383
+ if (lastSegmentDigitIndex >= from &&
1384
+ lastSegmentDigitIndex <= to &&
1385
+ segmentValue !== clamp(segmentValue, minSegmentValue, maxSegmentValue)) {
1386
+ return { elementState, data: '' }; // prevent insertion
1387
+ }
1388
+ offset +=
1389
+ stringifiedSegmentValue.length +
1390
+ // any time segment separator
1391
+ 1;
1392
+ }
1393
+ return { elementState, data };
1394
+ };
1395
+ }
1396
+
1397
+ function createMeridiemPreprocessor(timeMode) {
1398
+ if (!timeMode.includes('AA')) {
1399
+ return identity;
1400
+ }
1401
+ const mainMeridiemCharRE = /^[AP]$/gi;
1402
+ return ({ elementState, data }) => {
1403
+ const { value, selection } = elementState;
1404
+ const newValue = value.toUpperCase();
1405
+ const newData = data.toUpperCase();
1406
+ if (newValue.match(ALL_MERIDIEM_CHARACTERS_RE) &&
1407
+ newData.match(mainMeridiemCharRE)) {
1408
+ return {
1409
+ elementState: {
1410
+ value: newValue.replaceAll(ALL_MERIDIEM_CHARACTERS_RE, ''),
1411
+ selection,
1412
+ },
1413
+ data: `${newData}M`,
1414
+ };
1415
+ }
1416
+ return { elementState: { selection, value: newValue }, data: newData };
1417
+ };
1418
+ }
1419
+ function createMeridiemPostprocessor(timeMode) {
1420
+ if (!timeMode.includes('AA')) {
1421
+ return identity;
1422
+ }
1423
+ return ({ value, selection }, initialElementState) => {
1424
+ if (!value.match(ANY_MERIDIEM_CHARACTER_RE) ||
1425
+ value.match(ALL_MERIDIEM_CHARACTERS_RE)) {
1426
+ return { value, selection };
1427
+ }
1428
+ const [from, to] = selection;
1429
+ // any meridiem character was deleted
1430
+ if (initialElementState.value.match(ALL_MERIDIEM_CHARACTERS_RE)) {
1431
+ const newValue = value.replace(ANY_MERIDIEM_CHARACTER_RE, '');
1432
+ return {
1433
+ value: newValue,
1434
+ selection: [
1435
+ Math.min(from, newValue.length),
1436
+ Math.min(to, newValue.length),
1437
+ ],
1438
+ };
1439
+ }
1440
+ const fullMeridiem = `${CHAR_NO_BREAK_SPACE}${value.includes('P') ? 'P' : 'A'}M`;
1441
+ const newValue = value.replace(ANY_MERIDIEM_CHARACTER_RE, (x) => x !== CHAR_NO_BREAK_SPACE ? fullMeridiem : x);
1442
+ return {
1443
+ value: newValue,
1444
+ selection: to >= newValue.indexOf(fullMeridiem)
1445
+ ? [newValue.length, newValue.length]
1446
+ : selection,
1447
+ };
1448
+ };
1449
+ }
1209
1450
 
1210
1451
  function raiseSegmentValueToMin(segments, fullMode) {
1211
1452
  const segmentsLength = getDateSegmentValueLength(fullMode);
@@ -1284,6 +1525,59 @@ function normalizeDatePreprocessor({ dateModeTemplate, dateSegmentsSeparator, ra
1284
1525
  };
1285
1526
  }
1286
1527
 
1528
+ function maskitoPostfixPostprocessorGenerator(postfix) {
1529
+ const postfixRE = new RegExp(`${escapeRegExp(postfix)}$`);
1530
+ return postfix
1531
+ ? ({ value, selection }, initialElementState) => {
1532
+ if (!value && !initialElementState.value.endsWith(postfix)) {
1533
+ // cases when developer wants input to be empty (programmatically)
1534
+ return { value, selection };
1535
+ }
1536
+ if (!value.endsWith(postfix) &&
1537
+ !initialElementState.value.endsWith(postfix)) {
1538
+ return { selection, value: value + postfix };
1539
+ }
1540
+ const initialValueBeforePostfix = initialElementState.value.replace(postfixRE, '');
1541
+ const postfixWasModified = initialElementState.selection[1] > initialValueBeforePostfix.length;
1542
+ const alreadyExistedValueBeforePostfix = findCommonBeginningSubstr(initialValueBeforePostfix, value);
1543
+ return {
1544
+ selection,
1545
+ value: Array.from(postfix)
1546
+ .reverse()
1547
+ .reduce((newValue, char, index) => {
1548
+ const i = newValue.length - 1 - index;
1549
+ const isInitiallyMirroredChar = alreadyExistedValueBeforePostfix[i] === char &&
1550
+ postfixWasModified;
1551
+ return newValue[i] !== char || isInitiallyMirroredChar
1552
+ ? newValue.slice(0, i + 1) + char + newValue.slice(i + 1)
1553
+ : newValue;
1554
+ }, value),
1555
+ };
1556
+ }
1557
+ : identity;
1558
+ }
1559
+
1560
+ function maskitoPrefixPostprocessorGenerator(prefix) {
1561
+ return prefix
1562
+ ? ({ value, selection }, initialElementState) => {
1563
+ if (value.startsWith(prefix) || // already valid
1564
+ (!value && !initialElementState.value.startsWith(prefix)) // cases when developer wants input to be empty
1565
+ ) {
1566
+ return { value, selection };
1567
+ }
1568
+ const [from, to] = selection;
1569
+ const prefixedValue = Array.from(prefix).reduce((modifiedValue, char, i) => modifiedValue[i] === char
1570
+ ? modifiedValue
1571
+ : modifiedValue.slice(0, i) + char + modifiedValue.slice(i), value);
1572
+ const addedCharsCount = prefixedValue.length - value.length;
1573
+ return {
1574
+ selection: [from + addedCharsCount, to + addedCharsCount],
1575
+ value: prefixedValue,
1576
+ };
1577
+ }
1578
+ : identity;
1579
+ }
1580
+
1287
1581
  function createValidDatePreprocessor({ dateModeTemplate, dateSegmentsSeparator, rangeSeparator = '', }) {
1288
1582
  return ({ elementState, data }) => {
1289
1583
  const { value, selection } = elementState;
@@ -1390,6 +1684,108 @@ function maskitoCaretGuard(guard) {
1390
1684
  });
1391
1685
  }
1392
1686
 
1687
+ function createMeridiemSteppingPlugin(meridiemStartIndex) {
1688
+ if (meridiemStartIndex < 0) {
1689
+ return noop;
1690
+ }
1691
+ return (element) => {
1692
+ const listener = (event) => {
1693
+ const caretIndex = Number(element.selectionStart);
1694
+ const value = element.value.toUpperCase();
1695
+ if ((event.key !== 'ArrowUp' && event.key !== 'ArrowDown') ||
1696
+ caretIndex < meridiemStartIndex) {
1697
+ return;
1698
+ }
1699
+ event.preventDefault();
1700
+ // eslint-disable-next-line no-nested-ternary
1701
+ const meridiemMainCharacter = value.includes('A')
1702
+ ? 'P'
1703
+ : value.includes('P') || event.key === 'ArrowUp'
1704
+ ? 'A'
1705
+ : 'P';
1706
+ const newMeridiem = `${CHAR_NO_BREAK_SPACE}${meridiemMainCharacter}M`;
1707
+ maskitoUpdateElement(element, {
1708
+ value: value.length === meridiemStartIndex
1709
+ ? value + newMeridiem
1710
+ : value.replace(ANY_MERIDIEM_CHARACTER_RE, newMeridiem),
1711
+ selection: [caretIndex, caretIndex],
1712
+ });
1713
+ };
1714
+ element.addEventListener('keydown', listener);
1715
+ return () => element.removeEventListener('keydown', listener);
1716
+ };
1717
+ }
1718
+
1719
+ function createTimeSegmentsSteppingPlugin({ step, fullMode, timeSegmentMaxValues, }) {
1720
+ const segmentsIndexes = createTimeSegmentsIndexes(fullMode);
1721
+ return step <= 0
1722
+ ? noop
1723
+ : (element) => {
1724
+ const listener = (event) => {
1725
+ var _a;
1726
+ if (event.key !== 'ArrowUp' && event.key !== 'ArrowDown') {
1727
+ return;
1728
+ }
1729
+ event.preventDefault();
1730
+ const selectionStart = (_a = element.selectionStart) !== null && _a !== void 0 ? _a : 0;
1731
+ const activeSegment = getActiveSegment({
1732
+ segmentsIndexes,
1733
+ selectionStart,
1734
+ });
1735
+ if (!activeSegment) {
1736
+ return;
1737
+ }
1738
+ const updatedValue = updateSegmentValue({
1739
+ selection: segmentsIndexes.get(activeSegment),
1740
+ value: element.value,
1741
+ toAdd: event.key === 'ArrowUp' ? step : -step,
1742
+ max: timeSegmentMaxValues[activeSegment],
1743
+ });
1744
+ maskitoUpdateElement(element, {
1745
+ value: updatedValue,
1746
+ selection: [selectionStart, selectionStart],
1747
+ });
1748
+ };
1749
+ element.addEventListener('keydown', listener);
1750
+ return () => element.removeEventListener('keydown', listener);
1751
+ };
1752
+ }
1753
+ function createTimeSegmentsIndexes(fullMode) {
1754
+ return new Map([
1755
+ ['hours', getSegmentRange(fullMode, 'HH')],
1756
+ ['milliseconds', getSegmentRange(fullMode, 'MSS')],
1757
+ ['minutes', getSegmentRange(fullMode, 'MM')],
1758
+ ['seconds', getSegmentRange(fullMode, 'SS')],
1759
+ ]);
1760
+ }
1761
+ function getSegmentRange(mode, segment) {
1762
+ const index = mode.indexOf(segment);
1763
+ return index === -1 ? [-1, -1] : [index, index + segment.length];
1764
+ }
1765
+ function getActiveSegment({ segmentsIndexes, selectionStart, }) {
1766
+ for (const [segmentName, segmentRange] of segmentsIndexes.entries()) {
1767
+ const [from, to] = segmentRange;
1768
+ if (from <= selectionStart && selectionStart <= to) {
1769
+ return segmentName;
1770
+ }
1771
+ }
1772
+ return null;
1773
+ }
1774
+ function updateSegmentValue({ selection, value, toAdd, max, }) {
1775
+ const [from, to] = selection;
1776
+ const segmentValue = Number(value.slice(from, to).padEnd(to - from, '0'));
1777
+ const newSegmentValue = mod(segmentValue + toAdd, max + 1);
1778
+ return (value.slice(0, from) +
1779
+ String(newSegmentValue).padStart(to - from, '0') +
1780
+ value.slice(to, value.length));
1781
+ }
1782
+ function mod(value, max) {
1783
+ if (value < 0) {
1784
+ value += Math.floor(Math.abs(value) / max + 1) * max;
1785
+ }
1786
+ return value % max;
1787
+ }
1788
+
1393
1789
  function maskitoWithPlaceholder(placeholder, focusedOnly = false) {
1394
1790
  let lastClearValue = '';
1395
1791
  let action = 'validation';
@@ -1652,6 +2048,66 @@ function maskitoDateRangeOptionsGenerator({ mode, min, max, minLength, maxLength
1652
2048
  ] });
1653
2049
  }
1654
2050
 
2051
+ function maskitoTimeOptionsGenerator({ mode, timeSegmentMaxValues = {}, timeSegmentMinValues = {}, step = 0, prefix = '', postfix = '', }) {
2052
+ const hasMeridiem = mode.includes('AA');
2053
+ const enrichedTimeSegmentMaxValues = Object.assign(Object.assign(Object.assign({}, DEFAULT_TIME_SEGMENT_MAX_VALUES), (hasMeridiem ? { hours: 12 } : {})), timeSegmentMaxValues);
2054
+ const enrichedTimeSegmentMinValues = Object.assign(Object.assign(Object.assign({}, DEFAULT_TIME_SEGMENT_MIN_VALUES), (hasMeridiem ? { hours: 1 } : {})), timeSegmentMinValues);
2055
+ const maskExpression = [...prefix, ...createTimeMaskExpression(mode)];
2056
+ return {
2057
+ mask: postfix
2058
+ ? ({ value }) => cutExpression(maskExpression, value).concat(...postfix)
2059
+ : maskExpression,
2060
+ preprocessors: [
2061
+ createFullWidthToHalfWidthPreprocessor(),
2062
+ createColonConvertPreprocessor(),
2063
+ createZeroPlaceholdersPreprocessor(postfix),
2064
+ createMeridiemPreprocessor(mode),
2065
+ createInvalidTimeSegmentInsertionPreprocessor({
2066
+ timeMode: mode,
2067
+ timeSegmentMinValues: enrichedTimeSegmentMinValues,
2068
+ timeSegmentMaxValues: enrichedTimeSegmentMaxValues,
2069
+ }),
2070
+ ],
2071
+ postprocessors: [
2072
+ createMeridiemPostprocessor(mode),
2073
+ (elementState) => enrichTimeSegmentsWithZeroes(elementState, {
2074
+ mode,
2075
+ timeSegmentMaxValues: enrichedTimeSegmentMaxValues,
2076
+ }),
2077
+ maskitoPrefixPostprocessorGenerator(prefix),
2078
+ maskitoPostfixPostprocessorGenerator(postfix),
2079
+ ],
2080
+ plugins: [
2081
+ createTimeSegmentsSteppingPlugin({
2082
+ fullMode: mode,
2083
+ step,
2084
+ timeSegmentMaxValues: enrichedTimeSegmentMaxValues,
2085
+ }),
2086
+ createMeridiemSteppingPlugin(mode.indexOf('AA')),
2087
+ ],
2088
+ overwriteMode: 'replace',
2089
+ };
2090
+ }
2091
+ /**
2092
+ * Without cutting, the mask expression removes postfix on the last digit deletion
2093
+ * ___
2094
+ * Case 1 (static pattern mask expression)
2095
+ * Mask expression is [/\d/, /\d/, ':', /\d/, /\d/, ' left']
2096
+ * 12:34| left => Press Backspace => 12:3|
2097
+ * Mask correctly removes postfix because it's fixed characters after not yet inserted 4th digit.
2098
+ * ___
2099
+ * Case 2 (dynamic pattern mask expression)
2100
+ * Mask expression is [/\d/, /\d/, ':', /\d/, /\d/, ' left'] & textfield contains `12:34 left`
2101
+ * 12:34| left => Press Backspace => Mask expression becomes [/\d/, /\d/, ':', /\d/, ' left'] => 12:3| left
2102
+ * Mask correctly does not remove postfix because it's trailing fixed characters
2103
+ * and all non-fixed characters were already inserted.
2104
+ */
2105
+ function cutExpression(expression, value) {
2106
+ let digitsCount = Math.min(value.replaceAll(/\D/g, '').length, expression.filter((x) => typeof x !== 'string').length) || 1;
2107
+ const afterLastDigit = expression.findIndex((x) => typeof x !== 'string' && !--digitsCount) + 1;
2108
+ return expression.slice(0, afterLastDigit);
2109
+ }
2110
+
1655
2111
  /**
1656
2112
  * React adds `_valueTracker` property to every textfield elements for its internal logic with controlled inputs.
1657
2113
  * Also, React monkey-patches `value`-setter of the native textfield elements to update state inside its `_valueTracker`.
@@ -1765,14 +2221,6 @@ const useMaskito = ({
1765
2221
  return onRefChange;
1766
2222
  };
1767
2223
 
1768
- function usePrevious(value) {
1769
- const ref = useRef();
1770
- useEffect(() => {
1771
- ref.current = value;
1772
- }, [value]);
1773
- return ref.current;
1774
- }
1775
-
1776
2224
  function usePopoverSupport() {
1777
2225
  const [popoverSupported, setPopoverSupported] = useState(
1778
2226
  void 0
@@ -1783,1430 +2231,5 @@ function usePopoverSupport() {
1783
2231
  return popoverSupported;
1784
2232
  }
1785
2233
 
1786
- const makeZeroShortcutPreprocessor = (templateString, separator) => {
1787
- const zeroShortcutPreprocessor = ({ elementState, data }, actionType) => {
1788
- if (actionType === "insert" && // the user is inserting a character
1789
- data === separator && // the user typed the separator character
1790
- elementState.selection[0] === elementState.selection[1]) {
1791
- const selectionIndex = elementState.selection[0];
1792
- const separatorPositions = templateString.split("").map((char, index) => char === separator ? index : null).filter((position) => position !== null).filter((position) => position > selectionIndex);
1793
- const noRemainingSegments = !separatorPositions.length;
1794
- const nothingEnteredYet = !elementState.value.length;
1795
- const previousCharacterIsNotADigit = !/^\d$/.test(
1796
- elementState.value.slice(-1)
1797
- );
1798
- const currentCharacterIsSeparator = templateString[selectionIndex] === separator;
1799
- if (noRemainingSegments || nothingEnteredYet || previousCharacterIsNotADigit || currentCharacterIsSeparator) {
1800
- return { elementState, data };
1801
- }
1802
- const firstIndexOfSegment = Math.max(
1803
- elementState.value.lastIndexOf(separator) + 1,
1804
- elementState.value.lastIndexOf(" ") + 1,
1805
- 0
1806
- );
1807
- const lastIndexOfSegment = separatorPositions[0];
1808
- const digitsCurrentlyInSegment = elementState.value.slice(firstIndexOfSegment);
1809
- const targetNumberOfDigitsInSegment = templateString.slice(
1810
- firstIndexOfSegment,
1811
- lastIndexOfSegment
1812
- ).length;
1813
- const newSegment = digitsCurrentlyInSegment.padStart(
1814
- targetNumberOfDigitsInSegment,
1815
- "0"
1816
- );
1817
- const newValue = `${elementState.value.slice(0, firstIndexOfSegment)}${newSegment}`;
1818
- return {
1819
- elementState: {
1820
- value: newValue + data,
1821
- selection: [newValue.length, newValue.length]
1822
- },
1823
- data
1824
- };
1825
- }
1826
- return { elementState, data };
1827
- };
1828
- return zeroShortcutPreprocessor;
1829
- };
1830
-
1831
- const datePlaceholderMask$1 = ({
1832
- mode,
1833
- separator = "/",
1834
- placeholder
1835
- }) => {
1836
- const dateOptions = maskitoDateOptionsGenerator({
1837
- mode,
1838
- separator
1839
- });
1840
- const { plugins, removePlaceholder, ...placeholderOptions } = maskitoWithPlaceholder(placeholder);
1841
- const datePlaceholderMask2 = {
1842
- ...dateOptions,
1843
- plugins: plugins.concat(dateOptions.plugins || []),
1844
- preprocessors: [
1845
- ...placeholderOptions.preprocessors,
1846
- ...dateOptions.preprocessors,
1847
- makeZeroShortcutPreprocessor(mode, separator)
1848
- ],
1849
- postprocessors: [
1850
- ...dateOptions.postprocessors,
1851
- ...placeholderOptions.postprocessors
1852
- ]
1853
- };
1854
- return { options: datePlaceholderMask2, removePlaceholder };
1855
- };
1856
-
1857
- const DateModeToFormatMap = {
1858
- "mm/dd/yyyy": "MM/dd/yyyy",
1859
- "dd/mm/yyyy": "dd/MM/yyyy",
1860
- "yyyy/mm/dd": "yyyy/MM/dd"
1861
- };
1862
- const DateModeToPlaceholderMap = {
1863
- "dd/mm/yyyy": "__/__/____",
1864
- "mm/dd/yyyy": "__/__/____",
1865
- "yyyy/mm/dd": "____/__/__"
1866
- };
1867
-
1868
- const MaskedDateInput = forwardRef(
1869
- ({
1870
- onChange,
1871
- mode = "mm/dd/yyyy",
1872
- lastValidDate,
1873
- disableHint = false,
1874
- ...props
1875
- }, ref) => {
1876
- const placeholder = DateModeToPlaceholderMap[mode];
1877
- const [inputValue, setInputValue] = useState(placeholder);
1878
- const { options, removePlaceholder } = datePlaceholderMask$1({
1879
- mode,
1880
- placeholder
1881
- });
1882
- const maskedInputRef = useMaskito({ options });
1883
- const inputRef = useRef(null);
1884
- const combinedRef = useMergeRefs$1([maskedInputRef, inputRef, ref]);
1885
- const previousDateRef = useRef(null);
1886
- const previousMode = usePrevious(mode);
1887
- useEffect(() => {
1888
- if (mode !== previousMode) {
1889
- setInputValue(
1890
- (oldInputValue) => swapMode$2(oldInputValue, previousMode ?? mode, mode)
1891
- );
1892
- }
1893
- }, [mode, previousMode]);
1894
- useEffect(() => {
1895
- if (lastValidDate === void 0) return;
1896
- if (lastValidDate === previousDateRef.current) return;
1897
- if (!lastValidDate?.equals(previousDateRef.current ?? DateTime.now())) {
1898
- setInputValue(
1899
- lastValidDate?.toFormat(DateModeToFormatMap[mode]) ?? placeholder
1900
- );
1901
- previousDateRef.current = lastValidDate;
1902
- }
1903
- }, [lastValidDate, mode, placeholder]);
1904
- const currentParsedData = useMemo(() => {
1905
- return parseInputValue$1(inputValue, mode, removePlaceholder);
1906
- }, [inputValue, mode, removePlaceholder]);
1907
- const handleChange = (event) => {
1908
- setInputValue(event.target.value);
1909
- const { date, isInputValid, isInputEmpty } = parseInputValue$1(
1910
- event.target.value,
1911
- mode,
1912
- removePlaceholder
1913
- );
1914
- const isValueDifferent = isInputValid !== currentParsedData.isInputValid || // The input has changed validity
1915
- isInputEmpty !== currentParsedData.isInputEmpty || // The input has changed emptiness
1916
- date === null !== (lastValidDate === null) || // The input has changed from empty to non-empty or vice versa
1917
- date !== null && lastValidDate !== null && !date.equals(lastValidDate);
1918
- if (!isValueDifferent) return;
1919
- onChange?.({
1920
- date: date ?? lastValidDate ?? null,
1921
- isInputValid,
1922
- isInputEmpty
1923
- });
1924
- };
1925
- useImperativeHandle(ref, () => {
1926
- const input = inputRef.current;
1927
- if (!input) return null;
1928
- return Object.assign(input, {
1929
- setDate: (date) => {
1930
- setInputValue(
1931
- date?.toFormat(DateModeToFormatMap[mode]) ?? placeholder
1932
- );
1933
- }
1934
- });
1935
- }, [mode, placeholder]);
1936
- return /* @__PURE__ */ jsx(
1937
- TextField,
1938
- {
1939
- ref: combinedRef,
1940
- "data-date": lastValidDate?.toISODate() ?? "",
1941
- "data-input-valid": currentParsedData.isInputValid,
1942
- "data-input-empty": currentParsedData.isInputEmpty,
1943
- ...props,
1944
- showCounter: false,
1945
- value: inputValue,
1946
- onChange: handleChange,
1947
- prefix: /* @__PURE__ */ jsx(Icon, { svg: SvgEvent }),
1948
- hint: disableHint ? void 0 : `Format: ${mode}`
1949
- }
1950
- );
1951
- }
1952
- );
1953
- MaskedDateInput.displayName = "MaskedDateInput";
1954
- function parseInputValue$1(value, mode, removePlaceholder) {
1955
- const valueMinusPlaceholder = removePlaceholder(value);
1956
- const jsDate = maskitoParseDate(valueMinusPlaceholder, { mode });
1957
- const luxonDate = jsDate ? DateTime.fromJSDate(jsDate, { zone: "utc" }) : null;
1958
- return {
1959
- date: luxonDate,
1960
- isInputValid: !!luxonDate,
1961
- isInputEmpty: valueMinusPlaceholder === ""
1962
- };
1963
- }
1964
- function swapMode$2(inputString, previousMode, mode) {
1965
- const { day, month, year } = divideSegments$1(inputString, previousMode);
1966
- return orderSegmentsByMode(day, month, year, mode);
1967
- }
1968
- function divideSegments$1(value, mode) {
1969
- const [segment1, segment2, segment3] = value.split("/");
1970
- if (mode === "dd/mm/yyyy") {
1971
- return { day: segment1, month: segment2, year: segment3 };
1972
- } else if (mode === "mm/dd/yyyy") {
1973
- return { day: segment2, month: segment1, year: segment3 };
1974
- } else if (mode === "yyyy/mm/dd") {
1975
- return { day: segment3, month: segment2, year: segment1 };
1976
- }
1977
- return { day: "__", month: "__", year: "____" };
1978
- }
1979
- function orderSegmentsByMode(day, month, year, mode) {
1980
- if (mode === "dd/mm/yyyy") {
1981
- return `${day}/${month}/${year}`;
1982
- } else if (mode === "mm/dd/yyyy") {
1983
- return `${month}/${day}/${year}`;
1984
- } else if (mode === "yyyy/mm/dd") {
1985
- return `${year}/${month}/${day}`;
1986
- } else {
1987
- return "";
1988
- }
1989
- }
1990
-
1991
- function containsRelatedTarget(event) {
1992
- if (event.currentTarget instanceof HTMLElement && event.relatedTarget instanceof HTMLElement) {
1993
- return event.currentTarget.contains(event.relatedTarget);
1994
- }
1995
- return false;
1996
- }
1997
- function useFocusWithin({
1998
- onBlur,
1999
- onFocus
2000
- } = {}) {
2001
- const [focused, setFocused] = useState(false);
2002
- const focusedRef = useRef(false);
2003
- const elementRef = useRef(null);
2004
- const _setFocused = useCallback((value) => {
2005
- setFocused(value);
2006
- focusedRef.current = value;
2007
- }, []);
2008
- const handleFocusIn = useCallback(
2009
- (event) => {
2010
- if (!focusedRef.current) {
2011
- _setFocused(true);
2012
- onFocus?.(event);
2013
- }
2014
- },
2015
- // eslint-disable-next-line react-hooks/exhaustive-deps
2016
- [onFocus]
2017
- );
2018
- const handleFocusOut = useCallback(
2019
- (event) => {
2020
- if (focusedRef.current && !containsRelatedTarget(event)) {
2021
- _setFocused(false);
2022
- onBlur?.(event);
2023
- }
2024
- },
2025
- // eslint-disable-next-line react-hooks/exhaustive-deps
2026
- [onBlur]
2027
- );
2028
- useEffect(() => {
2029
- const element = elementRef.current;
2030
- if (!element) {
2031
- return;
2032
- }
2033
- element.addEventListener("focusin", handleFocusIn);
2034
- element.addEventListener("focusout", handleFocusOut);
2035
- return () => {
2036
- element.removeEventListener("focusin", handleFocusIn);
2037
- element.removeEventListener("focusout", handleFocusOut);
2038
- };
2039
- }, [handleFocusIn, handleFocusOut]);
2040
- return { ref: elementRef, focused };
2041
- }
2042
-
2043
- function convertStringToDate(v) {
2044
- if (v === void 0 || v === null) {
2045
- return v;
2046
- }
2047
- const date = DateTime.fromISO(v, { setZone: true, zone: "utc" }).startOf(
2048
- "day"
2049
- );
2050
- if (date.isValid) {
2051
- return date;
2052
- }
2053
- return null;
2054
- }
2055
- function validateDate({
2056
- date,
2057
- constraints
2058
- }) {
2059
- const { required, unavailable, minDate, maxDate } = constraints;
2060
- if (!date) {
2061
- return required ? false : true;
2062
- }
2063
- if (unavailable?.dates?.some((d) => d.equals(date))) {
2064
- return false;
2065
- }
2066
- if (unavailable?.daysOfWeek?.includes(date.weekday)) {
2067
- return false;
2068
- }
2069
- if (minDate && date < minDate) {
2070
- return false;
2071
- }
2072
- if (maxDate && date > maxDate) {
2073
- return false;
2074
- }
2075
- return true;
2076
- }
2077
- function validateYearlessDate({
2078
- value,
2079
- constraints
2080
- }) {
2081
- const { required, unavailable, minDate, maxDate } = constraints;
2082
- if (!value || !value?.day && !value?.month) {
2083
- return required ? false : true;
2084
- }
2085
- if (value.day === null || value.month === null) {
2086
- return false;
2087
- }
2088
- if (unavailable?.dates?.some(
2089
- (d) => d.day === value.day && d.month === value.month
2090
- )) {
2091
- return false;
2092
- }
2093
- const minDateMonth = minDate?.month ?? 1;
2094
- const minDateDay = minDate?.day ?? 1;
2095
- const maxDateMonth = maxDate?.month ?? 12;
2096
- const maxDateDay = maxDate?.day ?? 31;
2097
- if (value.month < minDateMonth || // Earlier month
2098
- value.month === minDateMonth && value.day < minDateDay) {
2099
- return false;
2100
- }
2101
- if (value.month > maxDateMonth || // Later month
2102
- value.month === maxDateMonth && value.day > maxDateDay) {
2103
- return false;
2104
- }
2105
- return true;
2106
- }
2107
-
2108
- function useDateFieldSingleConversion({
2109
- value,
2110
- defaultValue,
2111
- minDate,
2112
- maxDate,
2113
- unavailable,
2114
- onChange
2115
- }) {
2116
- const normalizedValue = useMemo(() => convertStringToDate(value), [value]);
2117
- const normalizedDefaultValue = useMemo(
2118
- () => convertStringToDate(defaultValue),
2119
- [defaultValue]
2120
- );
2121
- const normalizedMinDate = useMemo(
2122
- () => convertStringToDate(minDate),
2123
- [minDate]
2124
- );
2125
- const normalizedMaxDate = useMemo(
2126
- () => convertStringToDate(maxDate),
2127
- [maxDate]
2128
- );
2129
- const normalizedUnavailableDates = useMemo(
2130
- () => unavailable?.dates?.map((d) => convertStringToDate(d)).filter((d) => d !== null && d !== void 0),
2131
- [unavailable?.dates]
2132
- );
2133
- const handleChange = useCallback(
2134
- (change) => {
2135
- onChange?.({
2136
- ...change,
2137
- date: change.date?.toISODate() ?? null
2138
- });
2139
- },
2140
- [onChange]
2141
- );
2142
- return {
2143
- value: normalizedValue,
2144
- defaultValue: normalizedDefaultValue,
2145
- minDate: normalizedMinDate,
2146
- maxDate: normalizedMaxDate,
2147
- unavailable: {
2148
- dates: normalizedUnavailableDates,
2149
- daysOfWeek: unavailable?.daysOfWeek
2150
- },
2151
- onChange: handleChange
2152
- };
2153
- }
2154
-
2155
- function DateFieldSingleCalendar({
2156
- onKeyDown,
2157
- value,
2158
- onSelection,
2159
- minDate,
2160
- maxDate,
2161
- unavailable,
2162
- ...rest
2163
- }) {
2164
- const handleCalendarSelection = (data) => {
2165
- if (data.value) {
2166
- const date = DateTime.fromISO(data.value, { zone: "utc" });
2167
- onSelection(date);
2168
- }
2169
- };
2170
- return /* @__PURE__ */ jsx(
2171
- Calendar,
2172
- {
2173
- ...rest,
2174
- range: false,
2175
- onKeyDown,
2176
- defaultFocusedDate: value?.toISODate() || DateTime.now().toISODate(),
2177
- value: value?.toISODate() || void 0,
2178
- onSelection: handleCalendarSelection,
2179
- defaultTimeZone: "UTC",
2180
- minDate: minDate?.toISODate() ?? void 0,
2181
- maxDate: maxDate?.toISODate() ?? void 0,
2182
- unavailable: unavailable ? {
2183
- dates: unavailable.dates?.map((d) => d.toISODate() ?? ""),
2184
- daysOfWeek: unavailable.daysOfWeek
2185
- } : void 0,
2186
- _disableAutofocus: true
2187
- }
2188
- );
2189
- }
2190
-
2191
- const useDateFieldOrchestration = ({
2192
- inputRef,
2193
- calendarDefaultOpen,
2194
- popoverContentRef,
2195
- disableCalendar = false
2196
- }) => {
2197
- const documentRef = useRef(document.body);
2198
- const [calendarOpen, setCalendarOpen] = useState(calendarDefaultOpen);
2199
- const { focusables } = useKeyboardFocusables(documentRef, {
2200
- observeChange: true
2201
- });
2202
- const { focusables: popoverFocusables } = useKeyboardFocusables(
2203
- popoverContentRef,
2204
- {
2205
- observeChange: true
2206
- }
2207
- );
2208
- const pageFocusables = focusables?.filter(
2209
- (item) => !popoverFocusables?.includes(item)
2210
- );
2211
- const handleCalendarKeyDown = (event) => {
2212
- if (event.key === "Escape") {
2213
- inputRef.current?.focus();
2214
- }
2215
- };
2216
- const focusToCalendar = () => {
2217
- if (popoverContentRef.current) {
2218
- const currentFocusable = popoverContentRef.current.querySelectorAll('[tabindex = "0"]')[0];
2219
- if (currentFocusable) {
2220
- currentFocusable.focus();
2221
- }
2222
- }
2223
- };
2224
- const handleInputKeyDown = (ev) => {
2225
- if (disableCalendar) {
2226
- return;
2227
- }
2228
- let currentFocusIndex = 0;
2229
- switch (ev.key) {
2230
- case "Escape":
2231
- setCalendarOpen(false);
2232
- break;
2233
- case "Tab":
2234
- if (!calendarOpen || !pageFocusables?.length) {
2235
- break;
2236
- }
2237
- ev.preventDefault();
2238
- currentFocusIndex = pageFocusables.indexOf(inputRef.current) || 0;
2239
- setCalendarOpen(false);
2240
- if (ev.shiftKey) {
2241
- if (currentFocusIndex === 0) {
2242
- requestAnimationFrame(
2243
- () => pageFocusables[pageFocusables.length - 1].focus()
2244
- );
2245
- } else {
2246
- requestAnimationFrame(
2247
- () => pageFocusables[currentFocusIndex - 1].focus()
2248
- );
2249
- }
2250
- break;
2251
- }
2252
- if (pageFocusables.length > currentFocusIndex + 1) {
2253
- requestAnimationFrame(
2254
- () => pageFocusables[currentFocusIndex + 1].focus()
2255
- );
2256
- } else {
2257
- requestAnimationFrame(() => pageFocusables[0].focus());
2258
- }
2259
- break;
2260
- case "ArrowDown":
2261
- if (!calendarOpen) {
2262
- setCalendarOpen(true);
2263
- setTimeout(focusToCalendar, 200);
2264
- } else {
2265
- focusToCalendar();
2266
- }
2267
- }
2268
- };
2269
- return {
2270
- calendarOpen,
2271
- setCalendarOpen,
2272
- handleCalendarKeyDown,
2273
- handleInputKeyDown
2274
- };
2275
- };
2276
-
2277
- function useDateFieldSingleState({
2278
- valueProp,
2279
- defaultValueProp,
2280
- inputRef,
2281
- onChange
2282
- }) {
2283
- const [value, setValue] = useOptionallyControlledState({
2284
- controlledValue: valueProp,
2285
- defaultValue: defaultValueProp
2286
- });
2287
- const setSharedValue = (date) => {
2288
- inputRef.current?.setDate(date);
2289
- setValue(date);
2290
- };
2291
- const handleInputChange = (change) => {
2292
- const date = change.isInputEmpty ? null : change.date?.startOf("day") ?? null;
2293
- onChange?.({
2294
- date,
2295
- isInputValid: change.isInputValid,
2296
- isInputEmpty: change.isInputEmpty
2297
- });
2298
- if (change.isInputValid) {
2299
- setSharedValue(change.isInputEmpty ? null : change.date);
2300
- }
2301
- if (change.isInputEmpty) {
2302
- setSharedValue(null);
2303
- return;
2304
- }
2305
- if (change.date) {
2306
- setSharedValue(change.date);
2307
- }
2308
- };
2309
- const handleCalendarSelection = (date) => {
2310
- setSharedValue(date);
2311
- onChange?.({
2312
- date,
2313
- isInputValid: true,
2314
- isInputEmpty: false
2315
- });
2316
- };
2317
- return {
2318
- value,
2319
- setValue: setSharedValue,
2320
- handleInputChange,
2321
- handleCalendarSelection
2322
- };
2323
- }
2324
-
2325
- const DateFieldSingle = ({
2326
- onFocus,
2327
- onBlur,
2328
- disableCalendar,
2329
- required,
2330
- mode,
2331
- value: valueProp,
2332
- defaultValue: defaultValueProp,
2333
- minDate: minDateProp,
2334
- maxDate: maxDateProp,
2335
- unavailable: unavailableProp,
2336
- onChange: onChangeProp,
2337
- ...rest
2338
- }) => {
2339
- const inputRef = useRef(null);
2340
- const {
2341
- value: normalizedValue,
2342
- defaultValue: normalizedDefaultValue,
2343
- minDate,
2344
- maxDate,
2345
- unavailable,
2346
- onChange
2347
- } = useDateFieldSingleConversion({
2348
- value: valueProp,
2349
- defaultValue: defaultValueProp,
2350
- onChange: onChangeProp,
2351
- minDate: minDateProp,
2352
- maxDate: maxDateProp,
2353
- unavailable: unavailableProp
2354
- });
2355
- const handleChange = (change) => {
2356
- onChange?.({
2357
- ...change,
2358
- isDateValid: validateDate({
2359
- date: change.date,
2360
- constraints: {
2361
- required,
2362
- unavailable,
2363
- minDate: minDate ?? void 0,
2364
- maxDate: maxDate ?? void 0
2365
- }
2366
- })
2367
- });
2368
- };
2369
- const { value, handleInputChange, handleCalendarSelection } = useDateFieldSingleState({
2370
- valueProp: normalizedValue,
2371
- defaultValueProp: normalizedDefaultValue,
2372
- inputRef,
2373
- onChange: handleChange
2374
- });
2375
- const { ref: wrapperRef } = useFocusWithin({
2376
- onBlur,
2377
- onFocus
2378
- });
2379
- const [popoverTriggerRef, setPopoverTriggerRef] = useState();
2380
- const popoverContentRef = useRef(null);
2381
- const combinedRef = useMergeRefs([popoverTriggerRef, inputRef]);
2382
- const {
2383
- calendarOpen,
2384
- setCalendarOpen,
2385
- handleCalendarKeyDown,
2386
- handleInputKeyDown
2387
- } = useDateFieldOrchestration({
2388
- inputRef,
2389
- calendarDefaultOpen: false,
2390
- popoverContentRef,
2391
- disableCalendar
2392
- });
2393
- const popoverSupported = usePopoverSupport();
2394
- const shouldShowCalendar = usePopoverCloseDelayWorkaround(calendarOpen);
2395
- const currentValidity = useMemo(
2396
- () => validateDate({
2397
- date: value,
2398
- constraints: {
2399
- required,
2400
- unavailable,
2401
- minDate: minDate ?? void 0,
2402
- maxDate: maxDate ?? void 0
2403
- }
2404
- }),
2405
- [value, required, unavailable, minDate, maxDate]
2406
- );
2407
- const justTheField = /* @__PURE__ */ jsx(
2408
- MaskedDateInput,
2409
- {
2410
- ...rest,
2411
- mode,
2412
- ref: combinedRef,
2413
- onChange: handleInputChange,
2414
- onKeyDown: handleInputKeyDown,
2415
- onClick: () => setCalendarOpen(true),
2416
- lastValidDate: value,
2417
- required,
2418
- autoComplete: "off",
2419
- "data-date-valid": currentValidity
2420
- }
2421
- );
2422
- if (disableCalendar) {
2423
- return justTheField;
2424
- }
2425
- if (!popoverSupported) {
2426
- return justTheField;
2427
- }
2428
- return /* @__PURE__ */ jsx("div", { ref: wrapperRef, children: /* @__PURE__ */ jsxs(
2429
- Popover,
2430
- {
2431
- open: calendarOpen,
2432
- modal: true,
2433
- placement: "bottom-start",
2434
- disableFlipFallback: true,
2435
- disableTriggerFocus: true,
2436
- onClose: () => setCalendarOpen(false),
2437
- disableAutoUpdate: true,
2438
- onClickOutside: () => setCalendarOpen(false),
2439
- children: [
2440
- /* @__PURE__ */ jsx(Popover.Trigger, { children: ({ ref: iRef }) => {
2441
- setPopoverTriggerRef(iRef);
2442
- return justTheField;
2443
- } }),
2444
- /* @__PURE__ */ jsx(Popover.Content, { ref: popoverContentRef, "data-testid": "calendar-popover", children: shouldShowCalendar && /* @__PURE__ */ jsx(
2445
- DateFieldSingleCalendar,
2446
- {
2447
- onKeyDown: handleCalendarKeyDown,
2448
- value,
2449
- onSelection: handleCalendarSelection,
2450
- minDate,
2451
- maxDate,
2452
- unavailable
2453
- }
2454
- ) })
2455
- ]
2456
- }
2457
- ) });
2458
- };
2459
- DateFieldSingle.displayName = "DateFieldSingle";
2460
-
2461
- const datePlaceholderMask = ({
2462
- mode,
2463
- dateSeparator = "/",
2464
- rangeSeparator = " - ",
2465
- placeholder
2466
- }) => {
2467
- const dateRangeOptions = maskitoDateRangeOptionsGenerator({
2468
- mode,
2469
- dateSeparator,
2470
- rangeSeparator
2471
- });
2472
- const { plugins, removePlaceholder, ...placeholderOptions } = maskitoWithPlaceholder(placeholder);
2473
- const datePlaceholderMask2 = {
2474
- ...dateRangeOptions,
2475
- plugins: plugins.concat(dateRangeOptions.plugins || []),
2476
- preprocessors: [
2477
- ...placeholderOptions.preprocessors,
2478
- ...dateRangeOptions.preprocessors,
2479
- makeZeroShortcutPreprocessor(placeholder, dateSeparator)
2480
- ],
2481
- postprocessors: [
2482
- // NOTE this is super fragile. If Maskito maintainers change the order of the post-processors, this will break.
2483
- // The last postprocessor is the date swap postprocessor, which we don't want to run.
2484
- // A unit test is added to ensure this doesn't break on a dependency update.
2485
- ...dateRangeOptions.postprocessors.slice(0, -1),
2486
- ...placeholderOptions.postprocessors
2487
- ]
2488
- };
2489
- return { options: datePlaceholderMask2, removePlaceholder };
2490
- };
2491
-
2492
- const RANGE_SEPARATOR = " - ";
2493
- const MaskedDateRangeInput = forwardRef(
2494
- ({
2495
- onChange,
2496
- mode = "mm/dd/yyyy",
2497
- startDate,
2498
- endDate,
2499
- disableHint = false,
2500
- ...props
2501
- }, ref) => {
2502
- const halfPlaceholder = DateModeToPlaceholderMap[mode];
2503
- const fullPlaceholder = `${halfPlaceholder}${RANGE_SEPARATOR}${halfPlaceholder}`;
2504
- const [inputValue, setInputValue] = useState(fullPlaceholder);
2505
- const { options, removePlaceholder } = datePlaceholderMask({
2506
- mode,
2507
- placeholder: fullPlaceholder,
2508
- dateSeparator: "/",
2509
- rangeSeparator: RANGE_SEPARATOR
2510
- });
2511
- const maskedInputRef = useMaskito({ options });
2512
- const inputRef = useRef(null);
2513
- const combinedRef = useMergeRefs$1([maskedInputRef, inputRef, ref]);
2514
- const previousStartDate = usePrevious(startDate);
2515
- const previousEndDate = usePrevious(endDate);
2516
- const previousMode = usePrevious(mode);
2517
- useEffect(() => {
2518
- if (mode !== previousMode) {
2519
- setInputValue(
2520
- (previousInputValue) => swapMode$1(previousInputValue, previousMode ?? mode, mode)
2521
- );
2522
- }
2523
- }, [mode, fullPlaceholder, previousMode]);
2524
- useEffect(() => {
2525
- if (startDate === void 0 || endDate === void 0) return;
2526
- if (startDate === previousStartDate && endDate === previousEndDate)
2527
- return;
2528
- if (
2529
- // plus one just represents a date that is guaranteed to be different.
2530
- startDate?.equals(previousStartDate ?? startDate?.plus({ days: 1 })) && (endDate?.equals(previousEndDate ?? endDate?.plus({ days: 1 })) || endDate === previousEndDate)
2531
- )
2532
- return;
2533
- const startDateString = startDate?.toFormat(DateModeToFormatMap[mode]) ?? halfPlaceholder;
2534
- const endDateString = endDate?.toFormat(DateModeToFormatMap[mode]) ?? halfPlaceholder;
2535
- const newInputValue = `${startDateString}${RANGE_SEPARATOR}${endDateString}`;
2536
- setInputValue(newInputValue);
2537
- }, [
2538
- startDate,
2539
- endDate,
2540
- mode,
2541
- halfPlaceholder,
2542
- previousStartDate,
2543
- previousEndDate
2544
- ]);
2545
- const currentParsedData = useMemo(() => {
2546
- return parseRangeInputValue(inputValue, mode, removePlaceholder);
2547
- }, [inputValue, mode, removePlaceholder]);
2548
- const handleChange = (event) => {
2549
- setInputValue(event.target.value);
2550
- const {
2551
- startDate: parsedStartDate,
2552
- endDate: parsedEndDate,
2553
- isInputValid,
2554
- isInputEmpty,
2555
- isHalfEmpty
2556
- } = parseRangeInputValue(event.target.value, mode, removePlaceholder);
2557
- const isValueDifferent = isInputValid !== currentParsedData.isInputValid || // The input has changed validity
2558
- isInputEmpty !== currentParsedData.isInputEmpty || // The input has changed emptiness
2559
- parsedStartDate === null !== (startDate === null) || // The start date has changed from empty to non-empty or vice versa
2560
- parsedEndDate === null !== (endDate === null) || // The end date has changed from empty to non-empty or vice versa
2561
- parsedStartDate !== null && startDate !== null && !parsedStartDate.equals(startDate) || // The start date has changed
2562
- parsedEndDate !== null && endDate !== null && !parsedEndDate.equals(endDate);
2563
- if (!isValueDifferent) return;
2564
- onChange?.({
2565
- startDate: isInputEmpty ? null : parsedStartDate ?? startDate ?? null,
2566
- endDate: isInputEmpty || isHalfEmpty ? null : parsedEndDate ?? endDate ?? null,
2567
- isInputValid,
2568
- isInputEmpty
2569
- });
2570
- };
2571
- useImperativeHandle(ref, () => {
2572
- const input = inputRef.current;
2573
- if (!input) return null;
2574
- return Object.assign(input, {
2575
- setDateRange: (startDate2, endDate2) => {
2576
- const startDateString = startDate2?.toFormat(
2577
- DateModeToFormatMap[mode]
2578
- );
2579
- const endDateString = endDate2?.toFormat(DateModeToFormatMap[mode]);
2580
- const newInputValue = `${startDateString ?? halfPlaceholder}${RANGE_SEPARATOR}${endDateString ?? halfPlaceholder}`;
2581
- setInputValue(newInputValue);
2582
- }
2583
- });
2584
- }, [mode, halfPlaceholder]);
2585
- return /* @__PURE__ */ jsx(
2586
- TextField,
2587
- {
2588
- ref: combinedRef,
2589
- "data-start-date": startDate?.toISODate() ?? "",
2590
- "data-end-date": endDate?.toISODate() ?? "",
2591
- "data-input-valid": currentParsedData.isInputValid,
2592
- "data-input-empty": currentParsedData.isInputEmpty,
2593
- ...props,
2594
- showCounter: false,
2595
- value: inputValue,
2596
- onChange: handleChange,
2597
- prefix: /* @__PURE__ */ jsx(Icon, { svg: SvgEvent }),
2598
- hint: disableHint ? void 0 : `Format: ${mode}`
2599
- }
2600
- );
2601
- }
2602
- );
2603
- MaskedDateRangeInput.displayName = "MaskedDateRangeInput";
2604
- function parseRangeInputValue(value, mode, removePlaceholder) {
2605
- const valueMinusPlaceholder = removePlaceholder(value);
2606
- const [startDate, endDate] = valueMinusPlaceholder.split(RANGE_SEPARATOR);
2607
- const startJsDate = maskitoParseDate(startDate, { mode });
2608
- const endJsDate = endDate ? maskitoParseDate(endDate, { mode }) : null;
2609
- const startLuxonDate = startJsDate ? DateTime.fromJSDate(startJsDate, { zone: "utc" }) : null;
2610
- const endLuxonDate = endJsDate ? DateTime.fromJSDate(endJsDate, { zone: "utc" }) : null;
2611
- return {
2612
- startDate: startLuxonDate,
2613
- endDate: endLuxonDate,
2614
- isInputValid: !!(startLuxonDate && endLuxonDate),
2615
- // input valid if both dates are filled
2616
- isInputEmpty: valueMinusPlaceholder === "",
2617
- // input empty if nothing is typed
2618
- isHalfEmpty: endDate === void 0
2619
- };
2620
- }
2621
- function swapMode$1(inputString, previousMode, mode) {
2622
- const halves = inputString.split(RANGE_SEPARATOR);
2623
- const segments = halves.map((half) => half.split("/"));
2624
- let startDay, startMonth, startYear, endDay, endMonth, endYear;
2625
- if (previousMode === "mm/dd/yyyy") {
2626
- startDay = segments[0][1];
2627
- startMonth = segments[0][0];
2628
- startYear = segments[0][2];
2629
- endDay = segments[1][1];
2630
- endMonth = segments[1][0];
2631
- endYear = segments[1][2];
2632
- }
2633
- if (previousMode === "dd/mm/yyyy") {
2634
- startDay = segments[0][0];
2635
- startMonth = segments[0][1];
2636
- startYear = segments[0][2];
2637
- endDay = segments[1][0];
2638
- endMonth = segments[1][1];
2639
- endYear = segments[1][2];
2640
- }
2641
- if (previousMode === "yyyy/mm/dd") {
2642
- startDay = segments[0][2];
2643
- startMonth = segments[0][1];
2644
- startYear = segments[0][0];
2645
- endDay = segments[1][2];
2646
- endMonth = segments[1][1];
2647
- endYear = segments[1][0];
2648
- }
2649
- if (mode === "mm/dd/yyyy") {
2650
- return `${startMonth}/${startDay}/${startYear}${RANGE_SEPARATOR}${endMonth}/${endDay}/${endYear}`;
2651
- }
2652
- if (mode === "dd/mm/yyyy") {
2653
- return `${startDay}/${startMonth}/${startYear}${RANGE_SEPARATOR}${endDay}/${endMonth}/${endYear}`;
2654
- }
2655
- if (mode === "yyyy/mm/dd") {
2656
- return `${startYear}/${startMonth}/${startDay}${RANGE_SEPARATOR}${endYear}/${endMonth}/${endDay}`;
2657
- }
2658
- return inputString;
2659
- }
2660
-
2661
- const useDateFieldRangeConversion = (props) => {
2662
- const { value, defaultValue, minDate, maxDate, unavailable, onChange } = props;
2663
- const normalizedValue = useMemo(() => {
2664
- if (value === null || value === void 0) return value;
2665
- return {
2666
- startDate: convertStringToDate(value.startDate) ?? null,
2667
- endDate: convertStringToDate(value.endDate) ?? null
2668
- };
2669
- }, [value]);
2670
- const normalizedDefaultValue = useMemo(() => {
2671
- if (defaultValue === null || defaultValue === void 0)
2672
- return defaultValue;
2673
- return {
2674
- startDate: convertStringToDate(defaultValue.startDate) ?? null,
2675
- endDate: convertStringToDate(defaultValue.endDate) ?? null
2676
- };
2677
- }, [defaultValue]);
2678
- const normalizedMinDate = useMemo(
2679
- () => convertStringToDate(minDate),
2680
- [minDate]
2681
- );
2682
- const normalizedMaxDate = useMemo(
2683
- () => convertStringToDate(maxDate),
2684
- [maxDate]
2685
- );
2686
- const normalizedUnavailableDates = useMemo(() => {
2687
- return unavailable?.dates?.map((date) => convertStringToDate(date)).filter((date) => date !== null && date !== void 0);
2688
- }, [unavailable?.dates]);
2689
- const handleChange = useCallback(
2690
- (change) => {
2691
- onChange?.({
2692
- ...change,
2693
- startDate: change.startDate?.toISODate() ?? null,
2694
- endDate: change.endDate?.toISODate() ?? null
2695
- });
2696
- },
2697
- [onChange]
2698
- );
2699
- return {
2700
- value: normalizedValue,
2701
- defaultValue: normalizedDefaultValue,
2702
- minDate: normalizedMinDate,
2703
- maxDate: normalizedMaxDate,
2704
- unavailable: {
2705
- dates: normalizedUnavailableDates,
2706
- daysOfWeek: unavailable?.daysOfWeek
2707
- },
2708
- onChange: handleChange
2709
- };
2710
- };
2711
-
2712
- function useDateFieldRangeState({
2713
- valueProp,
2714
- defaultValueProp,
2715
- inputRef,
2716
- onChange
2717
- }) {
2718
- const [startDate, setStartDate] = useOptionallyControlledState({
2719
- controlledValue: valueProp !== void 0 ? valueProp?.startDate : void 0,
2720
- defaultValue: defaultValueProp !== void 0 ? defaultValueProp?.startDate : void 0
2721
- });
2722
- const [endDate, setEndDate] = useOptionallyControlledState({
2723
- controlledValue: valueProp !== void 0 ? valueProp?.endDate : void 0,
2724
- defaultValue: defaultValueProp !== void 0 ? defaultValueProp?.endDate : void 0
2725
- });
2726
- const setSharedValue = (value) => {
2727
- inputRef.current?.setDateRange(value.startDate, value.endDate);
2728
- setStartDate(value.startDate);
2729
- setEndDate(value.endDate);
2730
- };
2731
- const handleInputChange = (change) => {
2732
- const range = change.isInputEmpty ? null : {
2733
- startDate: change.startDate?.startOf("day") ?? null,
2734
- endDate: change.endDate?.startOf("day") ?? null
2735
- };
2736
- setStartDate(range?.startDate ?? null);
2737
- setEndDate(range?.endDate ?? null);
2738
- onChange?.({
2739
- startDate: range?.startDate ?? null,
2740
- endDate: range?.endDate ?? null,
2741
- isInputValid: change.isInputValid,
2742
- isInputEmpty: change.isInputEmpty
2743
- });
2744
- };
2745
- const handleCalendarSelection = ({
2746
- startDate: startDate2,
2747
- endDate: endDate2
2748
- }) => {
2749
- setSharedValue({ startDate: startDate2, endDate: endDate2 });
2750
- onChange?.({
2751
- startDate: startDate2,
2752
- endDate: endDate2,
2753
- isInputValid: true,
2754
- isInputEmpty: false
2755
- });
2756
- };
2757
- return {
2758
- startDate,
2759
- endDate,
2760
- setStartDate,
2761
- setEndDate,
2762
- handleInputChange,
2763
- handleCalendarSelection
2764
- };
2765
- }
2766
-
2767
- const DateFieldRangeCalendar = ({
2768
- startDate,
2769
- endDate,
2770
- onKeyDown,
2771
- onSelection,
2772
- minDate,
2773
- maxDate,
2774
- unavailable
2775
- }) => {
2776
- const previousStartDate = usePrevious(startDate);
2777
- const previousEndDate = usePrevious(endDate);
2778
- const handleCalendarSelection = (data) => {
2779
- if (!data.value) return;
2780
- const calStartDate = data.value.start ? DateTime.fromISO(data.value.start, { zone: "utc" }) : null;
2781
- const calEndDate = data.value.end ? DateTime.fromISO(data.value.end, { zone: "utc" }) : null;
2782
- onSelection({
2783
- startDate: calStartDate,
2784
- endDate: calEndDate
2785
- });
2786
- };
2787
- const defaultFocusedDate = useMemo(() => {
2788
- if (!startDate && !endDate) return DateTime.now().toISODate();
2789
- if (!startDate) return endDate?.toISODate();
2790
- if (!endDate) return startDate?.toISODate();
2791
- if (endDate && !previousEndDate?.equals(endDate)) {
2792
- return endDate.toISODate();
2793
- } else if (startDate && !previousStartDate?.equals(startDate)) {
2794
- return startDate.toISODate();
2795
- }
2796
- if (endDate) return endDate.toISODate();
2797
- if (startDate) return startDate.toISODate();
2798
- return DateTime.now().toISODate();
2799
- }, [previousStartDate, previousEndDate, startDate, endDate]);
2800
- return /* @__PURE__ */ jsx(
2801
- Calendar,
2802
- {
2803
- range: true,
2804
- onKeyDown,
2805
- defaultFocusedDate,
2806
- value: {
2807
- start: startDate?.toISODate() || void 0,
2808
- end: endDate?.toISODate() || void 0
2809
- },
2810
- onSelection: handleCalendarSelection,
2811
- defaultTimeZone: "UTC",
2812
- minDate: minDate?.toISODate() ?? void 0,
2813
- maxDate: maxDate?.toISODate() ?? void 0,
2814
- unavailable: unavailable ? {
2815
- dates: unavailable.dates?.map((d) => d.toISODate() ?? ""),
2816
- daysOfWeek: unavailable.daysOfWeek
2817
- } : void 0,
2818
- _disableAutofocus: true
2819
- },
2820
- `${startDate?.toISODate()}-${endDate?.toISODate()}`
2821
- );
2822
- };
2823
-
2824
- const DateFieldRange = ({
2825
- onFocus,
2826
- onBlur,
2827
- disableCalendar,
2828
- required,
2829
- mode,
2830
- value: valueProp,
2831
- defaultValue: defaultValueProp,
2832
- minDate: minDateProp,
2833
- maxDate: maxDateProp,
2834
- unavailable: unavailableProp,
2835
- onChange: onChangeProp,
2836
- ...rest
2837
- }) => {
2838
- const { ref: wrapperRef } = useFocusWithin({
2839
- onBlur,
2840
- onFocus
2841
- });
2842
- const { value, defaultValue, minDate, maxDate, unavailable, onChange } = useDateFieldRangeConversion({
2843
- value: valueProp,
2844
- defaultValue: defaultValueProp,
2845
- minDate: minDateProp,
2846
- maxDate: maxDateProp,
2847
- unavailable: unavailableProp,
2848
- onChange: onChangeProp
2849
- });
2850
- const inputRef = useRef(null);
2851
- const [popoverTriggerRef, setPopoverTriggerRef] = useState();
2852
- const popoverContentRef = useRef(null);
2853
- const combinedRef = useMergeRefs([popoverTriggerRef, inputRef]);
2854
- const popoverSupported = usePopoverSupport();
2855
- const handleChange = (change) => {
2856
- const sharedConstraints = {
2857
- unavailable,
2858
- minDate: minDate ?? void 0,
2859
- maxDate: maxDate ?? void 0
2860
- };
2861
- onChange?.({
2862
- startDate: change.startDate?.startOf("day") ?? null,
2863
- endDate: change.endDate?.startOf("day") ?? null,
2864
- isInputValid: change.isInputValid,
2865
- isInputEmpty: change.isInputEmpty,
2866
- isDateRangeValid: validateDateRange({
2867
- required,
2868
- startDate: change.startDate?.startOf("day") ?? null,
2869
- endDate: change.endDate?.startOf("day") ?? null,
2870
- startDateConstraints: sharedConstraints,
2871
- endDateConstraints: sharedConstraints
2872
- })
2873
- });
2874
- };
2875
- const { startDate, endDate, handleInputChange, handleCalendarSelection } = useDateFieldRangeState({
2876
- valueProp: value,
2877
- defaultValueProp: defaultValue,
2878
- inputRef,
2879
- onChange: handleChange
2880
- });
2881
- const {
2882
- calendarOpen,
2883
- setCalendarOpen,
2884
- handleCalendarKeyDown,
2885
- handleInputKeyDown
2886
- } = useDateFieldOrchestration({
2887
- inputRef,
2888
- calendarDefaultOpen: false,
2889
- popoverContentRef,
2890
- disableCalendar
2891
- });
2892
- const shouldShowCalendar = usePopoverCloseDelayWorkaround(calendarOpen);
2893
- const currentValidity = useMemo(() => {
2894
- return validateDateRange({
2895
- required,
2896
- startDate,
2897
- endDate,
2898
- startDateConstraints: {
2899
- unavailable,
2900
- minDate: minDate ?? void 0,
2901
- maxDate: maxDate ?? void 0
2902
- },
2903
- endDateConstraints: {
2904
- unavailable,
2905
- minDate: minDate ?? void 0,
2906
- maxDate: maxDate ?? void 0
2907
- }
2908
- });
2909
- }, [required, startDate, endDate, minDate, maxDate, unavailable]);
2910
- const justTheField = /* @__PURE__ */ jsx(
2911
- MaskedDateRangeInput,
2912
- {
2913
- ...rest,
2914
- mode,
2915
- ref: combinedRef,
2916
- startDate: startDate ?? null,
2917
- endDate: endDate ?? null,
2918
- onChange: handleInputChange,
2919
- disableHint: rest.disableHint,
2920
- onKeyDown: handleInputKeyDown,
2921
- onClick: () => setCalendarOpen(true),
2922
- required,
2923
- autoComplete: "off",
2924
- "data-date-range-valid": currentValidity
2925
- }
2926
- );
2927
- if (disableCalendar) {
2928
- return justTheField;
2929
- }
2930
- if (!popoverSupported) {
2931
- return justTheField;
2932
- }
2933
- return /* @__PURE__ */ jsx("div", { ref: wrapperRef, children: /* @__PURE__ */ jsxs(
2934
- Popover,
2935
- {
2936
- open: calendarOpen,
2937
- modal: true,
2938
- placement: "bottom-start",
2939
- disableFlipFallback: true,
2940
- disableTriggerFocus: true,
2941
- onClose: () => setCalendarOpen(false),
2942
- disableAutoUpdate: true,
2943
- onOutsidePress: () => setCalendarOpen(false),
2944
- children: [
2945
- /* @__PURE__ */ jsx(Popover.Trigger, { children: ({ ref: iRef }) => {
2946
- setPopoverTriggerRef(iRef);
2947
- return justTheField;
2948
- } }),
2949
- /* @__PURE__ */ jsx(Popover.Content, { ref: popoverContentRef, "data-testid": "calendar-popover", children: shouldShowCalendar && /* @__PURE__ */ jsx(
2950
- DateFieldRangeCalendar,
2951
- {
2952
- startDate,
2953
- endDate,
2954
- onKeyDown: handleCalendarKeyDown,
2955
- onSelection: handleCalendarSelection,
2956
- minDate,
2957
- maxDate,
2958
- unavailable
2959
- }
2960
- ) })
2961
- ]
2962
- }
2963
- ) });
2964
- };
2965
- DateFieldRange.displayName = "DateFieldRange";
2966
- function validateDateRange({
2967
- required,
2968
- startDate,
2969
- endDate,
2970
- startDateConstraints,
2971
- endDateConstraints
2972
- }) {
2973
- if (!required && !startDate && !endDate) return true;
2974
- return validateDate({
2975
- date: startDate,
2976
- constraints: { ...startDateConstraints, required: true }
2977
- }) && validateDate({
2978
- date: endDate,
2979
- constraints: { ...endDateConstraints, required: true }
2980
- }) && (!startDate || !endDate || startDate <= endDate);
2981
- }
2982
-
2983
- const yearlessDatePlaceholderMask = ({
2984
- mode,
2985
- separator = "/",
2986
- placeholder
2987
- }) => {
2988
- const dateOptions = maskitoDateOptionsGenerator({
2989
- mode,
2990
- separator
2991
- });
2992
- const { plugins, removePlaceholder, ...placeholderOptions } = maskitoWithPlaceholder(placeholder);
2993
- const datePlaceholderMask = {
2994
- ...dateOptions,
2995
- plugins: plugins.concat(dateOptions.plugins || []),
2996
- preprocessors: [
2997
- ...placeholderOptions.preprocessors,
2998
- ...dateOptions.preprocessors,
2999
- makeZeroShortcutPreprocessor(mode, separator)
3000
- ],
3001
- postprocessors: [
3002
- ...dateOptions.postprocessors,
3003
- ...placeholderOptions.postprocessors
3004
- ]
3005
- };
3006
- return { options: datePlaceholderMask, removePlaceholder };
3007
- };
3008
-
3009
- const MaskedYearlessDateInput = forwardRef(({ onChange, mode = "mm/dd", value, disableHint = false, ...props }, ref) => {
3010
- const placeholder = "__/__";
3011
- const [inputValue, setInputValue] = useState(placeholder);
3012
- const { options, removePlaceholder } = yearlessDatePlaceholderMask({
3013
- mode,
3014
- placeholder
3015
- });
3016
- const maskedInputRef = useMaskito({ options });
3017
- const inputRef = useRef(null);
3018
- const combinedRef = useMergeRefs$1([maskedInputRef, inputRef, ref]);
3019
- const previousValue = usePrevious(value);
3020
- const previousMode = usePrevious(mode);
3021
- useEffect(() => {
3022
- if (mode !== previousMode) {
3023
- setInputValue(
3024
- (oldInputValue) => swapMode(oldInputValue, previousMode ?? mode, mode)
3025
- );
3026
- }
3027
- }, [mode, previousMode]);
3028
- useEffect(() => {
3029
- if (previousValue?.day !== value?.day || previousValue?.month !== value?.month) {
3030
- if (value?.day && value?.month) {
3031
- setInputValue(stringifyYearlessDate(value.day, value.month, mode));
3032
- } else if (previousValue !== null && previousValue !== void 0 && (value === null || value?.day === null && value?.month === null)) {
3033
- setInputValue(placeholder);
3034
- } else {
3035
- return;
3036
- }
3037
- }
3038
- }, [value, mode, previousValue]);
3039
- const currentParsedData = useMemo(() => {
3040
- return parseInputValue(inputValue, mode, removePlaceholder);
3041
- }, [inputValue, mode, removePlaceholder]);
3042
- const handleChange = (event) => {
3043
- setInputValue(event.target.value);
3044
- const {
3045
- value: v,
3046
- isInputValid,
3047
- isInputEmpty
3048
- } = parseInputValue(event.target.value, mode, removePlaceholder);
3049
- onChange?.({
3050
- event,
3051
- value: v ?? currentParsedData.value ?? null,
3052
- isInputValid,
3053
- isInputEmpty
3054
- });
3055
- };
3056
- useImperativeHandle(ref, () => {
3057
- const input = inputRef.current;
3058
- if (!input) return null;
3059
- return Object.assign(input, {
3060
- setValue: (value2) => {
3061
- if (!value2) {
3062
- setInputValue(placeholder);
3063
- return;
3064
- }
3065
- setInputValue(
3066
- stringifyYearlessDate(value2.day ?? 1, value2.month ?? 1, mode)
3067
- );
3068
- }
3069
- });
3070
- }, [mode, placeholder]);
3071
- return /* @__PURE__ */ jsx(
3072
- TextField,
3073
- {
3074
- ref: combinedRef,
3075
- "data-month-value": value?.month,
3076
- "data-day-value": value?.day,
3077
- "data-input-valid": currentParsedData.isInputValid,
3078
- "data-input-empty": currentParsedData.isInputEmpty,
3079
- ...props,
3080
- showCounter: false,
3081
- value: inputValue,
3082
- onChange: handleChange,
3083
- hint: disableHint ? void 0 : `Format: ${mode}`
3084
- }
3085
- );
3086
- });
3087
- MaskedYearlessDateInput.displayName = "MaskedYearlessDateInput";
3088
- function parseInputValue(value, mode, removePlaceholder) {
3089
- const valueMinusPlaceholder = removePlaceholder(value);
3090
- const [segment1, segment2] = valueMinusPlaceholder.split("/");
3091
- const incompleteFirstSegment = segment1?.length !== 2;
3092
- const incompleteSecondSegment = segment2?.length !== 2;
3093
- if (mode === "dd/mm") {
3094
- return {
3095
- value: {
3096
- day: incompleteFirstSegment ? null : parseInt(segment1),
3097
- month: incompleteSecondSegment ? null : parseInt(segment2)
3098
- },
3099
- isInputValid: !incompleteFirstSegment && !incompleteSecondSegment,
3100
- isInputEmpty: valueMinusPlaceholder === ""
3101
- };
3102
- }
3103
- return {
3104
- value: {
3105
- day: incompleteSecondSegment ? null : parseInt(segment2),
3106
- month: incompleteFirstSegment ? null : parseInt(segment1)
3107
- },
3108
- isInputValid: !incompleteFirstSegment && !incompleteSecondSegment,
3109
- isInputEmpty: valueMinusPlaceholder === ""
3110
- };
3111
- }
3112
- function swapMode(inputString, previousMode, mode) {
3113
- const { day, month } = divideSegments(inputString, previousMode);
3114
- return stringifyYearlessDate(day, month, mode);
3115
- }
3116
- function divideSegments(value, mode) {
3117
- const [segment1, segment2] = value.split("/");
3118
- if (mode === "dd/mm") {
3119
- return { day: segment1, month: segment2 };
3120
- }
3121
- return { day: segment2, month: segment1 };
3122
- }
3123
- function stringifyYearlessDate(day, month, mode) {
3124
- const dd = day.toString().padStart(2, "0");
3125
- const mm = month.toString().padStart(2, "0");
3126
- if (mode === "dd/mm") {
3127
- return `${dd}/${mm}`;
3128
- }
3129
- return `${mm}/${dd}`;
3130
- }
3131
-
3132
- const useConditionalChange = ({
3133
- changeHandler,
3134
- compareFn
3135
- }) => {
3136
- const lastChangeRef = useRef(null);
3137
- const onChange = useCallback(
3138
- (change) => {
3139
- if (!!lastChangeRef.current && !compareFn(lastChangeRef.current, change)) {
3140
- return;
3141
- }
3142
- changeHandler?.(change);
3143
- lastChangeRef.current = change;
3144
- },
3145
- [changeHandler, compareFn]
3146
- );
3147
- return onChange;
3148
- };
3149
-
3150
- const DateFieldYearless = ({
3151
- value: valueProp,
3152
- defaultValue: defaultValueProp,
3153
- onChange,
3154
- minDate,
3155
- maxDate,
3156
- unavailable,
3157
- required,
3158
- ...restProps
3159
- }) => {
3160
- const [value, setValue] = useOptionallyControlledState({
3161
- controlledValue: valueProp,
3162
- defaultValue: defaultValueProp
3163
- });
3164
- const currentValidity = useMemo(
3165
- () => validateYearlessDate({
3166
- value: value ?? null,
3167
- constraints: {
3168
- required,
3169
- unavailable,
3170
- minDate,
3171
- maxDate
3172
- }
3173
- }),
3174
- [value, required, unavailable, minDate, maxDate]
3175
- );
3176
- const conditionalChange = useConditionalChange({
3177
- changeHandler: onChange,
3178
- compareFn: (a, b) => a.isInputValid !== b.isInputValid || a.isInputEmpty !== b.isInputEmpty || a.isValid !== b.isValid || a.value?.day !== b.value?.day || a.value?.month !== b.value?.month
3179
- });
3180
- const handleInputChange = (change) => {
3181
- const { event, value: value2, ...restChange } = change;
3182
- setValue(value2);
3183
- return conditionalChange({
3184
- ...restChange,
3185
- value: value2,
3186
- isValid: validateYearlessDate({
3187
- value: value2,
3188
- constraints: {
3189
- required,
3190
- unavailable,
3191
- minDate,
3192
- maxDate
3193
- }
3194
- })
3195
- });
3196
- };
3197
- return /* @__PURE__ */ jsx(
3198
- MaskedYearlessDateInput,
3199
- {
3200
- required,
3201
- ...restProps,
3202
- autoComplete: "off",
3203
- onChange: handleInputChange,
3204
- value,
3205
- "data-valid": currentValidity
3206
- }
3207
- );
3208
- };
3209
- DateFieldYearless.displayName = "DateFieldYearless";
3210
-
3211
- export { DateFieldRange as D, DateFieldSingle as a, DateFieldYearless as b };
3212
- //# sourceMappingURL=DateFieldYearless-DU0z2fEA-ByR2ixI5.js.map
2234
+ export { useMaskito as a, maskitoWithPlaceholder as b, maskitoParseDate as c, maskitoDateOptionsGenerator as d, maskitoTimeOptionsGenerator as e, maskitoDateRangeOptionsGenerator as m, usePopoverSupport as u };
2235
+ //# sourceMappingURL=usePopoverSupport-B9Lsqryr-DhZHMoNb.js.map