@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.
@@ -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 TimeCalendarProps extends BaseTimeProps, StepProps {
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: React__default.NamedExoticComponent<TimeCalendarProps>;
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 { Menus } from "../../menus/dist/index.js";
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 ? "AM" : "PM";
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 TimeCalendar = memo(function TimeCalendar2(props) {
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 scrollRef = useRef(null);
3050
- const elementsRef = useRef([]);
3051
- const customElementRef = useRef(null);
3052
- const hasInitialScrolled = useRef(false);
3053
- const lastSelectedIndexRef = useRef(null);
3054
- const isInternalOperationRef = useRef(false);
3055
- const [activeIndex, setActiveIndex] = useState(null);
3056
- const [isScrolling, setIsScrolling] = useState(false);
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 ? "AM" : "PM";
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("PM") && timeOptions[index - 1].label.includes("AM");
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
- const handleMouseEnter = useEventCallback((index) => {
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
- Menus,
3128
+ Dropdown2,
3257
3129
  {
3258
- ref: scrollRef,
3259
- onScroll: handleScroll,
3260
- onMouseLeave: handleMouseLeave,
3261
- onMouseMove: handleMouseMove,
3262
- className,
3263
- "data-testid": "time-calendar-menu",
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
- customTimeOption && /* @__PURE__ */ jsxs(Fragment, { children: [
3267
- /* @__PURE__ */ jsx(
3268
- Menus.Item,
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
- Menus.Item,
3146
+ Dropdown2.Item,
3293
3147
  {
3294
- ref: (node) => {
3295
- elementsRef.current[index] = node;
3296
- },
3297
- selected: isItemSelected,
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
- ] }, option.value);
3308
- }),
3309
- children
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
- TimeCalendar.displayName = "TimeCalendar";
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
- export interface TimeCalendarProps extends BaseTimeProps, StepProps {
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: React.NamedExoticComponent<TimeCalendarProps>;
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 { Menus } from "../../../menus/dist/index.js";
4
- import React__default, { memo, useRef, useState, useMemo, useCallback, useEffect } from "react";
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 TimeCalendar = memo(function TimeCalendar2(props) {
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 scrollRef = useRef(null);
21
- const elementsRef = useRef([]);
22
- const customElementRef = useRef(null);
23
- const hasInitialScrolled = useRef(false);
24
- const lastSelectedIndexRef = useRef(null);
25
- const isInternalOperationRef = useRef(false);
26
- const [activeIndex, setActiveIndex] = useState(null);
27
- const [isScrolling, setIsScrolling] = useState(false);
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 ? "AM" : "PM";
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("PM") && timeOptions[index - 1].label.includes("AM");
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
- const handleMouseEnter = useEventCallback((index) => {
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
- Menus,
99
+ Dropdown2,
228
100
  {
229
- ref: scrollRef,
230
- onScroll: handleScroll,
231
- onMouseLeave: handleMouseLeave,
232
- onMouseMove: handleMouseMove,
233
- className,
234
- "data-testid": "time-calendar-menu",
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
- customTimeOption && /* @__PURE__ */ jsxs(Fragment, { children: [
238
- /* @__PURE__ */ jsx(
239
- Menus.Item,
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
- Menus.Item,
117
+ Dropdown2.Item,
264
118
  {
265
- ref: (node) => {
266
- elementsRef.current[index] = node;
267
- },
268
- selected: isItemSelected,
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
- ] }, option.value);
279
- }),
280
- children
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
- TimeCalendar.displayName = "TimeCalendar";
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 ? "AM" : "PM";
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, memo, useContext, createContext, useMemo, startTransition, useCallback, useRef, Children, isValidElement, Fragment as Fragment$1, useState, useEffect } from "react";
3
- import { jsx, Fragment, jsxs } from "react/jsx-runtime";
4
- import { Check, ChevronRightSmall, ChevronDownSmall, ChevronUpSmall } from "@choiceform/icons-react";
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@choice-ui/react",
3
- "version": "1.5.7",
3
+ "version": "1.5.9",
4
4
  "description": "A desktop-first React UI component library built for professional desktop applications with comprehensive documentation",
5
5
  "sideEffects": false,
6
6
  "type": "module",