@servicetitan/anvil2 1.42.2 → 1.43.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +13 -0
- package/dist/{Combobox-D2aSaDkz.js → Combobox-C7ANSGIu.js} +2 -2
- package/dist/{Combobox-D2aSaDkz.js.map → Combobox-C7ANSGIu.js.map} +1 -1
- package/dist/Combobox.js +1 -1
- package/dist/{DateField-D28_sa7P.js → DateField-_TqCdcYv.js} +4 -3
- package/dist/{DateField-D28_sa7P.js.map → DateField-_TqCdcYv.js.map} +1 -1
- package/dist/DateField.js +1 -1
- package/dist/{DateFieldRange--oSGfjYa.js → DateFieldRange-Dk8WA52F.js} +2 -2
- package/dist/{DateFieldRange--oSGfjYa.js.map → DateFieldRange-Dk8WA52F.js.map} +1 -1
- package/dist/DateFieldRange.js +1 -1
- package/dist/{DateFieldSingle-0a8Bk7Yj.js → DateFieldSingle-D3xD8YZk.js} +2 -2
- package/dist/{DateFieldSingle-0a8Bk7Yj.js.map → DateFieldSingle-D3xD8YZk.js.map} +1 -1
- package/dist/DateFieldSingle.js +1 -1
- package/dist/{DateFieldYearless-DCv9WJdu.js → DateFieldYearless-BxkCSNk5.js} +2 -2
- package/dist/{DateFieldYearless-DCv9WJdu.js.map → DateFieldYearless-BxkCSNk5.js.map} +1 -1
- package/dist/DateFieldYearless-CAUHW6Ow-EUWxJ0OY.js +1431 -0
- package/dist/DateFieldYearless-CAUHW6Ow-EUWxJ0OY.js.map +1 -0
- package/dist/DateFieldYearless.js +1 -1
- package/dist/{Page-KN0DLtcf.js → Page-CxB5N9dR.js} +36 -36
- package/dist/{Page-KN0DLtcf.js.map → Page-CxB5N9dR.js.map} +1 -1
- package/dist/Page.css +72 -72
- package/dist/Page.js +1 -1
- package/dist/{Popover-B1HaUjGI.js → Popover-BPiqdyu1.js} +2 -2
- package/dist/{Popover-B1HaUjGI.js.map → Popover-BPiqdyu1.js.map} +1 -1
- package/dist/{Popover-CU2cGVD8-FWJOuFRj.js → Popover-CU2cGVD8-Casl3vM1.js} +2 -2
- package/dist/{Popover-CU2cGVD8-FWJOuFRj.js.map → Popover-CU2cGVD8-Casl3vM1.js.map} +1 -1
- package/dist/Popover.js +1 -1
- package/dist/TimeField-DRHLRqN3.js +913 -0
- package/dist/TimeField-DRHLRqN3.js.map +1 -0
- package/dist/TimeField.css +10 -0
- package/dist/TimeField.d.ts +2 -0
- package/dist/TimeField.js +2 -0
- package/dist/TimeField.js.map +1 -0
- package/dist/{Toolbar-BznMJKGJ.js → Toolbar-CSWhVSUM.js} +2 -2
- package/dist/{Toolbar-BznMJKGJ.js.map → Toolbar-CSWhVSUM.js.map} +1 -1
- package/dist/Toolbar.js +1 -1
- package/dist/assets/icons/st/gnav_field_pro_active.svg +1 -1
- package/dist/assets/icons/st/gnav_field_pro_inactive.svg +1 -1
- package/dist/components/TimeField/TimeField.d.ts +53 -0
- package/dist/components/TimeField/index.d.ts +1 -0
- package/dist/components/index.d.ts +1 -0
- package/dist/event-BEJFimi3.js +6 -0
- package/dist/event-BEJFimi3.js.map +1 -0
- package/dist/index.js +9 -8
- package/dist/index.js.map +1 -1
- package/dist/usePopoverCloseDelayWorkaround-BZcjPkvT-BZcjPkvT.js +18 -0
- package/dist/usePopoverCloseDelayWorkaround-BZcjPkvT-BZcjPkvT.js.map +1 -0
- package/dist/{DateFieldYearless-C3_oGDr3-5meexzZO.js → usePopoverSupport-B9Lsqryr-DhZHMoNb.js} +470 -1429
- package/dist/usePopoverSupport-B9Lsqryr-DhZHMoNb.js.map +1 -0
- package/package.json +4 -4
- package/dist/DateFieldYearless-C3_oGDr3-5meexzZO.js.map +0 -1
- package/dist/usePopoverCloseDelayWorkaround-BhhG-xEB-hfJZaXHC.js +0 -21
- package/dist/usePopoverCloseDelayWorkaround-BhhG-xEB-hfJZaXHC.js.map +0 -1
|
@@ -0,0 +1,913 @@
|
|
|
1
|
+
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
2
|
+
import * as React from 'react';
|
|
3
|
+
import { forwardRef, useState, useRef, useEffect, useMemo, useCallback, useImperativeHandle } from 'react';
|
|
4
|
+
import { useTrackingId } from './useTrackingId.js';
|
|
5
|
+
import { c as childrenToString } from './childrenToString-Bz9MqbHb-Bz9MqbHb.js';
|
|
6
|
+
import { T as TextField } from './TextField-CRTh0gL_-D2CjcYXX.js';
|
|
7
|
+
import { u as usePopoverSupport, a as useMaskito, e as maskitoTimeOptionsGenerator, b as maskitoWithPlaceholder } from './usePopoverSupport-B9Lsqryr-DhZHMoNb.js';
|
|
8
|
+
import { I as Icon } from './Icon-B6HmlQiR-BxQkO3X5.js';
|
|
9
|
+
import { c as cx } from './index-tZvMCc77.js';
|
|
10
|
+
import { u as useMergeRefs } from './useMergeRefs-Bde85AWI-Bde85AWI.js';
|
|
11
|
+
import { P as Popover } from './Popover-CU2cGVD8-Casl3vM1.js';
|
|
12
|
+
import { b as Listbox } from './Listbox-B-WUuj-_-BWWeWtLW.js';
|
|
13
|
+
import { u as useOptionallyControlledState } from './useOptionallyControlledState-DAv5LXXh-DAv5LXXh.js';
|
|
14
|
+
import { u as usePopoverCloseDelayWorkaround } from './usePopoverCloseDelayWorkaround-BZcjPkvT-BZcjPkvT.js';
|
|
15
|
+
|
|
16
|
+
import './TimeField.css';const SvgAccessTime = (props) => /* @__PURE__ */ React.createElement("svg", { xmlns: "http://www.w3.org/2000/svg", width: "1em", height: "1em", viewBox: "0 0 24 24", ...props }, /* @__PURE__ */ React.createElement("path", { d: "M11.99 2C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zM12 20c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8zm-.22-13h-.06c-.4 0-.72.32-.72.72v4.72c0 .35.18.68.49.86l4.15 2.49c.34.2.78.1.98-.24a.71.71 0 0 0-.25-.99l-3.87-2.3V7.72c0-.4-.32-.72-.72-.72z" }));
|
|
17
|
+
|
|
18
|
+
const timePlaceholderMask = ({
|
|
19
|
+
format,
|
|
20
|
+
placeholder
|
|
21
|
+
}) => {
|
|
22
|
+
const timeOptions = maskitoTimeOptionsGenerator({
|
|
23
|
+
mode: format === 12 ? "HH:MM AA" : "HH:MM",
|
|
24
|
+
step: 0
|
|
25
|
+
// We handle step logic in your business layer
|
|
26
|
+
});
|
|
27
|
+
const { plugins, removePlaceholder, ...placeholderOptions } = maskitoWithPlaceholder(placeholder);
|
|
28
|
+
const timePlaceholderMask2 = {
|
|
29
|
+
...timeOptions,
|
|
30
|
+
plugins: plugins.concat(timeOptions.plugins || []),
|
|
31
|
+
preprocessors: [
|
|
32
|
+
...placeholderOptions.preprocessors,
|
|
33
|
+
...timeOptions.preprocessors
|
|
34
|
+
],
|
|
35
|
+
postprocessors: [
|
|
36
|
+
...timeOptions.postprocessors,
|
|
37
|
+
...placeholderOptions.postprocessors
|
|
38
|
+
]
|
|
39
|
+
};
|
|
40
|
+
return { options: timePlaceholderMask2, removePlaceholder };
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const TIME_CONSTANTS = {
|
|
44
|
+
/** Minutes in a full day (24 hours * 60 minutes) */
|
|
45
|
+
MINUTES_IN_DAY: 1440,
|
|
46
|
+
/** Maximum hour in 24-hour format */
|
|
47
|
+
MAX_HOUR_24: 23,
|
|
48
|
+
/** Maximum minute value */
|
|
49
|
+
MAX_MINUTE: 59,
|
|
50
|
+
/** Timeout in milliseconds for input debouncing */
|
|
51
|
+
INPUT_DEBOUNCE_TIMEOUT: 100
|
|
52
|
+
};
|
|
53
|
+
const TIME_FORMAT_PLACEHOLDERS = {
|
|
54
|
+
12: "––:–– ––",
|
|
55
|
+
24: "––:––"
|
|
56
|
+
};
|
|
57
|
+
const CLOCK_ICON = SvgAccessTime;
|
|
58
|
+
|
|
59
|
+
function formatTimeString(hours, minutes, format) {
|
|
60
|
+
if (hours === 24) {
|
|
61
|
+
hours = 0;
|
|
62
|
+
}
|
|
63
|
+
if (format === 12) {
|
|
64
|
+
let displayHours = hours;
|
|
65
|
+
let period = "AM";
|
|
66
|
+
if (hours === 0) {
|
|
67
|
+
displayHours = 12;
|
|
68
|
+
} else if (hours > 12) {
|
|
69
|
+
displayHours = hours - 12;
|
|
70
|
+
period = "PM";
|
|
71
|
+
} else if (hours === 12) {
|
|
72
|
+
period = "PM";
|
|
73
|
+
}
|
|
74
|
+
return `${displayHours.toString().padStart(2, "0")}:${minutes.toString().padStart(2, "0")} ${period}`;
|
|
75
|
+
} else {
|
|
76
|
+
return `${hours.toString().padStart(2, "0")}:${minutes.toString().padStart(2, "0")}`;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const MAX_HOUR = TIME_CONSTANTS.MAX_HOUR_24;
|
|
81
|
+
const MAX_MINUTE = TIME_CONSTANTS.MAX_MINUTE;
|
|
82
|
+
function getConstraintMinutes(min, max) {
|
|
83
|
+
const minMinutes = min ? timeToMinutes(convertTimeFormat(min, 24)) : 0;
|
|
84
|
+
const maxMinutes = max ? timeToMinutes(convertTimeFormat(max, 24)) : TIME_CONSTANTS.MINUTES_IN_DAY;
|
|
85
|
+
return { minMinutes, maxMinutes };
|
|
86
|
+
}
|
|
87
|
+
function isWithinConstraints(timeMinutes, minMinutes, maxMinutes) {
|
|
88
|
+
return timeMinutes >= minMinutes && timeMinutes <= maxMinutes;
|
|
89
|
+
}
|
|
90
|
+
const timeToMinutes = (timeStr) => {
|
|
91
|
+
const match = timeStr.match(/^(\d{1,2}):(\d{2})(?:\s*(AM|PM))?$/i);
|
|
92
|
+
if (!match) return 0;
|
|
93
|
+
let hours = parseInt(match[1], 10);
|
|
94
|
+
const minutes = parseInt(match[2], 10);
|
|
95
|
+
const period = match[3]?.toUpperCase();
|
|
96
|
+
if (minutes > MAX_MINUTE) return 0;
|
|
97
|
+
if (period === "PM" && hours !== 12) {
|
|
98
|
+
hours += 12;
|
|
99
|
+
} else if (period === "AM" && hours === 12) {
|
|
100
|
+
hours = 0;
|
|
101
|
+
}
|
|
102
|
+
if (hours > MAX_HOUR) return 0;
|
|
103
|
+
return hours * 60 + minutes;
|
|
104
|
+
};
|
|
105
|
+
function convertTimeFormat(time, targetFormat) {
|
|
106
|
+
if (!time) return time;
|
|
107
|
+
const minutes = timeToMinutes(time);
|
|
108
|
+
if (minutes === 0 && time !== "00:00" && time !== "12:00 AM") {
|
|
109
|
+
return time;
|
|
110
|
+
}
|
|
111
|
+
const hours = Math.floor(minutes / 60);
|
|
112
|
+
const mins = minutes % 60;
|
|
113
|
+
return formatTimeString(hours, mins, targetFormat);
|
|
114
|
+
}
|
|
115
|
+
function normalizePartialInput(value) {
|
|
116
|
+
const cleanValue = value.replace(/[–_:]/g, "").replace(/\s+/g, "");
|
|
117
|
+
if (/^\d{1,2}$/.test(cleanValue)) {
|
|
118
|
+
const result = `${cleanValue}:00`;
|
|
119
|
+
return result;
|
|
120
|
+
} else if (/^\d{1,2}:\d{1}$/.test(cleanValue)) {
|
|
121
|
+
const result = `${cleanValue}0`;
|
|
122
|
+
return result;
|
|
123
|
+
} else if (/^\d{4}$/.test(cleanValue)) {
|
|
124
|
+
const result = `${cleanValue.slice(0, 2)}:${cleanValue.slice(2)}`;
|
|
125
|
+
return result;
|
|
126
|
+
} else if (/^\d{4}(AM|PM)$/i.test(cleanValue)) {
|
|
127
|
+
const digits = cleanValue.slice(0, 4);
|
|
128
|
+
const ampm = cleanValue.slice(4);
|
|
129
|
+
const result = `${digits.slice(0, 2)}:${digits.slice(2)} ${ampm}`;
|
|
130
|
+
return result;
|
|
131
|
+
}
|
|
132
|
+
return value;
|
|
133
|
+
}
|
|
134
|
+
function parse12HourFormat(normalizedValue, min, max) {
|
|
135
|
+
const match = normalizedValue.match(/^(\d{1,2}):(\d{1,2})(?:\s*(AM|PM))?$/i);
|
|
136
|
+
if (!match) return null;
|
|
137
|
+
let hours = parseInt(match[1], 10);
|
|
138
|
+
let minutes = parseInt(match[2], 10);
|
|
139
|
+
const ampm = match[3]?.toUpperCase();
|
|
140
|
+
if (minutes < 10 && normalizedValue.match(/^(\d{1,2}):(\d{1})$/)) {
|
|
141
|
+
minutes *= 10;
|
|
142
|
+
}
|
|
143
|
+
if (ampm === "PM") {
|
|
144
|
+
if (hours !== 12) {
|
|
145
|
+
hours += 12;
|
|
146
|
+
}
|
|
147
|
+
} else if (ampm === "AM") {
|
|
148
|
+
if (hours === 12) {
|
|
149
|
+
hours = 0;
|
|
150
|
+
}
|
|
151
|
+
} else {
|
|
152
|
+
if (hours === 12) {
|
|
153
|
+
hours = 12;
|
|
154
|
+
} else {
|
|
155
|
+
if (min || max) {
|
|
156
|
+
const amHours = hours;
|
|
157
|
+
const pmHours = hours + 12;
|
|
158
|
+
const { minMinutes, maxMinutes } = getConstraintMinutes(min, max);
|
|
159
|
+
const amMinutes = amHours * 60 + minutes;
|
|
160
|
+
const pmMinutes = pmHours * 60 + minutes;
|
|
161
|
+
const amValid = isWithinConstraints(amMinutes, minMinutes, maxMinutes);
|
|
162
|
+
const pmValid = isWithinConstraints(pmMinutes, minMinutes, maxMinutes);
|
|
163
|
+
if (amValid && pmValid) ; else if (pmValid) {
|
|
164
|
+
hours = pmHours;
|
|
165
|
+
} else ;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
return { hours, minutes };
|
|
170
|
+
}
|
|
171
|
+
function parse24HourFormat(normalizedValue) {
|
|
172
|
+
const match = normalizedValue.match(/^(\d{1,2}):(\d{1,2})$/);
|
|
173
|
+
if (!match) {
|
|
174
|
+
return null;
|
|
175
|
+
}
|
|
176
|
+
const hours = parseInt(match[1], 10);
|
|
177
|
+
let minutes = parseInt(match[2], 10);
|
|
178
|
+
if (minutes < 10 && normalizedValue.match(/^(\d{1,2}):(\d{1})$/)) {
|
|
179
|
+
minutes *= 10;
|
|
180
|
+
}
|
|
181
|
+
const result = { hours, minutes };
|
|
182
|
+
return result;
|
|
183
|
+
}
|
|
184
|
+
function validate12HourConstraints(timeString, value, min, max) {
|
|
185
|
+
if (!min && !max) return true;
|
|
186
|
+
const originalHasAMPM = value.toUpperCase().includes("AM") || value.toUpperCase().includes("PM");
|
|
187
|
+
if (!originalHasAMPM) {
|
|
188
|
+
const timePart = timeString.replace(/\s*(AM|PM)$/i, "");
|
|
189
|
+
const amTime = `${timePart} AM`;
|
|
190
|
+
const pmTime = `${timePart} PM`;
|
|
191
|
+
const amMinutes = timeToMinutes(convertTimeFormat(amTime, 24));
|
|
192
|
+
const pmMinutes = timeToMinutes(convertTimeFormat(pmTime, 24));
|
|
193
|
+
const { minMinutes, maxMinutes } = getConstraintMinutes(min, max);
|
|
194
|
+
return isWithinConstraints(amMinutes, minMinutes, maxMinutes) || isWithinConstraints(pmMinutes, minMinutes, maxMinutes);
|
|
195
|
+
} else {
|
|
196
|
+
const timeMinutes = timeToMinutes(convertTimeFormat(timeString, 24));
|
|
197
|
+
const { minMinutes, maxMinutes } = getConstraintMinutes(min, max);
|
|
198
|
+
return isWithinConstraints(timeMinutes, minMinutes, maxMinutes);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
function validate24HourConstraints(timeString, min, max) {
|
|
202
|
+
if (!min && !max) return true;
|
|
203
|
+
const timeMinutes = timeToMinutes(timeString);
|
|
204
|
+
const { minMinutes, maxMinutes } = getConstraintMinutes(min, max);
|
|
205
|
+
if (isWithinConstraints(timeMinutes, minMinutes, maxMinutes)) {
|
|
206
|
+
return true;
|
|
207
|
+
}
|
|
208
|
+
const hours = Math.floor(timeMinutes / 60);
|
|
209
|
+
const minutes = timeMinutes % 60;
|
|
210
|
+
if (minutes === 0) {
|
|
211
|
+
for (let h = minMinutes; h <= maxMinutes; h += 60) {
|
|
212
|
+
const potentialHour = Math.floor(h / 60);
|
|
213
|
+
if (potentialHour.toString().startsWith(hours.toString())) {
|
|
214
|
+
return true;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
if (hours >= Math.floor(minMinutes / 60) && hours <= Math.floor(maxMinutes / 60)) {
|
|
219
|
+
const hourMinutes = hours * 60;
|
|
220
|
+
const hourMinMinutes = Math.max(minMinutes, hourMinutes);
|
|
221
|
+
const hourMaxMinutes = Math.min(maxMinutes, hourMinutes + 59);
|
|
222
|
+
for (let m = hourMinMinutes; m <= hourMaxMinutes; m++) {
|
|
223
|
+
const potentialMinutes = m % 60;
|
|
224
|
+
if (potentialMinutes.toString().startsWith(minutes.toString())) {
|
|
225
|
+
return true;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
return false;
|
|
230
|
+
}
|
|
231
|
+
function parseInputValue(value, format, removePlaceholder, min, max) {
|
|
232
|
+
const valueMinusPlaceholder = removePlaceholder(value);
|
|
233
|
+
if (valueMinusPlaceholder === "") {
|
|
234
|
+
return {
|
|
235
|
+
time: null,
|
|
236
|
+
isInputValid: false,
|
|
237
|
+
isInputEmpty: true
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
const normalizedValue = normalizePartialInput(valueMinusPlaceholder);
|
|
241
|
+
const parsed = format === 12 ? parse12HourFormat(normalizedValue, min, max) : parse24HourFormat(normalizedValue);
|
|
242
|
+
if (!parsed) {
|
|
243
|
+
return {
|
|
244
|
+
time: null,
|
|
245
|
+
isInputValid: false,
|
|
246
|
+
isInputEmpty: false
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
const { hours, minutes } = parsed;
|
|
250
|
+
if (hours < 0 || hours > MAX_HOUR || minutes < 0 || minutes > MAX_MINUTE) {
|
|
251
|
+
return {
|
|
252
|
+
time: null,
|
|
253
|
+
isInputValid: false,
|
|
254
|
+
isInputEmpty: false
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
const timeString = formatTimeString(hours, minutes, format);
|
|
258
|
+
const isValid = format === 12 ? validate12HourConstraints(timeString, value, min, max) : validate24HourConstraints(timeString, min, max);
|
|
259
|
+
if (!isValid) {
|
|
260
|
+
return {
|
|
261
|
+
time: null,
|
|
262
|
+
isInputValid: false,
|
|
263
|
+
isInputEmpty: false
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
return {
|
|
267
|
+
time: timeString,
|
|
268
|
+
isInputValid: true,
|
|
269
|
+
isInputEmpty: false
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function isTimeOnStep(time, step) {
|
|
274
|
+
if (!step || step <= 0) return true;
|
|
275
|
+
const totalMinutes = timeToMinutes(time);
|
|
276
|
+
return totalMinutes % step === 0;
|
|
277
|
+
}
|
|
278
|
+
function isValidTime(time) {
|
|
279
|
+
if (!time) return false;
|
|
280
|
+
return timeToMinutes(time) > 0 || time === "00:00";
|
|
281
|
+
}
|
|
282
|
+
function isPartialInputPotentiallyValid(inputValue, format, min, max) {
|
|
283
|
+
if (!min || !max) return false;
|
|
284
|
+
const digits = inputValue.replace(/[^\d]/g, "");
|
|
285
|
+
if (digits.length === 1) {
|
|
286
|
+
const digit = parseInt(digits, 10);
|
|
287
|
+
const minMinutes = timeToMinutes(min);
|
|
288
|
+
const maxMinutes = timeToMinutes(max);
|
|
289
|
+
for (let h = 0; h <= 23; h++) {
|
|
290
|
+
const hourStr = h.toString().padStart(2, "0");
|
|
291
|
+
const hourStrNoPad = h.toString();
|
|
292
|
+
const startsWithDigit = hourStr.startsWith(digit.toString()) || hourStrNoPad.startsWith(digit.toString());
|
|
293
|
+
if (!startsWithDigit) continue;
|
|
294
|
+
if (format === 24) {
|
|
295
|
+
const hourMinutes = h * 60;
|
|
296
|
+
if (hourMinutes >= minMinutes && hourMinutes <= maxMinutes) {
|
|
297
|
+
return true;
|
|
298
|
+
}
|
|
299
|
+
} else {
|
|
300
|
+
if (h === 0) {
|
|
301
|
+
if (digit === 1) {
|
|
302
|
+
const midnightMinutes = 0;
|
|
303
|
+
if (midnightMinutes >= minMinutes && midnightMinutes <= maxMinutes) {
|
|
304
|
+
return true;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
} else if (h >= 1 && h <= 12) {
|
|
308
|
+
const amHour24 = h === 12 ? 0 : h;
|
|
309
|
+
const amMinutes = amHour24 * 60;
|
|
310
|
+
if (amMinutes >= minMinutes && amMinutes <= maxMinutes) {
|
|
311
|
+
return true;
|
|
312
|
+
}
|
|
313
|
+
const pmHour24 = h === 12 ? 12 : h + 12;
|
|
314
|
+
const pmMinutes = pmHour24 * 60;
|
|
315
|
+
if (pmMinutes >= minMinutes && pmMinutes <= maxMinutes) {
|
|
316
|
+
return true;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
return false;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
const styles = {
|
|
326
|
+
"time-field": "_time-field_11xzw_2"
|
|
327
|
+
};
|
|
328
|
+
|
|
329
|
+
const MaskedTimeInput = forwardRef(
|
|
330
|
+
({
|
|
331
|
+
lastValidTime,
|
|
332
|
+
format,
|
|
333
|
+
placeholder,
|
|
334
|
+
min,
|
|
335
|
+
max,
|
|
336
|
+
onInputChange,
|
|
337
|
+
onKeyDown,
|
|
338
|
+
onFocus,
|
|
339
|
+
className,
|
|
340
|
+
...props
|
|
341
|
+
}, ref) => {
|
|
342
|
+
const [inputValue, setInputValue] = useState(placeholder);
|
|
343
|
+
const { options, removePlaceholder } = timePlaceholderMask({
|
|
344
|
+
format,
|
|
345
|
+
placeholder
|
|
346
|
+
});
|
|
347
|
+
const maskedInputRef = useMaskito({ options });
|
|
348
|
+
const inputRef = useRef(null);
|
|
349
|
+
const combinedRef = useMergeRefs([maskedInputRef, inputRef, ref]);
|
|
350
|
+
const previousTimeRef = useRef(null);
|
|
351
|
+
const isUserTypingRef = useRef(false);
|
|
352
|
+
const inputValueRef = useRef(placeholder);
|
|
353
|
+
const previousPlaceholderRef = useRef(placeholder);
|
|
354
|
+
const currentParsedData = useMemo(() => {
|
|
355
|
+
return parseInputValue(inputValue, format, removePlaceholder, min, max);
|
|
356
|
+
}, [inputValue, format, removePlaceholder, min, max]);
|
|
357
|
+
useEffect(() => {
|
|
358
|
+
inputValueRef.current = inputValue;
|
|
359
|
+
}, [inputValue]);
|
|
360
|
+
useEffect(() => {
|
|
361
|
+
previousPlaceholderRef.current = placeholder;
|
|
362
|
+
}, [placeholder]);
|
|
363
|
+
useEffect(() => {
|
|
364
|
+
if (lastValidTime === void 0) return;
|
|
365
|
+
if (lastValidTime === previousTimeRef.current) return;
|
|
366
|
+
if (isUserTypingRef.current) return;
|
|
367
|
+
const newInputValue = lastValidTime ? convertTimeFormat(lastValidTime, format) : placeholder;
|
|
368
|
+
setInputValue(newInputValue);
|
|
369
|
+
inputValueRef.current = newInputValue;
|
|
370
|
+
previousTimeRef.current = lastValidTime;
|
|
371
|
+
}, [lastValidTime, format, placeholder]);
|
|
372
|
+
useEffect(() => {
|
|
373
|
+
if (placeholder === previousPlaceholderRef.current) return;
|
|
374
|
+
if (lastValidTime && inputValue === lastValidTime) return;
|
|
375
|
+
const isOldPlaceholder = inputValue === TIME_FORMAT_PLACEHOLDERS[12] || inputValue === TIME_FORMAT_PLACEHOLDERS[24];
|
|
376
|
+
if (isOldPlaceholder) {
|
|
377
|
+
setInputValue(placeholder);
|
|
378
|
+
}
|
|
379
|
+
previousPlaceholderRef.current = placeholder;
|
|
380
|
+
}, [placeholder, lastValidTime, inputValue]);
|
|
381
|
+
const handleChange = useCallback(
|
|
382
|
+
(event) => {
|
|
383
|
+
isUserTypingRef.current = true;
|
|
384
|
+
const { time, isInputValid, isInputEmpty } = parseInputValue(
|
|
385
|
+
event.target.value,
|
|
386
|
+
format,
|
|
387
|
+
removePlaceholder,
|
|
388
|
+
min,
|
|
389
|
+
max
|
|
390
|
+
);
|
|
391
|
+
const isPotentiallyValidPartial = !isInputValid && !isInputEmpty && event.target.value.length >= placeholder.length && // User is typing, not deleting
|
|
392
|
+
isPartialInputPotentiallyValid(event.target.value, format, min, max);
|
|
393
|
+
if (isInputValid || isInputEmpty || isPotentiallyValidPartial) {
|
|
394
|
+
setInputValue(event.target.value);
|
|
395
|
+
} else {
|
|
396
|
+
setInputValue(placeholder);
|
|
397
|
+
}
|
|
398
|
+
onInputChange?.({
|
|
399
|
+
event,
|
|
400
|
+
time,
|
|
401
|
+
isInputValid,
|
|
402
|
+
isInputEmpty
|
|
403
|
+
});
|
|
404
|
+
setTimeout(() => {
|
|
405
|
+
isUserTypingRef.current = false;
|
|
406
|
+
}, TIME_CONSTANTS.INPUT_DEBOUNCE_TIMEOUT);
|
|
407
|
+
},
|
|
408
|
+
[format, removePlaceholder, onInputChange, min, max, placeholder]
|
|
409
|
+
);
|
|
410
|
+
const resetTypingFlag = useCallback(() => {
|
|
411
|
+
isUserTypingRef.current = false;
|
|
412
|
+
}, []);
|
|
413
|
+
useImperativeHandle(
|
|
414
|
+
ref,
|
|
415
|
+
() => ({
|
|
416
|
+
...inputRef.current,
|
|
417
|
+
focus: () => inputRef.current?.focus(),
|
|
418
|
+
resetTypingFlag
|
|
419
|
+
})
|
|
420
|
+
);
|
|
421
|
+
const classNames = useMemo(() => {
|
|
422
|
+
return cx(styles["time-field"], className);
|
|
423
|
+
}, [className]);
|
|
424
|
+
return /* @__PURE__ */ jsx(
|
|
425
|
+
TextField,
|
|
426
|
+
{
|
|
427
|
+
ref: combinedRef,
|
|
428
|
+
"data-input-valid": currentParsedData.isInputValid,
|
|
429
|
+
"data-input-empty": currentParsedData.isInputEmpty,
|
|
430
|
+
"data-time": currentParsedData.time ?? "",
|
|
431
|
+
value: inputValue,
|
|
432
|
+
onChange: handleChange,
|
|
433
|
+
prefix: /* @__PURE__ */ jsx(Icon, { svg: CLOCK_ICON }),
|
|
434
|
+
autoComplete: "off",
|
|
435
|
+
onKeyDown,
|
|
436
|
+
onFocus,
|
|
437
|
+
className: classNames,
|
|
438
|
+
...props
|
|
439
|
+
}
|
|
440
|
+
);
|
|
441
|
+
}
|
|
442
|
+
);
|
|
443
|
+
MaskedTimeInput.displayName = "MaskedTimeInput";
|
|
444
|
+
|
|
445
|
+
function generateTimeOptions({
|
|
446
|
+
step = 30,
|
|
447
|
+
min,
|
|
448
|
+
max,
|
|
449
|
+
format
|
|
450
|
+
}) {
|
|
451
|
+
const options = [];
|
|
452
|
+
const minMinutes = min ? timeToMinutes(min) : 0;
|
|
453
|
+
const maxMinutes = max ? timeToMinutes(max) : TIME_CONSTANTS.MINUTES_IN_DAY - 1;
|
|
454
|
+
for (let minutes = 0; minutes < TIME_CONSTANTS.MINUTES_IN_DAY; minutes += step) {
|
|
455
|
+
if (minutes < minMinutes || minutes > maxMinutes) {
|
|
456
|
+
continue;
|
|
457
|
+
}
|
|
458
|
+
const hours = Math.floor(minutes / 60);
|
|
459
|
+
const mins = minutes % 60;
|
|
460
|
+
options.push(formatTimeString(hours, mins, format));
|
|
461
|
+
}
|
|
462
|
+
return options;
|
|
463
|
+
}
|
|
464
|
+
function normalizeTimeString(str) {
|
|
465
|
+
return str.replace(/[–_:]/g, "").replace(/\s+/g, "").toLowerCase();
|
|
466
|
+
}
|
|
467
|
+
function filterTimeOptions(options, userInput) {
|
|
468
|
+
if (!userInput || userInput === TIME_FORMAT_PLACEHOLDERS[12] || userInput === TIME_FORMAT_PLACEHOLDERS[24]) {
|
|
469
|
+
return options;
|
|
470
|
+
}
|
|
471
|
+
const normalizedInput = normalizeTimeString(userInput);
|
|
472
|
+
const filtered = options.filter((option) => {
|
|
473
|
+
const normalizedOption = normalizeTimeString(option);
|
|
474
|
+
return normalizedOption.startsWith(normalizedInput);
|
|
475
|
+
});
|
|
476
|
+
return filtered;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
function getNextFutureStep(time, step, format) {
|
|
480
|
+
if (!step || step <= 0) return time;
|
|
481
|
+
const totalMinutes = timeToMinutes(time);
|
|
482
|
+
const nextStep = Math.ceil(totalMinutes / step) * step;
|
|
483
|
+
const hours = Math.floor(nextStep / 60);
|
|
484
|
+
const minutes = nextStep % 60;
|
|
485
|
+
return formatTimeString(hours, minutes, format);
|
|
486
|
+
}
|
|
487
|
+
function handleAutoRounding(time, options) {
|
|
488
|
+
const { autoround, step, format } = options;
|
|
489
|
+
if (!autoround || !time) return time;
|
|
490
|
+
const stepValue = step ?? 30;
|
|
491
|
+
if (isTimeOnStep(time, stepValue)) {
|
|
492
|
+
return time;
|
|
493
|
+
}
|
|
494
|
+
return getNextFutureStep(time, stepValue, format);
|
|
495
|
+
}
|
|
496
|
+
function generateAmPmVariants(currentTime, step, inputValue) {
|
|
497
|
+
const hasAM = inputValue.toUpperCase().includes("AM");
|
|
498
|
+
const hasPM = inputValue.toUpperCase().includes("PM");
|
|
499
|
+
const getNextStepForTime = (time) => {
|
|
500
|
+
const currentMinutes = timeToMinutes(time);
|
|
501
|
+
const nextStepMinutes = Math.ceil(currentMinutes / step) * step;
|
|
502
|
+
const finalMinutes = nextStepMinutes === currentMinutes ? nextStepMinutes + step : nextStepMinutes;
|
|
503
|
+
const hours = Math.floor(finalMinutes / 60);
|
|
504
|
+
const minutes = finalMinutes % 60;
|
|
505
|
+
return formatTimeString(hours, minutes, 12);
|
|
506
|
+
};
|
|
507
|
+
const nextStep = getNextFutureStep(currentTime, step, 12);
|
|
508
|
+
if (hasAM || hasPM) {
|
|
509
|
+
return [nextStep];
|
|
510
|
+
} else {
|
|
511
|
+
const currentMinutes = timeToMinutes(currentTime);
|
|
512
|
+
const pmMinutes = currentMinutes + 12 * 60;
|
|
513
|
+
const pmHours = Math.floor(pmMinutes / 60);
|
|
514
|
+
const pmMins = pmMinutes % 60;
|
|
515
|
+
const pmVersion = getNextStepForTime(formatTimeString(pmHours, pmMins, 24));
|
|
516
|
+
return [nextStep, pmVersion];
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
function generatePmInterpretation(currentTime, step, inputValue) {
|
|
520
|
+
if (inputValue.toUpperCase().includes("AM") || inputValue.toUpperCase().includes("PM")) {
|
|
521
|
+
return [];
|
|
522
|
+
}
|
|
523
|
+
const currentMinutes = timeToMinutes(currentTime);
|
|
524
|
+
const pmMinutes = currentMinutes + 12 * 60;
|
|
525
|
+
const pmHours = Math.floor(pmMinutes / 60);
|
|
526
|
+
const pmMins = pmMinutes % 60;
|
|
527
|
+
const pmTime = formatTimeString(pmHours, pmMins, 12);
|
|
528
|
+
if (isValidTime(pmTime)) {
|
|
529
|
+
const pmNextStep = getNextFutureStep(pmTime, step, 12);
|
|
530
|
+
return [pmNextStep];
|
|
531
|
+
}
|
|
532
|
+
return [];
|
|
533
|
+
}
|
|
534
|
+
function getAutoRoundingOptions(currentTime, step, format, inputValue, min) {
|
|
535
|
+
if (!currentTime) {
|
|
536
|
+
return [];
|
|
537
|
+
}
|
|
538
|
+
if (!isValidTime(currentTime)) {
|
|
539
|
+
if (format === 12) {
|
|
540
|
+
return generatePmInterpretation(currentTime, step, inputValue);
|
|
541
|
+
}
|
|
542
|
+
return [];
|
|
543
|
+
}
|
|
544
|
+
const nextStep = getNextFutureStep(currentTime, step, format);
|
|
545
|
+
if (format === 12 && !min) {
|
|
546
|
+
return generateAmPmVariants(currentTime, step, inputValue);
|
|
547
|
+
}
|
|
548
|
+
return [nextStep];
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
const TimeFieldElement = forwardRef(
|
|
552
|
+
function TimeField2({
|
|
553
|
+
value: valueProp,
|
|
554
|
+
defaultValue,
|
|
555
|
+
min,
|
|
556
|
+
max,
|
|
557
|
+
onChange,
|
|
558
|
+
format = 12,
|
|
559
|
+
step = 30,
|
|
560
|
+
required,
|
|
561
|
+
description,
|
|
562
|
+
error: errorProp,
|
|
563
|
+
disableSuggestions = false,
|
|
564
|
+
autoround = false,
|
|
565
|
+
className,
|
|
566
|
+
...rest
|
|
567
|
+
}, ref) {
|
|
568
|
+
const [lastValidTime, setLastValidTime] = useOptionallyControlledState({
|
|
569
|
+
controlledValue: valueProp,
|
|
570
|
+
defaultValue: defaultValue ?? null
|
|
571
|
+
});
|
|
572
|
+
const [isDropdownOpen, setIsDropdownOpenRaw] = useState(false);
|
|
573
|
+
const setIsDropdownOpen = (open) => {
|
|
574
|
+
setIsDropdownOpenRaw(open);
|
|
575
|
+
};
|
|
576
|
+
const [selectedOptionIndex, setSelectedOptionIndex] = useState(0);
|
|
577
|
+
const [inputValueForFiltering, setInputValueForFiltering] = useState("");
|
|
578
|
+
const [currentParsedTime, setCurrentParsedTime] = useState(
|
|
579
|
+
null
|
|
580
|
+
);
|
|
581
|
+
const [inputParsedData, setInputParsedData] = useState({ time: null, isInputValid: false, isInputEmpty: true });
|
|
582
|
+
const lastOnChangeCallRef = useRef(null);
|
|
583
|
+
const isDropdownClickingRef = useRef(false);
|
|
584
|
+
const [justSelected, setJustSelected] = useState(false);
|
|
585
|
+
const tabJustPressedRef = useRef(false);
|
|
586
|
+
const inputRef = useRef(null);
|
|
587
|
+
const popoverContentRef = useRef(null);
|
|
588
|
+
const combinedRef = useMergeRefs([inputRef, ref]);
|
|
589
|
+
const popoverSupported = usePopoverSupport();
|
|
590
|
+
const placeholder = TIME_FORMAT_PLACEHOLDERS[format];
|
|
591
|
+
const shouldShowOptionsFromHook = usePopoverCloseDelayWorkaround(isDropdownOpen);
|
|
592
|
+
const shouldShowOptions = tabJustPressedRef.current ? false : shouldShowOptionsFromHook;
|
|
593
|
+
const dropdownTimeoutRef = useRef(null);
|
|
594
|
+
useEffect(() => {
|
|
595
|
+
const dropdownRef = dropdownTimeoutRef.current;
|
|
596
|
+
return () => {
|
|
597
|
+
if (dropdownRef) {
|
|
598
|
+
clearTimeout(dropdownRef);
|
|
599
|
+
}
|
|
600
|
+
};
|
|
601
|
+
}, []);
|
|
602
|
+
const allTimeOptions = useMemo(
|
|
603
|
+
() => generateTimeOptions({ step, min, max, format }),
|
|
604
|
+
[step, min, max, format]
|
|
605
|
+
);
|
|
606
|
+
const baseFilteredOptions = useMemo(() => {
|
|
607
|
+
if (!inputValueForFiltering || inputValueForFiltering.match(/^[–_:]+$/)) {
|
|
608
|
+
return allTimeOptions;
|
|
609
|
+
}
|
|
610
|
+
return filterTimeOptions(allTimeOptions, inputValueForFiltering);
|
|
611
|
+
}, [allTimeOptions, inputValueForFiltering]);
|
|
612
|
+
const autoRoundingOptions = useMemo(() => {
|
|
613
|
+
if (!autoround || baseFilteredOptions.length > 0 || !inputValueForFiltering || inputValueForFiltering.match(/^[–_:]+$/)) {
|
|
614
|
+
return [];
|
|
615
|
+
}
|
|
616
|
+
const currentTime = currentParsedTime || inputParsedData.time;
|
|
617
|
+
const autoRoundedOptions = getAutoRoundingOptions(
|
|
618
|
+
currentTime,
|
|
619
|
+
step ?? 30,
|
|
620
|
+
format,
|
|
621
|
+
inputValueForFiltering,
|
|
622
|
+
min
|
|
623
|
+
);
|
|
624
|
+
return autoRoundedOptions.filter(
|
|
625
|
+
(option) => !baseFilteredOptions.includes(option)
|
|
626
|
+
);
|
|
627
|
+
}, [
|
|
628
|
+
autoround,
|
|
629
|
+
baseFilteredOptions,
|
|
630
|
+
inputValueForFiltering,
|
|
631
|
+
currentParsedTime,
|
|
632
|
+
inputParsedData.time,
|
|
633
|
+
format,
|
|
634
|
+
min,
|
|
635
|
+
step
|
|
636
|
+
]);
|
|
637
|
+
const filteredOptions = useMemo(() => {
|
|
638
|
+
if (baseFilteredOptions.length === 0 && autoRoundingOptions.length === 0) {
|
|
639
|
+
if (!inputParsedData.isInputValid && inputValueForFiltering && !inputValueForFiltering.match(/^[–_:]+$/)) {
|
|
640
|
+
return allTimeOptions;
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
return [...baseFilteredOptions, ...autoRoundingOptions];
|
|
644
|
+
}, [
|
|
645
|
+
baseFilteredOptions,
|
|
646
|
+
autoRoundingOptions,
|
|
647
|
+
allTimeOptions,
|
|
648
|
+
inputValueForFiltering,
|
|
649
|
+
inputParsedData.isInputValid
|
|
650
|
+
]);
|
|
651
|
+
const handleBlur = useCallback(
|
|
652
|
+
(event) => {
|
|
653
|
+
if (event.relatedTarget && popoverContentRef.current?.contains(event.relatedTarget) && isDropdownClickingRef.current) {
|
|
654
|
+
isDropdownClickingRef.current = false;
|
|
655
|
+
return;
|
|
656
|
+
}
|
|
657
|
+
setIsDropdownOpen(false);
|
|
658
|
+
setSelectedOptionIndex(0);
|
|
659
|
+
const input = event.target;
|
|
660
|
+
const parsedInputTime = input.dataset.time || null;
|
|
661
|
+
const isInputValid = input.dataset.inputValid === "true";
|
|
662
|
+
const isInputEmpty = input.dataset.inputEmpty === "true";
|
|
663
|
+
let finalTime = parsedInputTime;
|
|
664
|
+
if (autoround && finalTime && isInputValid) {
|
|
665
|
+
finalTime = handleAutoRounding(finalTime, {
|
|
666
|
+
autoround,
|
|
667
|
+
step,
|
|
668
|
+
format
|
|
669
|
+
});
|
|
670
|
+
}
|
|
671
|
+
if (finalTime) {
|
|
672
|
+
inputRef.current?.resetTypingFlag?.();
|
|
673
|
+
setLastValidTime(finalTime);
|
|
674
|
+
}
|
|
675
|
+
const currentChange = {
|
|
676
|
+
time: finalTime,
|
|
677
|
+
isInputValid,
|
|
678
|
+
isInputEmpty
|
|
679
|
+
};
|
|
680
|
+
const lastChange = lastOnChangeCallRef.current;
|
|
681
|
+
const isDuplicate = lastChange && lastChange.time === currentChange.time && lastChange.isInputValid === currentChange.isInputValid && lastChange.isInputEmpty === currentChange.isInputEmpty;
|
|
682
|
+
if (!isDuplicate) {
|
|
683
|
+
onChange?.(currentChange);
|
|
684
|
+
lastOnChangeCallRef.current = currentChange;
|
|
685
|
+
}
|
|
686
|
+
},
|
|
687
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
688
|
+
[format, step, autoround, onChange]
|
|
689
|
+
);
|
|
690
|
+
const handleFocus = useCallback(() => {
|
|
691
|
+
if (!disableSuggestions && !justSelected) {
|
|
692
|
+
setIsDropdownOpen(true);
|
|
693
|
+
setSelectedOptionIndex(0);
|
|
694
|
+
}
|
|
695
|
+
}, [disableSuggestions, justSelected]);
|
|
696
|
+
useEffect(() => {
|
|
697
|
+
if (filteredOptions.length <= 1 && isDropdownOpen) {
|
|
698
|
+
setIsDropdownOpen(false);
|
|
699
|
+
}
|
|
700
|
+
if (!disableSuggestions && filteredOptions.length > 1 && document.activeElement && document.activeElement.tagName === "INPUT" && !isDropdownOpen && !justSelected && !tabJustPressedRef.current) {
|
|
701
|
+
setIsDropdownOpen(true);
|
|
702
|
+
setSelectedOptionIndex(0);
|
|
703
|
+
}
|
|
704
|
+
}, [filteredOptions, disableSuggestions, isDropdownOpen, justSelected]);
|
|
705
|
+
const handleInputChange = (change) => {
|
|
706
|
+
setInputValueForFiltering(change.event.target.value);
|
|
707
|
+
setCurrentParsedTime(change.time);
|
|
708
|
+
setInputParsedData({
|
|
709
|
+
time: change.time,
|
|
710
|
+
isInputValid: change.isInputValid,
|
|
711
|
+
isInputEmpty: change.isInputEmpty
|
|
712
|
+
});
|
|
713
|
+
setSelectedOptionIndex(0);
|
|
714
|
+
setJustSelected(false);
|
|
715
|
+
};
|
|
716
|
+
const scrollToSelectedOption = useCallback(() => {
|
|
717
|
+
if (!popoverContentRef.current || selectedOptionIndex < 0) return;
|
|
718
|
+
const optionElements = popoverContentRef.current.querySelectorAll('[role="option"]');
|
|
719
|
+
const targetElement = optionElements[selectedOptionIndex];
|
|
720
|
+
if (targetElement) {
|
|
721
|
+
targetElement.scrollIntoView({
|
|
722
|
+
block: "center",
|
|
723
|
+
behavior: "auto"
|
|
724
|
+
});
|
|
725
|
+
}
|
|
726
|
+
}, [selectedOptionIndex]);
|
|
727
|
+
useEffect(() => {
|
|
728
|
+
if (isDropdownOpen && selectedOptionIndex >= 0) {
|
|
729
|
+
scrollToSelectedOption();
|
|
730
|
+
}
|
|
731
|
+
}, [selectedOptionIndex, isDropdownOpen, scrollToSelectedOption]);
|
|
732
|
+
const handleOptionSelect = useCallback(
|
|
733
|
+
(selectedTime) => {
|
|
734
|
+
if (inputRef.current?.resetTypingFlag) {
|
|
735
|
+
inputRef.current.resetTypingFlag();
|
|
736
|
+
}
|
|
737
|
+
setIsDropdownOpen(false);
|
|
738
|
+
setSelectedOptionIndex(0);
|
|
739
|
+
setJustSelected(true);
|
|
740
|
+
setLastValidTime(selectedTime);
|
|
741
|
+
setCurrentParsedTime(selectedTime);
|
|
742
|
+
const changeData = {
|
|
743
|
+
time: selectedTime,
|
|
744
|
+
isInputValid: true,
|
|
745
|
+
isInputEmpty: false
|
|
746
|
+
};
|
|
747
|
+
onChange?.(changeData);
|
|
748
|
+
lastOnChangeCallRef.current = changeData;
|
|
749
|
+
requestAnimationFrame(() => {
|
|
750
|
+
inputRef.current?.focus();
|
|
751
|
+
});
|
|
752
|
+
},
|
|
753
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
754
|
+
[onChange]
|
|
755
|
+
);
|
|
756
|
+
const handleKeyDown = useCallback(
|
|
757
|
+
(event) => {
|
|
758
|
+
if (!isDropdownOpen) {
|
|
759
|
+
return;
|
|
760
|
+
}
|
|
761
|
+
switch (event.key) {
|
|
762
|
+
case "ArrowDown":
|
|
763
|
+
event.preventDefault();
|
|
764
|
+
setSelectedOptionIndex(
|
|
765
|
+
(prev) => prev < filteredOptions.length - 1 ? prev + 1 : 0
|
|
766
|
+
);
|
|
767
|
+
break;
|
|
768
|
+
case "ArrowUp":
|
|
769
|
+
event.preventDefault();
|
|
770
|
+
setSelectedOptionIndex(
|
|
771
|
+
(prev) => prev > 0 ? prev - 1 : filteredOptions.length - 1
|
|
772
|
+
);
|
|
773
|
+
break;
|
|
774
|
+
case "Enter":
|
|
775
|
+
event.preventDefault();
|
|
776
|
+
if (filteredOptions[selectedOptionIndex]) {
|
|
777
|
+
handleOptionSelect(filteredOptions[selectedOptionIndex]);
|
|
778
|
+
}
|
|
779
|
+
break;
|
|
780
|
+
case "Tab":
|
|
781
|
+
tabJustPressedRef.current = true;
|
|
782
|
+
setIsDropdownOpen(false);
|
|
783
|
+
setSelectedOptionIndex(0);
|
|
784
|
+
setTimeout(() => {
|
|
785
|
+
tabJustPressedRef.current = false;
|
|
786
|
+
}, 50);
|
|
787
|
+
break;
|
|
788
|
+
}
|
|
789
|
+
},
|
|
790
|
+
[
|
|
791
|
+
isDropdownOpen,
|
|
792
|
+
selectedOptionIndex,
|
|
793
|
+
filteredOptions,
|
|
794
|
+
handleOptionSelect
|
|
795
|
+
]
|
|
796
|
+
);
|
|
797
|
+
const popoverDisabled = disableSuggestions || !popoverSupported;
|
|
798
|
+
const justTheInput = /* @__PURE__ */ jsx(
|
|
799
|
+
MaskedTimeInput,
|
|
800
|
+
{
|
|
801
|
+
ref: combinedRef,
|
|
802
|
+
lastValidTime,
|
|
803
|
+
format,
|
|
804
|
+
placeholder,
|
|
805
|
+
min,
|
|
806
|
+
max,
|
|
807
|
+
onInputChange: handleInputChange,
|
|
808
|
+
onKeyDown: handleKeyDown,
|
|
809
|
+
onBlur: handleBlur,
|
|
810
|
+
onFocus: handleFocus,
|
|
811
|
+
description,
|
|
812
|
+
error: errorProp,
|
|
813
|
+
className: popoverDisabled ? className : void 0,
|
|
814
|
+
...rest,
|
|
815
|
+
"data-anv": "time-field"
|
|
816
|
+
}
|
|
817
|
+
);
|
|
818
|
+
if (popoverDisabled) {
|
|
819
|
+
return justTheInput;
|
|
820
|
+
}
|
|
821
|
+
return /* @__PURE__ */ jsxs(
|
|
822
|
+
Popover,
|
|
823
|
+
{
|
|
824
|
+
open: isDropdownOpen,
|
|
825
|
+
noPadding: true,
|
|
826
|
+
disableCaret: true,
|
|
827
|
+
placement: "bottom-start",
|
|
828
|
+
matchReferenceWidth: true,
|
|
829
|
+
modal: false,
|
|
830
|
+
onClickOutside: () => {
|
|
831
|
+
setIsDropdownOpen(false);
|
|
832
|
+
},
|
|
833
|
+
onClose: () => {
|
|
834
|
+
setIsDropdownOpen(false);
|
|
835
|
+
},
|
|
836
|
+
fitScreen: true,
|
|
837
|
+
children: [
|
|
838
|
+
/* @__PURE__ */ jsx(Popover.Trigger, { children: ({ ref: popoverRef }) => /* @__PURE__ */ jsx(
|
|
839
|
+
"div",
|
|
840
|
+
{
|
|
841
|
+
ref: popoverRef,
|
|
842
|
+
className,
|
|
843
|
+
children: justTheInput
|
|
844
|
+
}
|
|
845
|
+
) }),
|
|
846
|
+
/* @__PURE__ */ jsx(
|
|
847
|
+
Popover.Content,
|
|
848
|
+
{
|
|
849
|
+
ref: popoverContentRef,
|
|
850
|
+
"data-open": isDropdownOpen,
|
|
851
|
+
"data-disabled": disableSuggestions,
|
|
852
|
+
onMouseDown: () => {
|
|
853
|
+
isDropdownClickingRef.current = true;
|
|
854
|
+
},
|
|
855
|
+
onTouchStart: () => {
|
|
856
|
+
isDropdownClickingRef.current = true;
|
|
857
|
+
},
|
|
858
|
+
children: shouldShowOptions && /* @__PURE__ */ jsx(
|
|
859
|
+
Listbox,
|
|
860
|
+
{
|
|
861
|
+
selected: filteredOptions[selectedOptionIndex],
|
|
862
|
+
onSelectionChange: (selected) => {
|
|
863
|
+
if (selected) {
|
|
864
|
+
handleOptionSelect(selected);
|
|
865
|
+
} else {
|
|
866
|
+
const currentOption = filteredOptions[selectedOptionIndex];
|
|
867
|
+
if (currentOption) {
|
|
868
|
+
handleOptionSelect(currentOption);
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
},
|
|
872
|
+
"aria-label": "Time suggestions",
|
|
873
|
+
children: filteredOptions.map((option) => /* @__PURE__ */ jsx(Listbox.Option, { value: option, label: option, children: option }, option))
|
|
874
|
+
}
|
|
875
|
+
)
|
|
876
|
+
}
|
|
877
|
+
)
|
|
878
|
+
]
|
|
879
|
+
}
|
|
880
|
+
);
|
|
881
|
+
}
|
|
882
|
+
);
|
|
883
|
+
const TimeField$1 = Object.assign(TimeFieldElement, {});
|
|
884
|
+
TimeField$1.displayName = "TimeField";
|
|
885
|
+
|
|
886
|
+
const TimeField = forwardRef(
|
|
887
|
+
(props, ref) => {
|
|
888
|
+
const data = {
|
|
889
|
+
label: childrenToString(props.label),
|
|
890
|
+
labelProps: props.labelProps,
|
|
891
|
+
prefix: childrenToString(props.prefix),
|
|
892
|
+
hint: childrenToString(props.hint),
|
|
893
|
+
description: childrenToString(props.description),
|
|
894
|
+
size: props.size,
|
|
895
|
+
format: props.format,
|
|
896
|
+
step: props.step,
|
|
897
|
+
min: props.min,
|
|
898
|
+
max: props.max,
|
|
899
|
+
autoround: props.autoround,
|
|
900
|
+
disableSuggestions: props.disableSuggestions
|
|
901
|
+
};
|
|
902
|
+
const trackingId = useTrackingId({
|
|
903
|
+
name: "TimeField",
|
|
904
|
+
data,
|
|
905
|
+
hasOverride: !!props["data-tracking-id"]
|
|
906
|
+
});
|
|
907
|
+
return /* @__PURE__ */ jsx(TimeField$1, { ref, "data-tracking-id": trackingId, ...props });
|
|
908
|
+
}
|
|
909
|
+
);
|
|
910
|
+
TimeField.displayName = TimeField$1.displayName;
|
|
911
|
+
|
|
912
|
+
export { TimeField as T };
|
|
913
|
+
//# sourceMappingURL=TimeField-DRHLRqN3.js.map
|