Components / forms
Range calendar
Picks a period from a month grid, a first then a last day, for a stay or a rental: the days between are highlighted, and the range never crosses an unavailable day.
Import
In a Flutter app where fliin init has run:
Examples
Example
A range calendar: the first touch picks the start, the second the end, and the days between are filled.
import 'package:your_app/ui/fliin/components/range_calendar/fl_range_calendar.dart';
import 'package:flutter/widgets.dart';
class RangeCalendarExample extends StatefulWidget {
const RangeCalendarExample({super.key});
@override
State<RangeCalendarExample> createState() => _RangeCalendarExampleState();
}
class _RangeCalendarExampleState extends State<RangeCalendarExample> {
FlDateRange? _value;
@override
Widget build(BuildContext context) {
return FlRangeCalendar(
value: _value,
onChanged: (range) => setState(() => _value = range),
semanticLabel: 'Trip dates',
);
}
}Range
A chosen range, its ends in the accent and the days between tinted. value is an FlDateRange.
import 'package:your_app/ui/fliin/components/range_calendar/fl_range_calendar.dart';
import 'package:flutter/widgets.dart';
class RangeCalendarRangeExample extends StatefulWidget {
const RangeCalendarRangeExample({super.key});
@override
State<RangeCalendarRangeExample> createState() => _RangeCalendarRangeExampleState();
}
class _RangeCalendarRangeExampleState extends State<RangeCalendarRangeExample> {
FlDateRange? _value = FlDateRange(_thisMonth(8), _thisMonth(19));
@override
Widget build(BuildContext context) {
return FlRangeCalendar(
value: _value,
onChanged: (range) => setState(() => _value = range),
focusedDate: _value?.start,
semanticLabel: 'Trip dates',
);
}
}
/// Day [day] of the current month.
DateTime _thisMonth(int day) {
final now = DateTime.now();
return DateTime(now.year, now.month, day);
}Across months
A range that runs into next month. weeksInMonth: 6 keeps the grid the same height from one month to the next.
import 'package:your_app/ui/fliin/components/range_calendar/fl_range_calendar.dart';
import 'package:flutter/widgets.dart';
class RangeCalendarAcrossMonthsExample extends StatefulWidget {
const RangeCalendarAcrossMonthsExample({super.key});
@override
State<RangeCalendarAcrossMonthsExample> createState() => _RangeCalendarAcrossMonthsExampleState();
}
class _RangeCalendarAcrossMonthsExampleState extends State<RangeCalendarAcrossMonthsExample> {
FlDateRange? _value = FlDateRange(_thisMonth(27), _thisMonth(36));
@override
Widget build(BuildContext context) {
return FlRangeCalendar(
value: _value,
onChanged: (range) => setState(() => _value = range),
focusedDate: _value?.start,
weeksInMonth: 6,
semanticLabel: 'Trip dates',
);
}
}
/// Day [day] of the current month.
DateTime _thisMonth(int day) {
final now = DateTime.now();
return DateTime(now.year, now.month, day);
}Unavailable
isUnavailable strikes through the days that cannot be chosen; a range cannot be stretched across them.
import 'package:your_app/ui/fliin/components/range_calendar/fl_range_calendar.dart';
import 'package:flutter/widgets.dart';
class RangeCalendarUnavailableExample extends StatefulWidget {
const RangeCalendarUnavailableExample({super.key});
@override
State<RangeCalendarUnavailableExample> createState() => _RangeCalendarUnavailableExampleState();
}
class _RangeCalendarUnavailableExampleState extends State<RangeCalendarUnavailableExample> {
FlDateRange? _value = FlDateRange(_thisMonth(12), _thisMonth(14));
@override
Widget build(BuildContext context) {
return FlRangeCalendar(
value: _value,
onChanged: (range) => setState(() => _value = range),
focusedDate: _value?.start,
isUnavailable: _weekend,
semanticLabel: 'Trip dates',
);
}
}
/// Day [day] of the current month.
DateTime _thisMonth(int day) {
final now = DateTime.now();
return DateTime(now.year, now.month, day);
}
bool _weekend(DateTime day) => day.weekday == DateTime.saturday || day.weekday == DateTime.sunday;Single day
A range of one day: the same day touched twice.
import 'package:your_app/ui/fliin/components/range_calendar/fl_range_calendar.dart';
import 'package:flutter/widgets.dart';
class RangeCalendarSingleDayExample extends StatefulWidget {
const RangeCalendarSingleDayExample({super.key});
@override
State<RangeCalendarSingleDayExample> createState() => _RangeCalendarSingleDayExampleState();
}
class _RangeCalendarSingleDayExampleState extends State<RangeCalendarSingleDayExample> {
FlDateRange? _value = FlDateRange(_thisMonth(21), _thisMonth(21));
@override
Widget build(BuildContext context) {
return FlRangeCalendar(
value: _value,
onChanged: (range) => setState(() => _value = range),
focusedDate: _value?.start,
semanticLabel: 'Trip dates',
);
}
}
/// Day [day] of the current month.
DateTime _thisMonth(int day) {
final now = DateTime.now();
return DateTime(now.year, now.month, day);
}Start picked
Halfway through: the start is picked and the calendar waits for the end. initialStart sets it from the outside.
import 'package:your_app/ui/fliin/components/range_calendar/fl_range_calendar.dart';
import 'package:flutter/widgets.dart';
class RangeCalendarStartPickedExample extends StatefulWidget {
const RangeCalendarStartPickedExample({super.key});
@override
State<RangeCalendarStartPickedExample> createState() => _RangeCalendarStartPickedExampleState();
}
class _RangeCalendarStartPickedExampleState extends State<RangeCalendarStartPickedExample> {
FlDateRange? _value;
@override
Widget build(BuildContext context) {
return FlRangeCalendar(
value: _value,
onChanged: (range) => setState(() => _value = range),
initialStart: _thisMonth(12),
semanticLabel: 'Trip dates',
);
}
}
/// Day [day] of the current month.
DateTime _thisMonth(int day) {
final now = DateTime.now();
return DateTime(now.year, now.month, day);
}Nights
FlRangeUnit.nights counts nights, as for a stay: a taken night blocks the range, but the morning after it can still be a check-out.
import 'package:your_app/ui/fliin/components/range_calendar/fl_range_calendar.dart';
import 'package:flutter/widgets.dart';
class RangeCalendarNightsExample extends StatefulWidget {
const RangeCalendarNightsExample({super.key});
@override
State<RangeCalendarNightsExample> createState() => _RangeCalendarNightsExampleState();
}
class _RangeCalendarNightsExampleState extends State<RangeCalendarNightsExample> {
FlDateRange? _value = FlDateRange(_thisMonth(12), _thisMonth(15));
@override
Widget build(BuildContext context) {
return FlRangeCalendar(
value: _value,
onChanged: (range) => setState(() => _value = range),
focusedDate: _value?.start,
isUnavailable: _taken,
unit: FlRangeUnit.nights,
semanticLabel: 'Trip dates',
);
}
}
/// Day [day] of the current month.
DateTime _thisMonth(int day) {
final now = DateTime.now();
return DateTime(now.year, now.month, day);
}
/// The nights of the 15th and the 16th are already booked.
bool _taken(DateTime night) {
final now = DateTime.now();
return night.year == now.year && night.month == now.month && (night.day == 15 || night.day == 16);
}Events
indicatorOf marks the days already planned, inside the range or out of it: here the team's offsites, to plan a leave around them.
import 'package:your_app/ui/fliin/components/range_calendar/fl_range_calendar.dart';
import 'package:flutter/widgets.dart';
class RangeCalendarEventsExample extends StatefulWidget {
const RangeCalendarEventsExample({super.key});
@override
State<RangeCalendarEventsExample> createState() => _RangeCalendarEventsExampleState();
}
class _RangeCalendarEventsExampleState extends State<RangeCalendarEventsExample> {
FlDateRange? _value = FlDateRange(_thisMonth(8), _thisMonth(12));
@override
Widget build(BuildContext context) {
return FlRangeCalendar(
value: _value,
onChanged: (range) => setState(() => _value = range),
focusedDate: _value?.start,
indicatorOf: (day) => const {4, 10, 18, 25}.contains(day.day) ? 'Offsite' : null,
semanticLabel: 'Leave dates',
);
}
}
/// Day [day] of the current month.
DateTime _thisMonth(int day) {
final now = DateTime.now();
return DateTime(now.year, now.month, day);
}Usage
The code the reference images render:
ColoredBox(color: FlScope.of(context).colors.surface, child: Padding(padding: EdgeInsetsDirectional.all(FlScope.of(context).space.lg), child: Wrap(spacing: FlScope.of(context).space.lg, runSpacing: FlScope.of(context).space.lg, children: [SizedBox(width: 320, child: FlRangeCalendar(value: FlDateRange(DateTime(2026, 10, 8), DateTime(2026, 10, 19)), onChanged: (_) {}, today: DateTime(2026, 10, 24), strings: FlCalendarStrings.language'en', semanticLabel: 'Stay dates')), SizedBox(width: 320, child: FlRangeCalendar(value: FlDateRange(DateTime(2026, 10, 27), DateTime(2026, 11, 4)), onChanged: (_) {}, today: DateTime(2026, 10, 24), focusedDate: DateTime(2026, 10, 27), weeksInMonth: 6, strings: FlCalendarStrings.language'en', semanticLabel: 'Rental dates')), SizedBox(width: 320, child: FlRangeCalendar(value: FlDateRange(DateTime(2026, 10, 12), DateTime(2026, 10, 16)), onChanged: (_) {}, today: DateTime(2026, 10, 24), isUnavailable: (day) => day.weekday == DateTime.saturday || day.weekday == DateTime.sunday, strings: FlCalendarStrings.language'en', semanticLabel: 'Work week')), SizedBox(width: 320, child: FlRangeCalendar(value: FlDateRange(DateTime(2026, 10, 21), DateTime(2026, 10, 21)), onChanged: (_) {}, today: DateTime(2026, 10, 24), minDate: DateTime(2026, 10, 5), strings: FlCalendarStrings.language'en', semanticLabel: 'Period')), SizedBox(width: 320, child: FlRangeCalendar(value: null, initialStart: DateTime(2026, 10, 12), onChanged: (_) {}, today: DateTime(2026, 10, 24), strings: FlCalendarStrings.language'en', semanticLabel: 'Vacation dates'))])))API
FlRangeUnit: enum {days, nights}
FlRangeUnit.days: static final FlRangeUnit
FlRangeUnit.nights: static final FlRangeUnit
FlDateRange: final class
FlDateRange.new: new(DateTime start, DateTime end)
FlDateRange.between: factory new(DateTime a, DateTime b)
FlDateRange.start: get DateTime
FlDateRange.end: get DateTime
FlDateRange.contains: bool(DateTime day)
FlDateRange.operator ==: bool(Object other)
FlDateRange.hashCode: get int
FlDateRange.toString: String()
FlRangeCalendar: class extends StatefulWidget
FlRangeCalendar.new: const new({super.key, required FlDateRange? value, required ValueChanged<FlDateRange> onChanged, DateTime? focusedDate, DateTime? initialStart, DateTime? today, DateTime? minDate, DateTime? maxDate, bool Function(DateTime day)? isUnavailable, FlRangeUnit unit, int? firstDayOfWeek, FlCalendarStrings? strings, int? weeksInMonth, bool showYearPicker, bool defaultYearPickerOpen, bool enabled, bool autofocus, String? semanticLabel, ValueChanged<DateTime>? onMonthChanged, String? Function(DateTime day)? indicatorOf})
FlRangeCalendar.value: get FlDateRange?
FlRangeCalendar.onChanged: get ValueChanged<FlDateRange>
FlRangeCalendar.focusedDate: get DateTime?
FlRangeCalendar.initialStart: get DateTime?
FlRangeCalendar.today: get DateTime?
FlRangeCalendar.minDate: get DateTime?
FlRangeCalendar.maxDate: get DateTime?
FlRangeCalendar.isUnavailable: get bool Function(DateTime day)?
FlRangeCalendar.unit: get FlRangeUnit
FlRangeCalendar.firstDayOfWeek: get int?
FlRangeCalendar.strings: get FlCalendarStrings?
FlRangeCalendar.weeksInMonth: get int?
FlRangeCalendar.showYearPicker: get bool
FlRangeCalendar.defaultYearPickerOpen: get bool
FlRangeCalendar.enabled: get bool
FlRangeCalendar.autofocus: get bool
FlRangeCalendar.semanticLabel: get String?
FlRangeCalendar.onMonthChanged: get ValueChanged<DateTime>?
FlRangeCalendar.indicatorOf: get String? Function(DateTime day)?
FlRangeCalendar.createState: State<FlRangeCalendar>()Reference renders
The 16 images published with version 0.2.3, rendered by the shared harness on macOS with Flutter 3.47.7. fliin verify range-calendar compares your copy to them.
















Use it to
- pick check-in and check-out dates for a stay
- select a rental period in a calendar
- choose the first and last day of a vacation
- choose the dates covered by a report
- make already booked nights unavailable
- select a contiguous range of dates with a start and an end
- Not for
- a single day: use a calendar (calendar)
- a preset duration such as 7 or 30 days: use a segmented control (segmented)
- a form field that opens the grid on tap: use a date range picker (date-range-picker)
- States
Source
The files fliin add range-calendar copies into your app, before your prefix and imports are applied.
range_calendar/fl_range_calendar.dart
// Modified from the RangeCalendar of HeroUI React v3, Copyright 2020 Next UI
// Inc., licensed under the MIT license below and under Apache 2.0:
// https://www.apache.org/licenses/LICENSE-2.0
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
import 'package:flutter/widgets.dart';
import 'package:fliin_ui/components/calendar/fl_calendar_strings.dart';
import 'package:fliin_ui/components/calendar/fl_calendar_view.dart';
/// The farthest an unavailable day is searched for around the start of a
/// range: ten years.
const int _gapSearch = 3660;
/// What a range consumes, and therefore what `isUnavailable` means.
enum FlRangeUnit {
/// Its days, both ends included (time off): an unavailable day neither
/// starts, ends, nor is crossed by any range.
days,
/// Its nights, from the start's night to the eve of the end (a stay):
/// `isUnavailable` says a night is taken, named by the day it starts on.
/// The end doesn't consume its night: a day whose night is taken remains a
/// possible departure when the previous night is free. That "departure
/// only" day can't be an arrival, and a range crosses no taken night.
nights,
}
/// A range from [start] to [end], both included, in days without time.
final class FlDateRange {
/// A range whose [end] isn't before [start].
FlDateRange(DateTime start, DateTime end) : start = flDateOnly(start), end = flDateOnly(end) {
if (this.end.isBefore(this.start)) {
throw ArgumentError.value(end, 'end', 'before the start $start');
}
}
/// The range between two days, whichever order they come in.
factory FlDateRange.between(DateTime a, DateTime b) => b.isBefore(a) ? FlDateRange(b, a) : FlDateRange(a, b);
/// The first day.
final DateTime start;
/// The last day, [start] for a one-day range.
final DateTime end;
/// True when [day] is in the range, both ends included.
bool contains(DateTime day) {
final date = flDateOnly(day);
return !date.isBefore(start) && !date.isAfter(end);
}
@override
bool operator ==(Object other) => other is FlDateRange && other.start == start && other.end == end;
@override
int get hashCode => Object.hash(start, end);
@override
String toString() => 'FlDateRange($start, $end)';
}
/// The choice of a range in a month's grid:
/// the first tap sets the start, the second the end, and the range is
/// painted as a `surfaceSecondary` ribbon between two `selected` discs. The
/// ribbon rounds off at the edge of each week.
///
/// A second tap before the start swaps the two: the range runs from the
/// tapped day to the first one. That's what React Aria does, and the two
/// taps always state the intended range; starting over would silently
/// discard the first. While picking, the first day is only circled and read
/// "start set": nothing is highlighted until the app has received the
/// range. Escape abandons it. A range doesn't pass over an unavailable day:
/// after the first tap, days beyond the nearest unavailable one can no
/// longer be picked. In nights ([unit]), the day whose night is taken can
/// still be picked as the end, if it is reached without crossing a taken
/// night.
///
/// The rest (month, year, keyboard, screen reader, motion, texts) is that
/// of [FlCalendarView], the grid of `FlCalendar`.
class FlRangeCalendar extends StatefulWidget {
/// A calendar whose picked range is [value].
const FlRangeCalendar({
super.key,
required this.value,
required this.onChanged,
this.focusedDate,
this.initialStart,
this.today,
this.minDate,
this.maxDate,
this.isUnavailable,
this.unit = FlRangeUnit.days,
this.firstDayOfWeek,
this.strings,
this.weeksInMonth,
this.showYearPicker = true,
this.defaultYearPickerOpen = false,
this.enabled = true,
this.autofocus = false,
this.semanticLabel,
this.onMonthChanged,
this.indicatorOf,
});
/// The picked range; null before the first pick.
final FlDateRange? value;
/// Receives the range on the second tap; the parent passes it back in
/// [value].
final ValueChanged<FlDateRange> onChanged;
/// The cursor's initial day, and its month; by default the start of
/// [value], otherwise today. A range set by the app in another month also
/// takes the grid there.
final DateTime? focusedDate;
/// A first day already tapped at the outset, awaiting the second (a sheet
/// reopened mid-pick); null by default. In nights, a day whose night is
/// taken isn't an arrival: it is ignored.
final DateTime? initialStart;
/// See [FlCalendarView.today].
final DateTime? today;
/// See [FlCalendarView.minDate].
final DateTime? minDate;
/// See [FlCalendarView.maxDate].
final DateTime? maxDate;
/// True for a day no range takes; in nights ([unit]), for a day whose
/// night is taken. See [FlCalendarView.isUnavailable].
final bool Function(DateTime day)? isUnavailable;
/// What a range consumes: its days by default, its nights for a stay,
/// where the day whose night is taken remains a possible departure,
/// underlined and read "departure only" ([FlCalendarStrings.departureOnly],
/// then required).
final FlRangeUnit unit;
/// See [FlCalendarView.firstDayOfWeek].
final int? firstDayOfWeek;
/// See [FlCalendarView.strings].
final FlCalendarStrings? strings;
/// See [FlCalendarView.weeksInMonth].
final int? weeksInMonth;
/// See [FlCalendarView.showYearPicker].
final bool showYearPicker;
/// See [FlCalendarView.defaultYearPickerOpen].
final bool defaultYearPickerOpen;
/// See [FlCalendarView.enabled].
final bool enabled;
/// See [FlCalendarView.autofocus].
final bool autofocus;
/// See [FlCalendarView.semanticLabel].
final String? semanticLabel;
/// See [FlCalendarView.onMonthChanged].
final ValueChanged<DateTime>? onMonthChanged;
/// See [FlCalendarView.indicatorOf].
final String? Function(DateTime day)? indicatorOf;
@override
State<FlRangeCalendar> createState() => _FlRangeCalendarState();
}
class _FlRangeCalendarState extends State<FlRangeCalendar> {
/// The month shown: the start of [FlRangeCalendar.value] at first, then
/// that of a range set by the app. A range returned through `onChanged`
/// doesn't change it: the grid stays where the user closed the range.
late DateTime? _focus = widget.initialStart ?? widget.value?.start;
/// The last range returned through `onChanged`.
FlDateRange? _emitted;
/// The first day tapped, awaiting the second. It is only circled: the app
/// hasn't received it, and the screen never shows a range the app doesn't
/// have.
late DateTime? _anchor = switch (widget.initialStart) {
final start? when _canStart(flDateOnly(start)) => flDateOnly(start),
_ => null,
};
/// The nearest unavailable days before and after [_anchor].
DateTime? _gapBefore;
DateTime? _gapAfter;
@override
void initState() {
super.initState();
final anchor = _anchor;
if (anchor != null) _findGaps(anchor);
}
bool _unavailable(DateTime day) => widget.isUnavailable?.call(day) ?? false;
bool get _nights => widget.unit == FlRangeUnit.nights;
/// True when [day] can be the set start: available (in nights, its night
/// is free) and within bounds.
bool _canStart(DateTime day) {
final min = widget.minDate;
final max = widget.maxDate;
return !_unavailable(day) &&
(min == null || !day.isBefore(flDateOnly(min))) &&
(max == null || !day.isAfter(flDateOnly(max)));
}
/// In nights, a day whose night is taken and whose previous night is free,
/// within bounds: a range can end there.
bool _departureOnly(DateTime day) {
if (!_nights || !_unavailable(day)) return false;
final eve = DateTime(day.year, day.month, day.day - 1);
final min = widget.minDate;
final max = widget.maxDate;
return !_unavailable(eve) &&
(min == null || !eve.isBefore(flDateOnly(min))) &&
(max == null || !day.isAfter(flDateOnly(max)));
}
/// The nearest unavailable day after [from] in the direction of [step],
/// searched up to the bound, or ten years at most.
DateTime? _nearestGap(DateTime from, int step) {
final bound = step > 0 ? widget.maxDate : widget.minDate;
for (var i = 1; i <= _gapSearch; i++) {
final day = DateTime(from.year, from.month, from.day + step * i);
if (bound != null && (step > 0 ? day.isAfter(flDateOnly(bound)) : day.isBefore(flDateOnly(bound)))) {
break;
}
if (_unavailable(day)) return day;
}
return null;
}
void _findGaps(DateTime anchor) {
_gapBefore = _nearestGap(anchor, -1);
_gapAfter = _nearestGap(anchor, 1);
}
void _forget() => _anchor = _gapBefore = _gapAfter = null;
@override
void didUpdateWidget(FlRangeCalendar oldWidget) {
super.didUpdateWidget(oldWidget);
final value = widget.value;
if (value != oldWidget.value) {
_forget();
if (value != null && value != _emitted) _focus = value.start;
return;
}
final anchor = _anchor;
if (anchor != null &&
(widget.isUnavailable != oldWidget.isUnavailable ||
widget.minDate != oldWidget.minDate ||
widget.maxDate != oldWidget.maxDate)) {
// A start that became taken or out of bounds is dropped, as on
// closing: the range it would open would start on it.
if (_canStart(anchor)) {
_findGaps(anchor);
} else {
_forget();
}
}
}
FlDayMark _markOf(DateTime day) {
final range = widget.value;
if (range == null || !range.contains(day)) return FlDayMark.none;
final start = flSameDay(day, range.start);
final end = flSameDay(day, range.end);
return switch ((start, end)) {
(true, true) => FlDayMark.rangeSingle,
(true, false) => FlDayMark.rangeStart,
(false, true) => FlDayMark.rangeEnd,
(false, false) => FlDayMark.rangeMiddle,
};
}
void _press(DateTime day) {
final anchor = _anchor;
if (anchor == null) {
setState(() {
_anchor = day;
_findGaps(day);
});
return;
}
// In nights, the same day twice would make a stay with no night: the
// second tap drops the start, like Escape.
if (_nights && flSameDay(anchor, day)) {
setState(_forget);
return;
}
final range = FlDateRange.between(anchor, day);
setState(_forget);
_emitted = range;
widget.onChanged(range);
}
/// True for a day that can't be picked now: unavailable, or, after the
/// first tap, beyond the nearest unavailable one. In nights, that nearest
/// one after the start is the farthest end: the range doesn't consume its
/// night.
bool _blocked(DateTime day) {
final before = _gapBefore;
final after = _gapAfter;
final anchor = _anchor;
if (anchor == null) return _unavailable(day);
if (after != null && _nights && day.isAfter(anchor)) {
return day.isAfter(after);
}
return _unavailable(day) || (before != null && !day.isAfter(before)) || (after != null && !day.isBefore(after));
}
@override
Widget build(BuildContext context) {
assert(
!_nights || (widget.strings ?? FlCalendarStrings.of(context)).departureOnly != null,
'FlRangeCalendar in nights: strings.departureOnly reads the "departure only" day',
);
return FlCalendarView(
range: true,
markOf: _markOf,
pendingStart: _anchor,
onDayPressed: _press,
onEscape: () {
if (_anchor == null) return false;
setState(_forget);
return true;
},
focusedDate: widget.focusedDate ?? _focus,
today: widget.today,
minDate: widget.minDate,
maxDate: widget.maxDate,
isUnavailable: _blocked,
isDepartureOnly: _nights ? _departureOnly : null,
firstDayOfWeek: widget.firstDayOfWeek,
strings: widget.strings,
weeksInMonth: widget.weeksInMonth,
showYearPicker: widget.showYearPicker,
defaultYearPickerOpen: widget.defaultYearPickerOpen,
enabled: widget.enabled,
autofocus: widget.autofocus,
semanticLabel: widget.semanticLabel,
onMonthChanged: widget.onMonthChanged,
indicatorOf: widget.indicatorOf,
);
}
}