@proyecto-viviana/solid-stately 0.2.7 → 0.4.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 +30 -283
- package/dist/autocomplete/createAutocompleteState.d.ts +2 -2
- package/dist/autocomplete/index.d.ts +1 -1
- package/dist/calendar/IncompleteDate.d.ts +61 -0
- package/dist/calendar/createCalendarState.d.ts +41 -16
- package/dist/calendar/createDateFieldState.d.ts +39 -9
- package/dist/calendar/createDatePickerState.d.ts +60 -0
- package/dist/calendar/createRangeCalendarState.d.ts +37 -10
- package/dist/calendar/createTimeFieldState.d.ts +34 -7
- package/dist/calendar/index.d.ts +7 -6
- package/dist/checkbox/createCheckboxGroupState.d.ts +6 -6
- package/dist/checkbox/index.d.ts +1 -1
- package/dist/collections/ListCollection.d.ts +1 -1
- package/dist/collections/createListState.d.ts +11 -8
- package/dist/collections/createMenuState.d.ts +7 -7
- package/dist/collections/createSelectionState.d.ts +22 -10
- package/dist/collections/getChildNodes.d.ts +11 -0
- package/dist/collections/index.d.ts +6 -5
- package/dist/collections/types.d.ts +12 -12
- package/dist/color/Color.d.ts +1 -1
- package/dist/color/createColorAreaState.d.ts +4 -2
- package/dist/color/createColorFieldState.d.ts +38 -12
- package/dist/color/createColorSliderState.d.ts +9 -2
- package/dist/color/createColorWheelState.d.ts +13 -2
- package/dist/color/getColorChannels.d.ts +1 -1
- package/dist/color/index.d.ts +6 -6
- package/dist/color/types.d.ts +4 -4
- package/dist/combobox/createComboBoxState.d.ts +34 -13
- package/dist/combobox/index.d.ts +1 -1
- package/dist/data/createAsyncList.d.ts +4 -4
- package/dist/data/createListData.d.ts +2 -2
- package/dist/data/createTreeData.d.ts +6 -2
- package/dist/data/index.d.ts +3 -3
- package/dist/datepicker/index.d.ts +2 -4
- package/dist/disclosure/createDisclosureState.d.ts +2 -2
- package/dist/disclosure/index.d.ts +1 -1
- package/dist/dnd/createDragState.d.ts +2 -2
- package/dist/dnd/createDraggableCollectionState.d.ts +2 -2
- package/dist/dnd/createDropState.d.ts +2 -2
- package/dist/dnd/createDroppableCollectionState.d.ts +3 -3
- package/dist/dnd/index.d.ts +5 -5
- package/dist/dnd/types.d.ts +19 -17
- package/dist/form/createFormValidationState.d.ts +3 -3
- package/dist/form/index.d.ts +1 -1
- package/dist/grid/createGridState.d.ts +2 -2
- package/dist/grid/index.d.ts +2 -2
- package/dist/grid/types.d.ts +27 -11
- package/dist/index.d.ts +31 -30
- package/dist/index.js +9077 -7246
- package/dist/index.js.map +1 -1
- package/dist/menu/index.d.ts +2 -2
- package/dist/numberfield/createNumberFieldState.d.ts +2 -2
- package/dist/numberfield/index.d.ts +1 -1
- package/dist/overlays/createOverlayTriggerState.d.ts +2 -2
- package/dist/overlays/index.d.ts +1 -1
- package/dist/radio/createRadioGroupState.d.ts +7 -7
- package/dist/radio/index.d.ts +1 -1
- package/dist/searchfield/createSearchFieldState.d.ts +2 -2
- package/dist/searchfield/index.d.ts +2 -2
- package/dist/select/createSelectState.d.ts +14 -8
- package/dist/select/index.d.ts +1 -1
- package/dist/selection/Selection.d.ts +16 -0
- package/dist/selection/SelectionManager.d.ts +110 -0
- package/dist/selection/createMultipleSelectionState.d.ts +59 -0
- package/dist/selection/index.d.ts +6 -5
- package/dist/slider/createSliderState.d.ts +3 -3
- package/dist/slider/index.d.ts +2 -2
- package/dist/steplist/createStepListState.d.ts +48 -0
- package/dist/steplist/index.d.ts +1 -0
- package/dist/table/TableCollection.d.ts +33 -3
- package/dist/table/createTableColumnResizeState.d.ts +54 -0
- package/dist/table/createTableState.d.ts +2 -2
- package/dist/table/createTreeGridState.d.ts +15 -0
- package/dist/table/index.d.ts +6 -3
- package/dist/table/types.d.ts +68 -10
- package/dist/tabs/createTabListState.d.ts +5 -5
- package/dist/tabs/index.d.ts +1 -1
- package/dist/textfield/createTextFieldState.d.ts +2 -2
- package/dist/textfield/index.d.ts +1 -1
- package/dist/toast/createToastState.d.ts +24 -2
- package/dist/toast/index.d.ts +1 -1
- package/dist/toggle/createToggleGroupState.d.ts +4 -4
- package/dist/toggle/createToggleState.d.ts +2 -2
- package/dist/toggle/index.d.ts +2 -2
- package/dist/tooltip/createTooltipTriggerState.d.ts +2 -2
- package/dist/tooltip/index.d.ts +1 -1
- package/dist/tree/TreeCollection.d.ts +2 -2
- package/dist/tree/createTreeState.d.ts +2 -2
- package/dist/tree/index.d.ts +3 -3
- package/dist/tree/types.d.ts +27 -13
- package/dist/utils/index.d.ts +1 -1
- package/dist/utils/reactivity.d.ts +1 -1
- package/package.json +29 -30
- package/src/autocomplete/createAutocompleteState.d.ts +19 -17
- package/src/autocomplete/createAutocompleteState.ts +24 -30
- package/src/autocomplete/index.d.ts +6 -2
- package/src/autocomplete/index.ts +1 -1
- package/src/calendar/IncompleteDate.ts +430 -0
- package/src/calendar/createCalendarState.d.ts +139 -117
- package/src/calendar/createCalendarState.ts +308 -94
- package/src/calendar/createDateFieldState.d.ts +130 -95
- package/src/calendar/createDateFieldState.ts +447 -334
- package/src/calendar/createDatePickerState.ts +347 -0
- package/src/calendar/createRangeCalendarState.d.ts +154 -129
- package/src/calendar/createRangeCalendarState.ts +411 -85
- package/src/calendar/createTimeFieldState.d.ts +81 -79
- package/src/calendar/createTimeFieldState.ts +187 -108
- package/src/calendar/index.d.ts +80 -7
- package/src/calendar/index.ts +23 -12
- package/src/checkbox/createCheckboxGroupState.d.ts +57 -55
- package/src/checkbox/createCheckboxGroupState.ts +20 -29
- package/src/checkbox/index.d.ts +6 -2
- package/src/checkbox/index.ts +1 -1
- package/src/collections/ListCollection.d.ts +25 -22
- package/src/collections/ListCollection.ts +6 -6
- package/src/collections/createListState.d.ts +73 -58
- package/src/collections/createListState.ts +67 -35
- package/src/collections/createMenuState.d.ts +36 -31
- package/src/collections/createMenuState.ts +21 -12
- package/src/collections/createSelectionState.d.ts +78 -65
- package/src/collections/createSelectionState.ts +66 -52
- package/src/collections/getChildNodes.ts +98 -0
- package/src/collections/index.d.ts +38 -6
- package/src/collections/index.ts +14 -10
- package/src/collections/types.d.ts +101 -101
- package/src/collections/types.ts +12 -12
- package/src/color/Color.d.ts +20 -5
- package/src/color/Color.ts +357 -254
- package/src/color/createColorAreaState.d.ts +67 -65
- package/src/color/createColorAreaState.ts +21 -13
- package/src/color/createColorFieldState.d.ts +46 -44
- package/src/color/createColorFieldState.ts +285 -149
- package/src/color/createColorSliderState.d.ts +58 -56
- package/src/color/createColorSliderState.ts +45 -14
- package/src/color/createColorWheelState.d.ts +42 -40
- package/src/color/createColorWheelState.ts +101 -64
- package/src/color/getColorChannels.ts +7 -5
- package/src/color/index.d.ts +36 -7
- package/src/color/index.ts +6 -9
- package/src/color/types.d.ts +68 -63
- package/src/color/types.ts +11 -19
- package/src/combobox/createComboBoxState.d.ts +107 -105
- package/src/combobox/createComboBoxState.ts +184 -89
- package/src/combobox/index.d.ts +10 -2
- package/src/combobox/index.ts +1 -1
- package/src/data/createAsyncList.ts +89 -63
- package/src/data/createListData.ts +67 -67
- package/src/data/createTreeData.ts +304 -91
- package/src/data/index.ts +3 -3
- package/src/datepicker/index.ts +6 -10
- package/src/disclosure/createDisclosureState.d.ts +47 -43
- package/src/disclosure/createDisclosureState.ts +7 -21
- package/src/disclosure/index.d.ts +10 -2
- package/src/disclosure/index.ts +1 -1
- package/src/dnd/createDragState.d.ts +51 -42
- package/src/dnd/createDragState.ts +12 -14
- package/src/dnd/createDraggableCollectionState.d.ts +52 -41
- package/src/dnd/createDraggableCollectionState.ts +14 -21
- package/src/dnd/createDropState.d.ts +64 -45
- package/src/dnd/createDropState.ts +22 -32
- package/src/dnd/createDroppableCollectionState.d.ts +88 -59
- package/src/dnd/createDroppableCollectionState.ts +40 -43
- package/src/dnd/index.d.ts +55 -6
- package/src/dnd/index.ts +6 -18
- package/src/dnd/types.d.ts +170 -157
- package/src/dnd/types.ts +24 -22
- package/src/form/createFormValidationState.d.ts +47 -45
- package/src/form/createFormValidationState.ts +30 -63
- package/src/form/index.d.ts +16 -2
- package/src/form/index.ts +1 -1
- package/src/grid/createGridState.d.ts +7 -4
- package/src/grid/createGridState.ts +78 -51
- package/src/grid/index.d.ts +3 -3
- package/src/grid/index.ts +2 -8
- package/src/grid/types.d.ts +136 -129
- package/src/grid/types.ts +34 -18
- package/src/index.d.ts +311 -26
- package/src/index.ts +59 -89
- package/src/menu/index.ts +2 -4
- package/src/numberfield/createNumberFieldState.d.ts +56 -54
- package/src/numberfield/createNumberFieldState.ts +26 -31
- package/src/numberfield/index.d.ts +6 -2
- package/src/numberfield/index.ts +1 -1
- package/src/overlays/createOverlayTriggerState.d.ts +22 -20
- package/src/overlays/createOverlayTriggerState.ts +3 -4
- package/src/overlays/index.d.ts +6 -2
- package/src/overlays/index.ts +1 -1
- package/src/radio/createRadioGroupState.d.ts +54 -52
- package/src/radio/createRadioGroupState.ts +27 -29
- package/src/radio/index.d.ts +7 -2
- package/src/radio/index.ts +1 -1
- package/src/searchfield/createSearchFieldState.d.ts +16 -14
- package/src/searchfield/createSearchFieldState.ts +5 -11
- package/src/searchfield/index.d.ts +3 -3
- package/src/searchfield/index.ts +2 -5
- package/src/select/createSelectState.d.ts +63 -61
- package/src/select/createSelectState.ts +42 -29
- package/src/select/index.d.ts +2 -2
- package/src/select/index.ts +1 -5
- package/src/selection/Selection.ts +28 -0
- package/src/selection/SelectionManager.ts +526 -0
- package/src/selection/createMultipleSelectionState.ts +247 -0
- package/src/selection/index.ts +12 -7
- package/src/slider/createSliderState.d.ts +60 -60
- package/src/slider/createSliderState.ts +55 -33
- package/src/slider/index.d.ts +3 -3
- package/src/slider/index.ts +2 -6
- package/src/ssr/index.d.ts +1 -1
- package/src/ssr/index.ts +2 -2
- package/src/steplist/createStepListState.ts +191 -0
- package/src/steplist/index.ts +5 -0
- package/src/table/TableCollection.d.ts +42 -40
- package/src/table/TableCollection.ts +266 -99
- package/src/table/createTableColumnResizeState.ts +242 -0
- package/src/table/createTableState.d.ts +7 -4
- package/src/table/createTableState.ts +30 -7
- package/src/table/createTreeGridState.ts +146 -0
- package/src/table/index.d.ts +14 -4
- package/src/table/index.ts +13 -3
- package/src/table/types.d.ts +98 -92
- package/src/table/types.ts +83 -13
- package/src/tabs/createTabListState.d.ts +59 -57
- package/src/tabs/createTabListState.ts +34 -39
- package/src/tabs/index.d.ts +8 -2
- package/src/tabs/index.ts +1 -1
- package/src/textfield/createTextFieldState.d.ts +16 -14
- package/src/textfield/createTextFieldState.ts +7 -18
- package/src/textfield/index.d.ts +6 -2
- package/src/textfield/index.ts +1 -1
- package/src/toast/createToastState.d.ts +92 -86
- package/src/toast/createToastState.ts +90 -52
- package/src/toast/index.d.ts +12 -2
- package/src/toast/index.ts +2 -1
- package/src/toggle/createToggleGroupState.ts +11 -21
- package/src/toggle/createToggleState.d.ts +19 -19
- package/src/toggle/createToggleState.ts +2 -16
- package/src/toggle/index.d.ts +2 -2
- package/src/toggle/index.ts +2 -6
- package/src/tooltip/createTooltipTriggerState.d.ts +17 -17
- package/src/tooltip/createTooltipTriggerState.ts +12 -11
- package/src/tooltip/index.d.ts +7 -2
- package/src/tooltip/index.ts +1 -1
- package/src/tree/TreeCollection.d.ts +26 -23
- package/src/tree/TreeCollection.ts +29 -22
- package/src/tree/createTreeState.d.ts +7 -4
- package/src/tree/createTreeState.ts +70 -41
- package/src/tree/index.d.ts +10 -4
- package/src/tree/index.ts +3 -3
- package/src/tree/types.d.ts +131 -126
- package/src/tree/types.ts +30 -13
- package/src/utils/index.d.ts +2 -2
- package/src/utils/index.ts +1 -1
- package/src/utils/reactivity.d.ts +2 -2
- package/src/utils/reactivity.ts +3 -3
|
@@ -5,28 +5,35 @@
|
|
|
5
5
|
* Based on @react-stately/calendar useRangeCalendarState
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import { createSignal, createMemo, type Accessor } from
|
|
8
|
+
import { createSignal, createMemo, createEffect, type Accessor } from "solid-js";
|
|
9
9
|
import {
|
|
10
|
+
type Calendar as InternationalizedCalendar,
|
|
10
11
|
type CalendarDate,
|
|
12
|
+
type CalendarIdentifier,
|
|
11
13
|
type DateValue,
|
|
14
|
+
GregorianCalendar,
|
|
12
15
|
today,
|
|
13
16
|
getLocalTimeZone,
|
|
17
|
+
isEqualCalendar,
|
|
14
18
|
isSameDay,
|
|
15
19
|
isSameMonth,
|
|
16
20
|
startOfMonth,
|
|
17
21
|
endOfMonth,
|
|
18
22
|
startOfWeek,
|
|
19
23
|
getWeeksInMonth,
|
|
20
|
-
|
|
24
|
+
maxDate,
|
|
25
|
+
minDate,
|
|
21
26
|
DateFormatter,
|
|
27
|
+
createCalendar as intlCreateCalendar,
|
|
28
|
+
toCalendar as intlToCalendar,
|
|
22
29
|
toCalendarDate as intlToCalendarDate,
|
|
23
|
-
} from
|
|
24
|
-
import { access, type MaybeAccessor } from
|
|
25
|
-
import type { ValidationState } from
|
|
30
|
+
} from "@internationalized/date";
|
|
31
|
+
import { access, type MaybeAccessor } from "../utils";
|
|
32
|
+
import type { CalendarDayOfWeek, ValidationState } from "./createCalendarState";
|
|
26
33
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
34
|
+
const dayOfWeekNames: CalendarDayOfWeek[] = ["sun", "mon", "tue", "wed", "thu", "fri", "sat"];
|
|
35
|
+
type RangeCalendarSelectionAlignment = "start" | "center" | "end";
|
|
36
|
+
type RangeCalendarPageBehavior = "single" | "visible";
|
|
30
37
|
|
|
31
38
|
export interface DateRange {
|
|
32
39
|
start: CalendarDate;
|
|
@@ -44,7 +51,7 @@ export interface RangeCalendarStateProps<T extends DateValue = DateValue> {
|
|
|
44
51
|
/** The default value (uncontrolled). */
|
|
45
52
|
defaultValue?: RangeValue<T> | null;
|
|
46
53
|
/** Handler called when the value changes. */
|
|
47
|
-
onChange?: (value: RangeValue<T>) => void;
|
|
54
|
+
onChange?: (value: RangeValue<T> | null) => void;
|
|
48
55
|
/** The minimum allowed date. */
|
|
49
56
|
minValue?: MaybeAccessor<DateValue | undefined>;
|
|
50
57
|
/** The maximum allowed date. */
|
|
@@ -54,17 +61,27 @@ export interface RangeCalendarStateProps<T extends DateValue = DateValue> {
|
|
|
54
61
|
/** Whether the calendar is read-only. */
|
|
55
62
|
isReadOnly?: MaybeAccessor<boolean>;
|
|
56
63
|
/** The date that is focused when the calendar first mounts. */
|
|
57
|
-
focusedValue?: MaybeAccessor<DateValue | undefined>;
|
|
64
|
+
focusedValue?: MaybeAccessor<DateValue | null | undefined>;
|
|
58
65
|
/** The default focused date (uncontrolled). */
|
|
59
66
|
defaultFocusedValue?: DateValue;
|
|
60
67
|
/** Handler called when the focused date changes. */
|
|
61
68
|
onFocusChange?: (date: CalendarDate) => void;
|
|
62
69
|
/** The locale to use for formatting. */
|
|
63
|
-
locale?: string
|
|
64
|
-
/**
|
|
65
|
-
|
|
70
|
+
locale?: MaybeAccessor<string | undefined>;
|
|
71
|
+
/** Creates a calendar object for a locale calendar identifier. */
|
|
72
|
+
createCalendar?: (identifier: CalendarIdentifier) => InternationalizedCalendar;
|
|
73
|
+
/**
|
|
74
|
+
* Callback that determines if a date is unavailable. The second argument is the
|
|
75
|
+
* current selection anchor (the first selected date), so availability can be
|
|
76
|
+
* derived from it. Mirrors @react-stately/calendar 1.18.
|
|
77
|
+
*/
|
|
78
|
+
isDateUnavailable?: (date: DateValue, anchorDate: CalendarDate | null) => boolean;
|
|
66
79
|
/** The number of months to display at once. */
|
|
67
|
-
visibleMonths?: number
|
|
80
|
+
visibleMonths?: MaybeAccessor<number | undefined>;
|
|
81
|
+
/** Controls how the initial visible months are aligned around the current value. */
|
|
82
|
+
selectionAlignment?: MaybeAccessor<RangeCalendarSelectionAlignment | undefined>;
|
|
83
|
+
/** Controls whether pagination moves by one month or the visible month count. */
|
|
84
|
+
pageBehavior?: MaybeAccessor<RangeCalendarPageBehavior | undefined>;
|
|
68
85
|
/** Controls which days are disabled. */
|
|
69
86
|
isDateDisabled?: (date: DateValue) => boolean;
|
|
70
87
|
/** Validation state. */
|
|
@@ -84,6 +101,10 @@ export interface RangeCalendarState<T extends DateValue = DateValue> {
|
|
|
84
101
|
focusedDate: Accessor<CalendarDate>;
|
|
85
102
|
/** Sets the focused date. */
|
|
86
103
|
setFocusedDate: (date: CalendarDate) => void;
|
|
104
|
+
/** The minimum allowed date that a user may select. */
|
|
105
|
+
minValue: Accessor<DateValue | null>;
|
|
106
|
+
/** The maximum allowed date that a user may select. */
|
|
107
|
+
maxValue: Accessor<DateValue | null>;
|
|
87
108
|
/** The anchor date when selecting a range (first click). */
|
|
88
109
|
anchorDate: Accessor<CalendarDate | null>;
|
|
89
110
|
/** Sets the anchor date. */
|
|
@@ -98,8 +119,14 @@ export interface RangeCalendarState<T extends DateValue = DateValue> {
|
|
|
98
119
|
visibleRange: Accessor<{ start: CalendarDate; end: CalendarDate }>;
|
|
99
120
|
/** The timezone used for date calculations. */
|
|
100
121
|
timeZone: string;
|
|
122
|
+
/** The locale used for formatting and locale-specific week starts. */
|
|
123
|
+
locale: Accessor<string>;
|
|
124
|
+
/** The explicitly requested first weekday, or undefined for the locale default. */
|
|
125
|
+
firstDayOfWeek: Accessor<CalendarDayOfWeek | undefined>;
|
|
101
126
|
/** The validation state. */
|
|
102
127
|
validationState: Accessor<ValidationState | undefined>;
|
|
128
|
+
/** Whether the current value is invalid according to validation state. */
|
|
129
|
+
isValueInvalid: Accessor<boolean>;
|
|
103
130
|
/** Whether a date is within the selected range. */
|
|
104
131
|
isSelected: (date: DateValue) => boolean;
|
|
105
132
|
/** Whether a date is the start of the selection. */
|
|
@@ -124,6 +151,10 @@ export interface RangeCalendarState<T extends DateValue = DateValue> {
|
|
|
124
151
|
focusPreviousSection: () => void;
|
|
125
152
|
/** Moves focus to the next section (year). */
|
|
126
153
|
focusNextSection: () => void;
|
|
154
|
+
/** Whether the previous visible range would be outside the allowed date range. */
|
|
155
|
+
isPreviousVisibleRangeInvalid: () => boolean;
|
|
156
|
+
/** Whether the next visible range would be outside the allowed date range. */
|
|
157
|
+
isNextVisibleRangeInvalid: () => boolean;
|
|
127
158
|
/** Moves focus to the previous day. */
|
|
128
159
|
focusPreviousDay: () => void;
|
|
129
160
|
/** Moves focus to the next day. */
|
|
@@ -160,60 +191,230 @@ export interface RangeCalendarState<T extends DateValue = DateValue> {
|
|
|
160
191
|
setDragging: (dragging: boolean) => void;
|
|
161
192
|
}
|
|
162
193
|
|
|
163
|
-
// ============================================
|
|
164
|
-
// IMPLEMENTATION
|
|
165
|
-
// ============================================
|
|
166
|
-
|
|
167
194
|
/**
|
|
168
195
|
* Provides state management for a range calendar component.
|
|
169
196
|
*/
|
|
170
197
|
export function createRangeCalendarState<T extends DateValue = CalendarDate>(
|
|
171
|
-
props: RangeCalendarStateProps<T> = {}
|
|
198
|
+
props: RangeCalendarStateProps<T> = {},
|
|
172
199
|
): RangeCalendarState<T> {
|
|
173
200
|
const timeZone = getLocalTimeZone();
|
|
174
|
-
const locale = props.locale ??
|
|
175
|
-
const
|
|
201
|
+
const locale = createMemo(() => access(props.locale) ?? "en-US");
|
|
202
|
+
const minValueState = createMemo(() => access(props.minValue) ?? null);
|
|
203
|
+
const maxValueState = createMemo(() => access(props.maxValue) ?? null);
|
|
204
|
+
const resolvedOptions = createMemo(() => new DateFormatter(locale()).resolvedOptions());
|
|
205
|
+
const calendar = createMemo(() =>
|
|
206
|
+
(props.createCalendar ?? intlCreateCalendar)(resolvedOptions().calendar as CalendarIdentifier),
|
|
207
|
+
);
|
|
208
|
+
const visibleMonths = Math.max(1, Number(access(props.visibleMonths) ?? 1));
|
|
209
|
+
const firstDayOfWeekName = (): CalendarDayOfWeek | undefined =>
|
|
210
|
+
props.firstDayOfWeek == null ? undefined : dayOfWeekNames[props.firstDayOfWeek];
|
|
211
|
+
|
|
212
|
+
const toDisplayCalendarDate = (date: DateValue): CalendarDate =>
|
|
213
|
+
intlToCalendar(toCalendarDate(date), calendar());
|
|
214
|
+
|
|
215
|
+
// The anchor date is the first selected date during a range drag. Declared up
|
|
216
|
+
// here (ahead of constrainDate) because the available-range derivation below
|
|
217
|
+
// depends on it, and constrainDate consults that range.
|
|
218
|
+
const [anchorDate, setAnchorDate] = createSignal<CalendarDate | null>(null);
|
|
219
|
+
|
|
220
|
+
// Mirrors @react-stately/calendar useRangeCalendarState getAvailableRange: once
|
|
221
|
+
// a range selection is anchored and unavailable dates exist, contiguous ranges
|
|
222
|
+
// are required (unless allowsNonContiguousRanges), so the selectable span is
|
|
223
|
+
// bounded by the nearest unavailable date on each side of the anchor.
|
|
224
|
+
const getAvailableRange = (
|
|
225
|
+
anchor: CalendarDate | null,
|
|
226
|
+
): { start?: CalendarDate; end?: CalendarDate } | null => {
|
|
227
|
+
if (anchor && props.isDateUnavailable && !props.allowsNonContiguousRanges) {
|
|
228
|
+
const predicate = (date: CalendarDate): boolean => props.isDateUnavailable!(date, anchor);
|
|
229
|
+
return {
|
|
230
|
+
start: nextUnavailableDate(anchor, predicate, visibleMonths, -1),
|
|
231
|
+
end: nextUnavailableDate(anchor, predicate, visibleMonths, 1),
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
return null;
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
const availableRange = createMemo(() => getAvailableRange(anchorDate()));
|
|
238
|
+
|
|
239
|
+
// minValue/maxValue narrowed by the available range, in display-calendar space
|
|
240
|
+
// (what every cell/navigation bound below compares against). Mirrors upstream's
|
|
241
|
+
// maxDate(minValue, availableRange?.start) / minDate(maxValue, availableRange?.end);
|
|
242
|
+
// maxDate/minDate ignore undefined operands, so an absent bound passes through.
|
|
243
|
+
const effectiveMinValue = createMemo<CalendarDate | null>(() => {
|
|
244
|
+
const min = access(props.minValue);
|
|
245
|
+
const minCal = min != null ? toDisplayCalendarDate(min) : undefined;
|
|
246
|
+
return maxDate(minCal, availableRange()?.start) ?? null;
|
|
247
|
+
});
|
|
248
|
+
const effectiveMaxValue = createMemo<CalendarDate | null>(() => {
|
|
249
|
+
const max = access(props.maxValue);
|
|
250
|
+
const maxCal = max != null ? toDisplayCalendarDate(max) : undefined;
|
|
251
|
+
return minDate(maxCal, availableRange()?.end) ?? null;
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
const constrainDate = (date: CalendarDate): CalendarDate => {
|
|
255
|
+
const min = effectiveMinValue();
|
|
256
|
+
const max = effectiveMaxValue();
|
|
257
|
+
|
|
258
|
+
let constrained = date;
|
|
259
|
+
|
|
260
|
+
if (min && constrained.compare(min) < 0) {
|
|
261
|
+
constrained = min;
|
|
262
|
+
}
|
|
263
|
+
if (max && constrained.compare(max) > 0) {
|
|
264
|
+
constrained = max;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
return constrained;
|
|
268
|
+
};
|
|
269
|
+
|
|
270
|
+
const visibleRangeEndFromStart = (start: CalendarDate): CalendarDate => {
|
|
271
|
+
let end = endOfMonth(start);
|
|
272
|
+
|
|
273
|
+
for (let i = 1; i < visibleMonths; i++) {
|
|
274
|
+
end = endOfMonth(end.add({ months: 1 }));
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
return end;
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
const rawAlignStart = (date: CalendarDate): CalendarDate => startOfMonth(date);
|
|
281
|
+
|
|
282
|
+
const rawAlignEnd = (date: CalendarDate): CalendarDate =>
|
|
283
|
+
startOfMonth(date).subtract({ months: Math.max(visibleMonths - 1, 0) });
|
|
284
|
+
|
|
285
|
+
const constrainVisibleRangeStart = (date: CalendarDate, aligned: CalendarDate): CalendarDate => {
|
|
286
|
+
const minValue = access(props.minValue);
|
|
287
|
+
const maxValue = access(props.maxValue);
|
|
288
|
+
let constrained = aligned;
|
|
289
|
+
|
|
290
|
+
if (minValue && date.compare(toDisplayCalendarDate(minValue)) >= 0) {
|
|
291
|
+
const minStart = rawAlignStart(toDisplayCalendarDate(minValue));
|
|
292
|
+
if (minStart.compare(constrained) > 0) {
|
|
293
|
+
constrained = minStart;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
if (maxValue && date.compare(toDisplayCalendarDate(maxValue)) <= 0) {
|
|
298
|
+
const maxStart = rawAlignEnd(toDisplayCalendarDate(maxValue));
|
|
299
|
+
if (maxStart.compare(constrained) < 0) {
|
|
300
|
+
constrained = maxStart;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
return constrained;
|
|
305
|
+
};
|
|
306
|
+
|
|
307
|
+
const alignStart = (date: CalendarDate): CalendarDate =>
|
|
308
|
+
constrainVisibleRangeStart(date, rawAlignStart(date));
|
|
309
|
+
|
|
310
|
+
const alignEnd = (date: CalendarDate): CalendarDate =>
|
|
311
|
+
constrainVisibleRangeStart(date, rawAlignEnd(date));
|
|
312
|
+
|
|
313
|
+
const alignCenter = (date: CalendarDate): CalendarDate => {
|
|
314
|
+
let offset = Math.floor(visibleMonths / 2);
|
|
315
|
+
if (offset > 0 && visibleMonths % 2 === 0) {
|
|
316
|
+
offset--;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
return constrainVisibleRangeStart(date, rawAlignStart(date).subtract({ months: offset }));
|
|
320
|
+
};
|
|
321
|
+
|
|
322
|
+
const defaultSelectionAlignment = (): RangeCalendarSelectionAlignment => {
|
|
323
|
+
const currentValue = access(props.value) ?? props.defaultValue;
|
|
324
|
+
if (!currentValue?.start || !currentValue?.end) {
|
|
325
|
+
return "center";
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
const start = alignCenter(toDisplayCalendarDate(currentValue.start));
|
|
329
|
+
const end = visibleRangeEndFromStart(start);
|
|
330
|
+
return toDisplayCalendarDate(currentValue.end).compare(end) > 0 ? "start" : "center";
|
|
331
|
+
};
|
|
332
|
+
|
|
333
|
+
const alignVisibleRangeStart = (date: CalendarDate): CalendarDate => {
|
|
334
|
+
switch (access(props.selectionAlignment) ?? defaultSelectionAlignment()) {
|
|
335
|
+
case "start":
|
|
336
|
+
return alignStart(date);
|
|
337
|
+
case "end":
|
|
338
|
+
return alignEnd(date);
|
|
339
|
+
case "center":
|
|
340
|
+
default:
|
|
341
|
+
return alignCenter(date);
|
|
342
|
+
}
|
|
343
|
+
};
|
|
176
344
|
|
|
177
345
|
// Determine the initially focused date
|
|
178
346
|
const getInitialFocusedDate = (): CalendarDate => {
|
|
179
347
|
const controlledFocused = access(props.focusedValue);
|
|
180
348
|
if (controlledFocused) {
|
|
181
|
-
return
|
|
349
|
+
return toDisplayCalendarDate(controlledFocused);
|
|
182
350
|
}
|
|
183
351
|
if (props.defaultFocusedValue) {
|
|
184
|
-
return
|
|
352
|
+
return toDisplayCalendarDate(props.defaultFocusedValue);
|
|
185
353
|
}
|
|
186
354
|
const controlledValue = access(props.value);
|
|
187
355
|
if (controlledValue?.start) {
|
|
188
|
-
return
|
|
356
|
+
return toDisplayCalendarDate(controlledValue.start);
|
|
189
357
|
}
|
|
190
358
|
if (props.defaultValue?.start) {
|
|
191
|
-
return
|
|
359
|
+
return toDisplayCalendarDate(props.defaultValue.start);
|
|
192
360
|
}
|
|
193
|
-
return today(timeZone);
|
|
361
|
+
return intlToCalendar(today(timeZone), calendar());
|
|
194
362
|
};
|
|
195
363
|
|
|
196
364
|
// State signals
|
|
197
365
|
const [internalValue, setInternalValue] = createSignal<RangeValue<T> | null>(
|
|
198
|
-
props.defaultValue ?? null
|
|
366
|
+
props.defaultValue ?? null,
|
|
199
367
|
);
|
|
200
|
-
const
|
|
201
|
-
|
|
368
|
+
const initialFocusedDate = constrainDate(getInitialFocusedDate());
|
|
369
|
+
const [focusedDate, setFocusedDateInternal] = createSignal<CalendarDate>(initialFocusedDate);
|
|
370
|
+
const [visibleRangeStart, setVisibleRangeStart] = createSignal<CalendarDate>(
|
|
371
|
+
alignVisibleRangeStart(initialFocusedDate),
|
|
202
372
|
);
|
|
203
|
-
const [anchorDate, setAnchorDate] = createSignal<CalendarDate | null>(null);
|
|
204
373
|
const [isFocused, setFocused] = createSignal(false);
|
|
205
374
|
const [isDragging, setDragging] = createSignal(false);
|
|
206
375
|
|
|
207
376
|
// Controlled vs uncontrolled value
|
|
208
|
-
const
|
|
377
|
+
const sourceValue = createMemo<RangeValue<T> | null>(() => {
|
|
209
378
|
const controlled = access(props.value);
|
|
210
379
|
return controlled !== undefined ? controlled : internalValue();
|
|
211
380
|
});
|
|
381
|
+
const value = createMemo<RangeValue<T> | null>(() => {
|
|
382
|
+
const currentValue = sourceValue();
|
|
383
|
+
if (!currentValue) {
|
|
384
|
+
return null;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
return {
|
|
388
|
+
start: toDisplayCalendarDate(currentValue.start) as unknown as T,
|
|
389
|
+
end: toDisplayCalendarDate(currentValue.end) as unknown as T,
|
|
390
|
+
};
|
|
391
|
+
});
|
|
212
392
|
|
|
213
393
|
// Derived states
|
|
214
394
|
const isDisabled = createMemo(() => access(props.isDisabled) ?? false);
|
|
215
395
|
const isReadOnly = createMemo(() => access(props.isReadOnly) ?? false);
|
|
216
396
|
const validationState = createMemo(() => access(props.validationState));
|
|
397
|
+
// A committed range is invalid when (not mid-drag) either endpoint is
|
|
398
|
+
// unavailable or falls outside [minValue, maxValue]. Mirrors upstream
|
|
399
|
+
// useRangeCalendarState's isInvalid derivation. Calls props.isDateUnavailable
|
|
400
|
+
// directly (with a null anchor) rather than the closures defined further down,
|
|
401
|
+
// since this memo runs eagerly during init.
|
|
402
|
+
const isInvalidSelection = createMemo(() => {
|
|
403
|
+
const v = sourceValue();
|
|
404
|
+
if (!v || anchorDate()) return false;
|
|
405
|
+
if (props.isDateUnavailable?.(v.start, null) || props.isDateUnavailable?.(v.end, null)) {
|
|
406
|
+
return true;
|
|
407
|
+
}
|
|
408
|
+
const min = access(props.minValue);
|
|
409
|
+
const max = access(props.maxValue);
|
|
410
|
+
const minCal = min != null ? toDisplayCalendarDate(min) : null;
|
|
411
|
+
const maxCal = max != null ? toDisplayCalendarDate(max) : null;
|
|
412
|
+
return (
|
|
413
|
+
isDateOutsideRange(toDisplayCalendarDate(v.start), minCal, maxCal) ||
|
|
414
|
+
isDateOutsideRange(toDisplayCalendarDate(v.end), minCal, maxCal)
|
|
415
|
+
);
|
|
416
|
+
});
|
|
417
|
+
const isValueInvalid = createMemo(() => validationState() === "invalid" || isInvalidSelection());
|
|
217
418
|
|
|
218
419
|
// Highlighted range during selection
|
|
219
420
|
const highlightedRange = createMemo<RangeValue<CalendarDate> | null>(() => {
|
|
@@ -222,8 +423,8 @@ export function createRangeCalendarState<T extends DateValue = CalendarDate>(
|
|
|
222
423
|
const v = value();
|
|
223
424
|
if (v) {
|
|
224
425
|
return {
|
|
225
|
-
start:
|
|
226
|
-
end:
|
|
426
|
+
start: toDisplayCalendarDate(v.start),
|
|
427
|
+
end: toDisplayCalendarDate(v.end),
|
|
227
428
|
};
|
|
228
429
|
}
|
|
229
430
|
return null;
|
|
@@ -236,28 +437,68 @@ export function createRangeCalendarState<T extends DateValue = CalendarDate>(
|
|
|
236
437
|
return { start: focused, end: anchor };
|
|
237
438
|
});
|
|
238
439
|
|
|
239
|
-
// Visible range based on
|
|
440
|
+
// Visible range based on React Stately's selection alignment rules.
|
|
240
441
|
const visibleRange = createMemo(() => {
|
|
241
|
-
const
|
|
242
|
-
|
|
243
|
-
|
|
442
|
+
const start = visibleRangeStart();
|
|
443
|
+
return { start, end: visibleRangeEndFromStart(start) };
|
|
444
|
+
});
|
|
244
445
|
|
|
245
|
-
|
|
246
|
-
|
|
446
|
+
createEffect(() => {
|
|
447
|
+
const controlledFocused = access(props.focusedValue);
|
|
448
|
+
if (!controlledFocused) {
|
|
449
|
+
return;
|
|
247
450
|
}
|
|
248
451
|
|
|
249
|
-
|
|
452
|
+
const nextFocusedDate = constrainDate(toDisplayCalendarDate(controlledFocused));
|
|
453
|
+
const currentFocusedDate = focusedDate();
|
|
454
|
+
|
|
455
|
+
if (
|
|
456
|
+
nextFocusedDate.compare(currentFocusedDate) !== 0 ||
|
|
457
|
+
!isEqualCalendar(nextFocusedDate.calendar, currentFocusedDate.calendar)
|
|
458
|
+
) {
|
|
459
|
+
setFocusedDateInternal(nextFocusedDate);
|
|
460
|
+
setVisibleRangeStart(alignVisibleRangeStart(nextFocusedDate));
|
|
461
|
+
}
|
|
462
|
+
});
|
|
463
|
+
|
|
464
|
+
createEffect(() => {
|
|
465
|
+
const controlledFocused = access(props.focusedValue);
|
|
466
|
+
if (controlledFocused) {
|
|
467
|
+
return;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
const currentFocusedDate = focusedDate();
|
|
471
|
+
const nextFocusedDate = constrainDate(currentFocusedDate);
|
|
472
|
+
|
|
473
|
+
if (
|
|
474
|
+
nextFocusedDate.compare(currentFocusedDate) === 0 &&
|
|
475
|
+
isEqualCalendar(nextFocusedDate.calendar, currentFocusedDate.calendar)
|
|
476
|
+
) {
|
|
477
|
+
return;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
setFocusedDateInternal(nextFocusedDate);
|
|
481
|
+
setVisibleRangeStart(alignVisibleRangeStart(nextFocusedDate));
|
|
482
|
+
props.onFocusChange?.(nextFocusedDate);
|
|
483
|
+
});
|
|
484
|
+
|
|
485
|
+
createEffect(() => {
|
|
486
|
+
const currentFocusedDate = focusedDate();
|
|
487
|
+
const nextFocusedDate = toDisplayCalendarDate(currentFocusedDate);
|
|
488
|
+
|
|
489
|
+
if (isEqualCalendar(currentFocusedDate.calendar, nextFocusedDate.calendar)) {
|
|
490
|
+
return;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
setFocusedDateInternal(nextFocusedDate);
|
|
494
|
+
setVisibleRangeStart(alignVisibleRangeStart(nextFocusedDate));
|
|
250
495
|
});
|
|
251
496
|
|
|
252
497
|
// Format week days for headers
|
|
253
498
|
const weekDays = createMemo(() => {
|
|
254
|
-
const formatter = new DateFormatter(locale, { weekday:
|
|
255
|
-
const startDay = props.firstDayOfWeek ?? 0;
|
|
499
|
+
const formatter = new DateFormatter(locale(), { weekday: "narrow" });
|
|
256
500
|
const days: string[] = [];
|
|
257
|
-
const
|
|
258
|
-
|
|
259
|
-
const dayOfWeek = getDayOfWeek(base, locale);
|
|
260
|
-
const weekStart = base.subtract({ days: (dayOfWeek - startDay + 7) % 7 });
|
|
501
|
+
const weekStart = startOfWeek(today(timeZone), locale(), firstDayOfWeekName());
|
|
261
502
|
|
|
262
503
|
for (let i = 0; i < 7; i++) {
|
|
263
504
|
const day = weekStart.add({ days: i });
|
|
@@ -269,42 +510,51 @@ export function createRangeCalendarState<T extends DateValue = CalendarDate>(
|
|
|
269
510
|
|
|
270
511
|
// Title (formatted month/year)
|
|
271
512
|
const title = createMemo(() => {
|
|
272
|
-
const formatter = new DateFormatter(locale, {
|
|
273
|
-
month:
|
|
274
|
-
year:
|
|
513
|
+
const formatter = new DateFormatter(locale(), {
|
|
514
|
+
month: "long",
|
|
515
|
+
year: "numeric",
|
|
516
|
+
calendar: focusedDate().calendar.identifier,
|
|
275
517
|
});
|
|
276
|
-
|
|
518
|
+
const date = focusedDate();
|
|
519
|
+
const formattableMonth = date.calendar.getFormattableMonth?.(date) ?? date;
|
|
520
|
+
return formatter.format(formattableMonth.toDate(timeZone));
|
|
277
521
|
});
|
|
278
522
|
|
|
279
523
|
// Set value with onChange callback
|
|
280
524
|
const setValue = (newValue: RangeValue<T> | null) => {
|
|
281
525
|
if (isDisabled() || isReadOnly()) return;
|
|
282
526
|
|
|
527
|
+
const oldValue = sourceValue();
|
|
528
|
+
const nextValue = newValue
|
|
529
|
+
? {
|
|
530
|
+
start: convertValue(
|
|
531
|
+
constrainDate(toDisplayCalendarDate(newValue.start)),
|
|
532
|
+
oldValue?.start,
|
|
533
|
+
) as T,
|
|
534
|
+
end: convertValue(constrainDate(toDisplayCalendarDate(newValue.end)), oldValue?.end) as T,
|
|
535
|
+
}
|
|
536
|
+
: null;
|
|
537
|
+
|
|
283
538
|
const controlled = access(props.value);
|
|
284
539
|
if (controlled === undefined) {
|
|
285
|
-
setInternalValue(() =>
|
|
540
|
+
setInternalValue(() => nextValue);
|
|
286
541
|
}
|
|
287
542
|
|
|
288
|
-
if (
|
|
289
|
-
props.onChange(
|
|
543
|
+
if (!Object.is(oldValue, nextValue) && props.onChange) {
|
|
544
|
+
props.onChange(nextValue);
|
|
290
545
|
}
|
|
291
546
|
};
|
|
292
547
|
|
|
293
548
|
// Set focused date with constraints
|
|
294
549
|
const setFocusedDate = (date: CalendarDate) => {
|
|
295
|
-
const
|
|
296
|
-
const maxValue = access(props.maxValue);
|
|
297
|
-
|
|
298
|
-
let constrained = date;
|
|
550
|
+
const constrained = constrainDate(date);
|
|
299
551
|
|
|
300
|
-
if (
|
|
301
|
-
|
|
302
|
-
}
|
|
303
|
-
if (maxValue && date.compare(toCalendarDate(maxValue)) > 0) {
|
|
304
|
-
constrained = toCalendarDate(maxValue);
|
|
552
|
+
if (Object.is(constrained, focusedDate())) {
|
|
553
|
+
return;
|
|
305
554
|
}
|
|
306
555
|
|
|
307
556
|
setFocusedDateInternal(constrained);
|
|
557
|
+
setVisibleRangeStart(alignVisibleRangeStart(constrained));
|
|
308
558
|
props.onFocusChange?.(constrained);
|
|
309
559
|
};
|
|
310
560
|
|
|
@@ -313,34 +563,32 @@ export function createRangeCalendarState<T extends DateValue = CalendarDate>(
|
|
|
313
563
|
const range = highlightedRange();
|
|
314
564
|
if (!range) return false;
|
|
315
565
|
|
|
316
|
-
const calDate =
|
|
317
|
-
return (
|
|
318
|
-
calDate.compare(range.start) >= 0 && calDate.compare(range.end) <= 0
|
|
319
|
-
);
|
|
566
|
+
const calDate = toDisplayCalendarDate(date);
|
|
567
|
+
return calDate.compare(range.start) >= 0 && calDate.compare(range.end) <= 0;
|
|
320
568
|
};
|
|
321
569
|
|
|
322
570
|
// Check if a date is the start of the selection
|
|
323
571
|
const isSelectionStart = (date: DateValue): boolean => {
|
|
324
572
|
const range = highlightedRange();
|
|
325
573
|
if (!range) return false;
|
|
326
|
-
return isSameDay(
|
|
574
|
+
return isSameDay(toDisplayCalendarDate(date), range.start);
|
|
327
575
|
};
|
|
328
576
|
|
|
329
577
|
// Check if a date is the end of the selection
|
|
330
578
|
const isSelectionEnd = (date: DateValue): boolean => {
|
|
331
579
|
const range = highlightedRange();
|
|
332
580
|
if (!range) return false;
|
|
333
|
-
return isSameDay(
|
|
581
|
+
return isSameDay(toDisplayCalendarDate(date), range.end);
|
|
334
582
|
};
|
|
335
583
|
|
|
336
584
|
// Check if a date is focused
|
|
337
585
|
const isCellFocused = (date: DateValue): boolean => {
|
|
338
|
-
return isSameDay(
|
|
586
|
+
return isSameDay(toDisplayCalendarDate(date), focusedDate());
|
|
339
587
|
};
|
|
340
588
|
|
|
341
589
|
// Check if a date is unavailable
|
|
342
590
|
const isCellUnavailable = (date: DateValue): boolean => {
|
|
343
|
-
return props.isDateUnavailable?.(date) ?? false;
|
|
591
|
+
return props.isDateUnavailable?.(date, anchorDate()) ?? false;
|
|
344
592
|
};
|
|
345
593
|
|
|
346
594
|
// Check if a date is disabled
|
|
@@ -348,12 +596,23 @@ export function createRangeCalendarState<T extends DateValue = CalendarDate>(
|
|
|
348
596
|
if (isDisabled()) return true;
|
|
349
597
|
if (props.isDateDisabled?.(date)) return true;
|
|
350
598
|
|
|
351
|
-
const
|
|
352
|
-
const
|
|
353
|
-
const calDate =
|
|
599
|
+
const min = effectiveMinValue();
|
|
600
|
+
const max = effectiveMaxValue();
|
|
601
|
+
const calDate = toDisplayCalendarDate(date);
|
|
354
602
|
|
|
355
|
-
if (
|
|
356
|
-
if (
|
|
603
|
+
if (min && calDate.compare(min) < 0) return true;
|
|
604
|
+
if (max && calDate.compare(max) > 0) return true;
|
|
605
|
+
|
|
606
|
+
return false;
|
|
607
|
+
};
|
|
608
|
+
|
|
609
|
+
const isDateOutsideAllowedRange = (date: DateValue): boolean => {
|
|
610
|
+
const min = effectiveMinValue();
|
|
611
|
+
const max = effectiveMaxValue();
|
|
612
|
+
const calDate = toDisplayCalendarDate(date);
|
|
613
|
+
|
|
614
|
+
if (min && calDate.compare(min) < 0) return true;
|
|
615
|
+
if (max && calDate.compare(max) > 0) return true;
|
|
357
616
|
|
|
358
617
|
return false;
|
|
359
618
|
};
|
|
@@ -361,7 +620,7 @@ export function createRangeCalendarState<T extends DateValue = CalendarDate>(
|
|
|
361
620
|
// Check if a date is outside the visible range
|
|
362
621
|
const isOutsideVisibleRange = (date: DateValue): boolean => {
|
|
363
622
|
const range = visibleRange();
|
|
364
|
-
const calDate =
|
|
623
|
+
const calDate = toDisplayCalendarDate(date);
|
|
365
624
|
return !isSameMonth(calDate, range.start) && !isSameMonth(calDate, range.end);
|
|
366
625
|
};
|
|
367
626
|
|
|
@@ -372,11 +631,13 @@ export function createRangeCalendarState<T extends DateValue = CalendarDate>(
|
|
|
372
631
|
|
|
373
632
|
// Navigation methods
|
|
374
633
|
const focusPreviousPage = () => {
|
|
375
|
-
|
|
634
|
+
const pageMonths = access(props.pageBehavior) === "single" ? 1 : visibleMonths;
|
|
635
|
+
setFocusedDate(focusedDate().subtract({ months: pageMonths }));
|
|
376
636
|
};
|
|
377
637
|
|
|
378
638
|
const focusNextPage = () => {
|
|
379
|
-
|
|
639
|
+
const pageMonths = access(props.pageBehavior) === "single" ? 1 : visibleMonths;
|
|
640
|
+
setFocusedDate(focusedDate().add({ months: pageMonths }));
|
|
380
641
|
};
|
|
381
642
|
|
|
382
643
|
const focusPreviousSection = () => {
|
|
@@ -387,6 +648,18 @@ export function createRangeCalendarState<T extends DateValue = CalendarDate>(
|
|
|
387
648
|
setFocusedDate(focusedDate().add({ years: 1 }));
|
|
388
649
|
};
|
|
389
650
|
|
|
651
|
+
const isPreviousVisibleRangeInvalid = () => {
|
|
652
|
+
const start = visibleRange().start;
|
|
653
|
+
const previous = start.subtract({ days: 1 });
|
|
654
|
+
return isSameDay(previous, start) || isDateOutsideAllowedRange(previous);
|
|
655
|
+
};
|
|
656
|
+
|
|
657
|
+
const isNextVisibleRangeInvalid = () => {
|
|
658
|
+
const end = visibleRange().end;
|
|
659
|
+
const next = end.add({ days: 1 });
|
|
660
|
+
return isSameDay(next, end) || isDateOutsideAllowedRange(next);
|
|
661
|
+
};
|
|
662
|
+
|
|
390
663
|
const focusPreviousDay = () => {
|
|
391
664
|
setFocusedDate(focusedDate().subtract({ days: 1 }));
|
|
392
665
|
};
|
|
@@ -454,12 +727,12 @@ export function createRangeCalendarState<T extends DateValue = CalendarDate>(
|
|
|
454
727
|
// Get dates in a specific week
|
|
455
728
|
const getDatesInWeek = (
|
|
456
729
|
weekIndex: number,
|
|
457
|
-
monthStartDate?: CalendarDate
|
|
730
|
+
monthStartDate?: CalendarDate,
|
|
458
731
|
): (CalendarDate | null)[] => {
|
|
459
732
|
const startDate = monthStartDate ?? visibleRange().start;
|
|
460
733
|
|
|
461
734
|
const monthStart = startOfMonth(startDate);
|
|
462
|
-
const weekStart = startOfWeek(monthStart, locale);
|
|
735
|
+
const weekStart = startOfWeek(monthStart, locale(), firstDayOfWeekName());
|
|
463
736
|
|
|
464
737
|
const week: (CalendarDate | null)[] = [];
|
|
465
738
|
const firstDayOfWeek = weekStart.add({ weeks: weekIndex });
|
|
@@ -475,7 +748,7 @@ export function createRangeCalendarState<T extends DateValue = CalendarDate>(
|
|
|
475
748
|
// Get number of weeks in a month
|
|
476
749
|
const getWeeksInMonthFn = (date?: CalendarDate): number => {
|
|
477
750
|
const monthDate = date ?? focusedDate();
|
|
478
|
-
return getWeeksInMonth(monthDate, locale);
|
|
751
|
+
return getWeeksInMonth(monthDate, locale(), firstDayOfWeekName());
|
|
479
752
|
};
|
|
480
753
|
|
|
481
754
|
return {
|
|
@@ -490,7 +763,12 @@ export function createRangeCalendarState<T extends DateValue = CalendarDate>(
|
|
|
490
763
|
isReadOnly,
|
|
491
764
|
visibleRange,
|
|
492
765
|
timeZone,
|
|
766
|
+
locale,
|
|
767
|
+
minValue: minValueState,
|
|
768
|
+
maxValue: maxValueState,
|
|
769
|
+
firstDayOfWeek: firstDayOfWeekName,
|
|
493
770
|
validationState,
|
|
771
|
+
isValueInvalid,
|
|
494
772
|
isSelected,
|
|
495
773
|
isSelectionStart,
|
|
496
774
|
isSelectionEnd,
|
|
@@ -503,6 +781,8 @@ export function createRangeCalendarState<T extends DateValue = CalendarDate>(
|
|
|
503
781
|
focusNextPage,
|
|
504
782
|
focusPreviousSection,
|
|
505
783
|
focusNextSection,
|
|
784
|
+
isPreviousVisibleRangeInvalid,
|
|
785
|
+
isNextVisibleRangeInvalid,
|
|
506
786
|
focusPreviousDay,
|
|
507
787
|
focusNextDay,
|
|
508
788
|
focusPreviousWeek,
|
|
@@ -523,13 +803,59 @@ export function createRangeCalendarState<T extends DateValue = CalendarDate>(
|
|
|
523
803
|
};
|
|
524
804
|
}
|
|
525
805
|
|
|
526
|
-
// ============================================
|
|
527
|
-
// HELPER FUNCTIONS
|
|
528
|
-
// ============================================
|
|
529
|
-
|
|
530
806
|
/**
|
|
531
807
|
* Converts a DateValue to a CalendarDate.
|
|
532
808
|
*/
|
|
533
809
|
function toCalendarDate(date: DateValue): CalendarDate {
|
|
534
810
|
return intlToCalendarDate(date);
|
|
535
811
|
}
|
|
812
|
+
|
|
813
|
+
function convertValue(newValue: CalendarDate, oldValue?: DateValue | null): DateValue {
|
|
814
|
+
const localValue = intlToCalendar(newValue, oldValue?.calendar ?? new GregorianCalendar());
|
|
815
|
+
|
|
816
|
+
if (oldValue && "hour" in oldValue) {
|
|
817
|
+
return oldValue.set(localValue);
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
return localValue;
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
/** Whether a date falls outside the closed [minValue, maxValue] interval. */
|
|
824
|
+
function isDateOutsideRange(
|
|
825
|
+
date: CalendarDate,
|
|
826
|
+
minValue?: CalendarDate | null,
|
|
827
|
+
maxValue?: CalendarDate | null,
|
|
828
|
+
): boolean {
|
|
829
|
+
return (
|
|
830
|
+
(minValue != null && date.compare(minValue) < 0) ||
|
|
831
|
+
(maxValue != null && date.compare(maxValue) > 0)
|
|
832
|
+
);
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
/**
|
|
836
|
+
* Walks outward from the anchor one day at a time in `dir` until it reaches an
|
|
837
|
+
* unavailable date, then returns the last available date before it — or
|
|
838
|
+
* undefined when no unavailable date is found within `visibleMonths` of the
|
|
839
|
+
* anchor (no narrowing needed on that side). Mirrors @react-stately/calendar
|
|
840
|
+
* nextUnavailableDate.
|
|
841
|
+
*/
|
|
842
|
+
function nextUnavailableDate(
|
|
843
|
+
anchorDate: CalendarDate,
|
|
844
|
+
isDateUnavailable: (date: CalendarDate) => boolean,
|
|
845
|
+
visibleMonths: number,
|
|
846
|
+
dir: number,
|
|
847
|
+
): CalendarDate | undefined {
|
|
848
|
+
let nextDate = anchorDate.add({ days: dir });
|
|
849
|
+
const minBound = anchorDate.subtract({ months: visibleMonths });
|
|
850
|
+
const maxBound = anchorDate.add({ months: visibleMonths });
|
|
851
|
+
while (
|
|
852
|
+
(dir < 0 ? nextDate.compare(minBound) >= 0 : nextDate.compare(maxBound) <= 0) &&
|
|
853
|
+
!isDateUnavailable(nextDate)
|
|
854
|
+
) {
|
|
855
|
+
nextDate = nextDate.add({ days: dir });
|
|
856
|
+
}
|
|
857
|
+
if (isDateUnavailable(nextDate)) {
|
|
858
|
+
return nextDate.add({ days: -dir });
|
|
859
|
+
}
|
|
860
|
+
return undefined;
|
|
861
|
+
}
|