@dmsi/wedgekit-react 0.0.29 → 0.0.31

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 (43) hide show
  1. package/dist/{chunk-57WRM337.js → chunk-5POWRPIO.js} +3 -2
  2. package/dist/{chunk-S5KPS4IQ.js → chunk-HXEPUO5W.js} +189 -95
  3. package/dist/chunk-KHQX42T7.js +127 -0
  4. package/dist/{chunk-OUSNH76S.js → chunk-PCCJ7L3N.js} +29 -6
  5. package/dist/{chunk-PDDZ5PMY.js → chunk-S46RZBT4.js} +3 -2
  6. package/dist/components/CalendarRange.cjs +28 -5
  7. package/dist/components/CalendarRange.js +1 -1
  8. package/dist/components/DataGrid.cjs +490 -217
  9. package/dist/components/DataGrid.js +303 -122
  10. package/dist/components/DataGridCell.cjs +192 -96
  11. package/dist/components/DataGridCell.js +4 -2
  12. package/dist/components/DateInput.cjs +231 -30
  13. package/dist/components/DateInput.js +101 -27
  14. package/dist/components/DateRangeInput.cjs +550 -37
  15. package/dist/components/DateRangeInput.js +413 -34
  16. package/dist/components/MenuOption.cjs +3 -2
  17. package/dist/components/MenuOption.js +1 -1
  18. package/dist/components/MobileDataGrid.cjs +3 -2
  19. package/dist/components/MobileDataGrid.js +1 -1
  20. package/dist/components/NestedMenu.cjs +3 -2
  21. package/dist/components/NestedMenu.js +1 -1
  22. package/dist/components/Notification.cjs +3 -2
  23. package/dist/components/Notification.js +1 -1
  24. package/dist/components/SideMenuGroup.cjs +3 -2
  25. package/dist/components/SideMenuGroup.js +1 -1
  26. package/dist/components/SideMenuItem.cjs +3 -2
  27. package/dist/components/SideMenuItem.js +1 -1
  28. package/dist/components/Stack.cjs +3 -2
  29. package/dist/components/Stack.js +1 -1
  30. package/dist/components/Swatch.cjs +3 -2
  31. package/dist/components/Swatch.js +1 -1
  32. package/dist/components/Time.cjs +3 -2
  33. package/dist/components/Time.js +1 -1
  34. package/dist/index.css +9 -0
  35. package/package.json +1 -1
  36. package/src/components/CalendarRange.tsx +37 -6
  37. package/src/components/DataGrid.tsx +284 -48
  38. package/src/components/DataGridCell.tsx +122 -35
  39. package/src/components/DateInput.tsx +118 -25
  40. package/src/components/DateRangeInput.tsx +544 -30
  41. package/src/components/MenuOption.tsx +18 -14
  42. package/src/components/Stack.tsx +4 -2
  43. package/src/utils/date.ts +206 -0
@@ -1,9 +1,17 @@
1
- import { useRef, useEffect, useState } from "react";
1
+ import { useRef, useEffect, useState, useLayoutEffect } from "react";
2
2
  import { createPortal } from "react-dom";
3
3
  import { InputBaseProps, InputBase } from "./Input";
4
4
  import { CalendarRange } from "./CalendarRange";
5
5
  import { Icon } from "./Icon";
6
6
  import { findDocumentRoot } from "../utils";
7
+ import {
8
+ parseInputDate,
9
+ isValidDate,
10
+ formatInputValue,
11
+ calculateCursorPosition,
12
+ formatDate,
13
+ isValidDateRangeOrder,
14
+ } from "../utils/date";
7
15
 
8
16
  type DateRangeInputProps = Omit<InputBaseProps, "id"> & {
9
17
  id?: string;
@@ -23,7 +31,14 @@ type DateRangeInputProps = Omit<InputBaseProps, "id"> & {
23
31
  * Optional disabled state
24
32
  */
25
33
  disabled?: boolean;
26
- single?: boolean; // If true, allows single date selection instead of range
34
+ /**
35
+ * If true, shows single calendar instead of double calendar
36
+ */
37
+ single?: boolean;
38
+ /**
39
+ * If true, disables range selection (single date only)
40
+ */
41
+ disableRange?: boolean;
27
42
  readOnly?: boolean; // If true, input is read-only and cannot be focused
28
43
  label?: string; // Optional label for the input
29
44
  };
@@ -35,11 +50,14 @@ export const DateRangeInput = ({
35
50
  placeholder = "MM/DD/YYYY - MM/DD/YYYY",
36
51
  disabled,
37
52
  readOnly = false,
38
- single = false, // Enables single date selection instead of range
53
+ single = false,
54
+ disableRange = false,
39
55
  label,
40
56
  ...props
41
57
  }: DateRangeInputProps) => {
42
58
  const [visible, setVisible] = useState(false);
59
+ const [inputValue, setInputValue] = useState("");
60
+ const [isTyping, setIsTyping] = useState(false);
43
61
  const popoverRef = useRef<HTMLDivElement | null>(null);
44
62
  const triggerRef = useRef<HTMLInputElement | null>(null);
45
63
  const [calendarPosition, setCalendarPosition] = useState({
@@ -50,15 +68,26 @@ export const DateRangeInput = ({
50
68
 
51
69
  const [from, to] = value.split("|");
52
70
 
71
+ // Update input value when external value changes (but not when typing)
72
+ useEffect(() => {
73
+ if (!isTyping) {
74
+ setInputValue(formatDisplayValue(from, to));
75
+ }
76
+ }, [from, to, isTyping, disableRange]);
77
+
78
+ useLayoutEffect(() => {
79
+ if (visible) {
80
+ updatePosition();
81
+ }
82
+ }, [visible]);
83
+
53
84
  const updatePosition = () => {
54
85
  if (triggerRef.current) {
55
- requestAnimationFrame(() => {
56
- const rect = triggerRef.current!.getBoundingClientRect();
57
- setCalendarPosition({
58
- top: rect.bottom + window.scrollY,
59
- left: rect.left + window.scrollX,
60
- width: rect.width,
61
- });
86
+ const rect = triggerRef.current.getBoundingClientRect();
87
+ setCalendarPosition({
88
+ top: rect.bottom + window.scrollY,
89
+ left: rect.left + window.scrollX,
90
+ width: rect.width,
62
91
  });
63
92
  }
64
93
  };
@@ -90,7 +119,7 @@ export const DateRangeInput = ({
90
119
  return () => {
91
120
  document.removeEventListener("keydown", handleKeyDown);
92
121
  };
93
- });
122
+ }, []);
94
123
 
95
124
  useEffect(() => {
96
125
  const handleClickOutside = (event: MouseEvent) => {
@@ -110,27 +139,414 @@ export const DateRangeInput = ({
110
139
  }, []);
111
140
 
112
141
  function handleRangeChange(fromValue: string, toValue: string) {
113
- onChange(`${fromValue}|${toValue}`);
142
+ if (disableRange) {
143
+ onChange(`${fromValue}|${fromValue}`);
144
+ } else {
145
+ onChange(`${fromValue}|${toValue}`);
146
+ }
114
147
  setVisible(false);
148
+ setIsTyping(false);
115
149
  }
116
150
 
117
151
  const handleFocus = () => {
118
- if (readOnly) return; // Do not open calendar if read-only
152
+ if (readOnly) return;
119
153
  setVisible(true);
120
- updatePosition();
121
154
  };
122
155
 
123
- function formatDisplayValue(from?: string, to?: string) {
124
- if (!from && !to) return "";
125
- if (from && to) return `${formatDate(from)} - ${formatDate(to)}`;
126
- if (from) return `${formatDate(from)} -`;
127
- return "";
128
- }
156
+ const handleClick = () => {
157
+ handleFocus();
158
+ };
129
159
 
130
- function formatDate(date: string) {
131
- const [y, m, d] = date.split("-");
132
- return `${m}/${d}/${y}`;
133
- }
160
+ const handleInputChange = (event: React.ChangeEvent<HTMLInputElement>) => {
161
+ if (readOnly) return;
162
+
163
+ const rawValue = event.target.value;
164
+ const cursorPosition = event.target.selectionStart || 0;
165
+
166
+ if (shouldPreventManualDash(rawValue)) {
167
+ handleManualDashRemoval(rawValue, cursorPosition);
168
+ return;
169
+ }
170
+
171
+ setIsTyping(true);
172
+
173
+ const formattedValue = formatInputValue(rawValue);
174
+ const finalValue = shouldAutoInsertDash(formattedValue, rawValue)
175
+ ? `${formattedValue} - `
176
+ : formattedValue;
177
+
178
+ setInputValue(finalValue);
179
+
180
+ const newCursorPosition = calculateNewCursorPosition(
181
+ rawValue,
182
+ formattedValue,
183
+ finalValue,
184
+ cursorPosition
185
+ );
186
+
187
+ requestAnimationFrame(() => {
188
+ setCursorPosition(newCursorPosition);
189
+ });
190
+
191
+ updateParentValue(finalValue);
192
+ };
193
+
194
+ const shouldPreventManualDash = (value: string): boolean => {
195
+ return !disableRange && value.includes('-') && !value.includes(' - ');
196
+ };
197
+
198
+ const handleManualDashRemoval = (rawValue: string, cursorPosition: number) => {
199
+ const cleanValue = rawValue.replace(/-/g, '');
200
+ const formattedCleanValue = formatInputValue(cleanValue);
201
+ setInputValue(formattedCleanValue);
202
+
203
+ requestAnimationFrame(() => {
204
+ const newPosition = Math.min(cursorPosition - 1, formattedCleanValue.length);
205
+ setCursorPosition(newPosition);
206
+ });
207
+ };
208
+
209
+ const shouldAutoInsertDash = (formattedValue: string, rawValue: string): boolean => {
210
+ if (disableRange || formattedValue.includes(' - ')) {
211
+ return false;
212
+ }
213
+
214
+ const completeDate = formattedValue.match(/^(\d{2})\/(\d{2})\/(\d{4})$/);
215
+ if (!completeDate || rawValue.length !== formattedValue.length) {
216
+ return false;
217
+ }
218
+
219
+ // Only add dash if user just completed typing the year
220
+ const prevLength = rawValue.length - 1;
221
+ const prevFormatted = formatInputValue(rawValue.slice(0, prevLength));
222
+
223
+ return !prevFormatted.match(/^(\d{2})\/(\d{2})\/(\d{4})$/);
224
+ };
225
+
226
+ const calculateNewCursorPosition = (
227
+ rawValue: string,
228
+ formattedValue: string,
229
+ finalValue: string,
230
+ originalPosition: number
231
+ ): number => {
232
+ if (finalValue !== formattedValue) {
233
+ return finalValue.length; // Place cursor after auto-inserted dash
234
+ }
235
+
236
+ return calculateCursorPositionHelper(rawValue, finalValue, originalPosition, disableRange);
237
+ };
238
+
239
+ const setCursorPosition = (position: number) => {
240
+ if (triggerRef.current) {
241
+ triggerRef.current.setSelectionRange(position, position);
242
+ }
243
+ };
244
+
245
+ const updateParentValue = (value: string) => {
246
+ if (disableRange) {
247
+ updateSingleDateValue(value);
248
+ } else {
249
+ updateRangeValue(value);
250
+ }
251
+ };
252
+
253
+ const updateSingleDateValue = (value: string) => {
254
+ const parsedDate = parseInputDate(value);
255
+ if (parsedDate && isValidDate(parsedDate)) {
256
+ onChange(`${parsedDate}|${parsedDate}`);
257
+ }
258
+ };
259
+
260
+ const updateRangeValue = (value: string) => {
261
+ if (value === "") {
262
+ onChange("");
263
+ return;
264
+ }
265
+
266
+ const rangeMatch = value.match(/^(.+?)\s*-\s*(.+)$/);
267
+ if (rangeMatch) {
268
+ updateCompleteRange(rangeMatch);
269
+ } else {
270
+ updatePartialRange(value);
271
+ }
272
+ };
273
+
274
+ const updateCompleteRange = (rangeMatch: RegExpMatchArray) => {
275
+ const [, fromStr, toStr] = rangeMatch;
276
+ const fromDate = parseInputDate(fromStr.trim());
277
+ const toDate = parseInputDate(toStr.trim());
278
+
279
+ if (fromDate && toDate && isValidDateRange(fromDate, toDate)) {
280
+ onChange(`${fromDate}|${toDate}`);
281
+ }
282
+ };
283
+
284
+ const updatePartialRange = (value: string) => {
285
+ const singleDate = parseInputDate(value);
286
+ if (singleDate && isValidDate(singleDate)) {
287
+ onChange(`${singleDate}|`);
288
+ }
289
+ };
290
+
291
+ const handleBlur = () => {
292
+ setIsTyping(false);
293
+
294
+ // If the input is invalid, revert to the last valid value or empty
295
+ if (disableRange) {
296
+ const parsedDate = parseInputDate(inputValue);
297
+ if (!parsedDate || !isValidDate(parsedDate)) {
298
+ const lastValidValue = formatDisplayValue(from, to);
299
+ setInputValue(lastValidValue || "");
300
+ }
301
+ } else {
302
+ // For range, validate both parts
303
+ const rangeMatch = inputValue.match(/^(.+?)\s*-\s*(.+)$/);
304
+ if (rangeMatch) {
305
+ const [, fromStr, toStr] = rangeMatch;
306
+ const fromDate = parseInputDate(fromStr.trim());
307
+ const toDate = parseInputDate(toStr.trim());
308
+
309
+ // Only accept if both dates are valid AND the range is valid (to >= from)
310
+ if (!fromDate || !toDate || !isValidDateRange(fromDate, toDate)) {
311
+ // Invalid range - clear the input
312
+ setInputValue("");
313
+ onChange("");
314
+ }
315
+ } else {
316
+ // Check if input is just a dash or incomplete second date
317
+ if (inputValue.includes(' - ')) {
318
+ // Has dash but incomplete second date - clear the input
319
+ setInputValue("");
320
+ onChange("");
321
+ } else {
322
+ // Single date in range mode - check if we have a previous valid state
323
+ const singleDate = parseInputDate(inputValue);
324
+ if (!singleDate || !isValidDate(singleDate)) {
325
+ // Invalid single date - clear the input
326
+ setInputValue("");
327
+ onChange("");
328
+ } else {
329
+ // Valid single date but incomplete range - clear the input
330
+ setInputValue("");
331
+ onChange("");
332
+ }
333
+ }
334
+ }
335
+ }
336
+ };
337
+
338
+ const handleKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {
339
+ // Prevent manual typing of dash in range mode
340
+ if (!disableRange && event.key === "-") {
341
+ event.preventDefault();
342
+ return;
343
+ }
344
+
345
+ if (event.key === "Backspace") {
346
+ const input = event.target as HTMLInputElement;
347
+ const cursorPosition = input.selectionStart || 0;
348
+ const value = input.value;
349
+
350
+ // If cursor is right after a slash, move it before the slash
351
+ if (cursorPosition > 0 && value[cursorPosition - 1] === "/") {
352
+ event.preventDefault();
353
+ const newValue = value.slice(0, cursorPosition - 2) + value.slice(cursorPosition);
354
+ const formattedValue = formatInputValue(newValue);
355
+ setInputValue(formattedValue);
356
+
357
+ // Set cursor position after the deletion
358
+ requestAnimationFrame(() => {
359
+ if (triggerRef.current) {
360
+ const newPosition = Math.max(0, cursorPosition - 2);
361
+ triggerRef.current.setSelectionRange(newPosition, newPosition);
362
+ }
363
+ });
364
+
365
+ setIsTyping(true);
366
+ return;
367
+ }
368
+
369
+ // Handle deletion when cursor is on or near the dash separator
370
+ if (!disableRange && value.includes(' - ')) {
371
+ const dashIndex = value.indexOf(' - ');
372
+ const dashStart = dashIndex;
373
+ const dashEnd = dashIndex + 3; // " - " is 3 characters
374
+
375
+ // If cursor is within the dash area (including spaces)
376
+ if (cursorPosition >= dashStart && cursorPosition <= dashEnd) {
377
+ event.preventDefault();
378
+
379
+ // Remove the entire range and the last digit of the year from the first date
380
+ const beforeDash = value.slice(0, dashStart).trim();
381
+
382
+ // Extract the year part and remove the last digit
383
+ const yearMatch = beforeDash.match(/^(\d{2})\/(\d{2})\/(\d{4})$/);
384
+ if (yearMatch) {
385
+ const [, month, day, year] = yearMatch;
386
+ const truncatedYear = year.slice(0, -1); // Remove last digit
387
+ const newValue = `${month}/${day}/${truncatedYear}`;
388
+ const formattedValue = formatInputValue(newValue);
389
+ setInputValue(formattedValue);
390
+
391
+ // Update the value - no valid date yet since year is incomplete
392
+ onChange("");
393
+
394
+ // Position cursor at the end
395
+ requestAnimationFrame(() => {
396
+ if (triggerRef.current) {
397
+ triggerRef.current.setSelectionRange(formattedValue.length, formattedValue.length);
398
+ }
399
+ });
400
+ } else {
401
+ // Fallback to original behavior
402
+ const formattedValue = formatInputValue(beforeDash);
403
+ setInputValue(formattedValue);
404
+
405
+ const singleDate = parseInputDate(beforeDash);
406
+ if (singleDate && isValidDate(singleDate)) {
407
+ onChange(`${singleDate}|`);
408
+ } else {
409
+ onChange("");
410
+ }
411
+
412
+ requestAnimationFrame(() => {
413
+ if (triggerRef.current) {
414
+ const newPosition = formattedValue.length;
415
+ triggerRef.current.setSelectionRange(newPosition, newPosition);
416
+ }
417
+ });
418
+ }
419
+
420
+ setIsTyping(true);
421
+ return;
422
+ }
423
+
424
+ // If cursor is right after the dash, remove the second date
425
+ if (cursorPosition === dashEnd) {
426
+ event.preventDefault();
427
+
428
+ const beforeDash = value.slice(0, dashStart).trim();
429
+ const newValue = `${beforeDash} - `;
430
+ setInputValue(newValue);
431
+
432
+ // Update the value to only have the first date as partial range
433
+ const singleDate = parseInputDate(beforeDash);
434
+ if (singleDate && isValidDate(singleDate)) {
435
+ onChange(`${singleDate}|`);
436
+ }
437
+
438
+ // Position cursor after the dash
439
+ requestAnimationFrame(() => {
440
+ if (triggerRef.current) {
441
+ triggerRef.current.setSelectionRange(newValue.length, newValue.length);
442
+ }
443
+ });
444
+
445
+ setIsTyping(true);
446
+ return;
447
+ }
448
+ }
449
+ }
450
+
451
+ if (event.key === "Delete") {
452
+ const input = event.target as HTMLInputElement;
453
+ const cursorPosition = input.selectionStart || 0;
454
+ const value = input.value;
455
+
456
+ // Handle deletion when cursor is on or near the dash separator
457
+ if (!disableRange && value.includes(' - ')) {
458
+ const dashIndex = value.indexOf(' - ');
459
+ const dashStart = dashIndex;
460
+ const dashEnd = dashIndex + 3; // " - " is 3 characters
461
+
462
+ // If cursor is within the dash area or right before it
463
+ if (cursorPosition >= dashStart && cursorPosition <= dashEnd) {
464
+ event.preventDefault();
465
+
466
+ // Remove the dash and second date, and the last digit of the year from the first date
467
+ const beforeDash = value.slice(0, dashStart).trim();
468
+
469
+ // Extract the year part and remove the last digit
470
+ const yearMatch = beforeDash.match(/^(\d{2})\/(\d{2})\/(\d{4})$/);
471
+ if (yearMatch) {
472
+ const [, month, day, year] = yearMatch;
473
+ const truncatedYear = year.slice(0, -1); // Remove last digit
474
+ const newValue = `${month}/${day}/${truncatedYear}`;
475
+ const formattedValue = formatInputValue(newValue);
476
+ setInputValue(formattedValue);
477
+
478
+ // Update the value - no valid date yet since year is incomplete
479
+ onChange("");
480
+
481
+ // Position cursor at the end
482
+ requestAnimationFrame(() => {
483
+ if (triggerRef.current) {
484
+ triggerRef.current.setSelectionRange(formattedValue.length, formattedValue.length);
485
+ }
486
+ });
487
+ } else {
488
+ // Fallback to original behavior
489
+ const formattedValue = formatInputValue(beforeDash);
490
+ setInputValue(formattedValue);
491
+
492
+ const singleDate = parseInputDate(beforeDash);
493
+ if (singleDate && isValidDate(singleDate)) {
494
+ onChange(`${singleDate}|`);
495
+ } else {
496
+ onChange("");
497
+ }
498
+
499
+ requestAnimationFrame(() => {
500
+ if (triggerRef.current) {
501
+ const newPosition = formattedValue.length;
502
+ triggerRef.current.setSelectionRange(newPosition, newPosition);
503
+ }
504
+ });
505
+ }
506
+
507
+ setIsTyping(true);
508
+ return;
509
+ }
510
+ }
511
+ }
512
+
513
+ if (event.key === "Enter") {
514
+ if (disableRange) {
515
+ const parsedDate = parseInputDate(inputValue);
516
+ if (parsedDate && isValidDate(parsedDate)) {
517
+ onChange(`${parsedDate}|${parsedDate}`);
518
+ setVisible(false);
519
+ setIsTyping(false);
520
+ }
521
+ } else {
522
+ // In range mode, only accept complete and valid ranges
523
+ const rangeMatch = inputValue.match(/^(.+?)\s*-\s*(.+)$/);
524
+ if (rangeMatch) {
525
+ const [, fromStr, toStr] = rangeMatch;
526
+ const fromDate = parseInputDate(fromStr.trim());
527
+ const toDate = parseInputDate(toStr.trim());
528
+
529
+ if (fromDate && toDate && isValidDateRange(fromDate, toDate)) {
530
+ onChange(`${fromDate}|${toDate}`);
531
+ setVisible(false);
532
+ setIsTyping(false);
533
+ } else {
534
+ // Invalid range - clear the input
535
+ setInputValue("");
536
+ onChange("");
537
+ setVisible(false);
538
+ setIsTyping(false);
539
+ }
540
+ } else {
541
+ // Single date in range mode - clear the input
542
+ setInputValue("");
543
+ onChange("");
544
+ setVisible(false);
545
+ setIsTyping(false);
546
+ }
547
+ }
548
+ }
549
+ };
134
550
 
135
551
  return (
136
552
  <div className="relative">
@@ -140,16 +556,17 @@ export const DateRangeInput = ({
140
556
  triggerRef.current = el;
141
557
  }}
142
558
  {...props}
143
- value={formatDisplayValue(from, to)}
144
- placeholder={placeholder}
559
+ value={inputValue}
560
+ placeholder={disableRange ? "MM/DD/YYYY" : placeholder}
145
561
  disabled={disabled}
562
+ readOnly={readOnly}
146
563
  after={<Icon name="calendar_month" />}
147
564
  onFocus={handleFocus}
148
- readOnly={readOnly}
565
+ onClick={handleClick}
566
+ onChange={handleInputChange}
567
+ onBlur={handleBlur}
568
+ onKeyDown={handleKeyDown}
149
569
  label={label}
150
- onChange={() => {
151
- // Input is controlled by calendar picker, ignore direct text changes
152
- }}
153
570
  />
154
571
  {visible &&
155
572
  !readOnly &&
@@ -172,12 +589,109 @@ export const DateRangeInput = ({
172
589
  onChange={handleRangeChange}
173
590
  cardStyle
174
591
  mode={single ? "single" : "double"}
592
+ disableRange={disableRange}
175
593
  />
176
594
  </div>,
177
595
  findDocumentRoot(popoverRef.current),
178
596
  )}
179
597
  </div>
180
598
  );
599
+
600
+ function formatInputValue(value: string): string {
601
+ return formatInputValueHelper(value, disableRange);
602
+ }
603
+
604
+ function formatDisplayValue(from?: string, to?: string) {
605
+ return formatDisplayValueHelper(from, to, disableRange);
606
+ }
181
607
  };
182
608
 
183
609
  DateRangeInput.displayName = "DateRangeInput";
610
+
611
+ function isValidDateRange(fromDate: string, toDate: string): boolean {
612
+ return isValidDateRangeOrder(fromDate, toDate);
613
+ }
614
+
615
+ function formatInputValueHelper(value: string, disableRange: boolean): string {
616
+ if (disableRange) {
617
+ return formatInputValue(value);
618
+ }
619
+
620
+ if (value.includes(' - ')) {
621
+ const [from, to] = value.split(' - ');
622
+ const fromFormatted = formatInputValue(from);
623
+ const toFormatted = formatInputValue(to || '');
624
+ return `${fromFormatted} - ${toFormatted}`;
625
+ }
626
+
627
+ const cleanValue = value.replace(/-/g, '');
628
+ return formatInputValue(cleanValue);
629
+ }
630
+
631
+ function calculateCursorPositionHelper(originalValue: string, formattedValue: string, originalPosition: number, disableRange: boolean): number {
632
+ // Handle range input cursor positioning
633
+ if (!disableRange && formattedValue.includes(' - ')) {
634
+ const dashPosition = formattedValue.indexOf(' - ');
635
+ const originalDashPosition = originalValue.indexOf('-');
636
+
637
+ // If cursor was after the dash in original, maintain relative position
638
+ if (originalDashPosition !== -1 && originalPosition > originalDashPosition) {
639
+ const afterDashDigits = originalValue.slice(originalDashPosition + 1).replace(/\D/g, "").length;
640
+ const formattedAfterDash = formattedValue.slice(dashPosition + 3);
641
+
642
+ let newPosition = dashPosition + 3;
643
+ let digitCount = 0;
644
+
645
+ for (let i = 0; i < formattedAfterDash.length; i++) {
646
+ if (/\d/.test(formattedAfterDash[i])) {
647
+ digitCount++;
648
+ if (digitCount >= afterDashDigits) {
649
+ // Check if we're at a position where a slash was auto-inserted in the second date
650
+ // If the next character is a slash, place cursor after it
651
+ if (i + 1 < formattedAfterDash.length && formattedAfterDash[i + 1] === '/') {
652
+ newPosition = dashPosition + 3 + i + 2;
653
+ } else {
654
+ newPosition = dashPosition + 3 + i + 1;
655
+ }
656
+ break;
657
+ }
658
+ }
659
+ if (digitCount < afterDashDigits) {
660
+ newPosition = dashPosition + 3 + i + 1;
661
+ }
662
+ }
663
+
664
+ return Math.min(newPosition, formattedValue.length);
665
+ }
666
+ }
667
+
668
+ return calculateCursorPosition(originalValue, formattedValue, originalPosition);
669
+ }
670
+
671
+ function formatDisplayValueHelper(from?: string, to?: string, disableRange?: boolean) {
672
+ if (!from && !to) {
673
+ return "";
674
+ }
675
+
676
+ // Validate dates before displaying
677
+ const fromValid = from ? isValidDate(from) : false;
678
+ const toValid = to ? isValidDate(to) : false;
679
+
680
+ if (disableRange) {
681
+ return fromValid && from ? formatDate(from) : "";
682
+ }
683
+
684
+ // Return formatted display if we have a complete, valid range
685
+ if (fromValid && toValid && from && to && isValidDateRange(from, to)) {
686
+ return `${formatDate(from)} - ${formatDate(to)}`;
687
+ }
688
+
689
+ // If we have a valid from date but no to date, show the from date only
690
+ // This preserves the previous valid state during editing
691
+ if (fromValid && !to && from) {
692
+ return `${formatDate(from)} - `;
693
+ }
694
+
695
+ // Return empty string if no valid dates or invalid range
696
+ return "";
697
+ }
@@ -162,10 +162,22 @@ export const MenuOption = ({
162
162
  const disabledStyles =
163
163
  disabled && clsx("bg-transparent cursor-default pointer-events-none");
164
164
 
165
- const renderChildren =
166
- typeof children === "string" && highlightMatchingText
167
- ? highlightMatch(children, menuValue)
168
- : children;
165
+ const processChildren = typeof children === 'string' && highlightMatchingText
166
+ ? (
167
+ highlightMatch(children, menuValue)
168
+ ) : children;
169
+
170
+ const renderChildren = typeof children === 'object'
171
+ ? children
172
+ : variant === "action" ? (
173
+ <Label padded className={textLabelStyles}>
174
+ {processChildren}
175
+ </Label>
176
+ ) : (
177
+ <Paragraph padded className={textLabelStyles}>
178
+ {processChildren}
179
+ </Paragraph>
180
+ )
169
181
 
170
182
  return (
171
183
  <>
@@ -200,16 +212,8 @@ export const MenuOption = ({
200
212
  >
201
213
  {before && <div className="shrink-0 flex items-center">{before}</div>}
202
214
 
203
- {variant === "action" ? (
204
- <Label padded className={textLabelStyles}>
205
- {renderChildren}
206
- </Label>
207
- ) : (
208
- <Paragraph padded className={textLabelStyles}>
209
- {renderChildren}
210
- </Paragraph>
211
- )}
212
-
215
+ {renderChildren}
216
+
213
217
  {renderAfterProp()}
214
218
  </div>
215
219