myoperator-mcp 0.2.329 → 0.2.331
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 +324 -103
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -2523,7 +2523,7 @@ import { cn } from "@/lib/utils";
|
|
|
2523
2523
|
|
|
2524
2524
|
const DEFAULT_START_TIME = "10:30:00";
|
|
2525
2525
|
const DEFAULT_END_TIME = "12:30:00";
|
|
2526
|
-
const DEFAULT_PLACEHOLDER = "
|
|
2526
|
+
const DEFAULT_PLACEHOLDER = "--/--/---- --:-- --";
|
|
2527
2527
|
const POPOVER_WIDTH = 336;
|
|
2528
2528
|
const POPOVER_MARGIN = 8;
|
|
2529
2529
|
const POPOVER_GAP = 4;
|
|
@@ -2533,17 +2533,8 @@ const POPOVER_WIDTH_VAR = "--date-time-picker-popover-width";
|
|
|
2533
2533
|
const CALENDAR_PLACEMENT: Placement = "bottom-start";
|
|
2534
2534
|
const YEAR_RANGE_BEFORE = 100;
|
|
2535
2535
|
const YEAR_RANGE_AFTER = 10;
|
|
2536
|
-
const
|
|
2536
|
+
const CALENDAR_DROPDOWN_TRIGGER_CLASS =
|
|
2537
2537
|
"h-9 min-w-[90px] rounded-md border border-solid border-semantic-border-input bg-semantic-bg-primary px-3 text-sm 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)]";
|
|
2538
|
-
const DATE_TIME_INPUT_SEGMENT_RANGES = {
|
|
2539
|
-
day: [0, 2],
|
|
2540
|
-
month: [3, 5],
|
|
2541
|
-
year: [6, 10],
|
|
2542
|
-
hour: [11, 13],
|
|
2543
|
-
minute: [14, 16],
|
|
2544
|
-
meridiem: [17, 19],
|
|
2545
|
-
} as const;
|
|
2546
|
-
|
|
2547
2538
|
const weekDays = ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"];
|
|
2548
2539
|
const monthNames = Array.from({ length: 12 }, (_, monthIndex) =>
|
|
2549
2540
|
new Intl.DateTimeFormat("en-US", { month: "short" }).format(
|
|
@@ -2615,6 +2606,7 @@ export interface DateTimePickerProps
|
|
|
2615
2606
|
readOnly?: boolean;
|
|
2616
2607
|
name?: string;
|
|
2617
2608
|
showEndTime?: boolean;
|
|
2609
|
+
showSeconds?: boolean;
|
|
2618
2610
|
showClear?: boolean;
|
|
2619
2611
|
closeOnSelect?: boolean;
|
|
2620
2612
|
startTimeLabel?: string;
|
|
@@ -2631,6 +2623,13 @@ export interface DateTimePickerProps
|
|
|
2631
2623
|
type DateTimePickerVariant = NonNullable<
|
|
2632
2624
|
VariantProps<typeof dateTimePickerVariants>["variant"]
|
|
2633
2625
|
>;
|
|
2626
|
+
type CalendarDropdownKind = "month" | "year";
|
|
2627
|
+
|
|
2628
|
+
interface CalendarDropdownOption {
|
|
2629
|
+
value: string;
|
|
2630
|
+
label: string;
|
|
2631
|
+
disabled?: boolean;
|
|
2632
|
+
}
|
|
2634
2633
|
|
|
2635
2634
|
function normalizeValue(
|
|
2636
2635
|
value?: Partial<DateTimePickerValue>
|
|
@@ -2746,22 +2745,34 @@ function isPointerInsideElement(
|
|
|
2746
2745
|
return false;
|
|
2747
2746
|
}
|
|
2748
2747
|
|
|
2749
|
-
function
|
|
2750
|
-
const [
|
|
2748
|
+
function timeHasVisibleSeconds(time?: string) {
|
|
2749
|
+
const [, , second = "00"] = (time ?? "").split(":");
|
|
2750
|
+
return /^\\d{1,2}$/.test(second) && Number(second) !== 0;
|
|
2751
|
+
}
|
|
2752
|
+
|
|
2753
|
+
function formatTimeForDisplay(time: string, showSeconds = false) {
|
|
2754
|
+
const [hour = "0", minute = "0", second = "00"] = time.split(":");
|
|
2751
2755
|
const hourNumber = Number(hour);
|
|
2752
2756
|
const suffix = hourNumber >= 12 ? "PM" : "AM";
|
|
2753
2757
|
const hour12 = hourNumber % 12 || 12;
|
|
2758
|
+
const normalizedSecond = second.padStart(2, "0");
|
|
2759
|
+
const formattedTime = showSeconds
|
|
2760
|
+
? \`\${hour12.toString().padStart(2, "0")}:\${minute.padStart(2, "0")}:\${normalizedSecond}\`
|
|
2761
|
+
: \`\${hour12.toString().padStart(2, "0")}:\${minute.padStart(2, "0")}\`;
|
|
2754
2762
|
|
|
2755
|
-
return \`\${
|
|
2763
|
+
return \`\${formattedTime} \${suffix}\`;
|
|
2756
2764
|
}
|
|
2757
2765
|
|
|
2758
|
-
function formatDateForDisplay(date?: Date, time?: string) {
|
|
2766
|
+
function formatDateForDisplay(date?: Date, time?: string, showSeconds = false) {
|
|
2759
2767
|
if (!date) return "";
|
|
2760
2768
|
|
|
2761
2769
|
const month = (date.getMonth() + 1).toString().padStart(2, "0");
|
|
2762
2770
|
const day = date.getDate().toString().padStart(2, "0");
|
|
2763
2771
|
const year = date.getFullYear();
|
|
2764
|
-
const formattedTime = formatTimeForDisplay(
|
|
2772
|
+
const formattedTime = formatTimeForDisplay(
|
|
2773
|
+
time ?? DEFAULT_START_TIME,
|
|
2774
|
+
showSeconds
|
|
2775
|
+
);
|
|
2765
2776
|
|
|
2766
2777
|
return \`\${day}/\${month}/\${year} \${formattedTime}\`;
|
|
2767
2778
|
}
|
|
@@ -2780,7 +2791,8 @@ function formatValueForDisplay(
|
|
|
2780
2791
|
value: DateTimePickerValue,
|
|
2781
2792
|
variant: DateTimePickerVariant,
|
|
2782
2793
|
showEndTime: boolean,
|
|
2783
|
-
hasTimeValue: boolean
|
|
2794
|
+
hasTimeValue: boolean,
|
|
2795
|
+
showSeconds: boolean
|
|
2784
2796
|
) {
|
|
2785
2797
|
if (variant === "date-only") {
|
|
2786
2798
|
return formatDateOnlyForDisplay(value.date);
|
|
@@ -2790,16 +2802,23 @@ function formatValueForDisplay(
|
|
|
2790
2802
|
if (!hasTimeValue) return "";
|
|
2791
2803
|
|
|
2792
2804
|
return showEndTime
|
|
2793
|
-
? \`\${formatTimeForDisplay(value.startTime)} - \${formatTimeForDisplay(value.endTime)}\`
|
|
2794
|
-
: formatTimeForDisplay(value.startTime);
|
|
2805
|
+
? \`\${formatTimeForDisplay(value.startTime, showSeconds)} - \${formatTimeForDisplay(value.endTime, showSeconds)}\`
|
|
2806
|
+
: formatTimeForDisplay(value.startTime, showSeconds);
|
|
2795
2807
|
}
|
|
2796
2808
|
|
|
2797
|
-
return formatDateForDisplay(value.date, value.startTime);
|
|
2809
|
+
return formatDateForDisplay(value.date, value.startTime, showSeconds);
|
|
2798
2810
|
}
|
|
2799
2811
|
|
|
2800
|
-
function getDefaultPlaceholder(
|
|
2812
|
+
function getDefaultPlaceholder(
|
|
2813
|
+
variant: DateTimePickerVariant,
|
|
2814
|
+
showSeconds: boolean
|
|
2815
|
+
) {
|
|
2801
2816
|
if (variant === "date-only") return "--/--/----";
|
|
2802
|
-
if (variant === "time-only")
|
|
2817
|
+
if (variant === "time-only") {
|
|
2818
|
+
return showSeconds ? "--:--:-- --" : "--:-- --";
|
|
2819
|
+
}
|
|
2820
|
+
|
|
2821
|
+
if (showSeconds) return "--/--/---- --:--:-- --";
|
|
2803
2822
|
|
|
2804
2823
|
return DEFAULT_PLACEHOLDER;
|
|
2805
2824
|
}
|
|
@@ -3015,16 +3034,20 @@ function splitTypedDateInput(value: string) {
|
|
|
3015
3034
|
};
|
|
3016
3035
|
}
|
|
3017
3036
|
|
|
3018
|
-
function isPotentiallyValidTimeDigits(timeDigits: string) {
|
|
3037
|
+
function isPotentiallyValidTimeDigits(timeDigits: string, showSeconds: boolean) {
|
|
3019
3038
|
const hourValue = timeDigits.slice(0, 2);
|
|
3020
3039
|
const minuteValue = timeDigits.slice(2, 4);
|
|
3040
|
+
const secondValue = showSeconds ? timeDigits.slice(4, 6) : "";
|
|
3021
3041
|
const hour = Number(hourValue);
|
|
3022
3042
|
const minute = Number(minuteValue);
|
|
3043
|
+
const second = Number(secondValue);
|
|
3023
3044
|
|
|
3024
3045
|
if (hourValue.length === 1 && hour > 1) return false;
|
|
3025
3046
|
if (hourValue.length === 2 && (hour < 1 || hour > 12)) return false;
|
|
3026
3047
|
if (minuteValue.length === 1 && minute > 5) return false;
|
|
3027
3048
|
if (minuteValue.length === 2 && minute > 59) return false;
|
|
3049
|
+
if (showSeconds && secondValue.length === 1 && second > 5) return false;
|
|
3050
|
+
if (showSeconds && secondValue.length === 2 && second > 59) return false;
|
|
3028
3051
|
|
|
3029
3052
|
return true;
|
|
3030
3053
|
}
|
|
@@ -3037,21 +3060,25 @@ function formatMeridiemInput(letters: string) {
|
|
|
3037
3060
|
return null;
|
|
3038
3061
|
}
|
|
3039
3062
|
|
|
3040
|
-
function formatTimeInput(restValue: string) {
|
|
3063
|
+
function formatTimeInput(restValue: string, showSeconds: boolean) {
|
|
3041
3064
|
const normalizedValue = restValue.toUpperCase();
|
|
3042
|
-
const
|
|
3065
|
+
const maxTimeDigits = showSeconds ? 6 : 4;
|
|
3066
|
+
const timeDigits = normalizedValue.replace(/\\D/g, "").slice(0, maxTimeDigits);
|
|
3043
3067
|
const meridiemLetters = normalizedValue.replace(/[^A-Z]/g, "");
|
|
3044
3068
|
const meridiem = formatMeridiemInput(meridiemLetters.slice(0, 2));
|
|
3045
3069
|
|
|
3046
3070
|
if (!timeDigits && !meridiem) return "";
|
|
3047
|
-
if (!isPotentiallyValidTimeDigits(timeDigits) || meridiem === null) {
|
|
3071
|
+
if (!isPotentiallyValidTimeDigits(timeDigits, showSeconds) || meridiem === null) {
|
|
3048
3072
|
return null;
|
|
3049
3073
|
}
|
|
3050
3074
|
|
|
3051
|
-
|
|
3052
|
-
|
|
3053
|
-
|
|
3054
|
-
|
|
3075
|
+
let timeValue = timeDigits.slice(0, 2);
|
|
3076
|
+
if (timeDigits.length > 2) {
|
|
3077
|
+
timeValue = \`\${timeValue}:\${timeDigits.slice(2, 4)}\`;
|
|
3078
|
+
}
|
|
3079
|
+
if (showSeconds && timeDigits.length > 4) {
|
|
3080
|
+
timeValue = \`\${timeValue}:\${timeDigits.slice(4, 6)}\`;
|
|
3081
|
+
}
|
|
3055
3082
|
|
|
3056
3083
|
return [timeValue, meridiem].filter(Boolean).join(" ");
|
|
3057
3084
|
}
|
|
@@ -3070,20 +3097,28 @@ function sanitizeTypedDateInput(value: string, previousValue: string) {
|
|
|
3070
3097
|
return dateValue;
|
|
3071
3098
|
}
|
|
3072
3099
|
|
|
3073
|
-
function sanitizeTypedTimeInput(
|
|
3100
|
+
function sanitizeTypedTimeInput(
|
|
3101
|
+
value: string,
|
|
3102
|
+
previousValue: string,
|
|
3103
|
+
showSeconds: boolean
|
|
3104
|
+
) {
|
|
3074
3105
|
const trimmedValue = value.trimStart();
|
|
3075
3106
|
if (/^[A-Za-z]/.test(trimmedValue) && !/^[AP]/i.test(trimmedValue)) {
|
|
3076
3107
|
return previousValue;
|
|
3077
3108
|
}
|
|
3078
3109
|
|
|
3079
3110
|
const normalizedValue = value.toUpperCase().replace(/[^0-9\\s:APM]/g, "");
|
|
3080
|
-
const formattedTime = formatTimeInput(normalizedValue);
|
|
3111
|
+
const formattedTime = formatTimeInput(normalizedValue, showSeconds);
|
|
3081
3112
|
if (formattedTime === null) return previousValue;
|
|
3082
3113
|
|
|
3083
3114
|
return formattedTime;
|
|
3084
3115
|
}
|
|
3085
3116
|
|
|
3086
|
-
function sanitizeTypedDateTimeInput(
|
|
3117
|
+
function sanitizeTypedDateTimeInput(
|
|
3118
|
+
value: string,
|
|
3119
|
+
previousValue: string,
|
|
3120
|
+
showSeconds: boolean
|
|
3121
|
+
) {
|
|
3087
3122
|
const trimmedValue = value.trimStart();
|
|
3088
3123
|
if (/^[A-Za-z]/.test(trimmedValue)) return previousValue;
|
|
3089
3124
|
|
|
@@ -3097,22 +3132,59 @@ function sanitizeTypedDateTimeInput(value: string, previousValue: string) {
|
|
|
3097
3132
|
if (!limitedDateDigits) return "";
|
|
3098
3133
|
if (!isValid) return previousValue;
|
|
3099
3134
|
|
|
3100
|
-
const formattedTime = isComplete ? formatTimeInput(restValue) : "";
|
|
3135
|
+
const formattedTime = isComplete ? formatTimeInput(restValue, showSeconds) : "";
|
|
3101
3136
|
if (formattedTime === null) return previousValue;
|
|
3102
3137
|
|
|
3103
3138
|
return [dateValue, formattedTime].filter(Boolean).join(" ");
|
|
3104
3139
|
}
|
|
3105
3140
|
|
|
3106
|
-
type DateTimeInputSegment =
|
|
3107
|
-
|
|
3108
|
-
|
|
3109
|
-
|
|
3110
|
-
|
|
3111
|
-
|
|
3112
|
-
|
|
3113
|
-
|
|
3141
|
+
type DateTimeInputSegment =
|
|
3142
|
+
| "day"
|
|
3143
|
+
| "month"
|
|
3144
|
+
| "year"
|
|
3145
|
+
| "hour"
|
|
3146
|
+
| "minute"
|
|
3147
|
+
| "second"
|
|
3148
|
+
| "meridiem";
|
|
3149
|
+
|
|
3150
|
+
function getDateTimeInputSegmentRanges(showSeconds: boolean) {
|
|
3151
|
+
return showSeconds
|
|
3152
|
+
? ({
|
|
3153
|
+
day: [0, 2],
|
|
3154
|
+
month: [3, 5],
|
|
3155
|
+
year: [6, 10],
|
|
3156
|
+
hour: [11, 13],
|
|
3157
|
+
minute: [14, 16],
|
|
3158
|
+
second: [17, 19],
|
|
3159
|
+
meridiem: [20, 22],
|
|
3160
|
+
} satisfies Record<DateTimeInputSegment, readonly [number, number]>)
|
|
3161
|
+
: ({
|
|
3162
|
+
day: [0, 2],
|
|
3163
|
+
month: [3, 5],
|
|
3164
|
+
year: [6, 10],
|
|
3165
|
+
hour: [11, 13],
|
|
3166
|
+
minute: [14, 16],
|
|
3167
|
+
second: [17, 19],
|
|
3168
|
+
meridiem: [17, 19],
|
|
3169
|
+
} satisfies Record<DateTimeInputSegment, readonly [number, number]>);
|
|
3170
|
+
}
|
|
3171
|
+
|
|
3172
|
+
function getDateTimeInputSegment(
|
|
3173
|
+
cursorPosition: number,
|
|
3174
|
+
showSeconds: boolean
|
|
3175
|
+
): DateTimeInputSegment {
|
|
3176
|
+
const ranges = getDateTimeInputSegmentRanges(showSeconds);
|
|
3177
|
+
|
|
3178
|
+
if (cursorPosition <= ranges.day[1]) return "day";
|
|
3179
|
+
if (cursorPosition <= ranges.month[1]) return "month";
|
|
3180
|
+
if (cursorPosition <= ranges.year[1]) return "year";
|
|
3181
|
+
if (cursorPosition <= ranges.hour[1]) return "hour";
|
|
3182
|
+
if (cursorPosition <= ranges.minute[1]) {
|
|
3114
3183
|
return "minute";
|
|
3115
3184
|
}
|
|
3185
|
+
if (showSeconds && cursorPosition <= ranges.second[1]) {
|
|
3186
|
+
return "second";
|
|
3187
|
+
}
|
|
3116
3188
|
|
|
3117
3189
|
return "meridiem";
|
|
3118
3190
|
}
|
|
@@ -3125,12 +3197,13 @@ function stepDateTimeInputValue(
|
|
|
3125
3197
|
value: string,
|
|
3126
3198
|
cursorPosition: number,
|
|
3127
3199
|
direction: 1 | -1,
|
|
3128
|
-
fallbackTime: string
|
|
3200
|
+
fallbackTime: string,
|
|
3201
|
+
showSeconds: boolean
|
|
3129
3202
|
) {
|
|
3130
3203
|
const typedDateTime = parseTypedDateTime(value);
|
|
3131
3204
|
if (!typedDateTime) return null;
|
|
3132
3205
|
|
|
3133
|
-
const segment = getDateTimeInputSegment(cursorPosition);
|
|
3206
|
+
const segment = getDateTimeInputSegment(cursorPosition, showSeconds);
|
|
3134
3207
|
const nextDate = new Date(typedDateTime.date);
|
|
3135
3208
|
const [hourValue = "0", minuteValue = "0", secondValue = "00"] = (
|
|
3136
3209
|
typedDateTime.startTime ?? fallbackTime
|
|
@@ -3167,6 +3240,8 @@ function stepDateTimeInputValue(
|
|
|
3167
3240
|
nextDate.setHours(nextDate.getHours() + direction);
|
|
3168
3241
|
} else if (segment === "minute") {
|
|
3169
3242
|
nextDate.setMinutes(nextDate.getMinutes() + direction);
|
|
3243
|
+
} else if (segment === "second") {
|
|
3244
|
+
nextDate.setSeconds(nextDate.getSeconds() + direction);
|
|
3170
3245
|
} else {
|
|
3171
3246
|
nextDate.setHours(nextDate.getHours() + 12 * direction);
|
|
3172
3247
|
}
|
|
@@ -3189,6 +3264,34 @@ function stepDateTimeInputValue(
|
|
|
3189
3264
|
};
|
|
3190
3265
|
}
|
|
3191
3266
|
|
|
3267
|
+
function formatTimeForTimeInput(time: string, showSeconds: boolean) {
|
|
3268
|
+
const normalizedTime = parseTimePart(time) ?? DEFAULT_START_TIME;
|
|
3269
|
+
const [hour = "00", minute = "00", second = "00"] = normalizedTime.split(":");
|
|
3270
|
+
|
|
3271
|
+
return showSeconds ? \`\${hour}:\${minute}:\${second}\` : \`\${hour}:\${minute}\`;
|
|
3272
|
+
}
|
|
3273
|
+
|
|
3274
|
+
function normalizeTimeInputValue(time: string, fallbackTime: string) {
|
|
3275
|
+
return parseTimePart(time) ?? parseTimePart(fallbackTime) ?? DEFAULT_START_TIME;
|
|
3276
|
+
}
|
|
3277
|
+
|
|
3278
|
+
function openNativeTimePicker(input: HTMLInputElement | null) {
|
|
3279
|
+
if (!input) return;
|
|
3280
|
+
|
|
3281
|
+
input.focus();
|
|
3282
|
+
|
|
3283
|
+
const showPicker = (
|
|
3284
|
+
input as HTMLInputElement & { showPicker?: () => void }
|
|
3285
|
+
).showPicker;
|
|
3286
|
+
if (!showPicker) return;
|
|
3287
|
+
|
|
3288
|
+
try {
|
|
3289
|
+
showPicker.call(input);
|
|
3290
|
+
} catch {
|
|
3291
|
+
input.focus();
|
|
3292
|
+
}
|
|
3293
|
+
}
|
|
3294
|
+
|
|
3192
3295
|
function parseTypedDateTime(value: string) {
|
|
3193
3296
|
const typedValue = value.trim();
|
|
3194
3297
|
if (!typedValue) return undefined;
|
|
@@ -3268,6 +3371,85 @@ function FigmaCalendarIcon({ className }: { className?: string }) {
|
|
|
3268
3371
|
);
|
|
3269
3372
|
}
|
|
3270
3373
|
|
|
3374
|
+
function CalendarDropdown({
|
|
3375
|
+
id,
|
|
3376
|
+
label,
|
|
3377
|
+
value,
|
|
3378
|
+
options,
|
|
3379
|
+
open,
|
|
3380
|
+
onOpenChange,
|
|
3381
|
+
onValueChange,
|
|
3382
|
+
}: {
|
|
3383
|
+
id: string;
|
|
3384
|
+
label: string;
|
|
3385
|
+
value: string;
|
|
3386
|
+
options: CalendarDropdownOption[];
|
|
3387
|
+
open: boolean;
|
|
3388
|
+
onOpenChange: (open: boolean) => void;
|
|
3389
|
+
onValueChange: (value: string) => void;
|
|
3390
|
+
}) {
|
|
3391
|
+
const selectedOption = options.find((option) => option.value === value);
|
|
3392
|
+
|
|
3393
|
+
return (
|
|
3394
|
+
<div className="relative">
|
|
3395
|
+
<label className="sr-only" htmlFor={id}>
|
|
3396
|
+
{label}
|
|
3397
|
+
</label>
|
|
3398
|
+
<button
|
|
3399
|
+
id={id}
|
|
3400
|
+
type="button"
|
|
3401
|
+
role="combobox"
|
|
3402
|
+
aria-label={label}
|
|
3403
|
+
aria-expanded={open}
|
|
3404
|
+
aria-controls={\`\${id}-options\`}
|
|
3405
|
+
data-value={value}
|
|
3406
|
+
className={cn(
|
|
3407
|
+
CALENDAR_DROPDOWN_TRIGGER_CLASS,
|
|
3408
|
+
"inline-flex items-center justify-between gap-2"
|
|
3409
|
+
)}
|
|
3410
|
+
onClick={() => onOpenChange(!open)}
|
|
3411
|
+
>
|
|
3412
|
+
<span>{selectedOption?.label ?? value}</span>
|
|
3413
|
+
<ChevronRight
|
|
3414
|
+
className={cn("size-3 transition-transform", open && "-rotate-90")}
|
|
3415
|
+
aria-hidden="true"
|
|
3416
|
+
/>
|
|
3417
|
+
</button>
|
|
3418
|
+
{open && (
|
|
3419
|
+
<div
|
|
3420
|
+
id={\`\${id}-options\`}
|
|
3421
|
+
role="listbox"
|
|
3422
|
+
aria-label={\`\${label} options\`}
|
|
3423
|
+
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"
|
|
3424
|
+
>
|
|
3425
|
+
{options.map((option) => (
|
|
3426
|
+
<button
|
|
3427
|
+
key={option.value}
|
|
3428
|
+
type="button"
|
|
3429
|
+
role="option"
|
|
3430
|
+
aria-label={option.label}
|
|
3431
|
+
aria-selected={option.value === value}
|
|
3432
|
+
disabled={option.disabled}
|
|
3433
|
+
className={cn(
|
|
3434
|
+
"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",
|
|
3435
|
+
option.value === value && "bg-semantic-primary text-semantic-text-inverted"
|
|
3436
|
+
)}
|
|
3437
|
+
onClick={() => {
|
|
3438
|
+
if (option.disabled) return;
|
|
3439
|
+
|
|
3440
|
+
onValueChange(option.value);
|
|
3441
|
+
onOpenChange(false);
|
|
3442
|
+
}}
|
|
3443
|
+
>
|
|
3444
|
+
{option.label}
|
|
3445
|
+
</button>
|
|
3446
|
+
))}
|
|
3447
|
+
</div>
|
|
3448
|
+
)}
|
|
3449
|
+
</div>
|
|
3450
|
+
);
|
|
3451
|
+
}
|
|
3452
|
+
|
|
3271
3453
|
const DateTimePicker = React.forwardRef<HTMLDivElement, DateTimePickerProps>(
|
|
3272
3454
|
(
|
|
3273
3455
|
{
|
|
@@ -3283,6 +3465,7 @@ const DateTimePicker = React.forwardRef<HTMLDivElement, DateTimePickerProps>(
|
|
|
3283
3465
|
readOnly = false,
|
|
3284
3466
|
name,
|
|
3285
3467
|
showEndTime = true,
|
|
3468
|
+
showSeconds,
|
|
3286
3469
|
showClear = true,
|
|
3287
3470
|
closeOnSelect = false,
|
|
3288
3471
|
startTimeLabel = "Start Time",
|
|
@@ -3305,7 +3488,6 @@ const DateTimePicker = React.forwardRef<HTMLDivElement, DateTimePickerProps>(
|
|
|
3305
3488
|
const showCalendar = pickerVariant !== "time-only";
|
|
3306
3489
|
const showTimeFields = pickerVariant !== "date-only";
|
|
3307
3490
|
const resolvedShowEndTime = showTimeFields && showEndTime;
|
|
3308
|
-
const placeholder = placeholderProp ?? getDefaultPlaceholder(pickerVariant);
|
|
3309
3491
|
const isValueControlled = value !== undefined;
|
|
3310
3492
|
const isOpenControlled = controlledOpen !== undefined;
|
|
3311
3493
|
const [internalValue, setInternalValue] = React.useState(() =>
|
|
@@ -3321,17 +3503,33 @@ const DateTimePicker = React.forwardRef<HTMLDivElement, DateTimePickerProps>(
|
|
|
3321
3503
|
const hasTimeValue = isValueControlled
|
|
3322
3504
|
? Boolean(value?.date || value?.startTime || value?.endTime)
|
|
3323
3505
|
: internalHasTimeValue;
|
|
3506
|
+
const resolvedShowSeconds =
|
|
3507
|
+
showSeconds ??
|
|
3508
|
+
(timeHasVisibleSeconds(currentValue.startTime) ||
|
|
3509
|
+
(resolvedShowEndTime && timeHasVisibleSeconds(currentValue.endTime)));
|
|
3510
|
+
const placeholder =
|
|
3511
|
+
placeholderProp ?? getDefaultPlaceholder(pickerVariant, resolvedShowSeconds);
|
|
3324
3512
|
const open = isOpenControlled ? controlledOpen : internalOpen;
|
|
3325
3513
|
const [visibleMonth, setVisibleMonth] = React.useState(() =>
|
|
3326
3514
|
startOfMonth(currentValue.date ?? new Date())
|
|
3327
3515
|
);
|
|
3328
3516
|
const [dateInputValue, setDateInputValue] = React.useState(() =>
|
|
3329
|
-
|
|
3517
|
+
formatValueForDisplay(
|
|
3518
|
+
currentValue,
|
|
3519
|
+
pickerVariant,
|
|
3520
|
+
resolvedShowEndTime,
|
|
3521
|
+
hasTimeValue,
|
|
3522
|
+
resolvedShowSeconds
|
|
3523
|
+
)
|
|
3330
3524
|
);
|
|
3331
3525
|
const [isDateInputFocused, setIsDateInputFocused] = React.useState(false);
|
|
3332
3526
|
const rootRef = React.useRef<HTMLDivElement | null>(null);
|
|
3333
3527
|
const triggerRef = React.useRef<HTMLDivElement | null>(null);
|
|
3334
3528
|
const popoverRef = React.useRef<HTMLDivElement | null>(null);
|
|
3529
|
+
const startTimeInputRef = React.useRef<HTMLInputElement | null>(null);
|
|
3530
|
+
const endTimeInputRef = React.useRef<HTMLInputElement | null>(null);
|
|
3531
|
+
const [openCalendarDropdown, setOpenCalendarDropdown] =
|
|
3532
|
+
React.useState<CalendarDropdownKind | null>(null);
|
|
3335
3533
|
const usesContainerPortal = portalContainer !== undefined;
|
|
3336
3534
|
const floatingStrategy: Strategy = usesContainerPortal
|
|
3337
3535
|
? "absolute"
|
|
@@ -3378,7 +3576,8 @@ const DateTimePicker = React.forwardRef<HTMLDivElement, DateTimePickerProps>(
|
|
|
3378
3576
|
currentValue,
|
|
3379
3577
|
pickerVariant,
|
|
3380
3578
|
resolvedShowEndTime,
|
|
3381
|
-
hasTimeValue
|
|
3579
|
+
hasTimeValue,
|
|
3580
|
+
resolvedShowSeconds
|
|
3382
3581
|
);
|
|
3383
3582
|
const effectiveMinDate = React.useMemo(() => {
|
|
3384
3583
|
if (!disablePastDates) return minDate;
|
|
@@ -3397,6 +3596,10 @@ const DateTimePicker = React.forwardRef<HTMLDivElement, DateTimePickerProps>(
|
|
|
3397
3596
|
|
|
3398
3597
|
const setOpen = React.useCallback(
|
|
3399
3598
|
(nextOpen: boolean) => {
|
|
3599
|
+
if (!nextOpen) {
|
|
3600
|
+
setOpenCalendarDropdown(null);
|
|
3601
|
+
}
|
|
3602
|
+
|
|
3400
3603
|
if (!isOpenControlled) {
|
|
3401
3604
|
setInternalOpen(nextOpen);
|
|
3402
3605
|
}
|
|
@@ -3561,7 +3764,8 @@ const DateTimePicker = React.forwardRef<HTMLDivElement, DateTimePickerProps>(
|
|
|
3561
3764
|
if (pickerVariant === "time-only") {
|
|
3562
3765
|
const nextInputValue = sanitizeTypedTimeInput(
|
|
3563
3766
|
event.target.value,
|
|
3564
|
-
dateInputValue
|
|
3767
|
+
dateInputValue,
|
|
3768
|
+
resolvedShowSeconds
|
|
3565
3769
|
);
|
|
3566
3770
|
setDateInputValue(nextInputValue);
|
|
3567
3771
|
|
|
@@ -3627,7 +3831,8 @@ const DateTimePicker = React.forwardRef<HTMLDivElement, DateTimePickerProps>(
|
|
|
3627
3831
|
|
|
3628
3832
|
const nextInputValue = sanitizeTypedDateTimeInput(
|
|
3629
3833
|
event.target.value,
|
|
3630
|
-
dateInputValue
|
|
3834
|
+
dateInputValue,
|
|
3835
|
+
resolvedShowSeconds
|
|
3631
3836
|
);
|
|
3632
3837
|
setDateInputValue(nextInputValue);
|
|
3633
3838
|
|
|
@@ -3675,7 +3880,8 @@ const DateTimePicker = React.forwardRef<HTMLDivElement, DateTimePickerProps>(
|
|
|
3675
3880
|
dateInputValue,
|
|
3676
3881
|
cursorPosition,
|
|
3677
3882
|
direction,
|
|
3678
|
-
currentValue.startTime
|
|
3883
|
+
currentValue.startTime,
|
|
3884
|
+
resolvedShowSeconds
|
|
3679
3885
|
);
|
|
3680
3886
|
|
|
3681
3887
|
if (!steppedDateTime) return;
|
|
@@ -3691,10 +3897,13 @@ const DateTimePicker = React.forwardRef<HTMLDivElement, DateTimePickerProps>(
|
|
|
3691
3897
|
|
|
3692
3898
|
const nextInputValue = formatDateForDisplay(
|
|
3693
3899
|
steppedDateTime.date,
|
|
3694
|
-
steppedDateTime.startTime
|
|
3900
|
+
steppedDateTime.startTime,
|
|
3901
|
+
resolvedShowSeconds
|
|
3695
3902
|
);
|
|
3903
|
+
const dateTimeInputSegmentRanges =
|
|
3904
|
+
getDateTimeInputSegmentRanges(resolvedShowSeconds);
|
|
3696
3905
|
const [selectionStart, selectionEnd] =
|
|
3697
|
-
|
|
3906
|
+
dateTimeInputSegmentRanges[steppedDateTime.segment];
|
|
3698
3907
|
const resolvedInputValue =
|
|
3699
3908
|
pickerVariant === "date-only"
|
|
3700
3909
|
? formatDateOnlyForDisplay(steppedDateTime.date)
|
|
@@ -3723,7 +3932,8 @@ const DateTimePicker = React.forwardRef<HTMLDivElement, DateTimePickerProps>(
|
|
|
3723
3932
|
currentValue,
|
|
3724
3933
|
pickerVariant,
|
|
3725
3934
|
resolvedShowEndTime,
|
|
3726
|
-
hasTimeValue
|
|
3935
|
+
hasTimeValue,
|
|
3936
|
+
resolvedShowSeconds
|
|
3727
3937
|
)
|
|
3728
3938
|
);
|
|
3729
3939
|
};
|
|
@@ -3773,25 +3983,15 @@ const DateTimePicker = React.forwardRef<HTMLDivElement, DateTimePickerProps>(
|
|
|
3773
3983
|
<ChevronLeft className="size-4" aria-hidden="true" />
|
|
3774
3984
|
</button>
|
|
3775
3985
|
<div className="flex min-w-0 items-center gap-1.5">
|
|
3776
|
-
<
|
|
3777
|
-
Month
|
|
3778
|
-
</label>
|
|
3779
|
-
<select
|
|
3986
|
+
<CalendarDropdown
|
|
3780
3987
|
id={\`\${triggerId}-month\`}
|
|
3781
|
-
|
|
3782
|
-
className={CALENDAR_NATIVE_SELECT_CLASS}
|
|
3988
|
+
label="Month"
|
|
3783
3989
|
value={visibleMonth.getMonth().toString()}
|
|
3784
|
-
|
|
3785
|
-
|
|
3786
|
-
|
|
3787
|
-
|
|
3788
|
-
|
|
3789
|
-
1
|
|
3790
|
-
)
|
|
3791
|
-
);
|
|
3792
|
-
}}
|
|
3793
|
-
>
|
|
3794
|
-
{monthNames.map((monthName, monthIndex) => {
|
|
3990
|
+
open={openCalendarDropdown === "month"}
|
|
3991
|
+
onOpenChange={(nextOpen) =>
|
|
3992
|
+
setOpenCalendarDropdown(nextOpen ? "month" : null)
|
|
3993
|
+
}
|
|
3994
|
+
options={monthNames.map((monthName, monthIndex) => {
|
|
3795
3995
|
const optionMonth = new Date(
|
|
3796
3996
|
visibleMonth.getFullYear(),
|
|
3797
3997
|
monthIndex,
|
|
@@ -3802,41 +4002,44 @@ const DateTimePicker = React.forwardRef<HTMLDivElement, DateTimePickerProps>(
|
|
|
3802
4002
|
isMonthBefore(optionMonth, effectiveMinDate)) ||
|
|
3803
4003
|
(maxDate && isMonthAfter(optionMonth, maxDate));
|
|
3804
4004
|
|
|
3805
|
-
return
|
|
3806
|
-
|
|
3807
|
-
|
|
3808
|
-
|
|
3809
|
-
|
|
3810
|
-
>
|
|
3811
|
-
{monthName}
|
|
3812
|
-
</option>
|
|
3813
|
-
);
|
|
4005
|
+
return {
|
|
4006
|
+
value: monthIndex.toString(),
|
|
4007
|
+
label: monthName,
|
|
4008
|
+
disabled: !!isDisabled,
|
|
4009
|
+
};
|
|
3814
4010
|
})}
|
|
3815
|
-
|
|
3816
|
-
|
|
3817
|
-
|
|
3818
|
-
|
|
3819
|
-
|
|
4011
|
+
onValueChange={(nextMonth) => {
|
|
4012
|
+
syncCalendarMonthAndValue(
|
|
4013
|
+
new Date(
|
|
4014
|
+
visibleMonth.getFullYear(),
|
|
4015
|
+
Number(nextMonth),
|
|
4016
|
+
1
|
|
4017
|
+
)
|
|
4018
|
+
);
|
|
4019
|
+
}}
|
|
4020
|
+
/>
|
|
4021
|
+
<CalendarDropdown
|
|
3820
4022
|
id={\`\${triggerId}-year\`}
|
|
3821
|
-
|
|
3822
|
-
className={CALENDAR_NATIVE_SELECT_CLASS}
|
|
4023
|
+
label="Year"
|
|
3823
4024
|
value={visibleMonth.getFullYear().toString()}
|
|
3824
|
-
|
|
4025
|
+
open={openCalendarDropdown === "year"}
|
|
4026
|
+
onOpenChange={(nextOpen) =>
|
|
4027
|
+
setOpenCalendarDropdown(nextOpen ? "year" : null)
|
|
4028
|
+
}
|
|
4029
|
+
options={yearOptions.map((year) => ({
|
|
4030
|
+
value: year.toString(),
|
|
4031
|
+
label: year.toString(),
|
|
4032
|
+
}))}
|
|
4033
|
+
onValueChange={(nextYear) => {
|
|
3825
4034
|
syncCalendarMonthAndValue(
|
|
3826
4035
|
new Date(
|
|
3827
|
-
Number(
|
|
4036
|
+
Number(nextYear),
|
|
3828
4037
|
visibleMonth.getMonth(),
|
|
3829
4038
|
1
|
|
3830
4039
|
)
|
|
3831
4040
|
);
|
|
3832
4041
|
}}
|
|
3833
|
-
|
|
3834
|
-
{yearOptions.map((year) => (
|
|
3835
|
-
<option key={year} value={year.toString()}>
|
|
3836
|
-
{year}
|
|
3837
|
-
</option>
|
|
3838
|
-
))}
|
|
3839
|
-
</select>
|
|
4042
|
+
/>
|
|
3840
4043
|
<div id={\`\${triggerId}-calendar-heading\`} className="sr-only">
|
|
3841
4044
|
{monthFormatter.format(visibleMonth)}
|
|
3842
4045
|
</div>
|
|
@@ -3924,7 +4127,7 @@ const DateTimePicker = React.forwardRef<HTMLDivElement, DateTimePickerProps>(
|
|
|
3924
4127
|
>
|
|
3925
4128
|
<div className="flex flex-col gap-1.5">
|
|
3926
4129
|
<label
|
|
3927
|
-
|
|
4130
|
+
id={\`\${triggerId}-start-time-label\`}
|
|
3928
4131
|
className="block text-sm font-semibold text-semantic-text-primary"
|
|
3929
4132
|
>
|
|
3930
4133
|
{startTimeLabel}
|
|
@@ -3935,16 +4138,25 @@ const DateTimePicker = React.forwardRef<HTMLDivElement, DateTimePickerProps>(
|
|
|
3935
4138
|
aria-hidden="true"
|
|
3936
4139
|
/>
|
|
3937
4140
|
<input
|
|
4141
|
+
ref={startTimeInputRef}
|
|
3938
4142
|
id={\`\${triggerId}-start-time\`}
|
|
4143
|
+
aria-labelledby={\`\${triggerId}-start-time-label\`}
|
|
3939
4144
|
type="time"
|
|
3940
|
-
step="1"
|
|
3941
|
-
value={
|
|
4145
|
+
step={resolvedShowSeconds ? "1" : "60"}
|
|
4146
|
+
value={formatTimeForTimeInput(
|
|
4147
|
+
currentValue.startTime,
|
|
4148
|
+
resolvedShowSeconds
|
|
4149
|
+
)}
|
|
3942
4150
|
className="h-8 w-full rounded-md border border-solid border-semantic-border-input bg-semantic-bg-primary pl-9 pr-3 text-sm 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)]"
|
|
4151
|
+
onClick={() => openNativeTimePicker(startTimeInputRef.current)}
|
|
3943
4152
|
onChange={(event) =>
|
|
3944
4153
|
updateValue(
|
|
3945
4154
|
{
|
|
3946
4155
|
...currentValue,
|
|
3947
|
-
startTime:
|
|
4156
|
+
startTime: normalizeTimeInputValue(
|
|
4157
|
+
event.target.value,
|
|
4158
|
+
currentValue.startTime
|
|
4159
|
+
),
|
|
3948
4160
|
},
|
|
3949
4161
|
{ hasTimeValue: true }
|
|
3950
4162
|
)
|
|
@@ -3956,7 +4168,7 @@ const DateTimePicker = React.forwardRef<HTMLDivElement, DateTimePickerProps>(
|
|
|
3956
4168
|
{resolvedShowEndTime && (
|
|
3957
4169
|
<div className="flex flex-col gap-1.5">
|
|
3958
4170
|
<label
|
|
3959
|
-
|
|
4171
|
+
id={\`\${triggerId}-end-time-label\`}
|
|
3960
4172
|
className="block text-sm font-semibold text-semantic-text-primary"
|
|
3961
4173
|
>
|
|
3962
4174
|
{endTimeLabel}
|
|
@@ -3967,16 +4179,25 @@ const DateTimePicker = React.forwardRef<HTMLDivElement, DateTimePickerProps>(
|
|
|
3967
4179
|
aria-hidden="true"
|
|
3968
4180
|
/>
|
|
3969
4181
|
<input
|
|
4182
|
+
ref={endTimeInputRef}
|
|
3970
4183
|
id={\`\${triggerId}-end-time\`}
|
|
4184
|
+
aria-labelledby={\`\${triggerId}-end-time-label\`}
|
|
3971
4185
|
type="time"
|
|
3972
|
-
step="1"
|
|
3973
|
-
value={
|
|
4186
|
+
step={resolvedShowSeconds ? "1" : "60"}
|
|
4187
|
+
value={formatTimeForTimeInput(
|
|
4188
|
+
currentValue.endTime,
|
|
4189
|
+
resolvedShowSeconds
|
|
4190
|
+
)}
|
|
3974
4191
|
className="h-8 w-full rounded-md border border-solid border-semantic-border-input bg-semantic-bg-primary pl-9 pr-3 text-sm 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)]"
|
|
4192
|
+
onClick={() => openNativeTimePicker(endTimeInputRef.current)}
|
|
3975
4193
|
onChange={(event) =>
|
|
3976
4194
|
updateValue(
|
|
3977
4195
|
{
|
|
3978
4196
|
...currentValue,
|
|
3979
|
-
endTime:
|
|
4197
|
+
endTime: normalizeTimeInputValue(
|
|
4198
|
+
event.target.value,
|
|
4199
|
+
currentValue.endTime
|
|
4200
|
+
),
|
|
3980
4201
|
},
|
|
3981
4202
|
{ hasTimeValue: true }
|
|
3982
4203
|
)
|
package/package.json
CHANGED