@kalyx/react 0.2.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { createContext, forwardRef, useState, useCallback, useContext, useId, useRef, useMemo, useEffect } from 'react';
2
- import { parseInputValue, formatTimeString, parseTimeString, getTime, DateFnsAdapter, getMonthName, getWeekdayNames, getCalendarDays, formatMonthYear, formatFullDate, isDateDisabled, setTime, to12Hour, to24Hour, generateMinutes, generateHours } from '@kalyx/core';
2
+ import { parseInputValue, formatTimeString, parseTimeString, getTime, DateFnsAdapter, DEFAULT_DATEPICKER_LABELS, getMonthName, getWeekdayNames, getCalendarDays, formatMonthYear, isDateDisabled, DEFAULT_RANGEPICKER_LABELS, DEFAULT_TIMEPICKER_LABELS, setTime, to12Hour, to24Hour, generateMinutes, generateHours, formatFullDate } from '@kalyx/core';
3
3
  export { DateFnsAdapter } from '@kalyx/core';
4
4
  import { jsx, jsxs } from 'react/jsx-runtime';
5
5
  import { useFloating, autoUpdate, offset, flip, shift } from '@floating-ui/react';
@@ -30,6 +30,7 @@ function DatePickerRoot({
30
30
  displayFormat = "yyyy-MM-dd",
31
31
  locale = "en-US",
32
32
  adapter = DateFnsAdapter,
33
+ labels: labelsProp,
33
34
  children
34
35
  }) {
35
36
  const pickerId = useId();
@@ -46,8 +47,15 @@ function DatePickerRoot({
46
47
  const [focusedDate, setFocusedDate] = useState(
47
48
  currentValue ?? adapter.today()
48
49
  );
50
+ const mergedLabels = useMemo(
51
+ () => ({ ...DEFAULT_DATEPICKER_LABELS, ...labelsProp }),
52
+ [labelsProp]
53
+ );
49
54
  const isDisabled = typeof disabled === "boolean" ? disabled : false;
50
- const disabledRules = Array.isArray(disabled) ? disabled : [];
55
+ const disabledRules = useMemo(
56
+ () => Array.isArray(disabled) ? disabled : [],
57
+ [disabled]
58
+ );
51
59
  const selectDate = useCallback(
52
60
  (iso) => {
53
61
  if (isDisabled || readOnly) return;
@@ -96,7 +104,8 @@ function DatePickerRoot({
96
104
  locale,
97
105
  isDisabled,
98
106
  isReadOnly: readOnly,
99
- pickerId
107
+ pickerId,
108
+ labels: mergedLabels
100
109
  }),
101
110
  [
102
111
  currentValue,
@@ -114,28 +123,37 @@ function DatePickerRoot({
114
123
  locale,
115
124
  isDisabled,
116
125
  readOnly,
117
- pickerId
126
+ pickerId,
127
+ mergedLabels
118
128
  ]
119
129
  );
120
130
  return /* @__PURE__ */ jsx(DatePickerContext.Provider, { value: contextValue, children });
121
131
  }
122
132
  var DatePickerInput = forwardRef(
123
- function DatePickerInput2({ format: formatProp, onFocus, onBlur, onKeyDown, ...props }, ref) {
133
+ function DatePickerInput2({ format: formatProp, onClick, onBlur, onKeyDown, ...props }, ref) {
124
134
  const ctx = useDatePickerContext("DatePicker.Input");
125
135
  const displayFormat = formatProp ?? ctx.displayFormat;
126
136
  const [inputText, setInputText] = useState(null);
127
- const displayValue = inputText !== null ? inputText : ctx.value ? ctx.adapter.format(ctx.value, displayFormat) : "";
128
- const handleFocus = useCallback(
137
+ let formattedValue = "";
138
+ if (ctx.value) {
139
+ try {
140
+ formattedValue = ctx.adapter.format(ctx.value, displayFormat);
141
+ } catch {
142
+ formattedValue = ctx.value;
143
+ }
144
+ }
145
+ const displayValue = inputText !== null ? inputText : formattedValue;
146
+ const handleClick = useCallback(
129
147
  (e) => {
130
- ctx.open();
131
- onFocus?.(e);
148
+ if (!ctx.isOpen) ctx.open();
149
+ onClick?.(e);
132
150
  },
133
- [ctx, onFocus]
151
+ [ctx, onClick]
134
152
  );
135
153
  const handleBlur = useCallback(
136
154
  (e) => {
137
155
  if (inputText !== null) {
138
- const parsed = parseInputValue(inputText, displayFormat, ctx.adapter);
156
+ const parsed = parseInputValue(inputText, ctx.adapter);
139
157
  if (parsed) {
140
158
  ctx.selectDate(parsed);
141
159
  }
@@ -154,7 +172,7 @@ var DatePickerInput = forwardRef(
154
172
  setInputText(null);
155
173
  return;
156
174
  }
157
- const parsed = parseInputValue(text, displayFormat, ctx.adapter);
175
+ const parsed = parseInputValue(text, ctx.adapter);
158
176
  if (parsed) {
159
177
  ctx.selectDate(parsed);
160
178
  setInputText(null);
@@ -168,7 +186,7 @@ var DatePickerInput = forwardRef(
168
186
  ctx.close();
169
187
  } else if (e.key === "Enter") {
170
188
  if (inputText !== null) {
171
- const parsed = parseInputValue(inputText, displayFormat, ctx.adapter);
189
+ const parsed = parseInputValue(inputText, ctx.adapter);
172
190
  if (parsed) {
173
191
  ctx.selectDate(parsed);
174
192
  setInputText(null);
@@ -202,7 +220,7 @@ var DatePickerInput = forwardRef(
202
220
  disabled: ctx.isDisabled || props.disabled,
203
221
  readOnly: ctx.isReadOnly,
204
222
  onChange: handleChange,
205
- onFocus: handleFocus,
223
+ onClick: handleClick,
206
224
  onBlur: handleBlur,
207
225
  onKeyDown: handleKeyDown,
208
226
  ...props
@@ -231,7 +249,7 @@ var DatePickerTrigger = forwardRef(
231
249
  },
232
250
  type: "button",
233
251
  tabIndex: 0,
234
- "aria-label": ctx.isOpen ? "\uCE98\uB9B0\uB354 \uB2EB\uAE30" : "\uCE98\uB9B0\uB354 \uC5F4\uAE30",
252
+ "aria-label": ctx.isOpen ? ctx.labels.triggerClose : ctx.labels.triggerOpen,
235
253
  "aria-expanded": ctx.isOpen,
236
254
  "aria-controls": ctx.isOpen ? calendarId : void 0,
237
255
  disabled: ctx.isDisabled || props.disabled,
@@ -263,38 +281,39 @@ var DatePickerTrigger = forwardRef(
263
281
  );
264
282
  }
265
283
  );
266
- function DatePickerPopover({ children, ...props }) {
267
- const ctx = useDatePickerContext("DatePicker.Popover");
268
- const calendarId = `${ctx.pickerId}-calendar`;
284
+ function usePopover({ isOpen, close, referenceRef, placement = "bottom-start" }) {
269
285
  const floatingRef = useRef(null);
286
+ const previousFocusRef = useRef(null);
270
287
  const { refs, floatingStyles } = useFloating({
271
- open: ctx.isOpen,
272
- placement: "bottom-start",
288
+ open: isOpen,
289
+ placement,
273
290
  middleware: [offset(4), flip(), shift({ padding: 8 })],
274
291
  whileElementsMounted: autoUpdate
275
292
  });
276
293
  useEffect(() => {
277
- if (ctx.referenceRef.current) {
278
- refs.setReference(ctx.referenceRef.current);
294
+ if (referenceRef.current) {
295
+ refs.setReference(referenceRef.current);
279
296
  }
280
- }, [ctx.referenceRef, refs, ctx.isOpen]);
281
- const previousFocusRef = useRef(null);
297
+ }, [referenceRef, refs, isOpen]);
282
298
  useEffect(() => {
283
- if (ctx.isOpen) {
299
+ if (isOpen) {
284
300
  previousFocusRef.current = document.activeElement;
285
301
  } else if (previousFocusRef.current) {
286
- previousFocusRef.current.focus();
302
+ const el = previousFocusRef.current;
287
303
  previousFocusRef.current = null;
304
+ if (el !== referenceRef.current && typeof el.focus === "function") {
305
+ el.focus({ preventScroll: true });
306
+ }
288
307
  }
289
- }, [ctx.isOpen]);
308
+ }, [isOpen, referenceRef]);
290
309
  useEffect(() => {
291
- if (!ctx.isOpen) return;
310
+ if (!isOpen) return;
292
311
  function handleClickOutside(e) {
293
312
  const floating = floatingRef.current;
294
- const reference = ctx.referenceRef.current;
313
+ const reference = referenceRef.current;
295
314
  const target = e.target;
296
315
  if (floating && !floating.contains(target) && (!reference || !reference.contains(target))) {
297
- ctx.close();
316
+ close();
298
317
  }
299
318
  }
300
319
  const timer = setTimeout(() => {
@@ -304,28 +323,39 @@ function DatePickerPopover({ children, ...props }) {
304
323
  clearTimeout(timer);
305
324
  document.removeEventListener("mousedown", handleClickOutside);
306
325
  };
307
- }, [ctx.isOpen, ctx]);
326
+ }, [isOpen, close, referenceRef]);
308
327
  useEffect(() => {
309
- if (!ctx.isOpen) return;
328
+ if (!isOpen) return;
310
329
  function handleKeyDown(e) {
311
330
  if (e.key === "Escape") {
312
- ctx.close();
331
+ close();
313
332
  }
314
333
  }
315
334
  document.addEventListener("keydown", handleKeyDown);
316
335
  return () => document.removeEventListener("keydown", handleKeyDown);
317
- }, [ctx.isOpen, ctx]);
336
+ }, [isOpen, close]);
337
+ const setFloatingRef = (node) => {
338
+ floatingRef.current = node;
339
+ refs.setFloating(node);
340
+ };
341
+ return { floatingStyles, setFloatingRef };
342
+ }
343
+ function DatePickerPopover({ children, ...props }) {
344
+ const ctx = useDatePickerContext("DatePicker.Popover");
345
+ const calendarId = `${ctx.pickerId}-calendar`;
346
+ const { floatingStyles, setFloatingRef } = usePopover({
347
+ isOpen: ctx.isOpen,
348
+ close: ctx.close,
349
+ referenceRef: ctx.referenceRef
350
+ });
318
351
  if (!ctx.isOpen) return null;
319
352
  return /* @__PURE__ */ jsx(
320
353
  "div",
321
354
  {
322
- ref: (node) => {
323
- floatingRef.current = node;
324
- refs.setFloating(node);
325
- },
355
+ ref: setFloatingRef,
326
356
  id: calendarId,
327
357
  role: "dialog",
328
- "aria-label": "\uB0A0\uC9DC \uC120\uD0DD",
358
+ "aria-label": ctx.labels.popoverLabel,
329
359
  "aria-modal": "false",
330
360
  style: floatingStyles,
331
361
  ...props,
@@ -333,6 +363,13 @@ function DatePickerPopover({ children, ...props }) {
333
363
  }
334
364
  );
335
365
  }
366
+ function safeFormatFullDate(iso, locale) {
367
+ try {
368
+ return formatFullDate(iso, locale);
369
+ } catch {
370
+ return iso;
371
+ }
372
+ }
336
373
  var srOnly = {
337
374
  position: "absolute",
338
375
  width: "1px",
@@ -364,7 +401,7 @@ function DatePickerCalendar({ classNames, onTitleClick, ...props }) {
364
401
  const focusedButton = gridRef.current.querySelector(
365
402
  '[data-focused="true"]'
366
403
  );
367
- focusedButton?.focus();
404
+ focusedButton?.focus({ preventScroll: true });
368
405
  }, [focusedDate, ctx.isOpen]);
369
406
  const navigateMonth = useCallback(
370
407
  (direction) => {
@@ -381,7 +418,7 @@ function DatePickerCalendar({ classNames, onTitleClick, ...props }) {
381
418
  (day) => {
382
419
  if (day.isDisabled) return;
383
420
  ctx.selectDate(day.isoString);
384
- setAnnouncement(formatFullDate(day.isoString, locale));
421
+ setAnnouncement(safeFormatFullDate(day.isoString, locale));
385
422
  },
386
423
  [ctx, locale]
387
424
  );
@@ -453,7 +490,7 @@ function DatePickerCalendar({ classNames, onTitleClick, ...props }) {
453
490
  type: "button",
454
491
  className: classNames?.navButton,
455
492
  onClick: () => navigateMonth(-1),
456
- "aria-label": "\uC774\uC804 \uB2EC",
493
+ "aria-label": ctx.labels.prevMonth,
457
494
  children: "<"
458
495
  }
459
496
  ),
@@ -473,7 +510,7 @@ function DatePickerCalendar({ classNames, onTitleClick, ...props }) {
473
510
  type: "button",
474
511
  className: classNames?.navButton,
475
512
  onClick: () => navigateMonth(1),
476
- "aria-label": "\uB2E4\uC74C \uB2EC",
513
+ "aria-label": ctx.labels.nextMonth,
477
514
  children: ">"
478
515
  }
479
516
  )
@@ -526,7 +563,7 @@ function DatePickerCalendar({ classNames, onTitleClick, ...props }) {
526
563
  "data-outside-month": !day.isCurrentMonth || void 0,
527
564
  className: dayClasses,
528
565
  onClick: () => handleDayClick(day),
529
- "aria-label": formatFullDate(day.isoString, locale),
566
+ "aria-label": safeFormatFullDate(day.isoString, locale),
530
567
  children: day.dayNumber
531
568
  }
532
569
  )
@@ -582,7 +619,7 @@ function DatePickerMonthGrid({
582
619
  type: "button",
583
620
  className: classNames?.navButton,
584
621
  onClick: () => navigateYear(-1),
585
- "aria-label": "\uC774\uC804 \uB144",
622
+ "aria-label": ctx.labels.prevYear,
586
623
  children: "<"
587
624
  }
588
625
  ),
@@ -601,7 +638,7 @@ function DatePickerMonthGrid({
601
638
  type: "button",
602
639
  className: classNames?.navButton,
603
640
  onClick: () => navigateYear(1),
604
- "aria-label": "\uB2E4\uC74C \uB144",
641
+ "aria-label": ctx.labels.nextYear,
605
642
  children: ">"
606
643
  }
607
644
  )
@@ -686,7 +723,7 @@ function DatePickerYearGrid({
686
723
  type: "button",
687
724
  className: classNames?.navButton,
688
725
  onClick: () => navigateDecade(-1),
689
- "aria-label": "\uC774\uC804 12\uB144",
726
+ "aria-label": ctx.labels.prevDecade,
690
727
  children: "<"
691
728
  }
692
729
  ),
@@ -697,7 +734,7 @@ function DatePickerYearGrid({
697
734
  type: "button",
698
735
  className: classNames?.navButton,
699
736
  onClick: () => navigateDecade(1),
700
- "aria-label": "\uB2E4\uC74C 12\uB144",
737
+ "aria-label": ctx.labels.nextDecade,
701
738
  children: ">"
702
739
  }
703
740
  )
@@ -771,6 +808,7 @@ function RangePickerRoot({
771
808
  displayFormat = "yyyy-MM-dd",
772
809
  locale = "en-US",
773
810
  adapter = DateFnsAdapter,
811
+ labels: labelsProp,
774
812
  children
775
813
  }) {
776
814
  const pickerId = useId();
@@ -789,8 +827,15 @@ function RangePickerRoot({
789
827
  const [focusedDate, setFocusedDate] = useState(
790
828
  currentValue.start ?? adapter.today()
791
829
  );
830
+ const mergedLabels = useMemo(
831
+ () => ({ ...DEFAULT_RANGEPICKER_LABELS, ...labelsProp }),
832
+ [labelsProp]
833
+ );
792
834
  const isDisabled = typeof disabled === "boolean" ? disabled : false;
793
- const disabledRules = Array.isArray(disabled) ? disabled : [];
835
+ const disabledRules = useMemo(
836
+ () => Array.isArray(disabled) ? disabled : [],
837
+ [disabled]
838
+ );
794
839
  const setRange = useCallback(
795
840
  (range) => {
796
841
  if (isDisabled || readOnly) return;
@@ -872,7 +917,8 @@ function RangePickerRoot({
872
917
  locale,
873
918
  isDisabled,
874
919
  isReadOnly: readOnly,
875
- pickerId
920
+ pickerId,
921
+ labels: mergedLabels
876
922
  }),
877
923
  [
878
924
  currentValue,
@@ -893,23 +939,31 @@ function RangePickerRoot({
893
939
  locale,
894
940
  isDisabled,
895
941
  readOnly,
896
- pickerId
942
+ pickerId,
943
+ mergedLabels
897
944
  ]
898
945
  );
899
946
  return /* @__PURE__ */ jsx(RangePickerContext.Provider, { value: contextValue, children });
900
947
  }
901
948
  var RangePickerInput = forwardRef(
902
- function RangePickerInput2({ part, format: formatProp, onFocus, onKeyDown, ...props }, ref) {
949
+ function RangePickerInput2({ part, format: formatProp, onClick, onKeyDown, ...props }, ref) {
903
950
  const ctx = useRangePickerContext("RangePicker.Input");
904
951
  const displayFormat = formatProp ?? ctx.displayFormat;
905
952
  const value = ctx.value[part];
906
- const displayValue = value ? ctx.adapter.format(value, displayFormat) : "";
907
- const handleFocus = useCallback(
953
+ let displayValue = "";
954
+ if (value) {
955
+ try {
956
+ displayValue = ctx.adapter.format(value, displayFormat);
957
+ } catch {
958
+ displayValue = value;
959
+ }
960
+ }
961
+ const handleClick = useCallback(
908
962
  (e) => {
909
- ctx.open();
910
- onFocus?.(e);
963
+ if (!ctx.isOpen) ctx.open();
964
+ onClick?.(e);
911
965
  },
912
- [ctx, onFocus]
966
+ [ctx, onClick]
913
967
  );
914
968
  const handleKeyDown = useCallback(
915
969
  (e) => {
@@ -939,11 +993,11 @@ var RangePickerInput = forwardRef(
939
993
  "aria-haspopup": "dialog",
940
994
  "aria-controls": ctx.isOpen ? calendarId : void 0,
941
995
  "aria-autocomplete": "none",
942
- "aria-label": part === "start" ? "\uC2DC\uC791\uC77C" : "\uC885\uB8CC\uC77C",
996
+ "aria-label": part === "start" ? ctx.labels.startInput : ctx.labels.endInput,
943
997
  autoComplete: "off",
944
998
  value: displayValue,
945
999
  disabled: ctx.isDisabled || props.disabled,
946
- onFocus: handleFocus,
1000
+ onClick: handleClick,
947
1001
  onKeyDown: handleKeyDown,
948
1002
  "data-part": part,
949
1003
  ...props
@@ -954,66 +1008,19 @@ var RangePickerInput = forwardRef(
954
1008
  function RangePickerPopover({ children, ...props }) {
955
1009
  const ctx = useRangePickerContext("RangePicker.Popover");
956
1010
  const calendarId = `${ctx.pickerId}-calendar`;
957
- const floatingRef = useRef(null);
958
- const { refs, floatingStyles } = useFloating({
959
- open: ctx.isOpen,
960
- placement: "bottom-start",
961
- middleware: [offset(4), flip(), shift({ padding: 8 })],
962
- whileElementsMounted: autoUpdate
1011
+ const { floatingStyles, setFloatingRef } = usePopover({
1012
+ isOpen: ctx.isOpen,
1013
+ close: ctx.close,
1014
+ referenceRef: ctx.referenceRef
963
1015
  });
964
- useEffect(() => {
965
- if (ctx.referenceRef.current) {
966
- refs.setReference(ctx.referenceRef.current);
967
- }
968
- }, [ctx.referenceRef, refs, ctx.isOpen]);
969
- const previousFocusRef = useRef(null);
970
- useEffect(() => {
971
- if (ctx.isOpen) {
972
- previousFocusRef.current = document.activeElement;
973
- } else if (previousFocusRef.current) {
974
- previousFocusRef.current.focus();
975
- previousFocusRef.current = null;
976
- }
977
- }, [ctx.isOpen]);
978
- useEffect(() => {
979
- if (!ctx.isOpen) return;
980
- function handleClickOutside(e) {
981
- const floating = floatingRef.current;
982
- const reference = ctx.referenceRef.current;
983
- const target = e.target;
984
- if (floating && !floating.contains(target) && (!reference || !reference.contains(target))) {
985
- ctx.close();
986
- }
987
- }
988
- const timer = setTimeout(() => {
989
- document.addEventListener("mousedown", handleClickOutside);
990
- }, 0);
991
- return () => {
992
- clearTimeout(timer);
993
- document.removeEventListener("mousedown", handleClickOutside);
994
- };
995
- }, [ctx.isOpen, ctx]);
996
- useEffect(() => {
997
- if (!ctx.isOpen) return;
998
- function handleKeyDown(e) {
999
- if (e.key === "Escape") {
1000
- ctx.close();
1001
- }
1002
- }
1003
- document.addEventListener("keydown", handleKeyDown);
1004
- return () => document.removeEventListener("keydown", handleKeyDown);
1005
- }, [ctx.isOpen, ctx]);
1006
1016
  if (!ctx.isOpen) return null;
1007
1017
  return /* @__PURE__ */ jsx(
1008
1018
  "div",
1009
1019
  {
1010
- ref: (node) => {
1011
- floatingRef.current = node;
1012
- refs.setFloating(node);
1013
- },
1020
+ ref: setFloatingRef,
1014
1021
  id: calendarId,
1015
1022
  role: "dialog",
1016
- "aria-label": "\uB0A0\uC9DC \uBC94\uC704 \uC120\uD0DD",
1023
+ "aria-label": ctx.labels.popoverLabel,
1017
1024
  "aria-modal": "false",
1018
1025
  style: floatingStyles,
1019
1026
  ...props,
@@ -1021,6 +1028,13 @@ function RangePickerPopover({ children, ...props }) {
1021
1028
  }
1022
1029
  );
1023
1030
  }
1031
+ function safeFormatFullDate2(iso, locale) {
1032
+ try {
1033
+ return formatFullDate(iso, locale);
1034
+ } catch {
1035
+ return iso;
1036
+ }
1037
+ }
1024
1038
  var srOnly2 = {
1025
1039
  position: "absolute",
1026
1040
  width: "1px",
@@ -1063,7 +1077,7 @@ function RangePickerCalendar({ classNames, ...props }) {
1063
1077
  const focusedButton = gridRef.current.querySelector(
1064
1078
  '[data-focused="true"]'
1065
1079
  );
1066
- focusedButton?.focus();
1080
+ focusedButton?.focus({ preventScroll: true });
1067
1081
  }, [focusedDate, ctx.isOpen]);
1068
1082
  const navigateMonth = useCallback(
1069
1083
  (direction) => {
@@ -1080,7 +1094,7 @@ function RangePickerCalendar({ classNames, ...props }) {
1080
1094
  (day) => {
1081
1095
  if (day.isDisabled) return;
1082
1096
  ctx.selectDate(day.isoString);
1083
- setAnnouncement(formatFullDate(day.isoString, locale));
1097
+ setAnnouncement(safeFormatFullDate2(day.isoString, locale));
1084
1098
  },
1085
1099
  [ctx, locale]
1086
1100
  );
@@ -1157,7 +1171,7 @@ function RangePickerCalendar({ classNames, ...props }) {
1157
1171
  type: "button",
1158
1172
  className: classNames?.navButton,
1159
1173
  onClick: () => navigateMonth(-1),
1160
- "aria-label": "\uC774\uC804 \uB2EC",
1174
+ "aria-label": ctx.labels.prevMonth,
1161
1175
  children: "<"
1162
1176
  }
1163
1177
  ),
@@ -1168,7 +1182,7 @@ function RangePickerCalendar({ classNames, ...props }) {
1168
1182
  type: "button",
1169
1183
  className: classNames?.navButton,
1170
1184
  onClick: () => navigateMonth(1),
1171
- "aria-label": "\uB2E4\uC74C \uB2EC",
1185
+ "aria-label": ctx.labels.nextMonth,
1172
1186
  children: ">"
1173
1187
  }
1174
1188
  )
@@ -1228,7 +1242,7 @@ function RangePickerCalendar({ classNames, ...props }) {
1228
1242
  className: dayClasses,
1229
1243
  onClick: () => handleDayClick(day),
1230
1244
  onMouseEnter: () => handleDayMouseEnter(day),
1231
- "aria-label": formatFullDate(day.isoString, locale),
1245
+ "aria-label": safeFormatFullDate2(day.isoString, locale),
1232
1246
  children: day.dayNumber
1233
1247
  }
1234
1248
  )
@@ -1243,7 +1257,8 @@ function RangePickerCalendar({ classNames, ...props }) {
1243
1257
  ] });
1244
1258
  }
1245
1259
  function RangePickerPresets({ classNames, children, ...props }) {
1246
- return /* @__PURE__ */ jsx("div", { role: "group", "aria-label": "\uB0A0\uC9DC \uBC94\uC704 \uD504\uB9AC\uC14B", className: classNames?.root, ...props, children });
1260
+ const ctx = useRangePickerContext("RangePicker.Presets");
1261
+ return /* @__PURE__ */ jsx("div", { role: "group", "aria-label": ctx.labels.presetsGroup, className: classNames?.root, ...props, children });
1247
1262
  }
1248
1263
  function resolvePreset(key, today, adapter) {
1249
1264
  switch (key) {
@@ -1282,8 +1297,9 @@ function resolvePreset(key, today, adapter) {
1282
1297
  };
1283
1298
  }
1284
1299
  case "thisYear": {
1300
+ const currentMonth = new Date(today).getUTCMonth();
1285
1301
  const yearStart = adapter.startOfMonth(
1286
- adapter.addMonths(today, -new Date(today).getUTCMonth())
1302
+ adapter.addMonths(today, -currentMonth)
1287
1303
  );
1288
1304
  return { start: yearStart, end: today };
1289
1305
  }
@@ -1365,10 +1381,7 @@ function useTimePickerContext(componentName) {
1365
1381
  return context;
1366
1382
  }
1367
1383
  function getDefaultIso() {
1368
- const now = /* @__PURE__ */ new Date();
1369
- return new Date(
1370
- Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate())
1371
- ).toISOString();
1384
+ return DateFnsAdapter.today();
1372
1385
  }
1373
1386
  function TimePickerRoot({
1374
1387
  value: controlledValue,
@@ -1379,9 +1392,14 @@ function TimePickerRoot({
1379
1392
  withSeconds = false,
1380
1393
  disabled = false,
1381
1394
  readOnly = false,
1395
+ labels: labelsProp,
1382
1396
  children
1383
1397
  }) {
1384
1398
  const pickerId = useId();
1399
+ const mergedLabels = useMemo(
1400
+ () => ({ ...DEFAULT_TIMEPICKER_LABELS, ...labelsProp }),
1401
+ [labelsProp]
1402
+ );
1385
1403
  const isControlled = useRef(controlledValue !== void 0).current;
1386
1404
  const [uncontrolledValue, setUncontrolledValue] = useState(
1387
1405
  defaultValue ?? null
@@ -1410,9 +1428,10 @@ function TimePickerRoot({
1410
1428
  isDisabled: disabled,
1411
1429
  isReadOnly: readOnly,
1412
1430
  currentTime,
1413
- pickerId
1431
+ pickerId,
1432
+ labels: mergedLabels
1414
1433
  }),
1415
- [currentValue, setTime$1, format, step, withSeconds, disabled, readOnly, currentTime, pickerId]
1434
+ [currentValue, setTime$1, format, step, withSeconds, disabled, readOnly, currentTime, pickerId, mergedLabels]
1416
1435
  );
1417
1436
  return /* @__PURE__ */ jsx(TimePickerContext.Provider, { value: contextValue, children });
1418
1437
  }
@@ -1458,7 +1477,7 @@ var TimePickerInput = forwardRef(
1458
1477
  type: "text",
1459
1478
  inputMode: "numeric",
1460
1479
  autoComplete: "off",
1461
- "aria-label": "\uC2DC\uAC04 \uC785\uB825",
1480
+ "aria-label": ctx.labels.timeInput,
1462
1481
  placeholder: ctx.withSeconds ? "HH:MM:SS" : "HH:MM",
1463
1482
  value: displayValue,
1464
1483
  disabled: ctx.isDisabled || props.disabled,
@@ -1471,46 +1490,42 @@ var TimePickerInput = forwardRef(
1471
1490
  );
1472
1491
  }
1473
1492
  );
1474
- function TimePickerHourList({ classNames, ...props }) {
1475
- const ctx = useTimePickerContext("TimePicker.HourList");
1476
- const { format, currentTime, isDisabled, isReadOnly } = ctx;
1493
+ function useListboxNavigation({
1494
+ items,
1495
+ onSelect,
1496
+ disabled = false
1497
+ }) {
1477
1498
  const listRef = useRef(null);
1478
- const hours = generateHours(format);
1479
- const selectedHourDisplay = format === "12h" ? to12Hour(currentTime.hours).hours12 : currentTime.hours;
1480
- const currentPeriod = format === "12h" ? to12Hour(currentTime.hours).period : null;
1481
- const handleSelect = useCallback(
1482
- (hourDisplay) => {
1483
- if (isDisabled || isReadOnly) return;
1484
- const hours24 = format === "12h" && currentPeriod ? to24Hour(hourDisplay, currentPeriod) : hourDisplay;
1485
- ctx.setTime({ hours: hours24 });
1486
- },
1487
- [format, currentPeriod, ctx, isDisabled, isReadOnly]
1488
- );
1499
+ const rafIdRef = useRef(0);
1500
+ useEffect(() => {
1501
+ return () => cancelAnimationFrame(rafIdRef.current);
1502
+ }, []);
1489
1503
  const handleKeyDown = useCallback(
1490
- (e, hour) => {
1491
- if (isDisabled || isReadOnly) return;
1492
- const currentIndex = hours.indexOf(hour);
1504
+ (e, item) => {
1505
+ if (disabled) return;
1506
+ const currentIndex = items.indexOf(item);
1493
1507
  let newIndex = -1;
1494
1508
  if (e.key === "ArrowDown") {
1495
- newIndex = Math.min(currentIndex + 1, hours.length - 1);
1509
+ newIndex = Math.min(currentIndex + 1, items.length - 1);
1496
1510
  } else if (e.key === "ArrowUp") {
1497
1511
  newIndex = Math.max(currentIndex - 1, 0);
1498
1512
  } else if (e.key === "Home") {
1499
1513
  newIndex = 0;
1500
1514
  } else if (e.key === "End") {
1501
- newIndex = hours.length - 1;
1515
+ newIndex = items.length - 1;
1502
1516
  } else if (e.key === "Enter" || e.key === " ") {
1503
1517
  e.preventDefault();
1504
- handleSelect(hour);
1518
+ onSelect(item);
1505
1519
  return;
1506
1520
  } else {
1507
1521
  return;
1508
1522
  }
1509
1523
  e.preventDefault();
1510
- const targetHour = hours[newIndex];
1511
- if (targetHour !== void 0) {
1512
- handleSelect(targetHour);
1513
- requestAnimationFrame(() => {
1524
+ const target = items[newIndex];
1525
+ if (target !== void 0) {
1526
+ onSelect(target);
1527
+ cancelAnimationFrame(rafIdRef.current);
1528
+ rafIdRef.current = requestAnimationFrame(() => {
1514
1529
  const next = listRef.current?.querySelector(
1515
1530
  '[data-selected="true"]'
1516
1531
  );
@@ -1518,14 +1533,35 @@ function TimePickerHourList({ classNames, ...props }) {
1518
1533
  });
1519
1534
  }
1520
1535
  },
1521
- [hours, handleSelect, isDisabled, isReadOnly]
1536
+ [items, onSelect, disabled]
1522
1537
  );
1538
+ return { listRef, handleKeyDown };
1539
+ }
1540
+ function TimePickerHourList({ classNames, ...props }) {
1541
+ const ctx = useTimePickerContext("TimePicker.HourList");
1542
+ const { format, currentTime, isDisabled, isReadOnly } = ctx;
1543
+ const hours = generateHours(format);
1544
+ const selectedHourDisplay = format === "12h" ? to12Hour(currentTime.hours).hours12 : currentTime.hours;
1545
+ const currentPeriod = format === "12h" ? to12Hour(currentTime.hours).period : null;
1546
+ const handleSelect = useCallback(
1547
+ (hourDisplay) => {
1548
+ if (isDisabled || isReadOnly) return;
1549
+ const hours24 = format === "12h" && currentPeriod ? to24Hour(hourDisplay, currentPeriod) : hourDisplay;
1550
+ ctx.setTime({ hours: hours24 });
1551
+ },
1552
+ [format, currentPeriod, ctx, isDisabled, isReadOnly]
1553
+ );
1554
+ const { listRef, handleKeyDown } = useListboxNavigation({
1555
+ items: hours,
1556
+ onSelect: handleSelect,
1557
+ disabled: isDisabled || isReadOnly
1558
+ });
1523
1559
  return /* @__PURE__ */ jsx(
1524
1560
  "ul",
1525
1561
  {
1526
1562
  ref: listRef,
1527
1563
  role: "listbox",
1528
- "aria-label": "\uC2DC",
1564
+ "aria-label": ctx.labels.hourList,
1529
1565
  "aria-disabled": isDisabled || void 0,
1530
1566
  className: classNames?.root,
1531
1567
  ...props,
@@ -1538,7 +1574,7 @@ function TimePickerHourList({ classNames, ...props }) {
1538
1574
  role: "option",
1539
1575
  "aria-selected": isSelected,
1540
1576
  "aria-disabled": isDisabled || void 0,
1541
- "aria-label": `${hour}\uC2DC`,
1577
+ "aria-label": ctx.labels.hourOption(hour),
1542
1578
  "data-selected": isSelected || void 0,
1543
1579
  tabIndex: isSelected ? 0 : -1,
1544
1580
  className: optionClass,
@@ -1555,7 +1591,6 @@ function TimePickerHourList({ classNames, ...props }) {
1555
1591
  function TimePickerMinuteList({ classNames, ...props }) {
1556
1592
  const ctx = useTimePickerContext("TimePicker.MinuteList");
1557
1593
  const { step, currentTime, isDisabled, isReadOnly } = ctx;
1558
- const listRef = useRef(null);
1559
1594
  const minutes = generateMinutes(step);
1560
1595
  const handleSelect = useCallback(
1561
1596
  (minute) => {
@@ -1564,46 +1599,17 @@ function TimePickerMinuteList({ classNames, ...props }) {
1564
1599
  },
1565
1600
  [ctx, isDisabled, isReadOnly]
1566
1601
  );
1567
- const handleKeyDown = useCallback(
1568
- (e, minute) => {
1569
- if (isDisabled || isReadOnly) return;
1570
- const currentIndex = minutes.indexOf(minute);
1571
- let newIndex = -1;
1572
- if (e.key === "ArrowDown") {
1573
- newIndex = Math.min(currentIndex + 1, minutes.length - 1);
1574
- } else if (e.key === "ArrowUp") {
1575
- newIndex = Math.max(currentIndex - 1, 0);
1576
- } else if (e.key === "Home") {
1577
- newIndex = 0;
1578
- } else if (e.key === "End") {
1579
- newIndex = minutes.length - 1;
1580
- } else if (e.key === "Enter" || e.key === " ") {
1581
- e.preventDefault();
1582
- handleSelect(minute);
1583
- return;
1584
- } else {
1585
- return;
1586
- }
1587
- e.preventDefault();
1588
- const target = minutes[newIndex];
1589
- if (target !== void 0) {
1590
- handleSelect(target);
1591
- requestAnimationFrame(() => {
1592
- const next = listRef.current?.querySelector(
1593
- '[data-selected="true"]'
1594
- );
1595
- next?.focus();
1596
- });
1597
- }
1598
- },
1599
- [minutes, handleSelect, isDisabled, isReadOnly]
1600
- );
1602
+ const { listRef, handleKeyDown } = useListboxNavigation({
1603
+ items: minutes,
1604
+ onSelect: handleSelect,
1605
+ disabled: isDisabled || isReadOnly
1606
+ });
1601
1607
  return /* @__PURE__ */ jsx(
1602
1608
  "ul",
1603
1609
  {
1604
1610
  ref: listRef,
1605
1611
  role: "listbox",
1606
- "aria-label": "\uBD84",
1612
+ "aria-label": ctx.labels.minuteList,
1607
1613
  "aria-disabled": isDisabled || void 0,
1608
1614
  className: classNames?.root,
1609
1615
  ...props,
@@ -1616,7 +1622,7 @@ function TimePickerMinuteList({ classNames, ...props }) {
1616
1622
  role: "option",
1617
1623
  "aria-selected": isSelected,
1618
1624
  "aria-disabled": isDisabled || void 0,
1619
- "aria-label": `${minute}\uBD84`,
1625
+ "aria-label": ctx.labels.minuteOption(minute),
1620
1626
  "data-selected": isSelected || void 0,
1621
1627
  tabIndex: isSelected ? 0 : -1,
1622
1628
  className: optionClass,
@@ -1659,7 +1665,7 @@ function TimePickerAmPmToggle({ classNames, ...props }) {
1659
1665
  }
1660
1666
  );
1661
1667
  };
1662
- return /* @__PURE__ */ jsxs("div", { role: "radiogroup", "aria-label": "\uC624\uC804/\uC624\uD6C4", className: classNames?.root, ...props, children: [
1668
+ return /* @__PURE__ */ jsxs("div", { role: "radiogroup", "aria-label": ctx.labels.amPmToggle, className: classNames?.root, ...props, children: [
1663
1669
  renderButton("AM"),
1664
1670
  renderButton("PM")
1665
1671
  ] });
@@ -1673,10 +1679,7 @@ var TimePicker = Object.assign(TimePickerRoot, {
1673
1679
  AmPmToggle: TimePickerAmPmToggle
1674
1680
  });
1675
1681
  function getDefaultIso2() {
1676
- const now = /* @__PURE__ */ new Date();
1677
- return new Date(
1678
- Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate())
1679
- ).toISOString();
1682
+ return DateFnsAdapter.today();
1680
1683
  }
1681
1684
  function DateTimePickerRoot({
1682
1685
  value: controlledValue,
@@ -1690,9 +1693,18 @@ function DateTimePickerRoot({
1690
1693
  displayFormat = "yyyy-MM-dd HH:mm",
1691
1694
  locale = "en-US",
1692
1695
  adapter = DateFnsAdapter,
1696
+ labels: labelsProp,
1693
1697
  children
1694
1698
  }) {
1695
1699
  const pickerId = useId();
1700
+ const mergedDateLabels = useMemo(
1701
+ () => ({ ...DEFAULT_DATEPICKER_LABELS, ...labelsProp }),
1702
+ [labelsProp]
1703
+ );
1704
+ const mergedTimeLabels = useMemo(
1705
+ () => ({ ...DEFAULT_TIMEPICKER_LABELS, ...labelsProp }),
1706
+ [labelsProp]
1707
+ );
1696
1708
  const isControlled = useRef(controlledValue !== void 0).current;
1697
1709
  const referenceRef = useRef(null);
1698
1710
  const [uncontrolledValue, setUncontrolledValue] = useState(
@@ -1707,7 +1719,10 @@ function DateTimePickerRoot({
1707
1719
  currentValue ?? adapter.today()
1708
1720
  );
1709
1721
  const isDisabled = typeof disabled === "boolean" ? disabled : false;
1710
- const disabledRules = Array.isArray(disabled) ? disabled : [];
1722
+ const disabledRules = useMemo(
1723
+ () => Array.isArray(disabled) ? disabled : [],
1724
+ [disabled]
1725
+ );
1711
1726
  const baseIso = currentValue ?? getDefaultIso2();
1712
1727
  const currentTime = useMemo(() => getTime(baseIso), [baseIso]);
1713
1728
  const updateValue = useCallback(
@@ -1774,7 +1789,8 @@ function DateTimePickerRoot({
1774
1789
  locale,
1775
1790
  isDisabled,
1776
1791
  isReadOnly: readOnly,
1777
- pickerId
1792
+ pickerId,
1793
+ labels: mergedDateLabels
1778
1794
  }),
1779
1795
  [
1780
1796
  currentValue,
@@ -1792,7 +1808,8 @@ function DateTimePickerRoot({
1792
1808
  locale,
1793
1809
  isDisabled,
1794
1810
  readOnly,
1795
- pickerId
1811
+ pickerId,
1812
+ mergedDateLabels
1796
1813
  ]
1797
1814
  );
1798
1815
  const timeContext = useMemo(
@@ -1805,22 +1822,30 @@ function DateTimePickerRoot({
1805
1822
  isDisabled,
1806
1823
  isReadOnly: readOnly,
1807
1824
  currentTime,
1808
- pickerId
1825
+ pickerId,
1826
+ labels: mergedTimeLabels
1809
1827
  }),
1810
- [currentValue, setTime$1, format, step, isDisabled, readOnly, currentTime, pickerId]
1828
+ [currentValue, setTime$1, format, step, isDisabled, readOnly, currentTime, pickerId, mergedTimeLabels]
1811
1829
  );
1812
1830
  return /* @__PURE__ */ jsx(DatePickerContext.Provider, { value: dateContext, children: /* @__PURE__ */ jsx(TimePickerContext.Provider, { value: timeContext, children }) });
1813
1831
  }
1814
1832
  var DateTimePickerInput = forwardRef(
1815
- function DateTimePickerInput2({ onFocus, onKeyDown, ...props }, ref) {
1833
+ function DateTimePickerInput2({ onClick, onKeyDown, ...props }, ref) {
1816
1834
  const ctx = useDatePickerContext("DateTimePicker.Input");
1817
- const displayValue = ctx.value ? `${ctx.adapter.format(ctx.value, "yyyy-MM-dd")} ${formatTimeString(getTime(ctx.value))}` : "";
1818
- const handleFocus = useCallback(
1835
+ let displayValue = "";
1836
+ if (ctx.value) {
1837
+ try {
1838
+ displayValue = `${ctx.adapter.format(ctx.value, "yyyy-MM-dd")} ${formatTimeString(getTime(ctx.value))}`;
1839
+ } catch {
1840
+ displayValue = ctx.value;
1841
+ }
1842
+ }
1843
+ const handleClick = useCallback(
1819
1844
  (e) => {
1820
- ctx.open();
1821
- onFocus?.(e);
1845
+ if (!ctx.isOpen) ctx.open();
1846
+ onClick?.(e);
1822
1847
  },
1823
- [ctx, onFocus]
1848
+ [ctx, onClick]
1824
1849
  );
1825
1850
  const handleKeyDown = useCallback(
1826
1851
  (e) => {
@@ -1846,7 +1871,7 @@ var DateTimePickerInput = forwardRef(
1846
1871
  type: "text",
1847
1872
  role: "combobox",
1848
1873
  readOnly: true,
1849
- "aria-label": "\uB0A0\uC9DC \uBC0F \uC2DC\uAC04",
1874
+ "aria-label": ctx.labels.dateTimeInput ?? "Date and time",
1850
1875
  "aria-expanded": ctx.isOpen,
1851
1876
  "aria-haspopup": "dialog",
1852
1877
  "aria-controls": ctx.isOpen ? calendarId : void 0,
@@ -1854,7 +1879,7 @@ var DateTimePickerInput = forwardRef(
1854
1879
  autoComplete: "off",
1855
1880
  value: displayValue,
1856
1881
  disabled: ctx.isDisabled || props.disabled,
1857
- onFocus: handleFocus,
1882
+ onClick: handleClick,
1858
1883
  onKeyDown: handleKeyDown,
1859
1884
  ...props
1860
1885
  }
@@ -2061,10 +2086,7 @@ function useRangePicker(options = {}) {
2061
2086
  };
2062
2087
  }
2063
2088
  function getDefaultIso3() {
2064
- const now = /* @__PURE__ */ new Date();
2065
- return new Date(
2066
- Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate())
2067
- ).toISOString();
2089
+ return DateFnsAdapter.today();
2068
2090
  }
2069
2091
  function useTimePicker(options = {}) {
2070
2092
  const {