myoperator-mcp 0.2.381 → 0.2.383

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 +290 -129
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -2736,8 +2736,44 @@ function getCalendarDays(month: Date) {
2736
2736
  });
2737
2737
  }
2738
2738
 
2739
+ function isMonthSelectable(
2740
+ year: number,
2741
+ monthIndex: number,
2742
+ minDate?: Date,
2743
+ maxDate?: Date
2744
+ ) {
2745
+ const firstDay = new Date(year, monthIndex, 1);
2746
+ const lastDay = new Date(year, monthIndex + 1, 0);
2747
+
2748
+ if (minDate && isBeforeDay(lastDay, minDate)) return false;
2749
+ if (maxDate && isAfterDay(firstDay, maxDate)) return false;
2750
+
2751
+ return true;
2752
+ }
2753
+
2754
+ function clampRange(
2755
+ range: DateRangeValue,
2756
+ minDate?: Date,
2757
+ maxDate?: Date
2758
+ ): DateRangeValue | null {
2759
+ let { start, end } = range;
2760
+
2761
+ // The window and the preset don't overlap at all \u2014 clamping would collapse
2762
+ // the range onto the boundary day, which is not what the preset means.
2763
+ if (end && minDate && isBeforeDay(end, minDate)) return null;
2764
+ if (start && maxDate && isAfterDay(start, maxDate)) return null;
2765
+
2766
+ if (start && minDate && isBeforeDay(start, minDate)) start = minDate;
2767
+ if (end && maxDate && isAfterDay(end, maxDate)) end = maxDate;
2768
+
2769
+ // The whole preset sits outside the allowed window \u2014 nothing to select.
2770
+ if (start && end && isAfterDay(start, end)) return null;
2771
+
2772
+ return { start, end };
2773
+ }
2774
+
2739
2775
  function isPointerInsideElement(
2740
- event: MouseEvent,
2776
+ event: MouseEvent | PointerEvent,
2741
2777
  element: HTMLElement | null
2742
2778
  ) {
2743
2779
  if (!element) return false;
@@ -2886,6 +2922,11 @@ const DateRangePicker = React.forwardRef<HTMLDivElement, DateRangePickerProps>(
2886
2922
  // start a new one (false). Drives the "click 1 = start, click 2 = end,
2887
2923
  // click 3 = restart" cycle.
2888
2924
  const [pendingEnd, setPendingEnd] = React.useState(false);
2925
+ // Month and year share one slot so opening either closes the other \u2014
2926
+ // two independent uncontrolled menus can otherwise sit open at once.
2927
+ const [openMenu, setOpenMenu] = React.useState<"month" | "year" | null>(
2928
+ null
2929
+ );
2889
2930
  const [visibleMonth, setVisibleMonth] = React.useState(() =>
2890
2931
  startOfMonth(currentValue.start ?? currentValue.end ?? new Date())
2891
2932
  );
@@ -2941,6 +2982,21 @@ const DateRangePicker = React.forwardRef<HTMLDivElement, DateRangePickerProps>(
2941
2982
 
2942
2983
  return isBeforeDay(minDate, today) ? today : minDate;
2943
2984
  }, [disablePastDates, minDate]);
2985
+ // Year list is centred on the visible year but never offers a year that
2986
+ // has no selectable day in it.
2987
+ const yearOptions = React.useMemo(() => {
2988
+ const centre = visibleMonth.getFullYear();
2989
+ const all = Array.from(
2990
+ { length: YEAR_OPTIONS_SPAN * 2 + 1 },
2991
+ (_, index) => centre - YEAR_OPTIONS_SPAN + index
2992
+ );
2993
+
2994
+ return all.filter(
2995
+ (year) =>
2996
+ !(effectiveMinDate && year < effectiveMinDate.getFullYear()) &&
2997
+ !(maxDate && year > maxDate.getFullYear())
2998
+ );
2999
+ }, [visibleMonth, effectiveMinDate, maxDate]);
2944
3000
  const portalMount =
2945
3001
  typeof document !== "undefined"
2946
3002
  ? usesContainerPortal
@@ -2989,6 +3045,7 @@ const DateRangePicker = React.forwardRef<HTMLDivElement, DateRangePickerProps>(
2989
3045
  const latestValue = currentValueRef.current;
2990
3046
  setDraftValue(latestValue);
2991
3047
  setPendingEnd(false);
3048
+ setOpenMenu(null);
2992
3049
  setVisibleMonth(
2993
3050
  startOfMonth(latestValue.start ?? latestValue.end ?? new Date())
2994
3051
  );
@@ -2997,7 +3054,7 @@ const DateRangePicker = React.forwardRef<HTMLDivElement, DateRangePickerProps>(
2997
3054
  React.useEffect(() => {
2998
3055
  if (!open) return;
2999
3056
 
3000
- const handlePointerDown = (event: MouseEvent) => {
3057
+ const handlePointerDown = (event: PointerEvent) => {
3001
3058
  if (
3002
3059
  !isPointerInsideElement(event, rootRef.current) &&
3003
3060
  !isPointerInsideElement(event, popoverRef.current)
@@ -3009,14 +3066,17 @@ const DateRangePicker = React.forwardRef<HTMLDivElement, DateRangePickerProps>(
3009
3066
  const handleKeyDown = (event: KeyboardEvent) => {
3010
3067
  if (event.key === "Escape") {
3011
3068
  setOpen(false);
3069
+ // Escape must hand focus back to the trigger, otherwise focus is
3070
+ // stranded on a node that is about to unmount.
3071
+ triggerRef.current?.focus();
3012
3072
  }
3013
3073
  };
3014
3074
 
3015
- document.addEventListener("mousedown", handlePointerDown);
3075
+ document.addEventListener("pointerdown", handlePointerDown);
3016
3076
  document.addEventListener("keydown", handleKeyDown);
3017
3077
 
3018
3078
  return () => {
3019
- document.removeEventListener("mousedown", handlePointerDown);
3079
+ document.removeEventListener("pointerdown", handlePointerDown);
3020
3080
  document.removeEventListener("keydown", handleKeyDown);
3021
3081
  };
3022
3082
  }, [open, setOpen]);
@@ -3032,10 +3092,21 @@ const DateRangePicker = React.forwardRef<HTMLDivElement, DateRangePickerProps>(
3032
3092
  [isValueControlled, onValueChange]
3033
3093
  );
3034
3094
 
3095
+ // Radix dismisses the open menu on the same pointerdown that would toggle
3096
+ // the sibling trigger, so its own toggle is swallowed and switching menus
3097
+ // takes two clicks. Drive the slot from the trigger directly and
3098
+ // preventDefault so Radix's toggle stays out of it.
3099
+ const toggleCalendarMenu = (menu: "month" | "year") => {
3100
+ setOpenMenu((current) => (current === menu ? null : menu));
3101
+ };
3102
+
3035
3103
  const handlePresetClick = (preset: DateRangePreset) => {
3036
- const range = preset.getRange();
3104
+ const range = clampRange(preset.getRange(), effectiveMinDate, maxDate);
3105
+ if (!range) return;
3106
+
3037
3107
  commitValue(range);
3038
3108
  setOpen(false);
3109
+ triggerRef.current?.focus();
3039
3110
  };
3040
3111
 
3041
3112
  const handleDayClick = (day: Date) => {
@@ -3056,6 +3127,7 @@ const DateRangePicker = React.forwardRef<HTMLDivElement, DateRangePickerProps>(
3056
3127
  setPendingEnd(false);
3057
3128
  commitValue(nextValue);
3058
3129
  setOpen(false);
3130
+ triggerRef.current?.focus();
3059
3131
  };
3060
3132
 
3061
3133
  const popover =
@@ -3087,23 +3159,34 @@ const DateRangePicker = React.forwardRef<HTMLDivElement, DateRangePickerProps>(
3087
3159
  onWheel={(event) => event.stopPropagation()}
3088
3160
  onTouchMove={(event) => event.stopPropagation()}
3089
3161
  >
3090
- <div className="flex flex-row">
3162
+ <div className="flex flex-col sm:flex-row">
3091
3163
  {presets.length > 0 && (
3092
- <div className="flex w-36 shrink-0 flex-col gap-0.5 border-r border-solid border-semantic-border-layout p-3">
3093
- {presets.map((preset) => (
3094
- <button
3095
- key={preset.label}
3096
- type="button"
3097
- className="w-full rounded px-2 py-1.5 text-left text-sm text-semantic-text-primary transition-colors hover:bg-semantic-bg-hover"
3098
- onClick={() => handlePresetClick(preset)}
3099
- >
3100
- {preset.label}
3101
- </button>
3102
- ))}
3164
+ <div className="flex shrink-0 gap-0.5 border-solid border-semantic-border-layout p-3 max-sm:w-full max-sm:flex-row max-sm:overflow-x-auto max-sm:border-b sm:w-36 sm:flex-col sm:border-r">
3165
+ {presets.map((preset) => {
3166
+ const presetDisabled =
3167
+ clampRange(preset.getRange(), effectiveMinDate, maxDate) ===
3168
+ null;
3169
+
3170
+ return (
3171
+ <button
3172
+ key={preset.label}
3173
+ type="button"
3174
+ disabled={presetDisabled}
3175
+ className={cn(
3176
+ "shrink-0 whitespace-nowrap rounded px-2 py-2 text-left text-sm text-semantic-text-primary transition-colors hover:bg-semantic-bg-hover max-sm:border max-sm:border-solid max-sm:border-semantic-border-layout sm:w-full sm:py-1.5",
3177
+ presetDisabled &&
3178
+ "cursor-not-allowed opacity-40 hover:bg-transparent"
3179
+ )}
3180
+ onClick={() => handlePresetClick(preset)}
3181
+ >
3182
+ {preset.label}
3183
+ </button>
3184
+ );
3185
+ })}
3103
3186
  </div>
3104
3187
  )}
3105
3188
 
3106
- <div className="min-w-[272px] flex-1 p-3 touch-pan-y">
3189
+ <div className="min-w-0 flex-1 p-3 touch-pan-y sm:min-w-[272px]">
3107
3190
  <div className="mb-3 flex items-center justify-between gap-2">
3108
3191
  <button
3109
3192
  type="button"
@@ -3120,10 +3203,25 @@ const DateRangePicker = React.forwardRef<HTMLDivElement, DateRangePickerProps>(
3120
3203
  id={\`\${triggerId}-calendar-heading\`}
3121
3204
  className="flex items-center gap-2"
3122
3205
  >
3123
- <DropdownMenu>
3206
+ <DropdownMenu
3207
+ modal={false}
3208
+ open={openMenu === "month"}
3209
+ onOpenChange={(nextOpen) =>
3210
+ setOpenMenu((current) => {
3211
+ if (nextOpen) return "month";
3212
+ // Clicking the other trigger dismisses this menu
3213
+ // *after* the other one has opened \u2014 only clear the
3214
+ // slot if it is still ours.
3215
+ return current === "month" ? null : current;
3216
+ })
3217
+ }
3218
+ >
3124
3219
  <DropdownMenuTrigger asChild>
3125
3220
  <button
3126
3221
  type="button"
3222
+ data-calendar-menu="month"
3223
+ onPointerDown={(event) => event.preventDefault()}
3224
+ onClick={() => toggleCalendarMenu("month")}
3127
3225
  className="rounded border border-solid border-semantic-border-layout px-3 py-1.5 text-sm font-semibold text-semantic-text-primary transition-colors hover:bg-semantic-bg-hover"
3128
3226
  >
3129
3227
  {MONTH_LONG_NAMES[visibleMonth.getMonth()]}
@@ -3131,11 +3229,23 @@ const DateRangePicker = React.forwardRef<HTMLDivElement, DateRangePickerProps>(
3131
3229
  </DropdownMenuTrigger>
3132
3230
  <DropdownMenuContent
3133
3231
  align="start"
3232
+ // Closing must not pull focus back to the trigger: the
3233
+ // sibling menu opening in the same click would then see
3234
+ // focus land outside itself and dismiss immediately.
3235
+ onCloseAutoFocus={(event) => event.preventDefault()}
3134
3236
  className={cn(CALENDAR_DROPDOWN_Z_INDEX, "max-h-[240px] overflow-y-auto")}
3135
3237
  >
3136
3238
  {MONTH_LONG_NAMES.map((label, monthIndex) => (
3137
3239
  <DropdownMenuItem
3138
3240
  key={label}
3241
+ disabled={
3242
+ !isMonthSelectable(
3243
+ visibleMonth.getFullYear(),
3244
+ monthIndex,
3245
+ effectiveMinDate,
3246
+ maxDate
3247
+ )
3248
+ }
3139
3249
  onSelect={() =>
3140
3250
  setVisibleMonth(
3141
3251
  startOfMonth(
@@ -3150,10 +3260,25 @@ const DateRangePicker = React.forwardRef<HTMLDivElement, DateRangePickerProps>(
3150
3260
  </DropdownMenuContent>
3151
3261
  </DropdownMenu>
3152
3262
 
3153
- <DropdownMenu>
3263
+ <DropdownMenu
3264
+ modal={false}
3265
+ open={openMenu === "year"}
3266
+ onOpenChange={(nextOpen) =>
3267
+ setOpenMenu((current) => {
3268
+ if (nextOpen) return "year";
3269
+ // Clicking the other trigger dismisses this menu
3270
+ // *after* the other one has opened \u2014 only clear the
3271
+ // slot if it is still ours.
3272
+ return current === "year" ? null : current;
3273
+ })
3274
+ }
3275
+ >
3154
3276
  <DropdownMenuTrigger asChild>
3155
3277
  <button
3156
3278
  type="button"
3279
+ data-calendar-menu="year"
3280
+ onPointerDown={(event) => event.preventDefault()}
3281
+ onClick={() => toggleCalendarMenu("year")}
3157
3282
  className="flex items-center gap-1 rounded border border-solid border-semantic-border-layout px-3 py-1.5 text-sm font-semibold text-semantic-text-primary transition-colors hover:bg-semantic-bg-hover"
3158
3283
  >
3159
3284
  {visibleMonth.getFullYear()}
@@ -3165,12 +3290,13 @@ const DateRangePicker = React.forwardRef<HTMLDivElement, DateRangePickerProps>(
3165
3290
  </DropdownMenuTrigger>
3166
3291
  <DropdownMenuContent
3167
3292
  align="start"
3293
+ // Closing must not pull focus back to the trigger: the
3294
+ // sibling menu opening in the same click would then see
3295
+ // focus land outside itself and dismiss immediately.
3296
+ onCloseAutoFocus={(event) => event.preventDefault()}
3168
3297
  className={cn(CALENDAR_DROPDOWN_Z_INDEX, "max-h-[240px] overflow-y-auto")}
3169
3298
  >
3170
- {Array.from(
3171
- { length: YEAR_OPTIONS_SPAN * 2 + 1 },
3172
- (_, index) => visibleMonth.getFullYear() - YEAR_OPTIONS_SPAN + index
3173
- ).map((year) => (
3299
+ {yearOptions.map((year) => (
3174
3300
  <DropdownMenuItem
3175
3301
  key={year}
3176
3302
  onSelect={() =>
@@ -3198,11 +3324,11 @@ const DateRangePicker = React.forwardRef<HTMLDivElement, DateRangePickerProps>(
3198
3324
  </button>
3199
3325
  </div>
3200
3326
 
3201
- <div className="grid grid-cols-7">
3327
+ <div className="grid w-full grid-cols-7">
3202
3328
  {weekDays.map((day) => (
3203
3329
  <div
3204
3330
  key={day}
3205
- className="flex h-8 items-center justify-center text-xs font-medium text-semantic-text-muted"
3331
+ className="flex h-9 items-center justify-center text-xs font-medium text-semantic-text-muted sm:h-8"
3206
3332
  >
3207
3333
  {day}
3208
3334
  </div>
@@ -3238,7 +3364,7 @@ const DateRangePicker = React.forwardRef<HTMLDivElement, DateRangePickerProps>(
3238
3364
  <div
3239
3365
  key={day.toISOString()}
3240
3366
  className={cn(
3241
- "relative flex h-8 items-center justify-center",
3367
+ "relative flex h-9 items-center justify-center sm:h-8",
3242
3368
  isInBand && "bg-semantic-info-surface",
3243
3369
  isRangeStart && hasRange && "rounded-l-full",
3244
3370
  isRangeEnd && hasRange && "rounded-r-full"
@@ -3251,7 +3377,7 @@ const DateRangePicker = React.forwardRef<HTMLDivElement, DateRangePickerProps>(
3251
3377
  aria-current={isToday ? "date" : undefined}
3252
3378
  disabled={!!isDisabled}
3253
3379
  className={cn(
3254
- "relative flex size-8 items-center justify-center rounded-full text-xs transition-colors",
3380
+ "relative flex h-9 w-full max-w-9 items-center justify-center rounded-full text-xs transition-colors sm:h-8 sm:max-w-8",
3255
3381
  isSelectedEdge
3256
3382
  ? "bg-semantic-primary text-semantic-text-inverted font-semibold"
3257
3383
  : isCurrentMonth
@@ -3347,12 +3473,23 @@ import {
3347
3473
  type Strategy,
3348
3474
  } from "@floating-ui/react-dom";
3349
3475
  import { cva, type VariantProps } from "class-variance-authority";
3350
- import { ChevronDown, ChevronLeft, ChevronRight, Clock2, X } from "lucide-react";
3476
+ import {
3477
+ ChevronDown,
3478
+ ChevronLeft,
3479
+ ChevronRight,
3480
+ Clock2,
3481
+ X,
3482
+ } from "lucide-react";
3351
3483
 
3352
3484
  import { cn } from "@/lib/utils";
3353
3485
 
3354
3486
  const DEFAULT_START_TIME = "10:30:00";
3355
- const DEFAULT_END_TIME = "12:30:00";
3487
+ /**
3488
+ * What the time columns show while no time is selected. Clearing the value
3489
+ * returns the picker to this state, so an unset time always reads 12:00 AM
3490
+ * rather than re-proposing the sample default.
3491
+ */
3492
+ const UNSET_TIME = "00:00:00";
3356
3493
  const DEFAULT_MINUTE_STEP = 5;
3357
3494
  const DEFAULT_SECOND_STEP = 5;
3358
3495
  const TIME_COLUMN_MAX_HEIGHT = 168;
@@ -3427,8 +3564,10 @@ const dateTimePickerTriggerVariants = cva(
3427
3564
 
3428
3565
  export interface DateTimePickerValue {
3429
3566
  date?: Date;
3430
- startTime: string;
3431
- endTime: string;
3567
+ /** Undefined until a time is explicitly selected */
3568
+ startTime?: string;
3569
+ /** Undefined until a time is explicitly selected */
3570
+ endTime?: string;
3432
3571
  }
3433
3572
 
3434
3573
  export interface DateTimePickerProps
@@ -3482,8 +3621,8 @@ function normalizeValue(
3482
3621
  ): DateTimePickerValue {
3483
3622
  return {
3484
3623
  date: value?.date,
3485
- startTime: value?.startTime ?? DEFAULT_START_TIME,
3486
- endTime: value?.endTime ?? DEFAULT_END_TIME,
3624
+ startTime: value?.startTime,
3625
+ endTime: value?.endTime,
3487
3626
  };
3488
3627
  }
3489
3628
 
@@ -3760,7 +3899,6 @@ function formatValueForDisplay(
3760
3899
  value: DateTimePickerValue,
3761
3900
  variant: DateTimePickerVariant,
3762
3901
  showEndTime: boolean,
3763
- hasTimeValue: boolean,
3764
3902
  showSeconds: boolean
3765
3903
  ) {
3766
3904
  if (variant === "date-only") {
@@ -3768,14 +3906,21 @@ function formatValueForDisplay(
3768
3906
  }
3769
3907
 
3770
3908
  if (variant === "time-only") {
3771
- if (!hasTimeValue) return "";
3909
+ if (!value.startTime && !value.endTime) return "";
3772
3910
 
3773
3911
  return showEndTime
3774
- ? \`\${formatTimeForDisplay(value.startTime, showSeconds)} - \${formatTimeForDisplay(value.endTime, showSeconds)}\`
3775
- : formatTimeForDisplay(value.startTime, showSeconds);
3912
+ ? \`\${formatTimeForDisplay(value.startTime ?? UNSET_TIME, showSeconds)} - \${formatTimeForDisplay(value.endTime ?? UNSET_TIME, showSeconds)}\`
3913
+ : formatTimeForDisplay(value.startTime ?? UNSET_TIME, showSeconds);
3776
3914
  }
3777
3915
 
3778
- return formatDateForDisplay(value.date, value.startTime, showSeconds);
3916
+ const datePart = formatDateOnlyForDisplay(value.date);
3917
+ const timePart = value.startTime
3918
+ ? formatTimeForDisplay(value.startTime, showSeconds)
3919
+ : "";
3920
+
3921
+ if (!datePart && !timePart) return "";
3922
+
3923
+ return [datePart, timePart].filter(Boolean).join(" ");
3779
3924
  }
3780
3925
 
3781
3926
  function getDefaultPlaceholder(
@@ -3962,10 +4107,7 @@ function splitTypedDateInput(value: string) {
3962
4107
  dateSource.split(/[/-]/);
3963
4108
  const dayResult = consumeDigits(dayPart, 2);
3964
4109
  const monthResult = consumeDigits(\`\${dayResult.rest}\${monthPart}\`, 2);
3965
- const yearResult = consumeDigits(
3966
- \`\${monthResult.rest}\${yearPart}\`,
3967
- 4
3968
- );
4110
+ const yearResult = consumeDigits(\`\${monthResult.rest}\${yearPart}\`, 4);
3969
4111
  const day = dayResult.digits;
3970
4112
  const month = monthResult.digits;
3971
4113
  const year = yearResult.digits;
@@ -4003,7 +4145,10 @@ function splitTypedDateInput(value: string) {
4003
4145
  };
4004
4146
  }
4005
4147
 
4006
- function isPotentiallyValidTimeDigits(timeDigits: string, showSeconds: boolean) {
4148
+ function isPotentiallyValidTimeDigits(
4149
+ timeDigits: string,
4150
+ showSeconds: boolean
4151
+ ) {
4007
4152
  const hourValue = timeDigits.slice(0, 2);
4008
4153
  const minuteValue = timeDigits.slice(2, 4);
4009
4154
  const secondValue = showSeconds ? timeDigits.slice(4, 6) : "";
@@ -4037,7 +4182,10 @@ function formatTimeInput(restValue: string, showSeconds: boolean) {
4037
4182
  const meridiem = formatMeridiemInput(meridiemLetters.slice(0, 2));
4038
4183
 
4039
4184
  if (!timeDigits && !meridiem) return "";
4040
- if (!isPotentiallyValidTimeDigits(timeDigits, showSeconds) || meridiem === null) {
4185
+ if (
4186
+ !isPotentiallyValidTimeDigits(timeDigits, showSeconds) ||
4187
+ meridiem === null
4188
+ ) {
4041
4189
  return null;
4042
4190
  }
4043
4191
 
@@ -4057,7 +4205,8 @@ function sanitizeTypedDateInput(value: string, previousValue: string) {
4057
4205
  if (/^[A-Za-z]/.test(trimmedValue)) return previousValue;
4058
4206
 
4059
4207
  const normalizedValue = value.toUpperCase().replace(/[^0-9\\s/-]/g, "");
4060
- const { dateDigits, dateValue, isValid } = splitTypedDateInput(normalizedValue);
4208
+ const { dateDigits, dateValue, isValid } =
4209
+ splitTypedDateInput(normalizedValue);
4061
4210
  const limitedDateDigits = dateDigits.slice(0, 8);
4062
4211
 
4063
4212
  if (!limitedDateDigits) return "";
@@ -4091,9 +4240,7 @@ function sanitizeTypedDateTimeInput(
4091
4240
  const trimmedValue = value.trimStart();
4092
4241
  if (/^[A-Za-z]/.test(trimmedValue)) return previousValue;
4093
4242
 
4094
- const normalizedValue = value
4095
- .toUpperCase()
4096
- .replace(/[^0-9\\s/:APM-]/g, "");
4243
+ const normalizedValue = value.toUpperCase().replace(/[^0-9\\s/:APM-]/g, "");
4097
4244
  const { dateDigits, dateValue, isComplete, isValid, restValue } =
4098
4245
  splitTypedDateInput(normalizedValue);
4099
4246
  const limitedDateDigits = dateDigits.slice(0, 8);
@@ -4101,7 +4248,9 @@ function sanitizeTypedDateTimeInput(
4101
4248
  if (!limitedDateDigits) return "";
4102
4249
  if (!isValid) return previousValue;
4103
4250
 
4104
- const formattedTime = isComplete ? formatTimeInput(restValue, showSeconds) : "";
4251
+ const formattedTime = isComplete
4252
+ ? formatTimeInput(restValue, showSeconds)
4253
+ : "";
4105
4254
  if (formattedTime === null) return previousValue;
4106
4255
 
4107
4256
  return [dateValue, formattedTime].filter(Boolean).join(" ");
@@ -4177,7 +4326,11 @@ function stepDateTimeInputValue(
4177
4326
  const [hourValue = "0", minuteValue = "0", secondValue = "00"] = (
4178
4327
  typedDateTime.startTime ?? fallbackTime
4179
4328
  ).split(":");
4180
- nextDate.setHours(Number(hourValue), Number(minuteValue), Number(secondValue));
4329
+ nextDate.setHours(
4330
+ Number(hourValue),
4331
+ Number(minuteValue),
4332
+ Number(secondValue)
4333
+ );
4181
4334
 
4182
4335
  if (segment === "day") {
4183
4336
  nextDate.setDate(nextDate.getDate() + direction);
@@ -4221,10 +4374,7 @@ function stepDateTimeInputValue(
4221
4374
  .padStart(2, "0")}:\${nextDate
4222
4375
  .getMinutes()
4223
4376
  .toString()
4224
- .padStart(2, "0")}:\${nextDate
4225
- .getSeconds()
4226
- .toString()
4227
- .padStart(2, "0")}\`;
4377
+ .padStart(2, "0")}:\${nextDate.getSeconds().toString().padStart(2, "0")}\`;
4228
4378
 
4229
4379
  return {
4230
4380
  date: startOfDay(nextDate),
@@ -4307,13 +4457,15 @@ function parseTypedDate(value: string) {
4307
4457
  function formatHiddenValue(
4308
4458
  value: DateTimePickerValue,
4309
4459
  variant: DateTimePickerVariant,
4310
- showEndTime: boolean,
4311
- hasTimeValue: boolean
4460
+ showEndTime: boolean
4312
4461
  ) {
4462
+ const startTime = value.startTime ?? UNSET_TIME;
4463
+ const endTime = value.endTime ?? UNSET_TIME;
4464
+
4313
4465
  if (variant === "time-only") {
4314
- if (!hasTimeValue) return "";
4466
+ if (!value.startTime && !value.endTime) return "";
4315
4467
 
4316
- return showEndTime ? \`\${value.startTime}/\${value.endTime}\` : value.startTime;
4468
+ return showEndTime ? \`\${startTime}/\${endTime}\` : startTime;
4317
4469
  }
4318
4470
 
4319
4471
  if (!value.date) return "";
@@ -4330,6 +4482,8 @@ function formatHiddenValue(
4330
4482
  const day = value.date.getDate().toString().padStart(2, "0");
4331
4483
  const year = value.date.getFullYear();
4332
4484
 
4485
+ if (!value.startTime) return \`\${year}-\${month}-\${day}\`;
4486
+
4333
4487
  return \`\${year}-\${month}-\${day}T\${value.startTime}\`;
4334
4488
  }
4335
4489
 
@@ -4353,6 +4507,26 @@ function FigmaCalendarIcon({ className }: { className?: string }) {
4353
4507
  );
4354
4508
  }
4355
4509
 
4510
+ function FigmaClockIcon({ className }: { className?: string }) {
4511
+ return (
4512
+ <svg
4513
+ viewBox="0 0 18 18"
4514
+ fill="none"
4515
+ xmlns="http://www.w3.org/2000/svg"
4516
+ className={className}
4517
+ aria-hidden="true"
4518
+ >
4519
+ <path
4520
+ d="M9 4.5V9L11.25 11.25M15.75 9C15.75 12.7279 12.7279 15.75 9 15.75C5.27208 15.75 2.25 12.7279 2.25 9C2.25 5.27208 5.27208 2.25 9 2.25C12.7279 2.25 15.75 5.27208 15.75 9Z"
4521
+ stroke="currentColor"
4522
+ strokeWidth="1.5"
4523
+ strokeLinecap="round"
4524
+ strokeLinejoin="round"
4525
+ />
4526
+ </svg>
4527
+ );
4528
+ }
4529
+
4356
4530
  function CalendarDropdown({
4357
4531
  id,
4358
4532
  label,
@@ -4649,7 +4823,9 @@ function TimeField({
4649
4823
  ariaLabel={\`\${label} hours\`}
4650
4824
  options={hourOptions}
4651
4825
  onSelect={(key) =>
4652
- onChange(composeTime(Number(key), minute, effectiveSecond, meridiem))
4826
+ onChange(
4827
+ composeTime(Number(key), minute, effectiveSecond, meridiem)
4828
+ )
4653
4829
  }
4654
4830
  />
4655
4831
  <TimeColumn
@@ -4657,7 +4833,9 @@ function TimeField({
4657
4833
  ariaLabel={\`\${label} minutes\`}
4658
4834
  options={minuteOptions}
4659
4835
  onSelect={(key) =>
4660
- onChange(composeTime(hour12, Number(key), effectiveSecond, meridiem))
4836
+ onChange(
4837
+ composeTime(hour12, Number(key), effectiveSecond, meridiem)
4838
+ )
4661
4839
  }
4662
4840
  />
4663
4841
  {showSeconds && (
@@ -4735,22 +4913,19 @@ const DateTimePicker = React.forwardRef<HTMLDivElement, DateTimePickerProps>(
4735
4913
  const [internalValue, setInternalValue] = React.useState(() =>
4736
4914
  normalizeValue(defaultValue)
4737
4915
  );
4738
- const [internalHasTimeValue, setInternalHasTimeValue] = React.useState(() =>
4739
- Boolean(defaultValue?.date || defaultValue?.startTime || defaultValue?.endTime)
4740
- );
4741
4916
  const [internalOpen, setInternalOpen] = React.useState(defaultOpen);
4742
4917
  const currentValue = normalizeValue(
4743
4918
  isValueControlled ? value : internalValue
4744
4919
  );
4745
- const hasTimeValue = isValueControlled
4746
- ? Boolean(value?.date || value?.startTime || value?.endTime)
4747
- : internalHasTimeValue;
4920
+ const resolvedStartTime = currentValue.startTime ?? UNSET_TIME;
4921
+ const resolvedEndTime = currentValue.endTime ?? UNSET_TIME;
4748
4922
  const resolvedShowSeconds =
4749
4923
  showSeconds ??
4750
4924
  (timeHasVisibleSeconds(currentValue.startTime) ||
4751
4925
  (resolvedShowEndTime && timeHasVisibleSeconds(currentValue.endTime)));
4752
4926
  const placeholder =
4753
- placeholderProp ?? getDefaultPlaceholder(pickerVariant, resolvedShowSeconds);
4927
+ placeholderProp ??
4928
+ getDefaultPlaceholder(pickerVariant, resolvedShowSeconds);
4754
4929
  const open = isOpenControlled ? controlledOpen : internalOpen;
4755
4930
  const [visibleMonth, setVisibleMonth] = React.useState(() =>
4756
4931
  startOfMonth(currentValue.date ?? new Date())
@@ -4760,7 +4935,6 @@ const DateTimePicker = React.forwardRef<HTMLDivElement, DateTimePickerProps>(
4760
4935
  currentValue,
4761
4936
  pickerVariant,
4762
4937
  resolvedShowEndTime,
4763
- hasTimeValue,
4764
4938
  resolvedShowSeconds
4765
4939
  )
4766
4940
  );
@@ -4792,7 +4966,10 @@ const DateTimePicker = React.forwardRef<HTMLDivElement, DateTimePickerProps>(
4792
4966
  const maxWidth = Math.min(MAX_POPOVER_WIDTH, availableWidth);
4793
4967
  const width = Math.max(
4794
4968
  1,
4795
- Math.min(Math.max(rects.reference.width, MIN_POPOVER_WIDTH), maxWidth)
4969
+ Math.min(
4970
+ Math.max(rects.reference.width, MIN_POPOVER_WIDTH),
4971
+ maxWidth
4972
+ )
4796
4973
  );
4797
4974
  elements.floating.style.setProperty(
4798
4975
  POPOVER_SCROLL_HEIGHT_VAR,
@@ -4824,7 +5001,6 @@ const DateTimePicker = React.forwardRef<HTMLDivElement, DateTimePickerProps>(
4824
5001
  currentValue,
4825
5002
  pickerVariant,
4826
5003
  resolvedShowEndTime,
4827
- hasTimeValue,
4828
5004
  resolvedShowSeconds
4829
5005
  );
4830
5006
  const effectiveMinDate = React.useMemo(() => {
@@ -4915,15 +5091,9 @@ const DateTimePicker = React.forwardRef<HTMLDivElement, DateTimePickerProps>(
4915
5091
  }, [open, setOpen]);
4916
5092
 
4917
5093
  const updateValue = React.useCallback(
4918
- (
4919
- nextValue: DateTimePickerValue,
4920
- options?: { hasTimeValue?: boolean }
4921
- ) => {
5094
+ (nextValue: DateTimePickerValue) => {
4922
5095
  if (!isValueControlled) {
4923
5096
  setInternalValue(nextValue);
4924
- if (options?.hasTimeValue !== undefined) {
4925
- setInternalHasTimeValue(options.hasTimeValue);
4926
- }
4927
5097
  }
4928
5098
 
4929
5099
  onValueChange?.(nextValue);
@@ -4936,11 +5106,14 @@ const DateTimePicker = React.forwardRef<HTMLDivElement, DateTimePickerProps>(
4936
5106
  if (readOnly) return;
4937
5107
 
4938
5108
  setDateInputValue("");
5109
+ setOpenTimeField(null);
5110
+ setOpenCalendarDropdown(null);
5111
+ setVisibleMonth(startOfMonth(new Date()));
4939
5112
  updateValue({
4940
5113
  date: undefined,
4941
- startTime: currentValue.startTime,
4942
- endTime: currentValue.endTime,
4943
- }, { hasTimeValue: false });
5114
+ startTime: undefined,
5115
+ endTime: undefined,
5116
+ });
4944
5117
  };
4945
5118
 
4946
5119
  const yearOptions = React.useMemo(
@@ -4999,13 +5172,7 @@ const DateTimePicker = React.forwardRef<HTMLDivElement, DateTimePickerProps>(
4999
5172
  updateValue({ ...currentValue, date: nextSelectedDate });
5000
5173
  }
5001
5174
  },
5002
- [
5003
- currentValue,
5004
- effectiveMinDate,
5005
- maxDate,
5006
- pickerVariant,
5007
- updateValue,
5008
- ]
5175
+ [currentValue, effectiveMinDate, maxDate, pickerVariant, updateValue]
5009
5176
  );
5010
5177
 
5011
5178
  const handleTypedDateChange = (
@@ -5029,18 +5196,12 @@ const DateTimePicker = React.forwardRef<HTMLDivElement, DateTimePickerProps>(
5029
5196
 
5030
5197
  const typedTime = parseTimePart(nextInputValue);
5031
5198
  if (typedTime === undefined) {
5032
- updateValue({ ...currentValue, date: undefined }, { hasTimeValue: false });
5199
+ updateValue({ ...currentValue, startTime: undefined });
5033
5200
  return;
5034
5201
  }
5035
5202
 
5036
5203
  if (typedTime) {
5037
- updateValue(
5038
- {
5039
- ...currentValue,
5040
- startTime: typedTime,
5041
- },
5042
- { hasTimeValue: true }
5043
- );
5204
+ updateValue({ ...currentValue, startTime: typedTime });
5044
5205
  }
5045
5206
 
5046
5207
  return;
@@ -5130,7 +5291,7 @@ const DateTimePicker = React.forwardRef<HTMLDivElement, DateTimePickerProps>(
5130
5291
  dateInputValue,
5131
5292
  cursorPosition,
5132
5293
  direction,
5133
- currentValue.startTime,
5294
+ resolvedStartTime,
5134
5295
  resolvedShowSeconds
5135
5296
  );
5136
5297
 
@@ -5182,7 +5343,6 @@ const DateTimePicker = React.forwardRef<HTMLDivElement, DateTimePickerProps>(
5182
5343
  currentValue,
5183
5344
  pickerVariant,
5184
5345
  resolvedShowEndTime,
5185
- hasTimeValue,
5186
5346
  resolvedShowSeconds
5187
5347
  )
5188
5348
  );
@@ -5198,7 +5358,9 @@ const DateTimePicker = React.forwardRef<HTMLDivElement, DateTimePickerProps>(
5198
5358
  ref={setPopoverRef}
5199
5359
  role="dialog"
5200
5360
  aria-modal="false"
5201
- aria-labelledby={showCalendar ? \`\${triggerId}-calendar-heading\` : undefined}
5361
+ aria-labelledby={
5362
+ showCalendar ? \`\${triggerId}-calendar-heading\` : undefined
5363
+ }
5202
5364
  aria-label={showCalendar ? undefined : "Time picker"}
5203
5365
  className={cn(
5204
5366
  "rounded-lg border border-solid border-semantic-border-layout bg-semantic-bg-primary shadow-lg flex flex-col min-h-0 overflow-y-auto overflow-x-hidden overscroll-contain pointer-events-auto",
@@ -5288,11 +5450,7 @@ const DateTimePicker = React.forwardRef<HTMLDivElement, DateTimePickerProps>(
5288
5450
  }))}
5289
5451
  onValueChange={(nextYear) => {
5290
5452
  syncCalendarMonthAndValue(
5291
- new Date(
5292
- Number(nextYear),
5293
- visibleMonth.getMonth(),
5294
- 1
5295
- )
5453
+ new Date(Number(nextYear), visibleMonth.getMonth(), 1)
5296
5454
  );
5297
5455
  }}
5298
5456
  />
@@ -5384,7 +5542,7 @@ const DateTimePicker = React.forwardRef<HTMLDivElement, DateTimePickerProps>(
5384
5542
  <TimeField
5385
5543
  id={\`\${triggerId}-start-time\`}
5386
5544
  label={startTimeLabel}
5387
- value={currentValue.startTime}
5545
+ value={resolvedStartTime}
5388
5546
  showSeconds={resolvedShowSeconds}
5389
5547
  minuteStep={minuteStep}
5390
5548
  secondStep={secondStep}
@@ -5395,10 +5553,7 @@ const DateTimePicker = React.forwardRef<HTMLDivElement, DateTimePickerProps>(
5395
5553
  setOpenTimeField(nextOpen ? "start" : null)
5396
5554
  }
5397
5555
  onChange={(startTime) =>
5398
- updateValue(
5399
- { ...currentValue, startTime },
5400
- { hasTimeValue: true }
5401
- )
5556
+ updateValue({ ...currentValue, startTime })
5402
5557
  }
5403
5558
  />
5404
5559
 
@@ -5406,7 +5561,7 @@ const DateTimePicker = React.forwardRef<HTMLDivElement, DateTimePickerProps>(
5406
5561
  <TimeField
5407
5562
  id={\`\${triggerId}-end-time\`}
5408
5563
  label={endTimeLabel}
5409
- value={currentValue.endTime}
5564
+ value={resolvedEndTime}
5410
5565
  showSeconds={resolvedShowSeconds}
5411
5566
  minuteStep={minuteStep}
5412
5567
  secondStep={secondStep}
@@ -5417,15 +5572,13 @@ const DateTimePicker = React.forwardRef<HTMLDivElement, DateTimePickerProps>(
5417
5572
  setOpenTimeField(nextOpen ? "end" : null)
5418
5573
  }
5419
5574
  onChange={(endTime) =>
5420
- updateValue(
5421
- { ...currentValue, endTime },
5422
- { hasTimeValue: true }
5423
- )
5575
+ updateValue({ ...currentValue, endTime })
5424
5576
  }
5425
5577
  />
5426
5578
  )}
5427
5579
  </div>
5428
5580
  )}
5581
+
5429
5582
  </div>,
5430
5583
  portalMount
5431
5584
  );
@@ -5452,8 +5605,7 @@ const DateTimePicker = React.forwardRef<HTMLDivElement, DateTimePickerProps>(
5452
5605
  value={formatHiddenValue(
5453
5606
  currentValue,
5454
5607
  pickerVariant,
5455
- resolvedShowEndTime,
5456
- hasTimeValue
5608
+ resolvedShowEndTime
5457
5609
  )}
5458
5610
  />
5459
5611
  )}
@@ -5509,16 +5661,19 @@ const DateTimePicker = React.forwardRef<HTMLDivElement, DateTimePickerProps>(
5509
5661
  onKeyDown={handleTypedDateKeyDown}
5510
5662
  onBlur={handleTypedDateBlur}
5511
5663
  />
5512
- {showClear && displayValue && !disabled && !readOnly && (
5513
- <button
5514
- type="button"
5515
- aria-label="Clear date"
5516
- className="inline-flex size-5 items-center justify-center rounded text-semantic-text-muted hover:bg-semantic-bg-hover hover:text-semantic-text-primary"
5517
- onClick={clearValue}
5518
- >
5519
- <X className="size-4" aria-hidden="true" />
5520
- </button>
5521
- )}
5664
+ {showClear &&
5665
+ (displayValue || dateInputValue) &&
5666
+ !disabled &&
5667
+ !readOnly && (
5668
+ <button
5669
+ type="button"
5670
+ aria-label="Clear date"
5671
+ className="inline-flex size-5 items-center justify-center rounded text-semantic-text-muted hover:bg-semantic-bg-hover hover:text-semantic-text-primary"
5672
+ onClick={clearValue}
5673
+ >
5674
+ <X className="size-4" aria-hidden="true" />
5675
+ </button>
5676
+ )}
5522
5677
  <button
5523
5678
  type="button"
5524
5679
  disabled={disabled || readOnly}
@@ -5526,9 +5681,15 @@ const DateTimePicker = React.forwardRef<HTMLDivElement, DateTimePickerProps>(
5526
5681
  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"
5527
5682
  onClick={() => setOpen(!open)}
5528
5683
  >
5529
- <FigmaCalendarIcon
5530
- className={cn(size === "sm" ? "size-4" : "size-[18px]")}
5531
- />
5684
+ {showCalendar ? (
5685
+ <FigmaCalendarIcon
5686
+ className={cn(size === "sm" ? "size-4" : "size-[18px]")}
5687
+ />
5688
+ ) : (
5689
+ <FigmaClockIcon
5690
+ className={cn(size === "sm" ? "size-4" : "size-[18px]")}
5691
+ />
5692
+ )}
5532
5693
  </button>
5533
5694
  </div>
5534
5695
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "myoperator-mcp",
3
- "version": "0.2.381",
3
+ "version": "0.2.383",
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",