@comitor/ui 3.0.0 → 3.1.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.
Files changed (110) hide show
  1. package/README.md +5 -1
  2. package/dist/components/composite/confirm-dialog.js +56 -35
  3. package/dist/components/composite/confirm-dialog.js.map +1 -1
  4. package/dist/components/composite/search-filter-bar-constants.d.ts +3 -0
  5. package/dist/components/composite/search-filter-bar-constants.js.map +1 -1
  6. package/dist/components/composite/table-pagination-constants.d.ts +3 -1
  7. package/dist/components/composite/table-pagination-constants.js.map +1 -1
  8. package/dist/components/composite/table-pagination.js +10 -2
  9. package/dist/components/composite/table-pagination.js.map +1 -1
  10. package/dist/components/composite/text-utils.d.ts +4 -23
  11. package/dist/components/composite/text-utils.js +1 -11
  12. package/dist/components/composite/text-utils.js.map +1 -1
  13. package/dist/components/composite/toast.d.ts +2 -1
  14. package/dist/components/composite/toast.js +3 -9
  15. package/dist/components/composite/toast.js.map +1 -1
  16. package/dist/components/ui/action-bar.js +45 -1
  17. package/dist/components/ui/action-bar.js.map +1 -1
  18. package/dist/components/ui/banner.d.ts +8 -0
  19. package/dist/components/ui/banner.js +149 -35
  20. package/dist/components/ui/banner.js.map +1 -1
  21. package/dist/components/ui/circular-progress.js +10 -2
  22. package/dist/components/ui/circular-progress.js.map +1 -1
  23. package/dist/components/ui/kanban.js +9 -3
  24. package/dist/components/ui/kanban.js.map +1 -1
  25. package/dist/components/ui/key-value.js +70 -76
  26. package/dist/components/ui/key-value.js.map +1 -1
  27. package/dist/components/ui/mask-input-constants.d.ts +34 -2
  28. package/dist/components/ui/mask-input-constants.js +87 -76
  29. package/dist/components/ui/mask-input-constants.js.map +1 -1
  30. package/dist/components/ui/mask-input.d.ts +6 -5
  31. package/dist/components/ui/mask-input.js +23 -21
  32. package/dist/components/ui/mask-input.js.map +1 -1
  33. package/dist/components/ui/mention.d.ts +14 -2
  34. package/dist/components/ui/mention.js +33 -6
  35. package/dist/components/ui/mention.js.map +1 -1
  36. package/dist/components/ui/phone-input.d.ts +18 -8
  37. package/dist/components/ui/phone-input.js +125 -35
  38. package/dist/components/ui/phone-input.js.map +1 -1
  39. package/dist/components/ui/rating.js +4 -2
  40. package/dist/components/ui/rating.js.map +1 -1
  41. package/dist/components/ui/scroll-spy.js +46 -31
  42. package/dist/components/ui/scroll-spy.js.map +1 -1
  43. package/dist/components/ui/slider.d.ts +1 -1
  44. package/dist/components/ui/slider.js +15 -1
  45. package/dist/components/ui/slider.js.map +1 -1
  46. package/dist/components/ui/sonner.js +2 -0
  47. package/dist/components/ui/sonner.js.map +1 -1
  48. package/dist/components/ui/stepper.js +26 -3
  49. package/dist/components/ui/stepper.js.map +1 -1
  50. package/dist/components/ui/time-picker.d.ts +24 -4
  51. package/dist/components/ui/time-picker.js +425 -162
  52. package/dist/components/ui/time-picker.js.map +1 -1
  53. package/dist/components/ui/timeline.js +45 -9
  54. package/dist/components/ui/timeline.js.map +1 -1
  55. package/dist/components/ui/toggle-group.js +12 -1
  56. package/dist/components/ui/toggle-group.js.map +1 -1
  57. package/dist/components/ui/tour.js +3 -2
  58. package/dist/components/ui/tour.js.map +1 -1
  59. package/dist/data-grid/data-grid-cell-variants.js +4 -4
  60. package/dist/data-grid/data-grid-cell-variants.js.map +1 -1
  61. package/dist/data-grid/data-grid-cell-wrapper.d.ts +1 -1
  62. package/dist/data-grid/data-grid-cell-wrapper.js +6 -0
  63. package/dist/data-grid/data-grid-cell-wrapper.js.map +1 -1
  64. package/dist/data-grid/data-grid-row.js +1 -1
  65. package/dist/data-grid/data-grid-row.js.map +1 -1
  66. package/dist/data-grid/data-grid-select-column.js +6 -2
  67. package/dist/data-grid/data-grid-select-column.js.map +1 -1
  68. package/dist/data-grid/data-grid-skeleton.d.ts +5 -0
  69. package/dist/data-grid/data-grid-skeleton.js +1 -4
  70. package/dist/data-grid/data-grid-skeleton.js.map +1 -1
  71. package/dist/data-grid/data-grid.d.ts +1 -1
  72. package/dist/data-grid/data-grid.js +3 -2
  73. package/dist/data-grid/data-grid.js.map +1 -1
  74. package/dist/data-grid/filters.js +3 -2
  75. package/dist/data-grid/filters.js.map +1 -1
  76. package/dist/data-grid/types.d.ts +7 -0
  77. package/dist/data-grid/use-data-grid.d.ts +1 -0
  78. package/dist/data-grid/use-data-grid.js +70 -24
  79. package/dist/data-grid/use-data-grid.js.map +1 -1
  80. package/dist/data-grid/utils.d.ts +7 -0
  81. package/dist/data-grid/utils.js +3 -1
  82. package/dist/data-grid/utils.js.map +1 -1
  83. package/dist/i18n/locale-provider.d.ts +18 -0
  84. package/dist/i18n/locale-provider.js.map +1 -1
  85. package/dist/i18n/namespaces/rating.js +2 -1
  86. package/dist/i18n/namespaces/rating.js.map +1 -1
  87. package/dist/i18n/namespaces/search-filter-bar.js +8 -6
  88. package/dist/i18n/namespaces/search-filter-bar.js.map +1 -1
  89. package/dist/i18n/namespaces/shell.d.ts +4 -0
  90. package/dist/i18n/namespaces/shell.js +1 -1
  91. package/dist/i18n/namespaces/shell.js.map +1 -1
  92. package/dist/i18n/namespaces/table-pagination.js +6 -4
  93. package/dist/i18n/namespaces/table-pagination.js.map +1 -1
  94. package/dist/i18n/namespaces/toast.d.ts +3 -2
  95. package/dist/i18n/namespaces/toast.js.map +1 -1
  96. package/dist/index.d.ts +2 -1
  97. package/dist/lib/collect-item-elements.d.ts +47 -0
  98. package/dist/lib/collect-item-elements.js +22 -0
  99. package/dist/lib/collect-item-elements.js.map +1 -0
  100. package/dist/lib/text-search.d.ts +32 -0
  101. package/dist/lib/text-search.js +17 -0
  102. package/dist/lib/text-search.js.map +1 -0
  103. package/dist/lib/toast-language.d.ts +8 -0
  104. package/dist/lib/toast-language.js +19 -0
  105. package/dist/lib/toast-language.js.map +1 -0
  106. package/dist/shell/app-shell.d.ts +1 -0
  107. package/dist/shell/index.d.ts +2 -1
  108. package/dist/shell/shell-context.d.ts +1 -0
  109. package/dist/shell/utils.d.ts +2 -1
  110. package/package.json +1 -1
@@ -125,6 +125,208 @@ function getPeriodFromKey(key, labels) {
125
125
  if (lowerKey === labels.pm.charAt(0).toLowerCase()) return "PM";
126
126
  return null;
127
127
  }
128
+ const SECONDS_PER_HOUR = 3600;
129
+ const SECONDS_PER_HALF_DAY = 43200;
130
+ const LAST_SECOND_OF_DAY = 86399;
131
+ const LAST_MINUTE_OF_DAY = LAST_SECOND_OF_DAY - 59;
132
+ function toSeconds(time, showSeconds) {
133
+ return (time.hour ?? 0) * SECONDS_PER_HOUR + (time.minute ?? 0) * 60 + (showSeconds ? time.second ?? 0 : 0);
134
+ }
135
+ function fromSeconds(seconds) {
136
+ return {
137
+ hour: Math.floor(seconds / SECONDS_PER_HOUR),
138
+ minute: Math.floor(seconds % SECONDS_PER_HOUR / 60),
139
+ second: seconds % 60
140
+ };
141
+ }
142
+ function parseTimeBound(bound) {
143
+ const time = parseTimeString(bound);
144
+ if (!time || time.hour === void 0 || time.minute === void 0) return null;
145
+ return toSeconds(time, true);
146
+ }
147
+ function getTimeRange(min, max, showSeconds) {
148
+ const lower = parseTimeBound(min);
149
+ const upper = parseTimeBound(max);
150
+ if (lower === null && upper === null) return null;
151
+ const range = { min: lower ?? 0, max: upper ?? LAST_SECOND_OF_DAY, wraps: false };
152
+ range.wraps = range.min > range.max;
153
+ if (!showSeconds) {
154
+ range.min = Math.min(Math.ceil(range.min / 60) * 60, LAST_MINUTE_OF_DAY);
155
+ range.max = Math.floor(range.max / 60) * 60;
156
+ if (!range.wraps && range.min > range.max) range.min = range.max;
157
+ }
158
+ return range;
159
+ }
160
+ function findNearestAllowed(seconds, range, from = 0, to = LAST_SECOND_OF_DAY) {
161
+ const intervals = range.wraps ? [
162
+ [0, range.max],
163
+ [range.min, LAST_SECOND_OF_DAY]
164
+ ] : [[range.min, range.max]];
165
+ let nearest = null;
166
+ for (const [start, end] of intervals) {
167
+ const low = Math.max(start, from);
168
+ const high = Math.min(end, to);
169
+ if (low > high) continue;
170
+ const candidate = clamp(seconds, low, high);
171
+ if (nearest === null || Math.abs(candidate - seconds) < Math.abs(nearest - seconds)) {
172
+ nearest = candidate;
173
+ }
174
+ }
175
+ return nearest;
176
+ }
177
+ function getSegmentSpan(time, segment, showSeconds) {
178
+ if (time.hour === void 0) return null;
179
+ const hourStart = time.hour * SECONDS_PER_HOUR;
180
+ if (segment === "period") {
181
+ const halfDayStart = time.hour < 12 ? 0 : SECONDS_PER_HALF_DAY;
182
+ return [halfDayStart, halfDayStart + SECONDS_PER_HALF_DAY - 1];
183
+ }
184
+ if (segment === "hour") return [hourStart, hourStart + SECONDS_PER_HOUR - 1];
185
+ if (time.minute === void 0) return null;
186
+ const minuteStart = hourStart + time.minute * 60;
187
+ if (segment === "minute" || !showSeconds) return [minuteStart, minuteStart + 59];
188
+ if (time.second === void 0) return null;
189
+ return [minuteStart + time.second, minuteStart + time.second];
190
+ }
191
+ function isSegmentAllowed(time, segment, range, showSeconds) {
192
+ if (!range) return true;
193
+ const span = getSegmentSpan(time, segment, showSeconds);
194
+ return !span || findNearestAllowed(span[0], range, span[0], span[1]) !== null;
195
+ }
196
+ function constrainTime(time, range, showSeconds, keep) {
197
+ if (!range || time.hour === void 0) return time;
198
+ const seconds = toSeconds(time, showSeconds);
199
+ const isComplete = time.minute !== void 0 && (!showSeconds || time.second !== void 0);
200
+ if (isComplete) {
201
+ if (findNearestAllowed(seconds, range, seconds, seconds) !== null) return time;
202
+ } else {
203
+ const hourStart = time.hour * SECONDS_PER_HOUR;
204
+ if (findNearestAllowed(hourStart, range, hourStart, hourStart + SECONDS_PER_HOUR - 1) !== null) return time;
205
+ }
206
+ const span = keep && isComplete ? getSegmentSpan(time, keep, showSeconds) : null;
207
+ const nearest = (span && findNearestAllowed(seconds, range, span[0], span[1])) ?? findNearestAllowed(seconds, range) ?? seconds;
208
+ return fromSeconds(nearest);
209
+ }
210
+ function getReferenceTime(range, showSeconds) {
211
+ const now = /* @__PURE__ */ new Date();
212
+ const time = { hour: now.getHours(), minute: now.getMinutes(), second: now.getSeconds() };
213
+ if (!range) return time;
214
+ const seconds = toSeconds(time, showSeconds);
215
+ const nearest = findNearestAllowed(seconds, range);
216
+ return nearest === null || nearest === seconds ? time : fromSeconds(nearest);
217
+ }
218
+ function buildHourTime(timeValue, displayHour, is12Hour, showSeconds, reference) {
219
+ const currentTime = timeValue ?? {};
220
+ let hour24 = displayHour;
221
+ if (is12Hour) {
222
+ let currentPeriod;
223
+ if (timeValue?.period !== void 0) {
224
+ currentPeriod = timeValue.period;
225
+ } else if (timeValue?.hour !== void 0) {
226
+ currentPeriod = to12Hour(timeValue.hour).period;
227
+ } else {
228
+ currentPeriod = to12Hour(reference.hour).period;
229
+ }
230
+ hour24 = to24Hour(displayHour, currentPeriod);
231
+ }
232
+ const newTime = { ...currentTime, hour: hour24 };
233
+ if (timeValue && timeValue.period !== void 0) {
234
+ newTime.period = timeValue.period;
235
+ }
236
+ if (newTime.minute === void 0) {
237
+ newTime.minute = reference.minute;
238
+ }
239
+ if (showSeconds && newTime.second === void 0) {
240
+ newTime.second = reference.second;
241
+ }
242
+ return newTime;
243
+ }
244
+ function buildMinuteTime(timeValue, minute, showSeconds, reference) {
245
+ const currentTime = timeValue ?? {};
246
+ const newTime = { ...currentTime, minute };
247
+ if (newTime.hour === void 0) {
248
+ newTime.hour = reference.hour;
249
+ }
250
+ if (showSeconds && newTime.second === void 0) {
251
+ newTime.second = reference.second;
252
+ }
253
+ return newTime;
254
+ }
255
+ function buildSecondTime(timeValue, second, reference) {
256
+ const currentTime = timeValue ?? {};
257
+ const newTime = { ...currentTime, second };
258
+ if (newTime.hour === void 0) {
259
+ newTime.hour = reference.hour;
260
+ }
261
+ if (newTime.minute === void 0) {
262
+ newTime.minute = reference.minute;
263
+ }
264
+ return newTime;
265
+ }
266
+ function buildPeriodTime(timeValue, period, showSeconds, reference) {
267
+ const currentTime = timeValue ?? {};
268
+ const currentHour = currentTime.hour !== void 0 ? currentTime.hour : reference.hour;
269
+ const currentDisplay = to12Hour(currentHour);
270
+ const new24Hour = to24Hour(currentDisplay.hour, period);
271
+ const newTime = { ...currentTime, hour: new24Hour };
272
+ if (newTime.minute === void 0) {
273
+ newTime.minute = reference.minute;
274
+ }
275
+ if (showSeconds && newTime.second === void 0) {
276
+ newTime.second = reference.second;
277
+ }
278
+ return newTime;
279
+ }
280
+ function getColumnEntry(items) {
281
+ return items.find((item) => item.selected && !item.disabled) ?? items.find((item) => !item.disabled) ?? items.find((item) => item.selected) ?? items[0];
282
+ }
283
+ function getColumnEntryRefs(items) {
284
+ const entry = getColumnEntry(items);
285
+ const refs = items.map((item) => item.ref);
286
+ return entry ? [entry.ref, ...refs] : refs;
287
+ }
288
+ function isHiddenWithin(element, container) {
289
+ if (getComputedStyle(element).visibility === "hidden") return true;
290
+ for (let node = element; node && node !== container; node = node.parentElement) {
291
+ if (getComputedStyle(node).display === "none") return true;
292
+ }
293
+ return false;
294
+ }
295
+ function getTabbableElements(container) {
296
+ const elements = [];
297
+ const walker = document.createTreeWalker(container, NodeFilter.SHOW_ELEMENT, {
298
+ acceptNode: (node) => {
299
+ const element = node;
300
+ const isHiddenInput = element instanceof HTMLInputElement && element.type === "hidden";
301
+ if (element.disabled || element.hidden || isHiddenInput) {
302
+ return NodeFilter.FILTER_SKIP;
303
+ }
304
+ return element.tabIndex >= 0 ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_SKIP;
305
+ }
306
+ });
307
+ while (walker.nextNode()) elements.push(walker.currentNode);
308
+ return elements.filter((element) => !isHiddenWithin(element, container));
309
+ }
310
+ function getTabTarget(content, column, backward) {
311
+ const tabbables = getTabbableElements(content);
312
+ const isOutsideColumn = (element) => !column.contains(element);
313
+ if (backward) {
314
+ for (let index = tabbables.length - 1; index >= 0; index--) {
315
+ const element = tabbables[index];
316
+ if (element && isOutsideColumn(element) && (column.compareDocumentPosition(element) & Node.DOCUMENT_POSITION_PRECEDING) !== 0) {
317
+ return element;
318
+ }
319
+ }
320
+ return tabbables[tabbables.length - 1];
321
+ }
322
+ return tabbables.find(
323
+ (element) => isOutsideColumn(element) && (column.compareDocumentPosition(element) & Node.DOCUMENT_POSITION_FOLLOWING) !== 0
324
+ ) ?? tabbables[0];
325
+ }
326
+ function focusTabbable(element) {
327
+ element.focus();
328
+ if (element instanceof HTMLInputElement) element.select();
329
+ }
128
330
  const StoreContext = React.createContext(null);
129
331
  function useStoreContext(consumerName) {
130
332
  const context = React.useContext(StoreContext);
@@ -259,6 +461,7 @@ function TimePicker(props) {
259
461
  };
260
462
  }, [segmentPlaceholder]);
261
463
  const labels = useComitorLabels("timePicker", labelsProp);
464
+ const timeRange = React.useMemo(() => getTimeRange(min, max, showSeconds), [min, max, showSeconds]);
262
465
  const rootContext = React.useMemo(
263
466
  () => ({
264
467
  id: rootId,
@@ -282,7 +485,8 @@ function TimePicker(props) {
282
485
  segmentPlaceholder: normalizedPlaceholder,
283
486
  labels,
284
487
  min,
285
- max
488
+ max,
489
+ timeRange
286
490
  }),
287
491
  [
288
492
  rootId,
@@ -303,7 +507,8 @@ function TimePicker(props) {
303
507
  normalizedPlaceholder,
304
508
  labels,
305
509
  min,
306
- max
510
+ max,
511
+ timeRange
307
512
  ]
308
513
  );
309
514
  const RootPrimitive = asChild ? Slot : "div";
@@ -507,6 +712,33 @@ function TimePickerInputGroup(props) {
507
712
  }
508
713
  ) }) });
509
714
  }
715
+ function getSegmentText(timeValue, segment, is12Hour, segmentPlaceholder) {
716
+ if (!timeValue) {
717
+ if (!segment) return "";
718
+ return segmentPlaceholder[segment];
719
+ }
720
+ switch (segment) {
721
+ case "hour": {
722
+ if (timeValue.hour === void 0) return segmentPlaceholder.hour;
723
+ if (is12Hour) {
724
+ return to12Hour(timeValue.hour).hour.toString().padStart(2, "0");
725
+ }
726
+ return timeValue.hour.toString().padStart(2, "0");
727
+ }
728
+ case "minute":
729
+ if (timeValue.minute === void 0) return segmentPlaceholder.minute;
730
+ return timeValue.minute.toString().padStart(2, "0");
731
+ case "second":
732
+ if (timeValue.second === void 0) return segmentPlaceholder.second;
733
+ return timeValue.second.toString().padStart(2, "0");
734
+ case "period": {
735
+ if (!timeValue || timeValue.hour === void 0) return segmentPlaceholder.period;
736
+ return to12Hour(timeValue.hour).period;
737
+ }
738
+ default:
739
+ return "";
740
+ }
741
+ }
510
742
  function TimePickerInput(props) {
511
743
  const {
512
744
  segment,
@@ -522,7 +754,7 @@ function TimePickerInput(props) {
522
754
  ref,
523
755
  ...inputProps
524
756
  } = props;
525
- const { is12Hour, showSeconds, disabled, readOnly, invalid, segmentPlaceholder, labels, openOnFocus } = useTimePickerContext(INPUT_NAME);
757
+ const { is12Hour, showSeconds, disabled, readOnly, invalid, segmentPlaceholder, labels, openOnFocus, timeRange } = useTimePickerContext(INPUT_NAME);
526
758
  const store = useStoreContext(INPUT_NAME);
527
759
  const inputGroupContext = useTimePickerInputGroupContext(INPUT_NAME);
528
760
  const isDisabled = disabledProp || disabled;
@@ -537,33 +769,10 @@ function TimePickerInput(props) {
537
769
  return () => inputGroupContext.onInputUnregister(segment);
538
770
  }
539
771
  }, [inputGroupContext, segment]);
540
- const getSegmentValue = React.useCallback(() => {
541
- if (!timeValue) {
542
- if (!segment) return "";
543
- return segmentPlaceholder[segment];
544
- }
545
- switch (segment) {
546
- case "hour": {
547
- if (timeValue.hour === void 0) return segmentPlaceholder.hour;
548
- if (is12Hour) {
549
- return to12Hour(timeValue.hour).hour.toString().padStart(2, "0");
550
- }
551
- return timeValue.hour.toString().padStart(2, "0");
552
- }
553
- case "minute":
554
- if (timeValue.minute === void 0) return segmentPlaceholder.minute;
555
- return timeValue.minute.toString().padStart(2, "0");
556
- case "second":
557
- if (timeValue.second === void 0) return segmentPlaceholder.second;
558
- return timeValue.second.toString().padStart(2, "0");
559
- case "period": {
560
- if (!timeValue || timeValue.hour === void 0) return segmentPlaceholder.period;
561
- return to12Hour(timeValue.hour).period;
562
- }
563
- default:
564
- return "";
565
- }
566
- }, [timeValue, segment, is12Hour, segmentPlaceholder]);
772
+ const getSegmentValue = React.useCallback(
773
+ () => getSegmentText(timeValue, segment, is12Hour, segmentPlaceholder),
774
+ [timeValue, segment, is12Hour, segmentPlaceholder]
775
+ );
567
776
  const [editValue, setEditValue] = React.useState(getSegmentValue());
568
777
  const [isEditing, setIsEditing] = React.useState(false);
569
778
  const [pendingDigit, setPendingDigit] = React.useState(null);
@@ -592,8 +801,7 @@ function TimePickerInput(props) {
592
801
  } else if (timeValue?.hour !== void 0) {
593
802
  currentPeriod = to12Hour(timeValue.hour).period;
594
803
  } else {
595
- const now = /* @__PURE__ */ new Date();
596
- currentPeriod = to12Hour(now.getHours()).period;
804
+ currentPeriod = to12Hour(getReferenceTime(timeRange, showSeconds).hour).period;
597
805
  }
598
806
  const hour24 = to24Hour(clampedHour, currentPeriod);
599
807
  newTime.hour = hour24;
@@ -632,9 +840,17 @@ function TimePickerInput(props) {
632
840
  }
633
841
  }
634
842
  const newValue = formatTimeValue(newTime, showSeconds);
635
- store.setState("value", newValue);
843
+ if (newValue === store.getState().value) return;
844
+ const committedTime = constrainTime(newTime, timeRange, showSeconds, segment);
845
+ if (committedTime === newTime) {
846
+ store.setState("value", newValue);
847
+ return;
848
+ }
849
+ store.setState("value", formatTimeValue(committedTime, showSeconds));
850
+ setEditValue(getSegmentText(committedTime, segment, is12Hour, segmentPlaceholder));
851
+ setPendingDigit(null);
636
852
  },
637
- [timeValue, segment, is12Hour, showSeconds, store, segmentPlaceholder]
853
+ [timeValue, segment, is12Hour, showSeconds, store, segmentPlaceholder, timeRange]
638
854
  );
639
855
  const onBlur = React.useCallback(
640
856
  (event) => {
@@ -658,23 +874,23 @@ function TimePickerInput(props) {
658
874
  queueMicrotask(() => {
659
875
  const currentTimeValue = parseTimeString(store.getState().value);
660
876
  if (currentTimeValue) {
661
- const now = /* @__PURE__ */ new Date();
877
+ const now = getReferenceTime(timeRange, showSeconds);
662
878
  const newTime = { ...currentTimeValue };
663
879
  let needsUpdate = false;
664
880
  if (newTime.hour === void 0) {
665
- newTime.hour = now.getHours();
881
+ newTime.hour = now.hour;
666
882
  needsUpdate = true;
667
883
  }
668
884
  if (newTime.minute === void 0) {
669
- newTime.minute = now.getMinutes();
885
+ newTime.minute = now.minute;
670
886
  needsUpdate = true;
671
887
  }
672
888
  if (showSeconds && newTime.second === void 0) {
673
- newTime.second = now.getSeconds();
889
+ newTime.second = now.second;
674
890
  needsUpdate = true;
675
891
  }
676
892
  if (needsUpdate) {
677
- const newValue = formatTimeValue(newTime, showSeconds);
893
+ const newValue = formatTimeValue(constrainTime(newTime, timeRange, showSeconds, segment), showSeconds);
678
894
  store.setState("value", newValue);
679
895
  }
680
896
  }
@@ -683,7 +899,17 @@ function TimePickerInput(props) {
683
899
  setEditValue(getSegmentValue());
684
900
  setPendingDigit(null);
685
901
  },
686
- [onBlurProp, editValue, updateTimeValue, getSegmentValue, segment, segmentPlaceholder, showSeconds, store]
902
+ [
903
+ onBlurProp,
904
+ editValue,
905
+ updateTimeValue,
906
+ getSegmentValue,
907
+ segment,
908
+ segmentPlaceholder,
909
+ showSeconds,
910
+ store,
911
+ timeRange
912
+ ]
687
913
  );
688
914
  const onChange = React.useCallback(
689
915
  (event) => {
@@ -1104,11 +1330,14 @@ function TimePickerContent(props) {
1104
1330
  className,
1105
1331
  onOpenAutoFocus: onOpenAutoFocusProp,
1106
1332
  onInteractOutside: onInteractOutsideProp,
1333
+ ref,
1107
1334
  ...contentProps
1108
1335
  } = props;
1109
1336
  const store = useStoreContext(CONTENT_NAME);
1110
1337
  const { openOnFocus, inputGroupRef } = useTimePickerContext(CONTENT_NAME);
1111
1338
  const columnsRef = React.useRef(/* @__PURE__ */ new Map());
1339
+ const contentRef = React.useRef(null);
1340
+ const composedRef = useComposedRefs(ref, contentRef);
1112
1341
  const onColumnRegister = React.useCallback((column) => {
1113
1342
  columnsRef.current.set(column.id, column);
1114
1343
  }, []);
@@ -1116,9 +1345,9 @@ function TimePickerContent(props) {
1116
1345
  columnsRef.current.delete(id);
1117
1346
  }, []);
1118
1347
  const getColumns = React.useCallback(() => {
1119
- const columns = Array.from(columnsRef.current.entries()).map(([id, { ref, getSelectedItemRef, getItems }]) => ({
1348
+ const columns = Array.from(columnsRef.current.entries()).map(([id, { ref: ref2, getSelectedItemRef, getItems }]) => ({
1120
1349
  id,
1121
- ref,
1350
+ ref: ref2,
1122
1351
  getSelectedItemRef,
1123
1352
  getItems
1124
1353
  })).filter((c) => c.ref.current !== null);
@@ -1128,7 +1357,8 @@ function TimePickerContent(props) {
1128
1357
  () => ({
1129
1358
  getColumns,
1130
1359
  onColumnRegister,
1131
- onColumnUnregister
1360
+ onColumnUnregister,
1361
+ contentRef
1132
1362
  }),
1133
1363
  [getColumns, onColumnRegister, onColumnUnregister]
1134
1364
  );
@@ -1145,10 +1375,7 @@ function TimePickerContent(props) {
1145
1375
  const columns = getColumns();
1146
1376
  const firstColumn = columns[0];
1147
1377
  if (!firstColumn) return;
1148
- const items = firstColumn.getItems();
1149
- const selectedItem = items.find((item) => item.selected);
1150
- const candidateRefs = selectedItem ? [selectedItem.ref, ...items.map((item) => item.ref)] : items.map((item) => item.ref);
1151
- focusFirst(candidateRefs, false);
1378
+ focusFirst(getColumnEntryRefs(firstColumn.getItems()), false);
1152
1379
  },
1153
1380
  [onOpenAutoFocusProp, getColumns, store]
1154
1381
  );
@@ -1175,6 +1402,7 @@ function TimePickerContent(props) {
1175
1402
  align,
1176
1403
  sideOffset,
1177
1404
  ...contentProps,
1405
+ ref: composedRef,
1178
1406
  className: cn("flex w-auto max-w-(--radix-popover-trigger-width) p-0", className),
1179
1407
  onOpenAutoFocus,
1180
1408
  onInteractOutside
@@ -1197,8 +1425,8 @@ function TimePickerColumn(props) {
1197
1425
  const itemsRef = React.useRef(/* @__PURE__ */ new Map());
1198
1426
  const groupContext = useTimePickerGroupContext(COLUMN_NAME);
1199
1427
  const onItemRegister = React.useCallback(
1200
- (value, ref2, selected) => {
1201
- itemsRef.current.set(value, { ref: ref2, selected });
1428
+ (value, ref2, selected, disabled) => {
1429
+ itemsRef.current.set(value, { ref: ref2, selected, disabled });
1202
1430
  },
1203
1431
  []
1204
1432
  );
@@ -1206,10 +1434,11 @@ function TimePickerColumn(props) {
1206
1434
  itemsRef.current.delete(value);
1207
1435
  }, []);
1208
1436
  const getItems = React.useCallback(() => {
1209
- const items = Array.from(itemsRef.current.entries()).map(([value, { ref: ref2, selected }]) => ({
1437
+ const items = Array.from(itemsRef.current.entries()).map(([value, { ref: ref2, selected, disabled }]) => ({
1210
1438
  value,
1211
1439
  ref: ref2,
1212
- selected
1440
+ selected,
1441
+ disabled
1213
1442
  })).filter((item) => item.ref.current);
1214
1443
  return sortNodes(items);
1215
1444
  }, []);
@@ -1247,15 +1476,25 @@ function TimePickerColumn(props) {
1247
1476
  ) });
1248
1477
  }
1249
1478
  function TimePickerColumnItem(props) {
1250
- const { value, selected = false, format = "numeric", className, children, ref, ...itemProps } = props;
1479
+ const {
1480
+ value,
1481
+ selected = false,
1482
+ // ⚠ Khác thượng nguồn (3.1.0): mục bị khoá theo `min`/`max` — xem cuối chú thích ở thẻ `<button>`.
1483
+ disabled = false,
1484
+ format = "numeric",
1485
+ className,
1486
+ children,
1487
+ ref,
1488
+ ...itemProps
1489
+ } = props;
1251
1490
  const itemRef = React.useRef(null);
1252
1491
  const composedRef = useComposedRefs(ref, itemRef);
1253
1492
  const columnContext = useTimePickerColumnContext(COLUMN_ITEM_NAME);
1254
1493
  const groupContext = useTimePickerGroupContext(COLUMN_ITEM_NAME);
1255
1494
  useIsomorphicLayoutEffect(() => {
1256
- columnContext.onItemRegister(value, itemRef, selected);
1495
+ columnContext.onItemRegister(value, itemRef, selected, disabled);
1257
1496
  return () => columnContext.onItemUnregister(value);
1258
- }, [columnContext, value, selected]);
1497
+ }, [columnContext, value, selected, disabled]);
1259
1498
  useIsomorphicLayoutEffect(() => {
1260
1499
  if (selected && itemRef.current) {
1261
1500
  itemRef.current.scrollIntoView({ block: "nearest" });
@@ -1263,11 +1502,15 @@ function TimePickerColumnItem(props) {
1263
1502
  }, [selected]);
1264
1503
  const onClick = React.useCallback(
1265
1504
  (event) => {
1505
+ if (disabled) {
1506
+ event.preventDefault();
1507
+ return;
1508
+ }
1266
1509
  itemProps.onClick?.(event);
1267
1510
  if (event.defaultPrevented) return;
1268
1511
  itemRef.current?.focus();
1269
1512
  },
1270
- [itemProps.onClick]
1513
+ [itemProps.onClick, disabled]
1271
1514
  );
1272
1515
  const onKeyDown = React.useCallback(
1273
1516
  (event) => {
@@ -1282,30 +1525,37 @@ function TimePickerColumnItem(props) {
1282
1525
  return 0;
1283
1526
  });
1284
1527
  const currentIndex = items.findIndex((item) => item.value === value);
1285
- let nextIndex;
1286
- if (event.key === "ArrowUp") {
1287
- nextIndex = currentIndex > 0 ? currentIndex - 1 : items.length - 1;
1288
- } else {
1289
- nextIndex = currentIndex < items.length - 1 ? currentIndex + 1 : 0;
1528
+ const step = event.key === "ArrowUp" ? -1 : 1;
1529
+ const start = currentIndex === -1 ? step > 0 ? -1 : items.length : currentIndex;
1530
+ let nextItem;
1531
+ for (let offset = 1; offset <= items.length; offset++) {
1532
+ const candidate = items[((start + step * offset) % items.length + items.length) % items.length];
1533
+ if (candidate && !candidate.disabled) {
1534
+ nextItem = candidate;
1535
+ break;
1536
+ }
1290
1537
  }
1291
- const nextItem = items[nextIndex];
1292
1538
  nextItem?.ref.current?.focus();
1293
1539
  nextItem?.ref.current?.click();
1294
- } else if ((event.key === "Tab" || event.key === "ArrowLeft" || event.key === "ArrowRight") && groupContext) {
1540
+ } else if (event.key === "Tab" && groupContext) {
1541
+ const content = groupContext.contentRef.current;
1542
+ const column = groupContext.getColumns().find((c) => c.ref.current?.contains(itemRef.current) ?? false)?.ref.current;
1543
+ if (!content || !column) return;
1544
+ event.preventDefault();
1545
+ const target = getTabTarget(content, column, event.shiftKey);
1546
+ if (target) queueMicrotask(() => focusTabbable(target));
1547
+ } else if ((event.key === "ArrowLeft" || event.key === "ArrowRight") && groupContext) {
1295
1548
  event.preventDefault();
1296
1549
  queueMicrotask(() => {
1297
1550
  const columns = groupContext.getColumns();
1298
1551
  if (columns.length === 0) return;
1299
1552
  const currentColumnIndex = columns.findIndex((c) => c.ref.current?.contains(itemRef.current) ?? false);
1300
1553
  if (currentColumnIndex === -1) return;
1301
- const goToPrevious = event.key === "ArrowLeft" || event.key === "Tab" && event.shiftKey;
1554
+ const goToPrevious = event.key === "ArrowLeft";
1302
1555
  const nextColumnIndex = goToPrevious ? currentColumnIndex > 0 ? currentColumnIndex - 1 : columns.length - 1 : currentColumnIndex < columns.length - 1 ? currentColumnIndex + 1 : 0;
1303
1556
  const nextColumn = columns[nextColumnIndex];
1304
1557
  if (nextColumn?.ref.current) {
1305
- const items = nextColumn.getItems();
1306
- const selectedItem = items.find((item) => item.selected);
1307
- const candidateRefs = selectedItem ? [selectedItem.ref, ...items.map((item) => item.ref)] : items.map((item) => item.ref);
1308
- focusFirst(candidateRefs, false);
1558
+ focusFirst(getColumnEntryRefs(nextColumn.getItems()), false);
1309
1559
  }
1310
1560
  });
1311
1561
  }
@@ -1319,9 +1569,11 @@ function TimePickerColumnItem(props) {
1319
1569
  type: "button",
1320
1570
  role: "option",
1321
1571
  "aria-selected": selected,
1572
+ "aria-disabled": disabled || void 0,
1322
1573
  ...itemProps,
1323
1574
  ref: composedRef,
1324
1575
  "data-selected": selected ? "" : void 0,
1576
+ "data-disabled": disabled ? "" : void 0,
1325
1577
  className: cn(
1326
1578
  /*
1327
1579
  * ⚠ Khác thượng nguồn: focus đi theo khuôn `Checkbox`/`Calendar` của gói (`focus-visible`,
@@ -1332,9 +1584,17 @@ function TimePickerColumnItem(props) {
1332
1584
  * (`not-data-selected:`) thay vì để `data-selected:hover:` ghi đè lại: cùng kết quả trên
1333
1585
  * màn hình, nhưng cách cũ khiến `pnpm lint:a11y` ghép chữ hover `--accent-foreground`
1334
1586
  * với nền mục đang chọn (1,39:1 ở dark) — một cặp không bao giờ xuất hiện.
1587
+ *
1588
+ * ⚠ Khác thượng nguồn (3.1.0): mục bị khoá theo `min`/`max` mờ và không nhận chuột theo đúng
1589
+ * khuôn mục vô hiệu của gói (`DropdownMenu`, `Select`, `Mention`…: `pointer-events-none` +
1590
+ * `opacity-50`). Dùng `aria-disabled` chứ KHÔNG dùng `disabled` gốc: mục vẫn nhận focus, nên
1591
+ * một cột bị khoá TOÀN BỘ vẫn là một điểm dừng Tab (mục đón rơi vào nó — `getColumnEntry`) mà
1592
+ * trình đọc màn hình đọc là "không khả dụng", thay vì cả cột biến mất khỏi thứ tự Tab. Chọn
1593
+ * thì không được: `onClick` chặn (Enter/Space), mũi tên lên/xuống bỏ qua.
1335
1594
  */
1336
1595
  "w-full rounded px-3 py-1.5 text-left text-sm not-data-selected:hover:bg-accent not-data-selected:hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/50 focus-visible:ring-offset-2 focus-visible:ring-offset-background",
1337
1596
  "data-selected:bg-control-on data-selected:text-control-check",
1597
+ "data-disabled:pointer-events-none data-disabled:opacity-50",
1338
1598
  className
1339
1599
  ),
1340
1600
  onClick,
@@ -1345,7 +1605,7 @@ function TimePickerColumnItem(props) {
1345
1605
  }
1346
1606
  function TimePickerHour(props) {
1347
1607
  const { asChild, format = "numeric", className, ...hourProps } = props;
1348
- const { is12Hour, hourStep, showSeconds, disabled, readOnly, labels } = useTimePickerContext(HOUR_NAME);
1608
+ const { is12Hour, hourStep, showSeconds, disabled, readOnly, labels, timeRange } = useTimePickerContext(HOUR_NAME);
1349
1609
  const store = useStoreContext(HOUR_NAME);
1350
1610
  const value = useStore((state) => state.value);
1351
1611
  const timeValue = parseTimeString(value);
@@ -1364,38 +1624,32 @@ function TimePickerHour(props) {
1364
1624
  const onHourSelect = React.useCallback(
1365
1625
  (displayHour2) => {
1366
1626
  if (disabled || readOnly) return;
1367
- const now2 = /* @__PURE__ */ new Date();
1368
- const currentTime = timeValue ?? {};
1369
- let hour24 = displayHour2;
1370
- if (is12Hour) {
1371
- let currentPeriod;
1372
- if (timeValue?.period !== void 0) {
1373
- currentPeriod = timeValue.period;
1374
- } else if (timeValue?.hour !== void 0) {
1375
- currentPeriod = to12Hour(timeValue.hour).period;
1376
- } else {
1377
- currentPeriod = to12Hour(now2.getHours()).period;
1378
- }
1379
- hour24 = to24Hour(displayHour2, currentPeriod);
1380
- }
1381
- const newTime = { ...currentTime, hour: hour24 };
1382
- if (timeValue && timeValue.period !== void 0) {
1383
- newTime.period = timeValue.period;
1384
- }
1385
- if (newTime.minute === void 0) {
1386
- newTime.minute = now2.getMinutes();
1387
- }
1388
- if (showSeconds && newTime.second === void 0) {
1389
- newTime.second = now2.getSeconds();
1390
- }
1391
- const newValue = formatTimeValue(newTime, showSeconds);
1627
+ const newTime = buildHourTime(
1628
+ timeValue,
1629
+ displayHour2,
1630
+ is12Hour,
1631
+ showSeconds,
1632
+ getReferenceTime(timeRange, showSeconds)
1633
+ );
1634
+ const newValue = formatTimeValue(constrainTime(newTime, timeRange, showSeconds, "hour"), showSeconds);
1392
1635
  store.setState("value", newValue);
1393
1636
  },
1394
- [timeValue, showSeconds, is12Hour, disabled, readOnly, store]
1637
+ [timeValue, showSeconds, is12Hour, disabled, readOnly, store, timeRange]
1395
1638
  );
1396
- const now = /* @__PURE__ */ new Date();
1397
- const referenceHour = timeValue?.hour ?? now.getHours();
1639
+ const reference = getReferenceTime(timeRange, showSeconds);
1640
+ const referenceHour = timeValue?.hour ?? reference.hour;
1398
1641
  const displayHour = is12Hour ? to12Hour(referenceHour).hour : referenceHour;
1642
+ const items = hours.map((hour) => ({
1643
+ value: hour,
1644
+ selected: displayHour === hour,
1645
+ disabled: !isSegmentAllowed(
1646
+ buildHourTime(timeValue, hour, is12Hour, showSeconds, reference),
1647
+ "hour",
1648
+ timeRange,
1649
+ showSeconds
1650
+ )
1651
+ }));
1652
+ const entry = getColumnEntry(items);
1399
1653
  const HourPrimitive = asChild ? Slot : TimePickerColumn;
1400
1654
  return /* @__PURE__ */ jsx(
1401
1655
  HourPrimitive,
@@ -1404,22 +1658,24 @@ function TimePickerHour(props) {
1404
1658
  "aria-label": labels.hour,
1405
1659
  ...hourProps,
1406
1660
  className: cn("scrollbar-none flex max-h-[200px] flex-col gap-1 overflow-y-auto p-1", className),
1407
- children: hours.map((hour) => /* @__PURE__ */ jsx(
1661
+ children: items.map((item) => /* @__PURE__ */ jsx(
1408
1662
  TimePickerColumnItem,
1409
1663
  {
1410
- value: hour,
1411
- selected: displayHour === hour,
1664
+ value: item.value,
1665
+ selected: item.selected,
1666
+ disabled: item.disabled,
1667
+ tabIndex: item === entry ? 0 : -1,
1412
1668
  format,
1413
- onClick: () => onHourSelect(hour)
1669
+ onClick: () => onHourSelect(item.value)
1414
1670
  },
1415
- hour
1671
+ item.value
1416
1672
  ))
1417
1673
  }
1418
1674
  );
1419
1675
  }
1420
1676
  function TimePickerMinute(props) {
1421
1677
  const { asChild, format = "2-digit", className, ...minuteProps } = props;
1422
- const { minuteStep, showSeconds, disabled, readOnly, labels } = useTimePickerContext(MINUTE_NAME);
1678
+ const { minuteStep, showSeconds, disabled, readOnly, labels, timeRange } = useTimePickerContext(MINUTE_NAME);
1423
1679
  const store = useStoreContext(MINUTE_NAME);
1424
1680
  const value = useStore((state) => state.value);
1425
1681
  const timeValue = parseTimeString(value);
@@ -1427,23 +1683,26 @@ function TimePickerMinute(props) {
1427
1683
  const onMinuteSelect = React.useCallback(
1428
1684
  (minute) => {
1429
1685
  if (disabled || readOnly) return;
1430
- const now2 = /* @__PURE__ */ new Date();
1431
- const currentTime = timeValue ?? {};
1432
- const newTime = { ...currentTime, minute };
1433
- if (newTime.hour === void 0) {
1434
- newTime.hour = now2.getHours();
1435
- }
1436
- if (showSeconds && newTime.second === void 0) {
1437
- newTime.second = now2.getSeconds();
1438
- }
1439
- const newValue = formatTimeValue(newTime, showSeconds);
1686
+ const newTime = buildMinuteTime(timeValue, minute, showSeconds, getReferenceTime(timeRange, showSeconds));
1687
+ const newValue = formatTimeValue(constrainTime(newTime, timeRange, showSeconds, "minute"), showSeconds);
1440
1688
  store.setState("value", newValue);
1441
1689
  },
1442
- [timeValue, showSeconds, disabled, readOnly, store]
1690
+ [timeValue, showSeconds, disabled, readOnly, store, timeRange]
1443
1691
  );
1444
1692
  const MinutePrimitive = asChild ? Slot : TimePickerColumn;
1445
- const now = /* @__PURE__ */ new Date();
1446
- const referenceMinute = timeValue?.minute ?? now.getMinutes();
1693
+ const reference = getReferenceTime(timeRange, showSeconds);
1694
+ const referenceMinute = timeValue?.minute ?? reference.minute;
1695
+ const items = minutes.map((minute) => ({
1696
+ value: minute,
1697
+ selected: referenceMinute === minute,
1698
+ disabled: !isSegmentAllowed(
1699
+ buildMinuteTime(timeValue, minute, showSeconds, reference),
1700
+ "minute",
1701
+ timeRange,
1702
+ showSeconds
1703
+ )
1704
+ }));
1705
+ const entry = getColumnEntry(items);
1447
1706
  return /* @__PURE__ */ jsx(
1448
1707
  MinutePrimitive,
1449
1708
  {
@@ -1451,22 +1710,24 @@ function TimePickerMinute(props) {
1451
1710
  "aria-label": labels.minute,
1452
1711
  ...minuteProps,
1453
1712
  className: cn("scrollbar-none flex max-h-[200px] flex-col gap-1 overflow-y-auto p-1", className),
1454
- children: minutes.map((minute) => /* @__PURE__ */ jsx(
1713
+ children: items.map((item) => /* @__PURE__ */ jsx(
1455
1714
  TimePickerColumnItem,
1456
1715
  {
1457
- value: minute,
1458
- selected: referenceMinute === minute,
1716
+ value: item.value,
1717
+ selected: item.selected,
1718
+ disabled: item.disabled,
1719
+ tabIndex: item === entry ? 0 : -1,
1459
1720
  format,
1460
- onClick: () => onMinuteSelect(minute)
1721
+ onClick: () => onMinuteSelect(item.value)
1461
1722
  },
1462
- minute
1723
+ item.value
1463
1724
  ))
1464
1725
  }
1465
1726
  );
1466
1727
  }
1467
1728
  function TimePickerSecond(props) {
1468
1729
  const { asChild, format = "2-digit", className, ...secondProps } = props;
1469
- const { secondStep, disabled, readOnly, labels } = useTimePickerContext(SECOND_NAME);
1730
+ const { secondStep, showSeconds, disabled, readOnly, labels, timeRange } = useTimePickerContext(SECOND_NAME);
1470
1731
  const store = useStoreContext(SECOND_NAME);
1471
1732
  const value = useStore((state) => state.value);
1472
1733
  const timeValue = parseTimeString(value);
@@ -1474,23 +1735,21 @@ function TimePickerSecond(props) {
1474
1735
  const onSecondSelect = React.useCallback(
1475
1736
  (second) => {
1476
1737
  if (disabled || readOnly) return;
1477
- const now2 = /* @__PURE__ */ new Date();
1478
- const currentTime = timeValue ?? {};
1479
- const newTime = { ...currentTime, second };
1480
- if (newTime.hour === void 0) {
1481
- newTime.hour = now2.getHours();
1482
- }
1483
- if (newTime.minute === void 0) {
1484
- newTime.minute = now2.getMinutes();
1485
- }
1486
- const newValue = formatTimeValue(newTime, true);
1738
+ const newTime = buildSecondTime(timeValue, second, getReferenceTime(timeRange, showSeconds));
1739
+ const newValue = formatTimeValue(constrainTime(newTime, timeRange, showSeconds, "second"), true);
1487
1740
  store.setState("value", newValue);
1488
1741
  },
1489
- [timeValue, disabled, readOnly, store]
1742
+ [timeValue, showSeconds, disabled, readOnly, store, timeRange]
1490
1743
  );
1491
1744
  const SecondPrimitive = asChild ? Slot : TimePickerColumn;
1492
- const now = /* @__PURE__ */ new Date();
1493
- const referenceSecond = timeValue?.second ?? now.getSeconds();
1745
+ const reference = getReferenceTime(timeRange, showSeconds);
1746
+ const referenceSecond = timeValue?.second ?? reference.second;
1747
+ const items = seconds.map((second) => ({
1748
+ value: second,
1749
+ selected: referenceSecond === second,
1750
+ disabled: !isSegmentAllowed(buildSecondTime(timeValue, second, reference), "second", timeRange, showSeconds)
1751
+ }));
1752
+ const entry = getColumnEntry(items);
1494
1753
  return /* @__PURE__ */ jsx(
1495
1754
  SecondPrimitive,
1496
1755
  {
@@ -1498,49 +1757,51 @@ function TimePickerSecond(props) {
1498
1757
  "aria-label": labels.second,
1499
1758
  ...secondProps,
1500
1759
  className: cn("scrollbar-none flex max-h-[200px] flex-col gap-1 overflow-y-auto p-1", className),
1501
- children: seconds.map((second) => /* @__PURE__ */ jsx(
1760
+ children: items.map((item) => /* @__PURE__ */ jsx(
1502
1761
  TimePickerColumnItem,
1503
1762
  {
1504
- value: second,
1505
- selected: referenceSecond === second,
1763
+ value: item.value,
1764
+ selected: item.selected,
1765
+ disabled: item.disabled,
1766
+ tabIndex: item === entry ? 0 : -1,
1506
1767
  format,
1507
- onClick: () => onSecondSelect(second)
1768
+ onClick: () => onSecondSelect(item.value)
1508
1769
  },
1509
- second
1770
+ item.value
1510
1771
  ))
1511
1772
  }
1512
1773
  );
1513
1774
  }
1514
1775
  function TimePickerPeriod(props) {
1515
1776
  const { asChild, className, ...periodProps } = props;
1516
- const { is12Hour, showSeconds, disabled, readOnly, labels } = useTimePickerContext(PERIOD_NAME);
1777
+ const { is12Hour, showSeconds, disabled, readOnly, labels, timeRange } = useTimePickerContext(PERIOD_NAME);
1517
1778
  const store = useStoreContext(PERIOD_NAME);
1518
1779
  const value = useStore((state) => state.value);
1519
1780
  const timeValue = parseTimeString(value);
1520
1781
  const onPeriodToggle = React.useCallback(
1521
1782
  (period) => {
1522
1783
  if (disabled || readOnly) return;
1523
- const now2 = /* @__PURE__ */ new Date();
1524
- const currentTime = timeValue ?? {};
1525
- const currentHour = currentTime.hour !== void 0 ? currentTime.hour : now2.getHours();
1526
- const currentDisplay = to12Hour(currentHour);
1527
- const new24Hour = to24Hour(currentDisplay.hour, period);
1528
- const newTime = { ...currentTime, hour: new24Hour };
1529
- if (newTime.minute === void 0) {
1530
- newTime.minute = now2.getMinutes();
1531
- }
1532
- if (showSeconds && newTime.second === void 0) {
1533
- newTime.second = now2.getSeconds();
1534
- }
1535
- const newValue = formatTimeValue(newTime, showSeconds);
1784
+ const newTime = buildPeriodTime(timeValue, period, showSeconds, getReferenceTime(timeRange, showSeconds));
1785
+ const newValue = formatTimeValue(constrainTime(newTime, timeRange, showSeconds, "period"), showSeconds);
1536
1786
  store.setState("value", newValue);
1537
1787
  },
1538
- [timeValue, showSeconds, disabled, readOnly, store]
1788
+ [timeValue, showSeconds, disabled, readOnly, store, timeRange]
1539
1789
  );
1540
1790
  if (!is12Hour) return null;
1541
- const now = /* @__PURE__ */ new Date();
1542
- const referenceHour = timeValue?.hour ?? now.getHours();
1791
+ const reference = getReferenceTime(timeRange, showSeconds);
1792
+ const referenceHour = timeValue?.hour ?? reference.hour;
1543
1793
  const currentPeriod = to12Hour(referenceHour).period;
1794
+ const items = PERIODS.map((period) => ({
1795
+ value: period,
1796
+ selected: currentPeriod === period,
1797
+ disabled: !isSegmentAllowed(
1798
+ buildPeriodTime(timeValue, period, showSeconds, reference),
1799
+ "period",
1800
+ timeRange,
1801
+ showSeconds
1802
+ )
1803
+ }));
1804
+ const entry = getColumnEntry(items);
1544
1805
  const PeriodPrimitive = asChild ? Slot : TimePickerColumn;
1545
1806
  return /* @__PURE__ */ jsx(
1546
1807
  PeriodPrimitive,
@@ -1549,15 +1810,17 @@ function TimePickerPeriod(props) {
1549
1810
  "aria-label": labels.period,
1550
1811
  ...periodProps,
1551
1812
  className: cn("flex flex-col gap-1 p-1", className),
1552
- children: PERIODS.map((period) => /* @__PURE__ */ jsx(
1813
+ children: items.map((item) => /* @__PURE__ */ jsx(
1553
1814
  TimePickerColumnItem,
1554
1815
  {
1555
- value: period,
1556
- selected: currentPeriod === period,
1557
- onClick: () => onPeriodToggle(period),
1558
- children: getPeriodLabel(period, labels)
1816
+ value: item.value,
1817
+ selected: item.selected,
1818
+ disabled: item.disabled,
1819
+ tabIndex: item === entry ? 0 : -1,
1820
+ onClick: () => onPeriodToggle(item.value),
1821
+ children: getPeriodLabel(item.value, labels)
1559
1822
  },
1560
- period
1823
+ item.value
1561
1824
  ))
1562
1825
  }
1563
1826
  );