myoperator-mcp 0.2.319 → 0.2.321

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 +297 -52
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -2517,12 +2517,7 @@ 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";
2528
2523
 
@@ -2536,8 +2531,15 @@ const MAX_POPOVER_HEIGHT = 420;
2536
2531
  const POPOVER_SCROLL_HEIGHT_VAR = "--date-time-picker-scroll-height";
2537
2532
  const POPOVER_WIDTH_VAR = "--date-time-picker-popover-width";
2538
2533
  const CALENDAR_PLACEMENT: Placement = "bottom-start";
2534
+ const YEAR_RANGE_BEFORE = 100;
2535
+ const YEAR_RANGE_AFTER = 10;
2539
2536
 
2540
2537
  const weekDays = ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"];
2538
+ const monthNames = Array.from({ length: 12 }, (_, monthIndex) =>
2539
+ new Intl.DateTimeFormat("en-US", { month: "short" }).format(
2540
+ new Date(2026, monthIndex, 1)
2541
+ )
2542
+ );
2541
2543
  const monthFormatter = new Intl.DateTimeFormat("en-US", {
2542
2544
  month: "long",
2543
2545
  year: "numeric",
@@ -2650,6 +2652,43 @@ function addMonths(date: Date, months: number) {
2650
2652
  return new Date(date.getFullYear(), date.getMonth() + months, 1);
2651
2653
  }
2652
2654
 
2655
+ function getMonthKey(date: Date) {
2656
+ return date.getFullYear() * 12 + date.getMonth();
2657
+ }
2658
+
2659
+ function isMonthBefore(date: Date, minDate: Date) {
2660
+ return getMonthKey(date) < getMonthKey(minDate);
2661
+ }
2662
+
2663
+ function isMonthAfter(date: Date, maxDate: Date) {
2664
+ return getMonthKey(date) > getMonthKey(maxDate);
2665
+ }
2666
+
2667
+ function clampMonth(date: Date, minDate?: Date, maxDate?: Date) {
2668
+ if (minDate && isMonthBefore(date, minDate)) return startOfMonth(minDate);
2669
+ if (maxDate && isMonthAfter(date, maxDate)) return startOfMonth(maxDate);
2670
+
2671
+ return startOfMonth(date);
2672
+ }
2673
+
2674
+ function getYearOptions(visibleMonth: Date, minDate?: Date, maxDate?: Date) {
2675
+ const currentYear = new Date().getFullYear();
2676
+ const selectedYear = visibleMonth.getFullYear();
2677
+ const startYear = Math.min(
2678
+ minDate?.getFullYear() ?? currentYear - YEAR_RANGE_BEFORE,
2679
+ selectedYear
2680
+ );
2681
+ const endYear = Math.max(
2682
+ maxDate?.getFullYear() ?? currentYear + YEAR_RANGE_AFTER,
2683
+ selectedYear
2684
+ );
2685
+
2686
+ return Array.from(
2687
+ { length: endYear - startYear + 1 },
2688
+ (_, index) => startYear + index
2689
+ );
2690
+ }
2691
+
2653
2692
  function getCalendarDays(month: Date) {
2654
2693
  const firstDay = startOfMonth(month);
2655
2694
  const gridStart = new Date(firstDay);
@@ -2697,7 +2736,82 @@ function formatDateForDisplay(date?: Date, time?: string) {
2697
2736
  const year = date.getFullYear();
2698
2737
  const formattedTime = formatTimeForDisplay(time ?? DEFAULT_START_TIME);
2699
2738
 
2700
- return \`\${month}/\${day}/\${year} \${formattedTime}\`;
2739
+ return \`\${day}/\${month}/\${year} \${formattedTime}\`;
2740
+ }
2741
+
2742
+ function parseDatePart(datePart: string) {
2743
+ const isoMatch = datePart.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})$/);
2744
+ const dayFirstMatch = datePart.match(/^(\\d{1,2})[/-](\\d{1,2})[/-](\\d{4})$/);
2745
+ const [, yearValue, isoMonthValue, isoDayValue] = isoMatch ?? [];
2746
+ const [, dayValue, monthValue, dayFirstYearValue] = dayFirstMatch ?? [];
2747
+
2748
+ const year = Number(yearValue ?? dayFirstYearValue);
2749
+ const month = Number(isoMonthValue ?? monthValue);
2750
+ const day = Number(isoDayValue ?? dayValue);
2751
+
2752
+ if (!year || !month || !day) return null;
2753
+
2754
+ const parsedDate = new Date(year, month - 1, day);
2755
+ if (
2756
+ parsedDate.getFullYear() !== year ||
2757
+ parsedDate.getMonth() !== month - 1 ||
2758
+ parsedDate.getDate() !== day
2759
+ ) {
2760
+ return null;
2761
+ }
2762
+
2763
+ return parsedDate;
2764
+ }
2765
+
2766
+ function parseTimePart(timePart?: string) {
2767
+ if (!timePart) return undefined;
2768
+
2769
+ const timeMatch = timePart
2770
+ .trim()
2771
+ .match(/^(\\d{1,2}):(\\d{2})(?::(\\d{2}))?\\s*(AM|PM)?$/i);
2772
+ if (!timeMatch) return null;
2773
+
2774
+ const [, hourValue, minuteValue, secondValue = "00", meridiem] = timeMatch;
2775
+ let hour = Number(hourValue);
2776
+ const minute = Number(minuteValue);
2777
+ const second = Number(secondValue);
2778
+
2779
+ if (
2780
+ minute > 59 ||
2781
+ second > 59 ||
2782
+ (meridiem ? hour < 1 || hour > 12 : hour > 23)
2783
+ ) {
2784
+ return null;
2785
+ }
2786
+
2787
+ if (meridiem) {
2788
+ const normalizedMeridiem = meridiem.toUpperCase();
2789
+ if (normalizedMeridiem === "AM") {
2790
+ hour = hour === 12 ? 0 : hour;
2791
+ } else {
2792
+ hour = hour === 12 ? 12 : hour + 12;
2793
+ }
2794
+ }
2795
+
2796
+ return \`\${hour.toString().padStart(2, "0")}:\${minuteValue}:\${secondValue}\`;
2797
+ }
2798
+
2799
+ function parseTypedDateTime(value: string) {
2800
+ const typedValue = value.trim();
2801
+ if (!typedValue) return undefined;
2802
+
2803
+ const typedMatch = typedValue.match(
2804
+ /^(\\d{4}-\\d{1,2}-\\d{1,2}|\\d{1,2}[/-]\\d{1,2}[/-]\\d{4})(?:\\s+(.+))?$/
2805
+ );
2806
+ if (!typedMatch) return null;
2807
+
2808
+ const [, datePart, timePart] = typedMatch;
2809
+ const date = parseDatePart(datePart);
2810
+ const startTime = parseTimePart(timePart);
2811
+
2812
+ if (!date || startTime === null) return null;
2813
+
2814
+ return { date, startTime };
2701
2815
  }
2702
2816
 
2703
2817
  function formatHiddenValue(value: DateTimePickerValue) {
@@ -2775,8 +2889,12 @@ const DateTimePicker = React.forwardRef<HTMLDivElement, DateTimePickerProps>(
2775
2889
  const [visibleMonth, setVisibleMonth] = React.useState(() =>
2776
2890
  startOfMonth(currentValue.date ?? new Date())
2777
2891
  );
2892
+ const [dateInputValue, setDateInputValue] = React.useState(() =>
2893
+ formatDateForDisplay(currentValue.date, currentValue.startTime)
2894
+ );
2895
+ const [isDateInputFocused, setIsDateInputFocused] = React.useState(false);
2778
2896
  const rootRef = React.useRef<HTMLDivElement | null>(null);
2779
- const triggerRef = React.useRef<HTMLButtonElement | null>(null);
2897
+ const triggerRef = React.useRef<HTMLDivElement | null>(null);
2780
2898
  const popoverRef = React.useRef<HTMLDivElement | null>(null);
2781
2899
  const usesContainerPortal = portalContainer !== undefined;
2782
2900
  const floatingStrategy: Strategy = usesContainerPortal
@@ -2807,16 +2925,15 @@ const DateTimePicker = React.forwardRef<HTMLDivElement, DateTimePickerProps>(
2807
2925
  ],
2808
2926
  []
2809
2927
  );
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
- });
2928
+ const { refs, floatingStyles, isPositioned } = useFloating<HTMLDivElement>({
2929
+ open,
2930
+ placement: CALENDAR_PLACEMENT,
2931
+ strategy: floatingStrategy,
2932
+ transform: false,
2933
+ middleware: floatingMiddleware,
2934
+ whileElementsMounted: (reference, floating, update) =>
2935
+ autoUpdate(reference, floating, update, { animationFrame: true }),
2936
+ });
2820
2937
  const calendarDays = React.useMemo(
2821
2938
  () => getCalendarDays(visibleMonth),
2822
2939
  [visibleMonth]
@@ -2852,7 +2969,7 @@ const DateTimePicker = React.forwardRef<HTMLDivElement, DateTimePickerProps>(
2852
2969
  );
2853
2970
 
2854
2971
  const setTriggerRef = React.useCallback(
2855
- (node: HTMLButtonElement | null) => {
2972
+ (node: HTMLDivElement | null) => {
2856
2973
  triggerRef.current = node;
2857
2974
  refs.setReference(node);
2858
2975
  },
@@ -2873,6 +2990,12 @@ const DateTimePicker = React.forwardRef<HTMLDivElement, DateTimePickerProps>(
2873
2990
  }
2874
2991
  }, [currentValue.date]);
2875
2992
 
2993
+ React.useEffect(() => {
2994
+ if (!isDateInputFocused) {
2995
+ setDateInputValue(displayValue);
2996
+ }
2997
+ }, [displayValue, isDateInputFocused]);
2998
+
2876
2999
  React.useEffect(() => {
2877
3000
  if (!open) return;
2878
3001
 
@@ -2915,6 +3038,7 @@ const DateTimePicker = React.forwardRef<HTMLDivElement, DateTimePickerProps>(
2915
3038
  event.stopPropagation();
2916
3039
  if (readOnly) return;
2917
3040
 
3041
+ setDateInputValue("");
2918
3042
  updateValue({
2919
3043
  date: undefined,
2920
3044
  startTime: currentValue.startTime,
@@ -2922,6 +3046,53 @@ const DateTimePicker = React.forwardRef<HTMLDivElement, DateTimePickerProps>(
2922
3046
  });
2923
3047
  };
2924
3048
 
3049
+ const yearOptions = React.useMemo(
3050
+ () => getYearOptions(visibleMonth, effectiveMinDate, maxDate),
3051
+ [effectiveMinDate, maxDate, visibleMonth]
3052
+ );
3053
+
3054
+ const updateVisibleMonth = React.useCallback(
3055
+ (nextMonth: Date) => {
3056
+ setVisibleMonth(clampMonth(nextMonth, effectiveMinDate, maxDate));
3057
+ },
3058
+ [effectiveMinDate, maxDate]
3059
+ );
3060
+
3061
+ const handleTypedDateChange = (
3062
+ event: React.ChangeEvent<HTMLInputElement>
3063
+ ) => {
3064
+ const nextInputValue = event.target.value;
3065
+ setDateInputValue(nextInputValue);
3066
+
3067
+ const typedDateTime = parseTypedDateTime(nextInputValue);
3068
+ if (typedDateTime === undefined) {
3069
+ updateValue({ ...currentValue, date: undefined });
3070
+ return;
3071
+ }
3072
+
3073
+ if (
3074
+ typedDateTime &&
3075
+ !(
3076
+ effectiveMinDate && isBeforeDay(typedDateTime.date, effectiveMinDate)
3077
+ ) &&
3078
+ !(maxDate && isAfterDay(typedDateTime.date, maxDate))
3079
+ ) {
3080
+ updateValue({
3081
+ ...currentValue,
3082
+ date: typedDateTime.date,
3083
+ startTime: typedDateTime.startTime ?? currentValue.startTime,
3084
+ });
3085
+ updateVisibleMonth(typedDateTime.date);
3086
+ }
3087
+ };
3088
+
3089
+ const handleTypedDateBlur = () => {
3090
+ setIsDateInputFocused(false);
3091
+ setDateInputValue(
3092
+ formatDateForDisplay(currentValue.date, currentValue.startTime)
3093
+ );
3094
+ };
3095
+
2925
3096
  const popover =
2926
3097
  open &&
2927
3098
  !disabled &&
@@ -2953,26 +3124,89 @@ const DateTimePicker = React.forwardRef<HTMLDivElement, DateTimePickerProps>(
2953
3124
  onTouchMove={(event) => event.stopPropagation()}
2954
3125
  >
2955
3126
  <div className="p-3 touch-pan-y">
2956
- <div className="mb-3 flex items-center justify-between">
3127
+ <div className="mb-3 flex items-center justify-between gap-2">
2957
3128
  <button
2958
3129
  type="button"
2959
3130
  aria-label="Previous month"
2960
3131
  className="p-1 rounded hover:bg-semantic-bg-hover text-semantic-text-secondary transition-colors"
2961
- onClick={() => setVisibleMonth((month) => addMonths(month, -1))}
3132
+ onClick={() => updateVisibleMonth(addMonths(visibleMonth, -1))}
2962
3133
  >
2963
3134
  <ChevronLeft className="size-4" aria-hidden="true" />
2964
3135
  </button>
2965
- <div
2966
- id={\`\${triggerId}-calendar-heading\`}
2967
- className="text-sm font-semibold text-semantic-text-primary"
2968
- >
2969
- {monthFormatter.format(visibleMonth)}
3136
+ <div className="flex min-w-0 items-center gap-2">
3137
+ <label className="sr-only" htmlFor={\`\${triggerId}-month\`}>
3138
+ Month
3139
+ </label>
3140
+ <select
3141
+ id={\`\${triggerId}-month\`}
3142
+ aria-label="Month"
3143
+ value={visibleMonth.getMonth()}
3144
+ className="h-8 rounded-md border border-solid border-semantic-border-input bg-semantic-bg-primary px-2 text-sm font-medium text-semantic-text-primary outline-none transition-colors hover:border-semantic-border-input-focus/50 focus:border-semantic-border-input-focus/50"
3145
+ onChange={(event) =>
3146
+ updateVisibleMonth(
3147
+ new Date(
3148
+ visibleMonth.getFullYear(),
3149
+ Number(event.target.value),
3150
+ 1
3151
+ )
3152
+ )
3153
+ }
3154
+ >
3155
+ {monthNames.map((monthName, monthIndex) => {
3156
+ const optionMonth = new Date(
3157
+ visibleMonth.getFullYear(),
3158
+ monthIndex,
3159
+ 1
3160
+ );
3161
+ const isDisabled =
3162
+ (effectiveMinDate &&
3163
+ isMonthBefore(optionMonth, effectiveMinDate)) ||
3164
+ (maxDate && isMonthAfter(optionMonth, maxDate));
3165
+
3166
+ return (
3167
+ <option
3168
+ key={monthName}
3169
+ value={monthIndex}
3170
+ disabled={!!isDisabled}
3171
+ >
3172
+ {monthName}
3173
+ </option>
3174
+ );
3175
+ })}
3176
+ </select>
3177
+ <label className="sr-only" htmlFor={\`\${triggerId}-year\`}>
3178
+ Year
3179
+ </label>
3180
+ <select
3181
+ id={\`\${triggerId}-year\`}
3182
+ aria-label="Year"
3183
+ value={visibleMonth.getFullYear()}
3184
+ className="h-8 rounded-md border border-solid border-semantic-border-input bg-semantic-bg-primary px-2 text-sm font-medium text-semantic-text-primary outline-none transition-colors hover:border-semantic-border-input-focus/50 focus:border-semantic-border-input-focus/50"
3185
+ onChange={(event) =>
3186
+ updateVisibleMonth(
3187
+ new Date(
3188
+ Number(event.target.value),
3189
+ visibleMonth.getMonth(),
3190
+ 1
3191
+ )
3192
+ )
3193
+ }
3194
+ >
3195
+ {yearOptions.map((year) => (
3196
+ <option key={year} value={year}>
3197
+ {year}
3198
+ </option>
3199
+ ))}
3200
+ </select>
3201
+ <div id={\`\${triggerId}-calendar-heading\`} className="sr-only">
3202
+ {monthFormatter.format(visibleMonth)}
3203
+ </div>
2970
3204
  </div>
2971
3205
  <button
2972
3206
  type="button"
2973
3207
  aria-label="Next month"
2974
3208
  className="p-1 rounded hover:bg-semantic-bg-hover text-semantic-text-secondary transition-colors"
2975
- onClick={() => setVisibleMonth((month) => addMonths(month, 1))}
3209
+ onClick={() => updateVisibleMonth(addMonths(visibleMonth, 1))}
2976
3210
  >
2977
3211
  <ChevronRight className="size-4" aria-hidden="true" />
2978
3212
  </button>
@@ -3123,13 +3357,8 @@ const DateTimePicker = React.forwardRef<HTMLDivElement, DateTimePickerProps>(
3123
3357
  value={formatHiddenValue(currentValue)}
3124
3358
  />
3125
3359
  )}
3126
- <button
3360
+ <div
3127
3361
  ref={setTriggerRef}
3128
- id={triggerId}
3129
- type="button"
3130
- disabled={disabled || readOnly}
3131
- aria-haspopup="dialog"
3132
- aria-expanded={open}
3133
3362
  className={cn(
3134
3363
  dateTimePickerTriggerVariants({ size, state }),
3135
3364
  open &&
@@ -3137,32 +3366,48 @@ const DateTimePicker = React.forwardRef<HTMLDivElement, DateTimePickerProps>(
3137
3366
  "border-semantic-border-input-focus/50 shadow-[0_0_0_1px_rgba(43,188,202,0.15)]",
3138
3367
  !displayValue && "text-semantic-text-placeholder"
3139
3368
  )}
3140
- onClick={() => setOpen(!open)}
3141
3369
  >
3142
- <span
3143
- className={cn(
3144
- "min-w-0 flex-1 truncate",
3145
- !displayValue && "font-normal"
3146
- )}
3147
- >
3148
- {displayValue || placeholder}
3149
- </span>
3370
+ <input
3371
+ id={triggerId}
3372
+ type="text"
3373
+ disabled={disabled}
3374
+ readOnly={readOnly}
3375
+ value={dateInputValue}
3376
+ placeholder={placeholder}
3377
+ aria-haspopup="dialog"
3378
+ aria-expanded={open}
3379
+ aria-label="Date and time"
3380
+ 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"
3381
+ onFocus={() => {
3382
+ setIsDateInputFocused(true);
3383
+ setOpen(true);
3384
+ }}
3385
+ onClick={() => setOpen(true)}
3386
+ onChange={handleTypedDateChange}
3387
+ onBlur={handleTypedDateBlur}
3388
+ />
3150
3389
  {showClear && displayValue && !disabled && !readOnly && (
3151
- <span
3152
- aria-hidden="true"
3390
+ <button
3391
+ type="button"
3392
+ aria-label="Clear date"
3153
3393
  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
3394
  onClick={clearValue}
3155
3395
  >
3156
3396
  <X className="size-4" aria-hidden="true" />
3157
- </span>
3397
+ </button>
3158
3398
  )}
3159
- <FigmaCalendarIcon
3160
- className={cn(
3161
- "shrink-0 text-semantic-text-muted",
3162
- size === "sm" ? "size-4" : "size-[18px]"
3163
- )}
3164
- />
3165
- </button>
3399
+ <button
3400
+ type="button"
3401
+ disabled={disabled || readOnly}
3402
+ aria-label="Open calendar"
3403
+ 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"
3404
+ onClick={() => setOpen(!open)}
3405
+ >
3406
+ <FigmaCalendarIcon
3407
+ className={cn(size === "sm" ? "size-4" : "size-[18px]")}
3408
+ />
3409
+ </button>
3410
+ </div>
3166
3411
 
3167
3412
  {popover}
3168
3413
  </div>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "myoperator-mcp",
3
- "version": "0.2.319",
3
+ "version": "0.2.321",
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",