From db8854b084cd8e0974527b9764bf2815b880ecbc Mon Sep 17 00:00:00 2001 From: rmatharoo Date: Mon, 15 Jun 2026 19:03:05 -0700 Subject: [PATCH 1/2] fix(datepicker): collapse month/year grid after selection MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit NgdsCalendar drove its *ngIf views off the @Input() displayDepth, which round-trips through NgdsCalendarManager — leaving the date grid and the month/year grid visible at once. Track view depth in a local updated directly in the calendar's own click handlers so its *ngIf re-evaluates immediately; still emit changeDepth/Month/Year for nav. Also widen NgdsDropdown.dropdownClasses to string | string[]. --- projects/ngds-forms/package.json | 2 +- .../calendar/calendar/calendar.component.html | 20 ++++++------- .../calendar/calendar/calendar.component.ts | 28 +++++++++++++++++-- .../input-types/ngds-dropdown.component.ts | 2 +- 4 files changed, 38 insertions(+), 14 deletions(-) diff --git a/projects/ngds-forms/package.json b/projects/ngds-forms/package.json index f009633..6631455 100644 --- a/projects/ngds-forms/package.json +++ b/projects/ngds-forms/package.json @@ -1,6 +1,6 @@ { "name": "@digitalspace/ngds-forms", - "version": "0.0.129", + "version": "0.0.130", "peerDependencies": { "@angular/common": "^16.0.0", "@angular/core": "^16.0.0", diff --git a/projects/ngds-forms/src/lib/components/input-types/date-input/calendar/calendar/calendar.component.html b/projects/ngds-forms/src/lib/components/input-types/date-input/calendar/calendar/calendar.component.html index 79834ab..2aa964b 100644 --- a/projects/ngds-forms/src/lib/components/input-types/date-input/calendar/calendar/calendar.component.html +++ b/projects/ngds-forms/src/lib/components/input-types/date-input/calendar/calendar/calendar.component.html @@ -3,17 +3,17 @@
-
-
-
@@ -32,7 +32,7 @@

-
+
@@ -56,21 +56,21 @@
-
+
-
+
-
diff --git a/projects/ngds-forms/src/lib/components/input-types/date-input/calendar/calendar/calendar.component.ts b/projects/ngds-forms/src/lib/components/input-types/date-input/calendar/calendar/calendar.component.ts index 6fcb363..f54cb9e 100644 --- a/projects/ngds-forms/src/lib/components/input-types/date-input/calendar/calendar/calendar.component.ts +++ b/projects/ngds-forms/src/lib/components/input-types/date-input/calendar/calendar/calendar.component.ts @@ -128,9 +128,33 @@ export class NgdsCalendar implements OnInit { ], ]; + // Local view depth (0 = date, 1 = month, 2 = year). The calendar owns its own + // display state so the view updates from its own click handlers, rather than + // round-tripping the value out to the parent's @Input and back. + protected depth: number = 0; + ngOnInit(): void { - // set display to the minimum display depth. - this.displayDepth = this.minDisplayDepth; + this.depth = this.minDisplayDepth; + } + + // Switch the header between date/month/year views. + toggleDepth(index: number): void { + this.depth = (this.depth === index || index < this.minDisplayDepth) + ? this.minDisplayDepth + : index; + this.changeDepth.emit(index); + } + + // Collapse back to the base view after picking a month, then notify the parent. + onMonthSelected(monthNumber: number): void { + this.depth = this.minDisplayDepth; + this.changeMonth.emit(monthNumber); + } + + // Collapse back to the base view after picking a year, then notify the parent. + onYearSelected(year: number): void { + this.depth = this.minDisplayDepth; + this.changeYear.emit(year); } /** diff --git a/projects/ngds-forms/src/lib/components/input-types/ngds-dropdown.component.ts b/projects/ngds-forms/src/lib/components/input-types/ngds-dropdown.component.ts index 11e7a5d..61d384c 100644 --- a/projects/ngds-forms/src/lib/components/input-types/ngds-dropdown.component.ts +++ b/projects/ngds-forms/src/lib/components/input-types/ngds-dropdown.component.ts @@ -21,7 +21,7 @@ export class NgdsDropdown extends NgdsInput implements AfterViewInit { @Input() selectFirstItemOnEnter: boolean = true; // Custom classes to apply to the dropdown element - @Input() dropdownClasses: string + @Input() dropdownClasses: string | string[] // Whether to use dynamic positioning for the dropdown menu. If false, the dropdown will always be positioned statically below the input, which can prevent issues with certain parent elements that have overflow hidden or other positioning styles that interfere with Popper's ability to position the dropdown correctly. @Input() dynamicPositioning: boolean = true; From d5e9b23b3b6ebebcbfdbb7ad93af1fc0f507003b Mon Sep 17 00:00:00 2001 From: rmatharoo Date: Tue, 16 Jun 2026 15:18:06 -0700 Subject: [PATCH 2/2] fix(datepicker): keep both rangepicker calendars in sync when toggling month/year view --- .../calendar-manager.component.html | 2 - .../calendar-manager.component.ts | 44 +++++++++++++++---- .../calendar/calendar/calendar.component.ts | 19 +++++--- 3 files changed, 49 insertions(+), 16 deletions(-) diff --git a/projects/ngds-forms/src/lib/components/input-types/date-input/calendar/calendar-manager/calendar-manager.component.html b/projects/ngds-forms/src/lib/components/input-types/date-input/calendar/calendar-manager/calendar-manager.component.html index 3ef6841..8b02caf 100644 --- a/projects/ngds-forms/src/lib/components/input-types/date-input/calendar/calendar-manager/calendar-manager.component.html +++ b/projects/ngds-forms/src/lib/components/input-types/date-input/calendar/calendar-manager/calendar-manager.component.html @@ -5,7 +5,6 @@ [calendarConfig]="startCalendar" [disabledDatesFn]="disabledDatesFn" [dateRange]="dateRange" - [displayDepth]="displayDepth.value" [selectedDate]="selectedDate" [hoverDate]="hoverDate" [selectedEndDate]="selectedEndDate" @@ -30,7 +29,6 @@ [calendarConfig]="endCalendar" [disabledDatesFn]="disabledDatesFn" [dateRange]="dateRange" - [displayDepth]="displayDepth.value" [selectedDate]="selectedDate" [hoverDate]="hoverDate" [minDisplayDepth]="minDisplayDepth" diff --git a/projects/ngds-forms/src/lib/components/input-types/date-input/calendar/calendar-manager/calendar-manager.component.ts b/projects/ngds-forms/src/lib/components/input-types/date-input/calendar/calendar-manager/calendar-manager.component.ts index 0dd8d1e..afce0e8 100644 --- a/projects/ngds-forms/src/lib/components/input-types/date-input/calendar/calendar-manager/calendar-manager.component.ts +++ b/projects/ngds-forms/src/lib/components/input-types/date-input/calendar/calendar-manager/calendar-manager.component.ts @@ -1,7 +1,8 @@ -import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; +import { AfterViewInit, Component, EventEmitter, Input, OnInit, Output, QueryList, ViewChildren } from '@angular/core'; import { UntypedFormControl } from '@angular/forms'; import { DateTime, Interval } from 'luxon'; import { BehaviorSubject } from 'rxjs'; +import { NgdsCalendar } from '../calendar/calendar.component'; interface CalendarConfig { index: number, @@ -22,7 +23,7 @@ interface WeekConfig { templateUrl: './calendar-manager.component.html', styleUrls: ['../../../../../../../assets/styles/styles.scss'] }) -export class NgdsCalendarManager implements OnInit { +export class NgdsCalendarManager implements OnInit, AfterViewInit { // The control object @Input() control: UntypedFormControl; @@ -76,14 +77,28 @@ export class NgdsCalendarManager implements OnInit { protected startCalendar = new BehaviorSubject({ index: 1 }); protected endCalendar = new BehaviorSubject({ index: 2 }); - protected displayDepth = new BehaviorSubject(0); // date, 1 = month, 2 = year + // The authoritative display depth for the whole picker. This is the single source of + // truth: it both drives navigation maths (the end-calendar offset in toggleDepth and the + // arrow step unit in changeDisplayByValue) and, via syncDepth(), what grid each calendar + // renders. 0 = date, 1 = month, 2 = year. + protected displayDepth = new BehaviorSubject(0); + + // The rendered calendars (1 for a datepicker, 2 for a rangepicker), used to push the + // display depth into them synchronously when it changes. + @ViewChildren(NgdsCalendar) private calendars: QueryList; ngOnInit() { // set the current display depth to the minimum allowable amount. - this.displayDepth.next(this.minDisplayDepth); + this.syncDepth(this.minDisplayDepth); this.setToday(); } + ngAfterViewInit() { + // Seed both calendars with the current depth once they exist, so they start in sync + // regardless of their own initial value. + this.syncDepth(this.displayDepth.value); + } + /** * Set today. Set the calendar month to be based on today. * If rangepicker, also set the second calendar to be based on a month from today. @@ -192,7 +207,7 @@ export class NgdsCalendarManager implements OnInit { } this.updateCalendarConfig(newDate, calendar, sendDate); this.updateCalendarConfig(newDate.plus({ [incrementType]: increment }), otherCalendar, false); - this.displayDepth.next(this.minDisplayDepth); + this.syncDepth(this.minDisplayDepth); } /** @@ -217,7 +232,7 @@ export class NgdsCalendarManager implements OnInit { } this.updateCalendarConfig(newDate, calendar, sendDate); this.updateCalendarConfig(newDate.plus({ year: increment }), otherCalendar, false); - this.displayDepth.next(this.minDisplayDepth); + this.syncDepth(this.minDisplayDepth); } /** @@ -301,12 +316,25 @@ export class NgdsCalendarManager implements OnInit { this.updateCalendarConfig(this.startCalendar.value.date.plus({ months: 1 }), this.endCalendar); } if (this.displayDepth.value === index || index < this.minDisplayDepth) { - this.displayDepth.next(this.minDisplayDepth); + this.syncDepth(this.minDisplayDepth); } else { - this.displayDepth.next(index); + this.syncDepth(index); } } + /** + * Updates the authoritative display depth and pushes it into both calendars + * synchronously, so their views collapse together within the originating click — before + * change detection runs. (During init the calendars aren't queryable yet; they seed + * their own depth in ngOnInit, and ngAfterViewInit re-pushes once they exist, so the + * optional-chained push is a safe no-op then.) + * @param depth the display depth to apply (0 = date, 1 = month, 2 = year) + */ + syncDepth(depth: number) { + this.displayDepth.next(depth); + this.calendars?.forEach(calendar => calendar.setDepth(depth)); + } + /** * Update the current value of the last date that was hovered over. * @param date `luxon` DateTime that was hovered over. diff --git a/projects/ngds-forms/src/lib/components/input-types/date-input/calendar/calendar/calendar.component.ts b/projects/ngds-forms/src/lib/components/input-types/date-input/calendar/calendar/calendar.component.ts index f54cb9e..f5f71b3 100644 --- a/projects/ngds-forms/src/lib/components/input-types/date-input/calendar/calendar/calendar.component.ts +++ b/projects/ngds-forms/src/lib/components/input-types/date-input/calendar/calendar/calendar.component.ts @@ -16,9 +16,6 @@ export class NgdsCalendar implements OnInit { // A function that returns a boolean when provided a DateTime. DateTimes that return true are disabled in the calendar. @Input() disabledDatesFn; - // The current precision of the datepicker. - @Input() displayDepth: number = 0; // date, 1 = month, 2 = year - // The precision of the datepicker. By default (0), dates can be picked. // If minDisplayDepth is 1 or 2, only months or years will be displayed and enabled, respectively. // When picking months or years, the datepicker will return the luxon startOf('term'), where @@ -128,15 +125,25 @@ export class NgdsCalendar implements OnInit { ], ]; - // Local view depth (0 = date, 1 = month, 2 = year). The calendar owns its own - // display state so the view updates from its own click handlers, rather than - // round-tripping the value out to the parent's @Input and back. + // Local view depth — which grid this calendar currently shows: + // 0 = date grid (days), 1 = month grid, 2 = year grid. + // The calendar writes this directly in its own click handlers so its *ngIf collapses + // synchronously. NgdsCalendarManager — which owns the authoritative depth — also pushes + // the value in via setDepth(), so the two calendars of a rangepicker always show the + // same grid. Both writes happen synchronously within the originating click, before + // change detection runs. protected depth: number = 0; ngOnInit(): void { this.depth = this.minDisplayDepth; } + // Called by NgdsCalendarManager to keep both calendars' depth in sync. Invoked + // synchronously during the originating click, so it lands before change detection runs. + setDepth(depth: number): void { + this.depth = depth; + } + // Switch the header between date/month/year views. toggleDepth(index: number): void { this.depth = (this.depth === index || index < this.minDisplayDepth)