From db055c96008a609dcd517b551d4d940078fcc105 Mon Sep 17 00:00:00 2001 From: Lejard Hadrien Date: Thu, 20 Dec 2018 15:36:15 +0100 Subject: [PATCH 1/4] add supportsMonthSelector to MaterialDatepickerComponent --- .../material_datepicker.dart | 71 +++++++++++++++- .../material_datepicker.html | 40 ++++++++-- .../material_datepicker.scss | 80 ++++++++++++++++++- 3 files changed, 177 insertions(+), 14 deletions(-) diff --git a/angular_components/lib/material_datepicker/material_datepicker.dart b/angular_components/lib/material_datepicker/material_datepicker.dart index fd8253377..3ca6971b9 100644 --- a/angular_components/lib/material_datepicker/material_datepicker.dart +++ b/angular_components/lib/material_datepicker/material_datepicker.dart @@ -6,6 +6,10 @@ import 'dart:async'; import 'dart:html'; import 'package:angular/angular.dart'; +import 'package:angular_components/material_datepicker/material_month_picker.dart'; +import 'package:angular_components/material_icon/material_icon.dart'; +import 'package:angular_components/utils/browser/dom_service/dom_service.dart'; +import 'package:angular_components/utils/showhide/showhide.dart'; import 'package:intl/intl.dart'; import 'package:quiver/time.dart'; import 'package:angular_components/button_decorator/button_decorator.dart'; @@ -60,6 +64,9 @@ import 'package:angular_components/utils/angular/css/css.dart'; NgFor, NgIf, PopupSourceDirective, + MaterialMonthPickerComponent, + ShowHideDirective, + MaterialIconComponent, ], providers: [ Provider(HasDisabled, useExisting: MaterialDatepickerComponent), @@ -282,11 +289,67 @@ class MaterialDatepickerComponent @Input() String error; + /// Whether to display the month selector dropdown. + /// + /// Defaults to true. + @Input() + bool supportsMonthSelector = true; + + @ViewChild(MaterialCalendarPickerComponent) + MaterialCalendarPickerComponent calendarPicker; + + @ViewChild(MaterialMonthPickerComponent) + MaterialMonthPickerComponent monthSelector; + + void onMonthSelectorDropdownClicked() { + showMonthSelector = !showMonthSelector; + if (showMonthSelector) { + _domService.scheduleWrite(() { + monthSelector.scrollToYear(_visibleMonth.year); + }); + } + } + + set monthSelectorState(CalendarState state) { + _monthSelectorState = state; + if (state.has(state.currentSelection)) { + // A month was selected - switch back to the calendar picker and scroll + // the month into view. + showMonthSelector = false; + _monthSelectorState = + CalendarState.empty(resolution: CalendarResolution.months); + final selectedMonth = state.selection(state.currentSelection); + _domService.scheduleWrite(() { + calendarPicker.scrollToDate(selectedMonth.start); + }); + } + } + + CalendarState get monthSelectorState => _monthSelectorState; + CalendarState _monthSelectorState = + CalendarState.empty(resolution: CalendarResolution.months); + + static final _monthFormatter = DateFormat.yMMM(); + Date _visibleMonth; + + String get visibleMonthName => _visibleMonthName; + String _visibleMonthName = ''; + + void onVisibleMonthChange(Date month) { + _visibleMonth = month; + _visibleMonthName = _monthFormatter.format(month.asUtcTime()); + } + + bool showMonthSelector = false; + + final DomService _domService; + MaterialDatepickerComponent( - HtmlElement element, - @Attribute('popupClass') String popupClass, - @Optional() @Inject(datepickerClock) Clock clock) - : popupClassName = constructEncapsulatedCss(popupClass, element.classes) { + HtmlElement element, + @Attribute('popupClass') String popupClass, + @Optional() @Inject(datepickerClock) Clock clock, + this._domService, + ) : popupClassName = constructEncapsulatedCss(popupClass, element.classes) { clock ??= Clock(); // Init minDate and maxDate to sensible defaults diff --git a/angular_components/lib/material_datepicker/material_datepicker.html b/angular_components/lib/material_datepicker/material_datepicker.html index 553faeb8e..0ce543394 100644 --- a/angular_components/lib/material_datepicker/material_datepicker.html +++ b/angular_components/lib/material_datepicker/material_datepicker.html @@ -42,6 +42,18 @@ +
+
+ {{visibleMonthName}} + +
+
+
@@ -53,14 +65,26 @@
- - +
+ + + + +
diff --git a/angular_components/lib/material_datepicker/material_datepicker.scss b/angular_components/lib/material_datepicker/material_datepicker.scss index bbbd38fe7..a32e597d4 100644 --- a/angular_components/lib/material_datepicker/material_datepicker.scss +++ b/angular_components/lib/material_datepicker/material_datepicker.scss @@ -40,8 +40,13 @@ $main-font-size: 13px; } } -.popup-content.compact .date-input { - padding: 0 $picker-compact-horizontal-padding; +.popup-content.compact { + + .date-input, + .month-selector-toolbar { + padding: 0 $picker-compact-horizontal-padding; + } + } .icon { @@ -79,3 +84,74 @@ material-select-item { padding-bottom: 0; } } + +// note(lejard-h) share month selector style with date_range_editor ? +.picker-container { + @include calendar-height(7); + position: relative; + overflow: hidden; + flex-grow: 1; + + &.compact { + @include calendar-compact-height(7); + } +} + +.calendar-picker { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + transform: translateY(0); + transition: transform $mat-transition $mat-transition-standard; + will-change: transform; + + &.acx-showhide-hide { + transform: translateY(100%); + } + + &.acx-showhide-hidden { + visibility: hidden; + } +} + +.month-selector { + border-top: 1px solid $mat-border-light; + + &.acx-showhide-hide { + transform: translateY(-100%); + } +} + +.month-selector-toolbar { + align-items: center; + color: $mat-transparent-black; + display: flex; + flex-shrink: 0; + margin-bottom: $picker-horizontal-padding; + padding: 0 $picker-horizontal-padding; +} + +.month-selector-dropdown { + display: flex; + align-items: center; + margin-right: auto; + cursor: pointer; +} + +.month-selector-dropdown-icon { + will-change: transform; + transition: transform $mat-transition $mat-transition-standard; + + &.flipped { + transform: scaleY(-1); + } +} + +.visible-month { + // TODO(google): Migrate to extended mixin mat-font-body-2 + font-size: $mat-font-size-body; + font-weight: $mat-font-weight-medium; + text-transform: uppercase; +} \ No newline at end of file From fdd67d9b2d1576907d84d8c17de788c6a50c8fda Mon Sep 17 00:00:00 2001 From: Lejard Hadrien Date: Thu, 24 Jan 2019 11:20:36 +0100 Subject: [PATCH 2/4] add next prev buttons --- .../date_range_editor.dart | 36 ++----------------- .../material_datepicker.dart | 30 ++++++++++++++-- .../material_datepicker.html | 3 ++ .../next_prev_buttons.dart | 34 ++++++++++++++++++ 4 files changed, 67 insertions(+), 36 deletions(-) diff --git a/angular_components/lib/material_datepicker/date_range_editor.dart b/angular_components/lib/material_datepicker/date_range_editor.dart index 6ce45c4ae..3be7b29fb 100644 --- a/angular_components/lib/material_datepicker/date_range_editor.dart +++ b/angular_components/lib/material_datepicker/date_range_editor.dart @@ -34,7 +34,6 @@ import 'package:angular_components/material_select/material_select_item.dart'; import 'package:angular_components/material_tooltip/material_tooltip.dart'; import 'package:angular_components/model/date/date.dart'; import 'package:angular_components/model/date/date_formatter.dart'; -import 'package:angular_components/model/observable/observable.dart'; import 'package:angular_components/utils/angular/managed_zone/interface.dart'; import 'package:angular_components/utils/angular/scroll_host/angular_2.dart'; import 'package:angular_components/utils/browser/dom_service/dom_service.dart'; @@ -335,7 +334,7 @@ class DateRangeEditorComponent implements OnInit, AfterViewInit, Focusable { _clock ??= legacyClock; _today = Date.today(_clock); editorHost?.dateRangeEditorCreated(this); - nextPrevModel = DateRangeEditorNextPrevModel(onNext: () { + nextPrevModel = DatepickerNextPrevModel(onNext: () { calendarPicker.scrollToDate(_visibleMonth.add(months: 1)); }, onPrev: () { calendarPicker.scrollToDate(_visibleMonth.add(months: -1)); @@ -493,7 +492,7 @@ class DateRangeEditorComponent implements OnInit, AfterViewInit, Focusable { } /// The model for scrolling to the next or previous month. - DateRangeEditorNextPrevModel nextPrevModel; + DatepickerNextPrevModel nextPrevModel; bool showMonthSelector = false; @@ -554,34 +553,3 @@ class DateRangeEditorComponent implements OnInit, AfterViewInit, Focusable { name: 'DateRangeEditorComponent_rangeDisabledTooltip', desc: 'Message that explains why a date range is invalid.'); } - -typedef void NextPrevCallback(); - -class DateRangeEditorNextPrevModel implements Sequential { - final NextPrevCallback onNext; - final NextPrevCallback onPrev; - - DateRangeEditorNextPrevModel({this.onNext, this.onPrev}); - - @override - ObservableReference hasNext = ObservableReference(false); - - @override - ObservableReference hasPrev = ObservableReference(false); - - @override - void next() => onNext(); - - @override - void prev() => onPrev(); - - void update(Date visibleMonth, Date minDate, Date maxDate) { - if (visibleMonth == null) return; - hasPrev.value = compareDatesAtResolution( - visibleMonth, minDate, CalendarResolution.months) > - 0; - hasNext.value = compareDatesAtResolution( - visibleMonth, maxDate, CalendarResolution.months) < - 0; - } -} diff --git a/angular_components/lib/material_datepicker/material_datepicker.dart b/angular_components/lib/material_datepicker/material_datepicker.dart index 3ca6971b9..569ad62d8 100644 --- a/angular_components/lib/material_datepicker/material_datepicker.dart +++ b/angular_components/lib/material_datepicker/material_datepicker.dart @@ -7,6 +7,7 @@ import 'dart:html'; import 'package:angular/angular.dart'; import 'package:angular_components/material_datepicker/material_month_picker.dart'; +import 'package:angular_components/material_datepicker/next_prev_buttons.dart'; import 'package:angular_components/material_icon/material_icon.dart'; import 'package:angular_components/utils/browser/dom_service/dom_service.dart'; import 'package:angular_components/utils/showhide/showhide.dart'; @@ -67,6 +68,7 @@ import 'package:angular_components/utils/angular/css/css.dart'; MaterialMonthPickerComponent, ShowHideDirective, MaterialIconComponent, + NextPrevComponent, ], providers: [ Provider(HasDisabled, useExisting: MaterialDatepickerComponent), @@ -95,7 +97,14 @@ class MaterialDatepickerComponent /// date which makes sense in your domain context. e.g. For apps which analyse /// historical data, this could be the current day. @Input() - Date maxDate; + set maxDate(Date d) { + _maxDate = d; + nextPrevModel.update(_visibleMonth, minDate, maxDate); + } + + Date _maxDate; + + Date get maxDate => _maxDate; /// Dates earlier than `minDate` cannot be chosen. /// @@ -103,7 +112,14 @@ class MaterialDatepickerComponent /// makes sense in your domain context. e.g. The earliest date for which data /// is available for analysis. @Input() - Date minDate; + set minDate(Date d) { + _minDate = d; + nextPrevModel.update(_visibleMonth, minDate, maxDate); + } + + Date _minDate; + + Date get minDate => _minDate; /// Whether to enable compact calendar styles. @Input() @@ -338,8 +354,12 @@ class MaterialDatepickerComponent void onVisibleMonthChange(Date month) { _visibleMonth = month; _visibleMonthName = _monthFormatter.format(month.asUtcTime()); + nextPrevModel.update(_visibleMonth, minDate, maxDate); } + /// The model for scrolling to the next or previous month. + DatepickerNextPrevModel nextPrevModel; + bool showMonthSelector = false; final DomService _domService; @@ -350,6 +370,12 @@ class MaterialDatepickerComponent @Optional() @Inject(datepickerClock) Clock clock, this._domService, ) : popupClassName = constructEncapsulatedCss(popupClass, element.classes) { + nextPrevModel = DatepickerNextPrevModel(onNext: () { + calendarPicker.scrollToDate(_visibleMonth.add(months: 1)); + }, onPrev: () { + calendarPicker.scrollToDate(_visibleMonth.add(months: -1)); + }); + clock ??= Clock(); // Init minDate and maxDate to sensible defaults diff --git a/angular_components/lib/material_datepicker/material_datepicker.html b/angular_components/lib/material_datepicker/material_datepicker.html index 0ce543394..974425561 100644 --- a/angular_components/lib/material_datepicker/material_datepicker.html +++ b/angular_components/lib/material_datepicker/material_datepicker.html @@ -52,6 +52,9 @@ class="month-selector-dropdown-icon" [class.flipped]="showMonthSelector"> + +
hasNext = ObservableReference(false); + + @override + ObservableReference hasPrev = ObservableReference(false); + + @override + void next() => onNext(); + + @override + void prev() => onPrev(); + + void update(Date visibleMonth, Date minDate, Date maxDate) { + if (visibleMonth == null) return; + hasPrev.value = compareDatesAtResolution( + visibleMonth, minDate, CalendarResolution.months) > + 0; + hasNext.value = compareDatesAtResolution( + visibleMonth, maxDate, CalendarResolution.months) < + 0; + } +} \ No newline at end of file From 41e0633eeec6faf8881f809a53d41ebbc49f88b8 Mon Sep 17 00:00:00 2001 From: Lejard Hadrien Date: Wed, 30 Jan 2019 15:51:50 +0100 Subject: [PATCH 3/4] fix next prev model --- .../date_range_editor.dart | 34 +++++++++++++++++-- .../material_datepicker.dart | 33 ++++++++++++++++++ .../next_prev_buttons.dart | 32 ----------------- 3 files changed, 65 insertions(+), 34 deletions(-) diff --git a/angular_components/lib/material_datepicker/date_range_editor.dart b/angular_components/lib/material_datepicker/date_range_editor.dart index 3be7b29fb..08733e6e1 100644 --- a/angular_components/lib/material_datepicker/date_range_editor.dart +++ b/angular_components/lib/material_datepicker/date_range_editor.dart @@ -34,6 +34,7 @@ import 'package:angular_components/material_select/material_select_item.dart'; import 'package:angular_components/material_tooltip/material_tooltip.dart'; import 'package:angular_components/model/date/date.dart'; import 'package:angular_components/model/date/date_formatter.dart'; +import 'package:angular_components/model/observable/observable.dart'; import 'package:angular_components/utils/angular/managed_zone/interface.dart'; import 'package:angular_components/utils/angular/scroll_host/angular_2.dart'; import 'package:angular_components/utils/browser/dom_service/dom_service.dart'; @@ -334,7 +335,7 @@ class DateRangeEditorComponent implements OnInit, AfterViewInit, Focusable { _clock ??= legacyClock; _today = Date.today(_clock); editorHost?.dateRangeEditorCreated(this); - nextPrevModel = DatepickerNextPrevModel(onNext: () { + nextPrevModel = DateRangeEditorNextPrevModel(onNext: () { calendarPicker.scrollToDate(_visibleMonth.add(months: 1)); }, onPrev: () { calendarPicker.scrollToDate(_visibleMonth.add(months: -1)); @@ -492,7 +493,7 @@ class DateRangeEditorComponent implements OnInit, AfterViewInit, Focusable { } /// The model for scrolling to the next or previous month. - DatepickerNextPrevModel nextPrevModel; + DateRangeEditorNextPrevModel nextPrevModel; bool showMonthSelector = false; @@ -553,3 +554,32 @@ class DateRangeEditorComponent implements OnInit, AfterViewInit, Focusable { name: 'DateRangeEditorComponent_rangeDisabledTooltip', desc: 'Message that explains why a date range is invalid.'); } + +class DateRangeEditorNextPrevModel implements Sequential { + final NextPrevCallback onNext; + final NextPrevCallback onPrev; + + DateRangeEditorNextPrevModel({this.onNext, this.onPrev}); + + @override + ObservableReference hasNext = ObservableReference(false); + + @override + ObservableReference hasPrev = ObservableReference(false); + + @override + void next() => onNext(); + + @override + void prev() => onPrev(); + + void update(Date visibleMonth, Date minDate, Date maxDate) { + if (visibleMonth == null) return; + hasPrev.value = compareDatesAtResolution( + visibleMonth, minDate, CalendarResolution.months) > + 0; + hasNext.value = compareDatesAtResolution( + visibleMonth, maxDate, CalendarResolution.months) < + 0; + } +} diff --git a/angular_components/lib/material_datepicker/material_datepicker.dart b/angular_components/lib/material_datepicker/material_datepicker.dart index 569ad62d8..a6ae67b9a 100644 --- a/angular_components/lib/material_datepicker/material_datepicker.dart +++ b/angular_components/lib/material_datepicker/material_datepicker.dart @@ -9,6 +9,7 @@ import 'package:angular/angular.dart'; import 'package:angular_components/material_datepicker/material_month_picker.dart'; import 'package:angular_components/material_datepicker/next_prev_buttons.dart'; import 'package:angular_components/material_icon/material_icon.dart'; +import 'package:angular_components/model/observable/observable.dart'; import 'package:angular_components/utils/browser/dom_service/dom_service.dart'; import 'package:angular_components/utils/showhide/showhide.dart'; import 'package:intl/intl.dart'; @@ -384,3 +385,35 @@ class MaterialDatepickerComponent maxDate = Date(now.year + 10, DateTime.december, 31); } } + +class DatepickerNextPrevModel implements Sequential { + final NextPrevCallback onNext; + final NextPrevCallback onPrev; + + DatepickerNextPrevModel({this.onNext, this.onPrev}); + + @override + ObservableReference hasNext = ObservableReference(false); + + @override + ObservableReference hasPrev = ObservableReference(false); + + @override + void next() => onNext(); + + @override + void prev() => onPrev(); + + void update(Date visibleMonth, Date minDate, Date maxDate) { + if (visibleMonth == null) return; + + hasPrev.value = minDate != null && + compareDatesAtResolution( + visibleMonth, minDate, CalendarResolution.months) > + 0; + hasNext.value = maxDate != null && + compareDatesAtResolution( + visibleMonth, maxDate, CalendarResolution.months) < + 0; + } +} diff --git a/angular_components/lib/material_datepicker/next_prev_buttons.dart b/angular_components/lib/material_datepicker/next_prev_buttons.dart index 9a802fce3..2b3cca182 100644 --- a/angular_components/lib/material_datepicker/next_prev_buttons.dart +++ b/angular_components/lib/material_datepicker/next_prev_buttons.dart @@ -5,9 +5,6 @@ import 'dart:html' show Event; import 'package:angular/angular.dart'; -import 'package:angular_components/material_datepicker/calendar.dart'; -import 'package:angular_components/model/date/date.dart'; -import 'package:angular_components/model/observable/observable.dart'; import 'package:intl/intl.dart'; import 'package:angular_components/src/material_datepicker/sequential.dart'; import 'package:angular_components/material_icon/material_icon.dart'; @@ -134,32 +131,3 @@ class NextPrevComponent implements OnDestroy { } typedef void NextPrevCallback(); - -class DatepickerNextPrevModel implements Sequential { - final NextPrevCallback onNext; - final NextPrevCallback onPrev; - - DatepickerNextPrevModel({this.onNext, this.onPrev}); - - @override - ObservableReference hasNext = ObservableReference(false); - - @override - ObservableReference hasPrev = ObservableReference(false); - - @override - void next() => onNext(); - - @override - void prev() => onPrev(); - - void update(Date visibleMonth, Date minDate, Date maxDate) { - if (visibleMonth == null) return; - hasPrev.value = compareDatesAtResolution( - visibleMonth, minDate, CalendarResolution.months) > - 0; - hasNext.value = compareDatesAtResolution( - visibleMonth, maxDate, CalendarResolution.months) < - 0; - } -} \ No newline at end of file From 234092b4d3d563e2f221d4b7f849e94145ec1aa5 Mon Sep 17 00:00:00 2001 From: Hadrien Lejard Date: Thu, 13 Aug 2020 17:50:59 +0200 Subject: [PATCH 4/4] cleanup --- .../lib/material_datepicker/date_range_editor.dart | 2 -- 1 file changed, 2 deletions(-) diff --git a/angular_components/lib/material_datepicker/date_range_editor.dart b/angular_components/lib/material_datepicker/date_range_editor.dart index c7a606814..dbb899f00 100644 --- a/angular_components/lib/material_datepicker/date_range_editor.dart +++ b/angular_components/lib/material_datepicker/date_range_editor.dart @@ -630,8 +630,6 @@ class DateRangeEditorComponent implements OnInit, AfterViewInit, Focusable { desc: 'Message that explains why a date range is invalid.'); } -typedef NextPrevCallback = void Function(); - class DateRangeEditorNextPrevModel implements Sequential { final NextPrevCallback onNext; final NextPrevCallback onPrev;