@choice-ui/react 1.5.7 → 1.5.8
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/components/calendar/dist/index.d.ts +17 -2
- package/dist/components/calendar/dist/index.js +59 -203
- package/dist/components/calendar/src/time-calendar/time-calendar.d.ts +18 -2
- package/dist/components/calendar/src/time-calendar/time-calendar.js +59 -202
- package/dist/components/calendar/src/utils/time.js +1 -1
- package/dist/components/menus/dist/index.js +4 -4
- package/dist/components/menus/src/menus.js +1 -1
- package/package.json +1 -1
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { default as React__default } from 'react';
|
|
2
2
|
import { Locale, Quarter as Quarter$1, Day, isSameDay, isSameMonth, isSameYear, addMonths, startOfMonth, endOfMonth, startOfWeek, endOfWeek } from 'date-fns';
|
|
3
3
|
import { TextFieldProps } from '../../text-field/src';
|
|
4
|
+
import { MenuTrigger } from '../../menus/src';
|
|
4
5
|
import { TZDate } from '@date-fns/tz';
|
|
5
6
|
import { Locale as Locale$1 } from 'date-fns/locale';
|
|
6
7
|
import { PressMoveProps } from '../../../../../shared/src';
|
|
@@ -567,16 +568,30 @@ interface DateRangeInputProps extends Omit<TextFieldProps, "value" | "onChange"
|
|
|
567
568
|
}
|
|
568
569
|
declare const DateRangeInput: (props: DateRangeInputProps) => react_jsx_runtime.JSX.Element;
|
|
569
570
|
|
|
571
|
+
interface TimeCalendarComponentType extends React__default.MemoExoticComponent<React__default.FC<TimeCalendarProps>> {
|
|
572
|
+
Trigger: typeof MenuTrigger;
|
|
573
|
+
}
|
|
570
574
|
interface TimeCalendarProps extends BaseTimeProps, StepProps {
|
|
575
|
+
/** Custom trigger element. Use Dropdown.Trigger as wrapper */
|
|
571
576
|
children?: React__default.ReactNode;
|
|
572
|
-
/** Custom class name */
|
|
577
|
+
/** Custom class name for dropdown content */
|
|
573
578
|
className?: string;
|
|
574
579
|
/** Hour step, default 1 hour */
|
|
575
580
|
hourStep?: number;
|
|
576
581
|
/** Minute step, default 15 minutes */
|
|
577
582
|
minuteStep?: number;
|
|
583
|
+
/** Whether the dropdown is open (controlled) */
|
|
584
|
+
open?: boolean;
|
|
585
|
+
/** Callback when open state changes */
|
|
586
|
+
onOpenChange?: (open: boolean) => void;
|
|
587
|
+
/** Whether to close dropdown after selection */
|
|
588
|
+
closeOnSelect?: boolean;
|
|
589
|
+
/** External trigger element ref */
|
|
590
|
+
triggerRef?: React__default.RefObject<HTMLElement>;
|
|
591
|
+
/** CSS selector for external trigger element */
|
|
592
|
+
triggerSelector?: string;
|
|
578
593
|
}
|
|
579
|
-
declare const TimeCalendar:
|
|
594
|
+
declare const TimeCalendar: TimeCalendarComponentType;
|
|
580
595
|
|
|
581
596
|
interface TimeInputProps extends Omit<TextFieldProps, "value" | "onChange" | "format" | "defaultValue" | "step">, BaseTimeProps, StepProps, TimeInteractionProps {
|
|
582
597
|
prefixElement?: React__default.ReactNode;
|
|
@@ -10,7 +10,8 @@ import { jsx, jsxs, Fragment } from "react/jsx-runtime";
|
|
|
10
10
|
import { IconButton } from "../../icon-button/dist/index.js";
|
|
11
11
|
import { FieldTypeDate, Undo, ChevronUpSmall, ChevronLeftSmall, ChevronDownSmall, ChevronRightSmall, Check, Clock } from "@choiceform/icons-react";
|
|
12
12
|
import { TextField } from "../../text-field/dist/index.js";
|
|
13
|
-
import {
|
|
13
|
+
import { Dropdown as Dropdown2 } from "../../dropdown/dist/index.js";
|
|
14
|
+
import { MenuTrigger } from "../../menus/dist/index.js";
|
|
14
15
|
import { tcx, tcv } from "../../../shared/utils/tcx/tcx.js";
|
|
15
16
|
import { useMergedValue } from "../../../shared/hooks/use-merged-value/use-merged-value.js";
|
|
16
17
|
import { useModifierKeys } from "../../../shared/hooks/use-modifier-keys/use-modifier-keys.js";
|
|
@@ -1715,7 +1716,7 @@ var generateTimeOptions = (format10, step = 15) => {
|
|
|
1715
1716
|
const minutes = i % 60;
|
|
1716
1717
|
if (is12Hour) {
|
|
1717
1718
|
const displayHour = hours === 0 ? 12 : hours > 12 ? hours - 12 : hours;
|
|
1718
|
-
const ampm = hours < 12 ? "
|
|
1719
|
+
const ampm = hours < 12 ? "am" : "pm";
|
|
1719
1720
|
const value = `${hours.toString().padStart(2, "0")}:${minutes.toString().padStart(2, "0")}`;
|
|
1720
1721
|
const label = `${displayHour}:${minutes.toString().padStart(2, "0")} ${ampm}`;
|
|
1721
1722
|
options.push({ value, label });
|
|
@@ -3034,7 +3035,7 @@ var DateInput = forwardRef((props, ref) => {
|
|
|
3034
3035
|
);
|
|
3035
3036
|
});
|
|
3036
3037
|
DateInput.displayName = "DateInput";
|
|
3037
|
-
var
|
|
3038
|
+
var TimeCalendarBase = memo(function TimeCalendar(props) {
|
|
3038
3039
|
const {
|
|
3039
3040
|
value,
|
|
3040
3041
|
defaultValue,
|
|
@@ -3044,16 +3045,21 @@ var TimeCalendar = memo(function TimeCalendar2(props) {
|
|
|
3044
3045
|
className,
|
|
3045
3046
|
children,
|
|
3046
3047
|
readOnly = false,
|
|
3048
|
+
open: controlledOpen,
|
|
3049
|
+
onOpenChange,
|
|
3050
|
+
closeOnSelect = true,
|
|
3051
|
+
triggerRef,
|
|
3052
|
+
triggerSelector,
|
|
3047
3053
|
...rest
|
|
3048
3054
|
} = props;
|
|
3049
|
-
const
|
|
3050
|
-
const
|
|
3051
|
-
const
|
|
3052
|
-
|
|
3053
|
-
|
|
3054
|
-
|
|
3055
|
-
|
|
3056
|
-
|
|
3055
|
+
const [internalOpen, setInternalOpen] = useState(false);
|
|
3056
|
+
const isOpen = controlledOpen ?? internalOpen;
|
|
3057
|
+
const handleOpenChange = useEventCallback((newOpen) => {
|
|
3058
|
+
if (controlledOpen === void 0) {
|
|
3059
|
+
setInternalOpen(newOpen);
|
|
3060
|
+
}
|
|
3061
|
+
onOpenChange == null ? void 0 : onOpenChange(newOpen);
|
|
3062
|
+
});
|
|
3057
3063
|
const [innerValue, setValue] = useMergedValue({
|
|
3058
3064
|
value,
|
|
3059
3065
|
defaultValue,
|
|
@@ -3069,7 +3075,7 @@ var TimeCalendar = memo(function TimeCalendar2(props) {
|
|
|
3069
3075
|
const formatTo12Hour = useCallback((timeStr) => {
|
|
3070
3076
|
const [hour, minute] = timeStr.split(":").map(Number);
|
|
3071
3077
|
const displayHour = hour === 0 ? 12 : hour > 12 ? hour - 12 : hour;
|
|
3072
|
-
const ampm = hour < 12 ? "
|
|
3078
|
+
const ampm = hour < 12 ? "am" : "pm";
|
|
3073
3079
|
return `${displayHour}:${minute.toString().padStart(2, "0")} ${ampm}`;
|
|
3074
3080
|
}, []);
|
|
3075
3081
|
const customTimeOption = useMemo(() => {
|
|
@@ -3096,222 +3102,73 @@ var TimeCalendar = memo(function TimeCalendar2(props) {
|
|
|
3096
3102
|
},
|
|
3097
3103
|
[timeFormat]
|
|
3098
3104
|
);
|
|
3099
|
-
const selectedIndex = useMemo(() => {
|
|
3100
|
-
if (!normalizedTimeString) return null;
|
|
3101
|
-
const index = timeOptions.findIndex((option) => option.value === normalizedTimeString);
|
|
3102
|
-
return index === -1 ? -1 : index;
|
|
3103
|
-
}, [normalizedTimeString, timeOptions]);
|
|
3104
3105
|
const needsDivider = useMemo(() => {
|
|
3105
3106
|
const is12Hour = timeFormat.toLowerCase().includes("a") || timeFormat === "12h";
|
|
3106
3107
|
if (!is12Hour) return () => false;
|
|
3107
3108
|
return (index) => {
|
|
3108
|
-
return index > 0 && timeOptions[index].label.includes("
|
|
3109
|
+
return index > 0 && timeOptions[index].label.toLowerCase().includes("pm") && timeOptions[index - 1].label.toLowerCase().includes("am");
|
|
3109
3110
|
};
|
|
3110
3111
|
}, [timeFormat, timeOptions]);
|
|
3111
3112
|
const createPrefixElement = useCallback((isSelected) => {
|
|
3112
3113
|
return isSelected ? /* @__PURE__ */ jsx(Check, {}) : /* @__PURE__ */ jsx(Fragment, {});
|
|
3113
3114
|
}, []);
|
|
3114
|
-
const scrollTimeoutRef = useRef(null);
|
|
3115
|
-
const mousePositionRef = useRef(null);
|
|
3116
|
-
const handleScroll = useEventCallback(() => {
|
|
3117
|
-
if (!isScrolling) {
|
|
3118
|
-
setIsScrolling(true);
|
|
3119
|
-
setActiveIndex(null);
|
|
3120
|
-
}
|
|
3121
|
-
if (scrollTimeoutRef.current) {
|
|
3122
|
-
clearTimeout(scrollTimeoutRef.current);
|
|
3123
|
-
}
|
|
3124
|
-
scrollTimeoutRef.current = setTimeout(() => {
|
|
3125
|
-
var _a;
|
|
3126
|
-
setIsScrolling(false);
|
|
3127
|
-
if (mousePositionRef.current) {
|
|
3128
|
-
const elementUnderMouse = document.elementFromPoint(
|
|
3129
|
-
mousePositionRef.current.x,
|
|
3130
|
-
mousePositionRef.current.y
|
|
3131
|
-
);
|
|
3132
|
-
if (customElementRef.current && (customElementRef.current === elementUnderMouse || customElementRef.current.contains(elementUnderMouse))) {
|
|
3133
|
-
setActiveIndex(-1);
|
|
3134
|
-
return;
|
|
3135
|
-
}
|
|
3136
|
-
const elements = elementsRef.current;
|
|
3137
|
-
for (let i = 0; i < elements.length; i++) {
|
|
3138
|
-
if (elements[i] && (elements[i] === elementUnderMouse || ((_a = elements[i]) == null ? void 0 : _a.contains(elementUnderMouse)))) {
|
|
3139
|
-
setActiveIndex(i);
|
|
3140
|
-
break;
|
|
3141
|
-
}
|
|
3142
|
-
}
|
|
3143
|
-
}
|
|
3144
|
-
}, 200);
|
|
3145
|
-
});
|
|
3146
|
-
const handleMouseMove = useEventCallback((event) => {
|
|
3147
|
-
mousePositionRef.current = { x: event.clientX, y: event.clientY };
|
|
3148
|
-
});
|
|
3149
|
-
useEffect(() => {
|
|
3150
|
-
return () => {
|
|
3151
|
-
if (scrollTimeoutRef.current) {
|
|
3152
|
-
clearTimeout(scrollTimeoutRef.current);
|
|
3153
|
-
}
|
|
3154
|
-
};
|
|
3155
|
-
}, []);
|
|
3156
|
-
useEffect(() => {
|
|
3157
|
-
var _a;
|
|
3158
|
-
if (selectedIndex === -1) {
|
|
3159
|
-
const customElement = customElementRef.current;
|
|
3160
|
-
if (!customElement || !scrollRef.current) {
|
|
3161
|
-
return;
|
|
3162
|
-
}
|
|
3163
|
-
const previousSelectedIndex2 = lastSelectedIndexRef.current;
|
|
3164
|
-
const isSelectedIndexChanged2 = previousSelectedIndex2 !== selectedIndex;
|
|
3165
|
-
lastSelectedIndexRef.current = selectedIndex;
|
|
3166
|
-
const shouldScroll2 = !hasInitialScrolled.current || // Initial scrolling
|
|
3167
|
-
isSelectedIndexChanged2 && !isInternalOperationRef.current;
|
|
3168
|
-
if (shouldScroll2) {
|
|
3169
|
-
const isInitialScroll = !hasInitialScrolled.current;
|
|
3170
|
-
if (isInitialScroll) {
|
|
3171
|
-
scrollRef.current.scrollTo({
|
|
3172
|
-
top: 0,
|
|
3173
|
-
behavior: "auto"
|
|
3174
|
-
});
|
|
3175
|
-
} else {
|
|
3176
|
-
customElement.scrollIntoView({
|
|
3177
|
-
block: "nearest",
|
|
3178
|
-
behavior: "smooth"
|
|
3179
|
-
});
|
|
3180
|
-
}
|
|
3181
|
-
if (!hasInitialScrolled.current) {
|
|
3182
|
-
hasInitialScrolled.current = true;
|
|
3183
|
-
}
|
|
3184
|
-
}
|
|
3185
|
-
if (isInternalOperationRef.current) {
|
|
3186
|
-
isInternalOperationRef.current = false;
|
|
3187
|
-
}
|
|
3188
|
-
return;
|
|
3189
|
-
}
|
|
3190
|
-
if (selectedIndex === null || !scrollRef.current || !elementsRef.current[selectedIndex]) {
|
|
3191
|
-
return;
|
|
3192
|
-
}
|
|
3193
|
-
const previousSelectedIndex = lastSelectedIndexRef.current;
|
|
3194
|
-
const isSelectedIndexChanged = previousSelectedIndex !== selectedIndex;
|
|
3195
|
-
lastSelectedIndexRef.current = selectedIndex;
|
|
3196
|
-
const shouldScroll = !hasInitialScrolled.current || // Initial scrolling
|
|
3197
|
-
isSelectedIndexChanged && !isInternalOperationRef.current;
|
|
3198
|
-
if (shouldScroll) {
|
|
3199
|
-
const isInitialScroll = !hasInitialScrolled.current;
|
|
3200
|
-
if (isInitialScroll) {
|
|
3201
|
-
(_a = elementsRef.current[selectedIndex]) == null ? void 0 : _a.scrollIntoView({
|
|
3202
|
-
block: "center",
|
|
3203
|
-
behavior: "auto"
|
|
3204
|
-
});
|
|
3205
|
-
} else {
|
|
3206
|
-
const container = scrollRef.current;
|
|
3207
|
-
const element = elementsRef.current[selectedIndex];
|
|
3208
|
-
if (container && element) {
|
|
3209
|
-
const containerRect = container.getBoundingClientRect();
|
|
3210
|
-
const elementRect = element.getBoundingClientRect();
|
|
3211
|
-
const margin = 8;
|
|
3212
|
-
const elementTop = elementRect.top - containerRect.top + container.scrollTop;
|
|
3213
|
-
const elementBottom = elementTop + elementRect.height;
|
|
3214
|
-
const visibleTop = container.scrollTop + margin;
|
|
3215
|
-
const visibleBottom = container.scrollTop + container.clientHeight - margin;
|
|
3216
|
-
let targetScrollTop = container.scrollTop;
|
|
3217
|
-
if (elementTop < visibleTop) {
|
|
3218
|
-
targetScrollTop = elementTop - margin;
|
|
3219
|
-
} else if (elementBottom > visibleBottom) {
|
|
3220
|
-
targetScrollTop = elementBottom - container.clientHeight + margin;
|
|
3221
|
-
}
|
|
3222
|
-
if (targetScrollTop !== container.scrollTop) {
|
|
3223
|
-
container.scrollTo({
|
|
3224
|
-
top: targetScrollTop
|
|
3225
|
-
});
|
|
3226
|
-
}
|
|
3227
|
-
}
|
|
3228
|
-
}
|
|
3229
|
-
if (!hasInitialScrolled.current) {
|
|
3230
|
-
hasInitialScrolled.current = true;
|
|
3231
|
-
}
|
|
3232
|
-
}
|
|
3233
|
-
if (isInternalOperationRef.current) {
|
|
3234
|
-
isInternalOperationRef.current = false;
|
|
3235
|
-
}
|
|
3236
|
-
}, [selectedIndex]);
|
|
3237
3115
|
const handleTimeSelect = useEventCallback((timeValue) => {
|
|
3238
3116
|
if (readOnly) return;
|
|
3239
|
-
isInternalOperationRef.current = true;
|
|
3240
3117
|
const dateValue = timeStringToDate(timeValue);
|
|
3241
3118
|
setValue(dateValue);
|
|
3242
|
-
|
|
3243
|
-
|
|
3244
|
-
if (isScrolling) {
|
|
3245
|
-
return;
|
|
3119
|
+
if (closeOnSelect) {
|
|
3120
|
+
handleOpenChange(false);
|
|
3246
3121
|
}
|
|
3247
|
-
setActiveIndex(index);
|
|
3248
|
-
});
|
|
3249
|
-
const handleMouseLeave = useEventCallback(() => {
|
|
3250
|
-
setActiveIndex(null);
|
|
3251
|
-
});
|
|
3252
|
-
const handleClick = useEventCallback((timeValue) => {
|
|
3253
|
-
handleTimeSelect(timeValue);
|
|
3254
3122
|
});
|
|
3255
3123
|
return /* @__PURE__ */ jsxs(
|
|
3256
|
-
|
|
3124
|
+
Dropdown2,
|
|
3257
3125
|
{
|
|
3258
|
-
|
|
3259
|
-
|
|
3260
|
-
|
|
3261
|
-
|
|
3262
|
-
|
|
3263
|
-
"data-testid": "time-calendar-menu",
|
|
3126
|
+
open: isOpen,
|
|
3127
|
+
onOpenChange: handleOpenChange,
|
|
3128
|
+
selection: true,
|
|
3129
|
+
triggerRef,
|
|
3130
|
+
triggerSelector,
|
|
3264
3131
|
...rest,
|
|
3265
3132
|
children: [
|
|
3266
|
-
|
|
3267
|
-
|
|
3268
|
-
|
|
3269
|
-
{
|
|
3270
|
-
ref: (node) => {
|
|
3271
|
-
customElementRef.current = node;
|
|
3272
|
-
},
|
|
3273
|
-
selected: selectedIndex === -1,
|
|
3274
|
-
active: !isScrolling && activeIndex === -1,
|
|
3275
|
-
onClick: () => handleClick(customTimeOption.value),
|
|
3276
|
-
onMouseEnter: () => handleMouseEnter(-1),
|
|
3277
|
-
prefixElement: createPrefixElement(selectedIndex === -1),
|
|
3278
|
-
variant: "highlight",
|
|
3279
|
-
"data-testid": "custom-time-item",
|
|
3280
|
-
children: renderTimeLabel(customTimeOption.label)
|
|
3281
|
-
}
|
|
3282
|
-
),
|
|
3283
|
-
/* @__PURE__ */ jsx(Menus.Divider, { "data-testid": "custom-time-divider" })
|
|
3284
|
-
] }),
|
|
3285
|
-
timeOptions.map((option, index) => {
|
|
3286
|
-
const isAmToPmTransition = needsDivider(index);
|
|
3287
|
-
const isItemSelected = selectedIndex === index;
|
|
3288
|
-
const isItemActive = !isScrolling && activeIndex === index;
|
|
3289
|
-
return /* @__PURE__ */ jsxs(React__default.Fragment, { children: [
|
|
3290
|
-
isAmToPmTransition && /* @__PURE__ */ jsx(Menus.Divider, { "data-testid": "ampm-divider" }),
|
|
3133
|
+
children,
|
|
3134
|
+
/* @__PURE__ */ jsxs(Dropdown2.Content, { className, children: [
|
|
3135
|
+
customTimeOption && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
3291
3136
|
/* @__PURE__ */ jsx(
|
|
3292
|
-
|
|
3137
|
+
Dropdown2.Item,
|
|
3293
3138
|
{
|
|
3294
|
-
|
|
3295
|
-
|
|
3296
|
-
|
|
3297
|
-
|
|
3298
|
-
active: isItemActive,
|
|
3299
|
-
onClick: () => handleClick(option.value),
|
|
3300
|
-
onMouseEnter: () => handleMouseEnter(index),
|
|
3301
|
-
prefixElement: createPrefixElement(isItemSelected),
|
|
3302
|
-
variant: "highlight",
|
|
3303
|
-
"data-testid": option.value,
|
|
3304
|
-
children: renderTimeLabel(option.label)
|
|
3139
|
+
onMouseUp: () => handleTimeSelect(customTimeOption.value),
|
|
3140
|
+
prefixElement: createPrefixElement(normalizedTimeString === customTimeOption.value),
|
|
3141
|
+
"data-testid": "custom-time-item",
|
|
3142
|
+
children: renderTimeLabel(customTimeOption.label)
|
|
3305
3143
|
}
|
|
3306
|
-
)
|
|
3307
|
-
|
|
3308
|
-
|
|
3309
|
-
|
|
3144
|
+
),
|
|
3145
|
+
/* @__PURE__ */ jsx(Dropdown2.Divider, { "data-testid": "custom-time-divider" })
|
|
3146
|
+
] }),
|
|
3147
|
+
timeOptions.map((option, index) => {
|
|
3148
|
+
const isAmToPmTransition = needsDivider(index);
|
|
3149
|
+
const isSelected = normalizedTimeString === option.value;
|
|
3150
|
+
return /* @__PURE__ */ jsxs(React__default.Fragment, { children: [
|
|
3151
|
+
isAmToPmTransition && /* @__PURE__ */ jsx(Dropdown2.Divider, { "data-testid": "ampm-divider" }),
|
|
3152
|
+
/* @__PURE__ */ jsx(
|
|
3153
|
+
Dropdown2.Item,
|
|
3154
|
+
{
|
|
3155
|
+
onMouseUp: () => handleTimeSelect(option.value),
|
|
3156
|
+
prefixElement: createPrefixElement(isSelected),
|
|
3157
|
+
"data-testid": option.value,
|
|
3158
|
+
children: renderTimeLabel(option.label)
|
|
3159
|
+
}
|
|
3160
|
+
)
|
|
3161
|
+
] }, option.value);
|
|
3162
|
+
})
|
|
3163
|
+
] })
|
|
3310
3164
|
]
|
|
3311
3165
|
}
|
|
3312
3166
|
);
|
|
3313
3167
|
});
|
|
3314
|
-
|
|
3168
|
+
TimeCalendarBase.displayName = "TimeCalendar";
|
|
3169
|
+
Object.assign(TimeCalendarBase, {
|
|
3170
|
+
Trigger: MenuTrigger
|
|
3171
|
+
});
|
|
3315
3172
|
function useTimeInput(props) {
|
|
3316
3173
|
const {
|
|
3317
3174
|
value,
|
|
@@ -4529,7 +4386,6 @@ export {
|
|
|
4529
4386
|
LOCALE_MAP,
|
|
4530
4387
|
MonthCalendar,
|
|
4531
4388
|
QuarterCalendar,
|
|
4532
|
-
TimeCalendar,
|
|
4533
4389
|
TimeInput,
|
|
4534
4390
|
YearCalendar,
|
|
4535
4391
|
calculateWeekNumbers,
|
|
@@ -1,12 +1,28 @@
|
|
|
1
|
+
import { MenuTrigger } from '../../../menus/src';
|
|
1
2
|
import { default as React } from 'react';
|
|
2
3
|
import { BaseTimeProps, StepProps } from '../types';
|
|
4
|
+
interface TimeCalendarComponentType extends React.MemoExoticComponent<React.FC<TimeCalendarProps>> {
|
|
5
|
+
Trigger: typeof MenuTrigger;
|
|
6
|
+
}
|
|
3
7
|
export interface TimeCalendarProps extends BaseTimeProps, StepProps {
|
|
8
|
+
/** Custom trigger element. Use Dropdown.Trigger as wrapper */
|
|
4
9
|
children?: React.ReactNode;
|
|
5
|
-
/** Custom class name */
|
|
10
|
+
/** Custom class name for dropdown content */
|
|
6
11
|
className?: string;
|
|
7
12
|
/** Hour step, default 1 hour */
|
|
8
13
|
hourStep?: number;
|
|
9
14
|
/** Minute step, default 15 minutes */
|
|
10
15
|
minuteStep?: number;
|
|
16
|
+
/** Whether the dropdown is open (controlled) */
|
|
17
|
+
open?: boolean;
|
|
18
|
+
/** Callback when open state changes */
|
|
19
|
+
onOpenChange?: (open: boolean) => void;
|
|
20
|
+
/** Whether to close dropdown after selection */
|
|
21
|
+
closeOnSelect?: boolean;
|
|
22
|
+
/** External trigger element ref */
|
|
23
|
+
triggerRef?: React.RefObject<HTMLElement>;
|
|
24
|
+
/** CSS selector for external trigger element */
|
|
25
|
+
triggerSelector?: string;
|
|
11
26
|
}
|
|
12
|
-
export declare const TimeCalendar:
|
|
27
|
+
export declare const TimeCalendar: TimeCalendarComponentType;
|
|
28
|
+
export {};
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { jsxs, Fragment, jsx } from "react/jsx-runtime";
|
|
2
2
|
import { Check } from "@choiceform/icons-react";
|
|
3
|
-
import {
|
|
4
|
-
import
|
|
3
|
+
import { Dropdown as Dropdown2 } from "../../../dropdown/dist/index.js";
|
|
4
|
+
import { MenuTrigger } from "../../../menus/dist/index.js";
|
|
5
|
+
import React__default, { memo, useState, useMemo, useCallback } from "react";
|
|
5
6
|
import { useEventCallback } from "usehooks-ts";
|
|
6
7
|
import { generateTimeOptions, normalizeTimeValue, timeStringToDate } from "../utils/time.js";
|
|
7
8
|
import { useMergedValue } from "../../../../shared/hooks/use-merged-value/use-merged-value.js";
|
|
8
|
-
const
|
|
9
|
+
const TimeCalendarBase = memo(function TimeCalendar2(props) {
|
|
9
10
|
const {
|
|
10
11
|
value,
|
|
11
12
|
defaultValue,
|
|
@@ -15,16 +16,21 @@ const TimeCalendar = memo(function TimeCalendar2(props) {
|
|
|
15
16
|
className,
|
|
16
17
|
children,
|
|
17
18
|
readOnly = false,
|
|
19
|
+
open: controlledOpen,
|
|
20
|
+
onOpenChange,
|
|
21
|
+
closeOnSelect = true,
|
|
22
|
+
triggerRef,
|
|
23
|
+
triggerSelector,
|
|
18
24
|
...rest
|
|
19
25
|
} = props;
|
|
20
|
-
const
|
|
21
|
-
const
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
26
|
+
const [internalOpen, setInternalOpen] = useState(false);
|
|
27
|
+
const isOpen = controlledOpen ?? internalOpen;
|
|
28
|
+
const handleOpenChange = useEventCallback((newOpen) => {
|
|
29
|
+
if (controlledOpen === void 0) {
|
|
30
|
+
setInternalOpen(newOpen);
|
|
31
|
+
}
|
|
32
|
+
onOpenChange == null ? void 0 : onOpenChange(newOpen);
|
|
33
|
+
});
|
|
28
34
|
const [innerValue, setValue] = useMergedValue({
|
|
29
35
|
value,
|
|
30
36
|
defaultValue,
|
|
@@ -40,7 +46,7 @@ const TimeCalendar = memo(function TimeCalendar2(props) {
|
|
|
40
46
|
const formatTo12Hour = useCallback((timeStr) => {
|
|
41
47
|
const [hour, minute] = timeStr.split(":").map(Number);
|
|
42
48
|
const displayHour = hour === 0 ? 12 : hour > 12 ? hour - 12 : hour;
|
|
43
|
-
const ampm = hour < 12 ? "
|
|
49
|
+
const ampm = hour < 12 ? "am" : "pm";
|
|
44
50
|
return `${displayHour}:${minute.toString().padStart(2, "0")} ${ampm}`;
|
|
45
51
|
}, []);
|
|
46
52
|
const customTimeOption = useMemo(() => {
|
|
@@ -67,222 +73,73 @@ const TimeCalendar = memo(function TimeCalendar2(props) {
|
|
|
67
73
|
},
|
|
68
74
|
[timeFormat]
|
|
69
75
|
);
|
|
70
|
-
const selectedIndex = useMemo(() => {
|
|
71
|
-
if (!normalizedTimeString) return null;
|
|
72
|
-
const index = timeOptions.findIndex((option) => option.value === normalizedTimeString);
|
|
73
|
-
return index === -1 ? -1 : index;
|
|
74
|
-
}, [normalizedTimeString, timeOptions]);
|
|
75
76
|
const needsDivider = useMemo(() => {
|
|
76
77
|
const is12Hour = timeFormat.toLowerCase().includes("a") || timeFormat === "12h";
|
|
77
78
|
if (!is12Hour) return () => false;
|
|
78
79
|
return (index) => {
|
|
79
|
-
return index > 0 && timeOptions[index].label.includes("
|
|
80
|
+
return index > 0 && timeOptions[index].label.toLowerCase().includes("pm") && timeOptions[index - 1].label.toLowerCase().includes("am");
|
|
80
81
|
};
|
|
81
82
|
}, [timeFormat, timeOptions]);
|
|
82
83
|
const createPrefixElement = useCallback((isSelected) => {
|
|
83
84
|
return isSelected ? /* @__PURE__ */ jsx(Check, {}) : /* @__PURE__ */ jsx(Fragment, {});
|
|
84
85
|
}, []);
|
|
85
|
-
const scrollTimeoutRef = useRef(null);
|
|
86
|
-
const mousePositionRef = useRef(null);
|
|
87
|
-
const handleScroll = useEventCallback(() => {
|
|
88
|
-
if (!isScrolling) {
|
|
89
|
-
setIsScrolling(true);
|
|
90
|
-
setActiveIndex(null);
|
|
91
|
-
}
|
|
92
|
-
if (scrollTimeoutRef.current) {
|
|
93
|
-
clearTimeout(scrollTimeoutRef.current);
|
|
94
|
-
}
|
|
95
|
-
scrollTimeoutRef.current = setTimeout(() => {
|
|
96
|
-
var _a;
|
|
97
|
-
setIsScrolling(false);
|
|
98
|
-
if (mousePositionRef.current) {
|
|
99
|
-
const elementUnderMouse = document.elementFromPoint(
|
|
100
|
-
mousePositionRef.current.x,
|
|
101
|
-
mousePositionRef.current.y
|
|
102
|
-
);
|
|
103
|
-
if (customElementRef.current && (customElementRef.current === elementUnderMouse || customElementRef.current.contains(elementUnderMouse))) {
|
|
104
|
-
setActiveIndex(-1);
|
|
105
|
-
return;
|
|
106
|
-
}
|
|
107
|
-
const elements = elementsRef.current;
|
|
108
|
-
for (let i = 0; i < elements.length; i++) {
|
|
109
|
-
if (elements[i] && (elements[i] === elementUnderMouse || ((_a = elements[i]) == null ? void 0 : _a.contains(elementUnderMouse)))) {
|
|
110
|
-
setActiveIndex(i);
|
|
111
|
-
break;
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
}, 200);
|
|
116
|
-
});
|
|
117
|
-
const handleMouseMove = useEventCallback((event) => {
|
|
118
|
-
mousePositionRef.current = { x: event.clientX, y: event.clientY };
|
|
119
|
-
});
|
|
120
|
-
useEffect(() => {
|
|
121
|
-
return () => {
|
|
122
|
-
if (scrollTimeoutRef.current) {
|
|
123
|
-
clearTimeout(scrollTimeoutRef.current);
|
|
124
|
-
}
|
|
125
|
-
};
|
|
126
|
-
}, []);
|
|
127
|
-
useEffect(() => {
|
|
128
|
-
var _a;
|
|
129
|
-
if (selectedIndex === -1) {
|
|
130
|
-
const customElement = customElementRef.current;
|
|
131
|
-
if (!customElement || !scrollRef.current) {
|
|
132
|
-
return;
|
|
133
|
-
}
|
|
134
|
-
const previousSelectedIndex2 = lastSelectedIndexRef.current;
|
|
135
|
-
const isSelectedIndexChanged2 = previousSelectedIndex2 !== selectedIndex;
|
|
136
|
-
lastSelectedIndexRef.current = selectedIndex;
|
|
137
|
-
const shouldScroll2 = !hasInitialScrolled.current || // Initial scrolling
|
|
138
|
-
isSelectedIndexChanged2 && !isInternalOperationRef.current;
|
|
139
|
-
if (shouldScroll2) {
|
|
140
|
-
const isInitialScroll = !hasInitialScrolled.current;
|
|
141
|
-
if (isInitialScroll) {
|
|
142
|
-
scrollRef.current.scrollTo({
|
|
143
|
-
top: 0,
|
|
144
|
-
behavior: "auto"
|
|
145
|
-
});
|
|
146
|
-
} else {
|
|
147
|
-
customElement.scrollIntoView({
|
|
148
|
-
block: "nearest",
|
|
149
|
-
behavior: "smooth"
|
|
150
|
-
});
|
|
151
|
-
}
|
|
152
|
-
if (!hasInitialScrolled.current) {
|
|
153
|
-
hasInitialScrolled.current = true;
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
if (isInternalOperationRef.current) {
|
|
157
|
-
isInternalOperationRef.current = false;
|
|
158
|
-
}
|
|
159
|
-
return;
|
|
160
|
-
}
|
|
161
|
-
if (selectedIndex === null || !scrollRef.current || !elementsRef.current[selectedIndex]) {
|
|
162
|
-
return;
|
|
163
|
-
}
|
|
164
|
-
const previousSelectedIndex = lastSelectedIndexRef.current;
|
|
165
|
-
const isSelectedIndexChanged = previousSelectedIndex !== selectedIndex;
|
|
166
|
-
lastSelectedIndexRef.current = selectedIndex;
|
|
167
|
-
const shouldScroll = !hasInitialScrolled.current || // Initial scrolling
|
|
168
|
-
isSelectedIndexChanged && !isInternalOperationRef.current;
|
|
169
|
-
if (shouldScroll) {
|
|
170
|
-
const isInitialScroll = !hasInitialScrolled.current;
|
|
171
|
-
if (isInitialScroll) {
|
|
172
|
-
(_a = elementsRef.current[selectedIndex]) == null ? void 0 : _a.scrollIntoView({
|
|
173
|
-
block: "center",
|
|
174
|
-
behavior: "auto"
|
|
175
|
-
});
|
|
176
|
-
} else {
|
|
177
|
-
const container = scrollRef.current;
|
|
178
|
-
const element = elementsRef.current[selectedIndex];
|
|
179
|
-
if (container && element) {
|
|
180
|
-
const containerRect = container.getBoundingClientRect();
|
|
181
|
-
const elementRect = element.getBoundingClientRect();
|
|
182
|
-
const margin = 8;
|
|
183
|
-
const elementTop = elementRect.top - containerRect.top + container.scrollTop;
|
|
184
|
-
const elementBottom = elementTop + elementRect.height;
|
|
185
|
-
const visibleTop = container.scrollTop + margin;
|
|
186
|
-
const visibleBottom = container.scrollTop + container.clientHeight - margin;
|
|
187
|
-
let targetScrollTop = container.scrollTop;
|
|
188
|
-
if (elementTop < visibleTop) {
|
|
189
|
-
targetScrollTop = elementTop - margin;
|
|
190
|
-
} else if (elementBottom > visibleBottom) {
|
|
191
|
-
targetScrollTop = elementBottom - container.clientHeight + margin;
|
|
192
|
-
}
|
|
193
|
-
if (targetScrollTop !== container.scrollTop) {
|
|
194
|
-
container.scrollTo({
|
|
195
|
-
top: targetScrollTop
|
|
196
|
-
});
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
if (!hasInitialScrolled.current) {
|
|
201
|
-
hasInitialScrolled.current = true;
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
if (isInternalOperationRef.current) {
|
|
205
|
-
isInternalOperationRef.current = false;
|
|
206
|
-
}
|
|
207
|
-
}, [selectedIndex]);
|
|
208
86
|
const handleTimeSelect = useEventCallback((timeValue) => {
|
|
209
87
|
if (readOnly) return;
|
|
210
|
-
isInternalOperationRef.current = true;
|
|
211
88
|
const dateValue = timeStringToDate(timeValue);
|
|
212
89
|
setValue(dateValue);
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
if (isScrolling) {
|
|
216
|
-
return;
|
|
90
|
+
if (closeOnSelect) {
|
|
91
|
+
handleOpenChange(false);
|
|
217
92
|
}
|
|
218
|
-
setActiveIndex(index);
|
|
219
|
-
});
|
|
220
|
-
const handleMouseLeave = useEventCallback(() => {
|
|
221
|
-
setActiveIndex(null);
|
|
222
|
-
});
|
|
223
|
-
const handleClick = useEventCallback((timeValue) => {
|
|
224
|
-
handleTimeSelect(timeValue);
|
|
225
93
|
});
|
|
226
94
|
return /* @__PURE__ */ jsxs(
|
|
227
|
-
|
|
95
|
+
Dropdown2,
|
|
228
96
|
{
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
"data-testid": "time-calendar-menu",
|
|
97
|
+
open: isOpen,
|
|
98
|
+
onOpenChange: handleOpenChange,
|
|
99
|
+
selection: true,
|
|
100
|
+
triggerRef,
|
|
101
|
+
triggerSelector,
|
|
235
102
|
...rest,
|
|
236
103
|
children: [
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
{
|
|
241
|
-
ref: (node) => {
|
|
242
|
-
customElementRef.current = node;
|
|
243
|
-
},
|
|
244
|
-
selected: selectedIndex === -1,
|
|
245
|
-
active: !isScrolling && activeIndex === -1,
|
|
246
|
-
onClick: () => handleClick(customTimeOption.value),
|
|
247
|
-
onMouseEnter: () => handleMouseEnter(-1),
|
|
248
|
-
prefixElement: createPrefixElement(selectedIndex === -1),
|
|
249
|
-
variant: "highlight",
|
|
250
|
-
"data-testid": "custom-time-item",
|
|
251
|
-
children: renderTimeLabel(customTimeOption.label)
|
|
252
|
-
}
|
|
253
|
-
),
|
|
254
|
-
/* @__PURE__ */ jsx(Menus.Divider, { "data-testid": "custom-time-divider" })
|
|
255
|
-
] }),
|
|
256
|
-
timeOptions.map((option, index) => {
|
|
257
|
-
const isAmToPmTransition = needsDivider(index);
|
|
258
|
-
const isItemSelected = selectedIndex === index;
|
|
259
|
-
const isItemActive = !isScrolling && activeIndex === index;
|
|
260
|
-
return /* @__PURE__ */ jsxs(React__default.Fragment, { children: [
|
|
261
|
-
isAmToPmTransition && /* @__PURE__ */ jsx(Menus.Divider, { "data-testid": "ampm-divider" }),
|
|
104
|
+
children,
|
|
105
|
+
/* @__PURE__ */ jsxs(Dropdown2.Content, { className, children: [
|
|
106
|
+
customTimeOption && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
262
107
|
/* @__PURE__ */ jsx(
|
|
263
|
-
|
|
108
|
+
Dropdown2.Item,
|
|
264
109
|
{
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
active: isItemActive,
|
|
270
|
-
onClick: () => handleClick(option.value),
|
|
271
|
-
onMouseEnter: () => handleMouseEnter(index),
|
|
272
|
-
prefixElement: createPrefixElement(isItemSelected),
|
|
273
|
-
variant: "highlight",
|
|
274
|
-
"data-testid": option.value,
|
|
275
|
-
children: renderTimeLabel(option.label)
|
|
110
|
+
onMouseUp: () => handleTimeSelect(customTimeOption.value),
|
|
111
|
+
prefixElement: createPrefixElement(normalizedTimeString === customTimeOption.value),
|
|
112
|
+
"data-testid": "custom-time-item",
|
|
113
|
+
children: renderTimeLabel(customTimeOption.label)
|
|
276
114
|
}
|
|
277
|
-
)
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
115
|
+
),
|
|
116
|
+
/* @__PURE__ */ jsx(Dropdown2.Divider, { "data-testid": "custom-time-divider" })
|
|
117
|
+
] }),
|
|
118
|
+
timeOptions.map((option, index) => {
|
|
119
|
+
const isAmToPmTransition = needsDivider(index);
|
|
120
|
+
const isSelected = normalizedTimeString === option.value;
|
|
121
|
+
return /* @__PURE__ */ jsxs(React__default.Fragment, { children: [
|
|
122
|
+
isAmToPmTransition && /* @__PURE__ */ jsx(Dropdown2.Divider, { "data-testid": "ampm-divider" }),
|
|
123
|
+
/* @__PURE__ */ jsx(
|
|
124
|
+
Dropdown2.Item,
|
|
125
|
+
{
|
|
126
|
+
onMouseUp: () => handleTimeSelect(option.value),
|
|
127
|
+
prefixElement: createPrefixElement(isSelected),
|
|
128
|
+
"data-testid": option.value,
|
|
129
|
+
children: renderTimeLabel(option.label)
|
|
130
|
+
}
|
|
131
|
+
)
|
|
132
|
+
] }, option.value);
|
|
133
|
+
})
|
|
134
|
+
] })
|
|
281
135
|
]
|
|
282
136
|
}
|
|
283
137
|
);
|
|
284
138
|
});
|
|
285
|
-
|
|
139
|
+
TimeCalendarBase.displayName = "TimeCalendar";
|
|
140
|
+
const TimeCalendar = Object.assign(TimeCalendarBase, {
|
|
141
|
+
Trigger: MenuTrigger
|
|
142
|
+
});
|
|
286
143
|
export {
|
|
287
144
|
TimeCalendar
|
|
288
145
|
};
|
|
@@ -241,7 +241,7 @@ const generateTimeOptions = (format2, step = 15) => {
|
|
|
241
241
|
const minutes = i % 60;
|
|
242
242
|
if (is12Hour) {
|
|
243
243
|
const displayHour = hours === 0 ? 12 : hours > 12 ? hours - 12 : hours;
|
|
244
|
-
const ampm = hours < 12 ? "
|
|
244
|
+
const ampm = hours < 12 ? "am" : "pm";
|
|
245
245
|
const value = `${hours.toString().padStart(2, "0")}:${minutes.toString().padStart(2, "0")}`;
|
|
246
246
|
const label = `${displayHour}:${minutes.toString().padStart(2, "0")} ${ampm}`;
|
|
247
247
|
options.push({ value, label });
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Button } from "../../button/dist/index.js";
|
|
2
|
-
import React__default, { forwardRef,
|
|
3
|
-
import { jsx,
|
|
4
|
-
import { Check, ChevronRightSmall,
|
|
2
|
+
import React__default, { memo, forwardRef, useMemo, useContext, createContext, startTransition, useCallback, useRef, useState, useEffect, Children, isValidElement, Fragment as Fragment$1 } from "react";
|
|
3
|
+
import { jsx, jsxs, Fragment } from "react/jsx-runtime";
|
|
4
|
+
import { ChevronDownSmall, Check, ChevronRightSmall, ChevronUpSmall } from "@choiceform/icons-react";
|
|
5
5
|
import { Kbd } from "../../kbd/dist/index.js";
|
|
6
6
|
import { flushSync } from "react-dom";
|
|
7
7
|
import { SearchInput } from "../../search-input/dist/index.js";
|
|
@@ -722,7 +722,7 @@ var MenuValue = memo(({ children, ...rest }) => {
|
|
|
722
722
|
});
|
|
723
723
|
MenuValue.displayName = "MenuValue";
|
|
724
724
|
var MenusBase = forwardRef((props, ref) => {
|
|
725
|
-
const { children, className, matchTriggerWidth, variant, ...rest } = props;
|
|
725
|
+
const { children, className, matchTriggerWidth, variant = "default", ...rest } = props;
|
|
726
726
|
const tv = MenusTv({ matchTriggerWidth, variant });
|
|
727
727
|
const processChildren = (children2) => {
|
|
728
728
|
return Children.map(children2, (child) => {
|
|
@@ -11,7 +11,7 @@ import { MenuValue } from "./components/menu-value.js";
|
|
|
11
11
|
import { MenusTv } from "./tv.js";
|
|
12
12
|
import { tcx } from "../../../shared/utils/tcx/tcx.js";
|
|
13
13
|
const MenusBase = forwardRef((props, ref) => {
|
|
14
|
-
const { children, className, matchTriggerWidth, variant, ...rest } = props;
|
|
14
|
+
const { children, className, matchTriggerWidth, variant = "default", ...rest } = props;
|
|
15
15
|
const tv = MenusTv({ matchTriggerWidth, variant });
|
|
16
16
|
const processChildren = (children2) => {
|
|
17
17
|
return Children.map(children2, (child) => {
|
package/package.json
CHANGED