@gnome-ui/react-native 1.12.0 → 1.13.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 CHANGED
@@ -3005,6 +3005,216 @@ purely so the control can brighten on hover, and touch has no hover.
3005
3005
  as `BottomTabBar`'s `bottomInset` — this package takes no dependency on
3006
3006
  `react-native-safe-area-context` itself.
3007
3007
 
3008
+ ### Calendar
3009
+
3010
+ ```tsx
3011
+ import { Calendar } from '@gnome-ui/react-native';
3012
+
3013
+ const [value, setValue] = useState<Date | null>(null);
3014
+
3015
+ <Calendar value={value} onChange={setValue} />
3016
+
3017
+ <Calendar
3018
+ min={new Date(2024, 0, 1)}
3019
+ max={new Date(2024, 11, 31)}
3020
+ showWeekNumbers
3021
+ weekStartsOn={0}
3022
+ />
3023
+ ```
3024
+
3025
+ Month-grid date display — mirrors `GtkCalendar` and `@gnome-ui/react`'s own
3026
+ `Calendar`, ported from its `Calendar`+`CalendarBase`+`calendarUtils.ts`
3027
+ split. `calendarUtils.ts`'s pure date-math helpers port verbatim (zero DOM
3028
+ dependency on the web side already), the same `fileType.ts`/
3029
+ `coachMarkUtils.ts` precedent for dependency-free logic not worth a shared
3030
+ package.
3031
+
3032
+ **The web version's entire keyboard layer drops here** — roving tabindex,
3033
+ arrow keys, PageUp/Down, Home/End, Enter/Space are all gone, replaced by
3034
+ plain tap-to-select on every day/month/year cell. Unlike `Slider`'s 1D
3035
+ `accessibilityRole="adjustable"` fallback, a full 2D date grid has no
3036
+ screen-reader analog to page through, so tap-to-select is the strict touch
3037
+ subset of the web interaction — the same trade-off `RatingStars`/
3038
+ `ToggleGroup`/`ColorPicker` already made. The heading label still cycles
3039
+ day grid → month grid → year grid on tap, exactly as on the web, so a
3040
+ distant year is two taps away rather than many pages of month navigation.
3041
+
3042
+ `role="grid"`/`"row"` port 1:1 from RN's web-aligned `Role` union;
3043
+ `"gridcell"` isn't in that union, so day/month/year cells fall back to
3044
+ `"cell"` — the same kind of substitution `BoxedList` (`"list"`) and
3045
+ `ComboRow` (`"listbox"` → `"list"`) already made. The grid and its rows
3046
+ deliberately skip `accessible`, the `ToggleGroup`-corrected pattern: with
3047
+ many independently-focusable day cells inside, setting it would collapse
3048
+ the whole month into one VoiceOver stop. Day-name and week-number headers
3049
+ hold no interactive children, so they do set `accessible` to be announced
3050
+ as a single unit.
3051
+
3052
+ `visibleMonths` (side-by-side month panels) and `autoFocus`
3053
+ (keyboard-focus-on-mount) are dropped outright, not merely unimplemented —
3054
+ both are desktop/keyboard concerns with no honest phone-width or
3055
+ no-keyboard counterpart. `locale` is dropped too, in favor of the app-wide
3056
+ locale `GnomeProvider` already exposes through `useDateTimeFormatter` —
3057
+ `Calendar` is the first component in this package to seriously exercise
3058
+ that hook, and every `Intl.DateTimeFormatOptions` object it passes is
3059
+ hoisted to module scope so the hook's own `useMemo`-over-`options` identity
3060
+ check actually holds across renders. Day and drill-down cells are equal-
3061
+ width flex rows (7 columns for days, matching `getCalendarWeeks`'s fixed
3062
+ 6-row output; 4 columns for the 12-cell month/year grids) rather than a
3063
+ CSS Grid port, since Yoga has no grid layout at all.
3064
+
3065
+ No `CalendarBase` split was extracted ahead of need — it was pulled out of
3066
+ this component's grid/navigation logic once `CalendarRange` actually
3067
+ shipped and needed it (see below), not carried as unused abstraction before
3068
+ that. The extraction changed no observable behavior: `Calendar`'s full
3069
+ pre-existing test suite passed unmodified against the refactor.
3070
+
3071
+ ### CalendarRange
3072
+
3073
+ ```tsx
3074
+ import { CalendarRange, type DateRange } from '@gnome-ui/react-native';
3075
+
3076
+ const [range, setRange] = useState<DateRange | null>(null);
3077
+
3078
+ <CalendarRange value={range} onChange={setRange} />
3079
+
3080
+ <CalendarRange minRange={2} maxRange={5} value={range} onChange={setRange} />
3081
+ ```
3082
+
3083
+ Start/end date-range selection on the same grid engine as `Calendar` —
3084
+ month/year drill-down, `min`/`max`, week numbers — via `CalendarBase`, the
3085
+ shared engine `Calendar` extracted once this component actually needed it
3086
+ rather than adding the split ahead of need.
3087
+
3088
+ The first tap anchors the range; the second commits it, so `onChange` only
3089
+ ever fires with **both** ends filled in. Tapping backwards is fine — the
3090
+ pair is ordered before it's emitted.
3091
+
3092
+ **No live drag-preview band, unlike the web version.** The web
3093
+ `CalendarRange` grows the band under the mouse/keyboard focus between the
3094
+ two clicks; RN has no hover, and this package's whole keyboard layer is
3095
+ already dropped (see `Calendar`'s own doc comment above), so there's no
3096
+ signal to preview against. Rather than reach for a `PanResponder` drag
3097
+ gesture — a materially bigger feature the web version doesn't even have,
3098
+ since it's mouse-hover, not drag — the anchor day just shows as a normal
3099
+ selected day until the second tap lands and the full band appears at once,
3100
+ the same strict-touch-subset trade-off `RatingStars`/`ToggleGroup`/
3101
+ `Calendar` itself already made for other dropped hover/keyboard affordances.
3102
+
3103
+ The range band has no CSS `::before`/pseudo-element or `color-mix()` to
3104
+ lean on in RN — it's an absolutely-positioned `View` behind each day's
3105
+ button, bleeding half the cell's own padding into its neighbor via a
3106
+ negative inset (the JS-math equivalent of the web CSS's
3107
+ `inset-inline: calc(gap/-2)` bleed) so consecutive in-range days read as one
3108
+ continuous stripe under the round day buttons; end caps skip the bleed on
3109
+ their outer side and round that corner instead, so a single-day range
3110
+ composes into a full pill from the same two conditionals rather than a
3111
+ separate case. Tint alpha uses the `#RRGGBBAA` hex-suffix substitution
3112
+ `Blockquote`/`Chip` already established for the web's `color-mix()`.
3113
+
3114
+ ### DatePicker
3115
+
3116
+ ```tsx
3117
+ import { DatePicker } from '@gnome-ui/react-native';
3118
+
3119
+ const [value, setValue] = useState<Date | null>(null);
3120
+
3121
+ <DatePicker label="Date" value={value} onChange={setValue} />
3122
+
3123
+ <DatePicker
3124
+ label="Meeting time"
3125
+ showTime
3126
+ hourCycle={12}
3127
+ value={value}
3128
+ onChange={setValue}
3129
+ />
3130
+ ```
3131
+
3132
+ A `Popover`-anchored `Calendar` behind a text-entry-styled trigger, mirroring
3133
+ the `GtkCalendar` + `GtkPopover` composition GNOME apps use for date entry.
3134
+ Composed entirely from this package's own already-shipped pieces —
3135
+ `Popover` (Tier 5) and `Calendar` (Tier 20) — rather than reinventing any
3136
+ position-computation code, per this package's standing pitfall about not
3137
+ re-deriving `Popover`/`Dropdown`'s already-shipped trigger-rect + panel-size
3138
+ positioning.
3139
+
3140
+ The trigger is a plain themed `Pressable` styled like `Dropdown`'s own
3141
+ text-entry-look trigger (bordered row, dimmed placeholder, trailing
3142
+ `XOfficeCalendar` icon) rather than a literal `TextField` composition — a
3143
+ `TextField` wraps a real editable `TextInput`, which a date-picker trigger
3144
+ never wants (it opens a panel, it doesn't accept typed text).
3145
+ `locale`/`formatOptions` are dropped in favor of `GnomeProvider`'s app-wide
3146
+ `useDateTimeFormatter`, the same convention `Calendar` itself just
3147
+ established: reading the hook's own `{...dateTimeFormat, ...options}` merge
3148
+ confirmed it already expresses `dateStyle`+`timeStyle`+`hourCycle` together,
3149
+ so three fixed, module-scope option objects (date-only, 24-hour, 12-hour)
3150
+ cover every `showTime`/`hourCycle` combination without needing an arbitrary
3151
+ caller-supplied `formatOptions` escape hatch. The web version's entire
3152
+ keyboard layer drops (no `ArrowDown`-opens-the-popover, no `Enter`/`Space`
3153
+ on the trigger), and `Calendar`'s own already-dropped `autoFocus` prop is
3154
+ correctly never wired here either.
3155
+
3156
+ Picking a day keeps the popover open when `showTime` is on — only the Done
3157
+ button closes it, so the time columns stay reachable — and closes it
3158
+ immediately otherwise, ported as plain state logic from the web version's
3159
+ `handleSelect`, no web-only API involved.
3160
+
3161
+ **`showTime` brought its own dependency.** The web `TimePicker`'s
3162
+ `TimeFields`/`timeUtils.ts` (hour/minute/AM-PM `SpinButton` columns plus
3163
+ pure 12/24-hour math) first ported into `DatePicker`'s own folder as an
3164
+ internal, non-exported module, the same "shared piece built by its first
3165
+ real consumer" precedent `IconButton` set for `Drawer`'s `rail` — since
3166
+ relocated to `TimePicker`'s own folder now that it's shipped (see below),
3167
+ with `DatePicker` importing it from there instead of carrying its own copy.
3168
+ `timeUtils.ts` ports verbatim (zero DOM dependency already), and
3169
+ `TimeFields` rebuilds on this
3170
+ package's own `SpinButton` (Tier 5) — its `wrap` boolean and
3171
+ `format: (n: number) => string` callback already cover the AM/PM column's
3172
+ "numeric spinner whose `format` maps 0/1 to text" trick with no new prop
3173
+ needed, confirmed by reading `SpinButton.tsx` before wiring it up.
3174
+
3175
+ `SpinButton` has a fixed per-column minimum width (two 36 dp buttons plus a
3176
+ 56 dp value `Text`, ~130 dp), so a 12-hour row of three columns
3177
+ (hours/minutes/AM-PM) can approach or exceed a narrow phone's screen width
3178
+ even after widening the popover panel past its default 320 dp cap —
3179
+ `Popover`'s own `panelStyle` prop (a `StyleProp<ViewStyle>`, not a
3180
+ `panelClassName` string the way the web version's CSS-module trigger works;
3181
+ confirmed by reading `Popover.tsx` first) raises the cap to 420 dp for
3182
+ `showTime`, and the footer row itself is `flexWrap: 'wrap'` so the Done
3183
+ button drops to its own line instead of clipping or forcing horizontal
3184
+ scroll.
3185
+
3186
+ ### TimePicker
3187
+
3188
+ ```tsx
3189
+ import { TimePicker } from '@gnome-ui/react-native';
3190
+
3191
+ const [value, setValue] = useState<{ hours: number; minutes: number } | null>(null);
3192
+
3193
+ <TimePicker label="Time" value={value} onChange={setValue} />
3194
+
3195
+ <TimePicker label="Reminder" hourCycle={12} value={value} onChange={setValue} />
3196
+ ```
3197
+
3198
+ Paired hour/minute `SpinButton` columns behind the same bordered/dimmed-
3199
+ placeholder/trailing-icon trigger style `DatePicker` established — mirrors
3200
+ the `GtkSpinButton` + `GtkPopover` composition GNOME apps use for time
3201
+ entry, with 12- and 24-hour support.
3202
+
3203
+ `TimeFields`/`timeUtils.ts` — the hour/minute/AM-PM `SpinButton` columns and
3204
+ their pure 12/24-hour math — live here now, relocated out of `DatePicker`'s
3205
+ folder once this component gave them a real public home; `DatePicker`'s
3206
+ `showTime` footer imports them from here instead of carrying its own copy.
3207
+
3208
+ Unlike `DatePicker`'s `showTime` footer, there's no Done button: with no
3209
+ calendar-day tap to disambiguate from a close, each `SpinButton` column
3210
+ commits live via `onChange`, and the popover only closes by tapping outside
3211
+ or the trigger again — the same convention `Dropdown` already established.
3212
+ Formatting uses `hour`/`minute` `Intl.DateTimeFormat` component options
3213
+ (`{hour: '2-digit', minute: '2-digit', hourCycle}`) rather than `DatePicker`'s
3214
+ `dateStyle`+`timeStyle` pairing, since there's no date to render — ported
3215
+ straight from the web version's own formatter call, still routed through
3216
+ `GnomeProvider`'s `useDateTimeFormatter` for locale consistency.
3217
+
3008
3218
  ## Installation
3009
3219
 
3010
3220
  ```bash
@@ -0,0 +1,95 @@
1
+ import { StyleProp, ViewStyle } from 'react-native';
2
+ import { CalendarView } from './CalendarBase';
3
+ import { WeekStart } from './calendarUtils';
4
+ export type { CalendarView } from './CalendarBase';
5
+ export interface CalendarProps {
6
+ /** Controlled selected date. Pass `null` for "no selection". */
7
+ value?: Date | null;
8
+ /** Initial selected date when uncontrolled. Defaults to `null`. */
9
+ defaultValue?: Date | null;
10
+ /** Called when the user taps a day. */
11
+ onChange?: (date: Date) => void;
12
+ /** Controlled displayed month (any date within the desired month). */
13
+ month?: Date;
14
+ /**
15
+ * Initial displayed month when uncontrolled. Defaults to the selected
16
+ * date's month, falling back to the current month.
17
+ */
18
+ defaultMonth?: Date;
19
+ /** Called when the displayed month changes via navigation. */
20
+ onMonthChange?: (month: Date) => void;
21
+ /** Earliest selectable date (inclusive). Earlier days are disabled. */
22
+ min?: Date;
23
+ /** Latest selectable date (inclusive). Later days are disabled. */
24
+ max?: Date;
25
+ /**
26
+ * First day of the week: `0` (Sunday) … `6` (Saturday). Defaults to `1`
27
+ * (Monday) — the GNOME default across most locales.
28
+ */
29
+ weekStartsOn?: WeekStart;
30
+ /** Show the month/year heading with prev/next navigation. Mirrors `GtkCalendar:show-heading`. Defaults to `true`. */
31
+ showHeading?: boolean;
32
+ /** Show the abbreviated day-name column headers. Mirrors `GtkCalendar:show-day-names`. Defaults to `true`. */
33
+ showDayNames?: boolean;
34
+ /** Show an ISO week-number column. Mirrors `GtkCalendar:show-week-numbers`. Defaults to `false`. */
35
+ showWeekNumbers?: boolean;
36
+ /**
37
+ * Turn the heading label into a button that drills down day grid → month
38
+ * grid → year grid, the way modern date pickers let you jump years
39
+ * without paging month by month. Requires `showHeading`. Defaults to `true`.
40
+ */
41
+ showViewSwitcher?: boolean;
42
+ /** Grid shown on mount: `'days'`, `'months'` or `'years'`. Defaults to `'days'`. */
43
+ defaultView?: CalendarView;
44
+ /** Called when the drill-down view changes. */
45
+ onViewChange?: (view: CalendarView) => void;
46
+ /** Accessible label for the grid. Defaults to the current heading label. */
47
+ accessibilityLabel?: string;
48
+ style?: StyleProp<ViewStyle>;
49
+ testID?: string;
50
+ }
51
+ /**
52
+ * Month-grid date display — mirrors `GtkCalendar` and `@gnome-ui/react`'s own
53
+ * `Calendar`. Usable standalone (settings, forms) or as the panel inside
54
+ * `DatePicker`.
55
+ *
56
+ * The heading label drills down day grid → month grid → year grid so a
57
+ * distant year is two taps away instead of many pages, exactly as on the web.
58
+ * Leading/trailing days from adjacent months are shown dimmed and remain
59
+ * tappable — tapping one navigates to that month, matching `GtkCalendar`.
60
+ *
61
+ * A thin single-day `CalendarSelectionModel` over `CalendarBase` — the
62
+ * shared grid engine (month window, heading, day/month/year drill-down)
63
+ * extracted once `CalendarRange` actually needed it, per the note this
64
+ * component left when it first shipped rather than adding the split ahead
65
+ * of need.
66
+ *
67
+ * **The web version's entire keyboard layer drops here** (roving tabindex,
68
+ * arrow keys, PageUp/Down, Home/End, Enter/Space) — this package is
69
+ * touch-first, and unlike `Slider`'s 1D `accessibilityRole="adjustable"`
70
+ * escape hatch, there is no screen-reader analog for paging a full 2D date
71
+ * grid. Tap-to-select on each cell is the strict touch subset of the web
72
+ * interaction, the same trade-off already made for `RatingStars`/
73
+ * `ToggleGroup`/`ColorPicker`. `role="grid"`/`"row"` port 1:1 from RN's
74
+ * web-aligned `Role` union; `"gridcell"` doesn't exist in that union, so
75
+ * cells fall back to `"cell"`, the same kind of substitution `BoxedList`
76
+ * (`"list"`) and `ComboRow` (`"listbox"` → `"list"`) already made. The grid
77
+ * and its rows deliberately skip `accessible` — the `ToggleGroup`-corrected
78
+ * pattern: setting it on a container with many independently-focusable
79
+ * children (each day) would collapse the whole month into one VoiceOver
80
+ * stop. Column/row *headers* (day names, week numbers) hold no interactive
81
+ * children, so they do set `accessible` to be announced as a single unit.
82
+ *
83
+ * `visibleMonths` (side-by-side month panels) and `autoFocus`
84
+ * (keyboard-focus-on-mount) are dropped, not merely unimplemented: both are
85
+ * desktop/keyboard concerns with no honest RN counterpart — a phone screen
86
+ * has no room for two month panels side by side, and there is no keyboard to
87
+ * focus. `locale` is dropped too, in favor of the app-wide locale
88
+ * `GnomeProvider` already exposes through `useDateTimeFormatter`.
89
+ *
90
+ * For start/end selection use `CalendarRange`, which shares this same grid
91
+ * engine via `CalendarBase`.
92
+ *
93
+ * @see https://gnome.pages.gitlab.gnome.org/gtk/gtk4/class.Calendar.html
94
+ */
95
+ export declare const Calendar: ({ value: controlledValue, defaultValue, onChange, min, max, ...props }: CalendarProps) => import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,83 @@
1
+ import { ReactNode } from 'react';
2
+ import { StyleProp, ViewStyle } from 'react-native';
3
+ import { WeekStart } from './calendarUtils';
4
+ /** Which grid the calendar is currently showing. */
5
+ export type CalendarView = 'days' | 'months' | 'years';
6
+ /** How one day cell should be painted, as decided by the selection model. */
7
+ export interface CalendarDayState {
8
+ /** Endpoint of the selection — filled accent pill. */
9
+ selected?: boolean;
10
+ /** Left edge of a range. */
11
+ rangeStart?: boolean;
12
+ /** Right edge of a range. */
13
+ rangeEnd?: boolean;
14
+ /** Strictly between the two ends of a range. */
15
+ inRange?: boolean;
16
+ /** The range being painted is a preview, not a committed selection. */
17
+ preview?: boolean;
18
+ /** Appended to the day's accessible label, e.g. `', start of range'`. */
19
+ labelSuffix?: string;
20
+ }
21
+ /**
22
+ * Everything `CalendarBase` needs to know about *what is selected*, so the
23
+ * grid engine (month/year navigation, day/month/year drill-down) stays free
24
+ * of selection semantics and can drive both `Calendar` and `CalendarRange`.
25
+ *
26
+ * Trimmed from `@gnome-ui/react`'s own `CalendarSelectionModel`: no
27
+ * `seedFocus`/`hoverDay`/`focusDayChange`/`escape` — those exist only to
28
+ * drive the web version's keyboard roving-tabindex and mouse-hover range
29
+ * preview, and this package drops the entire keyboard layer the same way
30
+ * `Calendar` itself already does (no honest RN touch equivalent). Without a
31
+ * hover signal, a range picker's "preview" is just its own anchor day until
32
+ * the second tap commits — `CalendarRange` below relies on that rather than
33
+ * tracking a separate preview day.
34
+ */
35
+ export interface CalendarSelectionModel {
36
+ dayState: (date: Date) => CalendarDayState;
37
+ isDayDisabled?: (date: Date) => boolean;
38
+ activateDay: (date: Date) => void;
39
+ isMonthSelected?: (month: Date) => boolean;
40
+ isYearSelected?: (year: number) => boolean;
41
+ }
42
+ export interface CalendarBaseProps {
43
+ selection: CalendarSelectionModel;
44
+ /** Controlled displayed month (any date within the desired month). */
45
+ month?: Date;
46
+ /**
47
+ * Initial displayed month when uncontrolled. Defaults to `fallbackMonth`,
48
+ * falling back to the current month.
49
+ */
50
+ defaultMonth?: Date;
51
+ /** Called when the displayed month changes via navigation. */
52
+ onMonthChange?: (month: Date) => void;
53
+ /** Seeds the displayed month when neither `month` nor `defaultMonth` is given. */
54
+ fallbackMonth?: Date | null;
55
+ /** Earliest selectable date (inclusive). Also disables out-of-range months/years. */
56
+ min?: Date;
57
+ /** Latest selectable date (inclusive). Also disables out-of-range months/years. */
58
+ max?: Date;
59
+ weekStartsOn?: WeekStart;
60
+ showHeading?: boolean;
61
+ showDayNames?: boolean;
62
+ showWeekNumbers?: boolean;
63
+ showViewSwitcher?: boolean;
64
+ defaultView?: CalendarView;
65
+ onViewChange?: (view: CalendarView) => void;
66
+ accessibilityLabel?: string;
67
+ /** Rendered inside the root, after the grid — status live regions, presets. */
68
+ footer?: ReactNode;
69
+ style?: StyleProp<ViewStyle>;
70
+ testID?: string;
71
+ }
72
+ /**
73
+ * The shared calendar grid engine: month window, heading navigation, and the
74
+ * day / month / year drill-down grids. It owns *navigation*; `selection`
75
+ * owns *what is selected* — extracted from `Calendar` once `CalendarRange`
76
+ * actually needed the same engine, per the standing note `Calendar` left
77
+ * when it shipped rather than adding this split ahead of need.
78
+ *
79
+ * Not exported from the package barrel — `Calendar` and `CalendarRange` are
80
+ * its public faces, the same "internal engine, public wrapper" shape
81
+ * `TimeFields`/`TimePicker` already established.
82
+ */
83
+ export declare const CalendarBase: ({ selection, month: controlledMonth, defaultMonth, onMonthChange, fallbackMonth, min, max, weekStartsOn, showHeading, showDayNames, showWeekNumbers, showViewSwitcher, defaultView, onViewChange, accessibilityLabel, footer, style, testID, }: CalendarBaseProps) => import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Pure date helpers for `Calendar`. Duplicated verbatim from
3
+ * `@gnome-ui/react`'s `calendarUtils.ts` rather than imported cross-package
4
+ * — the same `fileType.ts`/`coachMarkUtils.ts` precedent for dependency-free
5
+ * logic that isn't worth a shared package for one file's worth of code. Zero
6
+ * DOM dependency on the web side already, so nothing here changes for RN.
7
+ *
8
+ * Every helper operates on the local time zone — a "day" is a wall-clock
9
+ * day, never a UTC instant.
10
+ *
11
+ * `fromISODateKey` doesn't make the cut: the web version uses it to decode
12
+ * `data-date` attributes from a single delegated `onMouseOver` listener (a
13
+ * hover-preview feature with no touch equivalent), and nothing else in this
14
+ * package needs to parse an ISO key back into a `Date`. `toISODateKey` stays
15
+ * — every day/week still wants a stable string for React `key`s and test
16
+ * IDs.
17
+ */
18
+ export type WeekStart = 0 | 1 | 2 | 3 | 4 | 5 | 6;
19
+ /** Midnight (local) of the given date, as a fresh `Date`. */
20
+ export declare const startOfDay: (date: Date) => Date;
21
+ /** First day of the month containing `date`, at local midnight. */
22
+ export declare const startOfMonth: (date: Date) => Date;
23
+ /** `true` when both dates fall on the same calendar day. */
24
+ export declare const isSameDay: (a: Date, b: Date) => boolean;
25
+ /** `true` when both dates fall in the same calendar month. */
26
+ export declare const isSameMonth: (a: Date, b: Date) => boolean;
27
+ /** `date` shifted by `amount` days (may be negative). */
28
+ export declare const addDays: (date: Date, amount: number) => Date;
29
+ /**
30
+ * The date `year-month-day`, with the day clamped to the last valid day of that
31
+ * month. Used whenever a month or year jump must keep the day-of-month intact.
32
+ */
33
+ export declare const clampDayToMonth: (year: number, month: number, day: number) => Date;
34
+ /**
35
+ * `date` shifted by `amount` months, keeping the day-of-month where possible
36
+ * and clamping to the last valid day otherwise (e.g. Jan 31 + 1 month → Feb 28).
37
+ */
38
+ export declare const addMonths: (date: Date, amount: number) => Date;
39
+ /**
40
+ * `date` shifted by `amount` years, clamping the day-of-month where the target
41
+ * year is shorter (e.g. 29 Feb 2028 - 1 year → 28 Feb 2027).
42
+ */
43
+ export declare const addYears: (date: Date, amount: number) => Date;
44
+ /** How many years one page of the year view shows. */
45
+ export declare const YEARS_PER_PAGE = 12;
46
+ /** First year of the fixed-size page of years containing `year`. */
47
+ export declare const startOfYearPage: (year: number) => number;
48
+ /** Distance in days from the week's start to `date`, given the first weekday. */
49
+ export declare const weekdayOffset: (date: Date, weekStartsOn: WeekStart) => number;
50
+ /**
51
+ * The six-week grid (always 6 rows × 7 days) that the web `Calendar` renders
52
+ * for a month — leading/trailing days belong to the adjacent months. A fixed
53
+ * row count avoids the layout jump months of differing length would
54
+ * otherwise cause.
55
+ */
56
+ export declare const getCalendarWeeks: (month: Date, weekStartsOn: WeekStart) => Date[][];
57
+ /** ISO-8601 week number (weeks start Monday; week 1 contains the first Thursday). */
58
+ export declare const isoWeekNumber: (date: Date) => number;
59
+ /** `true` when `date` is outside the inclusive `[min, max]` range. */
60
+ export declare const isOutOfRange: (date: Date, min?: Date, max?: Date) => boolean;
61
+ /** `true` when every day of `date`'s month falls outside the inclusive `[min, max]` range. */
62
+ export declare const isMonthOutOfRange: (date: Date, min?: Date, max?: Date) => boolean;
63
+ /** `true` when every day of `date`'s year falls outside the inclusive `[min, max]` range. */
64
+ export declare const isYearOutOfRange: (date: Date, min?: Date, max?: Date) => boolean;
65
+ /** A stable `YYYY-MM-DD` key for a date, used for React `key`s and test IDs. */
66
+ export declare const toISODateKey: (date: Date) => string;
@@ -0,0 +1,2 @@
1
+ export type { CalendarProps, CalendarView } from './Calendar';
2
+ export { Calendar } from './Calendar';
@@ -0,0 +1,75 @@
1
+ import { StyleProp, ViewStyle } from 'react-native';
2
+ import { CalendarView } from '../Calendar/CalendarBase';
3
+ import { WeekStart } from '../Calendar/calendarUtils';
4
+ import { DateRange, SelectedDateRange } from './rangeUtils';
5
+ export type { DateRange, SelectedDateRange } from './rangeUtils';
6
+ export interface CalendarRangeProps {
7
+ /** Controlled range. Either end may be `null` while nothing is chosen. */
8
+ value?: DateRange | null;
9
+ /** Initial range when uncontrolled. Defaults to an empty range. */
10
+ defaultValue?: DateRange | null;
11
+ /**
12
+ * Called with the finished range — **only once both ends have a value**.
13
+ * The first tap merely anchors the range, so this never fires with a half
14
+ * selection.
15
+ */
16
+ onChange?: (range: SelectedDateRange) => void;
17
+ /** Called with the anchor day when the first of the two taps lands. */
18
+ onRangeStart?: (date: Date) => void;
19
+ /** Controlled displayed month (any date within the desired month). */
20
+ month?: Date;
21
+ /**
22
+ * Initial displayed month when uncontrolled. Defaults to the range's
23
+ * start month, falling back to the current month.
24
+ */
25
+ defaultMonth?: Date;
26
+ /** Called when the displayed month changes via navigation. */
27
+ onMonthChange?: (month: Date) => void;
28
+ /** Earliest selectable date (inclusive). */
29
+ min?: Date;
30
+ /** Latest selectable date (inclusive). */
31
+ max?: Date;
32
+ /** Shortest range the user may commit, in days (both ends counted). Defaults to `1`. */
33
+ minRange?: number;
34
+ /** Longest range the user may commit, in days (both ends counted). Unlimited by default. */
35
+ maxRange?: number;
36
+ weekStartsOn?: WeekStart;
37
+ showHeading?: boolean;
38
+ showDayNames?: boolean;
39
+ showWeekNumbers?: boolean;
40
+ showViewSwitcher?: boolean;
41
+ defaultView?: CalendarView;
42
+ onViewChange?: (view: CalendarView) => void;
43
+ accessibilityLabel?: string;
44
+ style?: StyleProp<ViewStyle>;
45
+ testID?: string;
46
+ }
47
+ /**
48
+ * Start/end date-range selection driving the same grid engine as `Calendar`
49
+ * — month/year drill-down, `min`/`max`, week numbers — via `CalendarBase`,
50
+ * the shared engine `Calendar` extracted once this component actually
51
+ * needed it.
52
+ *
53
+ * The first tap anchors the range; the second commits it. `onChange`
54
+ * therefore only ever fires with **both** ends filled in. Tapping backwards
55
+ * is fine — the pair is ordered before it is emitted.
56
+ *
57
+ * **No live drag-preview band, unlike the web version.** The web
58
+ * `CalendarRange` grows the band under the mouse/keyboard focus between the
59
+ * two clicks; RN has no hover and this package's whole keyboard layer is
60
+ * already dropped (see `Calendar`'s own doc comment), so there is no signal
61
+ * to preview against. The anchor day simply shows as a normal selected day
62
+ * until the second tap lands and the full band appears at once — the
63
+ * touch-first subset of the same interaction, the same kind of trade-off
64
+ * `RatingStars`/`ToggleGroup`/`Calendar` itself already made for dropped
65
+ * hover/keyboard affordances. `CalendarBase`'s `CalendarSelectionModel` was
66
+ * trimmed to match — no `hoverDay`/`focusDayChange` to wire up here.
67
+ *
68
+ * `visibleMonths` (side-by-side month panels, the usual desktop range-picker
69
+ * shape) has no phone-width equivalent — dropped, the same `Calendar`
70
+ * `visibleMonths` precedent. `locale` is dropped too, in favor of
71
+ * `GnomeProvider`'s app-wide `useDateTimeFormatter`.
72
+ *
73
+ * @see https://gnome.pages.gitlab.gnome.org/gtk/gtk4/class.Calendar.html
74
+ */
75
+ export declare const CalendarRange: ({ value: controlledValue, defaultValue, onChange, onRangeStart, min, max, minRange, maxRange, ...props }: CalendarRangeProps) => import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,2 @@
1
+ export type { CalendarRangeProps, DateRange, SelectedDateRange } from './CalendarRange';
2
+ export { CalendarRange } from './CalendarRange';
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Pure helpers for `CalendarRange`. Kept dependency-free and side-effect-free,
3
+ * mirroring the `calendarUtils.ts` split `Calendar` already uses. Ported
4
+ * verbatim from `@gnome-ui/react`'s own `rangeUtils.ts` — zero DOM
5
+ * dependency there already.
6
+ */
7
+ /** A range under construction — either end may still be missing. */
8
+ export interface DateRange {
9
+ start: Date | null;
10
+ end: Date | null;
11
+ }
12
+ /** A finished range: both ends have a value. The only shape `onChange` emits. */
13
+ export interface SelectedDateRange {
14
+ start: Date;
15
+ end: Date;
16
+ }
17
+ /** The empty range, as a fresh object so callers never share one. */
18
+ export declare const emptyRange: () => DateRange;
19
+ /** Both dates as a range, oldest first — so picking backwards still works. */
20
+ export declare const orderRange: (a: Date, b: Date) => SelectedDateRange;
21
+ /**
22
+ * Length of the range in days, counting both ends (so a single day is `1`).
23
+ * Rounded, because a DST change makes a "day" 23 or 25 hours long.
24
+ */
25
+ export declare const rangeLength: (range: SelectedDateRange) => number;
26
+ /** `true` when `date` falls on or between both ends. */
27
+ export declare const isWithinRange: (date: Date, range: SelectedDateRange) => boolean;
28
+ /** `true` when the range satisfies the `minRange`/`maxRange` day limits. */
29
+ export declare const isRangeAllowed: (range: SelectedDateRange, minRange?: number, maxRange?: number) => boolean;
30
+ /** `true` when both ends have a value — the condition for emitting a change. */
31
+ export declare const isRangeComplete: (range: DateRange) => range is SelectedDateRange;
@@ -0,0 +1,75 @@
1
+ import { StyleProp, ViewStyle } from 'react-native';
2
+ import { WeekStart } from '../Calendar/calendarUtils';
3
+ import { PopoverPlacement } from '../Popover';
4
+ export interface DatePickerProps {
5
+ /** Controlled selected date. Pass `null` for "no selection". */
6
+ value?: Date | null;
7
+ /** Initial selected date when uncontrolled. Defaults to `null`. */
8
+ defaultValue?: Date | null;
9
+ /** Called when the user picks a date (or, with `showTime`, edits a time column). */
10
+ onChange?: (date: Date) => void;
11
+ /** Earliest selectable date (inclusive). */
12
+ min?: Date;
13
+ /** Latest selectable date (inclusive). */
14
+ max?: Date;
15
+ /** First day of the week: `0` (Sunday) … `6` (Saturday). Defaults to `1` (Monday). */
16
+ weekStartsOn?: WeekStart;
17
+ /**
18
+ * Add hour/minute columns under the calendar, making the emitted `Date` a
19
+ * point in time rather than a civil date. Picking a day then keeps the
20
+ * popover open — the selection is only finished by the Done button.
21
+ */
22
+ showTime?: boolean;
23
+ /** 12- or 24-hour columns when `showTime` is on. Defaults to `24`. */
24
+ hourCycle?: 12 | 24;
25
+ /** Minute increment for the time spinner. Defaults to `1`. */
26
+ minuteStep?: number;
27
+ /** Label on the button that closes a `showTime` popover. Defaults to `'Done'`. */
28
+ doneLabel?: string;
29
+ /** Text shown in the trigger while no date is selected. */
30
+ placeholder?: string;
31
+ /** Visible label rendered above the trigger. */
32
+ label?: string;
33
+ /** Accessible name for the trigger when no visible `label` is provided. */
34
+ accessibilityLabel?: string;
35
+ /** Show an ISO week-number column in the calendar. */
36
+ showWeekNumbers?: boolean;
37
+ /** Disable the control. */
38
+ disabled?: boolean;
39
+ /** Preferred popover placement relative to the trigger. Defaults to `'bottom'`. */
40
+ placement?: PopoverPlacement;
41
+ style?: StyleProp<ViewStyle>;
42
+ testID?: string;
43
+ }
44
+ /**
45
+ * A `Popover`-anchored `Calendar` behind a text-entry-styled trigger —
46
+ * mirrors the `GtkCalendar` + `GtkPopover` composition GNOME apps use for
47
+ * date entry.
48
+ *
49
+ * Rebuilt on this package's own already-shipped `Popover` (Tier 5) and
50
+ * `Calendar` (Tier 20) rather than reinventing either: the trigger is a
51
+ * `Pressable` styled like `Dropdown`'s own text-entry-look trigger (same
52
+ * bordered row, dimmed placeholder, trailing icon), and the panel is
53
+ * `Calendar` plus an optional `showTime` footer, composed exactly the way
54
+ * `Popover`'s own doc comment expects a rich-content consumer to use it —
55
+ * no new position-computation code, per this package's standing pitfall
56
+ * about not reinventing `Popover`/`Dropdown`'s already-shipped trigger-rect
57
+ * + panel-size positioning.
58
+ *
59
+ * **The web version's entire keyboard layer drops** — no `ArrowDown`-opens,
60
+ * no `Enter`/`Space` on the trigger — the same touch-first convention this
61
+ * whole package follows; `Calendar`'s own `autoFocus` prop was already
62
+ * dropped when `Calendar` was ported (no keyboard focus concept without a
63
+ * keyboard), so it isn't wired here either. `locale`/`formatOptions` are
64
+ * dropped in favor of `GnomeProvider`'s app-wide `useDateTimeFormatter`,
65
+ * following `Calendar`'s own just-shipped convention rather than
66
+ * reintroducing a locale prop pair.
67
+ *
68
+ * Picking a day keeps the popover open when `showTime` is on (only the Done
69
+ * button closes it, so the time columns stay reachable) and closes it
70
+ * immediately otherwise — ported as-is from the web version's
71
+ * `handleSelect`, plain state logic with no web-only API involved.
72
+ *
73
+ * @see https://gnome.pages.gitlab.gnome.org/gtk/gtk4/class.Calendar.html
74
+ */
75
+ export declare const DatePicker: ({ value: controlledValue, defaultValue, onChange, min, max, weekStartsOn, showTime, hourCycle, minuteStep, doneLabel, placeholder, label, accessibilityLabel, showWeekNumbers, disabled, placement, style, testID, }: DatePickerProps) => import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,2 @@
1
+ export type { DatePickerProps } from './DatePicker';
2
+ export { DatePicker } from './DatePicker';