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.
- package/dist/index.js +518 -137
- 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
|
-
|
|
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
|
-
|
|
3459
|
+
function padTime(value: number) {
|
|
3460
|
+
return value.toString().padStart(2, "0");
|
|
3329
3461
|
}
|
|
3330
3462
|
|
|
3331
|
-
function
|
|
3332
|
-
|
|
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
|
|
3336
|
-
|
|
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
|
-
|
|
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
|
-
|
|
3341
|
-
|
|
3342
|
-
|
|
3343
|
-
|
|
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
|
-
|
|
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
|
-
|
|
3477
|
-
|
|
3478
|
-
|
|
3479
|
-
|
|
3480
|
-
|
|
3481
|
-
|
|
3482
|
-
|
|
3483
|
-
|
|
3484
|
-
|
|
3485
|
-
|
|
3486
|
-
|
|
3487
|
-
|
|
3488
|
-
|
|
3489
|
-
|
|
3490
|
-
|
|
3491
|
-
|
|
3492
|
-
|
|
3493
|
-
|
|
3494
|
-
|
|
3495
|
-
|
|
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
|
-
|
|
3498
|
-
|
|
3499
|
-
|
|
3500
|
-
|
|
3501
|
-
|
|
3502
|
-
|
|
3503
|
-
|
|
3504
|
-
|
|
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
|
-
\`\${
|
|
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
|
-
|
|
4186
|
-
|
|
4187
|
-
|
|
4188
|
-
|
|
4189
|
-
|
|
4190
|
-
{
|
|
4191
|
-
|
|
4192
|
-
|
|
4193
|
-
|
|
4194
|
-
|
|
4195
|
-
|
|
4196
|
-
|
|
4197
|
-
|
|
4198
|
-
|
|
4199
|
-
|
|
4200
|
-
|
|
4201
|
-
|
|
4202
|
-
|
|
4203
|
-
|
|
4204
|
-
|
|
4205
|
-
|
|
4206
|
-
|
|
4207
|
-
|
|
4208
|
-
|
|
4209
|
-
|
|
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
|
-
|
|
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
|
|
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