@servicetitan/anvil2 1.38.0 → 1.40.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +51 -12
- package/dist/Breadcrumbs-C_gan90a.js +36 -0
- package/dist/Breadcrumbs-C_gan90a.js.map +1 -0
- package/dist/{Breadcrumbs-DJbCkSeD.js → Breadcrumbs-D_jgwoN3-Dlw-weD8.js} +8 -38
- package/dist/Breadcrumbs-D_jgwoN3-Dlw-weD8.js.map +1 -0
- package/dist/Breadcrumbs.js +1 -1
- package/dist/{Calendar-jl0s6W7p.js → Calendar-C2ONV7cq.js} +2 -2
- package/dist/{Calendar-jl0s6W7p.js.map → Calendar-C2ONV7cq.js.map} +1 -1
- package/dist/{Calendar-CohGSWFp-DFYJkf7Y.js → Calendar-DixTCgAW-CfphBge0.js} +16 -15
- package/dist/Calendar-DixTCgAW-CfphBge0.js.map +1 -0
- package/dist/Calendar.js +1 -1
- package/dist/{Checkbox-TIcImbEx-EgjesTNn.js → Checkbox-BNrjUtHs-BBLXLwO5.js} +3 -3
- package/dist/{Checkbox-TIcImbEx-EgjesTNn.js.map → Checkbox-BNrjUtHs-BBLXLwO5.js.map} +1 -1
- package/dist/{Checkbox-DbDwtefR.js → Checkbox-Dxr3rgQV.js} +2 -2
- package/dist/{Checkbox-DbDwtefR.js.map → Checkbox-Dxr3rgQV.js.map} +1 -1
- package/dist/Checkbox.js +1 -1
- package/dist/{Combobox-7ADxZKDE.js → Combobox-C7nV-w8I.js} +196 -392
- package/dist/Combobox-C7nV-w8I.js.map +1 -0
- package/dist/Combobox.css +43 -281
- package/dist/Combobox.js +1 -1
- package/dist/{DateField-CUO_26rh.js → DateField-Df42sxE9.js} +43 -43
- package/dist/DateField-Df42sxE9.js.map +1 -0
- package/dist/DateField.js +1 -1
- package/dist/DateFieldRange--ZCTVrKX-DoogWMrm.js +2759 -0
- package/dist/DateFieldRange--ZCTVrKX-DoogWMrm.js.map +1 -0
- package/dist/DateFieldRange-DQJ_FMeA.js +22 -0
- package/dist/DateFieldRange-DQJ_FMeA.js.map +1 -0
- package/dist/DateFieldRange.d.ts +2 -0
- package/dist/DateFieldRange.js +2 -0
- package/dist/DateFieldRange.js.map +1 -0
- package/dist/DateFieldSingle-BXPhS91g.js +22 -0
- package/dist/DateFieldSingle-BXPhS91g.js.map +1 -0
- package/dist/DateFieldSingle.d.ts +2 -0
- package/dist/DateFieldSingle.js +2 -0
- package/dist/DateFieldSingle.js.map +1 -0
- package/dist/{DaysOfTheWeek-CEKoAJSv.js → DaysOfTheWeek-C4ZGriW7.js} +3 -3
- package/dist/{DaysOfTheWeek-CEKoAJSv.js.map → DaysOfTheWeek-C4ZGriW7.js.map} +1 -1
- package/dist/DaysOfTheWeek.js +1 -1
- package/dist/{Dialog-D6zpW-GE.js → Dialog-CBWaZO5U.js} +2 -2
- package/dist/{Dialog-D6zpW-GE.js.map → Dialog-CBWaZO5U.js.map} +1 -1
- package/dist/Dialog.js +1 -1
- package/dist/{Drawer-qb7Q0BAm.js → Drawer-Be0tyTMM.js} +2 -2
- package/dist/{Drawer-qb7Q0BAm.js.map → Drawer-Be0tyTMM.js.map} +1 -1
- package/dist/Drawer.js +1 -1
- package/dist/{FieldMessage-ChFXWVDb-loVSCnCM.js → FieldMessage-Bobp105T-DYhStLY4.js} +4 -4
- package/dist/FieldMessage-Bobp105T-DYhStLY4.js.map +1 -0
- package/dist/{FieldMessage-ChFXWVDb.css → FieldMessage-Bobp105T.css} +13 -3
- package/dist/{FieldMessage-Cg3zcgk5.js → FieldMessage-DkJ0K5s-.js} +2 -2
- package/dist/{FieldMessage-Cg3zcgk5.js.map → FieldMessage-DkJ0K5s-.js.map} +1 -1
- package/dist/FieldMessage.js +1 -1
- package/dist/{Helper-C9sHaTrI-C5fAsK6i.js → Helper-h7k80qls-DHPFHTvI.js} +3 -3
- package/dist/{Helper-C9sHaTrI-C5fAsK6i.js.map → Helper-h7k80qls-DHPFHTvI.js.map} +1 -1
- package/dist/{InputMask-kJ-hlK0O-Ctwa0U7r.js → InputMask-CiHg25XE--lofQ4oF.js} +2 -2
- package/dist/{InputMask-kJ-hlK0O-Ctwa0U7r.js.map → InputMask-CiHg25XE--lofQ4oF.js.map} +1 -1
- package/dist/{InputMask-DKPqOpHs.js → InputMask-DYthrVaU.js} +2 -2
- package/dist/{InputMask-DKPqOpHs.js.map → InputMask-DYthrVaU.js.map} +1 -1
- package/dist/InputMask.js +1 -1
- package/dist/{ListView-0xHc5wT6.js → ListView-DgrUsTC8.js} +3 -3
- package/dist/{ListView-0xHc5wT6.js.map → ListView-DgrUsTC8.js.map} +1 -1
- package/dist/ListView.js +1 -1
- package/dist/Menu-By8ps-lb.js +60 -0
- package/dist/Menu-By8ps-lb.js.map +1 -0
- package/dist/{Menu-BAuADOyt.js → Menu-jt64Cbkd-mK95uUq6.js} +23 -73
- package/dist/Menu-jt64Cbkd-mK95uUq6.js.map +1 -0
- package/dist/{Menu.css → Menu-jt64Cbkd.css} +22 -21
- package/dist/Menu.js +1 -1
- package/dist/{Page-CtwsyE3i.js → Page-DoCYLmH8.js} +135 -105
- package/dist/Page-DoCYLmH8.js.map +1 -0
- package/dist/Page.js +1 -1
- package/dist/Pagination.css +124 -0
- package/dist/Pagination.d.ts +6 -0
- package/dist/Pagination.js +430 -0
- package/dist/Pagination.js.map +1 -0
- package/dist/{Popover-VztF0YHt-ci3tYiye.js → Popover-CVCAWhdO-CJw1mW49.js} +3 -3
- package/dist/{Popover-VztF0YHt-ci3tYiye.js.map → Popover-CVCAWhdO-CJw1mW49.js.map} +1 -1
- package/dist/{Popover-Bnkwq99S.js → Popover-Dv7ntx4P.js} +2 -2
- package/dist/{Popover-Bnkwq99S.js.map → Popover-Dv7ntx4P.js.map} +1 -1
- package/dist/Popover.js +1 -1
- package/dist/{ProgressBar-Cfi5zZRy-BpESsdh_.js → ProgressBar-CZhkKwaS-dlDRDjxo.js} +24 -11
- package/dist/ProgressBar-CZhkKwaS-dlDRDjxo.js.map +1 -0
- package/dist/{ProgressBar-BotOFymw.js → ProgressBar-Eeudkcyc.js} +2 -2
- package/dist/{ProgressBar-BotOFymw.js.map → ProgressBar-Eeudkcyc.js.map} +1 -1
- package/dist/ProgressBar.js +1 -1
- package/dist/{Radio-7U7IBI58-BDhdZJoC.js → Radio-DiBn0-hf-hhLKXDdl.js} +4 -4
- package/dist/{Radio-7U7IBI58-BDhdZJoC.js.map → Radio-DiBn0-hf-hhLKXDdl.js.map} +1 -1
- package/dist/{Radio-D4rRt1a6.js → Radio-Dx2GeEZp.js} +2 -2
- package/dist/{Radio-D4rRt1a6.js.map → Radio-Dx2GeEZp.js.map} +1 -1
- package/dist/Radio.js +1 -1
- package/dist/{SearchField-Bz4HPxCQ.js → SearchField-BQY7LHe2.js} +3 -3
- package/dist/SearchField-BQY7LHe2.js.map +1 -0
- package/dist/SearchField.js +1 -1
- package/dist/{SelectCard-B5EqtxOK-CpCgVjV8.js → SelectCard-CL8T4DQb-ClozUyqV.js} +46 -26
- package/dist/SelectCard-CL8T4DQb-ClozUyqV.js.map +1 -0
- package/dist/SelectCard-CL8T4DQb.css +51 -0
- package/dist/SelectCard.js +1 -1
- package/dist/{SelectCardGroup-DsHZgCqA.js → SelectCardGroup-KTtcsOar.js} +2 -2
- package/dist/{SelectCardGroup-DsHZgCqA.js.map → SelectCardGroup-KTtcsOar.js.map} +1 -1
- package/dist/SelectTrigger-BvUO-g1F.js +138 -0
- package/dist/SelectTrigger-BvUO-g1F.js.map +1 -0
- package/dist/SelectTrigger.css +16 -0
- package/dist/SelectTrigger.d.ts +6 -0
- package/dist/SelectTrigger.js +2 -0
- package/dist/SelectTrigger.js.map +1 -0
- package/dist/SelectTriggerBase-BMhRDkFW-DjkagPpu.js +301 -0
- package/dist/SelectTriggerBase-BMhRDkFW-DjkagPpu.js.map +1 -0
- package/dist/SelectTriggerBase-BMhRDkFW.css +270 -0
- package/dist/{Switch-Dd9tJFmG.js → Switch-DAQFzi-X.js} +2 -2
- package/dist/{Switch-Dd9tJFmG.js.map → Switch-DAQFzi-X.js.map} +1 -1
- package/dist/Switch.js +1 -1
- package/dist/{TextField-BYyyw3m2.js → TextField-0XKj7lDu.js} +2 -2
- package/dist/{TextField-BYyyw3m2.js.map → TextField-0XKj7lDu.js.map} +1 -1
- package/dist/{TextField-CGJtMoil-CJqYM83o.js → TextField-C5KbQxoU-CSkSyt7P.js} +8 -10
- package/dist/TextField-C5KbQxoU-CSkSyt7P.js.map +1 -0
- package/dist/TextField.js +1 -1
- package/dist/{Textarea-AczEXhHB.js → Textarea-DkSeFIg7.js} +8 -10
- package/dist/Textarea-DkSeFIg7.js.map +1 -0
- package/dist/Textarea.js +1 -1
- package/dist/Toast.js +1 -1
- package/dist/{Toolbar-Cu3u0TRX.js → Toolbar-38Z9fVxY.js} +5 -7
- package/dist/Toolbar-38Z9fVxY.js.map +1 -0
- package/dist/Toolbar.js +1 -1
- package/dist/{Tooltip-BL_bgvwA.js → Tooltip-BwUVaBEo.js} +2 -2
- package/dist/{Tooltip-BL_bgvwA.js.map → Tooltip-BwUVaBEo.js.map} +1 -1
- package/dist/Tooltip.js +1 -1
- package/dist/assets/css-utils/a2-border.css +53 -0
- package/dist/assets/css-utils/a2-color.css +235 -0
- package/dist/assets/css-utils/a2-font.css +49 -0
- package/dist/assets/css-utils/a2-spacing.css +483 -0
- package/dist/assets/css-utils/a2-utils.css +785 -0
- package/dist/assets/icons/st/document_audio.svg +1 -0
- package/dist/assets/icons/st/document_doc.svg +1 -0
- package/dist/assets/icons/st/document_drawing.svg +1 -0
- package/dist/assets/icons/st/document_form.svg +1 -0
- package/dist/assets/icons/st/document_message.svg +1 -0
- package/dist/assets/icons/st/document_other.svg +1 -0
- package/dist/assets/icons/st/document_pdf.svg +1 -0
- package/dist/assets/icons/st/document_spreadsheet.svg +1 -0
- package/dist/assets/icons/st/document_text.svg +1 -0
- package/dist/assets/icons/st/document_web.svg +1 -0
- package/dist/assets/icons/st/gnav_insurance_work_queue_active.svg +1 -0
- package/dist/assets/icons/st/gnav_insurance_work_queue_inactive.svg +1 -0
- package/dist/assets/icons/st/gnav_production_work_queue_active.svg +1 -0
- package/dist/assets/icons/st/gnav_production_work_queue_inactive.svg +1 -0
- package/dist/assets/icons/st.ts +14 -0
- package/dist/components/Alert/Alert.figma.d.ts +1 -0
- package/dist/components/DateFieldRange/DateFieldRange.d.ts +7 -0
- package/dist/components/DateFieldRange/index.d.ts +2 -0
- package/dist/components/DateFieldSingle/DateFieldSingle.d.ts +7 -0
- package/dist/components/DateFieldSingle/index.d.ts +2 -0
- package/dist/components/Page/Page.d.ts +19 -352
- package/dist/components/Page/PageContent.d.ts +24 -0
- package/dist/components/Page/PageContext.d.ts +4 -0
- package/dist/components/Page/PageFooter.d.ts +24 -0
- package/dist/components/Page/PageHeader.d.ts +135 -0
- package/dist/components/Page/PagePanel.d.ts +57 -0
- package/dist/components/Page/PageSidebar.d.ts +57 -0
- package/dist/components/Page/PageSidebarContext.d.ts +5 -0
- package/dist/components/Page/PageSidebarHeader.d.ts +23 -0
- package/dist/components/Page/index.d.ts +5 -0
- package/dist/components/Pagination/Pagination.d.ts +58 -0
- package/dist/components/Pagination/index.d.ts +2 -0
- package/dist/components/Pagination/internal/usePaginationArray.d.ts +36 -0
- package/dist/components/SelectTrigger/SelectTrigger.d.ts +11 -0
- package/dist/components/SelectTrigger/index.d.ts +2 -0
- package/dist/components/index.d.ts +3 -0
- package/dist/event-BEJFimi3.js +6 -0
- package/dist/event-BEJFimi3.js.map +1 -0
- package/dist/index.js +27 -24
- package/dist/index.js.map +1 -1
- package/dist/keyboard_arrow_right-DZWNVytH.js +8 -0
- package/dist/keyboard_arrow_right-DZWNVytH.js.map +1 -0
- package/dist/more_horiz-DJgdQiy0.js +6 -0
- package/dist/more_horiz-DJgdQiy0.js.map +1 -0
- package/dist/{toast-B39L6vJ0.js → toast-CWVuLkyv.js} +2 -2
- package/dist/{toast-B39L6vJ0.js.map → toast-CWVuLkyv.js.map} +1 -1
- package/dist/token/core/css-utils/a2-border.css +53 -0
- package/dist/token/core/css-utils/a2-color.css +235 -0
- package/dist/token/core/css-utils/a2-font.css +49 -0
- package/dist/token/core/css-utils/a2-spacing.css +483 -0
- package/dist/token/core/css-utils/a2-utils.css +785 -0
- package/package.json +8 -5
- package/dist/Breadcrumbs-DJbCkSeD.js.map +0 -1
- package/dist/Calendar-CohGSWFp-DFYJkf7Y.js.map +0 -1
- package/dist/Combobox-7ADxZKDE.js.map +0 -1
- package/dist/DateField-CUO_26rh.js.map +0 -1
- package/dist/FieldMessage-ChFXWVDb-loVSCnCM.js.map +0 -1
- package/dist/Menu-BAuADOyt.js.map +0 -1
- package/dist/Page-CtwsyE3i.js.map +0 -1
- package/dist/ProgressBar-Cfi5zZRy-BpESsdh_.js.map +0 -1
- package/dist/SearchField-Bz4HPxCQ.js.map +0 -1
- package/dist/SelectCard-B5EqtxOK-CpCgVjV8.js.map +0 -1
- package/dist/SelectCard-B5EqtxOK.css +0 -38
- package/dist/TextField-CGJtMoil-CJqYM83o.js.map +0 -1
- package/dist/Textarea-AczEXhHB.js.map +0 -1
- package/dist/Toolbar-Cu3u0TRX.js.map +0 -1
- /package/dist/{Breadcrumbs.css → Breadcrumbs-D_jgwoN3.css} +0 -0
- /package/dist/{Calendar-CohGSWFp.css → Calendar-DixTCgAW.css} +0 -0
- /package/dist/{Helper-C9sHaTrI.css → Helper-h7k80qls.css} +0 -0
- /package/dist/{Popover-VztF0YHt.css → Popover-CVCAWhdO.css} +0 -0
- /package/dist/{ProgressBar-Cfi5zZRy.css → ProgressBar-CZhkKwaS.css} +0 -0
- /package/dist/{Radio-7U7IBI58.css → Radio-DiBn0-hf.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-DixTCgAW-CfphBge0.js';
|
|
4
|
+
import { T as TextField } from './TextField-C5KbQxoU-CSkSyt7P.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-CVCAWhdO-CJw1mW49.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-CZhkKwaS-dlDRDjxo.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 `−`. 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--ZCTVrKX-DoogWMrm.js.map
|