myoperator-mcp 0.2.356 → 0.2.358

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 +518 -137
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -2574,14 +2574,22 @@ import {
2574
2574
  type Strategy,
2575
2575
  } from "@floating-ui/react-dom";
2576
2576
  import { cva, type VariantProps } from "class-variance-authority";
2577
- import { ChevronLeft, ChevronRight, Clock2, X } from "lucide-react";
2577
+ import { ChevronDown, ChevronLeft, ChevronRight, Clock2, X } from "lucide-react";
2578
2578
 
2579
2579
  import { cn } from "@/lib/utils";
2580
2580
 
2581
2581
  const DEFAULT_START_TIME = "10:30:00";
2582
2582
  const DEFAULT_END_TIME = "12:30:00";
2583
+ const DEFAULT_MINUTE_STEP = 5;
2584
+ const DEFAULT_SECOND_STEP = 5;
2585
+ const TIME_COLUMN_MAX_HEIGHT = 168;
2583
2586
  const DEFAULT_PLACEHOLDER = "--/--/---- --:-- --";
2584
2587
  const POPOVER_WIDTH = 336;
2588
+ // The popover follows the trigger width but is clamped to a usable design range
2589
+ // so it neither stretches across a full-width desktop field nor overflows a
2590
+ // narrow mobile viewport.
2591
+ const MIN_POPOVER_WIDTH = 280;
2592
+ const MAX_POPOVER_WIDTH = 360;
2585
2593
  const POPOVER_MARGIN = 8;
2586
2594
  const POPOVER_GAP = 4;
2587
2595
  const MAX_POPOVER_HEIGHT = 420;
@@ -2664,6 +2672,8 @@ export interface DateTimePickerProps
2664
2672
  name?: string;
2665
2673
  showEndTime?: boolean;
2666
2674
  showSeconds?: boolean;
2675
+ minuteStep?: number;
2676
+ secondStep?: number;
2667
2677
  showClear?: boolean;
2668
2678
  closeOnSelect?: boolean;
2669
2679
  startTimeLabel?: string;
@@ -2802,6 +2812,129 @@ function isPointerInsideElement(
2802
2812
  return false;
2803
2813
  }
2804
2814
 
2815
+ // Floating sub-dropdowns (Month / Year / Time) are portaled to document.body
2816
+ // so they escape the popover's overflow clipping. Because they live outside the
2817
+ // popover in the DOM, the picker's own outside-click handler can't recognize
2818
+ // them via \`contains\`. Tagging each portaled panel with this attribute lets the
2819
+ // handler detect clicks that land inside an open sub-dropdown.
2820
+ const DROPDOWN_MARKER_ATTR = "data-dtp-dropdown";
2821
+ const DROPDOWN_GAP = 4;
2822
+ const DROPDOWN_MARGIN = 8;
2823
+ const DROPDOWN_MAX_HEIGHT = 256;
2824
+ const DROPDOWN_Z_INDEX = 10060;
2825
+ const DROPDOWN_PLACEMENT: Placement = "bottom-start";
2826
+ const DROPDOWN_WIDTH_VAR = "--date-time-picker-dropdown-width";
2827
+ const DROPDOWN_HEIGHT_VAR = "--date-time-picker-dropdown-height";
2828
+
2829
+ function isPointerInsideDropdown(event: MouseEvent) {
2830
+ const target = event.target;
2831
+
2832
+ return (
2833
+ target instanceof Element &&
2834
+ target.closest(\`[\${DROPDOWN_MARKER_ATTR}]\`) !== null
2835
+ );
2836
+ }
2837
+
2838
+ // Shared floating + dismissal behavior for the Month, Year, and Time dropdowns.
2839
+ // Each instance positions its panel against its trigger, mirrors the picker's
2840
+ // portal strategy, and closes itself on an outside click or Escape \u2014 without
2841
+ // collapsing the parent picker (Escape is captured so it doesn't bubble to the
2842
+ // picker's own Escape handler).
2843
+ function useFloatingDropdown({
2844
+ open,
2845
+ onOpenChange,
2846
+ strategy,
2847
+ }: {
2848
+ open: boolean;
2849
+ onOpenChange: (open: boolean) => void;
2850
+ strategy: Strategy;
2851
+ }) {
2852
+ const middleware = React.useMemo(
2853
+ () => [
2854
+ offset(DROPDOWN_GAP),
2855
+ flip({ padding: DROPDOWN_MARGIN }),
2856
+ shift({ padding: DROPDOWN_MARGIN }),
2857
+ floatingSize({
2858
+ padding: DROPDOWN_MARGIN,
2859
+ apply({ availableHeight, rects, elements }) {
2860
+ const maxHeight = Math.max(
2861
+ 1,
2862
+ Math.min(DROPDOWN_MAX_HEIGHT, availableHeight)
2863
+ );
2864
+ elements.floating.style.setProperty(
2865
+ DROPDOWN_HEIGHT_VAR,
2866
+ \`\${maxHeight}px\`
2867
+ );
2868
+ elements.floating.style.setProperty(
2869
+ DROPDOWN_WIDTH_VAR,
2870
+ \`\${rects.reference.width}px\`
2871
+ );
2872
+ },
2873
+ }),
2874
+ ],
2875
+ []
2876
+ );
2877
+
2878
+ const floating = useFloating<HTMLButtonElement>({
2879
+ open,
2880
+ placement: DROPDOWN_PLACEMENT,
2881
+ strategy,
2882
+ transform: false,
2883
+ middleware,
2884
+ whileElementsMounted: (reference, floatingEl, update) =>
2885
+ autoUpdate(reference, floatingEl, update, { animationFrame: true }),
2886
+ });
2887
+
2888
+ const { refs, floatingStyles, isPositioned } = floating;
2889
+
2890
+ React.useEffect(() => {
2891
+ if (!open) return;
2892
+
2893
+ const handlePointerDown = (event: MouseEvent) => {
2894
+ const reference = refs.reference.current as HTMLElement | null;
2895
+ if (
2896
+ !isPointerInsideElement(event, reference) &&
2897
+ !isPointerInsideElement(event, refs.floating.current)
2898
+ ) {
2899
+ onOpenChange(false);
2900
+ }
2901
+ };
2902
+
2903
+ const handleKeyDown = (event: KeyboardEvent) => {
2904
+ if (event.key === "Escape") {
2905
+ // Capture phase + stopPropagation keeps Escape from also reaching the
2906
+ // picker's handler, so it dismisses just this dropdown.
2907
+ event.stopPropagation();
2908
+ onOpenChange(false);
2909
+ }
2910
+ };
2911
+
2912
+ // Capture phase: the popover container calls stopPropagation on its own
2913
+ // mousedown, which would otherwise swallow clicks inside it before they
2914
+ // reach a bubble-phase document listener. Listening on capture runs this
2915
+ // handler top-down \u2014 before that stopPropagation \u2014 so clicks anywhere
2916
+ // outside the dropdown (including elsewhere in the popover) dismiss it.
2917
+ document.addEventListener("mousedown", handlePointerDown, true);
2918
+ document.addEventListener("keydown", handleKeyDown, true);
2919
+
2920
+ return () => {
2921
+ document.removeEventListener("mousedown", handlePointerDown, true);
2922
+ document.removeEventListener("keydown", handleKeyDown, true);
2923
+ };
2924
+ }, [open, onOpenChange, refs]);
2925
+
2926
+ const setReference = React.useCallback(
2927
+ (node: HTMLButtonElement | null) => refs.setReference(node),
2928
+ [refs]
2929
+ );
2930
+ const setFloating = React.useCallback(
2931
+ (node: HTMLDivElement | null) => refs.setFloating(node),
2932
+ [refs]
2933
+ );
2934
+
2935
+ return { setReference, setFloating, floatingStyles, isPositioned };
2936
+ }
2937
+
2805
2938
  function timeHasVisibleSeconds(time?: string) {
2806
2939
  const [, , second = "00"] = (time ?? "").split(":");
2807
2940
  return /^\\d{1,2}$/.test(second) && Number(second) !== 0;
@@ -3321,32 +3454,45 @@ function stepDateTimeInputValue(
3321
3454
  };
3322
3455
  }
3323
3456
 
3324
- function formatTimeForTimeInput(time: string, showSeconds: boolean) {
3325
- const normalizedTime = parseTimePart(time) ?? DEFAULT_START_TIME;
3326
- const [hour = "00", minute = "00", second = "00"] = normalizedTime.split(":");
3457
+ type Meridiem = "AM" | "PM";
3327
3458
 
3328
- return showSeconds ? \`\${hour}:\${minute}:\${second}\` : \`\${hour}:\${minute}\`;
3459
+ function padTime(value: number) {
3460
+ return value.toString().padStart(2, "0");
3329
3461
  }
3330
3462
 
3331
- function normalizeTimeInputValue(time: string, fallbackTime: string) {
3332
- return parseTimePart(time) ?? parseTimePart(fallbackTime) ?? DEFAULT_START_TIME;
3463
+ function buildTimeRange(max: number, step: number) {
3464
+ const safeStep = Math.max(1, Math.floor(step));
3465
+ const values: number[] = [];
3466
+ for (let value = 0; value <= max; value += safeStep) {
3467
+ values.push(value);
3468
+ }
3469
+
3470
+ return values;
3333
3471
  }
3334
3472
 
3335
- function openNativeTimePicker(input: HTMLInputElement | null) {
3336
- if (!input) return;
3473
+ function decomposeTime(time: string) {
3474
+ const normalizedTime = parseTimePart(time) ?? DEFAULT_START_TIME;
3475
+ const [hour = "0", minute = "0", second = "0"] = normalizedTime.split(":");
3476
+ const hourNumber = Number(hour);
3337
3477
 
3338
- input.focus();
3478
+ return {
3479
+ hour12: hourNumber % 12 || 12,
3480
+ minute: Number(minute),
3481
+ second: Number(second),
3482
+ meridiem: (hourNumber >= 12 ? "PM" : "AM") as Meridiem,
3483
+ };
3484
+ }
3339
3485
 
3340
- const showPicker = (
3341
- input as HTMLInputElement & { showPicker?: () => void }
3342
- ).showPicker;
3343
- if (!showPicker) return;
3486
+ function composeTime(
3487
+ hour12: number,
3488
+ minute: number,
3489
+ second: number,
3490
+ meridiem: Meridiem
3491
+ ) {
3492
+ const normalizedHour12 = hour12 % 12;
3493
+ const hour = meridiem === "PM" ? normalizedHour12 + 12 : normalizedHour12;
3344
3494
 
3345
- try {
3346
- showPicker.call(input);
3347
- } catch {
3348
- input.focus();
3349
- }
3495
+ return \`\${padTime(hour)}:\${padTime(minute)}:\${padTime(second)}\`;
3350
3496
  }
3351
3497
 
3352
3498
  function parseTypedDateTime(value: string) {
@@ -3436,6 +3582,8 @@ function CalendarDropdown({
3436
3582
  open,
3437
3583
  onOpenChange,
3438
3584
  onValueChange,
3585
+ portalMount,
3586
+ strategy,
3439
3587
  }: {
3440
3588
  id: string;
3441
3589
  label: string;
@@ -3444,8 +3592,16 @@ function CalendarDropdown({
3444
3592
  open: boolean;
3445
3593
  onOpenChange: (open: boolean) => void;
3446
3594
  onValueChange: (value: string) => void;
3595
+ portalMount: HTMLElement | null;
3596
+ strategy: Strategy;
3447
3597
  }) {
3448
3598
  const selectedOption = options.find((option) => option.value === value);
3599
+ const { setReference, setFloating, floatingStyles, isPositioned } =
3600
+ useFloatingDropdown({
3601
+ open,
3602
+ onOpenChange,
3603
+ strategy,
3604
+ });
3449
3605
 
3450
3606
  return (
3451
3607
  <div className="relative">
@@ -3453,6 +3609,7 @@ function CalendarDropdown({
3453
3609
  {label}
3454
3610
  </label>
3455
3611
  <button
3612
+ ref={setReference}
3456
3613
  id={id}
3457
3614
  type="button"
3458
3615
  role="combobox"
@@ -3472,37 +3629,281 @@ function CalendarDropdown({
3472
3629
  aria-hidden="true"
3473
3630
  />
3474
3631
  </button>
3475
- {open && (
3476
- <div
3477
- id={\`\${id}-options\`}
3478
- role="listbox"
3479
- aria-label={\`\${label} options\`}
3480
- className="absolute left-0 top-full z-10 mt-1 max-h-52 min-w-full overflow-y-auto rounded-md border border-solid border-semantic-border-layout bg-semantic-bg-primary p-1 shadow-lg"
3481
- >
3482
- {options.map((option) => (
3483
- <button
3484
- key={option.value}
3485
- type="button"
3486
- role="option"
3487
- aria-label={option.label}
3488
- aria-selected={option.value === value}
3489
- disabled={option.disabled}
3490
- className={cn(
3491
- "flex w-full items-center rounded px-2 py-1.5 text-left text-sm text-semantic-text-primary transition-colors hover:bg-semantic-bg-hover disabled:cursor-not-allowed disabled:opacity-40",
3492
- option.value === value && "bg-semantic-primary text-semantic-text-inverted"
3493
- )}
3494
- onClick={() => {
3495
- if (option.disabled) return;
3632
+ {open &&
3633
+ portalMount &&
3634
+ createPortal(
3635
+ <div
3636
+ ref={setFloating}
3637
+ id={\`\${id}-options\`}
3638
+ role="listbox"
3639
+ aria-label={\`\${label} options\`}
3640
+ data-dtp-dropdown=""
3641
+ className="flex flex-col gap-0.5 overflow-y-auto rounded-md border border-solid border-semantic-border-layout bg-semantic-bg-primary p-1 shadow-lg [scrollbar-width:thin] [&::-webkit-scrollbar-thumb]:rounded-full [&::-webkit-scrollbar-thumb]:bg-semantic-border-secondary [&::-webkit-scrollbar-track]:bg-transparent [&::-webkit-scrollbar]:w-1.5"
3642
+ style={{
3643
+ ...floatingStyles,
3644
+ width: \`var(\${DROPDOWN_WIDTH_VAR}, auto)\`,
3645
+ maxHeight: \`var(\${DROPDOWN_HEIGHT_VAR}, \${DROPDOWN_MAX_HEIGHT}px)\`,
3646
+ zIndex: DROPDOWN_Z_INDEX,
3647
+ visibility: isPositioned ? undefined : "hidden",
3648
+ }}
3649
+ >
3650
+ {options.map((option) => (
3651
+ <button
3652
+ key={option.value}
3653
+ type="button"
3654
+ role="option"
3655
+ aria-label={option.label}
3656
+ aria-selected={option.value === value}
3657
+ disabled={option.disabled}
3658
+ className={cn(
3659
+ "flex w-full shrink-0 items-center rounded-md border border-solid px-2 py-1.5 text-left text-sm transition-colors disabled:cursor-not-allowed disabled:opacity-40",
3660
+ option.value === value
3661
+ ? "border-semantic-info-border bg-semantic-info-surface font-semibold text-semantic-text-primary"
3662
+ : "border-transparent text-semantic-text-secondary hover:bg-semantic-bg-hover"
3663
+ )}
3664
+ onClick={() => {
3665
+ if (option.disabled) return;
3496
3666
 
3497
- onValueChange(option.value);
3498
- onOpenChange(false);
3499
- }}
3500
- >
3501
- {option.label}
3502
- </button>
3503
- ))}
3504
- </div>
3505
- )}
3667
+ onValueChange(option.value);
3668
+ onOpenChange(false);
3669
+ }}
3670
+ >
3671
+ {option.label}
3672
+ </button>
3673
+ ))}
3674
+ </div>,
3675
+ portalMount
3676
+ )}
3677
+ </div>
3678
+ );
3679
+ }
3680
+
3681
+ interface TimeColumnOption {
3682
+ key: string;
3683
+ label: string;
3684
+ selected: boolean;
3685
+ }
3686
+
3687
+ function TimeColumn({
3688
+ header,
3689
+ ariaLabel,
3690
+ options,
3691
+ onSelect,
3692
+ }: {
3693
+ header: string;
3694
+ ariaLabel: string;
3695
+ options: TimeColumnOption[];
3696
+ onSelect: (key: string) => void;
3697
+ }) {
3698
+ const listRef = React.useRef<HTMLDivElement | null>(null);
3699
+ const selectedRef = React.useRef<HTMLButtonElement | null>(null);
3700
+
3701
+ React.useEffect(() => {
3702
+ const list = listRef.current;
3703
+ const selected = selectedRef.current;
3704
+ if (!list || !selected) return;
3705
+
3706
+ list.scrollTop =
3707
+ selected.offsetTop - list.clientHeight / 2 + selected.clientHeight / 2;
3708
+ }, []);
3709
+
3710
+ return (
3711
+ <div className="flex min-w-0 flex-col border-r border-solid border-semantic-border-layout last:border-r-0">
3712
+ <div className="border-b border-solid border-semantic-border-layout px-1 py-2 text-center text-[11px] font-semibold uppercase tracking-wide text-semantic-text-muted">
3713
+ {header}
3714
+ </div>
3715
+ <div
3716
+ ref={listRef}
3717
+ role="listbox"
3718
+ aria-label={ariaLabel}
3719
+ className="flex flex-col gap-0.5 overflow-y-auto p-1 [scrollbar-width:thin] [&::-webkit-scrollbar-thumb]:rounded-full [&::-webkit-scrollbar-thumb]:bg-semantic-border-secondary [&::-webkit-scrollbar-track]:bg-transparent [&::-webkit-scrollbar]:w-1.5"
3720
+ style={{ maxHeight: \`\${TIME_COLUMN_MAX_HEIGHT}px\` }}
3721
+ >
3722
+ {options.map((option) => (
3723
+ <button
3724
+ key={option.key}
3725
+ ref={option.selected ? selectedRef : undefined}
3726
+ type="button"
3727
+ role="option"
3728
+ aria-selected={option.selected}
3729
+ className={cn(
3730
+ "flex shrink-0 items-center justify-center rounded-md border border-solid px-2 py-1.5 text-sm transition-colors",
3731
+ option.selected
3732
+ ? "border-semantic-info-border bg-semantic-info-surface font-semibold text-semantic-text-primary"
3733
+ : "border-transparent text-semantic-text-secondary hover:bg-semantic-bg-hover"
3734
+ )}
3735
+ onClick={() => onSelect(option.key)}
3736
+ >
3737
+ {option.label}
3738
+ </button>
3739
+ ))}
3740
+ </div>
3741
+ </div>
3742
+ );
3743
+ }
3744
+
3745
+ function TimeField({
3746
+ id,
3747
+ label,
3748
+ value,
3749
+ showSeconds,
3750
+ minuteStep,
3751
+ secondStep,
3752
+ open,
3753
+ onOpenChange,
3754
+ onChange,
3755
+ portalMount,
3756
+ strategy,
3757
+ }: {
3758
+ id: string;
3759
+ label: string;
3760
+ value: string;
3761
+ showSeconds: boolean;
3762
+ minuteStep: number;
3763
+ secondStep: number;
3764
+ open: boolean;
3765
+ onOpenChange: (open: boolean) => void;
3766
+ onChange: (time: string) => void;
3767
+ portalMount: HTMLElement | null;
3768
+ strategy: Strategy;
3769
+ }) {
3770
+ const { hour12, minute, second, meridiem } = decomposeTime(value);
3771
+ const { setReference, setFloating, floatingStyles, isPositioned } =
3772
+ useFloatingDropdown({
3773
+ open,
3774
+ onOpenChange,
3775
+ strategy,
3776
+ });
3777
+ // When seconds are hidden they are not editable, so normalize to :00 on any
3778
+ // change rather than carrying a stale seconds value forward.
3779
+ const effectiveSecond = showSeconds ? second : 0;
3780
+ const hourOptions = React.useMemo<TimeColumnOption[]>(
3781
+ () =>
3782
+ Array.from({ length: 12 }, (_, index) => index + 1).map((hour) => ({
3783
+ key: hour.toString(),
3784
+ label: padTime(hour),
3785
+ selected: hour === hour12,
3786
+ })),
3787
+ [hour12]
3788
+ );
3789
+ const minuteOptions = React.useMemo<TimeColumnOption[]>(
3790
+ () =>
3791
+ buildTimeRange(59, minuteStep).map((minuteValue) => ({
3792
+ key: minuteValue.toString(),
3793
+ label: padTime(minuteValue),
3794
+ selected: minuteValue === minute,
3795
+ })),
3796
+ [minute, minuteStep]
3797
+ );
3798
+ const secondOptions = React.useMemo<TimeColumnOption[]>(
3799
+ () =>
3800
+ buildTimeRange(59, secondStep).map((secondValue) => ({
3801
+ key: secondValue.toString(),
3802
+ label: padTime(secondValue),
3803
+ selected: secondValue === second,
3804
+ })),
3805
+ [second, secondStep]
3806
+ );
3807
+ const meridiemOptions: TimeColumnOption[] = [
3808
+ { key: "AM", label: "AM", selected: meridiem === "AM" },
3809
+ { key: "PM", label: "PM", selected: meridiem === "PM" },
3810
+ ];
3811
+
3812
+ return (
3813
+ <div className="flex flex-col gap-1.5">
3814
+ <span
3815
+ id={\`\${id}-label\`}
3816
+ className="block text-sm font-semibold text-semantic-text-secondary"
3817
+ >
3818
+ {label}
3819
+ </span>
3820
+ <button
3821
+ ref={setReference}
3822
+ id={id}
3823
+ type="button"
3824
+ aria-label={label}
3825
+ aria-haspopup="listbox"
3826
+ aria-expanded={open}
3827
+ className={cn(
3828
+ "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",
3829
+ open &&
3830
+ "border-semantic-border-input-focus/50 shadow-[0_0_0_1px_rgba(43,188,202,0.15)]"
3831
+ )}
3832
+ onClick={() => onOpenChange(!open)}
3833
+ >
3834
+ <Clock2
3835
+ className="size-4 shrink-0 text-semantic-text-muted"
3836
+ aria-hidden="true"
3837
+ />
3838
+ <span className="m-0 min-w-0 flex-1 truncate">
3839
+ {formatTimeForDisplay(value, showSeconds)}
3840
+ </span>
3841
+ <ChevronDown
3842
+ className={cn(
3843
+ "size-4 shrink-0 text-semantic-text-muted transition-transform",
3844
+ open && "rotate-180"
3845
+ )}
3846
+ aria-hidden="true"
3847
+ />
3848
+ </button>
3849
+ {open &&
3850
+ portalMount &&
3851
+ createPortal(
3852
+ <div
3853
+ ref={setFloating}
3854
+ role="listbox"
3855
+ aria-label={\`\${label} options\`}
3856
+ data-dtp-dropdown=""
3857
+ className={cn(
3858
+ "grid overflow-hidden rounded-lg border border-solid border-semantic-border-layout bg-semantic-bg-primary shadow-lg",
3859
+ showSeconds ? "grid-cols-4" : "grid-cols-3"
3860
+ )}
3861
+ style={{
3862
+ ...floatingStyles,
3863
+ width: \`var(\${DROPDOWN_WIDTH_VAR}, auto)\`,
3864
+ zIndex: DROPDOWN_Z_INDEX,
3865
+ visibility: isPositioned ? undefined : "hidden",
3866
+ }}
3867
+ >
3868
+ <TimeColumn
3869
+ header="Hours"
3870
+ ariaLabel={\`\${label} hours\`}
3871
+ options={hourOptions}
3872
+ onSelect={(key) =>
3873
+ onChange(composeTime(Number(key), minute, effectiveSecond, meridiem))
3874
+ }
3875
+ />
3876
+ <TimeColumn
3877
+ header="Minutes"
3878
+ ariaLabel={\`\${label} minutes\`}
3879
+ options={minuteOptions}
3880
+ onSelect={(key) =>
3881
+ onChange(composeTime(hour12, Number(key), effectiveSecond, meridiem))
3882
+ }
3883
+ />
3884
+ {showSeconds && (
3885
+ <TimeColumn
3886
+ header="Seconds"
3887
+ ariaLabel={\`\${label} seconds\`}
3888
+ options={secondOptions}
3889
+ onSelect={(key) =>
3890
+ onChange(composeTime(hour12, minute, Number(key), meridiem))
3891
+ }
3892
+ />
3893
+ )}
3894
+ <TimeColumn
3895
+ header="AM/PM"
3896
+ ariaLabel={\`\${label} meridiem\`}
3897
+ options={meridiemOptions}
3898
+ onSelect={(key) =>
3899
+ onChange(
3900
+ composeTime(hour12, minute, effectiveSecond, key as Meridiem)
3901
+ )
3902
+ }
3903
+ />
3904
+ </div>,
3905
+ portalMount
3906
+ )}
3506
3907
  </div>
3507
3908
  );
3508
3909
  }
@@ -3523,6 +3924,8 @@ const DateTimePicker = React.forwardRef<HTMLDivElement, DateTimePickerProps>(
3523
3924
  name,
3524
3925
  showEndTime = true,
3525
3926
  showSeconds,
3927
+ minuteStep = DEFAULT_MINUTE_STEP,
3928
+ secondStep = DEFAULT_SECOND_STEP,
3526
3929
  showClear = true,
3527
3930
  closeOnSelect = false,
3528
3931
  startTimeLabel = "Start Time",
@@ -3583,10 +3986,11 @@ const DateTimePicker = React.forwardRef<HTMLDivElement, DateTimePickerProps>(
3583
3986
  const rootRef = React.useRef<HTMLDivElement | null>(null);
3584
3987
  const triggerRef = React.useRef<HTMLDivElement | null>(null);
3585
3988
  const popoverRef = React.useRef<HTMLDivElement | null>(null);
3586
- const startTimeInputRef = React.useRef<HTMLInputElement | null>(null);
3587
- const endTimeInputRef = React.useRef<HTMLInputElement | null>(null);
3588
3989
  const [openCalendarDropdown, setOpenCalendarDropdown] =
3589
3990
  React.useState<CalendarDropdownKind | null>(null);
3991
+ const [openTimeField, setOpenTimeField] = React.useState<
3992
+ "start" | "end" | null
3993
+ >(null);
3590
3994
  const usesContainerPortal = portalContainer !== undefined;
3591
3995
  const floatingStrategy: Strategy = usesContainerPortal
3592
3996
  ? "absolute"
@@ -3598,18 +4002,23 @@ const DateTimePicker = React.forwardRef<HTMLDivElement, DateTimePickerProps>(
3598
4002
  shift({ padding: POPOVER_MARGIN }),
3599
4003
  floatingSize({
3600
4004
  padding: POPOVER_MARGIN,
3601
- apply({ availableHeight, elements, rects }) {
4005
+ apply({ availableHeight, availableWidth, elements, rects }) {
3602
4006
  const maxHeight = Math.max(
3603
4007
  1,
3604
4008
  Math.min(MAX_POPOVER_HEIGHT, availableHeight)
3605
4009
  );
4010
+ const maxWidth = Math.min(MAX_POPOVER_WIDTH, availableWidth);
4011
+ const width = Math.max(
4012
+ 1,
4013
+ Math.min(Math.max(rects.reference.width, MIN_POPOVER_WIDTH), maxWidth)
4014
+ );
3606
4015
  elements.floating.style.setProperty(
3607
4016
  POPOVER_SCROLL_HEIGHT_VAR,
3608
4017
  \`\${maxHeight}px\`
3609
4018
  );
3610
4019
  elements.floating.style.setProperty(
3611
4020
  POPOVER_WIDTH_VAR,
3612
- \`\${rects.reference.width}px\`
4021
+ \`\${width}px\`
3613
4022
  );
3614
4023
  },
3615
4024
  }),
@@ -3655,6 +4064,7 @@ const DateTimePicker = React.forwardRef<HTMLDivElement, DateTimePickerProps>(
3655
4064
  (nextOpen: boolean) => {
3656
4065
  if (!nextOpen) {
3657
4066
  setOpenCalendarDropdown(null);
4067
+ setOpenTimeField(null);
3658
4068
  }
3659
4069
 
3660
4070
  if (!isOpenControlled) {
@@ -3700,7 +4110,8 @@ const DateTimePicker = React.forwardRef<HTMLDivElement, DateTimePickerProps>(
3700
4110
  const handlePointerDown = (event: MouseEvent) => {
3701
4111
  if (
3702
4112
  !isPointerInsideElement(event, rootRef.current) &&
3703
- !isPointerInsideElement(event, popoverRef.current)
4113
+ !isPointerInsideElement(event, popoverRef.current) &&
4114
+ !isPointerInsideDropdown(event)
3704
4115
  ) {
3705
4116
  setOpen(false);
3706
4117
  }
@@ -4014,7 +4425,9 @@ const DateTimePicker = React.forwardRef<HTMLDivElement, DateTimePickerProps>(
4014
4425
  )}
4015
4426
  style={{
4016
4427
  ...floatingStyles,
4017
- width: \`var(\${POPOVER_WIDTH_VAR}, \${POPOVER_WIDTH}px)\`,
4428
+ width: \`var(\${POPOVER_WIDTH_VAR}, min(\${POPOVER_WIDTH}px, calc(100vw - \${
4429
+ POPOVER_MARGIN * 2
4430
+ }px)))\`,
4018
4431
  maxHeight: \`var(\${POPOVER_SCROLL_HEIGHT_VAR}, min(\${MAX_POPOVER_HEIGHT}px, calc(100dvh - \${
4019
4432
  POPOVER_MARGIN * 2
4020
4433
  }px)))\`,
@@ -4044,6 +4457,8 @@ const DateTimePicker = React.forwardRef<HTMLDivElement, DateTimePickerProps>(
4044
4457
  id={\`\${triggerId}-month\`}
4045
4458
  label="Month"
4046
4459
  value={visibleMonth.getMonth().toString()}
4460
+ portalMount={portalMount}
4461
+ strategy={floatingStrategy}
4047
4462
  open={openCalendarDropdown === "month"}
4048
4463
  onOpenChange={(nextOpen) =>
4049
4464
  setOpenCalendarDropdown(nextOpen ? "month" : null)
@@ -4079,6 +4494,8 @@ const DateTimePicker = React.forwardRef<HTMLDivElement, DateTimePickerProps>(
4079
4494
  id={\`\${triggerId}-year\`}
4080
4495
  label="Year"
4081
4496
  value={visibleMonth.getFullYear().toString()}
4497
+ portalMount={portalMount}
4498
+ strategy={floatingStrategy}
4082
4499
  open={openCalendarDropdown === "year"}
4083
4500
  onOpenChange={(nextOpen) =>
4084
4501
  setOpenCalendarDropdown(nextOpen ? "year" : null)
@@ -4117,7 +4534,7 @@ const DateTimePicker = React.forwardRef<HTMLDivElement, DateTimePickerProps>(
4117
4534
  {weekDays.map((day) => (
4118
4535
  <div
4119
4536
  key={day}
4120
- className="flex size-8 items-center justify-center text-xs font-semibold text-semantic-text-muted"
4537
+ className="mx-auto flex size-8 items-center justify-center text-xs font-semibold text-semantic-text-muted"
4121
4538
  >
4122
4539
  {day}
4123
4540
  </div>
@@ -4151,6 +4568,9 @@ const DateTimePicker = React.forwardRef<HTMLDivElement, DateTimePickerProps>(
4151
4568
  : isCurrentMonth
4152
4569
  ? "text-semantic-text-primary hover:bg-semantic-bg-hover"
4153
4570
  : "text-semantic-text-muted hover:bg-semantic-bg-hover",
4571
+ isToday &&
4572
+ !isSelected &&
4573
+ "ring-1 ring-inset ring-semantic-border-secondary",
4154
4574
  isDisabled &&
4155
4575
  "opacity-40 cursor-not-allowed pointer-events-none"
4156
4576
  )}
@@ -4164,9 +4584,6 @@ const DateTimePicker = React.forwardRef<HTMLDivElement, DateTimePickerProps>(
4164
4584
  }}
4165
4585
  >
4166
4586
  {day.getDate()}
4167
- {isToday && !isSelected && (
4168
- <span className="absolute bottom-0.5 left-1/2 -translate-x-1/2 size-1 rounded-full bg-semantic-primary" />
4169
- )}
4170
4587
  </button>
4171
4588
  );
4172
4589
  })}
@@ -4182,87 +4599,49 @@ const DateTimePicker = React.forwardRef<HTMLDivElement, DateTimePickerProps>(
4182
4599
  "border-t border-solid border-semantic-border-layout"
4183
4600
  )}
4184
4601
  >
4185
- <div className="flex flex-col gap-1.5">
4186
- <label
4187
- id={\`\${triggerId}-start-time-label\`}
4188
- className="block text-sm font-semibold text-semantic-text-secondary"
4189
- >
4190
- {startTimeLabel}
4191
- </label>
4192
- <div className="relative">
4193
- <Clock2
4194
- className="pointer-events-none absolute left-3 top-1/2 size-4 -translate-y-1/2 text-semantic-text-muted"
4195
- aria-hidden="true"
4196
- />
4197
- <input
4198
- ref={startTimeInputRef}
4199
- id={\`\${triggerId}-start-time\`}
4200
- aria-labelledby={\`\${triggerId}-start-time-label\`}
4201
- type="time"
4202
- step={resolvedShowSeconds ? "1" : "60"}
4203
- value={formatTimeForTimeInput(
4204
- currentValue.startTime,
4205
- resolvedShowSeconds
4206
- )}
4207
- className="h-[42px] w-full rounded-md border border-solid border-semantic-border-input bg-semantic-bg-primary pl-9 pr-3 text-base text-semantic-text-primary outline-none transition-colors hover:border-semantic-border-input-focus/50 focus:border-semantic-border-input-focus/50 focus:shadow-[0_0_0_1px_rgba(43,188,202,0.15)]"
4208
- onClick={() => openNativeTimePicker(startTimeInputRef.current)}
4209
- onChange={(event) =>
4602
+ <TimeField
4603
+ id={\`\${triggerId}-start-time\`}
4604
+ label={startTimeLabel}
4605
+ value={currentValue.startTime}
4606
+ showSeconds={resolvedShowSeconds}
4607
+ minuteStep={minuteStep}
4608
+ secondStep={secondStep}
4609
+ portalMount={portalMount}
4610
+ strategy={floatingStrategy}
4611
+ open={openTimeField === "start"}
4612
+ onOpenChange={(nextOpen) =>
4613
+ setOpenTimeField(nextOpen ? "start" : null)
4614
+ }
4615
+ onChange={(startTime) =>
4616
+ updateValue(
4617
+ { ...currentValue, startTime },
4618
+ { hasTimeValue: true }
4619
+ )
4620
+ }
4621
+ />
4622
+
4623
+ {resolvedShowEndTime && (
4624
+ <TimeField
4625
+ id={\`\${triggerId}-end-time\`}
4626
+ label={endTimeLabel}
4627
+ value={currentValue.endTime}
4628
+ showSeconds={resolvedShowSeconds}
4629
+ minuteStep={minuteStep}
4630
+ secondStep={secondStep}
4631
+ portalMount={portalMount}
4632
+ strategy={floatingStrategy}
4633
+ open={openTimeField === "end"}
4634
+ onOpenChange={(nextOpen) =>
4635
+ setOpenTimeField(nextOpen ? "end" : null)
4636
+ }
4637
+ onChange={(endTime) =>
4210
4638
  updateValue(
4211
- {
4212
- ...currentValue,
4213
- startTime: normalizeTimeInputValue(
4214
- event.target.value,
4215
- currentValue.startTime
4216
- ),
4217
- },
4639
+ { ...currentValue, endTime },
4218
4640
  { hasTimeValue: true }
4219
4641
  )
4220
4642
  }
4221
4643
  />
4222
- </div>
4223
- </div>
4224
-
4225
- {resolvedShowEndTime && (
4226
- <div className="flex flex-col gap-1.5">
4227
- <label
4228
- id={\`\${triggerId}-end-time-label\`}
4229
- className="block text-sm font-semibold text-semantic-text-secondary"
4230
- >
4231
- {endTimeLabel}
4232
- </label>
4233
- <div className="relative">
4234
- <Clock2
4235
- className="pointer-events-none absolute left-3 top-1/2 size-4 -translate-y-1/2 text-semantic-text-muted"
4236
- aria-hidden="true"
4237
- />
4238
- <input
4239
- ref={endTimeInputRef}
4240
- id={\`\${triggerId}-end-time\`}
4241
- aria-labelledby={\`\${triggerId}-end-time-label\`}
4242
- type="time"
4243
- step={resolvedShowSeconds ? "1" : "60"}
4244
- value={formatTimeForTimeInput(
4245
- currentValue.endTime,
4246
- resolvedShowSeconds
4247
- )}
4248
- className="h-[42px] w-full rounded-md border border-solid border-semantic-border-input bg-semantic-bg-primary pl-9 pr-3 text-base text-semantic-text-primary outline-none transition-colors hover:border-semantic-border-input-focus/50 focus:border-semantic-border-input-focus/50 focus:shadow-[0_0_0_1px_rgba(43,188,202,0.15)]"
4249
- onClick={() => openNativeTimePicker(endTimeInputRef.current)}
4250
- onChange={(event) =>
4251
- updateValue(
4252
- {
4253
- ...currentValue,
4254
- endTime: normalizeTimeInputValue(
4255
- event.target.value,
4256
- currentValue.endTime
4257
- ),
4258
- },
4259
- { hasTimeValue: true }
4260
- )
4261
- }
4262
- />
4263
- </div>
4264
- </div>
4265
- )}
4644
+ )}
4266
4645
  </div>
4267
4646
  )}
4268
4647
  </div>,
@@ -4303,7 +4682,9 @@ const DateTimePicker = React.forwardRef<HTMLDivElement, DateTimePickerProps>(
4303
4682
  open &&
4304
4683
  state !== "error" &&
4305
4684
  "border-semantic-border-input-focus/50 shadow-[0_0_0_1px_rgba(43,188,202,0.15)]",
4306
- !displayValue && "text-semantic-text-placeholder"
4685
+ !displayValue && "text-semantic-text-placeholder",
4686
+ disabled &&
4687
+ "cursor-not-allowed bg-semantic-bg-ui text-semantic-text-muted hover:border-semantic-border-input"
4307
4688
  )}
4308
4689
  >
4309
4690
  <input
@@ -11372,7 +11753,7 @@ const TooltipContent = React.forwardRef(({ className, sideOffset = 4, ...props }
11372
11753
  ref={ref}
11373
11754
  sideOffset={sideOffset}
11374
11755
  className={cn(
11375
- "z-[9999] pointer-events-none data-[state=delayed-open]:pointer-events-auto data-[state=instant-open]:pointer-events-auto overflow-hidden rounded-md bg-semantic-primary px-3 py-1.5 text-xs text-semantic-text-inverted shadow-md animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 max-w-xs whitespace-normal",
11756
+ "z-[9999] pointer-events-none data-[state=delayed-open]:pointer-events-auto data-[state=instant-open]:pointer-events-auto rounded-md bg-semantic-primary px-3 py-1.5 text-xs text-semantic-text-inverted shadow-md animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 max-w-xs whitespace-normal",
11376
11757
  className
11377
11758
  )}
11378
11759
  {...props}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "myoperator-mcp",
3
- "version": "0.2.356",
3
+ "version": "0.2.358",
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",