@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.
@@ -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: React__default.NamedExoticComponent<TimeCalendarProps>;
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 { 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,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 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);
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 ? "AM" : "PM";
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("PM") && timeOptions[index - 1].label.includes("AM");
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
- const handleMouseEnter = useEventCallback((index) => {
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
- Menus,
3124
+ Dropdown2,
3257
3125
  {
3258
- ref: scrollRef,
3259
- onScroll: handleScroll,
3260
- onMouseLeave: handleMouseLeave,
3261
- onMouseMove: handleMouseMove,
3262
- className,
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
- 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" }),
3133
+ children,
3134
+ /* @__PURE__ */ jsxs(Dropdown2.Content, { className, children: [
3135
+ customTimeOption && /* @__PURE__ */ jsxs(Fragment, { children: [
3291
3136
  /* @__PURE__ */ jsx(
3292
- Menus.Item,
3137
+ Dropdown2.Item,
3293
3138
  {
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)
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
- ] }, option.value);
3308
- }),
3309
- children
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
- TimeCalendar.displayName = "TimeCalendar";
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: React.NamedExoticComponent<TimeCalendarProps>;
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 { 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,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 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);
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 ? "AM" : "PM";
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("PM") && timeOptions[index - 1].label.includes("AM");
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
- const handleMouseEnter = useEventCallback((index) => {
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
- Menus,
95
+ Dropdown2,
228
96
  {
229
- ref: scrollRef,
230
- onScroll: handleScroll,
231
- onMouseLeave: handleMouseLeave,
232
- onMouseMove: handleMouseMove,
233
- className,
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
- 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" }),
104
+ children,
105
+ /* @__PURE__ */ jsxs(Dropdown2.Content, { className, children: [
106
+ customTimeOption && /* @__PURE__ */ jsxs(Fragment, { children: [
262
107
  /* @__PURE__ */ jsx(
263
- Menus.Item,
108
+ Dropdown2.Item,
264
109
  {
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)
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
- ] }, option.value);
279
- }),
280
- children
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
- TimeCalendar.displayName = "TimeCalendar";
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 ? "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.8",
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",