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.
Files changed (2) hide show
  1. package/dist/index.js +711 -58
  2. 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 \`\${month}/\${day}/\${year} \${formattedTime}\`;
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<HTMLButtonElement | null>(null);
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
- useFloating<HTMLButtonElement>({
2812
- open,
2813
- placement: CALENDAR_PLACEMENT,
2814
- strategy: floatingStrategy,
2815
- transform: false,
2816
- middleware: floatingMiddleware,
2817
- whileElementsMounted: (reference, floating, update) =>
2818
- autoUpdate(reference, floating, update, { animationFrame: true }),
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: HTMLButtonElement | null) => {
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={() => setVisibleMonth((month) => addMonths(month, -1))}
3518
+ onClick={() => updateVisibleMonth(addMonths(visibleMonth, -1))}
2962
3519
  >
2963
3520
  <ChevronLeft className="size-4" aria-hidden="true" />
2964
3521
  </button>
2965
- <div
2966
- id={\`\${triggerId}-calendar-heading\`}
2967
- className="text-sm font-semibold text-semantic-text-primary"
2968
- >
2969
- {monthFormatter.format(visibleMonth)}
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={() => setVisibleMonth((month) => addMonths(month, 1))}
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
- <button
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
- <span
3143
- className={cn(
3144
- "min-w-0 flex-1 truncate",
3145
- !displayValue && "font-normal"
3146
- )}
3147
- >
3148
- {displayValue || placeholder}
3149
- </span>
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
- <span
3152
- aria-hidden="true"
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
- </span>
3802
+ </button>
3158
3803
  )}
3159
- <FigmaCalendarIcon
3160
- className={cn(
3161
- "shrink-0 text-semantic-text-muted",
3162
- size === "sm" ? "size-4" : "size-[18px]"
3163
- )}
3164
- />
3165
- </button>
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
- <p className="tw-m-0 tw-min-h-0 tw-min-w-0 tw-flex-1 tw-truncate tw-text-[14px] tw-leading-5 tw-text-[var(--semantic-text-muted,#717680)]">
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
- </p>
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "myoperator-mcp",
3
- "version": "0.2.320",
3
+ "version": "0.2.322",
4
4
  "description": "MCP server for myOperator UI components - enables AI assistants to access component metadata, examples, and design tokens",
5
5
  "type": "module",
6
6
  "bin": "./dist/index.js",