@velocis/timepicker1 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs ADDED
@@ -0,0 +1,709 @@
1
+ 'use client';
2
+ "use strict";
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+ var __copyProps = (to, from, except, desc) => {
12
+ if (from && typeof from === "object" || typeof from === "function") {
13
+ for (let key of __getOwnPropNames(from))
14
+ if (!__hasOwnProp.call(to, key) && key !== except)
15
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
16
+ }
17
+ return to;
18
+ };
19
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
20
+
21
+ // src/index.ts
22
+ var index_exports = {};
23
+ __export(index_exports, {
24
+ TIMEPICKER1_DEFAULTS: () => TIMEPICKER1_DEFAULTS,
25
+ TIMEPICKER1_DEFAULT_COLORS: () => TIMEPICKER1_DEFAULT_COLORS,
26
+ TIMEPICKER1_SCROLLBAR_WIDTH_PX: () => TIMEPICKER1_SCROLLBAR_WIDTH_PX,
27
+ TimePicker1: () => TimePicker1,
28
+ TimePicker1Root: () => TimePicker1Root,
29
+ TimePickerPanel: () => TimePickerPanel,
30
+ clampTimeToRange: () => clampTimeToRange,
31
+ formatTimeSlot: () => formatTimeSlot,
32
+ generateBoundedTimeSlots: () => generateBoundedTimeSlots,
33
+ getNextSlotAfterNow: () => getNextSlotAfterNow,
34
+ isTimeDisabledByRules: () => isTimeDisabledByRules,
35
+ isTimeInRange: () => isTimeInRange,
36
+ isTimePickerTimeDisabled: () => isTimePickerTimeDisabled,
37
+ isTimeRuleDisabled: () => isTimeRuleDisabled,
38
+ resolveListHeight: () => resolveListHeight,
39
+ resolveScrollbarStyle: () => resolveScrollbarStyle,
40
+ timePicker1ContentVariants: () => timePicker1ContentVariants,
41
+ timePicker1ItemVariants: () => timePicker1ItemVariants,
42
+ timePicker1TriggerVariants: () => timePicker1TriggerVariants,
43
+ timepicker1ListScrollbarStyles: () => timepicker1ListScrollbarStyles,
44
+ useTimePicker1: () => useTimePicker1
45
+ });
46
+ module.exports = __toCommonJS(index_exports);
47
+
48
+ // src/TimePicker1.tsx
49
+ var import_core3 = require("@velocis/core");
50
+ var import_react2 = require("react");
51
+
52
+ // src/TimePicker1.defaults.ts
53
+ var TIMEPICKER1_DEFAULTS = {
54
+ contentWidth: "w-40",
55
+ contentZIndex: 9999,
56
+ closeOnScroll: true,
57
+ fullWidth: false,
58
+ disabled: false,
59
+ defaultOpen: false,
60
+ closeOnSelect: true,
61
+ minuteStep: 15,
62
+ use24Hour: false,
63
+ slotCount: 48,
64
+ startFromNow: true,
65
+ listHeight: 280
66
+ };
67
+ var TIMEPICKER1_DEFAULT_COLORS = {
68
+ panelBgColor: "#141414",
69
+ activeItemColor: "#FF6B3D",
70
+ scrollbarThumbColor: "rgba(255, 255, 255, 0.18)",
71
+ scrollbarWidth: 2
72
+ };
73
+
74
+ // src/TimePicker1.variants.ts
75
+ var import_class_variance_authority = require("class-variance-authority");
76
+ var timePicker1TriggerVariants = (0, import_class_variance_authority.cva)(
77
+ "flex items-center gap-2 rounded-2xl px-4 py-2 text-sm font-medium shadow-sm ring-1 ring-inset transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-velocis-primary/40 bg-velocis-background text-velocis-foreground ring-velocis-border hover:bg-velocis-border/30",
78
+ {
79
+ variants: {
80
+ fullWidth: {
81
+ true: "w-full justify-between",
82
+ false: ""
83
+ },
84
+ disabled: {
85
+ true: "cursor-not-allowed opacity-60",
86
+ false: ""
87
+ }
88
+ },
89
+ defaultVariants: {
90
+ fullWidth: false,
91
+ disabled: false
92
+ }
93
+ }
94
+ );
95
+ var timePicker1ContentVariants = (0, import_class_variance_authority.cva)(
96
+ "fixed rounded-2xl border border-velocis-border p-4 shadow-2xl focus:outline-none"
97
+ );
98
+ var timePicker1ItemVariants = (0, import_class_variance_authority.cva)(
99
+ "flex h-9 w-full items-center justify-center rounded-2xl text-sm transition-colors"
100
+ );
101
+
102
+ // src/components/TimePickerPanel.tsx
103
+ var import_core = require("@velocis/core");
104
+ var import_react = require("react");
105
+
106
+ // src/presets/timepicker1Styles.ts
107
+ var TIMEPICKER1_SCROLLBAR_WIDTH_PX = 2;
108
+ var timepicker1ListScrollbarStyles = "velocis-timepicker1-scroll";
109
+ function resolveListHeight(height) {
110
+ return typeof height === "number" ? `${height}px` : height;
111
+ }
112
+ function resolveScrollbarStyle(colors) {
113
+ const width = colors?.scrollbarWidth ?? TIMEPICKER1_DEFAULT_COLORS.scrollbarWidth;
114
+ const thumb = colors?.scrollbarThumbColor ?? TIMEPICKER1_DEFAULT_COLORS.scrollbarThumbColor;
115
+ return {
116
+ scrollbarWidth: "thin",
117
+ scrollbarColor: `${thumb} transparent`,
118
+ ["--velocis-timepicker1-scrollbar-thumb"]: thumb,
119
+ ["--velocis-timepicker1-scrollbar-width"]: `${width}px`
120
+ };
121
+ }
122
+
123
+ // src/styles/ensureScrollbarStyles.ts
124
+ var SCROLLBAR_STYLE_ID = "velocis-timepicker1-scrollbar";
125
+ function ensureTimePicker1ScrollbarStyles() {
126
+ if (typeof document === "undefined") return;
127
+ if (document.getElementById(SCROLLBAR_STYLE_ID)) return;
128
+ const style = document.createElement("style");
129
+ style.id = SCROLLBAR_STYLE_ID;
130
+ style.textContent = `
131
+ .velocis-timepicker1-scroll {
132
+ scrollbar-width: thin;
133
+ scrollbar-color: var(--velocis-timepicker1-scrollbar-thumb, rgba(255, 255, 255, 0.18)) transparent;
134
+ }
135
+ .velocis-timepicker1-scroll::-webkit-scrollbar {
136
+ width: var(--velocis-timepicker1-scrollbar-width, 2px);
137
+ }
138
+ .velocis-timepicker1-scroll::-webkit-scrollbar-track {
139
+ background: transparent;
140
+ }
141
+ .velocis-timepicker1-scroll::-webkit-scrollbar-thumb {
142
+ min-height: 28px;
143
+ border-radius: 9999px;
144
+ background-color: var(--velocis-timepicker1-scrollbar-thumb, rgba(255, 255, 255, 0.18));
145
+ transition: background-color 0.2s ease;
146
+ }
147
+ .velocis-timepicker1-scroll:hover::-webkit-scrollbar-thumb {
148
+ background-color: color-mix(
149
+ in srgb,
150
+ var(--velocis-timepicker1-scrollbar-thumb, rgba(255, 255, 255, 0.18)) 55%,
151
+ white
152
+ );
153
+ }
154
+ .velocis-timepicker1-scroll:active::-webkit-scrollbar-thumb {
155
+ background-color: color-mix(
156
+ in srgb,
157
+ var(--velocis-timepicker1-scrollbar-thumb, rgba(255, 255, 255, 0.18)) 40%,
158
+ white
159
+ );
160
+ }
161
+ `;
162
+ document.head.appendChild(style);
163
+ }
164
+
165
+ // src/utils/timepicker1DisabledRules.ts
166
+ function timeToMinutes(time) {
167
+ return time.hours * 60 + time.minutes;
168
+ }
169
+ function isSameTimeValue(a, b) {
170
+ return a.hours === b.hours && a.minutes === b.minutes;
171
+ }
172
+ function isTimeRuleDisabled(time, rule) {
173
+ switch (rule.type) {
174
+ case "time":
175
+ return isSameTimeValue(time, rule.time);
176
+ case "times":
177
+ return rule.times.some((t) => isSameTimeValue(time, t));
178
+ case "hour":
179
+ return time.hours === rule.hour;
180
+ case "minute":
181
+ return time.minutes === rule.minute;
182
+ case "hourMinute":
183
+ return time.hours === rule.hour && time.minutes === rule.minute;
184
+ case "custom":
185
+ return rule.match(time);
186
+ default:
187
+ return false;
188
+ }
189
+ }
190
+ function isTimeDisabledByRules(time, rules) {
191
+ if (!rules?.length) return false;
192
+ return rules.some((rule) => isTimeRuleDisabled(time, rule));
193
+ }
194
+ function isTimePickerTimeDisabled(time, constraints = {}) {
195
+ const { minTime, maxTime, disabledRules } = constraints;
196
+ const total = timeToMinutes(time);
197
+ if (minTime !== void 0 && total < timeToMinutes(minTime)) return true;
198
+ if (maxTime !== void 0 && total > timeToMinutes(maxTime)) return true;
199
+ return isTimeDisabledByRules(time, disabledRules);
200
+ }
201
+
202
+ // src/utils/timeUtils.ts
203
+ function timeToMinutes2(time) {
204
+ return time.hours * 60 + time.minutes;
205
+ }
206
+ function minutesToTime(totalMinutes) {
207
+ const normalized = (totalMinutes % (24 * 60) + 24 * 60) % (24 * 60);
208
+ return {
209
+ hours: Math.floor(normalized / 60),
210
+ minutes: normalized % 60
211
+ };
212
+ }
213
+ function isSameTime(a, b) {
214
+ return a.hours === b.hours && a.minutes === b.minutes;
215
+ }
216
+ function isTimeInRange(time, minTime, maxTime) {
217
+ const total = timeToMinutes2(time);
218
+ if (minTime !== void 0 && total < timeToMinutes2(minTime)) return false;
219
+ if (maxTime !== void 0 && total > timeToMinutes2(maxTime)) return false;
220
+ return true;
221
+ }
222
+ function ceilTimeToStep(time, step) {
223
+ const total = timeToMinutes2(time);
224
+ return minutesToTime(Math.ceil(total / step) * step);
225
+ }
226
+ function getNextSlotAfterNow(step = 15) {
227
+ const now = /* @__PURE__ */ new Date();
228
+ const totalMinutes = now.getHours() * 60 + now.getMinutes();
229
+ const nextTotal = Math.ceil((totalMinutes + 1) / step) * step;
230
+ return minutesToTime(nextTotal);
231
+ }
232
+ function getEffectiveSlotStart(options) {
233
+ const candidates = [0];
234
+ if (options.startFromNow) {
235
+ candidates.push(timeToMinutes2(getNextSlotAfterNow(options.minuteStep)));
236
+ }
237
+ if (options.minTime !== void 0) {
238
+ candidates.push(timeToMinutes2(ceilTimeToStep(options.minTime, options.minuteStep)));
239
+ }
240
+ return minutesToTime(Math.max(...candidates));
241
+ }
242
+ function generateBoundedTimeSlots(options) {
243
+ const start = getEffectiveSlotStart({
244
+ startFromNow: options.startFromNow,
245
+ minTime: options.minTime,
246
+ minuteStep: options.minuteStep
247
+ });
248
+ const slots = [];
249
+ let current = timeToMinutes2(start);
250
+ const maxMinutes = options.maxTime !== void 0 ? timeToMinutes2(options.maxTime) : 24 * 60 - options.minuteStep;
251
+ while (slots.length < options.slotCount && current <= maxMinutes) {
252
+ const slot = minutesToTime(current);
253
+ if (isTimeInRange(slot, options.minTime, options.maxTime) && !isTimePickerTimeDisabled(slot, {
254
+ minTime: options.minTime,
255
+ maxTime: options.maxTime,
256
+ disabledRules: options.disabledRules
257
+ })) {
258
+ slots.push(slot);
259
+ }
260
+ current += options.minuteStep;
261
+ if (current >= 24 * 60) break;
262
+ }
263
+ return slots;
264
+ }
265
+ function padTwo(n) {
266
+ return n.toString().padStart(2, "0");
267
+ }
268
+ function formatTimeSlot(time, locale, use24Hour) {
269
+ if (!time) return "";
270
+ if (use24Hour) {
271
+ return `${padTwo(time.hours)}:${padTwo(time.minutes)}`;
272
+ }
273
+ if (locale.startsWith("ar")) {
274
+ const h12 = time.hours % 12 || 12;
275
+ const period = time.hours < 12 ? "\u0635" : "\u0645";
276
+ return `${h12}:${padTwo(time.minutes)} ${period}`;
277
+ }
278
+ const date = /* @__PURE__ */ new Date();
279
+ date.setHours(time.hours, time.minutes, 0, 0);
280
+ return date.toLocaleTimeString(locale, {
281
+ hour: "numeric",
282
+ minute: "2-digit",
283
+ hour12: true
284
+ });
285
+ }
286
+ function clampTimeToRange(time, minTime, maxTime) {
287
+ let total = timeToMinutes2(time);
288
+ if (minTime !== void 0) {
289
+ total = Math.max(total, timeToMinutes2(minTime));
290
+ }
291
+ if (maxTime !== void 0) {
292
+ total = Math.min(total, timeToMinutes2(maxTime));
293
+ }
294
+ return minutesToTime(total);
295
+ }
296
+
297
+ // src/components/TimePickerPanel.tsx
298
+ var import_jsx_runtime = require("react/jsx-runtime");
299
+ function TimePickerPanel({
300
+ value,
301
+ colors,
302
+ locale,
303
+ use24Hour,
304
+ minuteStep,
305
+ slotCount,
306
+ startFromNow,
307
+ isOpen,
308
+ minTime,
309
+ maxTime,
310
+ disabledRules,
311
+ listHeight,
312
+ listClassName,
313
+ onChange,
314
+ className
315
+ }) {
316
+ const listRef = (0, import_react.useRef)(null);
317
+ const selectedRef = (0, import_react.useRef)(null);
318
+ const resolvedColors = {
319
+ panelBgColor: colors.panelBgColor ?? TIMEPICKER1_DEFAULT_COLORS.panelBgColor,
320
+ activeItemColor: colors.activeItemColor ?? TIMEPICKER1_DEFAULT_COLORS.activeItemColor
321
+ };
322
+ const slots = (0, import_react.useMemo)(
323
+ () => generateBoundedTimeSlots({
324
+ startFromNow,
325
+ minTime,
326
+ maxTime,
327
+ disabledRules,
328
+ minuteStep,
329
+ slotCount
330
+ }),
331
+ [startFromNow, minTime, maxTime, disabledRules, minuteStep, slotCount, isOpen]
332
+ );
333
+ const timeConstraints = (0, import_react.useMemo)(
334
+ () => ({ minTime, maxTime, disabledRules }),
335
+ [minTime, maxTime, disabledRules]
336
+ );
337
+ (0, import_react.useEffect)(() => {
338
+ ensureTimePicker1ScrollbarStyles();
339
+ }, []);
340
+ (0, import_react.useEffect)(() => {
341
+ if (value) {
342
+ selectedRef.current?.scrollIntoView({ block: "nearest" });
343
+ }
344
+ }, [value, slots]);
345
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: (0, import_core.cn)("text-start", className), children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
346
+ "div",
347
+ {
348
+ ref: listRef,
349
+ role: "listbox",
350
+ "aria-label": locale.startsWith("ar") ? "\u0627\u062E\u062A\u0631 \u0627\u0644\u0648\u0642\u062A" : "Select time",
351
+ className: (0, import_core.cn)(
352
+ "overflow-y-auto rounded-2xl",
353
+ timepicker1ListScrollbarStyles,
354
+ listClassName
355
+ ),
356
+ style: {
357
+ height: resolveListHeight(listHeight),
358
+ ...resolveScrollbarStyle(colors)
359
+ },
360
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "flex flex-col gap-0.5 p-1", children: slots.map((slot) => {
361
+ const isActive = value ? isSameTime(slot, value) : false;
362
+ const isDisabled = isTimePickerTimeDisabled(slot, timeConstraints);
363
+ const label = formatTimeSlot(slot, locale, use24Hour);
364
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
365
+ "button",
366
+ {
367
+ ref: isActive ? selectedRef : void 0,
368
+ type: "button",
369
+ role: "option",
370
+ "aria-selected": isActive,
371
+ disabled: isDisabled,
372
+ onClick: () => !isDisabled && onChange(slot),
373
+ className: (0, import_core.cn)(
374
+ timePicker1ItemVariants(),
375
+ "w-full justify-start px-3",
376
+ isDisabled && "cursor-not-allowed opacity-40",
377
+ !isActive && !isDisabled && "text-velocis-foreground hover:bg-velocis-border/30"
378
+ ),
379
+ style: isActive ? { backgroundColor: resolvedColors.activeItemColor, color: "#ffffff" } : void 0,
380
+ children: label
381
+ },
382
+ `${slot.hours}-${slot.minutes}`
383
+ );
384
+ }) })
385
+ }
386
+ ) });
387
+ }
388
+
389
+ // src/positioning/computePopoverStyle.ts
390
+ var import_core2 = require("@velocis/core");
391
+ function parseContentWidthClass(contentWidth) {
392
+ const match = contentWidth.match(/^w-(\d+(?:\.\d+)?|\[\d+px\])$/);
393
+ if (!match?.[1]) return void 0;
394
+ const value = match[1];
395
+ if (value.startsWith("[")) {
396
+ return parseInt(value.slice(1, -2), 10);
397
+ }
398
+ const remMap = {
399
+ "72": 288,
400
+ "80": 320,
401
+ "96": 384
402
+ };
403
+ return remMap[value];
404
+ }
405
+
406
+ // src/TimePicker1.tsx
407
+ var import_jsx_runtime2 = require("react/jsx-runtime");
408
+ function useControllableOpen(controlledOpen, defaultOpen, onOpenChange) {
409
+ const [uncontrolledOpen, setUncontrolledOpen] = (0, import_react2.useState)(defaultOpen);
410
+ const isControlled = controlledOpen !== void 0;
411
+ const open = isControlled ? controlledOpen : uncontrolledOpen;
412
+ const setOpen = (0, import_react2.useCallback)(
413
+ (next) => {
414
+ if (!isControlled) {
415
+ setUncontrolledOpen(next);
416
+ }
417
+ onOpenChange?.(next);
418
+ },
419
+ [isControlled, onOpenChange]
420
+ );
421
+ return { open, setOpen };
422
+ }
423
+ function useControllableValue(controlledValue, defaultValue, onChange) {
424
+ const [uncontrolledValue, setUncontrolledValue] = (0, import_react2.useState)(
425
+ defaultValue ?? null
426
+ );
427
+ const isControlled = controlledValue !== void 0;
428
+ const value = isControlled ? controlledValue ?? null : uncontrolledValue;
429
+ const setValue = (0, import_react2.useCallback)(
430
+ (next) => {
431
+ if (!isControlled) {
432
+ setUncontrolledValue(next);
433
+ }
434
+ onChange?.(next);
435
+ },
436
+ [isControlled, onChange]
437
+ );
438
+ return { value, setValue };
439
+ }
440
+ function ClockIcon({ className }) {
441
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
442
+ "svg",
443
+ {
444
+ className,
445
+ width: "16",
446
+ height: "16",
447
+ viewBox: "0 0 16 16",
448
+ fill: "none",
449
+ "aria-hidden": true,
450
+ children: [
451
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("circle", { cx: "8", cy: "8", r: "6", stroke: "currentColor", strokeWidth: "1.5" }),
452
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M8 5v3.5l2.5 1.5", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round" })
453
+ ]
454
+ }
455
+ );
456
+ }
457
+ function TimePicker1Root({
458
+ trigger,
459
+ value: controlledValue,
460
+ defaultValue,
461
+ onChange,
462
+ open: controlledOpen,
463
+ defaultOpen = TIMEPICKER1_DEFAULTS.defaultOpen,
464
+ onOpenChange,
465
+ colors,
466
+ locale: localeProp,
467
+ use24Hour = TIMEPICKER1_DEFAULTS.use24Hour,
468
+ minuteStep = TIMEPICKER1_DEFAULTS.minuteStep,
469
+ slotCount = TIMEPICKER1_DEFAULTS.slotCount,
470
+ startFromNow = TIMEPICKER1_DEFAULTS.startFromNow,
471
+ minTime,
472
+ maxTime,
473
+ disabledRules,
474
+ listHeight = TIMEPICKER1_DEFAULTS.listHeight,
475
+ listClassName,
476
+ triggerClassName,
477
+ contentClassName,
478
+ contentWidth = TIMEPICKER1_DEFAULTS.contentWidth,
479
+ fullWidth = TIMEPICKER1_DEFAULTS.fullWidth,
480
+ disabled = TIMEPICKER1_DEFAULTS.disabled,
481
+ closeOnSelect = TIMEPICKER1_DEFAULTS.closeOnSelect,
482
+ closeOnScroll = TIMEPICKER1_DEFAULTS.closeOnScroll,
483
+ contentZIndex = TIMEPICKER1_DEFAULTS.contentZIndex,
484
+ testId,
485
+ contentTestId
486
+ }) {
487
+ const direction = (0, import_core3.useDirection)();
488
+ const locale = localeProp ?? (direction === "rtl" ? "ar" : "en");
489
+ const { open, setOpen } = useControllableOpen(controlledOpen, defaultOpen, onOpenChange);
490
+ const { value, setValue } = useControllableValue(controlledValue, defaultValue, onChange);
491
+ const contentRef = (0, import_react2.useRef)(null);
492
+ const buttonRef = (0, import_react2.useRef)(null);
493
+ const getPanelWidth = (0, import_react2.useCallback)(
494
+ (anchorRect) => {
495
+ if (fullWidth) return anchorRect.width;
496
+ const contentWidthPx = parseContentWidthClass(contentWidth);
497
+ return contentWidthPx ?? Math.min(320, window.innerWidth - 16);
498
+ },
499
+ [contentWidth, fullWidth]
500
+ );
501
+ const positionStyle = (0, import_core3.useFloatingPosition)({
502
+ open,
503
+ anchorRef: buttonRef,
504
+ contentRef,
505
+ direction,
506
+ getWidth: getPanelWidth,
507
+ offset: 4,
508
+ preferredPlacement: "bottom",
509
+ fullWidth
510
+ });
511
+ const resolvedColors = {
512
+ panelBgColor: colors?.panelBgColor ?? TIMEPICKER1_DEFAULT_COLORS.panelBgColor,
513
+ activeItemColor: colors?.activeItemColor ?? TIMEPICKER1_DEFAULT_COLORS.activeItemColor
514
+ };
515
+ (0, import_react2.useEffect)(() => {
516
+ if (!open) return;
517
+ const handleClickOutside = (event) => {
518
+ if (buttonRef.current && !buttonRef.current.contains(event.target) && !contentRef.current?.contains(event.target)) {
519
+ setOpen(false);
520
+ }
521
+ };
522
+ const handleKeyDown = (event) => {
523
+ if (event.key === "Escape") {
524
+ setOpen(false);
525
+ }
526
+ };
527
+ const handleScroll = (event) => {
528
+ if (!closeOnScroll) return;
529
+ const target = event.target;
530
+ if (target && !contentRef.current?.contains(target) && !buttonRef.current?.contains(target)) {
531
+ setOpen(false);
532
+ }
533
+ };
534
+ document.addEventListener("mousedown", handleClickOutside);
535
+ document.addEventListener("keydown", handleKeyDown);
536
+ document.addEventListener("scroll", handleScroll, true);
537
+ return () => {
538
+ document.removeEventListener("mousedown", handleClickOutside);
539
+ document.removeEventListener("keydown", handleKeyDown);
540
+ document.removeEventListener("scroll", handleScroll, true);
541
+ };
542
+ }, [closeOnScroll, open, setOpen]);
543
+ const handleChange = (time) => {
544
+ if (!isTimeInRange(time, minTime, maxTime)) return;
545
+ if (isTimePickerTimeDisabled(time, { minTime, maxTime, disabledRules })) return;
546
+ setValue(time);
547
+ if (closeOnSelect) {
548
+ setOpen(false);
549
+ }
550
+ };
551
+ const displayTrigger = trigger ?? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { className: "flex items-center gap-2", children: [
552
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(ClockIcon, { className: "text-velocis-muted" }),
553
+ value ? formatTimeSlot(value, locale, use24Hour) : locale.startsWith("ar") ? "\u0627\u062E\u062A\u0631 \u0627\u0644\u0648\u0642\u062A" : "Select time"
554
+ ] });
555
+ const pickerContent = open ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
556
+ "div",
557
+ {
558
+ ref: contentRef,
559
+ role: "dialog",
560
+ "aria-modal": "false",
561
+ "data-testid": contentTestId,
562
+ dir: direction,
563
+ className: (0, import_core3.cn)(timePicker1ContentVariants(), contentClassName),
564
+ style: {
565
+ ...positionStyle,
566
+ zIndex: contentZIndex,
567
+ backgroundColor: resolvedColors.panelBgColor
568
+ },
569
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
570
+ TimePickerPanel,
571
+ {
572
+ value,
573
+ colors: resolvedColors,
574
+ locale,
575
+ use24Hour,
576
+ minuteStep,
577
+ slotCount,
578
+ startFromNow,
579
+ isOpen: open,
580
+ minTime,
581
+ maxTime,
582
+ disabledRules,
583
+ listHeight,
584
+ listClassName,
585
+ onChange: handleChange
586
+ }
587
+ )
588
+ }
589
+ ) : null;
590
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
591
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
592
+ "div",
593
+ {
594
+ className: (0, import_core3.cn)(
595
+ "relative text-start",
596
+ fullWidth ? "block w-full" : "inline-block"
597
+ ),
598
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
599
+ "button",
600
+ {
601
+ ref: buttonRef,
602
+ type: "button",
603
+ disabled,
604
+ "aria-expanded": open,
605
+ "aria-haspopup": "dialog",
606
+ onClick: () => !disabled && setOpen(!open),
607
+ "data-testid": testId,
608
+ className: (0, import_core3.cn)(
609
+ timePicker1TriggerVariants({ fullWidth, disabled }),
610
+ triggerClassName
611
+ ),
612
+ children: displayTrigger
613
+ }
614
+ )
615
+ }
616
+ ),
617
+ open && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_core3.Portal, { children: pickerContent })
618
+ ] });
619
+ }
620
+
621
+ // src/hooks/useTimePicker1.ts
622
+ var import_react3 = require("react");
623
+ function useTimePicker1(options) {
624
+ const [open, setOpen] = (0, import_react3.useState)(options?.defaultOpen ?? false);
625
+ const anchorRef = (0, import_react3.useRef)(null);
626
+ const isValueControlled = options?.value !== void 0;
627
+ const [uncontrolledValue, setUncontrolledValue] = (0, import_react3.useState)(
628
+ options?.defaultValue ?? null
629
+ );
630
+ const value = isValueControlled ? options?.value ?? null : uncontrolledValue;
631
+ const setValue = (0, import_react3.useCallback)(
632
+ (time) => {
633
+ if (!isValueControlled) {
634
+ setUncontrolledValue(time);
635
+ }
636
+ options?.onChange?.(time);
637
+ },
638
+ [isValueControlled, options]
639
+ );
640
+ const isOpenControlled = options?.open !== void 0;
641
+ const resolvedOpen = isOpenControlled ? options?.open ?? false : open;
642
+ const setOpenState = (0, import_react3.useCallback)(
643
+ (next) => {
644
+ if (!isOpenControlled) {
645
+ setOpen(next);
646
+ }
647
+ options?.onOpenChange?.(next);
648
+ },
649
+ [isOpenControlled, options]
650
+ );
651
+ const show = (0, import_react3.useCallback)(() => setOpenState(true), [setOpenState]);
652
+ const hide = (0, import_react3.useCallback)(() => setOpenState(false), [setOpenState]);
653
+ return {
654
+ value,
655
+ setValue,
656
+ open: resolvedOpen,
657
+ setOpen: setOpenState,
658
+ show,
659
+ hide,
660
+ anchorRef,
661
+ timePickerProps: {
662
+ value,
663
+ onChange: setValue,
664
+ open: resolvedOpen,
665
+ onOpenChange: setOpenState,
666
+ colors: options?.colors,
667
+ locale: options?.locale,
668
+ use24Hour: options?.use24Hour,
669
+ minuteStep: options?.minuteStep,
670
+ slotCount: options?.slotCount,
671
+ startFromNow: options?.startFromNow,
672
+ minTime: options?.minTime,
673
+ maxTime: options?.maxTime,
674
+ disabledRules: options?.disabledRules,
675
+ listHeight: options?.listHeight,
676
+ listClassName: options?.listClassName,
677
+ closeOnSelect: options?.closeOnSelect,
678
+ contentWidth: options?.contentWidth,
679
+ fullWidth: options?.fullWidth
680
+ }
681
+ };
682
+ }
683
+
684
+ // src/index.ts
685
+ var TimePicker1 = Object.assign(TimePicker1Root, {});
686
+ // Annotate the CommonJS export names for ESM import in node:
687
+ 0 && (module.exports = {
688
+ TIMEPICKER1_DEFAULTS,
689
+ TIMEPICKER1_DEFAULT_COLORS,
690
+ TIMEPICKER1_SCROLLBAR_WIDTH_PX,
691
+ TimePicker1,
692
+ TimePicker1Root,
693
+ TimePickerPanel,
694
+ clampTimeToRange,
695
+ formatTimeSlot,
696
+ generateBoundedTimeSlots,
697
+ getNextSlotAfterNow,
698
+ isTimeDisabledByRules,
699
+ isTimeInRange,
700
+ isTimePickerTimeDisabled,
701
+ isTimeRuleDisabled,
702
+ resolveListHeight,
703
+ resolveScrollbarStyle,
704
+ timePicker1ContentVariants,
705
+ timePicker1ItemVariants,
706
+ timePicker1TriggerVariants,
707
+ timepicker1ListScrollbarStyles,
708
+ useTimePicker1
709
+ });