@choice-ui/react 1.5.7 → 1.5.9
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 +12 -5
- package/dist/components/calendar/dist/index.js +68 -203
- package/dist/components/calendar/src/time-calendar/time-calendar.d.ts +13 -5
- package/dist/components/calendar/src/time-calendar/time-calendar.js +68 -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,8 @@
|
|
|
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 { DropdownProps } from '../../dropdown/src';
|
|
5
|
+
import { MenuTrigger } from '../../menus/src';
|
|
4
6
|
import { TZDate } from '@date-fns/tz';
|
|
5
7
|
import { Locale as Locale$1 } from 'date-fns/locale';
|
|
6
8
|
import { PressMoveProps } from '../../../../../shared/src';
|
|
@@ -567,16 +569,21 @@ interface DateRangeInputProps extends Omit<TextFieldProps, "value" | "onChange"
|
|
|
567
569
|
}
|
|
568
570
|
declare const DateRangeInput: (props: DateRangeInputProps) => react_jsx_runtime.JSX.Element;
|
|
569
571
|
|
|
570
|
-
interface
|
|
572
|
+
interface TimeCalendarComponentType extends React__default.MemoExoticComponent<React__default.FC<TimeCalendarProps>> {
|
|
573
|
+
Trigger: typeof MenuTrigger;
|
|
574
|
+
}
|
|
575
|
+
interface TimeCalendarProps extends BaseTimeProps, StepProps, Pick<DropdownProps, "offset" | "placement" | "matchTriggerWidth" | "variant" | "readOnly"> {
|
|
571
576
|
children?: React__default.ReactNode;
|
|
572
|
-
/** Custom class name */
|
|
573
577
|
className?: string;
|
|
574
|
-
/** Hour step, default 1 hour */
|
|
575
578
|
hourStep?: number;
|
|
576
|
-
/** Minute step, default 15 minutes */
|
|
577
579
|
minuteStep?: number;
|
|
580
|
+
open?: boolean;
|
|
581
|
+
onOpenChange?: (open: boolean) => void;
|
|
582
|
+
closeOnSelect?: boolean;
|
|
583
|
+
triggerRef?: React__default.RefObject<HTMLElement>;
|
|
584
|
+
triggerSelector?: string;
|
|
578
585
|
}
|
|
579
|
-
declare const TimeCalendar:
|
|
586
|
+
declare const TimeCalendar: TimeCalendarComponentType;
|
|
580
587
|
|
|
581
588
|
interface TimeInputProps extends Omit<TextFieldProps, "value" | "onChange" | "format" | "defaultValue" | "step">, BaseTimeProps, StepProps, TimeInteractionProps {
|
|
582
589
|
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,25 @@ 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,
|
|
3053
|
+
offset,
|
|
3054
|
+
placement,
|
|
3055
|
+
matchTriggerWidth,
|
|
3056
|
+
variant,
|
|
3047
3057
|
...rest
|
|
3048
3058
|
} = props;
|
|
3049
|
-
const
|
|
3050
|
-
const
|
|
3051
|
-
const
|
|
3052
|
-
|
|
3053
|
-
|
|
3054
|
-
|
|
3055
|
-
|
|
3056
|
-
|
|
3059
|
+
const [internalOpen, setInternalOpen] = useState(false);
|
|
3060
|
+
const isOpen = controlledOpen ?? internalOpen;
|
|
3061
|
+
const handleOpenChange = useEventCallback((newOpen) => {
|
|
3062
|
+
if (controlledOpen === void 0) {
|
|
3063
|
+
setInternalOpen(newOpen);
|
|
3064
|
+
}
|
|
3065
|
+
onOpenChange == null ? void 0 : onOpenChange(newOpen);
|
|
3066
|
+
});
|
|
3057
3067
|
const [innerValue, setValue] = useMergedValue({
|
|
3058
3068
|
value,
|
|
3059
3069
|
defaultValue,
|
|
@@ -3069,7 +3079,7 @@ var TimeCalendar = memo(function TimeCalendar2(props) {
|
|
|
3069
3079
|
const formatTo12Hour = useCallback((timeStr) => {
|
|
3070
3080
|
const [hour, minute] = timeStr.split(":").map(Number);
|
|
3071
3081
|
const displayHour = hour === 0 ? 12 : hour > 12 ? hour - 12 : hour;
|
|
3072
|
-
const ampm = hour < 12 ? "
|
|
3082
|
+
const ampm = hour < 12 ? "am" : "pm";
|
|
3073
3083
|
return `${displayHour}:${minute.toString().padStart(2, "0")} ${ampm}`;
|
|
3074
3084
|
}, []);
|
|
3075
3085
|
const customTimeOption = useMemo(() => {
|
|
@@ -3096,222 +3106,78 @@ var TimeCalendar = memo(function TimeCalendar2(props) {
|
|
|
3096
3106
|
},
|
|
3097
3107
|
[timeFormat]
|
|
3098
3108
|
);
|
|
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
3109
|
const needsDivider = useMemo(() => {
|
|
3105
3110
|
const is12Hour = timeFormat.toLowerCase().includes("a") || timeFormat === "12h";
|
|
3106
3111
|
if (!is12Hour) return () => false;
|
|
3107
3112
|
return (index) => {
|
|
3108
|
-
return index > 0 && timeOptions[index].label.includes("
|
|
3113
|
+
return index > 0 && timeOptions[index].label.toLowerCase().includes("pm") && timeOptions[index - 1].label.toLowerCase().includes("am");
|
|
3109
3114
|
};
|
|
3110
3115
|
}, [timeFormat, timeOptions]);
|
|
3111
3116
|
const createPrefixElement = useCallback((isSelected) => {
|
|
3112
3117
|
return isSelected ? /* @__PURE__ */ jsx(Check, {}) : /* @__PURE__ */ jsx(Fragment, {});
|
|
3113
3118
|
}, []);
|
|
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
3119
|
const handleTimeSelect = useEventCallback((timeValue) => {
|
|
3238
3120
|
if (readOnly) return;
|
|
3239
|
-
isInternalOperationRef.current = true;
|
|
3240
3121
|
const dateValue = timeStringToDate(timeValue);
|
|
3241
3122
|
setValue(dateValue);
|
|
3242
|
-
|
|
3243
|
-
|
|
3244
|
-
if (isScrolling) {
|
|
3245
|
-
return;
|
|
3123
|
+
if (closeOnSelect) {
|
|
3124
|
+
handleOpenChange(false);
|
|
3246
3125
|
}
|
|
3247
|
-
setActiveIndex(index);
|
|
3248
|
-
});
|
|
3249
|
-
const handleMouseLeave = useEventCallback(() => {
|
|
3250
|
-
setActiveIndex(null);
|
|
3251
|
-
});
|
|
3252
|
-
const handleClick = useEventCallback((timeValue) => {
|
|
3253
|
-
handleTimeSelect(timeValue);
|
|
3254
3126
|
});
|
|
3255
3127
|
return /* @__PURE__ */ jsxs(
|
|
3256
|
-
|
|
3128
|
+
Dropdown2,
|
|
3257
3129
|
{
|
|
3258
|
-
|
|
3259
|
-
|
|
3260
|
-
|
|
3261
|
-
|
|
3262
|
-
|
|
3263
|
-
|
|
3130
|
+
open: isOpen,
|
|
3131
|
+
onOpenChange: handleOpenChange,
|
|
3132
|
+
selection: true,
|
|
3133
|
+
triggerRef,
|
|
3134
|
+
triggerSelector,
|
|
3135
|
+
offset,
|
|
3136
|
+
placement,
|
|
3137
|
+
matchTriggerWidth,
|
|
3138
|
+
variant,
|
|
3139
|
+
readOnly,
|
|
3264
3140
|
...rest,
|
|
3265
3141
|
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" }),
|
|
3142
|
+
children,
|
|
3143
|
+
/* @__PURE__ */ jsxs(Dropdown2.Content, { className, children: [
|
|
3144
|
+
customTimeOption && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
3291
3145
|
/* @__PURE__ */ jsx(
|
|
3292
|
-
|
|
3146
|
+
Dropdown2.Item,
|
|
3293
3147
|
{
|
|
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)
|
|
3148
|
+
onMouseUp: () => handleTimeSelect(customTimeOption.value),
|
|
3149
|
+
prefixElement: createPrefixElement(normalizedTimeString === customTimeOption.value),
|
|
3150
|
+
"data-testid": "custom-time-item",
|
|
3151
|
+
children: renderTimeLabel(customTimeOption.label)
|
|
3305
3152
|
}
|
|
3306
|
-
)
|
|
3307
|
-
|
|
3308
|
-
|
|
3309
|
-
|
|
3153
|
+
),
|
|
3154
|
+
/* @__PURE__ */ jsx(Dropdown2.Divider, { "data-testid": "custom-time-divider" })
|
|
3155
|
+
] }),
|
|
3156
|
+
timeOptions.map((option, index) => {
|
|
3157
|
+
const isAmToPmTransition = needsDivider(index);
|
|
3158
|
+
const isSelected = normalizedTimeString === option.value;
|
|
3159
|
+
return /* @__PURE__ */ jsxs(React__default.Fragment, { children: [
|
|
3160
|
+
isAmToPmTransition && /* @__PURE__ */ jsx(Dropdown2.Divider, { "data-testid": "ampm-divider" }),
|
|
3161
|
+
/* @__PURE__ */ jsx(
|
|
3162
|
+
Dropdown2.Item,
|
|
3163
|
+
{
|
|
3164
|
+
onMouseUp: () => handleTimeSelect(option.value),
|
|
3165
|
+
prefixElement: createPrefixElement(isSelected),
|
|
3166
|
+
"data-testid": option.value,
|
|
3167
|
+
children: renderTimeLabel(option.label)
|
|
3168
|
+
}
|
|
3169
|
+
)
|
|
3170
|
+
] }, option.value);
|
|
3171
|
+
})
|
|
3172
|
+
] })
|
|
3310
3173
|
]
|
|
3311
3174
|
}
|
|
3312
3175
|
);
|
|
3313
3176
|
});
|
|
3314
|
-
|
|
3177
|
+
TimeCalendarBase.displayName = "TimeCalendar";
|
|
3178
|
+
Object.assign(TimeCalendarBase, {
|
|
3179
|
+
Trigger: MenuTrigger
|
|
3180
|
+
});
|
|
3315
3181
|
function useTimeInput(props) {
|
|
3316
3182
|
const {
|
|
3317
3183
|
value,
|
|
@@ -4529,7 +4395,6 @@ export {
|
|
|
4529
4395
|
LOCALE_MAP,
|
|
4530
4396
|
MonthCalendar,
|
|
4531
4397
|
QuarterCalendar,
|
|
4532
|
-
TimeCalendar,
|
|
4533
4398
|
TimeInput,
|
|
4534
4399
|
YearCalendar,
|
|
4535
4400
|
calculateWeekNumbers,
|
|
@@ -1,12 +1,20 @@
|
|
|
1
|
+
import { DropdownProps } from '../../../dropdown/src';
|
|
2
|
+
import { MenuTrigger } from '../../../menus/src';
|
|
1
3
|
import { default as React } from 'react';
|
|
2
4
|
import { BaseTimeProps, StepProps } from '../types';
|
|
3
|
-
|
|
5
|
+
interface TimeCalendarComponentType extends React.MemoExoticComponent<React.FC<TimeCalendarProps>> {
|
|
6
|
+
Trigger: typeof MenuTrigger;
|
|
7
|
+
}
|
|
8
|
+
export interface TimeCalendarProps extends BaseTimeProps, StepProps, Pick<DropdownProps, "offset" | "placement" | "matchTriggerWidth" | "variant" | "readOnly"> {
|
|
4
9
|
children?: React.ReactNode;
|
|
5
|
-
/** Custom class name */
|
|
6
10
|
className?: string;
|
|
7
|
-
/** Hour step, default 1 hour */
|
|
8
11
|
hourStep?: number;
|
|
9
|
-
/** Minute step, default 15 minutes */
|
|
10
12
|
minuteStep?: number;
|
|
13
|
+
open?: boolean;
|
|
14
|
+
onOpenChange?: (open: boolean) => void;
|
|
15
|
+
closeOnSelect?: boolean;
|
|
16
|
+
triggerRef?: React.RefObject<HTMLElement>;
|
|
17
|
+
triggerSelector?: string;
|
|
11
18
|
}
|
|
12
|
-
export declare const TimeCalendar:
|
|
19
|
+
export declare const TimeCalendar: TimeCalendarComponentType;
|
|
20
|
+
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,25 @@ 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,
|
|
24
|
+
offset,
|
|
25
|
+
placement,
|
|
26
|
+
matchTriggerWidth,
|
|
27
|
+
variant,
|
|
18
28
|
...rest
|
|
19
29
|
} = props;
|
|
20
|
-
const
|
|
21
|
-
const
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
30
|
+
const [internalOpen, setInternalOpen] = useState(false);
|
|
31
|
+
const isOpen = controlledOpen ?? internalOpen;
|
|
32
|
+
const handleOpenChange = useEventCallback((newOpen) => {
|
|
33
|
+
if (controlledOpen === void 0) {
|
|
34
|
+
setInternalOpen(newOpen);
|
|
35
|
+
}
|
|
36
|
+
onOpenChange == null ? void 0 : onOpenChange(newOpen);
|
|
37
|
+
});
|
|
28
38
|
const [innerValue, setValue] = useMergedValue({
|
|
29
39
|
value,
|
|
30
40
|
defaultValue,
|
|
@@ -40,7 +50,7 @@ const TimeCalendar = memo(function TimeCalendar2(props) {
|
|
|
40
50
|
const formatTo12Hour = useCallback((timeStr) => {
|
|
41
51
|
const [hour, minute] = timeStr.split(":").map(Number);
|
|
42
52
|
const displayHour = hour === 0 ? 12 : hour > 12 ? hour - 12 : hour;
|
|
43
|
-
const ampm = hour < 12 ? "
|
|
53
|
+
const ampm = hour < 12 ? "am" : "pm";
|
|
44
54
|
return `${displayHour}:${minute.toString().padStart(2, "0")} ${ampm}`;
|
|
45
55
|
}, []);
|
|
46
56
|
const customTimeOption = useMemo(() => {
|
|
@@ -67,222 +77,78 @@ const TimeCalendar = memo(function TimeCalendar2(props) {
|
|
|
67
77
|
},
|
|
68
78
|
[timeFormat]
|
|
69
79
|
);
|
|
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
80
|
const needsDivider = useMemo(() => {
|
|
76
81
|
const is12Hour = timeFormat.toLowerCase().includes("a") || timeFormat === "12h";
|
|
77
82
|
if (!is12Hour) return () => false;
|
|
78
83
|
return (index) => {
|
|
79
|
-
return index > 0 && timeOptions[index].label.includes("
|
|
84
|
+
return index > 0 && timeOptions[index].label.toLowerCase().includes("pm") && timeOptions[index - 1].label.toLowerCase().includes("am");
|
|
80
85
|
};
|
|
81
86
|
}, [timeFormat, timeOptions]);
|
|
82
87
|
const createPrefixElement = useCallback((isSelected) => {
|
|
83
88
|
return isSelected ? /* @__PURE__ */ jsx(Check, {}) : /* @__PURE__ */ jsx(Fragment, {});
|
|
84
89
|
}, []);
|
|
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
90
|
const handleTimeSelect = useEventCallback((timeValue) => {
|
|
209
91
|
if (readOnly) return;
|
|
210
|
-
isInternalOperationRef.current = true;
|
|
211
92
|
const dateValue = timeStringToDate(timeValue);
|
|
212
93
|
setValue(dateValue);
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
if (isScrolling) {
|
|
216
|
-
return;
|
|
94
|
+
if (closeOnSelect) {
|
|
95
|
+
handleOpenChange(false);
|
|
217
96
|
}
|
|
218
|
-
setActiveIndex(index);
|
|
219
|
-
});
|
|
220
|
-
const handleMouseLeave = useEventCallback(() => {
|
|
221
|
-
setActiveIndex(null);
|
|
222
|
-
});
|
|
223
|
-
const handleClick = useEventCallback((timeValue) => {
|
|
224
|
-
handleTimeSelect(timeValue);
|
|
225
97
|
});
|
|
226
98
|
return /* @__PURE__ */ jsxs(
|
|
227
|
-
|
|
99
|
+
Dropdown2,
|
|
228
100
|
{
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
101
|
+
open: isOpen,
|
|
102
|
+
onOpenChange: handleOpenChange,
|
|
103
|
+
selection: true,
|
|
104
|
+
triggerRef,
|
|
105
|
+
triggerSelector,
|
|
106
|
+
offset,
|
|
107
|
+
placement,
|
|
108
|
+
matchTriggerWidth,
|
|
109
|
+
variant,
|
|
110
|
+
readOnly,
|
|
235
111
|
...rest,
|
|
236
112
|
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" }),
|
|
113
|
+
children,
|
|
114
|
+
/* @__PURE__ */ jsxs(Dropdown2.Content, { className, children: [
|
|
115
|
+
customTimeOption && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
262
116
|
/* @__PURE__ */ jsx(
|
|
263
|
-
|
|
117
|
+
Dropdown2.Item,
|
|
264
118
|
{
|
|
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)
|
|
119
|
+
onMouseUp: () => handleTimeSelect(customTimeOption.value),
|
|
120
|
+
prefixElement: createPrefixElement(normalizedTimeString === customTimeOption.value),
|
|
121
|
+
"data-testid": "custom-time-item",
|
|
122
|
+
children: renderTimeLabel(customTimeOption.label)
|
|
276
123
|
}
|
|
277
|
-
)
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
124
|
+
),
|
|
125
|
+
/* @__PURE__ */ jsx(Dropdown2.Divider, { "data-testid": "custom-time-divider" })
|
|
126
|
+
] }),
|
|
127
|
+
timeOptions.map((option, index) => {
|
|
128
|
+
const isAmToPmTransition = needsDivider(index);
|
|
129
|
+
const isSelected = normalizedTimeString === option.value;
|
|
130
|
+
return /* @__PURE__ */ jsxs(React__default.Fragment, { children: [
|
|
131
|
+
isAmToPmTransition && /* @__PURE__ */ jsx(Dropdown2.Divider, { "data-testid": "ampm-divider" }),
|
|
132
|
+
/* @__PURE__ */ jsx(
|
|
133
|
+
Dropdown2.Item,
|
|
134
|
+
{
|
|
135
|
+
onMouseUp: () => handleTimeSelect(option.value),
|
|
136
|
+
prefixElement: createPrefixElement(isSelected),
|
|
137
|
+
"data-testid": option.value,
|
|
138
|
+
children: renderTimeLabel(option.label)
|
|
139
|
+
}
|
|
140
|
+
)
|
|
141
|
+
] }, option.value);
|
|
142
|
+
})
|
|
143
|
+
] })
|
|
281
144
|
]
|
|
282
145
|
}
|
|
283
146
|
);
|
|
284
147
|
});
|
|
285
|
-
|
|
148
|
+
TimeCalendarBase.displayName = "TimeCalendar";
|
|
149
|
+
const TimeCalendar = Object.assign(TimeCalendarBase, {
|
|
150
|
+
Trigger: MenuTrigger
|
|
151
|
+
});
|
|
286
152
|
export {
|
|
287
153
|
TimeCalendar
|
|
288
154
|
};
|
|
@@ -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