@dmsi/wedgekit-react 0.0.551 → 0.0.552

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (137) hide show
  1. package/package.json +2 -3
  2. package/src/brand.css +0 -125
  3. package/src/classNames.ts +0 -174
  4. package/src/components/AccessChangerTabItem.tsx +0 -71
  5. package/src/components/Accordion.tsx +0 -108
  6. package/src/components/Alert.tsx +0 -81
  7. package/src/components/Breadcrumbs.tsx +0 -142
  8. package/src/components/Button.tsx +0 -216
  9. package/src/components/CalendarRange.tsx +0 -628
  10. package/src/components/Caption.tsx +0 -144
  11. package/src/components/Card.tsx +0 -88
  12. package/src/components/Checkbox.tsx +0 -206
  13. package/src/components/CompactImagesPreview.tsx +0 -135
  14. package/src/components/ContentTab.tsx +0 -84
  15. package/src/components/ContentTabs.tsx +0 -136
  16. package/src/components/DMSiLogo.tsx +0 -33
  17. package/src/components/DataGrid/ColumnSelectorHeaderCell/ColumnSelectorMenuOption.tsx +0 -35
  18. package/src/components/DataGrid/ColumnSelectorHeaderCell/index.tsx +0 -74
  19. package/src/components/DataGrid/PinnedColumns.tsx +0 -183
  20. package/src/components/DataGrid/TableBody/LoadingCell.tsx +0 -44
  21. package/src/components/DataGrid/TableBody/TableBodyRow.tsx +0 -157
  22. package/src/components/DataGrid/TableBody/index.tsx +0 -185
  23. package/src/components/DataGrid/index.tsx +0 -756
  24. package/src/components/DataGrid/types.ts +0 -98
  25. package/src/components/DataGrid/utils.tsx +0 -15
  26. package/src/components/DataGridCell.tsx +0 -526
  27. package/src/components/DataTable.tsx +0 -881
  28. package/src/components/DateInput.tsx +0 -306
  29. package/src/components/DateRangeInput.tsx +0 -758
  30. package/src/components/DebugJson.tsx +0 -28
  31. package/src/components/Display.tsx +0 -66
  32. package/src/components/EditingContext.tsx +0 -43
  33. package/src/components/EmptyCartIcon.tsx +0 -18
  34. package/src/components/FilterGroup.tsx +0 -264
  35. package/src/components/FullViewportBox.tsx +0 -19
  36. package/src/components/Grid.tsx +0 -97
  37. package/src/components/Heading.tsx +0 -72
  38. package/src/components/HorizontalDivider.tsx +0 -22
  39. package/src/components/Icon.tsx +0 -39
  40. package/src/components/ImagePlaceholder.tsx +0 -22
  41. package/src/components/Input.tsx +0 -609
  42. package/src/components/InputGroup.tsx +0 -59
  43. package/src/components/Label.tsx +0 -46
  44. package/src/components/Link.tsx +0 -117
  45. package/src/components/List.tsx +0 -18
  46. package/src/components/ListGroup.tsx +0 -82
  47. package/src/components/LiveChatComponent.tsx +0 -56
  48. package/src/components/LoadingScrim.tsx +0 -33
  49. package/src/components/LogoAgilityTopBar.tsx +0 -54
  50. package/src/components/LogoDMSiTopBar.tsx +0 -33
  51. package/src/components/LogoMillworkTopBar.tsx +0 -119
  52. package/src/components/MainBar.tsx +0 -91
  53. package/src/components/MaxViewportBox.tsx +0 -19
  54. package/src/components/Menu.tsx +0 -316
  55. package/src/components/MenuOption.tsx +0 -330
  56. package/src/components/MobileDataGrid/ColumnList.tsx +0 -66
  57. package/src/components/MobileDataGrid/ColumnSelector/index.tsx +0 -97
  58. package/src/components/MobileDataGrid/GridContextProvider/GridContext.tsx +0 -25
  59. package/src/components/MobileDataGrid/GridContextProvider/index.tsx +0 -132
  60. package/src/components/MobileDataGrid/GridContextProvider/useGridContext.ts +0 -10
  61. package/src/components/MobileDataGrid/MobileDataGridCard/MobileDataGridColumn.tsx +0 -27
  62. package/src/components/MobileDataGrid/MobileDataGridCard/index.tsx +0 -138
  63. package/src/components/MobileDataGrid/MobileDataGridHeader.tsx +0 -81
  64. package/src/components/MobileDataGrid/RowDetailModalProvider/ModalContent.tsx +0 -42
  65. package/src/components/MobileDataGrid/RowDetailModalProvider/index.tsx +0 -68
  66. package/src/components/MobileDataGrid/dataGridReducer.ts +0 -55
  67. package/src/components/MobileDataGrid/index.tsx +0 -92
  68. package/src/components/MobileDataGrid/types.ts +0 -4
  69. package/src/components/Modal.tsx +0 -312
  70. package/src/components/ModalButtons.tsx +0 -62
  71. package/src/components/ModalContent.tsx +0 -31
  72. package/src/components/ModalHeader.tsx +0 -78
  73. package/src/components/ModalScrim.tsx +0 -42
  74. package/src/components/NavigationTab.tsx +0 -95
  75. package/src/components/NavigationTabs.tsx +0 -70
  76. package/src/components/NestedMenu.tsx +0 -131
  77. package/src/components/Notification.tsx +0 -128
  78. package/src/components/OptionPill.tsx +0 -139
  79. package/src/components/OrderCheckIcon.tsx +0 -19
  80. package/src/components/PDFViewer/DownloadIcon.tsx +0 -25
  81. package/src/components/PDFViewer/PDFElement.tsx +0 -90
  82. package/src/components/PDFViewer/PDFNavigation.tsx +0 -68
  83. package/src/components/PDFViewer/PDFPage.tsx +0 -34
  84. package/src/components/PDFViewer/index.tsx +0 -128
  85. package/src/components/Pagination.tsx +0 -182
  86. package/src/components/Paragraph.tsx +0 -55
  87. package/src/components/Password.tsx +0 -62
  88. package/src/components/ProductImagePreview/CarouselPagination.tsx +0 -54
  89. package/src/components/ProductImagePreview/MobileImageCarousel.tsx +0 -226
  90. package/src/components/ProductImagePreview/ProductPrimaryImage.tsx +0 -219
  91. package/src/components/ProductImagePreview/Thumbnail.tsx +0 -55
  92. package/src/components/ProductImagePreview/ZoomWindow.tsx +0 -136
  93. package/src/components/ProductImagePreview/index.tsx +0 -182
  94. package/src/components/ProductImagePreview/useProductImagePreview.ts +0 -211
  95. package/src/components/ProjectBar.tsx +0 -82
  96. package/src/components/Radio.tsx +0 -146
  97. package/src/components/Search.tsx +0 -152
  98. package/src/components/SearchResultImage/index.tsx +0 -39
  99. package/src/components/Select.tsx +0 -114
  100. package/src/components/SideMenu.tsx +0 -30
  101. package/src/components/SideMenuGroup.tsx +0 -95
  102. package/src/components/SideMenuItem.tsx +0 -109
  103. package/src/components/SimpleTable.tsx +0 -77
  104. package/src/components/SkeletonParagraph.tsx +0 -31
  105. package/src/components/Spinner.tsx +0 -32
  106. package/src/components/Stack.tsx +0 -347
  107. package/src/components/StatusPill.tsx +0 -59
  108. package/src/components/Stepper.tsx +0 -128
  109. package/src/components/Subheader.tsx +0 -50
  110. package/src/components/Surface.tsx +0 -37
  111. package/src/components/Swatch.tsx +0 -1341
  112. package/src/components/Textarea.tsx +0 -102
  113. package/src/components/Theme.tsx +0 -27
  114. package/src/components/Time.tsx +0 -460
  115. package/src/components/Toast.tsx +0 -268
  116. package/src/components/Tooltip.tsx +0 -159
  117. package/src/components/TopBar.tsx +0 -139
  118. package/src/components/Upload.tsx +0 -107
  119. package/src/components/WorldpayIframe.tsx +0 -7
  120. package/src/components/index.ts +0 -34
  121. package/src/components/useMenuSystem.tsx +0 -456
  122. package/src/components/useMounted.tsx +0 -14
  123. package/src/darkmode.css +0 -278
  124. package/src/fonts.css +0 -23
  125. package/src/hooks/index.ts +0 -4
  126. package/src/hooks/useInfiniteScroll.tsx +0 -40
  127. package/src/hooks/useKeydown.ts +0 -42
  128. package/src/hooks/useMatchesMedia.ts +0 -18
  129. package/src/hooks/useTableLayout.ts +0 -106
  130. package/src/index.css +0 -800
  131. package/src/index.tsx +0 -5
  132. package/src/types.ts +0 -150
  133. package/src/utils/date.ts +0 -236
  134. package/src/utils/formatting.tsx +0 -81
  135. package/src/utils/index.ts +0 -4
  136. package/src/utils/mergeObjectArrays.ts +0 -18
  137. package/src/utils.ts +0 -24
@@ -1,758 +0,0 @@
1
- import { useRef, useEffect, useState, useLayoutEffect } from "react";
2
- import { createPortal } from "react-dom";
3
- import { InputBaseProps, InputBase } from "./Input";
4
- import { CalendarRange, CalendarRangeProps } from "./CalendarRange";
5
- import { Icon } from "./Icon";
6
- import { findDocumentRoot } from "../utils";
7
- import {
8
- parseInputDate,
9
- isValidDate,
10
- formatInputValue,
11
- calculateCursorPosition,
12
- formatDate,
13
- isValidDateRangeOrder,
14
- } from "../utils/date";
15
-
16
- type DateRangeInputProps = Omit<InputBaseProps, "id"> & {
17
- id?: string;
18
- testid?: string;
19
- /**
20
- * Value in the format "YYYY-MM-DD|YYYY-MM-DD" or empty string
21
- */
22
- value: string;
23
- /**
24
- * Called when the range changes. Value is in the format "YYYY-MM-DD|YYYY-MM-DD"
25
- */
26
- onChange: (value: string) => void;
27
- /**
28
- * Optional placeholder text
29
- */
30
- placeholder?: string;
31
- /**
32
- * Optional disabled state
33
- */
34
- disabled?: boolean;
35
- /**
36
- * If true, shows single calendar instead of double calendar
37
- */
38
- single?: boolean;
39
- /**
40
- * If true, disables range selection (single date only)
41
- */
42
- disableRange?: boolean;
43
- readOnly?: boolean; // If true, input is read-only and cannot be focused
44
- label?: string; // Optional label for the input
45
- } & Pick<CalendarRangeProps, "isDateAvailable" | "onPendingFromChange">;
46
-
47
- export const DateRangeInput = ({
48
- id,
49
- testid,
50
- value,
51
- onChange,
52
- placeholder = "MM/DD/YYYY - MM/DD/YYYY",
53
- disabled,
54
- readOnly = false,
55
- single = false,
56
- disableRange = false,
57
- label,
58
- isDateAvailable,
59
- onPendingFromChange,
60
- ...props
61
- }: DateRangeInputProps) => {
62
- const [visible, setVisible] = useState(false);
63
- const [inputValue, setInputValue] = useState("");
64
- const [isTyping, setIsTyping] = useState(false);
65
- const popoverRef = useRef<HTMLDivElement | null>(null);
66
- const rootRef = useRef<HTMLDivElement | null>(null);
67
- const triggerRef = useRef<HTMLInputElement | null>(null);
68
- const [calendarPosition, setCalendarPosition] = useState({
69
- top: 0,
70
- left: 0,
71
- width: 0,
72
- });
73
-
74
- const [from, to] = value.split("|");
75
-
76
- // Update input value when external value changes (but not when typing)
77
- useEffect(() => {
78
- if (!isTyping) {
79
- // Only update inputValue if the value is valid
80
- const displayValue = formatDisplayValue(from, to);
81
- if (displayValue) {
82
- setInputValue(displayValue);
83
- }
84
- // If displayValue is empty, keep previous inputValue unchanged
85
- }
86
- }, [from, to, isTyping, disableRange]);
87
-
88
- useLayoutEffect(() => {
89
- if (visible) {
90
- updatePosition();
91
- }
92
- }, [visible]);
93
-
94
- const updatePosition = () => {
95
- if (rootRef.current) {
96
- const rect = rootRef.current.getBoundingClientRect();
97
- setCalendarPosition({
98
- top: rect.bottom + window.scrollY,
99
- left: rect.left + window.scrollX,
100
- width: rect.width,
101
- });
102
- }
103
- };
104
-
105
- useEffect(() => {
106
- updatePosition();
107
-
108
- const resizeObserver = new ResizeObserver(updatePosition);
109
- if (triggerRef.current) {
110
- resizeObserver.observe(triggerRef.current);
111
- }
112
-
113
- window.addEventListener("scroll", updatePosition);
114
-
115
- return () => {
116
- resizeObserver.disconnect();
117
- window.removeEventListener("scroll", updatePosition);
118
- };
119
- }, []);
120
-
121
- useEffect(() => {
122
- const handleKeyDown = (event: KeyboardEvent) => {
123
- if (event.key === "Escape" && popoverRef.current) {
124
- setVisible(false);
125
- triggerRef.current?.blur();
126
- }
127
- };
128
- document.addEventListener("keydown", handleKeyDown);
129
- return () => {
130
- document.removeEventListener("keydown", handleKeyDown);
131
- };
132
- }, []);
133
-
134
- useEffect(() => {
135
- const handleClickOutside = (event: MouseEvent) => {
136
- if (
137
- popoverRef.current &&
138
- !popoverRef.current.contains(event.target as HTMLElement) &&
139
- triggerRef.current &&
140
- !triggerRef.current.contains(event.target as HTMLElement)
141
- ) {
142
- setVisible(false);
143
- }
144
- };
145
- document.addEventListener("mousedown", handleClickOutside);
146
- return () => {
147
- document.removeEventListener("mousedown", handleClickOutside);
148
- };
149
- }, []);
150
-
151
- function handleRangeChange(fromValue: string, toValue: string) {
152
- if (disableRange) {
153
- onChange(`${fromValue}|${fromValue}`);
154
- } else {
155
- onChange(`${fromValue}|${toValue}`);
156
- }
157
- setVisible(false);
158
- setIsTyping(false);
159
- }
160
-
161
- const handleFocus = () => {
162
- if (readOnly) return;
163
- setVisible(true);
164
- };
165
-
166
- const handleClick = () => {
167
- handleFocus();
168
- };
169
-
170
- const handleInputChange = (event: React.ChangeEvent<HTMLInputElement>) => {
171
- if (readOnly) return;
172
-
173
- const rawValue = event.target.value;
174
- const cursorPosition = event.target.selectionStart || 0;
175
-
176
- if (shouldPreventManualDash(rawValue)) {
177
- handleManualDashRemoval(rawValue, cursorPosition);
178
- return;
179
- }
180
-
181
- setIsTyping(true);
182
-
183
- const formattedValue = formatInputValue(rawValue);
184
- const finalValue = shouldAutoInsertDash(formattedValue, rawValue)
185
- ? `${formattedValue} - `
186
- : formattedValue;
187
-
188
- setInputValue(finalValue);
189
-
190
- const newCursorPosition = calculateNewCursorPosition(
191
- rawValue,
192
- formattedValue,
193
- finalValue,
194
- cursorPosition,
195
- );
196
-
197
- requestAnimationFrame(() => {
198
- setCursorPosition(newCursorPosition);
199
- });
200
-
201
- updateParentValue(finalValue);
202
- };
203
-
204
- const shouldPreventManualDash = (value: string): boolean => {
205
- return !disableRange && value.includes("-") && !value.includes(" - ");
206
- };
207
-
208
- const handleManualDashRemoval = (
209
- rawValue: string,
210
- cursorPosition: number,
211
- ) => {
212
- const cleanValue = rawValue.replace(/-/g, "");
213
- const formattedCleanValue = formatInputValue(cleanValue);
214
- setInputValue(formattedCleanValue);
215
-
216
- requestAnimationFrame(() => {
217
- const newPosition = Math.min(
218
- cursorPosition - 1,
219
- formattedCleanValue.length,
220
- );
221
- setCursorPosition(newPosition);
222
- });
223
- };
224
-
225
- const shouldAutoInsertDash = (
226
- formattedValue: string,
227
- rawValue: string,
228
- ): boolean => {
229
- if (disableRange || formattedValue.includes(" - ")) {
230
- return false;
231
- }
232
-
233
- const completeDate = formattedValue.match(/^(\d{2})\/(\d{2})\/(\d{4})$/);
234
- if (!completeDate || rawValue.length !== formattedValue.length) {
235
- return false;
236
- }
237
-
238
- // Only add dash if user just completed typing the year
239
- const prevLength = rawValue.length - 1;
240
- const prevFormatted = formatInputValue(rawValue.slice(0, prevLength));
241
-
242
- return !prevFormatted.match(/^(\d{2})\/(\d{2})\/(\d{4})$/);
243
- };
244
-
245
- const calculateNewCursorPosition = (
246
- rawValue: string,
247
- formattedValue: string,
248
- finalValue: string,
249
- originalPosition: number,
250
- ): number => {
251
- if (finalValue !== formattedValue) {
252
- return finalValue.length; // Place cursor after auto-inserted dash
253
- }
254
-
255
- return calculateCursorPositionHelper(
256
- rawValue,
257
- finalValue,
258
- originalPosition,
259
- disableRange,
260
- );
261
- };
262
-
263
- const setCursorPosition = (position: number) => {
264
- if (triggerRef.current) {
265
- triggerRef.current.setSelectionRange(position, position);
266
- }
267
- };
268
-
269
- const updateParentValue = (value: string) => {
270
- if (disableRange) {
271
- updateSingleDateValue(value);
272
- } else {
273
- updateRangeValue(value);
274
- }
275
- };
276
-
277
- const updateSingleDateValue = (value: string) => {
278
- const parsedDate = parseInputDate(value);
279
- if (parsedDate && isValidDate(parsedDate)) {
280
- onChange(`${parsedDate}|${parsedDate}`);
281
- }
282
- };
283
-
284
- const updateRangeValue = (value: string) => {
285
- if (value === "") {
286
- onChange("");
287
- return;
288
- }
289
-
290
- const rangeMatch = value.match(/^(.+?)\s*-\s*(.+)$/);
291
- if (rangeMatch) {
292
- updateCompleteRange(rangeMatch);
293
- } else {
294
- updatePartialRange(value);
295
- }
296
- };
297
-
298
- const updateCompleteRange = (rangeMatch: RegExpMatchArray) => {
299
- const [, fromStr, toStr] = rangeMatch;
300
- const fromDate = parseInputDate(fromStr.trim());
301
- const toDate = parseInputDate(toStr.trim());
302
-
303
- if (fromDate && toDate && isValidDateRange(fromDate, toDate)) {
304
- onChange(`${fromDate}|${toDate}`);
305
- }
306
- };
307
-
308
- const updatePartialRange = (value: string) => {
309
- const singleDate = parseInputDate(value);
310
- if (singleDate && isValidDate(singleDate)) {
311
- onChange(`${singleDate}|`);
312
- }
313
- };
314
-
315
- const handleBlur = () => {
316
- setIsTyping(false);
317
-
318
- // If the input is invalid, revert to the last valid value or empty
319
- if (disableRange) {
320
- const parsedDate = parseInputDate(inputValue);
321
- if (!parsedDate || !isValidDate(parsedDate)) {
322
- const lastValidValue = formatDisplayValue(from, to);
323
- setInputValue(lastValidValue || "");
324
- }
325
- } else {
326
- // For range, validate both parts
327
- const rangeMatch = inputValue.match(/^(.+?)\s*-\s*(.+)$/);
328
- if (rangeMatch) {
329
- const [, fromStr, toStr] = rangeMatch;
330
- const fromDate = parseInputDate(fromStr.trim());
331
- const toDate = parseInputDate(toStr.trim());
332
-
333
- // Only accept if both dates are valid AND the range is valid (to >= from)
334
- if (!fromDate || !toDate || !isValidDateRange(fromDate, toDate)) {
335
- // Invalid range - clear the input
336
- setInputValue("");
337
- onChange("");
338
- }
339
- } else {
340
- // Check if input is just a dash or incomplete second date
341
- if (inputValue.includes(" - ")) {
342
- // Has dash but incomplete second date - clear the input
343
- setInputValue("");
344
- onChange("");
345
- } else {
346
- // Single date in range mode - check if we have a previous valid state
347
- const singleDate = parseInputDate(inputValue);
348
- if (!singleDate || !isValidDate(singleDate)) {
349
- // Invalid single date - clear the input
350
- setInputValue("");
351
- onChange("");
352
- } else {
353
- // Valid single date but incomplete range - clear the input
354
- setInputValue("");
355
- onChange("");
356
- }
357
- }
358
- }
359
- }
360
- };
361
-
362
- const handleKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {
363
- // Prevent manual typing of dash in range mode
364
- if (!disableRange && event.key === "-") {
365
- event.preventDefault();
366
- return;
367
- }
368
-
369
- if (event.key === "Backspace") {
370
- const input = event.target as HTMLInputElement;
371
- const cursorPosition = input.selectionStart || 0;
372
- const value = input.value;
373
-
374
- // If cursor is right after a slash, move it before the slash
375
- if (cursorPosition > 0 && value[cursorPosition - 1] === "/") {
376
- event.preventDefault();
377
- const newValue =
378
- value.slice(0, cursorPosition - 2) + value.slice(cursorPosition);
379
- const formattedValue = formatInputValue(newValue);
380
- setInputValue(formattedValue);
381
-
382
- // Set cursor position after the deletion
383
- requestAnimationFrame(() => {
384
- if (triggerRef.current) {
385
- const newPosition = Math.max(0, cursorPosition - 2);
386
- triggerRef.current.setSelectionRange(newPosition, newPosition);
387
- }
388
- });
389
-
390
- setIsTyping(true);
391
- return;
392
- }
393
-
394
- // Handle deletion when cursor is on or near the dash separator
395
- if (!disableRange && value.includes(" - ")) {
396
- const dashIndex = value.indexOf(" - ");
397
- const dashStart = dashIndex;
398
- const dashEnd = dashIndex + 3; // " - " is 3 characters
399
-
400
- // If cursor is within the dash area (including spaces)
401
- if (cursorPosition >= dashStart && cursorPosition <= dashEnd) {
402
- event.preventDefault();
403
-
404
- // Remove the entire range and the last digit of the year from the first date
405
- const beforeDash = value.slice(0, dashStart).trim();
406
-
407
- // Extract the year part and remove the last digit
408
- const yearMatch = beforeDash.match(/^(\d{2})\/(\d{2})\/(\d{4})$/);
409
- if (yearMatch) {
410
- const [, month, day, year] = yearMatch;
411
- const truncatedYear = year.slice(0, -1); // Remove last digit
412
- const newValue = `${month}/${day}/${truncatedYear}`;
413
- const formattedValue = formatInputValue(newValue);
414
- setInputValue(formattedValue);
415
-
416
- // Update the value - no valid date yet since year is incomplete
417
- onChange("");
418
-
419
- // Position cursor at the end
420
- requestAnimationFrame(() => {
421
- if (triggerRef.current) {
422
- triggerRef.current.setSelectionRange(
423
- formattedValue.length,
424
- formattedValue.length,
425
- );
426
- }
427
- });
428
- } else {
429
- // Fallback to original behavior
430
- const formattedValue = formatInputValue(beforeDash);
431
- setInputValue(formattedValue);
432
-
433
- const singleDate = parseInputDate(beforeDash);
434
- if (singleDate && isValidDate(singleDate)) {
435
- onChange(`${singleDate}|`);
436
- } else {
437
- onChange("");
438
- }
439
-
440
- requestAnimationFrame(() => {
441
- if (triggerRef.current) {
442
- const newPosition = formattedValue.length;
443
- triggerRef.current.setSelectionRange(newPosition, newPosition);
444
- }
445
- });
446
- }
447
-
448
- setIsTyping(true);
449
- return;
450
- }
451
-
452
- // If cursor is right after the dash, remove the second date
453
- if (cursorPosition === dashEnd) {
454
- event.preventDefault();
455
-
456
- const beforeDash = value.slice(0, dashStart).trim();
457
- const newValue = `${beforeDash} - `;
458
- setInputValue(newValue);
459
-
460
- // Update the value to only have the first date as partial range
461
- const singleDate = parseInputDate(beforeDash);
462
- if (singleDate && isValidDate(singleDate)) {
463
- onChange(`${singleDate}|`);
464
- }
465
-
466
- // Position cursor after the dash
467
- requestAnimationFrame(() => {
468
- if (triggerRef.current) {
469
- triggerRef.current.setSelectionRange(
470
- newValue.length,
471
- newValue.length,
472
- );
473
- }
474
- });
475
-
476
- setIsTyping(true);
477
- return;
478
- }
479
- }
480
- }
481
-
482
- if (event.key === "Delete") {
483
- const input = event.target as HTMLInputElement;
484
- const cursorPosition = input.selectionStart || 0;
485
- const value = input.value;
486
-
487
- // Handle deletion when cursor is on or near the dash separator
488
- if (!disableRange && value.includes(" - ")) {
489
- const dashIndex = value.indexOf(" - ");
490
- const dashStart = dashIndex;
491
- const dashEnd = dashIndex + 3; // " - " is 3 characters
492
-
493
- // If cursor is within the dash area or right before it
494
- if (cursorPosition >= dashStart && cursorPosition <= dashEnd) {
495
- event.preventDefault();
496
-
497
- // Remove the dash and second date, and the last digit of the year from the first date
498
- const beforeDash = value.slice(0, dashStart).trim();
499
-
500
- // Extract the year part and remove the last digit
501
- const yearMatch = beforeDash.match(/^(\d{2})\/(\d{2})\/(\d{4})$/);
502
- if (yearMatch) {
503
- const [, month, day, year] = yearMatch;
504
- const truncatedYear = year.slice(0, -1); // Remove last digit
505
- const newValue = `${month}/${day}/${truncatedYear}`;
506
- const formattedValue = formatInputValue(newValue);
507
- setInputValue(formattedValue);
508
-
509
- // Update the value - no valid date yet since year is incomplete
510
- onChange("");
511
-
512
- // Position cursor at the end
513
- requestAnimationFrame(() => {
514
- if (triggerRef.current) {
515
- triggerRef.current.setSelectionRange(
516
- formattedValue.length,
517
- formattedValue.length,
518
- );
519
- }
520
- });
521
- } else {
522
- // Fallback to original behavior
523
- const formattedValue = formatInputValue(beforeDash);
524
- setInputValue(formattedValue);
525
-
526
- const singleDate = parseInputDate(beforeDash);
527
- if (singleDate && isValidDate(singleDate)) {
528
- onChange(`${singleDate}|`);
529
- } else {
530
- onChange("");
531
- }
532
-
533
- requestAnimationFrame(() => {
534
- if (triggerRef.current) {
535
- const newPosition = formattedValue.length;
536
- triggerRef.current.setSelectionRange(newPosition, newPosition);
537
- }
538
- });
539
- }
540
-
541
- setIsTyping(true);
542
- return;
543
- }
544
- }
545
- }
546
-
547
- if (event.key === "Enter") {
548
- if (disableRange) {
549
- const parsedDate = parseInputDate(inputValue);
550
- if (parsedDate && isValidDate(parsedDate)) {
551
- onChange(`${parsedDate}|${parsedDate}`);
552
- setVisible(false);
553
- setIsTyping(false);
554
- }
555
- } else {
556
- // In range mode, only accept complete and valid ranges
557
- const rangeMatch = inputValue.match(/^(.+?)\s*-\s*(.+)$/);
558
- if (rangeMatch) {
559
- const [, fromStr, toStr] = rangeMatch;
560
- const fromDate = parseInputDate(fromStr.trim());
561
- const toDate = parseInputDate(toStr.trim());
562
-
563
- if (fromDate && toDate && isValidDateRange(fromDate, toDate)) {
564
- onChange(`${fromDate}|${toDate}`);
565
- setVisible(false);
566
- setIsTyping(false);
567
- } else {
568
- // Invalid range - clear the input
569
- setInputValue("");
570
- onChange("");
571
- setVisible(false);
572
- setIsTyping(false);
573
- }
574
- } else {
575
- // Single date in range mode - clear the input
576
- setInputValue("");
577
- onChange("");
578
- setVisible(false);
579
- setIsTyping(false);
580
- }
581
- }
582
- }
583
- };
584
-
585
- return (
586
- <>
587
- <InputBase
588
- id={id}
589
- testid={testid}
590
- ref={(el) => {
591
- triggerRef.current = el;
592
- }}
593
- {...props}
594
- wrapperRef={rootRef}
595
- value={inputValue}
596
- placeholder={disableRange ? "MM/DD/YYYY" : placeholder}
597
- disabled={disabled}
598
- readOnly={readOnly}
599
- after={<Icon name="calendar_month" />}
600
- onFocus={handleFocus}
601
- onClick={handleClick}
602
- onChange={handleInputChange}
603
- onBlur={handleBlur}
604
- onKeyDown={handleKeyDown}
605
- label={label}
606
- secondaryIconColor
607
- />
608
- {visible &&
609
- !readOnly &&
610
- createPortal(
611
- <div
612
- ref={(el) => {
613
- popoverRef.current = el;
614
- }}
615
- className="absolute z-40"
616
- style={{
617
- top: `${calendarPosition.top + 4}px`,
618
- left: `${calendarPosition.left}px`,
619
- minWidth: `${calendarPosition.width}px`,
620
- }}
621
- >
622
- <CalendarRange
623
- id={id ? `${id}-calendar` : undefined}
624
- testid={testid ? `${testid}-calendar` : undefined}
625
- from={from}
626
- to={to}
627
- onChange={handleRangeChange}
628
- cardStyle
629
- mode={single ? "single" : "double"}
630
- disableRange={disableRange}
631
- isDateAvailable={isDateAvailable}
632
- onPendingFromChange={onPendingFromChange}
633
- />
634
- </div>,
635
- findDocumentRoot(popoverRef.current),
636
- )}
637
- </>
638
- );
639
-
640
- function formatInputValue(value: string): string {
641
- return formatInputValueHelper(value, disableRange);
642
- }
643
-
644
- function formatDisplayValue(from?: string, to?: string) {
645
- return formatDisplayValueHelper(from, to, disableRange);
646
- }
647
- };
648
-
649
- DateRangeInput.displayName = "DateRangeInput";
650
-
651
- function isValidDateRange(fromDate: string, toDate: string): boolean {
652
- return isValidDateRangeOrder(fromDate, toDate);
653
- }
654
-
655
- function formatInputValueHelper(value: string, disableRange: boolean): string {
656
- if (disableRange) {
657
- return formatInputValue(value);
658
- }
659
-
660
- if (value.includes(" - ")) {
661
- const [from, to] = value.split(" - ");
662
- const fromFormatted = formatInputValue(from);
663
- const toFormatted = formatInputValue(to || "");
664
- return `${fromFormatted} - ${toFormatted}`;
665
- }
666
-
667
- const cleanValue = value.replace(/-/g, "");
668
- return formatInputValue(cleanValue);
669
- }
670
-
671
- function calculateCursorPositionHelper(
672
- originalValue: string,
673
- formattedValue: string,
674
- originalPosition: number,
675
- disableRange: boolean,
676
- ): number {
677
- // Handle range input cursor positioning
678
- if (!disableRange && formattedValue.includes(" - ")) {
679
- const dashPosition = formattedValue.indexOf(" - ");
680
- const originalDashPosition = originalValue.indexOf("-");
681
-
682
- // If cursor was after the dash in original, maintain relative position
683
- if (
684
- originalDashPosition !== -1 &&
685
- originalPosition > originalDashPosition
686
- ) {
687
- const afterDashDigits = originalValue
688
- .slice(originalDashPosition + 1)
689
- .replace(/\D/g, "").length;
690
- const formattedAfterDash = formattedValue.slice(dashPosition + 3);
691
-
692
- let newPosition = dashPosition + 3;
693
- let digitCount = 0;
694
-
695
- for (let i = 0; i < formattedAfterDash.length; i++) {
696
- if (/\d/.test(formattedAfterDash[i])) {
697
- digitCount++;
698
- if (digitCount >= afterDashDigits) {
699
- // Check if we're at a position where a slash was auto-inserted in the second date
700
- // If the next character is a slash, place cursor after it
701
- if (
702
- i + 1 < formattedAfterDash.length &&
703
- formattedAfterDash[i + 1] === "/"
704
- ) {
705
- newPosition = dashPosition + 3 + i + 2;
706
- } else {
707
- newPosition = dashPosition + 3 + i + 1;
708
- }
709
- break;
710
- }
711
- }
712
- if (digitCount < afterDashDigits) {
713
- newPosition = dashPosition + 3 + i + 1;
714
- }
715
- }
716
-
717
- return Math.min(newPosition, formattedValue.length);
718
- }
719
- }
720
-
721
- return calculateCursorPosition(
722
- originalValue,
723
- formattedValue,
724
- originalPosition,
725
- );
726
- }
727
-
728
- function formatDisplayValueHelper(
729
- from?: string,
730
- to?: string,
731
- disableRange?: boolean,
732
- ) {
733
- if (!from && !to) {
734
- return "";
735
- }
736
-
737
- // Validate dates before displaying
738
- const fromValid = from ? isValidDate(from) : false;
739
- const toValid = to ? isValidDate(to) : false;
740
-
741
- if (disableRange) {
742
- return fromValid && from ? formatDate(from) : "";
743
- }
744
-
745
- // Return formatted display if we have a complete, valid range
746
- if (fromValid && toValid && from && to && isValidDateRange(from, to)) {
747
- return `${formatDate(from)} - ${formatDate(to)}`;
748
- }
749
-
750
- // If we have a valid from date but no to date, show the from date only
751
- // This preserves the previous valid state during editing
752
- if (fromValid && !to && from) {
753
- return `${formatDate(from)} - `;
754
- }
755
-
756
- // Return empty string if no valid dates or invalid range
757
- return "";
758
- }