@servicetitan/anvil2 1.39.0 → 1.40.1

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 (173) hide show
  1. package/CHANGELOG.md +40 -12
  2. package/dist/Breadcrumbs-C_gan90a.js +36 -0
  3. package/dist/Breadcrumbs-C_gan90a.js.map +1 -0
  4. package/dist/{Breadcrumbs-DJbCkSeD.js → Breadcrumbs-D_jgwoN3-Dlw-weD8.js} +8 -38
  5. package/dist/Breadcrumbs-D_jgwoN3-Dlw-weD8.js.map +1 -0
  6. package/dist/Breadcrumbs.js +1 -1
  7. package/dist/{Calendar-BT6eeZDr-CSY_7I1M.js → Calendar-B2lMJY8r-Ddu4TfZW.js} +16 -15
  8. package/dist/Calendar-B2lMJY8r-Ddu4TfZW.js.map +1 -0
  9. package/dist/{Calendar-CIWOPMcu.js → Calendar-C099gPDT.js} +2 -2
  10. package/dist/{Calendar-CIWOPMcu.js.map → Calendar-C099gPDT.js.map} +1 -1
  11. package/dist/Calendar.js +1 -1
  12. package/dist/{Checkbox-BNrjUtHs-CfExXZer.js → Checkbox-B25R5cdM-CQSPNPUv.js} +2 -2
  13. package/dist/{Checkbox-BNrjUtHs-CfExXZer.js.map → Checkbox-B25R5cdM-CQSPNPUv.js.map} +1 -1
  14. package/dist/{Checkbox-ic-HmakR.js → Checkbox-BEM_rIup.js} +2 -2
  15. package/dist/{Checkbox-ic-HmakR.js.map → Checkbox-BEM_rIup.js.map} +1 -1
  16. package/dist/Checkbox.js +1 -1
  17. package/dist/{Combobox-DR7O7aaZ.js → Combobox-mdEmKiV9.js} +195 -391
  18. package/dist/Combobox-mdEmKiV9.js.map +1 -0
  19. package/dist/Combobox.css +43 -281
  20. package/dist/Combobox.js +1 -1
  21. package/dist/{DateField-CYT6udu8.js → DateField-Cwvw5EXY.js} +43 -43
  22. package/dist/DateField-Cwvw5EXY.js.map +1 -0
  23. package/dist/DateField.js +1 -1
  24. package/dist/DateFieldRange-BmokKaNf.js +22 -0
  25. package/dist/DateFieldRange-BmokKaNf.js.map +1 -0
  26. package/dist/DateFieldRange-DxR0h7Y6-CV2wAhJB.js +2759 -0
  27. package/dist/DateFieldRange-DxR0h7Y6-CV2wAhJB.js.map +1 -0
  28. package/dist/DateFieldRange.d.ts +2 -0
  29. package/dist/DateFieldRange.js +2 -0
  30. package/dist/DateFieldRange.js.map +1 -0
  31. package/dist/DateFieldSingle-BlKlfasz.js +22 -0
  32. package/dist/DateFieldSingle-BlKlfasz.js.map +1 -0
  33. package/dist/DateFieldSingle.d.ts +2 -0
  34. package/dist/DateFieldSingle.js +2 -0
  35. package/dist/DateFieldSingle.js.map +1 -0
  36. package/dist/{DaysOfTheWeek-DbjM8UPz.js → DaysOfTheWeek-CnTEWxtN.js} +2 -2
  37. package/dist/{DaysOfTheWeek-DbjM8UPz.js.map → DaysOfTheWeek-CnTEWxtN.js.map} +1 -1
  38. package/dist/DaysOfTheWeek.js +1 -1
  39. package/dist/{Dialog-BmeXBcs-.js → Dialog-CTf90W9W.js} +21 -26
  40. package/dist/Dialog-CTf90W9W.js.map +1 -0
  41. package/dist/Dialog.js +1 -1
  42. package/dist/{Drawer-8jQ-jaiv.js → Drawer-BmyhW9rQ.js} +2 -2
  43. package/dist/{Drawer-8jQ-jaiv.js.map → Drawer-BmyhW9rQ.js.map} +1 -1
  44. package/dist/Drawer.js +1 -1
  45. package/dist/{InputMask-CiHg25XE-DXRWDeku.js → InputMask-C9FmGrFp-BEgnXA0F.js} +2 -2
  46. package/dist/{InputMask-CiHg25XE-DXRWDeku.js.map → InputMask-C9FmGrFp-BEgnXA0F.js.map} +1 -1
  47. package/dist/{InputMask-DH14Bp7r.js → InputMask-CzTlJ1ya.js} +2 -2
  48. package/dist/{InputMask-DH14Bp7r.js.map → InputMask-CzTlJ1ya.js.map} +1 -1
  49. package/dist/InputMask.js +1 -1
  50. package/dist/{ListView-HBQy0Qmv.js → ListView-CULEOIJt.js} +3 -3
  51. package/dist/{ListView-HBQy0Qmv.js.map → ListView-CULEOIJt.js.map} +1 -1
  52. package/dist/ListView.js +1 -1
  53. package/dist/Menu-C9fOIYH7.js +60 -0
  54. package/dist/Menu-C9fOIYH7.js.map +1 -0
  55. package/dist/{Menu-CijOsL76.js → Menu-Dh8roQgP-De9kBXuN.js} +23 -73
  56. package/dist/Menu-Dh8roQgP-De9kBXuN.js.map +1 -0
  57. package/dist/{Menu.css → Menu-Dh8roQgP.css} +22 -21
  58. package/dist/Menu.js +1 -1
  59. package/dist/{Page-CdkxWab-.js → Page--tqLz6hu.js} +135 -105
  60. package/dist/Page--tqLz6hu.js.map +1 -0
  61. package/dist/Page.js +1 -1
  62. package/dist/Pagination.css +124 -0
  63. package/dist/Pagination.d.ts +6 -0
  64. package/dist/Pagination.js +430 -0
  65. package/dist/Pagination.js.map +1 -0
  66. package/dist/{Popover-CCcd-JhF.js → Popover-DVPij2bz.js} +2 -2
  67. package/dist/{Popover-CCcd-JhF.js.map → Popover-DVPij2bz.js.map} +1 -1
  68. package/dist/{Popover-CVCAWhdO-dI3x1kfe.js → Popover-DwWPCZH--BIhxtOlK.js} +3 -3
  69. package/dist/{Popover-CVCAWhdO-dI3x1kfe.js.map → Popover-DwWPCZH--BIhxtOlK.js.map} +1 -1
  70. package/dist/Popover.js +1 -1
  71. package/dist/{ProgressBar-BEDtxKDv.js → ProgressBar-DOtekmJS.js} +2 -2
  72. package/dist/{ProgressBar-BEDtxKDv.js.map → ProgressBar-DOtekmJS.js.map} +1 -1
  73. package/dist/{ProgressBar-CZhkKwaS-BppwbCSh.js → ProgressBar-Dg77Xf_k-CyD-A4pF.js} +22 -23
  74. package/dist/ProgressBar-Dg77Xf_k-CyD-A4pF.js.map +1 -0
  75. package/dist/ProgressBar.js +1 -1
  76. package/dist/{Radio-DiBn0-hf-CCkWosaL.js → Radio-BZ44ktjU-CZuSckW6.js} +3 -3
  77. package/dist/{Radio-DiBn0-hf-CCkWosaL.js.map → Radio-BZ44ktjU-CZuSckW6.js.map} +1 -1
  78. package/dist/{Radio-Sj9M1KAg.js → Radio-BpE1s8OT.js} +2 -2
  79. package/dist/{Radio-Sj9M1KAg.js.map → Radio-BpE1s8OT.js.map} +1 -1
  80. package/dist/Radio.js +1 -1
  81. package/dist/{SelectCard-CY8IVFDT-lXO3-mQ_.js → SelectCard-BaKoHPjf-BLXKVIys.js} +22 -22
  82. package/dist/SelectCard-BaKoHPjf-BLXKVIys.js.map +1 -0
  83. package/dist/SelectCard-BaKoHPjf.css +51 -0
  84. package/dist/SelectCard.js +1 -1
  85. package/dist/{SelectCardGroup-DLt9z9b8.js → SelectCardGroup-BVGPNZft.js} +2 -2
  86. package/dist/{SelectCardGroup-DLt9z9b8.js.map → SelectCardGroup-BVGPNZft.js.map} +1 -1
  87. package/dist/SelectTrigger-DMMw_LkZ.js +138 -0
  88. package/dist/SelectTrigger-DMMw_LkZ.js.map +1 -0
  89. package/dist/SelectTrigger.css +16 -0
  90. package/dist/SelectTrigger.d.ts +6 -0
  91. package/dist/SelectTrigger.js +2 -0
  92. package/dist/SelectTrigger.js.map +1 -0
  93. package/dist/SelectTriggerBase-A6_sIBpr-DfI0HLx2.js +301 -0
  94. package/dist/SelectTriggerBase-A6_sIBpr-DfI0HLx2.js.map +1 -0
  95. package/dist/SelectTriggerBase-A6_sIBpr.css +270 -0
  96. package/dist/{TextField-C5KbQxoU-DGLAOhCu.js → TextField-CG6Nv-0C-jX4uvHT8.js} +2 -2
  97. package/dist/{TextField-C5KbQxoU-DGLAOhCu.js.map → TextField-CG6Nv-0C-jX4uvHT8.js.map} +1 -1
  98. package/dist/{TextField-DUohb_Y6.js → TextField-DoqYMou3.js} +2 -2
  99. package/dist/{TextField-DUohb_Y6.js.map → TextField-DoqYMou3.js.map} +1 -1
  100. package/dist/TextField.js +1 -1
  101. package/dist/{Textarea-HGQXwvO1.js → Textarea-6omWLtXb.js} +2 -2
  102. package/dist/{Textarea-HGQXwvO1.js.map → Textarea-6omWLtXb.js.map} +1 -1
  103. package/dist/Textarea.js +1 -1
  104. package/dist/Toast.js +1 -1
  105. package/dist/{Toolbar-28Uv31qc.js → Toolbar-B7NKUkgL.js} +5 -7
  106. package/dist/Toolbar-B7NKUkgL.js.map +1 -0
  107. package/dist/Toolbar.js +1 -1
  108. package/dist/{Tooltip-ZUko7Bl3.js → Tooltip-BcUIIzkM.js} +2 -2
  109. package/dist/{Tooltip-ZUko7Bl3.js.map → Tooltip-BcUIIzkM.js.map} +1 -1
  110. package/dist/Tooltip.js +1 -1
  111. package/dist/assets/icons/st/document_audio.svg +1 -0
  112. package/dist/assets/icons/st/document_doc.svg +1 -0
  113. package/dist/assets/icons/st/document_drawing.svg +1 -0
  114. package/dist/assets/icons/st/document_form.svg +1 -0
  115. package/dist/assets/icons/st/document_message.svg +1 -0
  116. package/dist/assets/icons/st/document_other.svg +1 -0
  117. package/dist/assets/icons/st/document_pdf.svg +1 -0
  118. package/dist/assets/icons/st/document_spreadsheet.svg +1 -0
  119. package/dist/assets/icons/st/document_text.svg +1 -0
  120. package/dist/assets/icons/st/document_web.svg +1 -0
  121. package/dist/assets/icons/st/gnav_insurance_work_queue_active.svg +1 -0
  122. package/dist/assets/icons/st/gnav_insurance_work_queue_inactive.svg +1 -0
  123. package/dist/assets/icons/st/gnav_production_work_queue_active.svg +1 -0
  124. package/dist/assets/icons/st/gnav_production_work_queue_inactive.svg +1 -0
  125. package/dist/assets/icons/st.ts +14 -0
  126. package/dist/components/Alert/Alert.figma.d.ts +1 -0
  127. package/dist/components/DateFieldRange/DateFieldRange.d.ts +7 -0
  128. package/dist/components/DateFieldRange/index.d.ts +2 -0
  129. package/dist/components/DateFieldSingle/DateFieldSingle.d.ts +7 -0
  130. package/dist/components/DateFieldSingle/index.d.ts +2 -0
  131. package/dist/components/Page/Page.d.ts +19 -352
  132. package/dist/components/Page/PageContent.d.ts +24 -0
  133. package/dist/components/Page/PageContext.d.ts +4 -0
  134. package/dist/components/Page/PageFooter.d.ts +24 -0
  135. package/dist/components/Page/PageHeader.d.ts +135 -0
  136. package/dist/components/Page/PagePanel.d.ts +57 -0
  137. package/dist/components/Page/PageSidebar.d.ts +57 -0
  138. package/dist/components/Page/PageSidebarContext.d.ts +5 -0
  139. package/dist/components/Page/PageSidebarHeader.d.ts +23 -0
  140. package/dist/components/Page/index.d.ts +5 -0
  141. package/dist/components/Pagination/Pagination.d.ts +58 -0
  142. package/dist/components/Pagination/index.d.ts +2 -0
  143. package/dist/components/Pagination/internal/usePaginationArray.d.ts +36 -0
  144. package/dist/components/SelectTrigger/SelectTrigger.d.ts +11 -0
  145. package/dist/components/SelectTrigger/index.d.ts +2 -0
  146. package/dist/components/index.d.ts +3 -0
  147. package/dist/event-BEJFimi3.js +6 -0
  148. package/dist/event-BEJFimi3.js.map +1 -0
  149. package/dist/index.js +24 -21
  150. package/dist/index.js.map +1 -1
  151. package/dist/keyboard_arrow_right-DZWNVytH.js +8 -0
  152. package/dist/keyboard_arrow_right-DZWNVytH.js.map +1 -0
  153. package/dist/more_horiz-DJgdQiy0.js +6 -0
  154. package/dist/more_horiz-DJgdQiy0.js.map +1 -0
  155. package/dist/{toast-DjypuZMf.js → toast-ByZDutpT.js} +2 -2
  156. package/dist/{toast-DjypuZMf.js.map → toast-ByZDutpT.js.map} +1 -1
  157. package/package.json +7 -4
  158. package/dist/Breadcrumbs-DJbCkSeD.js.map +0 -1
  159. package/dist/Calendar-BT6eeZDr-CSY_7I1M.js.map +0 -1
  160. package/dist/Combobox-DR7O7aaZ.js.map +0 -1
  161. package/dist/DateField-CYT6udu8.js.map +0 -1
  162. package/dist/Dialog-BmeXBcs-.js.map +0 -1
  163. package/dist/Menu-CijOsL76.js.map +0 -1
  164. package/dist/Page-CdkxWab-.js.map +0 -1
  165. package/dist/ProgressBar-CZhkKwaS-BppwbCSh.js.map +0 -1
  166. package/dist/SelectCard-CY8IVFDT-lXO3-mQ_.js.map +0 -1
  167. package/dist/SelectCard-CY8IVFDT.css +0 -38
  168. package/dist/Toolbar-28Uv31qc.js.map +0 -1
  169. /package/dist/{Breadcrumbs.css → Breadcrumbs-D_jgwoN3.css} +0 -0
  170. /package/dist/{Calendar-BT6eeZDr.css → Calendar-B2lMJY8r.css} +0 -0
  171. /package/dist/{Popover-CVCAWhdO.css → Popover-DwWPCZH-.css} +0 -0
  172. /package/dist/{ProgressBar-CZhkKwaS.css → ProgressBar-Dg77Xf_k.css} +0 -0
  173. /package/dist/{Radio-DiBn0-hf.css → Radio-BZ44ktjU.css} +0 -0
@@ -0,0 +1,2759 @@
1
+ import { jsx, jsxs } from 'react/jsx-runtime';
2
+ import { useState, useCallback, useRef, useLayoutEffect, useEffect, useMemo, forwardRef, useImperativeHandle } from 'react';
3
+ import { D as DateTime, f as Calendar } from './Calendar-B2lMJY8r-Ddu4TfZW.js';
4
+ import { T as TextField } from './TextField-CG6Nv-0C-jX4uvHT8.js';
5
+ import { u as useMergeRefs$1 } from './floating-ui.react-BFNinq1w.js';
6
+ import { I as Icon } from './Icon-B6HmlQiR-BxQkO3X5.js';
7
+ import { S as SvgEvent } from './event-BEJFimi3.js';
8
+ import { d as Popover } from './Popover-DwWPCZH--BIhxtOlK.js';
9
+ import { u as useMergeRefs } from './useMergeRefs-Bde85AWI-Bde85AWI.js';
10
+ import { u as useOptionallyControlledState } from './useOptionallyControlledState-DAv5LXXh-DAv5LXXh.js';
11
+ import { s as supportsPopover, u as useKeyboardFocusables } from './ProgressBar-Dg77Xf_k-CyD-A4pF.js';
12
+
13
+ function getContentEditableSelection(element) {
14
+ const { anchorOffset = 0, focusOffset = 0 } = element.ownerDocument.getSelection() || {};
15
+ const from = Math.min(anchorOffset, focusOffset);
16
+ const to = Math.max(anchorOffset, focusOffset);
17
+ return [from, to];
18
+ }
19
+
20
+ function setContentEditableSelection(element, [from, to]) {
21
+ var _a, _b, _c, _d;
22
+ const document = element.ownerDocument;
23
+ const range = document.createRange();
24
+ range.setStart(element.firstChild || element, Math.min(from, (_b = (_a = element.textContent) === null || _a === void 0 ? void 0 : _a.length) !== null && _b !== void 0 ? _b : 0));
25
+ range.setEnd(element.lastChild || element, Math.min(to, (_d = (_c = element.textContent) === null || _c === void 0 ? void 0 : _c.length) !== null && _d !== void 0 ? _d : 0));
26
+ const selection = document.getSelection();
27
+ if (selection) {
28
+ selection.removeAllRanges();
29
+ selection.addRange(range);
30
+ }
31
+ }
32
+
33
+ class ContentEditableAdapter {
34
+ constructor(element) {
35
+ this.element = element;
36
+ this.maxLength = Infinity;
37
+ }
38
+ get value() {
39
+ return this.element.innerText.replace(/\n\n$/, '\n');
40
+ }
41
+ set value(value) {
42
+ // Setting into innerHTML of element with `white-space: pre;` style
43
+ this.element.innerHTML = value.replace(/\n$/, '\n\n');
44
+ }
45
+ get selectionStart() {
46
+ return getContentEditableSelection(this.element)[0];
47
+ }
48
+ get selectionEnd() {
49
+ return getContentEditableSelection(this.element)[1];
50
+ }
51
+ setSelectionRange(from, to) {
52
+ setContentEditableSelection(this.element, [from !== null && from !== void 0 ? from : 0, to !== null && to !== void 0 ? to : 0]);
53
+ }
54
+ select() {
55
+ this.setSelectionRange(0, this.value.length);
56
+ }
57
+ }
58
+ function maskitoAdaptContentEditable(element) {
59
+ const adapter = new ContentEditableAdapter(element);
60
+ return new Proxy(element, {
61
+ get(target, prop) {
62
+ if (prop in adapter) {
63
+ return adapter[prop];
64
+ }
65
+ const nativeProperty = target[prop];
66
+ return typeof nativeProperty === 'function'
67
+ ? nativeProperty.bind(target)
68
+ : nativeProperty;
69
+ },
70
+ // eslint-disable-next-line @typescript-eslint/max-params
71
+ set(target, prop, val, receiver) {
72
+ return Reflect.set(prop in adapter ? adapter : target, prop, val, receiver);
73
+ },
74
+ });
75
+ }
76
+
77
+ const MASKITO_DEFAULT_ELEMENT_PREDICATE = (e) => e.isContentEditable
78
+ ? maskitoAdaptContentEditable(e)
79
+ : e.querySelector('input,textarea') ||
80
+ e;
81
+
82
+ const MASKITO_DEFAULT_OPTIONS = {
83
+ mask: /^.*$/,
84
+ preprocessors: [],
85
+ postprocessors: [],
86
+ plugins: [],
87
+ overwriteMode: 'shift',
88
+ };
89
+
90
+ class MaskHistory {
91
+ constructor() {
92
+ this.now = null;
93
+ this.past = [];
94
+ this.future = [];
95
+ }
96
+ undo() {
97
+ const state = this.past.pop();
98
+ if (state && this.now) {
99
+ this.future.push(this.now);
100
+ this.updateElement(state, 'historyUndo');
101
+ }
102
+ }
103
+ redo() {
104
+ const state = this.future.pop();
105
+ if (state && this.now) {
106
+ this.past.push(this.now);
107
+ this.updateElement(state, 'historyRedo');
108
+ }
109
+ }
110
+ updateHistory(state) {
111
+ if (!this.now) {
112
+ this.now = state;
113
+ return;
114
+ }
115
+ const isValueChanged = this.now.value !== state.value;
116
+ const isSelectionChanged = this.now.selection.some((item, index) => item !== state.selection[index]);
117
+ if (!isValueChanged && !isSelectionChanged) {
118
+ return;
119
+ }
120
+ if (isValueChanged) {
121
+ this.past.push(this.now);
122
+ this.future = [];
123
+ }
124
+ this.now = state;
125
+ }
126
+ updateElement(state, inputType) {
127
+ this.now = state;
128
+ this.updateElementState(state, { inputType, data: null });
129
+ }
130
+ }
131
+
132
+ function areElementValuesEqual(sampleState, ...states) {
133
+ return states.every(({ value }) => value === sampleState.value);
134
+ }
135
+ function areElementStatesEqual(sampleState, ...states) {
136
+ return states.every(({ value, selection }) => value === sampleState.value &&
137
+ selection[0] === sampleState.selection[0] &&
138
+ selection[1] === sampleState.selection[1]);
139
+ }
140
+
141
+ function applyOverwriteMode({ value, selection }, newCharacters, mode) {
142
+ const [from, to] = selection;
143
+ const computedMode = typeof mode === 'function' ? mode({ value, selection }) : mode;
144
+ return {
145
+ value,
146
+ selection: computedMode === 'replace'
147
+ ? [from, Math.max(from + newCharacters.length, to)]
148
+ : [from, to],
149
+ };
150
+ }
151
+
152
+ function isFixedCharacter(char) {
153
+ return typeof char === 'string';
154
+ }
155
+
156
+ // eslint-disable-next-line @typescript-eslint/max-params
157
+ function getLeadingFixedCharacters(mask, validatedValuePart, newCharacter, initialElementState) {
158
+ let leadingFixedCharacters = '';
159
+ for (let i = validatedValuePart.length; i < mask.length; i++) {
160
+ const charConstraint = mask[i] || '';
161
+ const isInitiallyExisted = (initialElementState === null || initialElementState === void 0 ? void 0 : initialElementState.value[i]) === charConstraint;
162
+ if (!isFixedCharacter(charConstraint) ||
163
+ (charConstraint === newCharacter && !isInitiallyExisted)) {
164
+ return leadingFixedCharacters;
165
+ }
166
+ leadingFixedCharacters += charConstraint;
167
+ }
168
+ return leadingFixedCharacters;
169
+ }
170
+
171
+ function validateValueWithMask(value, maskExpression) {
172
+ if (Array.isArray(maskExpression)) {
173
+ return (value.length === maskExpression.length &&
174
+ Array.from(value).every((char, i) => {
175
+ const charConstraint = maskExpression[i] || '';
176
+ return isFixedCharacter(charConstraint)
177
+ ? char === charConstraint
178
+ : char.match(charConstraint);
179
+ }));
180
+ }
181
+ return maskExpression.test(value);
182
+ }
183
+
184
+ function guessValidValueByPattern(elementState, mask, initialElementState) {
185
+ let maskedFrom = null;
186
+ let maskedTo = null;
187
+ const maskedValue = Array.from(elementState.value).reduce((validatedCharacters, char, charIndex) => {
188
+ const leadingCharacters = getLeadingFixedCharacters(mask, validatedCharacters, char, initialElementState);
189
+ const newValidatedChars = validatedCharacters + leadingCharacters;
190
+ const charConstraint = mask[newValidatedChars.length] || '';
191
+ if (maskedFrom === null && charIndex >= elementState.selection[0]) {
192
+ maskedFrom = newValidatedChars.length;
193
+ }
194
+ if (maskedTo === null && charIndex >= elementState.selection[1]) {
195
+ maskedTo = newValidatedChars.length;
196
+ }
197
+ if (isFixedCharacter(charConstraint)) {
198
+ return newValidatedChars + charConstraint;
199
+ }
200
+ if (char.match(charConstraint)) {
201
+ return newValidatedChars + char;
202
+ }
203
+ return leadingCharacters.startsWith(char)
204
+ ? newValidatedChars
205
+ : validatedCharacters;
206
+ }, '');
207
+ const trailingFixedCharacters = getLeadingFixedCharacters(mask, maskedValue, '', initialElementState);
208
+ return {
209
+ value: validateValueWithMask(maskedValue + trailingFixedCharacters, mask)
210
+ ? maskedValue + trailingFixedCharacters
211
+ : maskedValue,
212
+ selection: [maskedFrom !== null && maskedFrom !== void 0 ? maskedFrom : maskedValue.length, maskedTo !== null && maskedTo !== void 0 ? maskedTo : maskedValue.length],
213
+ };
214
+ }
215
+
216
+ function guessValidValueByRegExp({ value, selection }, maskRegExp) {
217
+ const [from, to] = selection;
218
+ let newFrom = from;
219
+ let newTo = to;
220
+ const validatedValue = Array.from(value).reduce((validatedValuePart, char, i) => {
221
+ const newPossibleValue = validatedValuePart + char;
222
+ if (from === i) {
223
+ newFrom = validatedValuePart.length;
224
+ }
225
+ if (to === i) {
226
+ newTo = validatedValuePart.length;
227
+ }
228
+ return newPossibleValue.match(maskRegExp) ? newPossibleValue : validatedValuePart;
229
+ }, '');
230
+ return {
231
+ value: validatedValue,
232
+ selection: [
233
+ Math.min(newFrom, validatedValue.length),
234
+ Math.min(newTo, validatedValue.length),
235
+ ],
236
+ };
237
+ }
238
+
239
+ function calibrateValueByMask(elementState, mask, initialElementState = null) {
240
+ if (validateValueWithMask(elementState.value, mask)) {
241
+ return elementState;
242
+ }
243
+ const { value, selection } = Array.isArray(mask)
244
+ ? guessValidValueByPattern(elementState, mask, initialElementState)
245
+ : guessValidValueByRegExp(elementState, mask);
246
+ return {
247
+ selection,
248
+ value: Array.isArray(mask) ? value.slice(0, mask.length) : value,
249
+ };
250
+ }
251
+
252
+ function removeFixedMaskCharacters(initialElementState, mask) {
253
+ if (!Array.isArray(mask)) {
254
+ return initialElementState;
255
+ }
256
+ const [from, to] = initialElementState.selection;
257
+ const selection = [];
258
+ const unmaskedValue = Array.from(initialElementState.value).reduce((rawValue, char, i) => {
259
+ const charConstraint = mask[i] || '';
260
+ if (i === from) {
261
+ selection.push(rawValue.length);
262
+ }
263
+ if (i === to) {
264
+ selection.push(rawValue.length);
265
+ }
266
+ return isFixedCharacter(charConstraint) && charConstraint === char
267
+ ? rawValue
268
+ : rawValue + char;
269
+ }, '');
270
+ if (selection.length < 2) {
271
+ selection.push(...new Array(2 - selection.length).fill(unmaskedValue.length));
272
+ }
273
+ return {
274
+ value: unmaskedValue,
275
+ selection: [selection[0], selection[1]],
276
+ };
277
+ }
278
+
279
+ class MaskModel {
280
+ constructor(initialElementState, maskOptions) {
281
+ this.maskOptions = maskOptions;
282
+ this.unmaskInitialState = { value: '', selection: [0, 0] };
283
+ this.value = '';
284
+ this.selection = [0, 0];
285
+ const expression = this.getMaskExpression(initialElementState);
286
+ const { value, selection } = calibrateValueByMask(initialElementState, expression);
287
+ this.unmaskInitialState = removeFixedMaskCharacters({ value, selection }, expression);
288
+ this.value = value;
289
+ this.selection = selection;
290
+ }
291
+ addCharacters(newCharacters) {
292
+ const { value, selection, maskOptions } = this;
293
+ const initialElementState = { value, selection };
294
+ const { selection: [from, to], } = applyOverwriteMode(initialElementState, newCharacters, maskOptions.overwriteMode);
295
+ const maskExpression = this.getMaskExpression({
296
+ value: value.slice(0, from) + newCharacters + value.slice(to),
297
+ selection: [from + newCharacters.length, from + newCharacters.length],
298
+ });
299
+ const [unmaskedFrom, unmaskedTo] = applyOverwriteMode(this.unmaskInitialState, newCharacters, maskOptions.overwriteMode).selection;
300
+ const newUnmaskedLeadingValuePart = this.unmaskInitialState.value.slice(0, unmaskedFrom) + newCharacters;
301
+ const newCaretIndex = newUnmaskedLeadingValuePart.length;
302
+ const maskedElementState = calibrateValueByMask({
303
+ value: newUnmaskedLeadingValuePart +
304
+ this.unmaskInitialState.value.slice(unmaskedTo),
305
+ selection: [newCaretIndex, newCaretIndex],
306
+ }, maskExpression, initialElementState);
307
+ const prevLeadingPart = value.slice(0, from);
308
+ const newLeadingValuePart = calibrateValueByMask({
309
+ value: newUnmaskedLeadingValuePart,
310
+ selection: [newCaretIndex, newCaretIndex],
311
+ }, maskExpression, initialElementState).value;
312
+ const isInvalidCharsInsertion = prevLeadingPart === newLeadingValuePart ||
313
+ newLeadingValuePart.length < prevLeadingPart.length;
314
+ if (isInvalidCharsInsertion ||
315
+ areElementStatesEqual(this, maskedElementState) // If typing new characters does not change value
316
+ ) {
317
+ throw new Error('Invalid mask value');
318
+ }
319
+ this.value = maskedElementState.value;
320
+ this.selection = maskedElementState.selection;
321
+ }
322
+ deleteCharacters() {
323
+ const [from, to] = this.selection;
324
+ if (from === to || !to) {
325
+ return;
326
+ }
327
+ const { value } = this;
328
+ const maskExpression = this.getMaskExpression({
329
+ value: value.slice(0, from) + value.slice(to),
330
+ selection: [from, from],
331
+ });
332
+ const initialElementState = { value};
333
+ const [unmaskedFrom, unmaskedTo] = this.unmaskInitialState.selection;
334
+ const newUnmaskedValue = this.unmaskInitialState.value.slice(0, unmaskedFrom) +
335
+ this.unmaskInitialState.value.slice(unmaskedTo);
336
+ const maskedElementState = calibrateValueByMask({ value: newUnmaskedValue, selection: [unmaskedFrom, unmaskedFrom] }, maskExpression, initialElementState);
337
+ this.value = maskedElementState.value;
338
+ this.selection = maskedElementState.selection;
339
+ }
340
+ getMaskExpression(elementState) {
341
+ const { mask } = this.maskOptions;
342
+ return typeof mask === 'function' ? mask(elementState) : mask;
343
+ }
344
+ }
345
+
346
+ class EventListener {
347
+ constructor(element) {
348
+ this.element = element;
349
+ this.listeners = [];
350
+ }
351
+ listen(eventType, fn, options) {
352
+ const untypedFn = fn;
353
+ this.element.addEventListener(eventType, untypedFn, options);
354
+ this.listeners.push(() => this.element.removeEventListener(eventType, untypedFn, options));
355
+ }
356
+ destroy() {
357
+ this.listeners.forEach((stopListen) => stopListen());
358
+ }
359
+ }
360
+
361
+ const HotkeyModifier = {
362
+ CTRL: 1 << 0,
363
+ ALT: 1 << 1,
364
+ SHIFT: 1 << 2,
365
+ META: 1 << 3,
366
+ };
367
+ // TODO add variants that can be processed correctly
368
+ const HotkeyCode = {
369
+ Y: 89,
370
+ Z: 90,
371
+ };
372
+ /**
373
+ * Checks if the passed keyboard event match the required hotkey.
374
+ *
375
+ * @example
376
+ * input.addEventListener('keydown', (event) => {
377
+ * if (isHotkey(event, HotkeyModifier.CTRL | HotkeyModifier.SHIFT, HotkeyCode.Z)) {
378
+ * // redo hotkey pressed
379
+ * }
380
+ * })
381
+ *
382
+ * @return will return `true` only if the {@link HotkeyCode} matches and only the necessary
383
+ * {@link HotkeyModifier modifiers} have been pressed
384
+ */
385
+ function isHotkey(event, modifiers, hotkeyCode) {
386
+ return (event.ctrlKey === !!(modifiers & HotkeyModifier.CTRL) &&
387
+ event.altKey === !!(modifiers & HotkeyModifier.ALT) &&
388
+ event.shiftKey === !!(modifiers & HotkeyModifier.SHIFT) &&
389
+ event.metaKey === !!(modifiers & HotkeyModifier.META) &&
390
+ /**
391
+ * We intentionally use legacy {@link KeyboardEvent#keyCode `keyCode`} property. It is more
392
+ * "keyboard-layout"-independent than {@link KeyboardEvent#key `key`} or {@link KeyboardEvent#code `code`} properties.
393
+ * @see {@link https://github.com/taiga-family/maskito/issues/315 `KeyboardEvent#code` issue}
394
+ */
395
+ event.keyCode === hotkeyCode);
396
+ }
397
+
398
+ function isRedo(event) {
399
+ return (isHotkey(event, HotkeyModifier.CTRL, HotkeyCode.Y) || // Windows
400
+ isHotkey(event, HotkeyModifier.CTRL | HotkeyModifier.SHIFT, HotkeyCode.Z) || // Windows & Android
401
+ isHotkey(event, HotkeyModifier.META | HotkeyModifier.SHIFT, HotkeyCode.Z) // macOS & iOS
402
+ );
403
+ }
404
+ function isUndo(event) {
405
+ return (isHotkey(event, HotkeyModifier.CTRL, HotkeyCode.Z) || // Windows & Android
406
+ isHotkey(event, HotkeyModifier.META, HotkeyCode.Z) // macOS & iOS
407
+ );
408
+ }
409
+
410
+ /**
411
+ * Sets value to element, and dispatches input event
412
+ * if you passed ELementState, it also sets selection range
413
+ *
414
+ * @example
415
+ * maskitoUpdateElement(input, newValue);
416
+ * maskitoUpdateElement(input, elementState);
417
+ *
418
+ * @see {@link https://github.com/taiga-family/maskito/issues/804 issue}
419
+ *
420
+ * @return void
421
+ */
422
+ function maskitoUpdateElement(element, valueOrElementState) {
423
+ var _a;
424
+ const initialValue = element.value;
425
+ if (typeof valueOrElementState === 'string') {
426
+ element.value = valueOrElementState;
427
+ }
428
+ else {
429
+ const [from, to] = valueOrElementState.selection;
430
+ element.value = valueOrElementState.value;
431
+ if (element.matches(':focus')) {
432
+ (_a = element.setSelectionRange) === null || _a === void 0 ? void 0 : _a.call(element, from, to);
433
+ }
434
+ }
435
+ if (element.value !== initialValue) {
436
+ element.dispatchEvent(new Event('input',
437
+ /**
438
+ * React handles this event only on bubbling phase
439
+ *
440
+ * here is the list of events that are processed in the capture stage, others are processed in the bubbling stage
441
+ * https://github.com/facebook/react/blob/cb2439624f43c510007f65aea5c50a8bb97917e4/packages/react-dom-bindings/src/events/DOMPluginEventSystem.js#L222
442
+ */
443
+ { bubbles: true }));
444
+ }
445
+ }
446
+
447
+ function getLineSelection({ value, selection }, isForward) {
448
+ const [from, to] = selection;
449
+ if (from !== to) {
450
+ return [from, to];
451
+ }
452
+ const nearestBreak = isForward
453
+ ? value.slice(from).indexOf('\n') + 1 || value.length
454
+ : value.slice(0, to).lastIndexOf('\n') + 1;
455
+ const selectFrom = isForward ? from : nearestBreak;
456
+ const selectTo = isForward ? nearestBreak : to;
457
+ return [selectFrom, selectTo];
458
+ }
459
+
460
+ function getNotEmptySelection({ value, selection }, isForward) {
461
+ const [from, to] = selection;
462
+ if (from !== to) {
463
+ return [from, to];
464
+ }
465
+ const notEmptySelection = isForward ? [from, to + 1] : [from - 1, to];
466
+ return notEmptySelection.map((x) => Math.min(Math.max(x, 0), value.length));
467
+ }
468
+
469
+ const TRAILING_SPACES_REG = /\s+$/g;
470
+ const LEADING_SPACES_REG = /^\s+/g;
471
+ const SPACE_REG = /\s/;
472
+ function getWordSelection({ value, selection }, isForward) {
473
+ const [from, to] = selection;
474
+ if (from !== to) {
475
+ return [from, to];
476
+ }
477
+ if (isForward) {
478
+ const valueAfterSelectionStart = value.slice(from);
479
+ const [leadingSpaces] = valueAfterSelectionStart.match(LEADING_SPACES_REG) || [
480
+ '',
481
+ ];
482
+ const nearestWordEndIndex = valueAfterSelectionStart
483
+ .trimStart()
484
+ .search(SPACE_REG);
485
+ return [
486
+ from,
487
+ nearestWordEndIndex !== -1
488
+ ? from + leadingSpaces.length + nearestWordEndIndex
489
+ : value.length,
490
+ ];
491
+ }
492
+ const valueBeforeSelectionEnd = value.slice(0, to);
493
+ const [trailingSpaces] = valueBeforeSelectionEnd.match(TRAILING_SPACES_REG) || [''];
494
+ const selectedWordLength = valueBeforeSelectionEnd
495
+ .trimEnd()
496
+ .split('')
497
+ .reverse()
498
+ .findIndex((char) => SPACE_REG.exec(char));
499
+ return [
500
+ selectedWordLength !== -1 ? to - trailingSpaces.length - selectedWordLength : 0,
501
+ to,
502
+ ];
503
+ }
504
+
505
+ /* eslint-disable @typescript-eslint/no-restricted-types */
506
+ /**
507
+ * @internal
508
+ */
509
+ function maskitoPipe(processors = []) {
510
+ return (initialData, ...readonlyArgs) => processors.reduce((data, fn) => (Object.assign(Object.assign({}, data), fn(data, ...readonlyArgs))), initialData);
511
+ }
512
+
513
+ function maskitoTransform(valueOrState, maskitoOptions) {
514
+ const options = Object.assign(Object.assign({}, MASKITO_DEFAULT_OPTIONS), maskitoOptions);
515
+ const preprocessor = maskitoPipe(options.preprocessors);
516
+ const postprocessor = maskitoPipe(options.postprocessors);
517
+ const initialElementState = typeof valueOrState === 'string'
518
+ ? { value: valueOrState, selection: [0, 0] }
519
+ : valueOrState;
520
+ const { elementState } = preprocessor({ elementState: initialElementState, data: '' }, 'validation');
521
+ const maskModel = new MaskModel(elementState, options);
522
+ const { value, selection } = postprocessor(maskModel, initialElementState);
523
+ return typeof valueOrState === 'string' ? value : { value, selection };
524
+ }
525
+
526
+ /**
527
+ * All `input` events with `inputType=deleteContentBackward` always follows `beforeinput` event with the same `inputType`.
528
+ * If `beforeinput[inputType=deleteContentBackward]` is prevented, subsequent `input[inputType=deleteContentBackward]` is prevented too.
529
+ * There is an exception – Android devices with Microsoft SwiftKey Keyboard in Mobile Chrome.
530
+ * These devices ignores `preventDefault` for `beforeinput` event if Backspace is pressed.
531
+ * @see https://github.com/taiga-family/maskito/issues/2135#issuecomment-2980729647
532
+ * ___
533
+ * TODO: track Chromium bug report and delete this plugin after bug fix
534
+ * https://issues.chromium.org/issues/40885402
535
+ */
536
+ function createBrokenDefaultPlugin() {
537
+ return (element) => {
538
+ const eventListener = new EventListener(element);
539
+ let isVirtualAndroidKeyboard = false;
540
+ let beforeinputEvent;
541
+ let value = element.value;
542
+ eventListener.listen('keydown', ({ key }) => {
543
+ isVirtualAndroidKeyboard = key === 'Unidentified';
544
+ });
545
+ eventListener.listen('beforeinput', (event) => {
546
+ beforeinputEvent = event;
547
+ value = element.value;
548
+ });
549
+ eventListener.listen('input', (event) => {
550
+ if (isVirtualAndroidKeyboard &&
551
+ beforeinputEvent.defaultPrevented &&
552
+ beforeinputEvent.inputType === 'deleteContentBackward' &&
553
+ event.inputType === 'deleteContentBackward') {
554
+ element.value = value;
555
+ }
556
+ }, { capture: true });
557
+ return () => eventListener.destroy();
558
+ };
559
+ }
560
+
561
+ const SPACE = ' ';
562
+ /**
563
+ * 1. Android user (with G-board keyboard or similar) presses 1st space
564
+ * ```
565
+ * {type: "beforeinput", data: " ", inputType: "insertText"}
566
+ * ```
567
+ * 2. User presses 2nd space
568
+ * ```
569
+ * // Android tries to delete previously inserted space
570
+ * {type: "beforeinput", inputType: "deleteContentBackward"}
571
+ * {type: "beforeinput", data: ". ", inputType: "insertText"}
572
+ * ```
573
+ * ---------
574
+ * 1. MacOS user presses 1st space
575
+ * ```
576
+ * {type: "beforeinput", data: " ", inputType: "insertText"}
577
+ * ```
578
+ * 2. User presses 2nd space
579
+ * ```
580
+ * // MacOS automatically run `element.setSelectionRange(indexBeforeSpace, indexAfterSpace)` and then
581
+ * {type: "beforeinput", data: ". ", inputType: "insertText"}
582
+ * ```
583
+ * ---------
584
+ * @see https://github.com/taiga-family/maskito/issues/2023
585
+ */
586
+ function createDoubleSpacePlugin() {
587
+ let prevValue = '';
588
+ let prevCaretIndex = 0;
589
+ let prevEvent = null;
590
+ let prevRejectedSpace = false;
591
+ return (element) => {
592
+ const eventListener = new EventListener(element);
593
+ eventListener.listen('beforeinput', (event) => {
594
+ var _a, _b;
595
+ const { value, selectionStart, selectionEnd } = element;
596
+ const rejectedSpace = (prevEvent === null || prevEvent === void 0 ? void 0 : prevEvent.inputType) === 'insertText' &&
597
+ (prevEvent === null || prevEvent === void 0 ? void 0 : prevEvent.data) === SPACE &&
598
+ !value.slice(0, Number(selectionEnd)).endsWith(SPACE);
599
+ if (event.inputType === 'insertText' && event.data === `.${SPACE}`) {
600
+ if ((prevEvent === null || prevEvent === void 0 ? void 0 : prevEvent.inputType) === 'deleteContentBackward' &&
601
+ prevRejectedSpace) {
602
+ // Android
603
+ element.value = prevValue;
604
+ (_a = element.setSelectionRange) === null || _a === void 0 ? void 0 : _a.call(element, prevCaretIndex, prevCaretIndex);
605
+ }
606
+ else if (rejectedSpace) {
607
+ // Mac OS
608
+ (_b = element.setSelectionRange) === null || _b === void 0 ? void 0 : _b.call(element, selectionStart, selectionStart);
609
+ }
610
+ }
611
+ prevRejectedSpace = rejectedSpace;
612
+ prevEvent = event;
613
+ prevValue = value;
614
+ prevCaretIndex = Number((rejectedSpace ? prevCaretIndex : selectionEnd) === value.length
615
+ ? selectionEnd
616
+ : selectionStart);
617
+ });
618
+ return () => eventListener.destroy();
619
+ };
620
+ }
621
+
622
+ const BUILT_IN_PLUGINS = [createDoubleSpacePlugin(), createBrokenDefaultPlugin()];
623
+ class Maskito extends MaskHistory {
624
+ constructor(element, maskitoOptions) {
625
+ super();
626
+ this.element = element;
627
+ this.maskitoOptions = maskitoOptions;
628
+ this.isTextArea = this.element.nodeName === 'TEXTAREA';
629
+ this.eventListener = new EventListener(this.element);
630
+ this.options = Object.assign(Object.assign({}, MASKITO_DEFAULT_OPTIONS), this.maskitoOptions);
631
+ this.upcomingElementState = null;
632
+ this.preprocessor = maskitoPipe(this.options.preprocessors);
633
+ this.postprocessor = maskitoPipe(this.options.postprocessors);
634
+ this.teardowns = this.options.plugins
635
+ .concat(BUILT_IN_PLUGINS)
636
+ .map((plugin) => plugin(this.element, this.options));
637
+ this.updateHistory(this.elementState);
638
+ this.eventListener.listen('keydown', (event) => {
639
+ if (isRedo(event)) {
640
+ event.preventDefault();
641
+ return this.redo();
642
+ }
643
+ if (isUndo(event)) {
644
+ event.preventDefault();
645
+ return this.undo();
646
+ }
647
+ });
648
+ this.eventListener.listen('beforeinput', (event) => {
649
+ var _a, _b, _c;
650
+ const isForward = event.inputType.includes('Forward');
651
+ this.updateHistory(this.elementState);
652
+ switch (event.inputType) {
653
+ case 'deleteByCut':
654
+ case 'deleteContentBackward':
655
+ case 'deleteContentForward':
656
+ return this.handleDelete({
657
+ event,
658
+ isForward,
659
+ selection: getNotEmptySelection(this.elementState, isForward),
660
+ });
661
+ case 'deleteHardLineBackward':
662
+ case 'deleteHardLineForward':
663
+ case 'deleteSoftLineBackward':
664
+ case 'deleteSoftLineForward':
665
+ return this.handleDelete({
666
+ event,
667
+ isForward,
668
+ selection: getLineSelection(this.elementState, isForward),
669
+ force: true,
670
+ });
671
+ case 'deleteWordBackward':
672
+ case 'deleteWordForward':
673
+ return this.handleDelete({
674
+ event,
675
+ isForward,
676
+ selection: getWordSelection(this.elementState, isForward),
677
+ });
678
+ case 'historyRedo':
679
+ event.preventDefault();
680
+ return this.redo();
681
+ // historyUndo/historyRedo will not be triggered if value was modified programmatically
682
+ case 'historyUndo':
683
+ event.preventDefault();
684
+ return this.undo();
685
+ case 'insertCompositionText':
686
+ return; // will be handled inside `compositionend` event
687
+ case 'insertLineBreak':
688
+ case 'insertParagraph':
689
+ return this.handleEnter(event);
690
+ case 'insertReplacementText':
691
+ /**
692
+ * According {@link https://www.w3.org/TR/input-events-2 W3C specification}:
693
+ * > `insertReplacementText` – insert or replace existing text by means of a spell checker,
694
+ * > auto-correct, writing suggestions or similar.
695
+ * ___
696
+ * Firefox emits `insertReplacementText` event for its suggestion/autofill and for spell checker.
697
+ * However, it is impossible to detect which part of the textfield value is going to be replaced
698
+ * (`selectionStart` and `selectionEnd` just equal to the last caret position).
699
+ * ___
700
+ * Chrome does not fire `beforeinput` event for its suggestion/autofill.
701
+ * It emits only `input` event with `inputType` and `data` set to `undefined`.
702
+ * ___
703
+ * All these browser limitations make us to validate the result value later in `input` event.
704
+ */
705
+ return;
706
+ case 'insertFromDrop':
707
+ case 'insertFromPaste':
708
+ case 'insertText':
709
+ default:
710
+ return this.handleInsert(event, (_c = (_a = event.data) !== null && _a !== void 0 ? _a :
711
+ // `event.data` for `contentEditable` is always `null` for paste/drop events
712
+ (_b = event.dataTransfer) === null || _b === void 0 ? void 0 : _b.getData('text/plain')) !== null && _c !== void 0 ? _c : '');
713
+ }
714
+ });
715
+ this.eventListener.listen('input', () => {
716
+ if (this.upcomingElementState &&
717
+ !areElementStatesEqual(this.upcomingElementState, this.elementState)) {
718
+ this.updateElementState(this.upcomingElementState);
719
+ }
720
+ this.upcomingElementState = null;
721
+ }, { capture: true });
722
+ this.eventListener.listen('input', ({ inputType }) => {
723
+ if (inputType === 'insertCompositionText') {
724
+ return; // will be handled inside `compositionend` event
725
+ }
726
+ this.ensureValueFitsMask();
727
+ this.updateHistory(this.elementState);
728
+ });
729
+ this.eventListener.listen('compositionend', () => {
730
+ this.ensureValueFitsMask();
731
+ this.updateHistory(this.elementState);
732
+ });
733
+ }
734
+ destroy() {
735
+ this.eventListener.destroy();
736
+ this.teardowns.forEach((teardown) => teardown === null || teardown === void 0 ? void 0 : teardown());
737
+ }
738
+ updateElementState({ value, selection }, eventInit) {
739
+ const initialValue = this.elementState.value;
740
+ this.updateValue(value);
741
+ this.updateSelectionRange(selection);
742
+ if (eventInit && initialValue !== value) {
743
+ this.dispatchInputEvent(eventInit);
744
+ }
745
+ }
746
+ get elementState() {
747
+ const { value, selectionStart, selectionEnd } = this.element;
748
+ return {
749
+ value,
750
+ selection: [selectionStart !== null && selectionStart !== void 0 ? selectionStart : 0, selectionEnd !== null && selectionEnd !== void 0 ? selectionEnd : 0],
751
+ };
752
+ }
753
+ get maxLength() {
754
+ const { maxLength } = this.element;
755
+ return maxLength === -1 ? Infinity : maxLength;
756
+ }
757
+ updateSelectionRange([from, to]) {
758
+ var _a;
759
+ const { element } = this;
760
+ if (element.matches(':focus') &&
761
+ (element.selectionStart !== from || element.selectionEnd !== to)) {
762
+ (_a = element.setSelectionRange) === null || _a === void 0 ? void 0 : _a.call(element, from, to);
763
+ }
764
+ }
765
+ updateValue(value) {
766
+ /**
767
+ * Don't "disturb" unnecessarily `value`-setter
768
+ * (i.e. it breaks React controlled input behavior)
769
+ */
770
+ if (this.element.value !== value || this.element.isContentEditable) {
771
+ this.element.value = value;
772
+ }
773
+ }
774
+ ensureValueFitsMask() {
775
+ this.updateElementState(maskitoTransform(this.elementState, this.options), {
776
+ inputType: 'insertText',
777
+ data: null,
778
+ });
779
+ }
780
+ dispatchInputEvent(eventInit = {
781
+ inputType: 'insertText',
782
+ data: null,
783
+ }) {
784
+ if (globalThis.InputEvent) {
785
+ this.element.dispatchEvent(new InputEvent('input', Object.assign(Object.assign({}, eventInit), { bubbles: true, cancelable: false })));
786
+ }
787
+ }
788
+ handleDelete({ event, selection, isForward, }) {
789
+ const initialState = {
790
+ value: this.elementState.value,
791
+ selection,
792
+ };
793
+ const { elementState } = this.preprocessor({
794
+ elementState: initialState,
795
+ data: '',
796
+ }, isForward ? 'deleteForward' : 'deleteBackward');
797
+ const maskModel = new MaskModel(elementState, this.options);
798
+ maskModel.deleteCharacters();
799
+ const newElementState = this.postprocessor(maskModel, initialState);
800
+ if (areElementValuesEqual(initialState, elementState, maskModel, newElementState)) {
801
+ const [from, to] = elementState.selection;
802
+ event.preventDefault();
803
+ // User presses Backspace/Delete for the fixed value
804
+ return this.updateSelectionRange(isForward ? [to, to] : [from, from]);
805
+ }
806
+ this.upcomingElementState = newElementState;
807
+ }
808
+ handleInsert(event, data) {
809
+ const { options, maxLength, elementState: initialElementState } = this;
810
+ const { elementState, data: insertedText = data } = this.preprocessor({
811
+ data,
812
+ elementState: initialElementState,
813
+ }, 'insert');
814
+ const maskModel = new MaskModel(elementState, options);
815
+ try {
816
+ maskModel.addCharacters(insertedText);
817
+ }
818
+ catch (_a) {
819
+ return event.preventDefault();
820
+ }
821
+ const [from, to] = initialElementState.selection;
822
+ const newPossibleState = {
823
+ value: initialElementState.value.slice(0, from) +
824
+ data +
825
+ initialElementState.value.slice(to),
826
+ selection: [from + data.length, from + data.length],
827
+ };
828
+ this.upcomingElementState = this.clampState(this.postprocessor(maskModel, initialElementState));
829
+ if (!areElementStatesEqual(this.clampState(newPossibleState), this.upcomingElementState) &&
830
+ options.overwriteMode === 'replace' &&
831
+ newPossibleState.value.length > maxLength) {
832
+ /**
833
+ * Browsers know nothing about Maskito and its `overwriteMode`.
834
+ * When textfield value length is already equal to attribute `maxlength`,
835
+ * pressing any key (even with valid value) does not emit `input` event.
836
+ */
837
+ this.dispatchInputEvent({ inputType: 'insertText', data });
838
+ }
839
+ }
840
+ handleEnter(event) {
841
+ if (this.isTextArea || this.element.isContentEditable) {
842
+ this.handleInsert(event, '\n');
843
+ }
844
+ }
845
+ clampState({ value, selection }) {
846
+ const [from, to] = selection;
847
+ const max = this.maxLength;
848
+ return {
849
+ value: value.slice(0, max),
850
+ selection: [Math.min(from, max), Math.min(to, max)],
851
+ };
852
+ }
853
+ }
854
+
855
+ /**
856
+ * Clamps a value between two inclusive limits
857
+ *
858
+ * @param value
859
+ * @param min lower limit
860
+ * @param max upper limit
861
+ */
862
+ function clamp(value, min, max) {
863
+ const clampedValue = Math.min(Number(max), Math.max(Number(min), Number(value)));
864
+ return (value instanceof Date ? new Date(clampedValue) : clampedValue);
865
+ }
866
+
867
+ function countDigits(str) {
868
+ return str.replaceAll(/\W/g, '').length;
869
+ }
870
+
871
+ function appendDate(initialDate, { day, month, year } = {}) {
872
+ const date = new Date(initialDate);
873
+ if (day) {
874
+ date.setDate(date.getDate() + day);
875
+ }
876
+ if (month) {
877
+ date.setMonth(date.getMonth() + month);
878
+ }
879
+ if (year) {
880
+ date.setFullYear(date.getFullYear() + year);
881
+ }
882
+ return date;
883
+ }
884
+
885
+ const getDateSegmentValueLength = (dateString) => {
886
+ var _a, _b, _c, _d, _e, _f;
887
+ return ({
888
+ day: (_b = (_a = dateString.match(/d/g)) === null || _a === void 0 ? void 0 : _a.length) !== null && _b !== void 0 ? _b : 0,
889
+ month: (_d = (_c = dateString.match(/m/g)) === null || _c === void 0 ? void 0 : _c.length) !== null && _d !== void 0 ? _d : 0,
890
+ year: (_f = (_e = dateString.match(/y/g)) === null || _e === void 0 ? void 0 : _e.length) !== null && _f !== void 0 ? _f : 0,
891
+ });
892
+ };
893
+
894
+ function dateToSegments(date) {
895
+ return {
896
+ day: String(date.getDate()).padStart(2, '0'),
897
+ month: String(date.getMonth() + 1).padStart(2, '0'),
898
+ year: String(date.getFullYear()).padStart(4, '0'),
899
+ hours: String(date.getHours()).padStart(2, '0'),
900
+ minutes: String(date.getMinutes()).padStart(2, '0'),
901
+ seconds: String(date.getSeconds()).padStart(2, '0'),
902
+ milliseconds: String(date.getMilliseconds()).padStart(3, '0'),
903
+ };
904
+ }
905
+
906
+ const ALL_POSSIBLE_SEGMENTS = [
907
+ 'day',
908
+ 'month',
909
+ 'year',
910
+ ];
911
+ function getDateSegmentsOrder(template) {
912
+ return [...ALL_POSSIBLE_SEGMENTS].sort((a, b) => template.indexOf(a[0]) > template.indexOf(b[0]) ? 1 : -1);
913
+ }
914
+
915
+ function getFirstCompleteDate(dateString, dateModeTemplate) {
916
+ const digitsInDate = countDigits(dateModeTemplate);
917
+ const [completeDate = ''] = new RegExp(`(\\D*\\d){${digitsInDate}}`).exec(dateString) || [];
918
+ return completeDate;
919
+ }
920
+
921
+ function isDateStringComplete(dateString, dateModeTemplate) {
922
+ if (dateString.length < dateModeTemplate.length) {
923
+ return false;
924
+ }
925
+ return dateString.split(/\D/).every((segment) => !/^0+$/.exec(segment));
926
+ }
927
+
928
+ function parseDateRangeString(dateRange, dateModeTemplate, rangeSeparator) {
929
+ const digitsInDate = countDigits(dateModeTemplate);
930
+ return (dateRange
931
+ .replace(rangeSeparator, '')
932
+ .match(new RegExp(`(\\D*\\d[^\\d\\s]*){1,${digitsInDate}}`, 'g')) || []);
933
+ }
934
+
935
+ function parseDateString(dateString, fullMode) {
936
+ const cleanMode = fullMode.replaceAll(/[^dmy]/g, '');
937
+ const onlyDigitsDate = dateString.replaceAll(/\D+/g, '');
938
+ const dateSegments = {
939
+ day: onlyDigitsDate.slice(cleanMode.indexOf('d'), cleanMode.lastIndexOf('d') + 1),
940
+ month: onlyDigitsDate.slice(cleanMode.indexOf('m'), cleanMode.lastIndexOf('m') + 1),
941
+ year: onlyDigitsDate.slice(cleanMode.indexOf('y'), cleanMode.lastIndexOf('y') + 1),
942
+ };
943
+ return Object.fromEntries(Object.entries(dateSegments)
944
+ .filter(([_, value]) => Boolean(value))
945
+ .sort(([a], [b]) => fullMode.toLowerCase().indexOf(a.slice(0, 1)) >
946
+ fullMode.toLowerCase().indexOf(b.slice(0, 1))
947
+ ? 1
948
+ : -1));
949
+ }
950
+
951
+ function segmentsToDate(parsedDate, parsedTime) {
952
+ var _a, _b, _c, _d, _e, _f, _g;
953
+ const year = ((_a = parsedDate.year) === null || _a === void 0 ? void 0 : _a.length) === 2 ? `20${parsedDate.year}` : parsedDate.year;
954
+ const date = new Date(Number(year !== null && year !== void 0 ? year : '0'), Number((_b = parsedDate.month) !== null && _b !== void 0 ? _b : '1') - 1, Number((_c = parsedDate.day) !== null && _c !== void 0 ? _c : '1'), Number((_d = void 0 ) !== null && _d !== void 0 ? _d : '0'), Number((_e = void 0 ) !== null && _e !== void 0 ? _e : '0'), Number((_f = void 0 ) !== null && _f !== void 0 ? _f : '0'), Number((_g = void 0 ) !== null && _g !== void 0 ? _g : '0'));
955
+ // needed for years less than 1900
956
+ date.setFullYear(Number(year !== null && year !== void 0 ? year : '0'));
957
+ return date;
958
+ }
959
+
960
+ const DATE_TIME_SEPARATOR = ', ';
961
+
962
+ function toDateString({ day, month, year, hours, minutes, seconds, milliseconds, }, { dateMode, dateTimeSeparator = DATE_TIME_SEPARATOR, timeMode, }) {
963
+ var _a;
964
+ const safeYear = ((_a = dateMode.match(/y/g)) === null || _a === void 0 ? void 0 : _a.length) === 2 ? year === null || year === void 0 ? void 0 : year.slice(-2) : year;
965
+ const fullMode = dateMode + (timeMode ? dateTimeSeparator + timeMode : '');
966
+ return fullMode
967
+ .replaceAll(/d+/g, day !== null && day !== void 0 ? day : '')
968
+ .replaceAll(/m+/g, month !== null && month !== void 0 ? month : '')
969
+ .replaceAll(/y+/g, safeYear !== null && safeYear !== void 0 ? safeYear : '')
970
+ .replaceAll(/H+/g, hours !== null && hours !== void 0 ? hours : '')
971
+ .replaceAll('MSS', milliseconds !== null && milliseconds !== void 0 ? milliseconds : '')
972
+ .replaceAll(/M+/g, minutes !== null && minutes !== void 0 ? minutes : '')
973
+ .replaceAll(/S+/g, seconds !== null && seconds !== void 0 ? seconds : '')
974
+ .replaceAll(/^\D+/g, '')
975
+ .replaceAll(/\D+$/g, '');
976
+ }
977
+
978
+ const DATE_SEGMENTS_MAX_VALUES = {
979
+ day: 31,
980
+ month: 12,
981
+ year: 9999,
982
+ };
983
+
984
+ const DEFAULT_MIN_DATE = new Date('0001-01-01T00:00');
985
+ const DEFAULT_MAX_DATE = new Date('9999-12-31T23:59:59.999');
986
+
987
+ /**
988
+ * {@link https://unicode-table.com/en/00A0/ Non-breaking space}.
989
+ */
990
+ const CHAR_NO_BREAK_SPACE = '\u00A0';
991
+ /**
992
+ * {@link https://unicode-table.com/en/2013/ EN dash}
993
+ * is used to indicate a range of numbers or a span of time.
994
+ * @example 2006–2022
995
+ */
996
+ const CHAR_EN_DASH = '\u2013';
997
+ /**
998
+ * {@link https://unicode-table.com/en/2014/ EM dash}
999
+ * is used to mark a break in a sentence.
1000
+ * @example Taiga UI — powerful set of open source components for Angular
1001
+ * ___
1002
+ * Don't confuse with {@link CHAR_EN_DASH} or {@link CHAR_HYPHEN}!
1003
+ */
1004
+ const CHAR_EM_DASH = '\u2014';
1005
+ /**
1006
+ * {@link https://unicode-table.com/en/002D/ Hyphen (minus sign)}
1007
+ * is used to combine words.
1008
+ * @example well-behaved
1009
+ * ___
1010
+ * Don't confuse with {@link CHAR_EN_DASH} or {@link CHAR_EM_DASH}!
1011
+ */
1012
+ const CHAR_HYPHEN = '\u002D';
1013
+ /**
1014
+ * {@link https://unicode-table.com/en/2212/ Minus}
1015
+ * is used as math operator symbol or before negative digits.
1016
+ * ---
1017
+ * Can be used as `&minus;`. Don't confuse with {@link CHAR_HYPHEN}
1018
+ */
1019
+ const CHAR_MINUS = '\u2212';
1020
+ /**
1021
+ * {@link https://symbl.cc/en/30FC/ Katakana-Hiragana Prolonged Sound Mark}
1022
+ * is used as prolonged sounds in Japanese.
1023
+ */
1024
+ const CHAR_JP_HYPHEN = '\u30FC';
1025
+
1026
+ const TIME_FIXED_CHARACTERS = [':', '.'];
1027
+
1028
+ function validateDateString({ dateString, dateModeTemplate, dateSegmentsSeparator, offset, selection: [from, to], }) {
1029
+ var _a, _b;
1030
+ const parsedDate = parseDateString(dateString, dateModeTemplate);
1031
+ const dateSegments = Object.entries(parsedDate);
1032
+ const segmentsOrder = getDateSegmentsOrder(dateModeTemplate);
1033
+ const validatedDateSegments = {};
1034
+ for (let i = 0; i < dateSegments.length; i++) {
1035
+ const [segmentName, segmentValue] = dateSegments[i];
1036
+ const validatedDate = toDateString(validatedDateSegments, {
1037
+ dateMode: dateModeTemplate,
1038
+ });
1039
+ const maxSegmentValue = DATE_SEGMENTS_MAX_VALUES[segmentName];
1040
+ const fantomSeparator = validatedDate.length && dateSegmentsSeparator.length;
1041
+ const lastSegmentDigitIndex = offset +
1042
+ validatedDate.length +
1043
+ fantomSeparator +
1044
+ getDateSegmentValueLength(dateModeTemplate)[segmentName];
1045
+ const isLastSegmentDigitAdded = lastSegmentDigitIndex >= from && lastSegmentDigitIndex === to;
1046
+ if (isLastSegmentDigitAdded && Number(segmentValue) > Number(maxSegmentValue)) {
1047
+ const nextSegment = segmentsOrder[segmentsOrder.indexOf(segmentName) + 1];
1048
+ if (!nextSegment || nextSegment === 'year') {
1049
+ // 31.1|0.2010 => Type 9 => 31.1|0.2010
1050
+ return { validatedDateString: '', updatedSelection: [from, to] }; // prevent changes
1051
+ }
1052
+ validatedDateSegments[segmentName] = `0${segmentValue.slice(0, -1)}`;
1053
+ dateSegments[i + 1] = [
1054
+ nextSegment,
1055
+ segmentValue.slice(-1) + ((_b = (_a = dateSegments[i + 1]) === null || _a === void 0 ? void 0 : _a[1]) !== null && _b !== void 0 ? _b : '').slice(1),
1056
+ ];
1057
+ continue;
1058
+ }
1059
+ if (isLastSegmentDigitAdded && Number(segmentValue) < 1) {
1060
+ // 31.0|1.2010 => Type 0 => 31.0|1.2010
1061
+ return { validatedDateString: '', updatedSelection: [from, to] }; // prevent changes
1062
+ }
1063
+ validatedDateSegments[segmentName] = segmentValue;
1064
+ }
1065
+ const validatedDateString = toDateString(validatedDateSegments, {
1066
+ dateMode: dateModeTemplate,
1067
+ });
1068
+ const addedDateSegmentSeparators = validatedDateString.length - dateString.length;
1069
+ return {
1070
+ validatedDateString,
1071
+ updatedSelection: [
1072
+ from + addedDateSegmentSeparators,
1073
+ to + addedDateSegmentSeparators,
1074
+ ],
1075
+ };
1076
+ }
1077
+
1078
+ function identity(x) {
1079
+ return x;
1080
+ }
1081
+
1082
+ /**
1083
+ * Copy-pasted solution from lodash
1084
+ * @see https://lodash.com/docs/4.17.15#escapeRegExp
1085
+ */
1086
+ const reRegExpChar = /[\\^$.*+?()[\]{}|]/g;
1087
+ const reHasRegExpChar = new RegExp(reRegExpChar.source);
1088
+ function escapeRegExp(str) {
1089
+ return str && reHasRegExpChar.test(str)
1090
+ ? str.replaceAll(reRegExpChar, String.raw `\$&`)
1091
+ : str;
1092
+ }
1093
+
1094
+ function isEmpty(entity) {
1095
+ return !entity || (typeof entity === 'object' && Object.keys(entity).length === 0);
1096
+ }
1097
+
1098
+ const ALL_ZEROES_RE = /^0+$/;
1099
+ function padWithZeroesUntilValid(segmentValue, paddedMaxValue, prefixedZeroesCount = 0) {
1100
+ const paddedSegmentValue = segmentValue.padEnd(paddedMaxValue.length, '0');
1101
+ if (Number(paddedSegmentValue) <= Number(paddedMaxValue)) {
1102
+ return { validatedSegmentValue: segmentValue, prefixedZeroesCount };
1103
+ }
1104
+ if (paddedSegmentValue.endsWith('0')) {
1105
+ // 00:|00 => Type 9 => 00:09|
1106
+ return padWithZeroesUntilValid(`0${segmentValue.slice(0, paddedMaxValue.length - 1)}`, paddedMaxValue, prefixedZeroesCount + 1);
1107
+ }
1108
+ const valueWithoutLastChar = segmentValue.slice(0, paddedMaxValue.length - 1);
1109
+ if (ALL_ZEROES_RE.exec(valueWithoutLastChar)) {
1110
+ return { validatedSegmentValue: '', prefixedZeroesCount };
1111
+ }
1112
+ // |19:00 => Type 2 => 2|0:00
1113
+ return padWithZeroesUntilValid(`${valueWithoutLastChar}0`, paddedMaxValue, prefixedZeroesCount);
1114
+ }
1115
+
1116
+ /**
1117
+ * Replace fullwidth numbers with half width number
1118
+ * @param fullWidthNumber full width number
1119
+ * @returns processed half width number
1120
+ */
1121
+ function toHalfWidthNumber(fullWidthNumber) {
1122
+ return fullWidthNumber.replaceAll(/[0-9]/g, (s) => String.fromCharCode(s.charCodeAt(0) - 0xfee0));
1123
+ }
1124
+
1125
+ function createDateSegmentsZeroPaddingPostprocessor({ dateModeTemplate, dateSegmentSeparator, splitFn, uniteFn, }) {
1126
+ return ({ value, selection }) => {
1127
+ var _a;
1128
+ const [from, to] = selection;
1129
+ const { dateStrings, restPart = '' } = splitFn(value);
1130
+ const validatedDateStrings = [];
1131
+ let caretShift = 0;
1132
+ dateStrings.forEach((dateString) => {
1133
+ const parsedDate = parseDateString(dateString, dateModeTemplate);
1134
+ const dateSegments = Object.entries(parsedDate);
1135
+ const validatedDateSegments = dateSegments.reduce((acc, [segmentName, segmentValue]) => {
1136
+ const { validatedSegmentValue, prefixedZeroesCount } = padWithZeroesUntilValid(segmentValue, `${DATE_SEGMENTS_MAX_VALUES[segmentName]}`);
1137
+ caretShift += prefixedZeroesCount;
1138
+ return Object.assign(Object.assign({}, acc), { [segmentName]: validatedSegmentValue });
1139
+ }, {});
1140
+ validatedDateStrings.push(toDateString(validatedDateSegments, { dateMode: dateModeTemplate }));
1141
+ });
1142
+ const validatedValue = uniteFn(validatedDateStrings, value) +
1143
+ (((_a = dateStrings[dateStrings.length - 1]) === null || _a === void 0 ? void 0 : _a.endsWith(dateSegmentSeparator))
1144
+ ? dateSegmentSeparator
1145
+ : '') +
1146
+ restPart;
1147
+ if (caretShift &&
1148
+ validatedValue.slice(to + caretShift, to + caretShift + dateSegmentSeparator.length) === dateSegmentSeparator) {
1149
+ /**
1150
+ * If `caretShift` > 0, it means that time segment was padded with zero.
1151
+ * It is only possible if any character insertion happens.
1152
+ * If caret is before `dateSegmentSeparator` => it should be moved after `dateSegmentSeparator`.
1153
+ */
1154
+ caretShift += dateSegmentSeparator.length;
1155
+ }
1156
+ return {
1157
+ selection: [from + caretShift, to + caretShift],
1158
+ value: validatedValue,
1159
+ };
1160
+ };
1161
+ }
1162
+
1163
+ /**
1164
+ * It replaces pseudo range separators with valid one.
1165
+ * @example '01.01.2000_11.11.2000' -> '01.01.2000 - 01.01.2000'
1166
+ * @example '01.01.2000_23:59' -> '01.01.2000, 23:59'
1167
+ */
1168
+ function createFirstDateEndSeparatorPreprocessor({ dateModeTemplate, firstDateEndSeparator, dateSegmentSeparator, pseudoFirstDateEndSeparators, }) {
1169
+ return ({ elementState, data }) => {
1170
+ const { value, selection } = elementState;
1171
+ const [from, to] = selection;
1172
+ const firstCompleteDate = getFirstCompleteDate(value, dateModeTemplate);
1173
+ const pseudoSeparators = pseudoFirstDateEndSeparators.filter((x) => !firstDateEndSeparator.includes(x) && x !== dateSegmentSeparator);
1174
+ const pseudoSeparatorsRE = new RegExp(`[${pseudoSeparators.join('')}]`, 'gi');
1175
+ const newValue = firstCompleteDate && value.length > firstCompleteDate.length
1176
+ ? firstCompleteDate +
1177
+ value
1178
+ .slice(firstCompleteDate.length)
1179
+ .replace(/^[\D\s]*/, firstDateEndSeparator)
1180
+ : value;
1181
+ const caretShift = newValue.length - value.length;
1182
+ return {
1183
+ elementState: {
1184
+ selection: [from + caretShift, to + caretShift],
1185
+ value: newValue,
1186
+ },
1187
+ data: data.replace(pseudoSeparatorsRE, firstDateEndSeparator),
1188
+ };
1189
+ };
1190
+ }
1191
+
1192
+ /**
1193
+ * Convert full width numbers like 1, 2 to half width numbers 1, 2
1194
+ */
1195
+ function createFullWidthToHalfWidthPreprocessor() {
1196
+ return ({ elementState, data }) => {
1197
+ const { value, selection } = elementState;
1198
+ return {
1199
+ elementState: {
1200
+ selection,
1201
+ value: toHalfWidthNumber(value),
1202
+ },
1203
+ data: toHalfWidthNumber(data),
1204
+ };
1205
+ };
1206
+ }
1207
+
1208
+ new RegExp(`[${TIME_FIXED_CHARACTERS.map(escapeRegExp).join('')}]$`);
1209
+
1210
+ function raiseSegmentValueToMin(segments, fullMode) {
1211
+ const segmentsLength = getDateSegmentValueLength(fullMode);
1212
+ return Object.fromEntries(Object.entries(segments).map(([key, value]) => {
1213
+ const segmentLength = segmentsLength[key];
1214
+ return [
1215
+ key,
1216
+ value.length === segmentLength && /^0+$/.exec(value)
1217
+ ? '1'.padStart(segmentLength, '0')
1218
+ : value,
1219
+ ];
1220
+ }));
1221
+ }
1222
+
1223
+ const LEAP_YEAR = '1972';
1224
+ function createMinMaxDatePostprocessor({ dateModeTemplate, min = DEFAULT_MIN_DATE, max = DEFAULT_MAX_DATE, rangeSeparator = '', dateSegmentSeparator = '.', }) {
1225
+ return ({ value, selection }) => {
1226
+ const endsWithRangeSeparator = rangeSeparator && value.endsWith(rangeSeparator);
1227
+ const dateStrings = parseDateRangeString(value, dateModeTemplate, rangeSeparator);
1228
+ let validatedValue = '';
1229
+ for (const dateString of dateStrings) {
1230
+ validatedValue += validatedValue ? rangeSeparator : '';
1231
+ const parsedDate = parseDateString(dateString, dateModeTemplate);
1232
+ if (!isDateStringComplete(dateString, dateModeTemplate)) {
1233
+ const fixedDate = raiseSegmentValueToMin(parsedDate, dateModeTemplate);
1234
+ const fixedValue = toDateString(fixedDate, { dateMode: dateModeTemplate });
1235
+ const tail = dateString.endsWith(dateSegmentSeparator)
1236
+ ? dateSegmentSeparator
1237
+ : '';
1238
+ validatedValue += fixedValue + tail;
1239
+ continue;
1240
+ }
1241
+ const date = segmentsToDate(Object.assign({ year: LEAP_YEAR }, parsedDate));
1242
+ const clampedDate = clamp(date, min, max);
1243
+ validatedValue += toDateString(dateToSegments(clampedDate), {
1244
+ dateMode: dateModeTemplate,
1245
+ });
1246
+ }
1247
+ return {
1248
+ selection,
1249
+ value: validatedValue + (endsWithRangeSeparator ? rangeSeparator : ''),
1250
+ };
1251
+ };
1252
+ }
1253
+
1254
+ function normalizeDatePreprocessor({ dateModeTemplate, dateSegmentsSeparator, rangeSeparator = '', dateTimeSeparator = DATE_TIME_SEPARATOR, }) {
1255
+ return ({ elementState, data }) => {
1256
+ const templateSegments = dateModeTemplate.split(dateSegmentsSeparator);
1257
+ const includesTime = data.includes(dateTimeSeparator);
1258
+ const dateSegments = data
1259
+ .slice(0, includesTime ? data.indexOf(dateTimeSeparator) : Infinity)
1260
+ .split(/\D/)
1261
+ .filter(Boolean);
1262
+ if (!dateSegments.length || dateSegments.length % templateSegments.length !== 0) {
1263
+ return { elementState, data };
1264
+ }
1265
+ const dates = dateSegments.reduce((dates, segment, index) => {
1266
+ var _a;
1267
+ const template = (_a = templateSegments[index % templateSegments.length]) !== null && _a !== void 0 ? _a : '';
1268
+ const dateIndex = Math.trunc(index / templateSegments.length);
1269
+ const isLastDateSegment = index % templateSegments.length === templateSegments.length - 1;
1270
+ if (!dates[dateIndex]) {
1271
+ dates[dateIndex] = '';
1272
+ }
1273
+ dates[dateIndex] += isLastDateSegment
1274
+ ? segment
1275
+ : `${segment.padStart(template.length, '0')}${dateSegmentsSeparator}`;
1276
+ return dates;
1277
+ }, []);
1278
+ return {
1279
+ elementState,
1280
+ data: includesTime
1281
+ ? `${dates[0]}${data.slice(data.indexOf(dateTimeSeparator))}`
1282
+ : dates.join(rangeSeparator),
1283
+ };
1284
+ };
1285
+ }
1286
+
1287
+ function createValidDatePreprocessor({ dateModeTemplate, dateSegmentsSeparator, rangeSeparator = '', }) {
1288
+ return ({ elementState, data }) => {
1289
+ const { value, selection } = elementState;
1290
+ if (data === dateSegmentsSeparator) {
1291
+ return {
1292
+ elementState,
1293
+ data: selection[0] === value.length ? data : '',
1294
+ };
1295
+ }
1296
+ if (!data.replaceAll(/\D/g, '')) {
1297
+ return { elementState, data };
1298
+ }
1299
+ const newCharacters = data.replaceAll(new RegExp(`[^\\d${escapeRegExp(dateSegmentsSeparator)}${rangeSeparator}]`, 'g'), '');
1300
+ const [from, rawTo] = selection;
1301
+ let to = rawTo + data.length;
1302
+ const newPossibleValue = value.slice(0, from) + newCharacters + value.slice(to);
1303
+ const dateStrings = parseDateRangeString(newPossibleValue, dateModeTemplate, rangeSeparator);
1304
+ let validatedValue = '';
1305
+ const hasRangeSeparator = Boolean(rangeSeparator) && newPossibleValue.includes(rangeSeparator);
1306
+ for (const dateString of dateStrings) {
1307
+ const { validatedDateString, updatedSelection } = validateDateString({
1308
+ dateString,
1309
+ dateModeTemplate,
1310
+ dateSegmentsSeparator,
1311
+ offset: validatedValue.length,
1312
+ selection: [from, to],
1313
+ });
1314
+ if (dateString && !validatedDateString) {
1315
+ return { elementState, data: '' }; // prevent changes
1316
+ }
1317
+ to = updatedSelection[1];
1318
+ validatedValue +=
1319
+ hasRangeSeparator && !validatedValue
1320
+ ? validatedDateString + rangeSeparator
1321
+ : validatedDateString;
1322
+ }
1323
+ const newData = validatedValue.slice(from, to);
1324
+ return {
1325
+ elementState: {
1326
+ selection,
1327
+ value: validatedValue.slice(0, from) +
1328
+ newData
1329
+ .split(dateSegmentsSeparator)
1330
+ .map((segment) => '0'.repeat(segment.length))
1331
+ .join(dateSegmentsSeparator) +
1332
+ validatedValue.slice(to),
1333
+ },
1334
+ data: newData,
1335
+ };
1336
+ };
1337
+ }
1338
+
1339
+ function maskitoEventHandler(name, handler, eventListenerOptions) {
1340
+ return (element, maskitoOptions) => {
1341
+ const listener = () => handler(element, maskitoOptions);
1342
+ element.addEventListener(name, listener, eventListenerOptions);
1343
+ return () => element.removeEventListener(name, listener, eventListenerOptions);
1344
+ };
1345
+ }
1346
+
1347
+ function maskitoSelectionChangeHandler(handler) {
1348
+ return (element, options) => {
1349
+ const document = element.ownerDocument;
1350
+ let isPointerDown = 0;
1351
+ const onPointerDown = () => isPointerDown++;
1352
+ const onPointerUp = () => {
1353
+ isPointerDown = Math.max(--isPointerDown, 0);
1354
+ };
1355
+ const listener = () => {
1356
+ if (!element.matches(':focus')) {
1357
+ return;
1358
+ }
1359
+ if (isPointerDown) {
1360
+ return document.addEventListener('mouseup', listener, {
1361
+ once: true,
1362
+ passive: true,
1363
+ });
1364
+ }
1365
+ handler(element, options);
1366
+ };
1367
+ document.addEventListener('selectionchange', listener, { passive: true });
1368
+ // Safari does not fire `selectionchange` on focus after programmatic update of textfield value
1369
+ element.addEventListener('focus', listener, { passive: true });
1370
+ element.addEventListener('mousedown', onPointerDown, { passive: true });
1371
+ document.addEventListener('mouseup', onPointerUp, { passive: true });
1372
+ return () => {
1373
+ document.removeEventListener('selectionchange', listener);
1374
+ element.removeEventListener('focus', listener);
1375
+ element.removeEventListener('mousedown', onPointerDown);
1376
+ document.removeEventListener('mouseup', onPointerUp);
1377
+ };
1378
+ };
1379
+ }
1380
+
1381
+ function maskitoCaretGuard(guard) {
1382
+ return maskitoSelectionChangeHandler((element) => {
1383
+ var _a, _b;
1384
+ const start = (_a = element.selectionStart) !== null && _a !== void 0 ? _a : 0;
1385
+ const end = (_b = element.selectionEnd) !== null && _b !== void 0 ? _b : 0;
1386
+ const [fromLimit, toLimit] = guard(element.value, [start, end]);
1387
+ if (fromLimit > start || toLimit < end) {
1388
+ element.setSelectionRange(clamp(start, fromLimit, toLimit), clamp(end, fromLimit, toLimit));
1389
+ }
1390
+ });
1391
+ }
1392
+
1393
+ function maskitoWithPlaceholder(placeholder, focusedOnly = false) {
1394
+ let lastClearValue = '';
1395
+ let action = 'validation';
1396
+ const removePlaceholder = (value) => {
1397
+ for (let i = value.length - 1; i >= lastClearValue.length; i--) {
1398
+ if (value[i] !== placeholder[i]) {
1399
+ return value.slice(0, i + 1);
1400
+ }
1401
+ }
1402
+ return value.slice(0, lastClearValue.length);
1403
+ };
1404
+ const plugins = [maskitoCaretGuard((value) => [0, removePlaceholder(value).length])];
1405
+ let focused = false;
1406
+ if (focusedOnly) {
1407
+ const focus = maskitoEventHandler('focus', (element) => {
1408
+ focused = true;
1409
+ maskitoUpdateElement(element, element.value + placeholder.slice(element.value.length));
1410
+ }, { capture: true });
1411
+ const blur = maskitoEventHandler('blur', (element) => {
1412
+ focused = false;
1413
+ maskitoUpdateElement(element, removePlaceholder(element.value));
1414
+ }, { capture: true });
1415
+ plugins.push(focus, blur);
1416
+ }
1417
+ return {
1418
+ plugins,
1419
+ removePlaceholder,
1420
+ preprocessors: [
1421
+ ({ elementState, data }, actionType) => {
1422
+ action = actionType;
1423
+ const { value, selection } = elementState;
1424
+ return {
1425
+ elementState: {
1426
+ selection,
1427
+ value: removePlaceholder(value),
1428
+ },
1429
+ data,
1430
+ };
1431
+ },
1432
+ ],
1433
+ postprocessors: [
1434
+ ({ value, selection }, initialElementState) => {
1435
+ lastClearValue = value;
1436
+ const justPlaceholderRemoval = value +
1437
+ placeholder.slice(value.length, initialElementState.value.length) ===
1438
+ initialElementState.value;
1439
+ if (action === 'validation' && justPlaceholderRemoval) {
1440
+ /**
1441
+ * If `value` still equals to `initialElementState.value`,
1442
+ * then it means that value is patched programmatically (from Maskito's plugin or externally).
1443
+ * In this case, we don't want to mutate value and automatically add/remove placeholder.
1444
+ * ___
1445
+ * For example, developer wants to remove manually placeholder (+ do something else with value) on blur.
1446
+ * Without this condition, placeholder will be unexpectedly added again.
1447
+ */
1448
+ return { selection, value: initialElementState.value };
1449
+ }
1450
+ const newValue = focused || !focusedOnly
1451
+ ? value + placeholder.slice(value.length)
1452
+ : value;
1453
+ if (newValue === initialElementState.value &&
1454
+ action === 'deleteBackward') {
1455
+ const [caretIndex] = initialElementState.selection;
1456
+ return {
1457
+ value: newValue,
1458
+ selection: [caretIndex, caretIndex],
1459
+ };
1460
+ }
1461
+ return { value: newValue, selection };
1462
+ },
1463
+ ],
1464
+ };
1465
+ }
1466
+
1467
+ function createZeroPlaceholdersPreprocessor(postfix = '') {
1468
+ const isLastChar = (value, [_, to]) => to >= value.length - postfix.length;
1469
+ return ({ elementState }, actionType) => {
1470
+ const { value, selection } = elementState;
1471
+ if (!value || isLastChar(value, selection)) {
1472
+ return { elementState };
1473
+ }
1474
+ const [from, to] = selection;
1475
+ const zeroes = value.slice(from, to).replaceAll(/\d/g, '0');
1476
+ const newValue = value.slice(0, from) + zeroes + value.slice(to);
1477
+ if (!zeroes.replaceAll(/\D/g, '')) {
1478
+ return { elementState };
1479
+ }
1480
+ if (actionType === 'validation' || (actionType === 'insert' && from === to)) {
1481
+ return {
1482
+ elementState: { selection, value: newValue },
1483
+ };
1484
+ }
1485
+ return {
1486
+ elementState: {
1487
+ selection: actionType === 'deleteBackward' || actionType === 'insert'
1488
+ ? [from, from]
1489
+ : [to, to],
1490
+ value: newValue,
1491
+ },
1492
+ };
1493
+ };
1494
+ }
1495
+
1496
+ function maskitoDateOptionsGenerator({ mode, separator = '.', max, min, }) {
1497
+ const dateModeTemplate = mode.split('/').join(separator);
1498
+ return Object.assign(Object.assign({}, MASKITO_DEFAULT_OPTIONS), { mask: Array.from(dateModeTemplate).map((char) => separator.includes(char) ? char : /\d/), overwriteMode: 'replace', preprocessors: [
1499
+ createFullWidthToHalfWidthPreprocessor(),
1500
+ createZeroPlaceholdersPreprocessor(),
1501
+ normalizeDatePreprocessor({
1502
+ dateModeTemplate,
1503
+ dateSegmentsSeparator: separator,
1504
+ }),
1505
+ createValidDatePreprocessor({
1506
+ dateModeTemplate,
1507
+ dateSegmentsSeparator: separator,
1508
+ }),
1509
+ ], postprocessors: [
1510
+ createDateSegmentsZeroPaddingPostprocessor({
1511
+ dateModeTemplate,
1512
+ dateSegmentSeparator: separator,
1513
+ splitFn: (value) => ({ dateStrings: [value] }),
1514
+ uniteFn: ([dateString = '']) => dateString,
1515
+ }),
1516
+ createMinMaxDatePostprocessor({
1517
+ min,
1518
+ max,
1519
+ dateModeTemplate,
1520
+ dateSegmentSeparator: separator,
1521
+ }),
1522
+ ] });
1523
+ }
1524
+
1525
+ function maskitoParseDate(value, { mode, min = DEFAULT_MIN_DATE, max = DEFAULT_MAX_DATE }) {
1526
+ if (value.length < mode.length) {
1527
+ return null;
1528
+ }
1529
+ const dateSegments = parseDateString(value, mode);
1530
+ const parsedDate = segmentsToDate(dateSegments);
1531
+ return clamp(parsedDate, min, max);
1532
+ }
1533
+
1534
+ const POSSIBLE_DATE_RANGE_SEPARATOR = [
1535
+ CHAR_HYPHEN,
1536
+ CHAR_EN_DASH,
1537
+ CHAR_EM_DASH,
1538
+ CHAR_MINUS,
1539
+ CHAR_JP_HYPHEN,
1540
+ ];
1541
+
1542
+ function createMinMaxRangeLengthPostprocessor({ dateModeTemplate, rangeSeparator, minLength, maxLength, max = DEFAULT_MAX_DATE, }) {
1543
+ if (isEmpty(minLength) && isEmpty(maxLength)) {
1544
+ return identity;
1545
+ }
1546
+ return ({ value, selection }) => {
1547
+ const dateStrings = parseDateRangeString(value, dateModeTemplate, rangeSeparator);
1548
+ if (dateStrings.length !== 2 ||
1549
+ dateStrings.some((date) => !isDateStringComplete(date, dateModeTemplate))) {
1550
+ return { value, selection };
1551
+ }
1552
+ const [fromDate, toDate] = dateStrings.map((dateString) => segmentsToDate(parseDateString(dateString, dateModeTemplate)));
1553
+ if (!fromDate || !toDate) {
1554
+ return { value, selection };
1555
+ }
1556
+ const minDistantToDate = appendDate(fromDate, Object.assign(Object.assign({}, minLength), {
1557
+ // 06.02.2023 - 07.02.2023 => {minLength: {day: 3}} => 06.02.2023 - 08.02.2023
1558
+ // "from"-day is included in the range
1559
+ day: (minLength === null || minLength === void 0 ? void 0 : minLength.day) && minLength.day - 1 }));
1560
+ const maxDistantToDate = !isEmpty(maxLength)
1561
+ ? appendDate(fromDate, Object.assign(Object.assign({}, maxLength), { day: (maxLength === null || maxLength === void 0 ? void 0 : maxLength.day) && maxLength.day - 1 }))
1562
+ : max;
1563
+ const minLengthClampedToDate = clamp(toDate, minDistantToDate, max);
1564
+ const minMaxLengthClampedToDate = minLengthClampedToDate > maxDistantToDate
1565
+ ? maxDistantToDate
1566
+ : minLengthClampedToDate;
1567
+ return {
1568
+ selection,
1569
+ value: dateStrings[0] +
1570
+ rangeSeparator +
1571
+ toDateString(dateToSegments(minMaxLengthClampedToDate), {
1572
+ dateMode: dateModeTemplate,
1573
+ }),
1574
+ };
1575
+ };
1576
+ }
1577
+
1578
+ function createSwapDatesPostprocessor({ dateModeTemplate, rangeSeparator, }) {
1579
+ return ({ value, selection }) => {
1580
+ const dateStrings = parseDateRangeString(value, dateModeTemplate, rangeSeparator);
1581
+ const isDateRangeComplete = dateStrings.length === 2 &&
1582
+ dateStrings.every((date) => isDateStringComplete(date, dateModeTemplate));
1583
+ const [from, to] = selection;
1584
+ const caretAtTheEnd = from >= value.length;
1585
+ const allValueSelected = from === 0 && to >= value.length; // dropping text inside with a pointer
1586
+ if (!(caretAtTheEnd || allValueSelected) || !isDateRangeComplete) {
1587
+ return { value, selection };
1588
+ }
1589
+ const [fromDate, toDate] = dateStrings.map((dateString) => segmentsToDate(parseDateString(dateString, dateModeTemplate)));
1590
+ return {
1591
+ selection,
1592
+ value: fromDate && toDate && fromDate > toDate
1593
+ ? dateStrings.reverse().join(rangeSeparator)
1594
+ : value,
1595
+ };
1596
+ };
1597
+ }
1598
+
1599
+ function maskitoDateRangeOptionsGenerator({ mode, min, max, minLength, maxLength, dateSeparator = '.', rangeSeparator = `${CHAR_NO_BREAK_SPACE}${CHAR_EN_DASH}${CHAR_NO_BREAK_SPACE}`, }) {
1600
+ const dateModeTemplate = mode.split('/').join(dateSeparator);
1601
+ const dateMask = Array.from(dateModeTemplate).map((char) => dateSeparator.includes(char) ? char : /\d/);
1602
+ return Object.assign(Object.assign({}, MASKITO_DEFAULT_OPTIONS), { mask: [...dateMask, ...Array.from(rangeSeparator), ...dateMask], overwriteMode: 'replace', preprocessors: [
1603
+ createFullWidthToHalfWidthPreprocessor(),
1604
+ createFirstDateEndSeparatorPreprocessor({
1605
+ dateModeTemplate,
1606
+ dateSegmentSeparator: dateSeparator,
1607
+ firstDateEndSeparator: rangeSeparator,
1608
+ pseudoFirstDateEndSeparators: POSSIBLE_DATE_RANGE_SEPARATOR,
1609
+ }),
1610
+ createZeroPlaceholdersPreprocessor(),
1611
+ normalizeDatePreprocessor({
1612
+ dateModeTemplate,
1613
+ rangeSeparator,
1614
+ dateSegmentsSeparator: dateSeparator,
1615
+ }),
1616
+ createValidDatePreprocessor({
1617
+ dateModeTemplate,
1618
+ rangeSeparator,
1619
+ dateSegmentsSeparator: dateSeparator,
1620
+ }),
1621
+ ], postprocessors: [
1622
+ createDateSegmentsZeroPaddingPostprocessor({
1623
+ dateModeTemplate,
1624
+ dateSegmentSeparator: dateSeparator,
1625
+ splitFn: (value) => ({
1626
+ dateStrings: parseDateRangeString(value, dateModeTemplate, rangeSeparator),
1627
+ }),
1628
+ uniteFn: (validatedDateStrings, initialValue) => validatedDateStrings.reduce((acc, dateString, dateIndex) => acc +
1629
+ dateString +
1630
+ (!dateIndex && initialValue.includes(rangeSeparator)
1631
+ ? rangeSeparator
1632
+ : ''), ''),
1633
+ }),
1634
+ createMinMaxDatePostprocessor({
1635
+ min,
1636
+ max,
1637
+ dateModeTemplate,
1638
+ rangeSeparator,
1639
+ dateSegmentSeparator: dateSeparator,
1640
+ }),
1641
+ createMinMaxRangeLengthPostprocessor({
1642
+ dateModeTemplate,
1643
+ minLength,
1644
+ maxLength,
1645
+ max,
1646
+ rangeSeparator,
1647
+ }),
1648
+ createSwapDatesPostprocessor({
1649
+ dateModeTemplate,
1650
+ rangeSeparator,
1651
+ }),
1652
+ ] });
1653
+ }
1654
+
1655
+ /**
1656
+ * React adds `_valueTracker` property to every textfield elements for its internal logic with controlled inputs.
1657
+ * Also, React monkey-patches `value`-setter of the native textfield elements to update state inside its `_valueTracker`.
1658
+ * @see https://github.com/facebook/react/blob/ee76351917106c6146745432a52e9a54a41ee181/packages/react-dom-bindings/src/client/inputValueTracking.js#L12-L19
1659
+ *
1660
+ * React depends on `_valueTracker` to know if the value was changed to decide:
1661
+ * - should it revert state for controlled input (if its state handler does not update value)
1662
+ * - should it dispatch its synthetic (not native!) `change` event
1663
+ *
1664
+ * When Maskito patches textfield with a valid value (using setter of `value` property),
1665
+ * it also updates `_valueTracker` state and React mistakenly decides that nothing has happened.
1666
+ * React should update `_valueTracker` state by itself!
1667
+ * ___
1668
+ * @see https://github.com/facebook/react/blob/ee76351917106c6146745432a52e9a54a41ee181/packages/react-dom-bindings/src/client/inputValueTracking.js#L173-L177
1669
+ */
1670
+ function adaptReactControlledElement(element) {
1671
+ var _a;
1672
+ const valueSetter = (_a = Object.getOwnPropertyDescriptor(getPrototype(element), 'value')) === null || _a === void 0 ? void 0 : _a.set;
1673
+ if (!valueSetter) {
1674
+ return element;
1675
+ }
1676
+ const adapter = {
1677
+ set value(value) {
1678
+ /**
1679
+ * Mimics exactly what happens when a browser silently changes the value property.
1680
+ * Bypass the React monkey-patching.
1681
+ */
1682
+ valueSetter.call(element, value);
1683
+ }
1684
+ };
1685
+ return new Proxy(element, {
1686
+ get(target, prop) {
1687
+ const nativeProperty = target[prop];
1688
+ return typeof nativeProperty === 'function' ? nativeProperty.bind(target) : nativeProperty;
1689
+ },
1690
+ // eslint-disable-next-line @typescript-eslint/max-params
1691
+ set(target, prop, val, receiver) {
1692
+ return Reflect.set(prop in adapter ? adapter : target, prop, val, receiver);
1693
+ }
1694
+ });
1695
+ }
1696
+ function getPrototype(element) {
1697
+ var _a, _b;
1698
+ switch (element.nodeName) {
1699
+ case 'INPUT':
1700
+ return (_a = globalThis.HTMLInputElement) === null || _a === void 0 ? void 0 : _a.prototype;
1701
+ case 'TEXTAREA':
1702
+ return (_b = globalThis.HTMLTextAreaElement) === null || _b === void 0 ? void 0 : _b.prototype;
1703
+ default:
1704
+ return null;
1705
+ }
1706
+ }
1707
+
1708
+ const useIsomorphicLayoutEffect = typeof window !== 'undefined' ? useLayoutEffect : useEffect;
1709
+
1710
+ function isThenable(x) {
1711
+ return x && typeof x === 'object' && 'then' in x;
1712
+ }
1713
+ /**
1714
+ * Hook for convenient use of Maskito in React
1715
+ * @description For controlled inputs use `onInput` event
1716
+ * @param options options used for creating Maskito
1717
+ * @param elementPredicate function that can help find nested Input or TextArea
1718
+ * @returns ref callback to pass it in React Element
1719
+ * @example
1720
+ * // To avoid unnecessary hook runs with Maskito recreation pass named variables
1721
+ * // good example ✅
1722
+ * useMaskito({ options: maskitoOptions, elementPredicate: maskitoPredicate })
1723
+ *
1724
+ * // bad example ❌
1725
+ * useMaskito({ options: { mask: /^.*$/ }, elementPredicate: () => e.querySelector('input') })
1726
+ */
1727
+ const useMaskito = ({
1728
+ options = null,
1729
+ elementPredicate = MASKITO_DEFAULT_ELEMENT_PREDICATE
1730
+ } = {}) => {
1731
+ const [hostElement, setHostElement] = useState(null);
1732
+ const [element, setElement] = useState(null);
1733
+ const onRefChange = useCallback(node => {
1734
+ setHostElement(node);
1735
+ }, []);
1736
+ const latestPredicateRef = useRef(elementPredicate);
1737
+ const latestOptionsRef = useRef(options);
1738
+ latestPredicateRef.current = elementPredicate;
1739
+ latestOptionsRef.current = options;
1740
+ useIsomorphicLayoutEffect(() => {
1741
+ if (!hostElement) {
1742
+ return;
1743
+ }
1744
+ const elementOrPromise = elementPredicate(hostElement);
1745
+ if (isThenable(elementOrPromise)) {
1746
+ void elementOrPromise.then(el => {
1747
+ if (latestPredicateRef.current === elementPredicate && latestOptionsRef.current === options) {
1748
+ setElement(el);
1749
+ }
1750
+ });
1751
+ } else {
1752
+ setElement(elementOrPromise);
1753
+ }
1754
+ }, [hostElement, elementPredicate, latestPredicateRef, options, latestOptionsRef]);
1755
+ useIsomorphicLayoutEffect(() => {
1756
+ if (!element || !options) {
1757
+ return;
1758
+ }
1759
+ const maskedElement = new Maskito(adaptReactControlledElement(element), options);
1760
+ return () => {
1761
+ maskedElement.destroy();
1762
+ setElement(null);
1763
+ };
1764
+ }, [options, element]);
1765
+ return onRefChange;
1766
+ };
1767
+
1768
+ function usePrevious(value) {
1769
+ const ref = useRef();
1770
+ useEffect(() => {
1771
+ ref.current = value;
1772
+ }, [value]);
1773
+ return ref.current;
1774
+ }
1775
+
1776
+ function usePopoverSupport() {
1777
+ const [popoverSupported, setPopoverSupported] = useState(
1778
+ void 0
1779
+ );
1780
+ useEffect(() => {
1781
+ setPopoverSupported(supportsPopover());
1782
+ }, []);
1783
+ return popoverSupported;
1784
+ }
1785
+
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$1(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(inputValue, mode, removePlaceholder);
1906
+ }, [inputValue, mode, removePlaceholder]);
1907
+ const handleChange = (event) => {
1908
+ setInputValue(event.target.value);
1909
+ const { date, isInputValid, isInputEmpty } = parseInputValue(
1910
+ event.target.value,
1911
+ mode,
1912
+ removePlaceholder
1913
+ );
1914
+ onChange?.({
1915
+ event,
1916
+ date: date ?? lastValidDate ?? null,
1917
+ isInputValid,
1918
+ isInputEmpty
1919
+ });
1920
+ };
1921
+ useImperativeHandle(ref, () => {
1922
+ const input = inputRef.current;
1923
+ if (!input) return null;
1924
+ return Object.assign(input, {
1925
+ setDate: (date) => {
1926
+ setInputValue(
1927
+ date?.toFormat(DateModeToFormatMap[mode]) ?? placeholder
1928
+ );
1929
+ }
1930
+ });
1931
+ }, [mode, placeholder]);
1932
+ return /* @__PURE__ */ jsx(
1933
+ TextField,
1934
+ {
1935
+ ref: combinedRef,
1936
+ "data-date": lastValidDate?.toISODate() ?? "",
1937
+ "data-input-valid": currentParsedData.isInputValid,
1938
+ "data-input-empty": currentParsedData.isInputEmpty,
1939
+ ...props,
1940
+ showCounter: false,
1941
+ value: inputValue,
1942
+ onChange: handleChange,
1943
+ prefix: /* @__PURE__ */ jsx(Icon, { svg: SvgEvent }),
1944
+ hint: disableHint ? void 0 : `format: ${mode}`
1945
+ }
1946
+ );
1947
+ }
1948
+ );
1949
+ MaskedDateInput.displayName = "MaskedDateInput";
1950
+ function parseInputValue(value, mode, removePlaceholder) {
1951
+ const valueMinusPlaceholder = removePlaceholder(value);
1952
+ const jsDate = maskitoParseDate(valueMinusPlaceholder, { mode });
1953
+ const luxonDate = jsDate ? DateTime.fromJSDate(jsDate, { zone: "utc" }) : null;
1954
+ return {
1955
+ date: luxonDate,
1956
+ isInputValid: !!luxonDate,
1957
+ isInputEmpty: valueMinusPlaceholder === ""
1958
+ };
1959
+ }
1960
+ function swapMode$1(inputString, previousMode, mode) {
1961
+ const { day, month, year } = divideSegments(inputString, previousMode);
1962
+ return orderSegmentsByMode(day, month, year, mode);
1963
+ }
1964
+ function divideSegments(value, mode) {
1965
+ const [segment1, segment2, segment3] = value.split("/");
1966
+ if (mode === "dd/mm/yyyy") {
1967
+ return { day: segment1, month: segment2, year: segment3 };
1968
+ } else if (mode === "mm/dd/yyyy") {
1969
+ return { day: segment2, month: segment1, year: segment3 };
1970
+ } else if (mode === "yyyy/mm/dd") {
1971
+ return { day: segment3, month: segment2, year: segment1 };
1972
+ }
1973
+ return { day: "__", month: "__", year: "____" };
1974
+ }
1975
+ function orderSegmentsByMode(day, month, year, mode) {
1976
+ if (mode === "dd/mm/yyyy") {
1977
+ return `${day}/${month}/${year}`;
1978
+ } else if (mode === "mm/dd/yyyy") {
1979
+ return `${month}/${day}/${year}`;
1980
+ } else if (mode === "yyyy/mm/dd") {
1981
+ return `${year}/${month}/${day}`;
1982
+ } else {
1983
+ return "";
1984
+ }
1985
+ }
1986
+
1987
+ const useDateFieldOrchestration = ({
1988
+ inputRef,
1989
+ calendarDefaultOpen,
1990
+ popoverContentRef,
1991
+ disableCalendar = false
1992
+ }) => {
1993
+ const documentRef = useRef(document.body);
1994
+ const [calendarOpen, setCalendarOpen] = useState(calendarDefaultOpen);
1995
+ const { focusables } = useKeyboardFocusables(documentRef, {
1996
+ observeChange: true
1997
+ });
1998
+ const { focusables: popoverFocusables } = useKeyboardFocusables(
1999
+ popoverContentRef,
2000
+ {
2001
+ observeChange: true
2002
+ }
2003
+ );
2004
+ const pageFocusables = focusables?.filter(
2005
+ (item) => !popoverFocusables?.includes(item)
2006
+ );
2007
+ const handleCalendarKeyDown = (event) => {
2008
+ if (event.key === "Escape") {
2009
+ inputRef.current?.focus();
2010
+ }
2011
+ };
2012
+ const focusToCalendar = () => {
2013
+ if (popoverContentRef.current) {
2014
+ const currentFocusable = popoverContentRef.current.querySelectorAll('[tabindex = "0"]')[0];
2015
+ if (currentFocusable) {
2016
+ currentFocusable.focus();
2017
+ }
2018
+ }
2019
+ };
2020
+ const handleInputKeyDown = (ev) => {
2021
+ if (disableCalendar) {
2022
+ return;
2023
+ }
2024
+ let currentFocusIndex = 0;
2025
+ switch (ev.key) {
2026
+ case "Escape":
2027
+ setCalendarOpen(false);
2028
+ break;
2029
+ case "Tab":
2030
+ if (!calendarOpen || !pageFocusables?.length) {
2031
+ break;
2032
+ }
2033
+ ev.preventDefault();
2034
+ currentFocusIndex = pageFocusables.indexOf(inputRef.current) || 0;
2035
+ setCalendarOpen(false);
2036
+ if (ev.shiftKey) {
2037
+ if (currentFocusIndex === 0) {
2038
+ requestAnimationFrame(
2039
+ () => pageFocusables[pageFocusables.length - 1].focus()
2040
+ );
2041
+ } else {
2042
+ requestAnimationFrame(
2043
+ () => pageFocusables[currentFocusIndex - 1].focus()
2044
+ );
2045
+ }
2046
+ break;
2047
+ }
2048
+ if (pageFocusables.length > currentFocusIndex + 1) {
2049
+ requestAnimationFrame(
2050
+ () => pageFocusables[currentFocusIndex + 1].focus()
2051
+ );
2052
+ } else {
2053
+ requestAnimationFrame(() => pageFocusables[0].focus());
2054
+ }
2055
+ break;
2056
+ case "ArrowDown":
2057
+ if (!calendarOpen) {
2058
+ setCalendarOpen(true);
2059
+ setTimeout(focusToCalendar, 200);
2060
+ } else {
2061
+ focusToCalendar();
2062
+ }
2063
+ }
2064
+ };
2065
+ return {
2066
+ calendarOpen,
2067
+ setCalendarOpen,
2068
+ handleCalendarKeyDown,
2069
+ handleInputKeyDown
2070
+ };
2071
+ };
2072
+
2073
+ function convertStringToDate(v) {
2074
+ if (v === void 0 || v === null) {
2075
+ return v;
2076
+ }
2077
+ const date = DateTime.fromISO(v, { setZone: true, zone: "utc" }).startOf(
2078
+ "day"
2079
+ );
2080
+ if (date.isValid) {
2081
+ return date;
2082
+ }
2083
+ return null;
2084
+ }
2085
+ function validateDate({
2086
+ date,
2087
+ constraints
2088
+ }) {
2089
+ const { required, unavailable, minDate, maxDate } = constraints;
2090
+ if (!date) {
2091
+ return required ? false : true;
2092
+ }
2093
+ if (unavailable?.dates?.some((d) => d.equals(date))) {
2094
+ return false;
2095
+ }
2096
+ if (unavailable?.daysOfWeek?.includes(date.weekday)) {
2097
+ return false;
2098
+ }
2099
+ if (minDate && date < minDate) {
2100
+ return false;
2101
+ }
2102
+ if (maxDate && date > maxDate) {
2103
+ return false;
2104
+ }
2105
+ return true;
2106
+ }
2107
+
2108
+ const NormalizedDateFieldSingle = forwardRef(
2109
+ ({
2110
+ value: valueProp,
2111
+ mode = "mm/dd/yyyy",
2112
+ defaultValue: defaultValueProp,
2113
+ onChange,
2114
+ disableCalendar = false,
2115
+ unavailable,
2116
+ minDate,
2117
+ maxDate,
2118
+ required = false,
2119
+ ...rest
2120
+ }, ref) => {
2121
+ const inputRef = useRef(null);
2122
+ const [popoverTriggerRef, setPopoverTriggerRef] = useState();
2123
+ const popoverContentRef = useRef(null);
2124
+ const combinedRef = useMergeRefs([popoverTriggerRef, inputRef, ref]);
2125
+ const {
2126
+ calendarOpen,
2127
+ setCalendarOpen,
2128
+ handleCalendarKeyDown,
2129
+ handleInputKeyDown
2130
+ } = useDateFieldOrchestration({
2131
+ inputRef,
2132
+ calendarDefaultOpen: false,
2133
+ popoverContentRef,
2134
+ disableCalendar
2135
+ });
2136
+ const [lastValidDate, setLastValidDate] = useOptionallyControlledState({
2137
+ controlledValue: valueProp,
2138
+ defaultValue: defaultValueProp
2139
+ });
2140
+ const popoverSupported = usePopoverSupport();
2141
+ const currentValidity = useMemo(
2142
+ () => validateDate({
2143
+ date: lastValidDate,
2144
+ constraints: {
2145
+ required,
2146
+ unavailable,
2147
+ minDate: minDate ?? void 0,
2148
+ maxDate: maxDate ?? void 0
2149
+ }
2150
+ }),
2151
+ [lastValidDate, required, unavailable, minDate, maxDate]
2152
+ );
2153
+ const handleInputChange = (change) => {
2154
+ const date = change.isInputEmpty ? null : change.date?.startOf("day") ?? null;
2155
+ onChange?.({
2156
+ date: date?.toISODate() ?? null,
2157
+ isInputValid: change.isInputValid,
2158
+ isInputEmpty: change.isInputEmpty,
2159
+ isDateValid: validateDate({
2160
+ date,
2161
+ constraints: {
2162
+ required,
2163
+ unavailable,
2164
+ minDate: minDate ?? void 0,
2165
+ maxDate: maxDate ?? void 0
2166
+ }
2167
+ })
2168
+ });
2169
+ if (change.isInputValid) {
2170
+ setLastValidDate(change.isInputEmpty ? null : change.date);
2171
+ }
2172
+ if (change.isInputEmpty) {
2173
+ setLastValidDate(null);
2174
+ return;
2175
+ }
2176
+ if (change.date) {
2177
+ setLastValidDate(change.date);
2178
+ }
2179
+ };
2180
+ const handleCalendarSelection = (data) => {
2181
+ if (data.value) {
2182
+ const date = DateTime.fromISO(data.value, { zone: "utc" });
2183
+ setLastValidDate(date);
2184
+ inputRef.current?.setDate(date);
2185
+ onChange?.({
2186
+ date: date.toISODate(),
2187
+ isInputValid: true,
2188
+ isInputEmpty: false,
2189
+ isDateValid: validateDate({
2190
+ date,
2191
+ constraints: {
2192
+ required,
2193
+ unavailable,
2194
+ minDate: minDate ?? void 0,
2195
+ maxDate: maxDate ?? void 0
2196
+ }
2197
+ })
2198
+ });
2199
+ }
2200
+ };
2201
+ const justTheField = /* @__PURE__ */ jsx(
2202
+ MaskedDateInput,
2203
+ {
2204
+ mode,
2205
+ ref: combinedRef,
2206
+ ...rest,
2207
+ onChange: handleInputChange,
2208
+ onKeyDown: handleInputKeyDown,
2209
+ onClick: () => setCalendarOpen(true),
2210
+ lastValidDate,
2211
+ required,
2212
+ autoComplete: "off",
2213
+ "data-date-valid": currentValidity
2214
+ }
2215
+ );
2216
+ if (disableCalendar) {
2217
+ return justTheField;
2218
+ }
2219
+ if (!popoverSupported) {
2220
+ return justTheField;
2221
+ }
2222
+ return /* @__PURE__ */ jsxs(
2223
+ Popover,
2224
+ {
2225
+ open: calendarOpen,
2226
+ modal: true,
2227
+ placement: "bottom-start",
2228
+ disableTriggerFocus: true,
2229
+ onClose: () => setCalendarOpen(false),
2230
+ disableAutoUpdate: true,
2231
+ onOutsidePress: () => setCalendarOpen(false),
2232
+ children: [
2233
+ /* @__PURE__ */ jsx(Popover.Trigger, { children: ({ ref: iRef }) => {
2234
+ setPopoverTriggerRef(iRef);
2235
+ return justTheField;
2236
+ } }),
2237
+ /* @__PURE__ */ jsx(Popover.Content, { ref: popoverContentRef, "data-calendar-popover": true, children: /* @__PURE__ */ jsx(
2238
+ Calendar,
2239
+ {
2240
+ range: false,
2241
+ onKeyDown: handleCalendarKeyDown,
2242
+ defaultFocusedDate: lastValidDate?.toISODate() || DateTime.now().toISODate(),
2243
+ value: lastValidDate?.toISODate() || void 0,
2244
+ onSelection: handleCalendarSelection,
2245
+ defaultTimeZone: "UTC",
2246
+ minDate: minDate?.toISODate() ?? void 0,
2247
+ maxDate: maxDate?.toISODate() ?? void 0,
2248
+ unavailable: unavailable ? {
2249
+ dates: unavailable.dates?.map((d) => d.toISODate() ?? ""),
2250
+ daysOfWeek: unavailable.daysOfWeek
2251
+ } : void 0,
2252
+ _disableAutofocus: true
2253
+ }
2254
+ ) })
2255
+ ]
2256
+ }
2257
+ );
2258
+ }
2259
+ );
2260
+ NormalizedDateFieldSingle.displayName = "NormalizedDateFieldSingle";
2261
+
2262
+ const DateFieldSingle = ({
2263
+ value,
2264
+ defaultValue,
2265
+ minDate,
2266
+ maxDate,
2267
+ unavailable,
2268
+ ...rest
2269
+ }) => {
2270
+ const normalizedValue = useMemo(() => convertStringToDate(value), [value]);
2271
+ const normalizedDefaultValue = useMemo(
2272
+ () => convertStringToDate(defaultValue),
2273
+ [defaultValue]
2274
+ );
2275
+ const normalizedMinDate = useMemo(
2276
+ () => convertStringToDate(minDate),
2277
+ [minDate]
2278
+ );
2279
+ const normalizedMaxDate = useMemo(
2280
+ () => convertStringToDate(maxDate),
2281
+ [maxDate]
2282
+ );
2283
+ const normalizedUnavailableDates = useMemo(
2284
+ () => unavailable?.dates?.map((d) => convertStringToDate(d)).filter((d) => d !== null && d !== void 0),
2285
+ [unavailable?.dates]
2286
+ );
2287
+ return /* @__PURE__ */ jsx(
2288
+ NormalizedDateFieldSingle,
2289
+ {
2290
+ ...rest,
2291
+ value: normalizedValue,
2292
+ defaultValue: normalizedDefaultValue,
2293
+ minDate: normalizedMinDate,
2294
+ maxDate: normalizedMaxDate,
2295
+ unavailable: unavailable ? {
2296
+ dates: normalizedUnavailableDates,
2297
+ daysOfWeek: unavailable?.daysOfWeek
2298
+ } : void 0
2299
+ }
2300
+ );
2301
+ };
2302
+ DateFieldSingle.displayName = "DateFieldSingle";
2303
+
2304
+ const datePlaceholderMask = ({
2305
+ mode,
2306
+ dateSeparator = "/",
2307
+ rangeSeparator = " - ",
2308
+ placeholder
2309
+ }) => {
2310
+ const dateRangeOptions = maskitoDateRangeOptionsGenerator({
2311
+ mode,
2312
+ dateSeparator,
2313
+ rangeSeparator
2314
+ });
2315
+ const { plugins, removePlaceholder, ...placeholderOptions } = maskitoWithPlaceholder(placeholder);
2316
+ const datePlaceholderMask2 = {
2317
+ ...dateRangeOptions,
2318
+ plugins: plugins.concat(dateRangeOptions.plugins || []),
2319
+ preprocessors: [
2320
+ ...placeholderOptions.preprocessors,
2321
+ ...dateRangeOptions.preprocessors,
2322
+ makeZeroShortcutPreprocessor(placeholder, dateSeparator)
2323
+ ],
2324
+ postprocessors: [
2325
+ // NOTE this is super fragile. If Maskito maintainers change the order of the post-processors, this will break.
2326
+ // The last postprocessor is the date swap postprocessor, which we don't want to run.
2327
+ // A unit test is added to ensure this doesn't break on a dependency update.
2328
+ ...dateRangeOptions.postprocessors.slice(0, -1),
2329
+ ...placeholderOptions.postprocessors
2330
+ ]
2331
+ };
2332
+ return { options: datePlaceholderMask2, removePlaceholder };
2333
+ };
2334
+
2335
+ const RANGE_SEPARATOR = " - ";
2336
+ const MaskedDateRangeInput = forwardRef(
2337
+ ({
2338
+ onChange,
2339
+ mode = "mm/dd/yyyy",
2340
+ startDate,
2341
+ endDate,
2342
+ disableHint = false,
2343
+ ...props
2344
+ }, ref) => {
2345
+ const halfPlaceholder = DateModeToPlaceholderMap[mode];
2346
+ const fullPlaceholder = `${halfPlaceholder}${RANGE_SEPARATOR}${halfPlaceholder}`;
2347
+ const [inputValue, setInputValue] = useState(fullPlaceholder);
2348
+ const { options, removePlaceholder } = datePlaceholderMask({
2349
+ mode,
2350
+ placeholder: fullPlaceholder,
2351
+ dateSeparator: "/",
2352
+ rangeSeparator: RANGE_SEPARATOR
2353
+ });
2354
+ const maskedInputRef = useMaskito({ options });
2355
+ const inputRef = useRef(null);
2356
+ const combinedRef = useMergeRefs$1([maskedInputRef, inputRef, ref]);
2357
+ const previousStartDate = usePrevious(startDate);
2358
+ const previousEndDate = usePrevious(endDate);
2359
+ const previousMode = usePrevious(mode);
2360
+ useEffect(() => {
2361
+ if (mode !== previousMode) {
2362
+ setInputValue(
2363
+ (previousInputValue) => swapMode(previousInputValue, previousMode ?? mode, mode)
2364
+ );
2365
+ }
2366
+ }, [mode, fullPlaceholder, previousMode]);
2367
+ useEffect(() => {
2368
+ if (startDate === void 0 || endDate === void 0) return;
2369
+ if (startDate === previousStartDate && endDate === previousEndDate)
2370
+ return;
2371
+ if (
2372
+ // plus one just represents a date that is guaranteed to be different.
2373
+ startDate?.equals(previousStartDate ?? startDate?.plus({ days: 1 })) && (endDate?.equals(previousEndDate ?? endDate?.plus({ days: 1 })) || endDate === previousEndDate)
2374
+ )
2375
+ return;
2376
+ const startDateString = startDate?.toFormat(DateModeToFormatMap[mode]) ?? halfPlaceholder;
2377
+ const endDateString = endDate?.toFormat(DateModeToFormatMap[mode]) ?? halfPlaceholder;
2378
+ const newInputValue = `${startDateString}${RANGE_SEPARATOR}${endDateString}`;
2379
+ setInputValue(newInputValue);
2380
+ }, [
2381
+ startDate,
2382
+ endDate,
2383
+ mode,
2384
+ halfPlaceholder,
2385
+ previousStartDate,
2386
+ previousEndDate
2387
+ ]);
2388
+ const handleChange = (event) => {
2389
+ setInputValue(event.target.value);
2390
+ const {
2391
+ startDate: parsedStartDate,
2392
+ endDate: parsedEndDate,
2393
+ isInputValid,
2394
+ isInputEmpty,
2395
+ isHalfEmpty
2396
+ } = parseRangeInputValue(event.target.value, mode, removePlaceholder);
2397
+ onChange?.({
2398
+ event,
2399
+ startDate: isInputEmpty ? null : parsedStartDate ?? startDate ?? null,
2400
+ endDate: isInputEmpty || isHalfEmpty ? null : parsedEndDate ?? endDate ?? null,
2401
+ isInputValid,
2402
+ isInputEmpty
2403
+ });
2404
+ };
2405
+ const currentParsedData = useMemo(() => {
2406
+ return parseRangeInputValue(inputValue, mode, removePlaceholder);
2407
+ }, [inputValue, mode, removePlaceholder]);
2408
+ useImperativeHandle(ref, () => {
2409
+ const input = inputRef.current;
2410
+ if (!input) return null;
2411
+ return Object.assign(input, {
2412
+ setDateRange: (startDate2, endDate2) => {
2413
+ const startDateString = startDate2?.toFormat(
2414
+ DateModeToFormatMap[mode]
2415
+ );
2416
+ const endDateString = endDate2?.toFormat(DateModeToFormatMap[mode]);
2417
+ const newInputValue = `${startDateString ?? halfPlaceholder}${RANGE_SEPARATOR}${endDateString ?? halfPlaceholder}`;
2418
+ setInputValue(newInputValue);
2419
+ }
2420
+ });
2421
+ }, [mode, halfPlaceholder]);
2422
+ return /* @__PURE__ */ jsx(
2423
+ TextField,
2424
+ {
2425
+ ref: combinedRef,
2426
+ "data-start-date": startDate?.toISODate() ?? "",
2427
+ "data-end-date": endDate?.toISODate() ?? "",
2428
+ "data-input-valid": currentParsedData.isInputValid,
2429
+ "data-input-empty": currentParsedData.isInputEmpty,
2430
+ ...props,
2431
+ showCounter: false,
2432
+ value: inputValue,
2433
+ onChange: handleChange,
2434
+ prefix: /* @__PURE__ */ jsx(Icon, { svg: SvgEvent }),
2435
+ hint: disableHint ? void 0 : `format: ${mode}`
2436
+ }
2437
+ );
2438
+ }
2439
+ );
2440
+ MaskedDateRangeInput.displayName = "MaskedDateRangeInput";
2441
+ function parseRangeInputValue(value, mode, removePlaceholder) {
2442
+ const valueMinusPlaceholder = removePlaceholder(value);
2443
+ const [startDate, endDate] = valueMinusPlaceholder.split(RANGE_SEPARATOR);
2444
+ const startJsDate = maskitoParseDate(startDate, { mode });
2445
+ const endJsDate = endDate ? maskitoParseDate(endDate, { mode }) : null;
2446
+ const startLuxonDate = startJsDate ? DateTime.fromJSDate(startJsDate, { zone: "utc" }) : null;
2447
+ const endLuxonDate = endJsDate ? DateTime.fromJSDate(endJsDate, { zone: "utc" }) : null;
2448
+ return {
2449
+ startDate: startLuxonDate,
2450
+ endDate: endLuxonDate,
2451
+ isInputValid: !!(startLuxonDate && endLuxonDate),
2452
+ // input valid if both dates are filled
2453
+ isInputEmpty: valueMinusPlaceholder === "",
2454
+ // input empty if nothing is typed
2455
+ isHalfEmpty: endDate === void 0
2456
+ };
2457
+ }
2458
+ function swapMode(inputString, previousMode, mode) {
2459
+ const halves = inputString.split(RANGE_SEPARATOR);
2460
+ const segments = halves.map((half) => half.split("/"));
2461
+ let startDay, startMonth, startYear, endDay, endMonth, endYear;
2462
+ if (previousMode === "mm/dd/yyyy") {
2463
+ startDay = segments[0][1];
2464
+ startMonth = segments[0][0];
2465
+ startYear = segments[0][2];
2466
+ endDay = segments[1][1];
2467
+ endMonth = segments[1][0];
2468
+ endYear = segments[1][2];
2469
+ }
2470
+ if (previousMode === "dd/mm/yyyy") {
2471
+ startDay = segments[0][0];
2472
+ startMonth = segments[0][1];
2473
+ startYear = segments[0][2];
2474
+ endDay = segments[1][0];
2475
+ endMonth = segments[1][1];
2476
+ endYear = segments[1][2];
2477
+ }
2478
+ if (previousMode === "yyyy/mm/dd") {
2479
+ startDay = segments[0][2];
2480
+ startMonth = segments[0][1];
2481
+ startYear = segments[0][0];
2482
+ endDay = segments[1][2];
2483
+ endMonth = segments[1][1];
2484
+ endYear = segments[1][0];
2485
+ }
2486
+ if (mode === "mm/dd/yyyy") {
2487
+ return `${startMonth}/${startDay}/${startYear}${RANGE_SEPARATOR}${endMonth}/${endDay}/${endYear}`;
2488
+ }
2489
+ if (mode === "dd/mm/yyyy") {
2490
+ return `${startDay}/${startMonth}/${startYear}${RANGE_SEPARATOR}${endDay}/${endMonth}/${endYear}`;
2491
+ }
2492
+ if (mode === "yyyy/mm/dd") {
2493
+ return `${startYear}/${startMonth}/${startDay}${RANGE_SEPARATOR}${endYear}/${endMonth}/${endDay}`;
2494
+ }
2495
+ return inputString;
2496
+ }
2497
+
2498
+ const NormalizedDateFieldRange = forwardRef(
2499
+ ({
2500
+ value: valueProp,
2501
+ mode = "mm/dd/yyyy",
2502
+ defaultValue: defaultValueProp,
2503
+ onChange,
2504
+ disableCalendar = false,
2505
+ unavailable,
2506
+ minDate,
2507
+ maxDate,
2508
+ required = false,
2509
+ ...rest
2510
+ }, ref) => {
2511
+ const inputRef = useRef(null);
2512
+ const [popoverTriggerRef, setPopoverTriggerRef] = useState();
2513
+ const popoverContentRef = useRef(null);
2514
+ const combinedRef = useMergeRefs([popoverTriggerRef, inputRef, ref]);
2515
+ const [startDate, setStartDate] = useOptionallyControlledState({
2516
+ controlledValue: valueProp !== void 0 ? valueProp?.startDate : void 0,
2517
+ defaultValue: defaultValueProp !== void 0 ? defaultValueProp?.startDate : void 0
2518
+ });
2519
+ const [endDate, setEndDate] = useOptionallyControlledState(
2520
+ {
2521
+ controlledValue: valueProp !== void 0 ? valueProp?.endDate : void 0,
2522
+ defaultValue: defaultValueProp !== void 0 ? defaultValueProp?.endDate : void 0
2523
+ }
2524
+ );
2525
+ const previousStartDate = usePrevious(startDate);
2526
+ const previousEndDate = usePrevious(endDate);
2527
+ const popoverSupported = usePopoverSupport();
2528
+ const {
2529
+ calendarOpen,
2530
+ setCalendarOpen,
2531
+ handleCalendarKeyDown,
2532
+ handleInputKeyDown
2533
+ } = useDateFieldOrchestration({
2534
+ inputRef: { current: null },
2535
+ calendarDefaultOpen: false,
2536
+ popoverContentRef,
2537
+ disableCalendar
2538
+ });
2539
+ const isDateRangeValid = useMemo(() => {
2540
+ return validateDate({
2541
+ date: startDate ?? null,
2542
+ constraints: {
2543
+ required,
2544
+ unavailable,
2545
+ minDate: minDate ?? void 0,
2546
+ maxDate: maxDate ?? void 0
2547
+ }
2548
+ }) && validateDate({
2549
+ date: endDate ?? null,
2550
+ constraints: {
2551
+ required,
2552
+ unavailable,
2553
+ minDate: minDate ?? void 0,
2554
+ maxDate: maxDate ?? void 0
2555
+ }
2556
+ }) && (!startDate || !endDate || startDate <= endDate);
2557
+ }, [required, unavailable, minDate, maxDate, startDate, endDate]);
2558
+ const defaultFocusedDate = useMemo(() => {
2559
+ if (!startDate && !endDate) return DateTime.now().toISODate();
2560
+ if (!startDate) return endDate?.toISODate();
2561
+ if (!endDate) return startDate?.toISODate();
2562
+ if (endDate && !previousEndDate?.equals(endDate)) {
2563
+ return endDate.toISODate();
2564
+ } else if (startDate && !previousStartDate?.equals(startDate)) {
2565
+ return startDate.toISODate();
2566
+ }
2567
+ if (endDate) return endDate.toISODate();
2568
+ if (startDate) return startDate.toISODate();
2569
+ return DateTime.now().toISODate();
2570
+ }, [previousStartDate, previousEndDate, startDate, endDate]);
2571
+ const handleInputChange = (change) => {
2572
+ const sharedConstraints = {
2573
+ unavailable,
2574
+ minDate: minDate ?? void 0,
2575
+ maxDate: maxDate ?? void 0
2576
+ };
2577
+ const range = change.isInputEmpty ? null : {
2578
+ startDate: change.startDate?.startOf("day") ?? null,
2579
+ endDate: change.endDate?.startOf("day") ?? null
2580
+ };
2581
+ setStartDate(range?.startDate ?? null);
2582
+ setEndDate(range?.endDate ?? null);
2583
+ onChange?.({
2584
+ startDate: range?.startDate?.toISODate() ?? null,
2585
+ endDate: range?.endDate?.toISODate() ?? null,
2586
+ isInputValid: change.isInputValid,
2587
+ isInputEmpty: change.isInputEmpty,
2588
+ isDateRangeValid: validateDateRange({
2589
+ required,
2590
+ startDate: range?.startDate ?? null,
2591
+ endDate: range?.endDate ?? null,
2592
+ startDateConstraints: sharedConstraints,
2593
+ endDateConstraints: sharedConstraints
2594
+ })
2595
+ });
2596
+ };
2597
+ const handleCalendarSelection = (data) => {
2598
+ if (data.value) {
2599
+ const calStartDate = data.value.start ? DateTime.fromISO(data.value.start, { zone: "utc" }) : null;
2600
+ const calEndDate = data.value.end ? DateTime.fromISO(data.value.end, { zone: "utc" }) : null;
2601
+ setStartDate(calStartDate);
2602
+ setEndDate(calEndDate);
2603
+ inputRef.current?.setDateRange(calStartDate, calEndDate);
2604
+ const sharedConstraints = {
2605
+ unavailable,
2606
+ minDate: minDate ?? void 0,
2607
+ maxDate: maxDate ?? void 0
2608
+ };
2609
+ onChange?.({
2610
+ startDate: calStartDate?.toISODate() ?? null,
2611
+ endDate: calEndDate?.toISODate() ?? null,
2612
+ isInputValid: !!calStartDate && !!calEndDate,
2613
+ isInputEmpty: !calStartDate && !calEndDate,
2614
+ isDateRangeValid: validateDateRange({
2615
+ required,
2616
+ startDate: calStartDate,
2617
+ endDate: calEndDate,
2618
+ startDateConstraints: sharedConstraints,
2619
+ endDateConstraints: sharedConstraints
2620
+ })
2621
+ });
2622
+ }
2623
+ };
2624
+ const justTheField = /* @__PURE__ */ jsx(
2625
+ MaskedDateRangeInput,
2626
+ {
2627
+ mode,
2628
+ ref: combinedRef,
2629
+ ...rest,
2630
+ onChange: handleInputChange,
2631
+ disableHint: rest.disableHint,
2632
+ onKeyDown: handleInputKeyDown,
2633
+ onClick: () => setCalendarOpen(true),
2634
+ required,
2635
+ autoComplete: "off",
2636
+ "data-date-range-valid": isDateRangeValid,
2637
+ startDate: startDate ?? null,
2638
+ endDate: endDate ?? null
2639
+ }
2640
+ );
2641
+ if (disableCalendar) {
2642
+ return justTheField;
2643
+ }
2644
+ if (!popoverSupported) {
2645
+ return justTheField;
2646
+ }
2647
+ return /* @__PURE__ */ jsxs(
2648
+ Popover,
2649
+ {
2650
+ open: calendarOpen,
2651
+ modal: true,
2652
+ placement: "bottom-start",
2653
+ disableTriggerFocus: true,
2654
+ onClose: () => setCalendarOpen(false),
2655
+ disableAutoUpdate: true,
2656
+ onOutsidePress: () => setCalendarOpen(false),
2657
+ children: [
2658
+ /* @__PURE__ */ jsx(Popover.Trigger, { children: ({ ref: iRef }) => {
2659
+ setPopoverTriggerRef(iRef);
2660
+ return justTheField;
2661
+ } }),
2662
+ /* @__PURE__ */ jsx(Popover.Content, { ref: popoverContentRef, "data-calendar-popover": true, children: /* @__PURE__ */ jsx(
2663
+ Calendar,
2664
+ {
2665
+ range: true,
2666
+ onKeyDown: handleCalendarKeyDown,
2667
+ defaultFocusedDate,
2668
+ value: {
2669
+ start: startDate?.toISODate() || void 0,
2670
+ end: endDate?.toISODate() || void 0
2671
+ },
2672
+ onSelection: handleCalendarSelection,
2673
+ defaultTimeZone: "UTC",
2674
+ minDate: minDate?.toISODate() ?? void 0,
2675
+ maxDate: maxDate?.toISODate() ?? void 0,
2676
+ unavailable: unavailable ? {
2677
+ dates: unavailable.dates?.map((d) => d.toISODate() ?? ""),
2678
+ daysOfWeek: unavailable.daysOfWeek
2679
+ } : void 0,
2680
+ _disableAutofocus: true
2681
+ },
2682
+ `${startDate?.toISODate()}-${endDate?.toISODate()}`
2683
+ ) })
2684
+ ]
2685
+ }
2686
+ );
2687
+ }
2688
+ );
2689
+ NormalizedDateFieldRange.displayName = "NormalizedDateFieldRange";
2690
+ function validateDateRange({
2691
+ required,
2692
+ startDate,
2693
+ endDate,
2694
+ startDateConstraints,
2695
+ endDateConstraints
2696
+ }) {
2697
+ if (!required && !startDate && !endDate) return true;
2698
+ return validateDate({
2699
+ date: startDate,
2700
+ constraints: { ...startDateConstraints, required: true }
2701
+ }) && validateDate({
2702
+ date: endDate,
2703
+ constraints: { ...endDateConstraints, required: true }
2704
+ }) && (!startDate || !endDate || startDate <= endDate);
2705
+ }
2706
+
2707
+ const DateFieldRange = ({
2708
+ value,
2709
+ defaultValue,
2710
+ minDate,
2711
+ maxDate,
2712
+ unavailable,
2713
+ ...rest
2714
+ }) => {
2715
+ const normalizedValue = useMemo(
2716
+ () => value !== void 0 ? {
2717
+ startDate: convertStringToDate(value?.startDate ?? null) ?? null,
2718
+ endDate: convertStringToDate(value?.endDate ?? null) ?? null
2719
+ } : void 0,
2720
+ [value]
2721
+ );
2722
+ const normalizedDefaultValue = useMemo(
2723
+ () => defaultValue !== void 0 ? {
2724
+ startDate: convertStringToDate(defaultValue?.startDate ?? null) ?? null,
2725
+ endDate: convertStringToDate(defaultValue?.endDate ?? null) ?? null
2726
+ } : void 0,
2727
+ [defaultValue]
2728
+ );
2729
+ const normalizedMinDate = useMemo(
2730
+ () => convertStringToDate(minDate),
2731
+ [minDate]
2732
+ );
2733
+ const normalizedMaxDate = useMemo(
2734
+ () => convertStringToDate(maxDate),
2735
+ [maxDate]
2736
+ );
2737
+ const normalizedUnavailableDates = useMemo(
2738
+ () => unavailable?.dates?.map((d) => convertStringToDate(d)).filter((d) => d !== null && d !== void 0),
2739
+ [unavailable?.dates]
2740
+ );
2741
+ return /* @__PURE__ */ jsx(
2742
+ NormalizedDateFieldRange,
2743
+ {
2744
+ ...rest,
2745
+ value: normalizedValue,
2746
+ defaultValue: normalizedDefaultValue,
2747
+ minDate: normalizedMinDate,
2748
+ maxDate: normalizedMaxDate,
2749
+ unavailable: unavailable ? {
2750
+ dates: normalizedUnavailableDates,
2751
+ daysOfWeek: unavailable?.daysOfWeek
2752
+ } : void 0
2753
+ }
2754
+ );
2755
+ };
2756
+ DateFieldRange.displayName = "DateFieldRange";
2757
+
2758
+ export { DateFieldRange as D, DateFieldSingle as a };
2759
+ //# sourceMappingURL=DateFieldRange-DxR0h7Y6-CV2wAhJB.js.map