Skip to main content

Providers

Each selection mode has its own provider component. All three share the same bounds / disabled / modifier props and the same callback trio (onChange, onConfirm, onClear).

Shared Props

These work on SingleDateProvider, RangeDateProvider, and MultipleDateProvider:

PropTypeDescription
systemsreadonly CalendarSystem[]Defaults to [gregorianSystem].
activeSystemIdstringControlled active system. Unknown ids fall back to systems[0].
minDateunknownEarliest selectable day (inclusive).
maxDateunknownLatest selectable day (inclusive).
disabledDatesreadonly unknown[]Individual disabled days.
disabledRangesreadonly { start, end }[]Inclusive disabled ranges.
disabled(nativeDate: Date) => booleanDynamic disable predicate.
modifiersCalendarModifiersNamed matchers → cell.modifiers[name].
firstDayOfWeek0 | 1 | … | 6First column of the grid. Default: 1 (Monday).
onChangemode-specific payloadFires after every successful selectDate or clear.
onConfirmmode-specific payloadFires when confirm() is called.
onClear() => voidFires when clear() is called.

Date inputs (minDate, initialDate, …) accept anything your active system's from() understands — native Date, ISO strings, or the system's native value.

SingleDateProvider

<SingleDateProvider
initialDate={new Date()}
onConfirm={({ date, parts, systemId }) => {}}
>
{children}
</SingleDateProvider>
PropDescription
initialDatePre-selects a date and sets the displayed month.

Payload (SingleSelectionPayload):

{ gregorianDate?: Date; systemId: string; system?: { year, month, day } }

Hooks: useSingleCalendarSelector, useSingleCalendarActions, selectSingleDays, selectSingleMonths, selectSingleYears, selectSingleCanConfirm.

RangeDateProvider

<RangeDateProvider
initialStart={new Date()}
initialEnd={undefined}
allowSameDay={false}
minRangeDays={2}
maxRangeDays={14}
disabledInRangeBehavior="reject"
onConfirm={({ gregorianStartDate, gregorianEndDate, system, systemId }) => {}}
>
{children}
</RangeDateProvider>
PropDefaultDescription
initialStartFirst range endpoint.
initialEndSecond endpoint (optional).
allowSameDayfalseAllow start === end as a one-day range.
minRangeDaysMinimum inclusive span (in days).
maxRangeDaysMaximum inclusive span (in days).
disabledInRangeBehavior'reject'What to do when a range would include disabled days between start and end. See below.

Disabled days inside a range

Disabled rules (disabledDates, disabledRanges, disabled, bounds) always block endpoints — you cannot tap a disabled day as start or end.

When the user picks a second endpoint that would span one or more disabled days in the interior, disabledInRangeBehavior controls the outcome:

ValueBehaviorTypical use
'reject'The second tap is ignored. rangeStart stays set; rangeEnd stays undefined. No onChange.Hotel booking — unavailable nights must not appear in the stay.
'include'The full range is stored. Interior cells keep isDisabled: true and inRange: true so your UI can strike them through or show a warning. selectRangeCanConfirm still returns true.Analytics / filters where you want the span visible even if some days are blocked.
'exclude'rangeEnd is clamped to the day before the first disabled interior day (largest clean range on the start side). If that would be a same-day range and allowSameDay is false, rangeEnd is dropped instead.“Pick as much as you can” flows without rejecting the tap entirely.

Example: start = Aug 1, disabled = Aug 5, user taps Aug 10.

// reject (default) — range stays start-only
<RangeDateProvider disabledDates={[aug5]} disabledInRangeBehavior="reject" />

// include — Aug 1–10; Aug 5 cell: isDisabled && inRange
<RangeDateProvider disabledDates={[aug5]} disabledInRangeBehavior="include" />

// exclude — range becomes Aug 1–4
<RangeDateProvider disabledDates={[aug5]} disabledInRangeBehavior="exclude" />

Initial props: initialStart and initialEnd go through the same policy at bootstrap. If 'reject' would apply, the store keeps initialStart and drops initialEnd (same as a refused second tap). Inverted seeds (initialEnd before initialStart) are ordered before validation.

Length rules: After the policy runs, minRangeDays / maxRangeDays still apply. A clamped range that is too short or too long is rejected the same as any other invalid span.

Exported type: DisabledInRangeBehavior from react-native-headless-calendar.

Payload (RangeSelectionPayload):

{
gregorianStartDate?: Date;
gregorianEndDate?: Date;
systemId: string;
system: {
start?: DateParts;
end?: DateParts;
};
}

Hooks: useRangeCalendarSelector, useRangeCalendarActions, selectRangeDays, …

MultipleDateProvider

<MultipleDateProvider
initialDates={[new Date()]}
maxSelected={5}
onConfirm={({ gregorianDates, system, systemId }) => {}}
>
{children}
</MultipleDateProvider>
PropDescription
initialDatesPre-selected dates; first entry seeds the displayed month.
maxSelectedCap on how many dates can be selected.

Payload (MultipleSelectionPayload):

{ gregorianDates: readonly Date[]; systemId: string; system: readonly DateParts[] }

Hooks: useMultipleCalendarSelector, useMultipleCalendarActions, selectMultipleDays, …

Live Prop Updates

Providers call store.configure(...) on every render (via useLayoutEffect). Inline callbacks and arrays are stabilised internally so you can write:

onConfirm={({ gregorianDate }) => setPicked(gregorianDate)}
systems={[gregorianSystem]}
modifiers={{ booked: [someDate] }}

without unnecessary store churn — though memoising heavy modifiers objects still helps.

Errors

Hooks throw when used outside their provider:

[Calendar] single-mode hooks must be used within <SingleDateProvider>
[Calendar] range-mode hooks must be used within <RangeDateProvider>
[Calendar] multiple-mode hooks must be used within <MultipleDateProvider>