@stenajs-webui/calendar 23.23.1 → 24.0.1

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.
@@ -4,10 +4,10 @@ import { ValueAndOnValueChangeProps } from "@stenajs-webui/forms";
4
4
  import { TravelCalendarSizeVariant } from "../../../features/travel-calendar/components/TravelCalendar";
5
5
  import { DateTextInputVariant, RenderTravelDateContent } from "../../../features/travel-calendar/types";
6
6
  import { SupportedLocaleCode } from "../../../features/localize-date-format/LocaleMapper";
7
- export interface TravelDateCalendarProps extends ValueAndOnValueChangeProps<string> {
7
+ import { VisibleMonthState } from "../../../features/travel-calendar/hooks/UseVisibleMonthState";
8
+ export interface TravelDateCalendarProps extends ValueAndOnValueChangeProps<string>, VisibleMonthState {
8
9
  localeCode?: SupportedLocaleCode;
9
10
  languageLocaleCode?: SupportedLocaleCode;
10
- initialMonthInFocus?: Date;
11
11
  label?: string;
12
12
  previousMonthButtonAriaLabel?: string;
13
13
  nextMonthButtonAriaLabel?: string;
@@ -22,13 +22,6 @@ export interface TravelDateCalendarProps extends ValueAndOnValueChangeProps<stri
22
22
  * navigation, stays the calendar's. See RenderTravelDateContent.
23
23
  */
24
24
  renderDateContent?: RenderTravelDateContent;
25
- /**
26
- * Called with the first day of the month the calendar shows: once when the
27
- * component mounts, and again each time the user moves to another month.
28
- * For loading data one month at a time, such as prices for
29
- * renderDateContent.
30
- */
31
- onVisibleMonthChange?: (visibleMonth: Date) => void;
32
25
  prevMonthDisabled?: (visibleMonth: Date) => boolean;
33
26
  nextMonthDisabled?: (visibleMonth: Date) => boolean;
34
27
  size?: TravelCalendarSizeVariant;
@@ -3,10 +3,10 @@ import { ValueAndOnValueChangeProps } from "@stenajs-webui/forms";
3
3
  import { TravelCalendarSizeVariant } from "../../../features/travel-calendar/components/TravelCalendar";
4
4
  import { SupportedLocaleCode } from "../../../features/localize-date-format/LocaleMapper";
5
5
  import { RenderTravelDateContent } from "../../../features/travel-calendar/types";
6
- export interface TravelDateCalendarPanelProps extends ValueAndOnValueChangeProps<string> {
6
+ import { VisibleMonthState } from "../../../features/travel-calendar/hooks/UseVisibleMonthState";
7
+ export interface TravelDateCalendarPanelProps extends ValueAndOnValueChangeProps<string>, VisibleMonthState {
7
8
  localeCode?: SupportedLocaleCode;
8
9
  languageLocaleCode?: SupportedLocaleCode;
9
- initialMonthInFocus?: Date;
10
10
  previousMonthButtonAriaLabel?: string;
11
11
  nextMonthButtonAriaLabel?: string;
12
12
  firstMonthInMonthPicker?: Date;
@@ -18,13 +18,6 @@ export interface TravelDateCalendarPanelProps extends ValueAndOnValueChangeProps
18
18
  * navigation, stays the calendar's. See RenderTravelDateContent.
19
19
  */
20
20
  renderDateContent?: RenderTravelDateContent;
21
- /**
22
- * Called with the first day of the month the calendar shows: once when the
23
- * component mounts, and again each time the user moves to another month.
24
- * For loading data one month at a time, such as prices for
25
- * renderDateContent.
26
- */
27
- onVisibleMonthChange?: (visibleMonth: Date) => void;
28
21
  prevMonthDisabled?: (visibleMonth: Date) => boolean;
29
22
  nextMonthDisabled?: (visibleMonth: Date) => boolean;
30
23
  size?: TravelCalendarSizeVariant;
@@ -4,10 +4,10 @@ import { ValueAndOnValueChangeProps } from "@stenajs-webui/forms";
4
4
  import { TravelCalendarSizeVariant } from "../../../features/travel-calendar/components/TravelCalendar";
5
5
  import { DateTextInputVariant, TravelDateRangeInputValue, RenderTravelDateContent } from "../../../features/travel-calendar/types";
6
6
  import { SupportedLocaleCode } from "../../../features/localize-date-format/LocaleMapper";
7
- export interface TravelDateRangeCalendarProps extends ValueAndOnValueChangeProps<TravelDateRangeInputValue> {
7
+ import { VisibleMonthState } from "../../../features/travel-calendar/hooks/UseVisibleMonthState";
8
+ export interface TravelDateRangeCalendarProps extends ValueAndOnValueChangeProps<TravelDateRangeInputValue>, VisibleMonthState {
8
9
  localeCode?: SupportedLocaleCode;
9
10
  languageLocaleCode?: SupportedLocaleCode;
10
- initialMonthInFocus?: Date;
11
11
  startDateLabel?: string;
12
12
  endDateLabel?: string;
13
13
  previousMonthButtonAriaLabel?: string;
@@ -23,13 +23,6 @@ export interface TravelDateRangeCalendarProps extends ValueAndOnValueChangeProps
23
23
  * navigation, stays the calendar's. See RenderTravelDateContent.
24
24
  */
25
25
  renderDateContent?: RenderTravelDateContent;
26
- /**
27
- * Called with the first day of the month the calendar shows: once when the
28
- * component mounts, and again each time the user moves to another month.
29
- * For loading data one month at a time, such as prices for
30
- * renderDateContent.
31
- */
32
- onVisibleMonthChange?: (visibleMonth: Date) => void;
33
26
  prevMonthDisabled?: (visibleMonth: Date) => boolean;
34
27
  nextMonthDisabled?: (visibleMonth: Date) => boolean;
35
28
  size?: TravelCalendarSizeVariant;
@@ -5,13 +5,13 @@ import { ValueAndOnValueChangeProps } from "@stenajs-webui/forms";
5
5
  import { TravelCalendarSizeVariant } from "../../../features/travel-calendar/components/TravelCalendar";
6
6
  import { DateTextInputVariant, RenderTravelDateContent } from "../../../features/travel-calendar/types";
7
7
  import { SupportedLocaleCode } from "../../../features/localize-date-format/LocaleMapper";
8
+ import { VisibleMonthState } from "../../../features/travel-calendar/hooks/UseVisibleMonthState";
8
9
  export interface RenderBelowSingleDateCalendarArgs {
9
10
  hideCalendar: () => void;
10
11
  }
11
- export interface TravelDateInputProps extends ValueAndOnValueChangeProps<string> {
12
+ export interface TravelDateInputProps extends ValueAndOnValueChangeProps<string>, VisibleMonthState {
12
13
  localeCode?: SupportedLocaleCode;
13
14
  languageLocaleCode?: SupportedLocaleCode;
14
- initialMonthInFocus?: Date;
15
15
  label?: string;
16
16
  previousMonthButtonAriaLabel?: string;
17
17
  nextMonthButtonAriaLabel?: string;
@@ -26,13 +26,6 @@ export interface TravelDateInputProps extends ValueAndOnValueChangeProps<string>
26
26
  * navigation, stays the calendar's. See RenderTravelDateContent.
27
27
  */
28
28
  renderDateContent?: RenderTravelDateContent;
29
- /**
30
- * Called with the first day of the month the calendar shows: once when the
31
- * component mounts, and again each time the user moves to another month.
32
- * For loading data one month at a time, such as prices for
33
- * renderDateContent.
34
- */
35
- onVisibleMonthChange?: (visibleMonth: Date) => void;
36
29
  prevMonthDisabled?: (visibleMonth: Date) => boolean;
37
30
  nextMonthDisabled?: (visibleMonth: Date) => boolean;
38
31
  zIndex?: number;
@@ -5,13 +5,13 @@ import { ValueAndOnValueChangeProps } from "@stenajs-webui/forms";
5
5
  import { TravelCalendarSizeVariant } from "../../../features/travel-calendar/components/TravelCalendar";
6
6
  import { DateTextInputVariant, TravelDateRangeInputValue, RenderTravelDateContent } from "../../../features/travel-calendar/types";
7
7
  import { SupportedLocaleCode } from "../../../features/localize-date-format/LocaleMapper";
8
+ import { VisibleMonthState } from "../../../features/travel-calendar/hooks/UseVisibleMonthState";
8
9
  export interface RenderBelowCalendarArgs {
9
10
  hideCalendar: () => void;
10
11
  }
11
- export interface TravelDateRangeInputProps extends ValueAndOnValueChangeProps<TravelDateRangeInputValue> {
12
+ export interface TravelDateRangeInputProps extends ValueAndOnValueChangeProps<TravelDateRangeInputValue>, VisibleMonthState {
12
13
  localeCode?: SupportedLocaleCode;
13
14
  languageLocaleCode?: SupportedLocaleCode;
14
- initialMonthInFocus?: Date;
15
15
  startDateLabel?: string;
16
16
  endDateLabel?: string;
17
17
  previousMonthButtonAriaLabel?: string;
@@ -27,13 +27,6 @@ export interface TravelDateRangeInputProps extends ValueAndOnValueChangeProps<Tr
27
27
  * navigation, stays the calendar's. See RenderTravelDateContent.
28
28
  */
29
29
  renderDateContent?: RenderTravelDateContent;
30
- /**
31
- * Called with the first day of the month the calendar shows: once when the
32
- * component mounts, and again each time the user moves to another month.
33
- * For loading data one month at a time, such as prices for
34
- * renderDateContent.
35
- */
36
- onVisibleMonthChange?: (visibleMonth: Date) => void;
37
30
  prevMonthDisabled?: (visibleMonth: Date) => boolean;
38
31
  nextMonthDisabled?: (visibleMonth: Date) => boolean;
39
32
  zIndex?: number;
@@ -10,6 +10,8 @@ export interface TravelDateCellProps {
10
10
  selectedEndDate: Date | undefined;
11
11
  isValidDateRange: boolean;
12
12
  onChangeVisibleMonth: (visibleMonth: Date) => void;
13
+ /** Focuses the date once its month is shown, which the app decides. */
14
+ focusDateWhenShown: (date: Date) => void;
13
15
  onStartHover: (date: Date) => void;
14
16
  onEndHover: (date: Date) => void;
15
17
  /** Current date that the mouse is hovering over. */
@@ -0,0 +1,16 @@
1
+ import * as React from "react";
2
+ import { VisibleMonthState } from "../../hooks/UseVisibleMonthState";
3
+ /**
4
+ * The travel calendars with their visible month kept in state, the way an app
5
+ * using useVisibleMonthState would. For tests that are about something else,
6
+ * and still need the month buttons and the arrow keys to move the calendar.
7
+ */
8
+ type WithMonthState<P> = Omit<P, keyof VisibleMonthState> & {
9
+ initialMonth?: Date;
10
+ };
11
+ export declare const StatefulTravelDateInput: ({ initialMonth, ...props }: WithMonthState<import("../../../..").TravelDateInputProps>) => React.JSX.Element;
12
+ export declare const StatefulTravelDateRangeInput: ({ initialMonth, ...props }: WithMonthState<import("../../../..").TravelDateRangeInputProps>) => React.JSX.Element;
13
+ export declare const StatefulTravelDateCalendar: ({ initialMonth, ...props }: WithMonthState<import("../../../..").TravelDateCalendarProps>) => React.JSX.Element;
14
+ export declare const StatefulTravelDateCalendarPanel: ({ initialMonth, ...props }: WithMonthState<import("../../../..").TravelDateCalendarPanelProps>) => React.JSX.Element;
15
+ export declare const StatefulTravelDateRangeCalendar: ({ initialMonth, ...props }: WithMonthState<import("../../../..").TravelDateRangeCalendarProps>) => React.JSX.Element;
16
+ export {};
@@ -0,0 +1,26 @@
1
+ /**
2
+ * The visible month of a travel calendar, which the app owns.
3
+ *
4
+ * The calendar never changes the month itself. Everything that would move it,
5
+ * the month buttons, the month picker, the arrow keys and a new value, asks the
6
+ * app instead, through onVisibleMonthChange.
7
+ *
8
+ * The request is always the first day of the month. The arrow keys and the
9
+ * month picker work with other days of the month, and the app should get the
10
+ * same month in the same form whichever way it was reached.
11
+ *
12
+ * A request for the month that is already visible is not made at all. The
13
+ * check is against the month of the last render, so two requests made before
14
+ * the app has passed a month back, such as two quick clicks while it loads,
15
+ * both reach it, the same way two quick edits reach any controlled input
16
+ * that updates later. The second request is left to the app rather than
17
+ * dropped here, since the app may not have taken the first.
18
+ *
19
+ * The month is kept by year and month, so an app that passes a new Date on
20
+ * every render, such as new Date(), does not rebuild the calendar each time.
21
+ */
22
+ export declare const useControlledVisibleMonth: (visibleMonthValue: Date, onVisibleMonthChange: (visibleMonth: Date) => void, today: Date) => {
23
+ visibleMonth: Date;
24
+ setVisibleMonth: (requested: Date) => void;
25
+ setVisibleMonthClamped: (requested: Date) => void;
26
+ };
@@ -1,6 +1,6 @@
1
1
  import { SupportedLocaleCode } from "../../localize-date-format/LocaleMapper";
2
2
  import { VisiblePanel } from "../types";
3
- export declare const useTravelDateInput: (value: string | undefined, onValueChange: ((value: string) => void) | undefined, localeCode: SupportedLocaleCode, languageLocaleCode: SupportedLocaleCode, initialMonthInFocus: Date | undefined, onClickDate?: (date: Date) => void) => {
3
+ export declare const useTravelDateInput: (value: string | undefined, onValueChange: ((value: string) => void) | undefined, localeCode: SupportedLocaleCode, languageLocaleCode: SupportedLocaleCode, visibleMonthValue: Date, onVisibleMonthChange: (visibleMonth: Date) => void, onClickDate?: (date: Date) => void) => {
4
4
  onClickDate: (date: Date) => void;
5
5
  onValueChangeByInputs: (value: string) => void;
6
6
  monthPickerButtonRef: import("react").RefObject<HTMLButtonElement | null>;
@@ -8,7 +8,7 @@ export declare const useTravelDateInput: (value: string | undefined, onValueChan
8
8
  monthPickerButtonLabel: string;
9
9
  visiblePanel: VisiblePanel;
10
10
  setVisiblePanel: import("react").Dispatch<import("react").SetStateAction<VisiblePanel>>;
11
- setVisibleMonth: import("react").Dispatch<import("react").SetStateAction<Date>>;
11
+ setVisibleMonth: (requested: Date) => void;
12
12
  visibleMonthData: import("../../..").MonthData;
13
13
  todayIsInVisibleMonth: boolean;
14
14
  hoverDate: Date | undefined;
@@ -1,6 +1,6 @@
1
1
  import { SupportedLocaleCode } from "../../localize-date-format/LocaleMapper";
2
2
  import { TravelDateRangeInputValue, VisiblePanel } from "../types";
3
- export declare const useTravelDateRangeInput: (value: TravelDateRangeInputValue | undefined, onValueChange: ((value: TravelDateRangeInputValue) => void) | undefined, localeCode: SupportedLocaleCode, languageLocaleCode: SupportedLocaleCode, initialMonthInFocus: Date | undefined, fieldLastInFocus: "startDate" | "endDate" | undefined, setFieldLastInFocus: (field: "startDate" | "endDate" | undefined) => void, moveFocusToField: (field: "startDate" | "endDate") => void, hideCalendar: undefined | (() => void)) => {
3
+ export declare const useTravelDateRangeInput: (value: TravelDateRangeInputValue | undefined, onValueChange: ((value: TravelDateRangeInputValue) => void) | undefined, localeCode: SupportedLocaleCode, languageLocaleCode: SupportedLocaleCode, visibleMonthValue: Date, onVisibleMonthChange: (visibleMonth: Date) => void, fieldLastInFocus: "startDate" | "endDate" | undefined, setFieldLastInFocus: (field: "startDate" | "endDate" | undefined) => void, moveFocusToField: (field: "startDate" | "endDate") => void, hideCalendar: undefined | (() => void)) => {
4
4
  onClickDate: (date: Date) => void;
5
5
  onValueChangeByInputs: (value: TravelDateRangeInputValue) => void;
6
6
  isValidDateRange: boolean;
@@ -9,7 +9,7 @@ export declare const useTravelDateRangeInput: (value: TravelDateRangeInputValue
9
9
  monthPickerButtonLabel: string;
10
10
  visiblePanel: VisiblePanel;
11
11
  setVisiblePanel: import("react").Dispatch<import("react").SetStateAction<VisiblePanel>>;
12
- setVisibleMonth: import("react").Dispatch<import("react").SetStateAction<Date>>;
12
+ setVisibleMonth: (requested: Date) => void;
13
13
  visibleMonthData: import("../../..").MonthData;
14
14
  todayIsInVisibleMonth: boolean;
15
15
  hoverDate: Date | undefined;
@@ -0,0 +1,89 @@
1
+ /**
2
+ * The visible month of a travel calendar, which the app keeps. All five
3
+ * travel calendars take these two props, and useVisibleMonthState returns
4
+ * them, ready to spread onto one.
5
+ */
6
+ export interface VisibleMonthState {
7
+ /**
8
+ * The month the calendar shows. Only its year and month are read, so any
9
+ * day of the month will do. The calendar never changes it by itself; see
10
+ * onVisibleMonthChange.
11
+ *
12
+ * It is not taken from the value. To open on the month of the value, pass
13
+ * that month; useVisibleMonthState shows how.
14
+ */
15
+ visibleMonth: Date;
16
+ /**
17
+ * Called when the calendar should show another month: from the month
18
+ * buttons, the month picker, the arrow keys, or a date in another month
19
+ * that the calendar follows. The single date calendars follow their value,
20
+ * typed or set from outside. The range calendars only follow a date typed
21
+ * into one of their fields.
22
+ *
23
+ * It gets the first day of that month, and the calendar moves once
24
+ * visibleMonth is set to it. It is not called for the month already shown.
25
+ *
26
+ * When it follows a date, it never asks for a month before the current
27
+ * one: a date in the past asks for the current month, unless it is in the
28
+ * month already shown. The month buttons and the arrow keys are not
29
+ * limited that way. To keep the user out of past months, set
30
+ * prevMonthDisabled and isDateDisabled, for example with
31
+ * usePrevMonthDisabledBeforeToday() and useDisabledBeforeToday(). The
32
+ * month picker starts at firstMonthInMonthPicker, the current month by
33
+ * default.
34
+ *
35
+ * useVisibleMonthState() keeps this state, for when nothing else needs it.
36
+ */
37
+ onVisibleMonthChange: (visibleMonth: Date) => void;
38
+ }
39
+ /**
40
+ * Keeps the visible month of a travel calendar, for when nothing else needs to
41
+ * know it.
42
+ *
43
+ * The travel calendars do not keep their visible month themselves; the app
44
+ * passes it in as visibleMonth and changes it in onVisibleMonthChange. This is
45
+ * that state, returned as those two props, so it can be spread onto the
46
+ * calendar. Keep the state elsewhere instead when something else depends on
47
+ * the month, such as data loaded for it.
48
+ *
49
+ * It starts at the current month, not at the month of a value. Before the
50
+ * month was controlled, a calendar without initialMonthInFocus opened on the
51
+ * month of its value. To keep that, start from the value's date, with a
52
+ * function, the way useState takes one:
53
+ *
54
+ * const visibleMonthState = useVisibleMonthState(() =>
55
+ * value ? parseLocalizedDateString(value, localeCode) : undefined,
56
+ * );
57
+ *
58
+ * The function only runs on the first render, so the value is not parsed
59
+ * again on every render after it, and the check for a missing value keeps
60
+ * it type safe for an optional value. parseLocalizedDateString returns
61
+ * undefined for an incomplete value as well, and undefined falls back to
62
+ * the current month.
63
+ *
64
+ * The value of a range calendar is an object of two strings, so parse its
65
+ * start date instead:
66
+ *
67
+ * const visibleMonthState = useVisibleMonthState(() =>
68
+ * value?.startDate
69
+ * ? parseLocalizedDateString(value.startDate, localeCode)
70
+ * : undefined,
71
+ * );
72
+ *
73
+ * A calendar that had initialMonthInFocus passes that month instead:
74
+ * useVisibleMonthState(initialMonthInFocus).
75
+ *
76
+ * @param initialMonth The month to show first, or a function that returns it,
77
+ * called once. Defaults to the current month, also when it is or returns
78
+ * undefined.
79
+ *
80
+ * @example
81
+ * const visibleMonthState = useVisibleMonthState();
82
+ *
83
+ * <TravelDateInput
84
+ * value={value}
85
+ * onValueChange={setValue}
86
+ * {...visibleMonthState}
87
+ * />
88
+ */
89
+ export declare const useVisibleMonthState: (initialMonth?: Date | (() => Date | undefined)) => VisibleMonthState;
package/dist/index.d.ts CHANGED
@@ -35,6 +35,7 @@ export * from "./components/calendar-types/travel-date-calendar-panel/TravelDate
35
35
  export type { TravelCalendarSizeVariant } from "./features/travel-calendar/components/TravelCalendar";
36
36
  export * from "./components/calendar-types/travel-date-range-calendar/TravelDateRangeCalendar";
37
37
  export * from "./features/travel-calendar/hooks/DisabledDateHooks";
38
+ export * from "./features/travel-calendar/hooks/UseVisibleMonthState";
38
39
  export * from "./features/travel-calendar/types";
39
40
  export * from "./features/localize-date-format/LocalizedDateFormatter";
40
41
  export * from "./features/localize-date-format/LocalizedDateParser";