@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.
- package/README.md +5 -1
- package/dist/components/composite/confirm-dialog.js +56 -35
- package/dist/components/composite/confirm-dialog.js.map +1 -1
- package/dist/components/composite/search-filter-bar-constants.d.ts +3 -0
- package/dist/components/composite/search-filter-bar-constants.js.map +1 -1
- package/dist/components/composite/table-pagination-constants.d.ts +3 -1
- package/dist/components/composite/table-pagination-constants.js.map +1 -1
- package/dist/components/composite/table-pagination.js +10 -2
- package/dist/components/composite/table-pagination.js.map +1 -1
- package/dist/components/composite/text-utils.d.ts +4 -23
- package/dist/components/composite/text-utils.js +1 -11
- package/dist/components/composite/text-utils.js.map +1 -1
- package/dist/components/composite/toast.d.ts +2 -1
- package/dist/components/composite/toast.js +3 -9
- package/dist/components/composite/toast.js.map +1 -1
- package/dist/components/ui/action-bar.js +45 -1
- package/dist/components/ui/action-bar.js.map +1 -1
- package/dist/components/ui/banner.d.ts +8 -0
- package/dist/components/ui/banner.js +149 -35
- package/dist/components/ui/banner.js.map +1 -1
- package/dist/components/ui/circular-progress.js +10 -2
- package/dist/components/ui/circular-progress.js.map +1 -1
- package/dist/components/ui/kanban.js +9 -3
- package/dist/components/ui/kanban.js.map +1 -1
- package/dist/components/ui/key-value.js +70 -76
- package/dist/components/ui/key-value.js.map +1 -1
- package/dist/components/ui/mask-input-constants.d.ts +34 -2
- package/dist/components/ui/mask-input-constants.js +87 -76
- package/dist/components/ui/mask-input-constants.js.map +1 -1
- package/dist/components/ui/mask-input.d.ts +6 -5
- package/dist/components/ui/mask-input.js +23 -21
- package/dist/components/ui/mask-input.js.map +1 -1
- package/dist/components/ui/mention.d.ts +14 -2
- package/dist/components/ui/mention.js +33 -6
- package/dist/components/ui/mention.js.map +1 -1
- package/dist/components/ui/phone-input.d.ts +18 -8
- package/dist/components/ui/phone-input.js +125 -35
- package/dist/components/ui/phone-input.js.map +1 -1
- package/dist/components/ui/rating.js +4 -2
- package/dist/components/ui/rating.js.map +1 -1
- package/dist/components/ui/scroll-spy.js +46 -31
- package/dist/components/ui/scroll-spy.js.map +1 -1
- package/dist/components/ui/slider.d.ts +1 -1
- package/dist/components/ui/slider.js +15 -1
- package/dist/components/ui/slider.js.map +1 -1
- package/dist/components/ui/sonner.js +2 -0
- package/dist/components/ui/sonner.js.map +1 -1
- package/dist/components/ui/stepper.js +26 -3
- package/dist/components/ui/stepper.js.map +1 -1
- package/dist/components/ui/time-picker.d.ts +24 -4
- package/dist/components/ui/time-picker.js +425 -162
- package/dist/components/ui/time-picker.js.map +1 -1
- package/dist/components/ui/timeline.js +45 -9
- package/dist/components/ui/timeline.js.map +1 -1
- package/dist/components/ui/toggle-group.js +12 -1
- package/dist/components/ui/toggle-group.js.map +1 -1
- package/dist/components/ui/tour.js +3 -2
- package/dist/components/ui/tour.js.map +1 -1
- package/dist/data-grid/data-grid-cell-variants.js +4 -4
- package/dist/data-grid/data-grid-cell-variants.js.map +1 -1
- package/dist/data-grid/data-grid-cell-wrapper.d.ts +1 -1
- package/dist/data-grid/data-grid-cell-wrapper.js +6 -0
- package/dist/data-grid/data-grid-cell-wrapper.js.map +1 -1
- package/dist/data-grid/data-grid-row.js +1 -1
- package/dist/data-grid/data-grid-row.js.map +1 -1
- package/dist/data-grid/data-grid-select-column.js +6 -2
- package/dist/data-grid/data-grid-select-column.js.map +1 -1
- package/dist/data-grid/data-grid-skeleton.d.ts +5 -0
- package/dist/data-grid/data-grid-skeleton.js +1 -4
- package/dist/data-grid/data-grid-skeleton.js.map +1 -1
- package/dist/data-grid/data-grid.d.ts +1 -1
- package/dist/data-grid/data-grid.js +3 -2
- package/dist/data-grid/data-grid.js.map +1 -1
- package/dist/data-grid/filters.js +3 -2
- package/dist/data-grid/filters.js.map +1 -1
- package/dist/data-grid/types.d.ts +7 -0
- package/dist/data-grid/use-data-grid.d.ts +1 -0
- package/dist/data-grid/use-data-grid.js +70 -24
- package/dist/data-grid/use-data-grid.js.map +1 -1
- package/dist/data-grid/utils.d.ts +7 -0
- package/dist/data-grid/utils.js +3 -1
- package/dist/data-grid/utils.js.map +1 -1
- package/dist/i18n/locale-provider.d.ts +18 -0
- package/dist/i18n/locale-provider.js.map +1 -1
- package/dist/i18n/namespaces/rating.js +2 -1
- package/dist/i18n/namespaces/rating.js.map +1 -1
- package/dist/i18n/namespaces/search-filter-bar.js +8 -6
- package/dist/i18n/namespaces/search-filter-bar.js.map +1 -1
- package/dist/i18n/namespaces/shell.d.ts +4 -0
- package/dist/i18n/namespaces/shell.js +1 -1
- package/dist/i18n/namespaces/shell.js.map +1 -1
- package/dist/i18n/namespaces/table-pagination.js +6 -4
- package/dist/i18n/namespaces/table-pagination.js.map +1 -1
- package/dist/i18n/namespaces/toast.d.ts +3 -2
- package/dist/i18n/namespaces/toast.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/lib/collect-item-elements.d.ts +47 -0
- package/dist/lib/collect-item-elements.js +22 -0
- package/dist/lib/collect-item-elements.js.map +1 -0
- package/dist/lib/text-search.d.ts +32 -0
- package/dist/lib/text-search.js +17 -0
- package/dist/lib/text-search.js.map +1 -0
- package/dist/lib/toast-language.d.ts +8 -0
- package/dist/lib/toast-language.js +19 -0
- package/dist/lib/toast-language.js.map +1 -0
- package/dist/shell/app-shell.d.ts +1 -0
- package/dist/shell/index.d.ts +2 -1
- package/dist/shell/shell-context.d.ts +1 -0
- package/dist/shell/utils.d.ts +2 -1
- 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
|
-
|
|
542
|
-
|
|
543
|
-
|
|
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
|
-
|
|
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.
|
|
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 =
|
|
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.
|
|
881
|
+
newTime.hour = now.hour;
|
|
666
882
|
needsUpdate = true;
|
|
667
883
|
}
|
|
668
884
|
if (newTime.minute === void 0) {
|
|
669
|
-
newTime.minute = now.
|
|
885
|
+
newTime.minute = now.minute;
|
|
670
886
|
needsUpdate = true;
|
|
671
887
|
}
|
|
672
888
|
if (showSeconds && newTime.second === void 0) {
|
|
673
|
-
newTime.second = now.
|
|
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
|
-
[
|
|
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
|
-
|
|
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 {
|
|
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
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
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 (
|
|
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"
|
|
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
|
-
|
|
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
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
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
|
|
1397
|
-
const referenceHour = timeValue?.hour ??
|
|
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:
|
|
1661
|
+
children: items.map((item) => /* @__PURE__ */ jsx(
|
|
1408
1662
|
TimePickerColumnItem,
|
|
1409
1663
|
{
|
|
1410
|
-
value:
|
|
1411
|
-
selected:
|
|
1664
|
+
value: item.value,
|
|
1665
|
+
selected: item.selected,
|
|
1666
|
+
disabled: item.disabled,
|
|
1667
|
+
tabIndex: item === entry ? 0 : -1,
|
|
1412
1668
|
format,
|
|
1413
|
-
onClick: () => onHourSelect(
|
|
1669
|
+
onClick: () => onHourSelect(item.value)
|
|
1414
1670
|
},
|
|
1415
|
-
|
|
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
|
|
1431
|
-
const
|
|
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
|
|
1446
|
-
const referenceMinute = timeValue?.minute ??
|
|
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:
|
|
1713
|
+
children: items.map((item) => /* @__PURE__ */ jsx(
|
|
1455
1714
|
TimePickerColumnItem,
|
|
1456
1715
|
{
|
|
1457
|
-
value:
|
|
1458
|
-
selected:
|
|
1716
|
+
value: item.value,
|
|
1717
|
+
selected: item.selected,
|
|
1718
|
+
disabled: item.disabled,
|
|
1719
|
+
tabIndex: item === entry ? 0 : -1,
|
|
1459
1720
|
format,
|
|
1460
|
-
onClick: () => onMinuteSelect(
|
|
1721
|
+
onClick: () => onMinuteSelect(item.value)
|
|
1461
1722
|
},
|
|
1462
|
-
|
|
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
|
|
1478
|
-
const
|
|
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
|
|
1493
|
-
const referenceSecond = timeValue?.second ??
|
|
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:
|
|
1760
|
+
children: items.map((item) => /* @__PURE__ */ jsx(
|
|
1502
1761
|
TimePickerColumnItem,
|
|
1503
1762
|
{
|
|
1504
|
-
value:
|
|
1505
|
-
selected:
|
|
1763
|
+
value: item.value,
|
|
1764
|
+
selected: item.selected,
|
|
1765
|
+
disabled: item.disabled,
|
|
1766
|
+
tabIndex: item === entry ? 0 : -1,
|
|
1506
1767
|
format,
|
|
1507
|
-
onClick: () => onSecondSelect(
|
|
1768
|
+
onClick: () => onSecondSelect(item.value)
|
|
1508
1769
|
},
|
|
1509
|
-
|
|
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
|
|
1524
|
-
const
|
|
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
|
|
1542
|
-
const referenceHour = timeValue?.hour ??
|
|
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:
|
|
1813
|
+
children: items.map((item) => /* @__PURE__ */ jsx(
|
|
1553
1814
|
TimePickerColumnItem,
|
|
1554
1815
|
{
|
|
1555
|
-
value:
|
|
1556
|
-
selected:
|
|
1557
|
-
|
|
1558
|
-
|
|
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
|
-
|
|
1823
|
+
item.value
|
|
1561
1824
|
))
|
|
1562
1825
|
}
|
|
1563
1826
|
);
|