@xsolla/xui-calendar 0.226.0 → 0.228.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/web/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  // src/Calendar.tsx
2
- import { forwardRef, useCallback as useCallback3, useEffect, useState as useState2 } from "react";
2
+ import { forwardRef, useCallback as useCallback3, useEffect as useEffect2, useState as useState2 } from "react";
3
3
 
4
4
  // ../../foundation/primitives-web/src/Box.tsx
5
5
  import React2 from "react";
@@ -336,9 +336,12 @@ var Text = ({
336
336
  );
337
337
  };
338
338
 
339
+ // ../../foundation/primitives-web/src/index.tsx
340
+ var isWeb = true;
341
+
339
342
  // src/Calendar.tsx
340
343
  import { useResolvedTheme as useResolvedTheme3 } from "@xsolla/xui-core";
341
- import { addMonths, subDays, subMonths, startOfDay as startOfDay2 } from "date-fns";
344
+ import { addMonths as addMonths2, subDays, subMonths, startOfDay as startOfDay2 } from "date-fns";
342
345
 
343
346
  // src/CalendarHeader.tsx
344
347
  import { useCallback, useMemo } from "react";
@@ -490,10 +493,17 @@ var CalendarHeader = ({
490
493
  };
491
494
 
492
495
  // src/CalendarGrid.tsx
493
- import { useMemo as useMemo2, useCallback as useCallback2, useState } from "react";
496
+ import {
497
+ useMemo as useMemo2,
498
+ useCallback as useCallback2,
499
+ useEffect,
500
+ useRef,
501
+ useState
502
+ } from "react";
494
503
  import { useResolvedTheme } from "@xsolla/xui-core";
495
504
  import {
496
505
  addDays,
506
+ addMonths,
497
507
  eachDayOfInterval,
498
508
  endOfDay,
499
509
  endOfMonth,
@@ -513,6 +523,7 @@ import * as locales2 from "date-fns/locale";
513
523
  import { jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
514
524
  var DAY_CELL_WIDTH = 40;
515
525
  var GRID_WIDTH = DAY_CELL_WIDTH * 7;
526
+ var dayKeyOf = (date) => format2(date, "yyyy-MM-dd");
516
527
  var CalendarGrid = ({
517
528
  currentMonth,
518
529
  locale = "enUS",
@@ -525,6 +536,7 @@ var CalendarGrid = ({
525
536
  selectedDate,
526
537
  selectingRange,
527
538
  onDayPress,
539
+ onMonthChange,
528
540
  testID,
529
541
  themeMode,
530
542
  themeProductContext
@@ -573,33 +585,122 @@ var CalendarGrid = ({
573
585
  },
574
586
  [selectsRange, startDate, endDate, selectingRange]
575
587
  );
576
- return /* @__PURE__ */ jsxs2(Box, { testID, children: [
588
+ const [focusedDate, setFocusedDate] = useState(null);
589
+ const dayRefs = useRef(/* @__PURE__ */ new Map());
590
+ const shouldMoveFocus = useRef(false);
591
+ const tabStopDate = useMemo2(() => {
592
+ if (focusedDate && isSameMonth(focusedDate, currentMonth)) {
593
+ return focusedDate;
594
+ }
595
+ const preferred = selectsRange ? startDate : selectedDate;
596
+ if (preferred && isSameMonth(preferred, currentMonth)) return preferred;
597
+ const today = /* @__PURE__ */ new Date();
598
+ if (isSameMonth(today, currentMonth)) return today;
599
+ return monthStart;
600
+ }, [
601
+ focusedDate,
602
+ currentMonth,
603
+ selectsRange,
604
+ startDate,
605
+ selectedDate,
606
+ monthStart
607
+ ]);
608
+ const tabStopKey = dayKeyOf(tabStopDate);
609
+ useEffect(() => {
610
+ if (!shouldMoveFocus.current) return;
611
+ shouldMoveFocus.current = false;
612
+ dayRefs.current.get(tabStopKey)?.focus();
613
+ }, [tabStopKey, currentMonth]);
614
+ const moveFocus = useCallback2(
615
+ (target) => {
616
+ if (!isSameMonth(target, currentMonth)) {
617
+ if (!onMonthChange) return;
618
+ onMonthChange(startOfMonth(target));
619
+ }
620
+ shouldMoveFocus.current = true;
621
+ setFocusedDate(target);
622
+ },
623
+ [currentMonth, onMonthChange]
624
+ );
625
+ const handleDayKeyDown = useCallback2(
626
+ (event, day) => {
627
+ let target = null;
628
+ switch (event.key) {
629
+ case "ArrowLeft":
630
+ target = addDays(day, -1);
631
+ break;
632
+ case "ArrowRight":
633
+ target = addDays(day, 1);
634
+ break;
635
+ case "ArrowUp":
636
+ target = addDays(day, -7);
637
+ break;
638
+ case "ArrowDown":
639
+ target = addDays(day, 7);
640
+ break;
641
+ case "Home":
642
+ target = startOfWeek(day, { weekStartsOn });
643
+ break;
644
+ case "End":
645
+ target = endOfWeek(day, { weekStartsOn });
646
+ break;
647
+ case "PageUp":
648
+ target = addMonths(day, -1);
649
+ break;
650
+ case "PageDown":
651
+ target = addMonths(day, 1);
652
+ break;
653
+ case "Enter":
654
+ case " ":
655
+ event.preventDefault();
656
+ if (!isDateDisabled(day)) onDayPress?.(day);
657
+ return;
658
+ default:
659
+ return;
660
+ }
661
+ event.preventDefault();
662
+ moveFocus(target);
663
+ },
664
+ [isDateDisabled, moveFocus, onDayPress, weekStartsOn]
665
+ );
666
+ return /* @__PURE__ */ jsxs2(Box, { testID, role: "grid", children: [
577
667
  /* @__PURE__ */ jsx4(
578
668
  Box,
579
669
  {
670
+ role: "row",
580
671
  flexDirection: "row",
581
672
  justifyContent: "flex-start",
582
673
  width: GRID_WIDTH,
583
674
  marginBottom: 8,
584
- children: weekDays.map((day, i) => /* @__PURE__ */ jsx4(Box, { width: DAY_CELL_WIDTH, alignItems: "center", children: /* @__PURE__ */ jsx4(
585
- Text,
675
+ children: weekDays.map((day, i) => /* @__PURE__ */ jsx4(
676
+ Box,
586
677
  {
587
- fontSize: 12,
588
- fontWeight: "600",
589
- color: theme.colors.content.tertiary,
590
- children: day.toUpperCase()
591
- }
592
- ) }, i))
678
+ role: "columnheader",
679
+ width: DAY_CELL_WIDTH,
680
+ alignItems: "center",
681
+ children: /* @__PURE__ */ jsx4(
682
+ Text,
683
+ {
684
+ fontSize: 12,
685
+ fontWeight: "600",
686
+ color: theme.colors.content.tertiary,
687
+ children: day.toUpperCase()
688
+ }
689
+ )
690
+ },
691
+ i
692
+ ))
593
693
  }
594
694
  ),
595
695
  /* @__PURE__ */ jsx4(Box, { gap: 2, children: weeks.map((week, i) => /* @__PURE__ */ jsx4(
596
696
  Box,
597
697
  {
698
+ role: "row",
598
699
  flexDirection: "row",
599
700
  justifyContent: "flex-start",
600
701
  width: GRID_WIDTH,
601
702
  children: week.map((day, j) => {
602
- const dayKey = format2(day, "yyyy-MM-dd");
703
+ const dayKey = dayKeyOf(day);
603
704
  const isOutsideMonth = !isSameMonth(day, currentMonth);
604
705
  const isSelected = !selectsRange && selectedDate && isSameDay(day, selectedDate) || selectsRange && startDate && endDate && isSameDay(day, startDate) && isSameDay(day, endDate);
605
706
  const isRangeStart = selectsRange && startDate && isSameDay(day, startDate);
@@ -609,6 +710,7 @@ var CalendarGrid = ({
609
710
  const disabled = isDateDisabled(day);
610
711
  const isHoverable = !disabled && !isOutsideMonth && !isSelected && !isRangeStart && !isRangeEnd;
611
712
  const isHovered = isHoverable && hoveredKey === dayKey;
713
+ const isTabStop = !isOutsideMonth && dayKey === tabStopKey;
612
714
  let bgColor = "transparent";
613
715
  let textColor = theme.colors.content.primary;
614
716
  if (isSelected || isRangeStart || isRangeEnd || inRange) {
@@ -620,9 +722,23 @@ var CalendarGrid = ({
620
722
  } else if (isOutsideMonth || disabled) {
621
723
  textColor = theme.colors.control.text.disable;
622
724
  }
725
+ const keyboardProps = isWeb && !isOutsideMonth ? {
726
+ ref: (node) => {
727
+ if (node) dayRefs.current.set(dayKey, node);
728
+ else dayRefs.current.delete(dayKey);
729
+ },
730
+ tabIndex: isTabStop ? 0 : -1,
731
+ onKeyDown: (event) => handleDayKeyDown(event, day)
732
+ } : {};
623
733
  return /* @__PURE__ */ jsx4(
624
734
  Box,
625
735
  {
736
+ ...keyboardProps,
737
+ role: "gridcell",
738
+ "aria-label": isOutsideMonth ? void 0 : format2(day, "EEEE, d MMMM yyyy", { locale: localeObj }),
739
+ "aria-selected": isOutsideMonth ? void 0 : Boolean(isSelected || isRangeStart || isRangeEnd),
740
+ "aria-disabled": disabled || isOutsideMonth || void 0,
741
+ "aria-current": today ? "date" : void 0,
626
742
  width: DAY_CELL_WIDTH,
627
743
  height: 32,
628
744
  alignItems: "center",
@@ -633,7 +749,11 @@ var CalendarGrid = ({
633
749
  borderBottomRightRadius: isSelected || isRangeEnd ? 4 : 0,
634
750
  backgroundColor: bgColor,
635
751
  cursor: disabled || isOutsideMonth ? "default" : "pointer",
636
- onPress: () => !disabled && !isOutsideMonth && onDayPress?.(day),
752
+ onPress: () => {
753
+ if (disabled || isOutsideMonth) return;
754
+ setFocusedDate(day);
755
+ onDayPress?.(day);
756
+ },
637
757
  onMouseEnter: () => isHoverable && setHoveredKey(dayKey),
638
758
  onMouseLeave: () => setHoveredKey(
639
759
  (current) => current === dayKey ? null : current
@@ -760,7 +880,7 @@ var Calendar = forwardRef(
760
880
  const [selectedEndDate, setSelectedEndDate] = useState2(endDate);
761
881
  const [locSelectedDate, setLocSelectedDate] = useState2(selectedDate);
762
882
  const [selectingRange, setSelectingRange] = useState2(null);
763
- useEffect(() => {
883
+ useEffect2(() => {
764
884
  setSelectedStartDate(startDate);
765
885
  setSelectedEndDate(endDate);
766
886
  setLocSelectedDate(selectedDate);
@@ -801,7 +921,7 @@ var Calendar = forwardRef(
801
921
  []
802
922
  );
803
923
  const increaseMonth = useCallback3(
804
- () => setCurrentMonth((prev) => addMonths(prev, 1)),
924
+ () => setCurrentMonth((prev) => addMonths2(prev, 1)),
805
925
  []
806
926
  );
807
927
  const changeMonth = useCallback3(
@@ -901,6 +1021,7 @@ var Calendar = forwardRef(
901
1021
  selectedDate: locSelectedDate,
902
1022
  selectingRange,
903
1023
  onDayPress: handleChange,
1024
+ onMonthChange: setCurrentMonth,
904
1025
  themeMode,
905
1026
  themeProductContext
906
1027
  }
@@ -915,10 +1036,10 @@ var Calendar = forwardRef(
915
1036
  Calendar.displayName = "Calendar";
916
1037
 
917
1038
  // src/DualCalendar.tsx
918
- import { forwardRef as forwardRef2, useCallback as useCallback4, useEffect as useEffect2, useState as useState3 } from "react";
1039
+ import { forwardRef as forwardRef2, useCallback as useCallback4, useEffect as useEffect3, useState as useState3 } from "react";
919
1040
  import { useResolvedTheme as useResolvedTheme4 } from "@xsolla/xui-core";
920
1041
  import {
921
- addMonths as addMonths2,
1042
+ addMonths as addMonths3,
922
1043
  isSameMonth as isSameMonth2,
923
1044
  subDays as subDays2,
924
1045
  subMonths as subMonths2,
@@ -951,21 +1072,21 @@ var DualCalendar = forwardRef2(
951
1072
  month || initialMonth || startDate || /* @__PURE__ */ new Date()
952
1073
  );
953
1074
  const [rightMonth, setRightMonth] = useState3(
954
- () => addMonths2(month || initialMonth || startDate || /* @__PURE__ */ new Date(), 1)
1075
+ () => addMonths3(month || initialMonth || startDate || /* @__PURE__ */ new Date(), 1)
955
1076
  );
956
1077
  const [selectedStartDate, setSelectedStartDate] = useState3(startDate);
957
1078
  const [selectedEndDate, setSelectedEndDate] = useState3(endDate);
958
1079
  const [selectingRange, setSelectingRange] = useState3(null);
959
1080
  const showFrom = useCallback4((date) => {
960
1081
  setLeftMonth(date);
961
- setRightMonth(addMonths2(date, 1));
1082
+ setRightMonth(addMonths3(date, 1));
962
1083
  }, []);
963
- useEffect2(() => {
1084
+ useEffect3(() => {
964
1085
  setSelectedStartDate(startDate);
965
1086
  setSelectedEndDate(endDate);
966
1087
  }, [startDate, endDate]);
967
1088
  const monthKey = month ? month.getFullYear() * 12 + month.getMonth() : null;
968
- useEffect2(() => {
1089
+ useEffect3(() => {
969
1090
  if (!month) return;
970
1091
  if (isSameMonth2(month, leftMonth) || isSameMonth2(month, rightMonth)) {
971
1092
  return;
@@ -1028,7 +1149,7 @@ var DualCalendar = forwardRef2(
1028
1149
  []
1029
1150
  );
1030
1151
  const increaseLeftMonth = useCallback4(
1031
- () => setLeftMonth((prev) => addMonths2(prev, 1)),
1152
+ () => setLeftMonth((prev) => addMonths3(prev, 1)),
1032
1153
  []
1033
1154
  );
1034
1155
  const changeLeftMonth = useCallback4(
@@ -1044,7 +1165,7 @@ var DualCalendar = forwardRef2(
1044
1165
  []
1045
1166
  );
1046
1167
  const increaseRightMonth = useCallback4(
1047
- () => setRightMonth((prev) => addMonths2(prev, 1)),
1168
+ () => setRightMonth((prev) => addMonths3(prev, 1)),
1048
1169
  []
1049
1170
  );
1050
1171
  const changeRightMonth = useCallback4(