myoperator-mcp 0.2.320 → 0.2.322
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/dist/index.js +711 -58
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -2517,14 +2517,16 @@ import {
|
|
|
2517
2517
|
type Strategy,
|
|
2518
2518
|
} from "@floating-ui/react-dom";
|
|
2519
2519
|
import { cva, type VariantProps } from "class-variance-authority";
|
|
2520
|
-
import {
|
|
2521
|
-
ChevronLeft,
|
|
2522
|
-
ChevronRight,
|
|
2523
|
-
Clock2,
|
|
2524
|
-
X,
|
|
2525
|
-
} from "lucide-react";
|
|
2520
|
+
import { ChevronLeft, ChevronRight, Clock2, X } from "lucide-react";
|
|
2526
2521
|
|
|
2527
2522
|
import { cn } from "@/lib/utils";
|
|
2523
|
+
import {
|
|
2524
|
+
Select,
|
|
2525
|
+
SelectContent,
|
|
2526
|
+
SelectItem,
|
|
2527
|
+
SelectTrigger,
|
|
2528
|
+
SelectValue,
|
|
2529
|
+
} from "./select";
|
|
2528
2530
|
|
|
2529
2531
|
const DEFAULT_START_TIME = "10:30:00";
|
|
2530
2532
|
const DEFAULT_END_TIME = "12:30:00";
|
|
@@ -2536,8 +2538,28 @@ const MAX_POPOVER_HEIGHT = 420;
|
|
|
2536
2538
|
const POPOVER_SCROLL_HEIGHT_VAR = "--date-time-picker-scroll-height";
|
|
2537
2539
|
const POPOVER_WIDTH_VAR = "--date-time-picker-popover-width";
|
|
2538
2540
|
const CALENDAR_PLACEMENT: Placement = "bottom-start";
|
|
2541
|
+
const YEAR_RANGE_BEFORE = 100;
|
|
2542
|
+
const YEAR_RANGE_AFTER = 10;
|
|
2543
|
+
const CALENDAR_SELECT_CONTENT_SELECTOR =
|
|
2544
|
+
"[data-date-time-picker-calendar-select]";
|
|
2545
|
+
const CALENDAR_SELECT_TRIGGER_CLASS = "!w-[90px] !gap-[6px]";
|
|
2546
|
+
const CALENDAR_SELECT_CONTENT_CLASS =
|
|
2547
|
+
"z-[10060] w-[var(--radix-select-trigger-width)] min-w-[var(--radix-select-trigger-width)] max-h-[min(16rem,var(--radix-select-content-available-height))]";
|
|
2548
|
+
const DATE_TIME_INPUT_SEGMENT_RANGES = {
|
|
2549
|
+
day: [0, 2],
|
|
2550
|
+
month: [3, 5],
|
|
2551
|
+
year: [6, 10],
|
|
2552
|
+
hour: [11, 13],
|
|
2553
|
+
minute: [14, 16],
|
|
2554
|
+
meridiem: [17, 19],
|
|
2555
|
+
} as const;
|
|
2539
2556
|
|
|
2540
2557
|
const weekDays = ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"];
|
|
2558
|
+
const monthNames = Array.from({ length: 12 }, (_, monthIndex) =>
|
|
2559
|
+
new Intl.DateTimeFormat("en-US", { month: "short" }).format(
|
|
2560
|
+
new Date(2026, monthIndex, 1)
|
|
2561
|
+
)
|
|
2562
|
+
);
|
|
2541
2563
|
const monthFormatter = new Intl.DateTimeFormat("en-US", {
|
|
2542
2564
|
month: "long",
|
|
2543
2565
|
year: "numeric",
|
|
@@ -2650,6 +2672,43 @@ function addMonths(date: Date, months: number) {
|
|
|
2650
2672
|
return new Date(date.getFullYear(), date.getMonth() + months, 1);
|
|
2651
2673
|
}
|
|
2652
2674
|
|
|
2675
|
+
function getMonthKey(date: Date) {
|
|
2676
|
+
return date.getFullYear() * 12 + date.getMonth();
|
|
2677
|
+
}
|
|
2678
|
+
|
|
2679
|
+
function isMonthBefore(date: Date, minDate: Date) {
|
|
2680
|
+
return getMonthKey(date) < getMonthKey(minDate);
|
|
2681
|
+
}
|
|
2682
|
+
|
|
2683
|
+
function isMonthAfter(date: Date, maxDate: Date) {
|
|
2684
|
+
return getMonthKey(date) > getMonthKey(maxDate);
|
|
2685
|
+
}
|
|
2686
|
+
|
|
2687
|
+
function clampMonth(date: Date, minDate?: Date, maxDate?: Date) {
|
|
2688
|
+
if (minDate && isMonthBefore(date, minDate)) return startOfMonth(minDate);
|
|
2689
|
+
if (maxDate && isMonthAfter(date, maxDate)) return startOfMonth(maxDate);
|
|
2690
|
+
|
|
2691
|
+
return startOfMonth(date);
|
|
2692
|
+
}
|
|
2693
|
+
|
|
2694
|
+
function getYearOptions(visibleMonth: Date, minDate?: Date, maxDate?: Date) {
|
|
2695
|
+
const currentYear = new Date().getFullYear();
|
|
2696
|
+
const selectedYear = visibleMonth.getFullYear();
|
|
2697
|
+
const startYear = Math.min(
|
|
2698
|
+
minDate?.getFullYear() ?? currentYear - YEAR_RANGE_BEFORE,
|
|
2699
|
+
selectedYear
|
|
2700
|
+
);
|
|
2701
|
+
const endYear = Math.max(
|
|
2702
|
+
maxDate?.getFullYear() ?? currentYear + YEAR_RANGE_AFTER,
|
|
2703
|
+
selectedYear
|
|
2704
|
+
);
|
|
2705
|
+
|
|
2706
|
+
return Array.from(
|
|
2707
|
+
{ length: endYear - startYear + 1 },
|
|
2708
|
+
(_, index) => startYear + index
|
|
2709
|
+
);
|
|
2710
|
+
}
|
|
2711
|
+
|
|
2653
2712
|
function getCalendarDays(month: Date) {
|
|
2654
2713
|
const firstDay = startOfMonth(month);
|
|
2655
2714
|
const gridStart = new Date(firstDay);
|
|
@@ -2680,6 +2739,12 @@ function isPointerInsideElement(
|
|
|
2680
2739
|
return false;
|
|
2681
2740
|
}
|
|
2682
2741
|
|
|
2742
|
+
function isPointerInsideSelector(event: MouseEvent, selector: string) {
|
|
2743
|
+
const target = event.target;
|
|
2744
|
+
|
|
2745
|
+
return target instanceof Element && target.closest(selector) !== null;
|
|
2746
|
+
}
|
|
2747
|
+
|
|
2683
2748
|
function formatTimeForDisplay(time: string) {
|
|
2684
2749
|
const [hour = "0", minute = "0"] = time.split(":");
|
|
2685
2750
|
const hourNumber = Number(hour);
|
|
@@ -2697,7 +2762,383 @@ function formatDateForDisplay(date?: Date, time?: string) {
|
|
|
2697
2762
|
const year = date.getFullYear();
|
|
2698
2763
|
const formattedTime = formatTimeForDisplay(time ?? DEFAULT_START_TIME);
|
|
2699
2764
|
|
|
2700
|
-
return \`\${
|
|
2765
|
+
return \`\${day}/\${month}/\${year} \${formattedTime}\`;
|
|
2766
|
+
}
|
|
2767
|
+
|
|
2768
|
+
function parseDatePart(datePart: string) {
|
|
2769
|
+
const isoMatch = datePart.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})$/);
|
|
2770
|
+
const dayFirstMatch = datePart.match(/^(\\d{1,2})[/-](\\d{1,2})[/-](\\d{4})$/);
|
|
2771
|
+
const [, yearValue, isoMonthValue, isoDayValue] = isoMatch ?? [];
|
|
2772
|
+
const [, dayValue, monthValue, dayFirstYearValue] = dayFirstMatch ?? [];
|
|
2773
|
+
|
|
2774
|
+
const year = Number(yearValue ?? dayFirstYearValue);
|
|
2775
|
+
const month = Number(isoMonthValue ?? monthValue);
|
|
2776
|
+
const day = Number(isoDayValue ?? dayValue);
|
|
2777
|
+
|
|
2778
|
+
if (!year || !month || !day) return null;
|
|
2779
|
+
|
|
2780
|
+
const parsedDate = new Date(year, month - 1, day);
|
|
2781
|
+
if (
|
|
2782
|
+
parsedDate.getFullYear() !== year ||
|
|
2783
|
+
parsedDate.getMonth() !== month - 1 ||
|
|
2784
|
+
parsedDate.getDate() !== day
|
|
2785
|
+
) {
|
|
2786
|
+
return null;
|
|
2787
|
+
}
|
|
2788
|
+
|
|
2789
|
+
return parsedDate;
|
|
2790
|
+
}
|
|
2791
|
+
|
|
2792
|
+
function parseTimePart(timePart?: string) {
|
|
2793
|
+
if (!timePart) return undefined;
|
|
2794
|
+
|
|
2795
|
+
const timeMatch = timePart
|
|
2796
|
+
.trim()
|
|
2797
|
+
.match(/^(\\d{1,2}):(\\d{2})(?::(\\d{2}))?\\s*(AM|PM)?$/i);
|
|
2798
|
+
if (!timeMatch) return null;
|
|
2799
|
+
|
|
2800
|
+
const [, hourValue, minuteValue, secondValue = "00", meridiem] = timeMatch;
|
|
2801
|
+
let hour = Number(hourValue);
|
|
2802
|
+
const minute = Number(minuteValue);
|
|
2803
|
+
const second = Number(secondValue);
|
|
2804
|
+
|
|
2805
|
+
if (
|
|
2806
|
+
minute > 59 ||
|
|
2807
|
+
second > 59 ||
|
|
2808
|
+
(meridiem ? hour < 1 || hour > 12 : hour > 23)
|
|
2809
|
+
) {
|
|
2810
|
+
return null;
|
|
2811
|
+
}
|
|
2812
|
+
|
|
2813
|
+
if (meridiem) {
|
|
2814
|
+
const normalizedMeridiem = meridiem.toUpperCase();
|
|
2815
|
+
if (normalizedMeridiem === "AM") {
|
|
2816
|
+
hour = hour === 12 ? 0 : hour;
|
|
2817
|
+
} else {
|
|
2818
|
+
hour = hour === 12 ? 12 : hour + 12;
|
|
2819
|
+
}
|
|
2820
|
+
}
|
|
2821
|
+
|
|
2822
|
+
return \`\${hour.toString().padStart(2, "0")}:\${minuteValue}:\${secondValue}\`;
|
|
2823
|
+
}
|
|
2824
|
+
|
|
2825
|
+
function getDaysInMonth(year: number, month: number) {
|
|
2826
|
+
return new Date(year, month, 0).getDate();
|
|
2827
|
+
}
|
|
2828
|
+
|
|
2829
|
+
function getPotentialMaxDay(month: number, year?: number) {
|
|
2830
|
+
if (month === 2 && year === undefined) return 29;
|
|
2831
|
+
if (month === 2) return getDaysInMonth(year ?? 2000, month);
|
|
2832
|
+
if ([4, 6, 9, 11].includes(month)) return 30;
|
|
2833
|
+
|
|
2834
|
+
return 31;
|
|
2835
|
+
}
|
|
2836
|
+
|
|
2837
|
+
function isPotentiallyValidDateParts(
|
|
2838
|
+
dayValue: string,
|
|
2839
|
+
monthValue: string,
|
|
2840
|
+
yearValue: string
|
|
2841
|
+
) {
|
|
2842
|
+
const day = Number(dayValue);
|
|
2843
|
+
const month = Number(monthValue);
|
|
2844
|
+
const year = Number(yearValue);
|
|
2845
|
+
|
|
2846
|
+
if (dayValue.length === 1 && day > 3) return false;
|
|
2847
|
+
if (dayValue.length === 2 && (day < 1 || day > 31)) return false;
|
|
2848
|
+
if (monthValue.length === 1 && month > 1) return false;
|
|
2849
|
+
if (monthValue.length === 2 && (month < 1 || month > 12)) return false;
|
|
2850
|
+
if (yearValue.length === 4 && year < 1000) return false;
|
|
2851
|
+
|
|
2852
|
+
if (dayValue.length === 2 && monthValue.length === 2) {
|
|
2853
|
+
const maxDay = getPotentialMaxDay(
|
|
2854
|
+
month,
|
|
2855
|
+
yearValue.length === 4 ? year : undefined
|
|
2856
|
+
);
|
|
2857
|
+
if (day > maxDay) return false;
|
|
2858
|
+
}
|
|
2859
|
+
|
|
2860
|
+
return true;
|
|
2861
|
+
}
|
|
2862
|
+
|
|
2863
|
+
function isPotentiallyValidDateDigits(dateDigits: string) {
|
|
2864
|
+
return isPotentiallyValidDateParts(
|
|
2865
|
+
dateDigits.slice(0, 2),
|
|
2866
|
+
dateDigits.slice(2, 4),
|
|
2867
|
+
dateDigits.slice(4, 8)
|
|
2868
|
+
);
|
|
2869
|
+
}
|
|
2870
|
+
|
|
2871
|
+
function formatDateDigits(dateDigits: string) {
|
|
2872
|
+
const day = dateDigits.slice(0, 2);
|
|
2873
|
+
const month = dateDigits.slice(2, 4);
|
|
2874
|
+
const year = dateDigits.slice(4, 8);
|
|
2875
|
+
|
|
2876
|
+
if (dateDigits.length <= 2) {
|
|
2877
|
+
return dateDigits.length === 2 ? \`\${day}/\` : day;
|
|
2878
|
+
}
|
|
2879
|
+
|
|
2880
|
+
if (dateDigits.length <= 4) {
|
|
2881
|
+
return \`\${day}/\${dateDigits.length === 4 ? \`\${month}/\` : month}\`;
|
|
2882
|
+
}
|
|
2883
|
+
|
|
2884
|
+
return \`\${day}/\${month}/\${year}\`;
|
|
2885
|
+
}
|
|
2886
|
+
|
|
2887
|
+
function formatSegmentedDateInput(
|
|
2888
|
+
dateSource: string,
|
|
2889
|
+
day: string,
|
|
2890
|
+
month: string,
|
|
2891
|
+
year: string
|
|
2892
|
+
) {
|
|
2893
|
+
const separatorCount = dateSource.match(/[/-]/g)?.length ?? 0;
|
|
2894
|
+
const shouldShowMonth = separatorCount >= 1 || !!month || !!year;
|
|
2895
|
+
const shouldShowYear = separatorCount >= 2 || !!year;
|
|
2896
|
+
|
|
2897
|
+
return [
|
|
2898
|
+
day,
|
|
2899
|
+
shouldShowMonth ? month : undefined,
|
|
2900
|
+
shouldShowYear ? year : undefined,
|
|
2901
|
+
]
|
|
2902
|
+
.filter((part): part is string => part !== undefined)
|
|
2903
|
+
.join("/");
|
|
2904
|
+
}
|
|
2905
|
+
|
|
2906
|
+
function consumeDigits(source: string, maxLength: number) {
|
|
2907
|
+
let digits = "";
|
|
2908
|
+
let endIndex = source.length;
|
|
2909
|
+
|
|
2910
|
+
for (let index = 0; index < source.length; index += 1) {
|
|
2911
|
+
if (!/\\d/.test(source[index])) continue;
|
|
2912
|
+
|
|
2913
|
+
digits += source[index];
|
|
2914
|
+
if (digits.length === maxLength) {
|
|
2915
|
+
endIndex = index + 1;
|
|
2916
|
+
break;
|
|
2917
|
+
}
|
|
2918
|
+
}
|
|
2919
|
+
|
|
2920
|
+
return {
|
|
2921
|
+
digits,
|
|
2922
|
+
rest: source.slice(endIndex),
|
|
2923
|
+
};
|
|
2924
|
+
}
|
|
2925
|
+
|
|
2926
|
+
function splitTypedDateInput(value: string) {
|
|
2927
|
+
const firstSpaceIndex = value.search(/\\s/);
|
|
2928
|
+
const dateSource =
|
|
2929
|
+
firstSpaceIndex === -1 ? value : value.slice(0, firstSpaceIndex);
|
|
2930
|
+
const restValue =
|
|
2931
|
+
firstSpaceIndex === -1 ? "" : value.slice(firstSpaceIndex + 1);
|
|
2932
|
+
|
|
2933
|
+
if (/[/-]/.test(dateSource)) {
|
|
2934
|
+
const [dayPart = "", monthPart = "", yearPart = ""] =
|
|
2935
|
+
dateSource.split(/[/-]/);
|
|
2936
|
+
const dayResult = consumeDigits(dayPart, 2);
|
|
2937
|
+
const monthResult = consumeDigits(\`\${dayResult.rest}\${monthPart}\`, 2);
|
|
2938
|
+
const yearResult = consumeDigits(
|
|
2939
|
+
\`\${monthResult.rest}\${yearPart}\`,
|
|
2940
|
+
4
|
|
2941
|
+
);
|
|
2942
|
+
const day = dayResult.digits;
|
|
2943
|
+
const month = monthResult.digits;
|
|
2944
|
+
const year = yearResult.digits;
|
|
2945
|
+
|
|
2946
|
+
return {
|
|
2947
|
+
dateDigits: \`\${day}\${month}\${year}\`,
|
|
2948
|
+
dateValue: formatSegmentedDateInput(dateSource, day, month, year),
|
|
2949
|
+
isComplete: !!day && !!month && year.length === 4,
|
|
2950
|
+
isValid: isPotentiallyValidDateParts(day, month, year),
|
|
2951
|
+
restValue: [yearResult.rest, restValue].filter(Boolean).join(" "),
|
|
2952
|
+
};
|
|
2953
|
+
}
|
|
2954
|
+
|
|
2955
|
+
let dateDigits = "";
|
|
2956
|
+
let dateEndIndex = value.length;
|
|
2957
|
+
|
|
2958
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
2959
|
+
const character = value[index];
|
|
2960
|
+
|
|
2961
|
+
if (/\\d/.test(character)) {
|
|
2962
|
+
dateDigits += character;
|
|
2963
|
+
if (dateDigits.length === 8) {
|
|
2964
|
+
dateEndIndex = index + 1;
|
|
2965
|
+
break;
|
|
2966
|
+
}
|
|
2967
|
+
}
|
|
2968
|
+
}
|
|
2969
|
+
|
|
2970
|
+
return {
|
|
2971
|
+
dateDigits,
|
|
2972
|
+
dateValue: formatDateDigits(dateDigits),
|
|
2973
|
+
isComplete: dateDigits.length === 8,
|
|
2974
|
+
isValid: isPotentiallyValidDateDigits(dateDigits),
|
|
2975
|
+
restValue: value.slice(dateEndIndex),
|
|
2976
|
+
};
|
|
2977
|
+
}
|
|
2978
|
+
|
|
2979
|
+
function isPotentiallyValidTimeDigits(timeDigits: string) {
|
|
2980
|
+
const hourValue = timeDigits.slice(0, 2);
|
|
2981
|
+
const minuteValue = timeDigits.slice(2, 4);
|
|
2982
|
+
const hour = Number(hourValue);
|
|
2983
|
+
const minute = Number(minuteValue);
|
|
2984
|
+
|
|
2985
|
+
if (hourValue.length === 1 && hour > 1) return false;
|
|
2986
|
+
if (hourValue.length === 2 && (hour < 1 || hour > 12)) return false;
|
|
2987
|
+
if (minuteValue.length === 1 && minute > 5) return false;
|
|
2988
|
+
if (minuteValue.length === 2 && minute > 59) return false;
|
|
2989
|
+
|
|
2990
|
+
return true;
|
|
2991
|
+
}
|
|
2992
|
+
|
|
2993
|
+
function formatMeridiemInput(letters: string) {
|
|
2994
|
+
if (!letters) return "";
|
|
2995
|
+
if ("AM".startsWith(letters)) return letters;
|
|
2996
|
+
if ("PM".startsWith(letters)) return letters;
|
|
2997
|
+
|
|
2998
|
+
return null;
|
|
2999
|
+
}
|
|
3000
|
+
|
|
3001
|
+
function formatTimeInput(restValue: string) {
|
|
3002
|
+
const normalizedValue = restValue.toUpperCase();
|
|
3003
|
+
const timeDigits = normalizedValue.replace(/\\D/g, "").slice(0, 4);
|
|
3004
|
+
const meridiemLetters = normalizedValue.replace(/[^A-Z]/g, "");
|
|
3005
|
+
const meridiem = formatMeridiemInput(meridiemLetters.slice(0, 2));
|
|
3006
|
+
|
|
3007
|
+
if (!timeDigits && !meridiem) return "";
|
|
3008
|
+
if (!isPotentiallyValidTimeDigits(timeDigits) || meridiem === null) {
|
|
3009
|
+
return null;
|
|
3010
|
+
}
|
|
3011
|
+
|
|
3012
|
+
const timeValue =
|
|
3013
|
+
timeDigits.length <= 2
|
|
3014
|
+
? timeDigits
|
|
3015
|
+
: \`\${timeDigits.slice(0, 2)}:\${timeDigits.slice(2, 4)}\`;
|
|
3016
|
+
|
|
3017
|
+
return [timeValue, meridiem].filter(Boolean).join(" ");
|
|
3018
|
+
}
|
|
3019
|
+
|
|
3020
|
+
function sanitizeTypedDateTimeInput(value: string, previousValue: string) {
|
|
3021
|
+
const trimmedValue = value.trimStart();
|
|
3022
|
+
if (/^[A-Za-z]/.test(trimmedValue)) return previousValue;
|
|
3023
|
+
|
|
3024
|
+
const normalizedValue = value
|
|
3025
|
+
.toUpperCase()
|
|
3026
|
+
.replace(/[^0-9\\s/:APM-]/g, "");
|
|
3027
|
+
const { dateDigits, dateValue, isComplete, isValid, restValue } =
|
|
3028
|
+
splitTypedDateInput(normalizedValue);
|
|
3029
|
+
const limitedDateDigits = dateDigits.slice(0, 8);
|
|
3030
|
+
|
|
3031
|
+
if (!limitedDateDigits) return "";
|
|
3032
|
+
if (!isValid) return previousValue;
|
|
3033
|
+
|
|
3034
|
+
const formattedTime = isComplete ? formatTimeInput(restValue) : "";
|
|
3035
|
+
if (formattedTime === null) return previousValue;
|
|
3036
|
+
|
|
3037
|
+
return [dateValue, formattedTime].filter(Boolean).join(" ");
|
|
3038
|
+
}
|
|
3039
|
+
|
|
3040
|
+
type DateTimeInputSegment = keyof typeof DATE_TIME_INPUT_SEGMENT_RANGES;
|
|
3041
|
+
|
|
3042
|
+
function getDateTimeInputSegment(cursorPosition: number): DateTimeInputSegment {
|
|
3043
|
+
if (cursorPosition <= DATE_TIME_INPUT_SEGMENT_RANGES.day[1]) return "day";
|
|
3044
|
+
if (cursorPosition <= DATE_TIME_INPUT_SEGMENT_RANGES.month[1]) return "month";
|
|
3045
|
+
if (cursorPosition <= DATE_TIME_INPUT_SEGMENT_RANGES.year[1]) return "year";
|
|
3046
|
+
if (cursorPosition <= DATE_TIME_INPUT_SEGMENT_RANGES.hour[1]) return "hour";
|
|
3047
|
+
if (cursorPosition <= DATE_TIME_INPUT_SEGMENT_RANGES.minute[1]) {
|
|
3048
|
+
return "minute";
|
|
3049
|
+
}
|
|
3050
|
+
|
|
3051
|
+
return "meridiem";
|
|
3052
|
+
}
|
|
3053
|
+
|
|
3054
|
+
function clampDateToMonth(year: number, month: number, day: number) {
|
|
3055
|
+
return new Date(year, month, Math.min(day, getDaysInMonth(year, month + 1)));
|
|
3056
|
+
}
|
|
3057
|
+
|
|
3058
|
+
function stepDateTimeInputValue(
|
|
3059
|
+
value: string,
|
|
3060
|
+
cursorPosition: number,
|
|
3061
|
+
direction: 1 | -1,
|
|
3062
|
+
fallbackTime: string
|
|
3063
|
+
) {
|
|
3064
|
+
const typedDateTime = parseTypedDateTime(value);
|
|
3065
|
+
if (!typedDateTime) return null;
|
|
3066
|
+
|
|
3067
|
+
const segment = getDateTimeInputSegment(cursorPosition);
|
|
3068
|
+
const nextDate = new Date(typedDateTime.date);
|
|
3069
|
+
const [hourValue = "0", minuteValue = "0", secondValue = "00"] = (
|
|
3070
|
+
typedDateTime.startTime ?? fallbackTime
|
|
3071
|
+
).split(":");
|
|
3072
|
+
nextDate.setHours(Number(hourValue), Number(minuteValue), Number(secondValue));
|
|
3073
|
+
|
|
3074
|
+
if (segment === "day") {
|
|
3075
|
+
nextDate.setDate(nextDate.getDate() + direction);
|
|
3076
|
+
} else if (segment === "month") {
|
|
3077
|
+
const day = nextDate.getDate();
|
|
3078
|
+
const steppedMonth = clampDateToMonth(
|
|
3079
|
+
nextDate.getFullYear(),
|
|
3080
|
+
nextDate.getMonth() + direction,
|
|
3081
|
+
day
|
|
3082
|
+
);
|
|
3083
|
+
nextDate.setFullYear(
|
|
3084
|
+
steppedMonth.getFullYear(),
|
|
3085
|
+
steppedMonth.getMonth(),
|
|
3086
|
+
steppedMonth.getDate()
|
|
3087
|
+
);
|
|
3088
|
+
} else if (segment === "year") {
|
|
3089
|
+
const day = nextDate.getDate();
|
|
3090
|
+
const steppedYear = clampDateToMonth(
|
|
3091
|
+
nextDate.getFullYear() + direction,
|
|
3092
|
+
nextDate.getMonth(),
|
|
3093
|
+
day
|
|
3094
|
+
);
|
|
3095
|
+
nextDate.setFullYear(
|
|
3096
|
+
steppedYear.getFullYear(),
|
|
3097
|
+
steppedYear.getMonth(),
|
|
3098
|
+
steppedYear.getDate()
|
|
3099
|
+
);
|
|
3100
|
+
} else if (segment === "hour") {
|
|
3101
|
+
nextDate.setHours(nextDate.getHours() + direction);
|
|
3102
|
+
} else if (segment === "minute") {
|
|
3103
|
+
nextDate.setMinutes(nextDate.getMinutes() + direction);
|
|
3104
|
+
} else {
|
|
3105
|
+
nextDate.setHours(nextDate.getHours() + 12 * direction);
|
|
3106
|
+
}
|
|
3107
|
+
|
|
3108
|
+
const startTime = \`\${nextDate
|
|
3109
|
+
.getHours()
|
|
3110
|
+
.toString()
|
|
3111
|
+
.padStart(2, "0")}:\${nextDate
|
|
3112
|
+
.getMinutes()
|
|
3113
|
+
.toString()
|
|
3114
|
+
.padStart(2, "0")}:\${nextDate
|
|
3115
|
+
.getSeconds()
|
|
3116
|
+
.toString()
|
|
3117
|
+
.padStart(2, "0")}\`;
|
|
3118
|
+
|
|
3119
|
+
return {
|
|
3120
|
+
date: startOfDay(nextDate),
|
|
3121
|
+
startTime,
|
|
3122
|
+
segment,
|
|
3123
|
+
};
|
|
3124
|
+
}
|
|
3125
|
+
|
|
3126
|
+
function parseTypedDateTime(value: string) {
|
|
3127
|
+
const typedValue = value.trim();
|
|
3128
|
+
if (!typedValue) return undefined;
|
|
3129
|
+
|
|
3130
|
+
const typedMatch = typedValue.match(
|
|
3131
|
+
/^(\\d{4}-\\d{1,2}-\\d{1,2}|\\d{1,2}[/-]\\d{1,2}[/-]\\d{4})(?:\\s+(.+))?$/
|
|
3132
|
+
);
|
|
3133
|
+
if (!typedMatch) return null;
|
|
3134
|
+
|
|
3135
|
+
const [, datePart, timePart] = typedMatch;
|
|
3136
|
+
const date = parseDatePart(datePart);
|
|
3137
|
+
const startTime = parseTimePart(timePart);
|
|
3138
|
+
|
|
3139
|
+
if (!date || startTime === null) return null;
|
|
3140
|
+
|
|
3141
|
+
return { date, startTime };
|
|
2701
3142
|
}
|
|
2702
3143
|
|
|
2703
3144
|
function formatHiddenValue(value: DateTimePickerValue) {
|
|
@@ -2775,8 +3216,12 @@ const DateTimePicker = React.forwardRef<HTMLDivElement, DateTimePickerProps>(
|
|
|
2775
3216
|
const [visibleMonth, setVisibleMonth] = React.useState(() =>
|
|
2776
3217
|
startOfMonth(currentValue.date ?? new Date())
|
|
2777
3218
|
);
|
|
3219
|
+
const [dateInputValue, setDateInputValue] = React.useState(() =>
|
|
3220
|
+
formatDateForDisplay(currentValue.date, currentValue.startTime)
|
|
3221
|
+
);
|
|
3222
|
+
const [isDateInputFocused, setIsDateInputFocused] = React.useState(false);
|
|
2778
3223
|
const rootRef = React.useRef<HTMLDivElement | null>(null);
|
|
2779
|
-
const triggerRef = React.useRef<
|
|
3224
|
+
const triggerRef = React.useRef<HTMLDivElement | null>(null);
|
|
2780
3225
|
const popoverRef = React.useRef<HTMLDivElement | null>(null);
|
|
2781
3226
|
const usesContainerPortal = portalContainer !== undefined;
|
|
2782
3227
|
const floatingStrategy: Strategy = usesContainerPortal
|
|
@@ -2807,16 +3252,15 @@ const DateTimePicker = React.forwardRef<HTMLDivElement, DateTimePickerProps>(
|
|
|
2807
3252
|
],
|
|
2808
3253
|
[]
|
|
2809
3254
|
);
|
|
2810
|
-
const { refs, floatingStyles, isPositioned } =
|
|
2811
|
-
|
|
2812
|
-
|
|
2813
|
-
|
|
2814
|
-
|
|
2815
|
-
|
|
2816
|
-
|
|
2817
|
-
|
|
2818
|
-
|
|
2819
|
-
});
|
|
3255
|
+
const { refs, floatingStyles, isPositioned } = useFloating<HTMLDivElement>({
|
|
3256
|
+
open,
|
|
3257
|
+
placement: CALENDAR_PLACEMENT,
|
|
3258
|
+
strategy: floatingStrategy,
|
|
3259
|
+
transform: false,
|
|
3260
|
+
middleware: floatingMiddleware,
|
|
3261
|
+
whileElementsMounted: (reference, floating, update) =>
|
|
3262
|
+
autoUpdate(reference, floating, update, { animationFrame: true }),
|
|
3263
|
+
});
|
|
2820
3264
|
const calendarDays = React.useMemo(
|
|
2821
3265
|
() => getCalendarDays(visibleMonth),
|
|
2822
3266
|
[visibleMonth]
|
|
@@ -2852,7 +3296,7 @@ const DateTimePicker = React.forwardRef<HTMLDivElement, DateTimePickerProps>(
|
|
|
2852
3296
|
);
|
|
2853
3297
|
|
|
2854
3298
|
const setTriggerRef = React.useCallback(
|
|
2855
|
-
(node:
|
|
3299
|
+
(node: HTMLDivElement | null) => {
|
|
2856
3300
|
triggerRef.current = node;
|
|
2857
3301
|
refs.setReference(node);
|
|
2858
3302
|
},
|
|
@@ -2873,13 +3317,20 @@ const DateTimePicker = React.forwardRef<HTMLDivElement, DateTimePickerProps>(
|
|
|
2873
3317
|
}
|
|
2874
3318
|
}, [currentValue.date]);
|
|
2875
3319
|
|
|
3320
|
+
React.useEffect(() => {
|
|
3321
|
+
if (!isDateInputFocused) {
|
|
3322
|
+
setDateInputValue(displayValue);
|
|
3323
|
+
}
|
|
3324
|
+
}, [displayValue, isDateInputFocused]);
|
|
3325
|
+
|
|
2876
3326
|
React.useEffect(() => {
|
|
2877
3327
|
if (!open) return;
|
|
2878
3328
|
|
|
2879
3329
|
const handlePointerDown = (event: MouseEvent) => {
|
|
2880
3330
|
if (
|
|
2881
3331
|
!isPointerInsideElement(event, rootRef.current) &&
|
|
2882
|
-
!isPointerInsideElement(event, popoverRef.current)
|
|
3332
|
+
!isPointerInsideElement(event, popoverRef.current) &&
|
|
3333
|
+
!isPointerInsideSelector(event, CALENDAR_SELECT_CONTENT_SELECTOR)
|
|
2883
3334
|
) {
|
|
2884
3335
|
setOpen(false);
|
|
2885
3336
|
}
|
|
@@ -2915,6 +3366,7 @@ const DateTimePicker = React.forwardRef<HTMLDivElement, DateTimePickerProps>(
|
|
|
2915
3366
|
event.stopPropagation();
|
|
2916
3367
|
if (readOnly) return;
|
|
2917
3368
|
|
|
3369
|
+
setDateInputValue("");
|
|
2918
3370
|
updateValue({
|
|
2919
3371
|
date: undefined,
|
|
2920
3372
|
startTime: currentValue.startTime,
|
|
@@ -2922,6 +3374,111 @@ const DateTimePicker = React.forwardRef<HTMLDivElement, DateTimePickerProps>(
|
|
|
2922
3374
|
});
|
|
2923
3375
|
};
|
|
2924
3376
|
|
|
3377
|
+
const yearOptions = React.useMemo(
|
|
3378
|
+
() => getYearOptions(visibleMonth, effectiveMinDate, maxDate),
|
|
3379
|
+
[effectiveMinDate, maxDate, visibleMonth]
|
|
3380
|
+
);
|
|
3381
|
+
|
|
3382
|
+
const updateVisibleMonth = React.useCallback(
|
|
3383
|
+
(nextMonth: Date) => {
|
|
3384
|
+
setVisibleMonth(clampMonth(nextMonth, effectiveMinDate, maxDate));
|
|
3385
|
+
},
|
|
3386
|
+
[effectiveMinDate, maxDate]
|
|
3387
|
+
);
|
|
3388
|
+
|
|
3389
|
+
const handleTypedDateChange = (
|
|
3390
|
+
event: React.ChangeEvent<HTMLInputElement>
|
|
3391
|
+
) => {
|
|
3392
|
+
const nextInputValue = sanitizeTypedDateTimeInput(
|
|
3393
|
+
event.target.value,
|
|
3394
|
+
dateInputValue
|
|
3395
|
+
);
|
|
3396
|
+
setDateInputValue(nextInputValue);
|
|
3397
|
+
|
|
3398
|
+
if (
|
|
3399
|
+
nextInputValue === dateInputValue &&
|
|
3400
|
+
event.target.value !== dateInputValue
|
|
3401
|
+
) {
|
|
3402
|
+
event.currentTarget.value = nextInputValue;
|
|
3403
|
+
return;
|
|
3404
|
+
}
|
|
3405
|
+
|
|
3406
|
+
const typedDateTime = parseTypedDateTime(nextInputValue);
|
|
3407
|
+
if (typedDateTime === undefined) {
|
|
3408
|
+
updateValue({ ...currentValue, date: undefined });
|
|
3409
|
+
return;
|
|
3410
|
+
}
|
|
3411
|
+
|
|
3412
|
+
if (
|
|
3413
|
+
typedDateTime &&
|
|
3414
|
+
!(
|
|
3415
|
+
effectiveMinDate && isBeforeDay(typedDateTime.date, effectiveMinDate)
|
|
3416
|
+
) &&
|
|
3417
|
+
!(maxDate && isAfterDay(typedDateTime.date, maxDate))
|
|
3418
|
+
) {
|
|
3419
|
+
updateValue({
|
|
3420
|
+
...currentValue,
|
|
3421
|
+
date: typedDateTime.date,
|
|
3422
|
+
startTime: typedDateTime.startTime ?? currentValue.startTime,
|
|
3423
|
+
});
|
|
3424
|
+
updateVisibleMonth(typedDateTime.date);
|
|
3425
|
+
}
|
|
3426
|
+
};
|
|
3427
|
+
|
|
3428
|
+
const handleTypedDateKeyDown = (
|
|
3429
|
+
event: React.KeyboardEvent<HTMLInputElement>
|
|
3430
|
+
) => {
|
|
3431
|
+
if (event.key !== "ArrowUp" && event.key !== "ArrowDown") return;
|
|
3432
|
+
|
|
3433
|
+
const direction = event.key === "ArrowUp" ? 1 : -1;
|
|
3434
|
+
const cursorPosition =
|
|
3435
|
+
event.currentTarget.selectionStart ?? dateInputValue.length;
|
|
3436
|
+
const inputElement = event.currentTarget;
|
|
3437
|
+
const steppedDateTime = stepDateTimeInputValue(
|
|
3438
|
+
dateInputValue,
|
|
3439
|
+
cursorPosition,
|
|
3440
|
+
direction,
|
|
3441
|
+
currentValue.startTime
|
|
3442
|
+
);
|
|
3443
|
+
|
|
3444
|
+
if (!steppedDateTime) return;
|
|
3445
|
+
if (
|
|
3446
|
+
(effectiveMinDate &&
|
|
3447
|
+
isBeforeDay(steppedDateTime.date, effectiveMinDate)) ||
|
|
3448
|
+
(maxDate && isAfterDay(steppedDateTime.date, maxDate))
|
|
3449
|
+
) {
|
|
3450
|
+
return;
|
|
3451
|
+
}
|
|
3452
|
+
|
|
3453
|
+
event.preventDefault();
|
|
3454
|
+
|
|
3455
|
+
const nextInputValue = formatDateForDisplay(
|
|
3456
|
+
steppedDateTime.date,
|
|
3457
|
+
steppedDateTime.startTime
|
|
3458
|
+
);
|
|
3459
|
+
const [selectionStart, selectionEnd] =
|
|
3460
|
+
DATE_TIME_INPUT_SEGMENT_RANGES[steppedDateTime.segment];
|
|
3461
|
+
|
|
3462
|
+
setDateInputValue(nextInputValue);
|
|
3463
|
+
updateValue({
|
|
3464
|
+
...currentValue,
|
|
3465
|
+
date: steppedDateTime.date,
|
|
3466
|
+
startTime: steppedDateTime.startTime,
|
|
3467
|
+
});
|
|
3468
|
+
updateVisibleMonth(steppedDateTime.date);
|
|
3469
|
+
|
|
3470
|
+
window.requestAnimationFrame(() => {
|
|
3471
|
+
inputElement.setSelectionRange(selectionStart, selectionEnd);
|
|
3472
|
+
});
|
|
3473
|
+
};
|
|
3474
|
+
|
|
3475
|
+
const handleTypedDateBlur = () => {
|
|
3476
|
+
setIsDateInputFocused(false);
|
|
3477
|
+
setDateInputValue(
|
|
3478
|
+
formatDateForDisplay(currentValue.date, currentValue.startTime)
|
|
3479
|
+
);
|
|
3480
|
+
};
|
|
3481
|
+
|
|
2925
3482
|
const popover =
|
|
2926
3483
|
open &&
|
|
2927
3484
|
!disabled &&
|
|
@@ -2953,26 +3510,107 @@ const DateTimePicker = React.forwardRef<HTMLDivElement, DateTimePickerProps>(
|
|
|
2953
3510
|
onTouchMove={(event) => event.stopPropagation()}
|
|
2954
3511
|
>
|
|
2955
3512
|
<div className="p-3 touch-pan-y">
|
|
2956
|
-
<div className="mb-3 flex items-center justify-between">
|
|
3513
|
+
<div className="mb-3 flex items-center justify-between gap-2">
|
|
2957
3514
|
<button
|
|
2958
3515
|
type="button"
|
|
2959
3516
|
aria-label="Previous month"
|
|
2960
3517
|
className="p-1 rounded hover:bg-semantic-bg-hover text-semantic-text-secondary transition-colors"
|
|
2961
|
-
onClick={() =>
|
|
3518
|
+
onClick={() => updateVisibleMonth(addMonths(visibleMonth, -1))}
|
|
2962
3519
|
>
|
|
2963
3520
|
<ChevronLeft className="size-4" aria-hidden="true" />
|
|
2964
3521
|
</button>
|
|
2965
|
-
<div
|
|
2966
|
-
|
|
2967
|
-
|
|
2968
|
-
|
|
2969
|
-
|
|
3522
|
+
<div className="flex min-w-0 items-center gap-1.5">
|
|
3523
|
+
<label className="sr-only" htmlFor={\`\${triggerId}-month\`}>
|
|
3524
|
+
Month
|
|
3525
|
+
</label>
|
|
3526
|
+
<Select
|
|
3527
|
+
value={visibleMonth.getMonth().toString()}
|
|
3528
|
+
onValueChange={(nextMonth) =>
|
|
3529
|
+
updateVisibleMonth(
|
|
3530
|
+
new Date(
|
|
3531
|
+
visibleMonth.getFullYear(),
|
|
3532
|
+
Number(nextMonth),
|
|
3533
|
+
1
|
|
3534
|
+
)
|
|
3535
|
+
)
|
|
3536
|
+
}
|
|
3537
|
+
>
|
|
3538
|
+
<SelectTrigger
|
|
3539
|
+
id={\`\${triggerId}-month\`}
|
|
3540
|
+
aria-label="Month"
|
|
3541
|
+
className={CALENDAR_SELECT_TRIGGER_CLASS}
|
|
3542
|
+
>
|
|
3543
|
+
<SelectValue />
|
|
3544
|
+
</SelectTrigger>
|
|
3545
|
+
<SelectContent
|
|
3546
|
+
data-date-time-picker-calendar-select=""
|
|
3547
|
+
className={CALENDAR_SELECT_CONTENT_CLASS}
|
|
3548
|
+
>
|
|
3549
|
+
{monthNames.map((monthName, monthIndex) => {
|
|
3550
|
+
const optionMonth = new Date(
|
|
3551
|
+
visibleMonth.getFullYear(),
|
|
3552
|
+
monthIndex,
|
|
3553
|
+
1
|
|
3554
|
+
);
|
|
3555
|
+
const isDisabled =
|
|
3556
|
+
(effectiveMinDate &&
|
|
3557
|
+
isMonthBefore(optionMonth, effectiveMinDate)) ||
|
|
3558
|
+
(maxDate && isMonthAfter(optionMonth, maxDate));
|
|
3559
|
+
|
|
3560
|
+
return (
|
|
3561
|
+
<SelectItem
|
|
3562
|
+
key={monthName}
|
|
3563
|
+
value={monthIndex.toString()}
|
|
3564
|
+
disabled={!!isDisabled}
|
|
3565
|
+
>
|
|
3566
|
+
{monthName}
|
|
3567
|
+
</SelectItem>
|
|
3568
|
+
);
|
|
3569
|
+
})}
|
|
3570
|
+
</SelectContent>
|
|
3571
|
+
</Select>
|
|
3572
|
+
<label className="sr-only" htmlFor={\`\${triggerId}-year\`}>
|
|
3573
|
+
Year
|
|
3574
|
+
</label>
|
|
3575
|
+
<Select
|
|
3576
|
+
value={visibleMonth.getFullYear().toString()}
|
|
3577
|
+
onValueChange={(nextYear) =>
|
|
3578
|
+
updateVisibleMonth(
|
|
3579
|
+
new Date(
|
|
3580
|
+
Number(nextYear),
|
|
3581
|
+
visibleMonth.getMonth(),
|
|
3582
|
+
1
|
|
3583
|
+
)
|
|
3584
|
+
)
|
|
3585
|
+
}
|
|
3586
|
+
>
|
|
3587
|
+
<SelectTrigger
|
|
3588
|
+
id={\`\${triggerId}-year\`}
|
|
3589
|
+
aria-label="Year"
|
|
3590
|
+
className={CALENDAR_SELECT_TRIGGER_CLASS}
|
|
3591
|
+
>
|
|
3592
|
+
<SelectValue />
|
|
3593
|
+
</SelectTrigger>
|
|
3594
|
+
<SelectContent
|
|
3595
|
+
data-date-time-picker-calendar-select=""
|
|
3596
|
+
className={CALENDAR_SELECT_CONTENT_CLASS}
|
|
3597
|
+
>
|
|
3598
|
+
{yearOptions.map((year) => (
|
|
3599
|
+
<SelectItem key={year} value={year.toString()}>
|
|
3600
|
+
{year}
|
|
3601
|
+
</SelectItem>
|
|
3602
|
+
))}
|
|
3603
|
+
</SelectContent>
|
|
3604
|
+
</Select>
|
|
3605
|
+
<div id={\`\${triggerId}-calendar-heading\`} className="sr-only">
|
|
3606
|
+
{monthFormatter.format(visibleMonth)}
|
|
3607
|
+
</div>
|
|
2970
3608
|
</div>
|
|
2971
3609
|
<button
|
|
2972
3610
|
type="button"
|
|
2973
3611
|
aria-label="Next month"
|
|
2974
3612
|
className="p-1 rounded hover:bg-semantic-bg-hover text-semantic-text-secondary transition-colors"
|
|
2975
|
-
onClick={() =>
|
|
3613
|
+
onClick={() => updateVisibleMonth(addMonths(visibleMonth, 1))}
|
|
2976
3614
|
>
|
|
2977
3615
|
<ChevronRight className="size-4" aria-hidden="true" />
|
|
2978
3616
|
</button>
|
|
@@ -3123,13 +3761,8 @@ const DateTimePicker = React.forwardRef<HTMLDivElement, DateTimePickerProps>(
|
|
|
3123
3761
|
value={formatHiddenValue(currentValue)}
|
|
3124
3762
|
/>
|
|
3125
3763
|
)}
|
|
3126
|
-
<
|
|
3764
|
+
<div
|
|
3127
3765
|
ref={setTriggerRef}
|
|
3128
|
-
id={triggerId}
|
|
3129
|
-
type="button"
|
|
3130
|
-
disabled={disabled || readOnly}
|
|
3131
|
-
aria-haspopup="dialog"
|
|
3132
|
-
aria-expanded={open}
|
|
3133
3766
|
className={cn(
|
|
3134
3767
|
dateTimePickerTriggerVariants({ size, state }),
|
|
3135
3768
|
open &&
|
|
@@ -3137,32 +3770,49 @@ const DateTimePicker = React.forwardRef<HTMLDivElement, DateTimePickerProps>(
|
|
|
3137
3770
|
"border-semantic-border-input-focus/50 shadow-[0_0_0_1px_rgba(43,188,202,0.15)]",
|
|
3138
3771
|
!displayValue && "text-semantic-text-placeholder"
|
|
3139
3772
|
)}
|
|
3140
|
-
onClick={() => setOpen(!open)}
|
|
3141
3773
|
>
|
|
3142
|
-
<
|
|
3143
|
-
|
|
3144
|
-
|
|
3145
|
-
|
|
3146
|
-
|
|
3147
|
-
|
|
3148
|
-
{
|
|
3149
|
-
|
|
3774
|
+
<input
|
|
3775
|
+
id={triggerId}
|
|
3776
|
+
type="text"
|
|
3777
|
+
disabled={disabled}
|
|
3778
|
+
readOnly={readOnly}
|
|
3779
|
+
value={dateInputValue}
|
|
3780
|
+
placeholder={placeholder}
|
|
3781
|
+
aria-haspopup="dialog"
|
|
3782
|
+
aria-expanded={open}
|
|
3783
|
+
aria-label="Date and time"
|
|
3784
|
+
className="min-w-0 flex-1 bg-transparent text-sm text-semantic-text-primary outline-none placeholder:text-semantic-text-placeholder disabled:cursor-not-allowed read-only:cursor-not-allowed"
|
|
3785
|
+
onFocus={() => {
|
|
3786
|
+
setIsDateInputFocused(true);
|
|
3787
|
+
setOpen(true);
|
|
3788
|
+
}}
|
|
3789
|
+
onClick={() => setOpen(true)}
|
|
3790
|
+
onChange={handleTypedDateChange}
|
|
3791
|
+
onKeyDown={handleTypedDateKeyDown}
|
|
3792
|
+
onBlur={handleTypedDateBlur}
|
|
3793
|
+
/>
|
|
3150
3794
|
{showClear && displayValue && !disabled && !readOnly && (
|
|
3151
|
-
<
|
|
3152
|
-
|
|
3795
|
+
<button
|
|
3796
|
+
type="button"
|
|
3797
|
+
aria-label="Clear date"
|
|
3153
3798
|
className="inline-flex size-5 items-center justify-center rounded text-semantic-text-muted hover:bg-semantic-bg-hover hover:text-semantic-text-primary"
|
|
3154
3799
|
onClick={clearValue}
|
|
3155
3800
|
>
|
|
3156
3801
|
<X className="size-4" aria-hidden="true" />
|
|
3157
|
-
</
|
|
3802
|
+
</button>
|
|
3158
3803
|
)}
|
|
3159
|
-
<
|
|
3160
|
-
|
|
3161
|
-
|
|
3162
|
-
|
|
3163
|
-
|
|
3164
|
-
|
|
3165
|
-
|
|
3804
|
+
<button
|
|
3805
|
+
type="button"
|
|
3806
|
+
disabled={disabled || readOnly}
|
|
3807
|
+
aria-label="Open calendar"
|
|
3808
|
+
className="inline-flex shrink-0 items-center justify-center rounded text-semantic-text-muted hover:bg-semantic-bg-hover hover:text-semantic-text-primary disabled:cursor-not-allowed"
|
|
3809
|
+
onClick={() => setOpen(!open)}
|
|
3810
|
+
>
|
|
3811
|
+
<FigmaCalendarIcon
|
|
3812
|
+
className={cn(size === "sm" ? "size-4" : "size-[18px]")}
|
|
3813
|
+
/>
|
|
3814
|
+
</button>
|
|
3815
|
+
</div>
|
|
3166
3816
|
|
|
3167
3817
|
{popover}
|
|
3168
3818
|
</div>
|
|
@@ -6362,7 +7012,7 @@ const ReplyQuote = React.forwardRef(
|
|
|
6362
7012
|
<div
|
|
6363
7013
|
ref={ref}
|
|
6364
7014
|
className={cn(
|
|
6365
|
-
"tw-w-full tw-min-w-0 tw-bg-[var(--semantic-bg-ui,#F5F5F5)] tw-border-l-[3px] tw-border-solid tw-border-[var(--semantic-border-accent,#27ABB8)] tw-rounded-sm tw-px-4 tw-py-1.5 tw-mb-2 tw-h-[56px] tw-flex tw-flex-col tw-justify-center tw-gap-0 tw-overflow-hidden tw-cursor-pointer hover:tw-bg-[var(--semantic-bg-hover,#D5D7DA)] tw-transition-colors",
|
|
7015
|
+
"tw-max-w-full tw-min-w-0 tw-bg-[var(--semantic-bg-ui,#F5F5F5)] tw-border-l-[3px] tw-border-solid tw-border-[var(--semantic-border-accent,#27ABB8)] tw-rounded-sm tw-px-4 tw-py-1.5 tw-mb-2 tw-h-[56px] tw-flex tw-flex-col tw-justify-center tw-gap-0 tw-overflow-hidden tw-cursor-pointer hover:tw-bg-[var(--semantic-bg-hover,#D5D7DA)] tw-transition-colors",
|
|
6366
7016
|
isInteractive && "focus-visible:tw-ring-2 focus-visible:tw-ring-[var(--semantic-border-focus,#2BBCCA)] focus-visible:tw-ring-offset-1 focus-visible:tw-outline-none",
|
|
6367
7017
|
className
|
|
6368
7018
|
)}
|
|
@@ -6376,9 +7026,9 @@ const ReplyQuote = React.forwardRef(
|
|
|
6376
7026
|
<p className="tw-m-0 tw-min-w-0 tw-shrink-0 tw-truncate tw-text-[14px] tw-font-semibold tw-leading-5 tw-tracking-[0.014px] tw-text-[var(--semantic-text-primary,#181D27)]">
|
|
6377
7027
|
{sender}
|
|
6378
7028
|
</p>
|
|
6379
|
-
<
|
|
7029
|
+
<div className="tw-m-0 tw-min-h-0 tw-min-w-0 tw-flex-1 tw-overflow-hidden tw-text-ellipsis tw-whitespace-nowrap tw-text-[14px] tw-leading-5 tw-text-[var(--semantic-text-muted,#717680)] [&_*]:tw-inline">
|
|
6380
7030
|
{message}
|
|
6381
|
-
</
|
|
7031
|
+
</div>
|
|
6382
7032
|
</div>
|
|
6383
7033
|
);
|
|
6384
7034
|
}
|
|
@@ -6732,6 +7382,7 @@ const SelectField = React.forwardRef(
|
|
|
6732
7382
|
</SelectTrigger>
|
|
6733
7383
|
<SelectContent
|
|
6734
7384
|
onViewportScrollEnd={hasMore !== false ? onScrollEnd : undefined}
|
|
7385
|
+
hideScrollButtons={totalRendered === 0}
|
|
6735
7386
|
>
|
|
6736
7387
|
{/* Search input */}
|
|
6737
7388
|
{searchable && (
|
|
@@ -6988,6 +7639,7 @@ export type SelectContentProps = React.ComponentPropsWithoutRef<
|
|
|
6988
7639
|
* can still read scrollTop/scrollHeight/clientHeight from it.
|
|
6989
7640
|
*/
|
|
6990
7641
|
onViewportScrollEnd?: (event: React.UIEvent<HTMLDivElement>) => void;
|
|
7642
|
+
hideScrollButtons?: boolean;
|
|
6991
7643
|
};
|
|
6992
7644
|
|
|
6993
7645
|
const BOTTOM_THRESHOLD_PX = 24;
|
|
@@ -7000,6 +7652,7 @@ const SelectContent = React.forwardRef(
|
|
|
7000
7652
|
children,
|
|
7001
7653
|
position = "popper",
|
|
7002
7654
|
onViewportScrollEnd,
|
|
7655
|
+
hideScrollButtons,
|
|
7003
7656
|
...props
|
|
7004
7657
|
}: SelectContentProps,
|
|
7005
7658
|
ref: React.Ref<React.ElementRef<typeof SelectPrimitive.Content>>
|
|
@@ -7073,7 +7726,7 @@ const SelectContent = React.forwardRef(
|
|
|
7073
7726
|
position={position}
|
|
7074
7727
|
{...props}
|
|
7075
7728
|
>
|
|
7076
|
-
<SelectScrollUpButton />
|
|
7729
|
+
{!hideScrollButtons && <SelectScrollUpButton />}
|
|
7077
7730
|
<SelectPrimitive.Viewport
|
|
7078
7731
|
ref={setViewport}
|
|
7079
7732
|
data-select-viewport=""
|
|
@@ -7085,7 +7738,7 @@ const SelectContent = React.forwardRef(
|
|
|
7085
7738
|
>
|
|
7086
7739
|
{children}
|
|
7087
7740
|
</SelectPrimitive.Viewport>
|
|
7088
|
-
<SelectScrollDownButton />
|
|
7741
|
+
{!hideScrollButtons && <SelectScrollDownButton />}
|
|
7089
7742
|
</SelectPrimitive.Content>
|
|
7090
7743
|
</SelectPrimitive.Portal>
|
|
7091
7744
|
);
|
package/package.json
CHANGED