myoperator-mcp 0.2.380 → 0.2.382

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 +159 -33
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -2628,7 +2628,7 @@ const MONTH_LONG_NAMES = [
2628
2628
  ];
2629
2629
 
2630
2630
  const dateRangePickerTriggerVariants = cva(
2631
- "flex h-10 w-full items-center gap-2 rounded-lg border border-solid border-semantic-border-input bg-semantic-bg-primary px-4 py-2.5 text-left text-sm text-semantic-text-primary outline-none transition-colors hover:border-semantic-border-input-focus/50 disabled:cursor-not-allowed disabled:opacity-50",
2631
+ "flex h-10 w-full items-center gap-2 rounded border border-solid border-semantic-border-input bg-semantic-bg-primary px-4 py-2.5 text-left text-sm text-semantic-text-primary outline-none transition-colors hover:border-semantic-border-input-focus/50 disabled:cursor-not-allowed disabled:opacity-50",
2632
2632
  {
2633
2633
  variants: {
2634
2634
  state: {
@@ -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
@@ -3408,9 +3534,9 @@ const dateTimePickerTriggerVariants = cva(
3408
3534
  {
3409
3535
  variants: {
3410
3536
  size: {
3411
- sm: "h-9 gap-2 rounded-lg px-3 py-2 text-sm",
3412
- default: "h-[42px] gap-2 rounded-lg px-4 py-2.5 text-base",
3413
- lg: "h-[42px] gap-2 rounded-lg px-4 py-2.5 text-base",
3537
+ sm: "h-9 gap-2 rounded px-3 py-2 text-sm",
3538
+ default: "h-[42px] gap-2 rounded px-4 py-2.5 text-base",
3539
+ lg: "h-[42px] gap-2 rounded px-4 py-2.5 text-base",
3414
3540
  },
3415
3541
  state: {
3416
3542
  default: "",
@@ -4604,7 +4730,7 @@ function TimeField({
4604
4730
  aria-haspopup="listbox"
4605
4731
  aria-expanded={open}
4606
4732
  className={cn(
4607
- "flex h-[42px] w-full items-center gap-2 rounded-lg border border-solid border-semantic-border-input bg-semantic-bg-primary px-3 text-left text-base text-semantic-text-primary outline-none transition-colors hover:border-semantic-border-input-focus/50",
4733
+ "flex h-[42px] w-full items-center gap-2 rounded border border-solid border-semantic-border-input bg-semantic-bg-primary px-3 text-left text-base text-semantic-text-primary outline-none transition-colors hover:border-semantic-border-input-focus/50",
4608
4734
  open &&
4609
4735
  "border-semantic-border-input-focus/50 shadow-[0_0_0_1px_rgba(43,188,202,0.15)]"
4610
4736
  )}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "myoperator-mcp",
3
- "version": "0.2.380",
3
+ "version": "0.2.382",
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",