@brickclay-org/ui 0.1.87 → 0.1.88

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/index.d.ts CHANGED
@@ -1,13 +1,13 @@
1
1
  import * as _angular_core from '@angular/core';
2
- import { EventEmitter, OnInit, OnDestroy, OnChanges, ElementRef, Renderer2, SimpleChanges, AfterViewInit, QueryList, ComponentRef, Type, InjectionToken, NgZone, ChangeDetectorRef, TemplateRef, WritableSignal } from '@angular/core';
2
+ import { EventEmitter, OnInit, OnDestroy, OnChanges, ElementRef, SimpleChanges, AfterViewInit, QueryList, ComponentRef, Type, InjectionToken, NgZone, Renderer2, ChangeDetectorRef, TemplateRef, WritableSignal } from '@angular/core';
3
3
  import { ControlValueAccessor, Validator, AbstractControl, ValidationErrors, NgControl, NgModel } from '@angular/forms';
4
+ import * as i2 from '@angular/cdk/overlay';
5
+ import { CdkConnectedOverlay, ConnectedPosition, ConnectedOverlayPositionChange, ScrollStrategy } from '@angular/cdk/overlay';
4
6
  import * as rxjs from 'rxjs';
5
7
  import { Observable } from 'rxjs';
6
8
  import * as i1 from '@angular/common';
7
9
  import * as i1$3 from '@angular/cdk/drag-drop';
8
10
  import { CdkDragDrop, CdkDragMove, CdkDragStart } from '@angular/cdk/drag-drop';
9
- import * as i2 from '@angular/cdk/overlay';
10
- import { CdkConnectedOverlay, ConnectedOverlayPositionChange, ConnectedPosition, ScrollStrategy } from '@angular/cdk/overlay';
11
11
  import { NgxMaskDirective } from 'ngx-mask';
12
12
  import * as i1$2 from '@angular/cdk/dialog';
13
13
  import { CdkDialogContainer, DialogRef } from '@angular/cdk/dialog';
@@ -66,6 +66,16 @@ interface CustomRangesConfig {
66
66
  }
67
67
  declare class BkCalendarManagerService {
68
68
  private calendarInstances;
69
+ /**
70
+ * Separate from {@link calendarInstances} on purpose: a bk-time-picker embedded inside an open
71
+ * bk-custom-calendar (enableTimepicker) must NOT close its own parent calendar when it opens.
72
+ * If time-pickers shared the calendar registry, opening the embedded picker would call
73
+ * closeAllExcept(thatPicker'sCloseFn) — which closes the parent calendar too, since its
74
+ * close-fn is in the same set and isn't the "except" one. Keeping the registries independent
75
+ * means time-pickers only ever coordinate with other time-pickers, calendars only with other
76
+ * calendars — each family closes its own siblings, never the other family.
77
+ */
78
+ private timePickerInstances;
69
79
  private closeAllSubject;
70
80
  closeAll$: rxjs.Observable<void>;
71
81
  customRanges: Record<string, CalendarRange>;
@@ -86,6 +96,16 @@ declare class BkCalendarManagerService {
86
96
  * Close all calendars except the one being opened
87
97
  */
88
98
  closeAllExcept(exceptCloseFn: () => void): void;
99
+ /**
100
+ * Register a time-picker instance with its close function — separate registry from calendars,
101
+ * see {@link timePickerInstances}.
102
+ */
103
+ registerTimePicker(closeFn: () => void): () => void;
104
+ /**
105
+ * Close all time-pickers except the one being opened. Never touches calendarInstances — see
106
+ * {@link timePickerInstances}.
107
+ */
108
+ closeAllTimePickersExcept(exceptCloseFn: () => void): void;
89
109
  /**
90
110
  * Close all calendars
91
111
  */
@@ -95,6 +115,43 @@ declare class BkCalendarManagerService {
95
115
  static ɵprov: _angular_core.ɵɵInjectableDeclaration<BkCalendarManagerService>;
96
116
  }
97
117
 
118
+ /**
119
+ * 1-based month numbers (January = 1 … December = 12) — deliberately NOT JS `Date`'s 0-based
120
+ * `getMonth()` convention, so callers reading `allowedMonths` see the calendar month number they'd
121
+ * expect. Backs {@link BkCustomCalendar.allowedMonths} so callers can write `CalendarMonth.March`
122
+ * instead of a bare `3`. {@link BkCustomCalendar.allowedMonthsZeroBased} converts back to 0-based
123
+ * for every internal comparison against `Date.getMonth()`/the month grid's loop index.
124
+ */
125
+ declare enum CalendarMonth {
126
+ January = 1,
127
+ February = 2,
128
+ March = 3,
129
+ April = 4,
130
+ May = 5,
131
+ June = 6,
132
+ July = 7,
133
+ August = 8,
134
+ September = 9,
135
+ October = 10,
136
+ November = 11,
137
+ December = 12
138
+ }
139
+ /**
140
+ * 1-based day-of-week numbers (Sunday = 1 … Saturday = 7) — same reasoning as {@link
141
+ * CalendarMonth}: deliberately NOT JS `Date`'s 0-based `getDay()` convention, so a bare `0` in
142
+ * `allowedDaysOfWeek` can't be mistaken for "unset" or silently match the wrong day. Backs
143
+ * {@link BkCustomCalendar.allowedDaysOfWeek}. {@link BkCustomCalendar.allowedDaysOfWeekZeroBased}
144
+ * converts back to 0-based for every internal comparison against `Date.getDay()`.
145
+ */
146
+ declare enum CalendarWeekday {
147
+ Sunday = 1,
148
+ Monday = 2,
149
+ Tuesday = 3,
150
+ Wednesday = 4,
151
+ Thursday = 5,
152
+ Friday = 6,
153
+ Saturday = 7
154
+ }
98
155
  declare class CalendarSelection {
99
156
  startDate: string | null;
100
157
  endDate: string | null;
@@ -103,23 +160,53 @@ declare class CalendarSelection {
103
160
  /** End time in 12-hour format with AM/PM (e.g. "2:00 AM"). */
104
161
  endTime: string | null;
105
162
  selectedDates?: string[];
163
+ /**
164
+ * Set only when `pickerView` is 'month' (paired with {@link selectedYear}) — the selected
165
+ * month, 0-11. `startDate`/`endDate` stay null in this mode; there is no day-level value.
166
+ */
167
+ selectedMonth?: number | null;
168
+ /** Set when `pickerView` is 'year' (alone) or 'month' (paired with {@link selectedMonth}). */
169
+ selectedYear?: number | null;
106
170
  }
107
171
  declare class BkCustomCalendar implements OnInit, OnDestroy, OnChanges, ControlValueAccessor, Validator {
108
172
  private calendarManager;
109
- private renderer;
110
173
  enableTimepicker: boolean;
111
174
  autoApply: boolean;
112
175
  closeOnAutoApply: boolean;
113
176
  showCancel: boolean;
114
177
  linkedCalendars: boolean;
115
178
  singleDatePicker: boolean;
179
+ /**
180
+ * Restricts selection to just a month or just a year, skipping the day grid entirely. Only
181
+ * takes effect with `singleDatePicker` and `!dualCalendar` — ignored (falls back to normal
182
+ * day-grid behaviour) for range/dual selection, which isn't supported yet. Picking a
183
+ * month/year is the final action: it commits immediately (like `autoApply`) and populates only
184
+ * `selectedMonth`/`selectedYear` on the emitted {@link CalendarSelection} — `startDate` stays
185
+ * null, there is no day-level value to give it.
186
+ */
187
+ pickerView: 'day' | 'month' | 'year';
116
188
  showWeekNumbers: boolean;
117
189
  showISOWeekNumbers: boolean;
118
190
  customRangeDirection: boolean;
119
191
  lockStartDate: boolean;
120
- position: 'center' | 'left' | 'right';
121
- /** Vertical placement relative to the input. When explicitly set, overrides viewport-space detection entirely. */
122
- popupPosition?: 'top' | 'bottom';
192
+ /**
193
+ * @deprecated Use {@link opens} instead same 'left' | 'right' | 'center' values, same meaning.
194
+ * Kept, and still honoured, only so existing `[position]="..."` bindings keep working; when set,
195
+ * it takes priority over `opens` (see {@link horizontalAlign}). No known consumers currently set
196
+ * this — safe to remove once none do.
197
+ */
198
+ position?: 'center' | 'left' | 'right';
199
+ /**
200
+ * Preferred vertical side of the popup relative to the input. The opposite side is always
201
+ * offered as a CDK flip fallback (see {@link computeCalendarPositions}) — there is no way to
202
+ * lock the popup to one side regardless of available space.
203
+ */
204
+ popupPosition: 'top' | 'bottom';
205
+ /**
206
+ * @deprecated No-op. Use {@link popupPosition} ('bottom' | 'top') instead — 'down'/'up' were
207
+ * renamed to 'bottom'/'top' to match it. Auto-flip is always on now, so this no longer has a
208
+ * "never flips" mode to opt out of either. Safe to remove from call sites.
209
+ */
123
210
  drop: 'up' | 'down';
124
211
  dualCalendar: boolean;
125
212
  showRanges: boolean;
@@ -132,25 +219,80 @@ declare class BkCustomCalendar implements OnInit, OnDestroy, OnChanges, ControlV
132
219
  multiDateSelection: boolean;
133
220
  maxDate?: Date;
134
221
  minDate?: Date;
222
+ /**
223
+ * Allow-list of selectable months, for the month grid — e.g.
224
+ * `[CalendarMonth.January, CalendarMonth.March]` for Jan/Mar only. 1-based (January = 1 …
225
+ * December = 12) — a bare `0` won't match January here, unlike JS `Date.getMonth()`. Unset
226
+ * (default) means no restriction beyond `minDate`/`maxDate`. Combines with them: a month must
227
+ * satisfy both to be selectable. Backs every month grid ({@link isMonthDisabled}) — `pickerView`
228
+ * 'month' and the day grid's own month quick-pick alike.
229
+ */
230
+ allowedMonths?: CalendarMonth[];
231
+ /**
232
+ * Allow-list of selectable years, for the year grid — e.g. `[2025, 2027]`. Unset (default) means
233
+ * no restriction beyond `minDate`/`maxDate`. Combines with them the same way as
234
+ * {@link allowedMonths}. Backs every year grid ({@link isYearDisabled}).
235
+ */
236
+ allowedYears?: number[];
237
+ /**
238
+ * Allow-list of selectable weekdays, for the day grid — e.g.
239
+ * `[CalendarWeekday.Monday, ..., CalendarWeekday.Friday]` for business days only. 1-based
240
+ * (Sunday = 1 … Saturday = 7) — a bare `0` won't match Sunday here, unlike JS `Date.getDay()`.
241
+ * Unset (default) means no restriction. Unlike `allowedMonths`/`allowedYears`, this is
242
+ * day-granularity only — it does NOT disable a whole month/year grid cell just because some of
243
+ * its days fall on a disallowed weekday, since a month/year still has other selectable days.
244
+ * Combines (AND) with every other constraint. Backs {@link isDateDisabled}.
245
+ */
246
+ allowedDaysOfWeek?: CalendarWeekday[];
247
+ /**
248
+ * Blackout list — specific dates that are never selectable regardless of every other
249
+ * constraint (minDate/maxDate/allowedMonths/allowedYears/allowedDaysOfWeek all still apply on
250
+ * top; this only ever narrows further, e.g. for holidays or dates already booked elsewhere).
251
+ * Compared by calendar day (year/month/date), time-of-day is ignored. Backs {@link
252
+ * isDateDisabled}.
253
+ */
254
+ disabledDates?: Date[];
255
+ /**
256
+ * Allow-list of specific selectable dates — same AND semantics as `allowedMonths`/
257
+ * `allowedYears`/`allowedDaysOfWeek` (a date must satisfy this AND every other constraint that's
258
+ * set, not "these dates are selectable regardless of the rest"). Compared by calendar day
259
+ * (year/month/date), time-of-day is ignored. Backs {@link isDateDisabled}.
260
+ */
261
+ allowedDates?: Date[];
135
262
  placeholder: string;
136
263
  opens: 'left' | 'right' | 'center';
137
264
  inline: boolean;
138
265
  compact: boolean;
139
- autoPosition: boolean;
266
+ /**
267
+ * @deprecated No-op, kept only so existing `[appendToBody]="true"` bindings don't break.
268
+ * The popup now always positions via Angular CDK Overlay, which portals into the shared
269
+ * `cdk-overlay-container` unconditionally — the exact clipping/stacking escape this input used
270
+ * to opt into by hand is now the only behaviour there is. Safe to remove from call sites.
271
+ */
140
272
  appendToBody: boolean;
273
+ /**
274
+ * Extra px CDK keeps clear of the viewport edges when flipping/pushing the popup. CDK's
275
+ * flip/push only avoids the window edges — it has no idea a sticky app header or a sticky
276
+ * table `<thead>` occupies part of that space, and will happily flip/push the popup underneath
277
+ * one. Set this to the height of any such sticky/fixed chrome the popup must never land under.
278
+ * Same fix, same reasoning, as `bk-popover.viewportMargin`.
279
+ */
280
+ viewportMargin: number;
281
+ /**
282
+ * Classes applied to the CDK overlay pane (`cdkConnectedOverlayPanelClass`). z-index overrides
283
+ * MUST go here (e.g. `panelClass="!z-[1100]"`) — the pane already establishes its own stacking
284
+ * context once portalled, so setting z-index anywhere else (the calendar's host element, a
285
+ * consumer's own wrapper) is a no-op. Same convention as `bk-popover.panelClass`.
286
+ */
287
+ panelClass: string | string[];
141
288
  isDisplayCrossIcon: boolean;
142
289
  hasError: boolean;
143
290
  errorMessage: string;
144
291
  selected: EventEmitter<CalendarSelection>;
145
292
  inputWrapper: ElementRef<HTMLDivElement>;
146
293
  calendarPopupRef?: ElementRef<HTMLDivElement>;
147
- /** Used when appendToBody is true to position the popup in viewport coordinates */
148
- dropdownStyle: {
149
- top?: string;
150
- bottom?: string;
151
- left?: string;
152
- };
153
- /** Resolved after layout; used for CSS `drop-up` and appendToBody positioning with viewport flip */
294
+ calendarOverlay?: CdkConnectedOverlay;
295
+ /** Resolved from CDK's (positionChange); drives the CSS `drop-up` class + slide direction. */
154
296
  popupPlacementAbove: boolean;
155
297
  opened: EventEmitter<void>;
156
298
  closed: EventEmitter<void>;
@@ -197,9 +339,21 @@ declare class BkCustomCalendar implements OnInit, OnDestroy, OnChanges, ControlV
197
339
  day: number;
198
340
  currentMonth: boolean;
199
341
  }[][];
342
+ /** Drill-down state for the header's month/year quick-pick. Independent per side in dual mode. */
343
+ view: 'days' | 'months' | 'years';
344
+ leftView: 'days' | 'months' | 'years';
345
+ rightView: 'days' | 'months' | 'years';
346
+ /** First year shown in the currently open year-grid (a 12-year page). */
347
+ yearRangeStart: number;
348
+ leftYearRangeStart: number;
349
+ rightYearRangeStart: number;
350
+ readonly monthNamesShort: string[];
200
351
  startDate: Date | null;
201
352
  endDate: Date | null;
202
353
  selectedDates: Date[];
354
+ /** Committed selection for `pickerView` 'month' (paired with {@link selectedYearValue}) / 'year'. */
355
+ selectedMonthValue: number | null;
356
+ selectedYearValue: number | null;
203
357
  disableHighlight: boolean;
204
358
  hoveredDate: Date | null;
205
359
  minuteInputValues: {
@@ -238,25 +392,32 @@ declare class BkCustomCalendar implements OnInit, OnDestroy, OnChanges, ControlV
238
392
  private unregisterFn?;
239
393
  private closeAllSubscription?;
240
394
  private closeFn?;
241
- constructor(calendarManager: BkCalendarManagerService, renderer: Renderer2);
395
+ constructor(calendarManager: BkCalendarManagerService);
396
+ /**
397
+ * CDK connected-overlay positions for the current open. Rebuilt each time the popup opens (see
398
+ * {@link toggle}) from `opens` (horizontal alignment) and `popupPosition` (which vertical side is
399
+ * primary). Auto-flip is always on — the opposite vertical side is always offered as a fallback,
400
+ * the same as `opens`'s horizontal push; there is no opt-out flag, matching `bk-select`/
401
+ * `bk-popover`. `cdkConnectedOverlayPush` (see the template) stays on regardless, so CDK slides
402
+ * the popup back on-screen horizontally instead of the old hand-rolled flip-then-clamp in
403
+ * computePopupLeft() — the same push-based approach bk-popover uses.
404
+ */
405
+ calendarPositions: ConnectedPosition[];
406
+ /** Tags each entry in {@link calendarPositions} with whether it places the popup above the
407
+ * trigger, so `onPositionChange` can read back which one CDK actually used. Matched by field
408
+ * value, not object identity — CDK reconstructs its own ConnectedPosition objects internally. */
409
+ private positionMeta;
410
+ /** Horizontal alignment from `opens` (deprecated `position` wins when explicitly set — see its
411
+ * JSDoc): 'left' aligns left edges (opens rightwards, the default), 'right' aligns right edges
412
+ * (opens leftwards), 'center' centers on the trigger. */
413
+ private horizontalAlign;
414
+ private buildVerticalPosition;
242
415
  /**
243
- * appendToBody popup relocation.
244
- *
245
- * `appendToBody` uses `position: fixed`, which only anchors to the viewport when NO ancestor
246
- * establishes a containing block (transform / filter / perspective / will-change / contain /
247
- * backdrop-filter). Consumers frequently place the calendar inside such an ancestor (an animated
248
- * panel, a CSS-transformed modal, etc.), which silently re-anchors the fixed popup and pushes it
249
- * off-position. To be robust everywhere we physically move the popup node to <body> while it is
250
- * open, then move it back before Angular's *ngIf tears it down. Angular keeps managing the node by
251
- * reference after the move, so its event bindings, change detection, and emulated-encapsulation
252
- * styles all continue to work.
253
- */
254
- private popupMovedToBody;
255
- private popupOriginalParent;
256
- private popupOriginalNextSibling;
257
- private movePopupToBody;
258
- /** Return the popup to its original DOM slot so *ngIf can destroy it cleanly (safe to call twice). */
259
- private restorePopupFromBody;
416
+ * (Re)computes {@link calendarPositions}: `popupPosition` picks the preferred/primary side, and
417
+ * the opposite side always follows as CDK's flip fallback — used only when the preferred side
418
+ * genuinely doesn't fit the viewport (`viewportMargin` included).
419
+ */
420
+ private computeCalendarPositions;
260
421
  /** Weekday headers; falls back to Mon–Sun if the input is not length 7. */
261
422
  get resolvedWeekDayLabels(): string[];
262
423
  /** When dual range mode, Apply is blocked until both endpoints exist. */
@@ -270,8 +431,6 @@ declare class BkCustomCalendar implements OnInit, OnDestroy, OnChanges, ControlV
270
431
  /** Discard in-popup draft and restore last committed value from {@link selectedValue}. */
271
432
  private revertSingleDateDraftIfNeeded;
272
433
  private finishPopupDismissal;
273
- /** User preference before viewport adjustment (only used when popupPosition is not explicitly set). */
274
- private preferPopupAbove;
275
434
  private resolveCustomRangesFromInputsOrService;
276
435
  private labelToJsWeekday;
277
436
  private getWeekStartDayIndex;
@@ -289,15 +448,27 @@ declare class BkCustomCalendar implements OnInit, OnDestroy, OnChanges, ControlV
289
448
  private applyValueToState;
290
449
  /** Format to "H:MM AM/PM" string. */
291
450
  private formatTimeToAmPm;
292
- onClickOutside(event: MouseEvent): void;
293
- /** True while capture-phase scroll + resize listeners are attached (only while the popup is open). */
451
+ /**
452
+ * CDK's overlay origin already excludes clicks on the trigger from outside-click dispatch by
453
+ * design (same note in bk-popover's onOverlayOutsideClick), so the input's own (click)="toggle()"
454
+ * stays the sole opener/closer via the trigger.
455
+ */
456
+ onOverlayOutsideClick(): void;
457
+ /** Fires whenever CDK (re)applies a position, including the first one after open. Reads back
458
+ * which of `positionMeta`'s tagged entries CDK actually used, to drive the `drop-up` CSS class
459
+ * (and its slide-up/slide-down animation) — the same value computePlacementAbove() used to. */
460
+ onPositionChange(event: ConnectedOverlayPositionChange): void;
461
+ /** True while the capture-phase scroll/resize listener is attached (only while the popup is open). */
294
462
  private viewportListenersAttached;
295
463
  /** Pending rAF id so bursts of scroll events collapse into one layout pass. */
296
464
  private viewportRafId;
297
465
  /**
298
- * Recipe steps 2 & 4: while the popup is open, keep it glued to the trigger as the page
299
- * (or any nested scroll container) scrolls, and dismiss it once the trigger leaves the viewport.
300
- * Throttled through requestAnimationFrame to avoid layout thrash during fast scrolling.
466
+ * CDK's own scroll strategy only reacts to real `document`/`window` scroll it has no way to
467
+ * know an app shell might scroll a nested container instead (a dashboard layout with a fixed
468
+ * header/sidebar and a scrollable content pane, a dialog body, etc). A capture-phase listener on
469
+ * `document` still sees scroll events fired on any descendant scrollable element (scroll doesn't
470
+ * bubble, but capture does) — the same trick bk-popover uses. Throttled through
471
+ * requestAnimationFrame to avoid layout thrash during fast scrolling.
301
472
  */
302
473
  private readonly onViewportChange;
303
474
  private handleViewportChange;
@@ -318,32 +489,102 @@ declare class BkCustomCalendar implements OnInit, OnDestroy, OnChanges, ControlV
318
489
  ngOnDestroy(): void;
319
490
  checkAndSetActiveRange(): void;
320
491
  initializeTimeFromDate(date: Date, isStart: boolean): void;
492
+ /** True while the trigger input itself has DOM focus — tracked because a click on an already-
493
+ * focused-but-closed input doesn't re-fire `focus` (no actual focus change happens), so
494
+ * onTriggerMouseDown needs another way to know it should still open. Set/cleared by
495
+ * onTriggerFocus/onTriggerBlur. */
496
+ private triggerHasFocus;
497
+ /** Set immediately before {@link returnFocusToTrigger} programmatically refocuses the input, so
498
+ * the resulting onTriggerFocus() doesn't treat "focus we just gave back after closing" as a
499
+ * request to reopen the very popup it was asked to close. Consumed (cleared) by the next
500
+ * onTriggerFocus() call — see its own comment. */
501
+ private suppressNextTriggerAutoOpen;
502
+ /**
503
+ * Opens the popup as soon as the trigger input receives focus — including via Tab, not just a
504
+ * click. Guarded on `!this.show` so it's a no-op if something else already opened it. Also
505
+ * consumes {@link suppressNextTriggerAutoOpen}: when {@link returnFocusToTrigger} refocuses the
506
+ * input after Cancel/Apply closed the popup, this fires too (a genuine focus transition, same
507
+ * as any other) — without the guard it would immediately reopen the popup right after closing
508
+ * it.
509
+ */
510
+ onTriggerFocus(): void;
511
+ /** Pairs with (blur)="onTriggerBlur()" on the trigger input — keeps {@link triggerHasFocus} in
512
+ * sync, on top of the pre-existing markAsTouched() call this replaces inline. */
513
+ onTriggerBlur(): void;
514
+ /**
515
+ * `mousedown` (not `click`) so this cooperates with {@link onTriggerFocus} instead of racing
516
+ * it: `mousedown` fires *before* the browser's default focus shift, `focus` fires *after* it —
517
+ * same click sequence, different moments.
518
+ * - Not yet focused, closed: do nothing here — let the mousedown's own default focus shift
519
+ * fire `onTriggerFocus` right after, which opens it. Exactly one open, no double-handling.
520
+ * - Already focused (so `focus` won't fire again — no actual focus change) but somehow closed
521
+ * (e.g. Cancel/Apply just closed it while focus was returned here): open it explicitly, since
522
+ * nothing else will.
523
+ * - Already open (and therefore already focused, since focus always opens): this click means
524
+ * close — toggle closed, and preventDefault so the browser doesn't do anything odd with a
525
+ * mousedown on an already-focused field it's about to lose visually.
526
+ */
527
+ onTriggerMouseDown(event: MouseEvent): void;
528
+ /**
529
+ * Opens the popup from the keyboard once the trigger input has focus — Enter, Space, or
530
+ * ArrowDown, the standard combobox/datepicker open keys. Focus alone already opens it (see
531
+ * onTriggerFocus), so in practice this only matters if focus is regained without opening for
532
+ * some other reason. Guarded on `!this.show` so it never re-closes an already-open popup.
533
+ */
534
+ onTriggerKeydownOpen(event: Event): void;
321
535
  toggle(): void;
322
- /** Update popup position when appendToBody is true (fixed positioning relative to viewport). */
323
- updatePosition(): void;
324
- /**
325
- * Horizontal placement for the fixed (appendToBody) popup.
326
- *
327
- * The base edge follows the author's {@link opens} preference:
328
- * 'left' → align left edges, panel opens rightwards (default)
329
- * • 'right' align right edges, panel opens leftwards
330
- * 'center' centred on the trigger
331
- * When {@link autoPosition} is on, the panel auto-flips to the opposite edge if the preferred
332
- * side would overflow the viewport, then clamps to an 8px margin so it can never leave the screen.
333
- * When autoPosition is off, the explicit `opens` value is honoured as-is.
334
- */
335
- private computePopupLeft;
336
- /** Non–append-to-body: set `popupPlacementAbove` for CSS `drop-up` with viewport flip. */
337
- refreshPopupPlacement(): void;
338
- private computePlacementAbove;
339
- /** Normalize to local midnight and clamp to min/max selectable day. */
536
+ /** minDate/maxDate day clamp, factored out so {@link clampCalendarDayToSelectableRange} can
537
+ * apply it a second time after jumping to a different month (see there for why). */
538
+ private clampToMinMax;
539
+ /** Nearest month (year+month pair) to `month`/`year` that isn't disabled — i.e. that has at
540
+ * least one selectable day once minDate/maxDate AND allowedMonths AND allowedYears are all
541
+ * applied (see isMonthDisabled). Searches outward a month at a time, forward and back in
542
+ * lockstep, so it finds the true nearest regardless of which side the given month missed on.
543
+ * Bounded to 50 years either way as a safety net against a contradictory config (e.g.
544
+ * allowedYears entirely outside minDate/maxDate) that has no answer at all — returns the
545
+ * original month/year unchanged in that case. */
546
+ private nearestSelectableMonth;
547
+ /**
548
+ * Normalize to local midnight, clamp into [minDate, maxDate], then — if that alone still lands
549
+ * on a month allowedMonths/allowedYears excludes — jump to the nearest month that actually has
550
+ * a selectable day. Without this, opening the calendar (or narrowing allowedMonths/allowedYears
551
+ * while a stale view was showing) could land squarely on a dead month: minDate/maxDate-valid but
552
+ * allowedMonths-disabled, e.g. minDate falls in August with allowedMonths excluding it — since
553
+ * month/year nav stays deliberately unguarded (see nextMonth/prevMonth), the user would have to
554
+ * page there by hand with no indication which direction to go. Re-clamps to minDate/maxDate
555
+ * after the jump too, in case the nearest allowed month is the very month minDate or maxDate
556
+ * falls in (the 1st of that month alone could still sit outside the day-level bound). If
557
+ * nearestSelectableMonth can't find anything better at all (a self-contradictory config, e.g.
558
+ * allowedYears entirely outside minDate/maxDate — it then returns the same month/year back
559
+ * unchanged), this leaves the original clamped day exactly as-is instead of needlessly
560
+ * resetting it to the 1st for no actual improvement.
561
+ */
340
562
  private clampCalendarDayToSelectableRange;
341
563
  private initKeyboardFocus;
564
+ /**
565
+ * Re-centers the visible month(s) and keyboard focus on `date` (clamped into the selectable
566
+ * range). Shared by {@link resetCalendarFocusAfterClear} and
567
+ * {@link revalidateSelectionAgainstConstraints} — callers differ only in whether they also
568
+ * steal DOM focus afterwards.
569
+ */
570
+ private recenterCalendarOn;
342
571
  /**
343
572
  * After clearing values, drop stale keyboard highlight (was last start/end) and reset the
344
573
  * visible month(s) to today (clamped). Move DOM focus to the popup when open, else the input.
345
574
  */
346
575
  private resetCalendarFocusAfterClear;
576
+ /**
577
+ * Called from {@link ngOnChanges} when any of `minDate`/`maxDate`/`allowedMonths`/
578
+ * `allowedYears`/`allowedDaysOfWeek`/`disabledDates`/`allowedDates` change: clears any existing
579
+ * selection that no longer satisfies all of them — e.g. a date picked before the range was
580
+ * tightened, or before allowedMonths excluded its month — instead of leaving it displayed as
581
+ * "selected" while every cell that could represent it is now disabled. Delegates to {@link
582
+ * isDateDisabled} so this can never drift from what the day grid itself considers selectable.
583
+ * Re-emits so the bound model stays in sync with what's actually shown, and re-centers the
584
+ * visible month(s) on the closest still-valid day so the user isn't left staring at a month
585
+ * that no longer contains their selection.
586
+ */
587
+ private revalidateSelectionAgainstConstraints;
347
588
  private scheduleDomFocusAfterClear;
348
589
  private ensureKeyboardFocusVisible;
349
590
  private moveKeyboardFocus;
@@ -354,6 +595,20 @@ declare class BkCustomCalendar implements OnInit, OnDestroy, OnChanges, ControlV
354
595
  */
355
596
  private isKeyboardEventFromInteractiveDescendant;
356
597
  onCalendarPopupKeydown(event: KeyboardEvent): void;
598
+ /**
599
+ * CDK portals the popup into `.cdk-overlay-container` (position: fixed, full-viewport). Some
600
+ * browsers don't chain a wheel scroll from inside that fixed container back out to the page
601
+ * underneath, so hovering the (non-inline) popup and scrolling silently does nothing instead of
602
+ * scrolling the page — breaking the "popup follows the trigger as the page scrolls" behaviour
603
+ * the viewport-tracking above exists for. Forward the wheel delta to the page manually, but only
604
+ * once nothing inside the popup can still consume it: walk up from the actual wheel target
605
+ * looking for a genuinely scrollable ancestor with room left in that direction — e.g. the time
606
+ * picker's minute/second `.time-scroll` wheel, or the whole popup once `@media
607
+ * (max-width:1024px)` makes it scroll — and let that handle it natively instead.
608
+ */
609
+ onCalendarPopupWheel(event: WheelEvent): void;
610
+ /** True when `el` is a scroll container with room left to move further in `deltaY`'s direction. */
611
+ private canElementConsumeWheel;
357
612
  private applyKeyboardSelection;
358
613
  isKeyboardFocusedCell(year: number, month: number, day: number): boolean;
359
614
  onRangeButtonKeydown(event: KeyboardEvent, rangeKey: string): void;
@@ -366,7 +621,27 @@ declare class BkCustomCalendar implements OnInit, OnDestroy, OnChanges, ControlV
366
621
  isDateInMultiSelection(year: number, month: number, day: number): boolean;
367
622
  apply(): void;
368
623
  cancel(): void;
624
+ /**
625
+ * Returns keyboard focus to the trigger input after an explicit in-popup dismissal (Cancel /
626
+ * Apply). Without this, focus is left on the Cancel/Apply button as the (portalled) popup tears
627
+ * down out from under it — the browser then drops focus to <body>, so a keyboard user loses
628
+ * their place entirely and Tab restarts from the top of the page instead of continuing from the
629
+ * calendar. Not called from the generic outside-click/scroll-out dismissal path (finishPopupDismissal) —
630
+ * there the user's own click already established where focus should go; forcing it back to the
631
+ * trigger would fight that. setTimeout(0) mirrors scheduleDomFocusAfterClear: it defers past the
632
+ * click's own default focus handling (which would otherwise re-focus the Cancel/Apply button
633
+ * right after and win the race against a synchronous call here).
634
+ */
635
+ private returnFocusToTrigger;
369
636
  clear(): void;
637
+ /**
638
+ * Applies a preset range (Today/Yesterday/Last 7 days/etc.) or activates "Custom Range".
639
+ * Deliberately does NOT check minDate/maxDate/allowedMonths/allowedYears/allowedDaysOfWeek/
640
+ * disabledDates/allowedDates — those constraints are scoped to manual day-grid picking only
641
+ * (selectDate() routes through isDateDisabled before ever setting startDate/endDate; this does
642
+ * not). A preset always applies exactly as configured, even if part of it falls outside those
643
+ * constraints — isDateSelected() has a matching note on why it doesn't re-check them either.
644
+ */
370
645
  chooseRange(key: string): void;
371
646
  emitSelection(): void;
372
647
  addDays(date: Date, days: number): Date;
@@ -377,6 +652,86 @@ declare class BkCustomCalendar implements OnInit, OnDestroy, OnChanges, ControlV
377
652
  prevLeftMonth(): void;
378
653
  nextRightMonth(): void;
379
654
  prevRightMonth(): void;
655
+ nextYear(): void;
656
+ prevYear(): void;
657
+ nextLeftYear(): void;
658
+ prevLeftYear(): void;
659
+ nextRightYear(): void;
660
+ prevRightYear(): void;
661
+ /** Drives both the click guard and the `<<`/`>>` chevrons' disabled state. */
662
+ isNextYearDisabled(currentYear: number): boolean;
663
+ isPrevYearDisabled(currentYear: number): boolean;
664
+ private computeYearRangeStart;
665
+ /** The 12 years shown in an open year-grid, given its page anchor. */
666
+ getYearGridYears(rangeStart: number): number[];
667
+ /** {@link allowedMonths} (1-based) converted to the 0-based month indices used everywhere
668
+ * internally — JS `Date.getMonth()`, `this.month`, the month grid's own loop index. */
669
+ private get allowedMonthsZeroBased();
670
+ /** {@link allowedDaysOfWeek} (1-based) converted to the 0-based weekday indices JS
671
+ * `Date.getDay()` uses. */
672
+ private get allowedDaysOfWeekZeroBased();
673
+ /** True if `list` contains a Date matching `year`/`month`/`day` — compared by calendar day
674
+ * only, same normalize-to-midnight reasoning as every other date comparison in this file (see
675
+ * isDateDisabled). Backs {@link disabledDates}/{@link allowedDates}. */
676
+ private dateListIncludes;
677
+ /** minDate/maxDate checked first, then allowedMonths — same precedence as isDateDisabled/
678
+ * isYearDisabled (AND overall, so check order doesn't change the result, only readability). */
679
+ isMonthDisabled(month: number, year: number): boolean;
680
+ /** minDate/maxDate checked first, then allowedYears — see isMonthDisabled's note on ordering. */
681
+ isYearDisabled(year: number): boolean;
682
+ /** 0-based entry in {@link allowedMonthsZeroBased} nearest to `month`, ties broken toward the
683
+ * earlier one. */
684
+ private nearestAllowedMonth;
685
+ /** If switching to `year` would leave the current month disabled, snap it back inside range. */
686
+ private clampMonthForYear;
687
+ /** True when `pickerView` actually restricts this calendar to a month/year grid — single-date
688
+ * only, per its JSDoc; dual/range calendars always use the day grid regardless of the input. */
689
+ isPickerViewActive(): boolean;
690
+ /** The view a single calendar starts on / collapses back to: the day grid, unless `pickerView`
691
+ * pins it to the month or year grid instead — there is no day grid to fall back to there. */
692
+ private baseView;
693
+ /** Collapse any open month/year grid back to {@link baseView} (called whenever the popup resets).
694
+ * When that lands on the year grid, also re-centers {@link yearRangeStart} on `this.year` — the
695
+ * grid is otherwise never reached via {@link openYearView} (the only other place that sets it),
696
+ * so it would stay at its `0` default and show the wrong 12-year page. */
697
+ private resetDrillDownViews;
698
+ openMonthView(): void;
699
+ /** Year-changing chevrons shown alongside the month grid — shift its year without leaving the view. */
700
+ prevMonthGridYear(): void;
701
+ nextMonthGridYear(): void;
702
+ openYearView(): void;
703
+ prevYearRange(): void;
704
+ nextYearRange(): void;
705
+ selectMonthFromGrid(month: number): void;
706
+ selectYearFromGrid(year: number): void;
707
+ /** Commits a `pickerView` 'month'/'year' selection: populates {@link selectedMonthValue}/
708
+ * {@link selectedYearValue}, emits, and closes the popup (unless inline) — the same "pick →
709
+ * done" flow `autoApply` gives the day grid. */
710
+ private commitMonthYearSelection;
711
+ /** "Today" quick-jump shown at the bottom of the month/year grids — jumps to today's month/year.
712
+ * In `pickerView` 'month'/'year' mode this finalizes the selection there; otherwise it jumps
713
+ * back to the day grid and selects today's date. */
714
+ goToToday(): void;
715
+ openLeftMonthView(): void;
716
+ prevLeftMonthGridYear(): void;
717
+ nextLeftMonthGridYear(): void;
718
+ openLeftYearView(): void;
719
+ prevLeftYearRange(): void;
720
+ nextLeftYearRange(): void;
721
+ selectLeftMonthFromGrid(month: number): void;
722
+ selectLeftYearFromGrid(year: number): void;
723
+ /** "Today" quick-jump for the left calendar's month/year grids. */
724
+ goToLeftToday(): void;
725
+ openRightMonthView(): void;
726
+ prevRightMonthGridYear(): void;
727
+ nextRightMonthGridYear(): void;
728
+ openRightYearView(): void;
729
+ prevRightYearRange(): void;
730
+ nextRightYearRange(): void;
731
+ selectRightMonthFromGrid(month: number): void;
732
+ selectRightYearFromGrid(year: number): void;
733
+ /** "Today" quick-jump for the right calendar's month/year grids. */
734
+ goToRightToday(): void;
380
735
  initializeDual(): void;
381
736
  generateDualCalendars(): void;
382
737
  buildCalendar(year: number, month: number): {
@@ -385,8 +740,23 @@ declare class BkCustomCalendar implements OnInit, OnDestroy, OnChanges, ControlV
385
740
  }[][];
386
741
  isDateSelected(year: number, month: number, day: number): boolean;
387
742
  isDateInRange(year: number, month: number, day: number): boolean;
743
+ /**
744
+ * A cell is disabled unless it satisfies EVERY constraint that's set — minDate AND maxDate AND
745
+ * allowedMonths AND allowedYears AND allowedDaysOfWeek AND NOT-in-disabledDates AND (if set)
746
+ * in-allowedDates. Checked in that order, matching isMonthDisabled/isYearDisabled's own
747
+ * minDate/maxDate-first ordering below — purely for readability, since AND makes the overall
748
+ * result order-independent. This is the single source of truth for day-level selectability:
749
+ * selectDate() routes through it too (rather than re-checking constraints on its own), so a
750
+ * value can never be committed — including via keyboard Enter, which bypasses the template's
751
+ * click guard — that this marks disabled.
752
+ */
388
753
  isDateDisabled(year: number, month: number, day: number): boolean;
389
754
  isToday(year: number, month: number, day: number): boolean;
755
+ /** Today's month cell in the month grid — same "current" marker as {@link isToday} gives the
756
+ * day grid, independent of `active` (the picked/displayed month). */
757
+ isCurrentMonth(month: number, year: number): boolean;
758
+ /** Today's year cell in the year grid — same idea as {@link isCurrentMonth}. */
759
+ isCurrentYear(year: number): boolean;
390
760
  getDisplayValue(): string;
391
761
  getTimeInputValue(isStart?: boolean): string;
392
762
  getSingleTimeInputValue(): string;
@@ -427,7 +797,7 @@ declare class BkCustomCalendar implements OnInit, OnDestroy, OnChanges, ControlV
427
797
  private parseDateString;
428
798
  formatDateToString(date: Date): string;
429
799
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<BkCustomCalendar, never>;
430
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<BkCustomCalendar, "bk-custom-calendar", never, { "enableTimepicker": { "alias": "enableTimepicker"; "required": false; }; "autoApply": { "alias": "autoApply"; "required": false; }; "closeOnAutoApply": { "alias": "closeOnAutoApply"; "required": false; }; "showCancel": { "alias": "showCancel"; "required": false; }; "linkedCalendars": { "alias": "linkedCalendars"; "required": false; }; "singleDatePicker": { "alias": "singleDatePicker"; "required": false; }; "showWeekNumbers": { "alias": "showWeekNumbers"; "required": false; }; "showISOWeekNumbers": { "alias": "showISOWeekNumbers"; "required": false; }; "customRangeDirection": { "alias": "customRangeDirection"; "required": false; }; "lockStartDate": { "alias": "lockStartDate"; "required": false; }; "position": { "alias": "position"; "required": false; }; "popupPosition": { "alias": "popupPosition"; "required": false; }; "drop": { "alias": "drop"; "required": false; }; "dualCalendar": { "alias": "dualCalendar"; "required": false; }; "showRanges": { "alias": "showRanges"; "required": false; }; "timeFormat": { "alias": "timeFormat"; "required": false; }; "clearableTime": { "alias": "clearableTime"; "required": false; }; "enableSeconds": { "alias": "enableSeconds"; "required": false; }; "customRanges": { "alias": "customRanges"; "required": false; }; "weekDayLabels": { "alias": "weekDayLabels"; "required": false; }; "multiDateSelection": { "alias": "multiDateSelection"; "required": false; }; "maxDate": { "alias": "maxDate"; "required": false; }; "minDate": { "alias": "minDate"; "required": false; }; "placeholder": { "alias": "placeholder"; "required": false; }; "opens": { "alias": "opens"; "required": false; }; "inline": { "alias": "inline"; "required": false; }; "compact": { "alias": "compact"; "required": false; }; "autoPosition": { "alias": "autoPosition"; "required": false; }; "appendToBody": { "alias": "appendToBody"; "required": false; }; "isDisplayCrossIcon": { "alias": "isDisplayCrossIcon"; "required": false; }; "hasError": { "alias": "hasError"; "required": false; }; "errorMessage": { "alias": "errorMessage"; "required": false; }; "showCancelApply": { "alias": "showCancelApply"; "required": false; }; "selectedValue": { "alias": "selectedValue"; "required": false; }; "displayFormat": { "alias": "displayFormat"; "required": false; }; "required": { "alias": "required"; "required": false; }; "rangeOrder": { "alias": "rangeOrder"; "required": false; }; }, { "selected": "selected"; "opened": "opened"; "closed": "closed"; }, never, never, true, never>;
800
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<BkCustomCalendar, "bk-custom-calendar", never, { "enableTimepicker": { "alias": "enableTimepicker"; "required": false; }; "autoApply": { "alias": "autoApply"; "required": false; }; "closeOnAutoApply": { "alias": "closeOnAutoApply"; "required": false; }; "showCancel": { "alias": "showCancel"; "required": false; }; "linkedCalendars": { "alias": "linkedCalendars"; "required": false; }; "singleDatePicker": { "alias": "singleDatePicker"; "required": false; }; "pickerView": { "alias": "pickerView"; "required": false; }; "showWeekNumbers": { "alias": "showWeekNumbers"; "required": false; }; "showISOWeekNumbers": { "alias": "showISOWeekNumbers"; "required": false; }; "customRangeDirection": { "alias": "customRangeDirection"; "required": false; }; "lockStartDate": { "alias": "lockStartDate"; "required": false; }; "position": { "alias": "position"; "required": false; }; "popupPosition": { "alias": "popupPosition"; "required": false; }; "drop": { "alias": "drop"; "required": false; }; "dualCalendar": { "alias": "dualCalendar"; "required": false; }; "showRanges": { "alias": "showRanges"; "required": false; }; "timeFormat": { "alias": "timeFormat"; "required": false; }; "clearableTime": { "alias": "clearableTime"; "required": false; }; "enableSeconds": { "alias": "enableSeconds"; "required": false; }; "customRanges": { "alias": "customRanges"; "required": false; }; "weekDayLabels": { "alias": "weekDayLabels"; "required": false; }; "multiDateSelection": { "alias": "multiDateSelection"; "required": false; }; "maxDate": { "alias": "maxDate"; "required": false; }; "minDate": { "alias": "minDate"; "required": false; }; "allowedMonths": { "alias": "allowedMonths"; "required": false; }; "allowedYears": { "alias": "allowedYears"; "required": false; }; "allowedDaysOfWeek": { "alias": "allowedDaysOfWeek"; "required": false; }; "disabledDates": { "alias": "disabledDates"; "required": false; }; "allowedDates": { "alias": "allowedDates"; "required": false; }; "placeholder": { "alias": "placeholder"; "required": false; }; "opens": { "alias": "opens"; "required": false; }; "inline": { "alias": "inline"; "required": false; }; "compact": { "alias": "compact"; "required": false; }; "appendToBody": { "alias": "appendToBody"; "required": false; }; "viewportMargin": { "alias": "viewportMargin"; "required": false; }; "panelClass": { "alias": "panelClass"; "required": false; }; "isDisplayCrossIcon": { "alias": "isDisplayCrossIcon"; "required": false; }; "hasError": { "alias": "hasError"; "required": false; }; "errorMessage": { "alias": "errorMessage"; "required": false; }; "showCancelApply": { "alias": "showCancelApply"; "required": false; }; "selectedValue": { "alias": "selectedValue"; "required": false; }; "displayFormat": { "alias": "displayFormat"; "required": false; }; "required": { "alias": "required"; "required": false; }; "rangeOrder": { "alias": "rangeOrder"; "required": false; }; }, { "selected": "selected"; "opened": "opened"; "closed": "closed"; }, never, never, true, never>;
431
801
  }
432
802
 
433
803
  interface TimeConfiguration {
@@ -507,7 +877,7 @@ declare class BkScheduledDatePicker implements OnInit {
507
877
  }
508
878
 
509
879
  declare class BkTimePicker implements OnInit, OnChanges, OnDestroy, AfterViewInit, ControlValueAccessor {
510
- private renderer;
880
+ private calendarManager;
511
881
  required: boolean;
512
882
  /** @deprecated Prefer [(ngModel)] */
513
883
  value: string | null;
@@ -520,11 +890,30 @@ declare class BkTimePicker implements OnInit, OnChanges, OnDestroy, AfterViewIni
520
890
  closePicker: number;
521
891
  timeFormat: 12 | 24;
522
892
  showSeconds: boolean;
523
- /** When true, auto-flip the dropdown above/below and left/right based on available viewport space. */
893
+ /**
894
+ * When true, offers a flipped-above fallback if there isn't room below the trigger (CDK only
895
+ * uses it when the preferred side genuinely doesn't fit). When false, the dropdown always opens
896
+ * below, never flipping — matches this input's original (pre-CDK) semantics.
897
+ */
524
898
  autoPosition: boolean;
525
- /** When true, position the dropdown with `fixed` so it escapes ancestor overflow (dialogs, scroll containers),
526
- * follows the trigger on scroll, and closes when the trigger is scrolled out of view. */
899
+ /**
900
+ * @deprecated No-op, kept only so existing `[appendToBody]="true"` bindings don't break. The
901
+ * dropdown now always positions via Angular CDK Overlay, which portals into the shared
902
+ * `cdk-overlay-container` unconditionally — the exact clipping/stacking escape this input used
903
+ * to opt into by hand is now the only behaviour there is. Safe to remove from call sites.
904
+ */
527
905
  appendToBody: boolean;
906
+ /**
907
+ * Extra px CDK keeps clear of the viewport edges when flipping/pushing the dropdown. Same fix,
908
+ * same reasoning, as `bk-popover.viewportMargin` / `bk-custom-calendar.viewportMargin`.
909
+ */
910
+ viewportMargin: number;
911
+ /**
912
+ * Classes applied to the CDK overlay pane (`cdkConnectedOverlayPanelClass`). z-index overrides
913
+ * MUST go here — the pane already establishes its own stacking context once portalled. Same
914
+ * convention as `bk-popover.panelClass` / `bk-custom-calendar.panelClass`.
915
+ */
916
+ panelClass: string | string[];
528
917
  change: EventEmitter<string | null>;
529
918
  timeChange: EventEmitter<string | null>;
530
919
  pickerOpened: EventEmitter<string>;
@@ -535,14 +924,28 @@ declare class BkTimePicker implements OnInit, OnChanges, OnDestroy, AfterViewIni
535
924
  hourScrollEl?: ElementRef<HTMLElement>;
536
925
  minuteScrollEl?: ElementRef<HTMLElement>;
537
926
  secondScrollEl?: ElementRef<HTMLElement>;
538
- /** The positioned trigger (its rect anchors the dropdown). */
927
+ /** The trigger — also the CDK connected-overlay origin (see #tpOrigin in the template). */
539
928
  tpWrapper?: ElementRef<HTMLElement>;
540
- /** The dropdown panel element (measured for flip decisions). */
541
- tpDropdown?: ElementRef<HTMLElement>;
542
- /** Resolved placement state, driven by updatePosition(). */
929
+ tpOverlay?: CdkConnectedOverlay;
930
+ /** Resolved from CDK's (positionChange); drives the CSS flip-direction class. */
543
931
  placeAbove: boolean;
544
- resolvedPosition: 'left' | 'right';
545
- dropdownStyle: Record<string, string>;
932
+ /**
933
+ * CDK connected-overlay positions for the current open. Rebuilt each time the dropdown opens
934
+ * (see {@link togglePicker}) from `position` (horizontal side, fixed — never flips; CDK's push,
935
+ * always on, handles keeping it on-screen instead, same approach `bk-popover`/
936
+ * `bk-custom-calendar` use for their own horizontal axis) and `autoPosition` (whether a flipped
937
+ * vertical fallback is offered at all — matches this input's original semantics exactly).
938
+ */
939
+ pickerPositions: ConnectedPosition[];
940
+ /** Tags each entry in {@link pickerPositions} with whether it places the dropdown above the
941
+ * trigger, so `onPositionChange` can read back which one CDK actually used. Matched by field
942
+ * value, not object identity — CDK reconstructs its own ConnectedPosition objects internally. */
943
+ private positionMeta;
944
+ private buildVerticalPosition;
945
+ /** (Re)computes {@link pickerPositions}: always tries `position`'s side below the trigger first;
946
+ * `autoPosition` additionally offers the same side flipped above as a fallback CDK only falls
947
+ * back to when below genuinely doesn't fit. */
948
+ private computePickerPositions;
546
949
  /** Row height in px. MUST match the .time-item height in CSS for the active variation. */
547
950
  private get ITEM_HEIGHT();
548
951
  /** Finite lists (no cyclic loop) — same as the hours column, so 00..59 has a hard start/end. */
@@ -570,21 +973,6 @@ declare class BkTimePicker implements OnInit, OnChanges, OnDestroy, AfterViewIni
570
973
  readonly calenderIcon: "assets/icons/custom-calender.svg";
571
974
  readonly timerIcon: "assets/icons/timer.svg";
572
975
  };
573
- constructor(renderer: Renderer2);
574
- /**
575
- * `appendToBody` anchors the dropdown with `position: fixed`, which only tracks the viewport when
576
- * NO ancestor establishes a containing block (transform / filter / perspective / will-change /
577
- * contain / backdrop-filter). Inside such an ancestor the fixed coordinates resolve against that
578
- * ancestor instead and the dropdown drifts off-position. To be robust we physically move the panel
579
- * to <body> while open (Angular keeps managing it by reference) and move it back before *ngIf
580
- * tears it down. The panel carries a `tp-compact` self-class so its `default`-variation sizing —
581
- * otherwise scoped under the `.time-input-group.default` ancestor it just left — still applies.
582
- */
583
- private dropdownMovedToBody;
584
- private dropdownOriginalParent;
585
- private moveDropdownToBody;
586
- /** Return the dropdown to its original slot so *ngIf can destroy it cleanly (safe to call twice). */
587
- private restoreDropdownFromBody;
588
976
  writeValue(value: string | null): void;
589
977
  registerOnChange(fn: (value: string | null) => void): void;
590
978
  registerOnTouched(fn: () => void): void;
@@ -594,6 +982,9 @@ declare class BkTimePicker implements OnInit, OnChanges, OnDestroy, AfterViewIni
594
982
  private setDefaultsForDropdown;
595
983
  clear(): void;
596
984
  markAsTouched(): void;
985
+ constructor(calendarManager: BkCalendarManagerService);
986
+ private closeFn?;
987
+ private unregisterFn?;
597
988
  ngOnInit(): void;
598
989
  ngAfterViewInit(): void;
599
990
  ngOnChanges(changes: SimpleChanges): void;
@@ -605,37 +996,102 @@ declare class BkTimePicker implements OnInit, OnChanges, OnDestroy, AfterViewIni
605
996
  ampm: string;
606
997
  };
607
998
  formatTimeFromComponents(hour: number, minute: number, second: number, ampm: string): string;
999
+ /** True while the trigger input itself has DOM focus — see onTriggerMouseDown for why this is
1000
+ * tracked (same reasoning as bk-custom-calendar's triggerHasFocus). */
1001
+ private triggerHasFocus;
1002
+ /** Opens the dropdown as soon as the trigger input receives focus — including via Tab, not
1003
+ * just a click. Guarded on `!this.showPicker` so it's a no-op if already open. */
1004
+ onTriggerFocus(): void;
1005
+ /** Pairs with (blur) — keeps triggerHasFocus in sync alongside the pre-existing
1006
+ * markAsTouched() call this replaces inline. */
1007
+ onTriggerBlur(): void;
1008
+ /**
1009
+ * `mousedown` (not `click`) so this cooperates with {@link onTriggerFocus} instead of racing
1010
+ * it — same pattern and reasoning as bk-custom-calendar's onTriggerMouseDown: not-yet-focused
1011
+ * clicks are left to the default focus shift (which opens via onTriggerFocus); an
1012
+ * already-focused click means close (or, if somehow already focused but closed, open — `focus`
1013
+ * won't re-fire without an actual focus change).
1014
+ */
1015
+ onTriggerMouseDown(event: MouseEvent): void;
1016
+ /**
1017
+ * Opens the dropdown from the keyboard once the trigger input has focus — Enter, Space, or
1018
+ * ArrowDown, the standard combobox/datepicker open keys. Focus alone already opens it (see
1019
+ * onTriggerFocus); this mainly matters if focus is regained without opening for some other
1020
+ * reason.
1021
+ */
1022
+ onTriggerKeydownOpen(event: Event): void;
608
1023
  togglePicker(): void;
609
- /** Central close path so listeners are always detached and events emitted once. */
610
- private dismiss;
1024
+ /** Central close path so listeners are always detached and events emitted once. Not private:
1025
+ * the template's own (detach) binding on cdkConnectedOverlay calls it directly. */
1026
+ dismiss(): void;
1027
+ /** Cancels every pending scroll-related timer/rAF (deferred open-scroll, snap-settle debounce,
1028
+ * active-highlight rAF throttle) so none of them fire against a picker that has since closed
1029
+ * or been destroyed. */
1030
+ private clearScrollTimers;
611
1031
  onHourChange(hour: number): void;
612
1032
  onMinuteChange(minute: number): void;
613
1033
  onSecondChange(second: number): void;
614
1034
  onAMPMChange(ampm: string): void;
615
1035
  updateTime(): void;
1036
+ /** Defers scrollToSelectedTimes() until the just-opened overlay's columns have rendered.
1037
+ * Clears any previously pending call first, so opening/closing/reopening in quick succession
1038
+ * can never stack more than one pending scroll (each would otherwise reset the columns to the
1039
+ * committed value, undoing any scrolling the user did in between). */
1040
+ private scheduleScrollToSelectedTimes;
616
1041
  /** Position each column so the committed value sits at the top of its viewport. */
617
1042
  scrollToSelectedTimes(): void;
618
1043
  private scrollColumnTo;
619
1044
  /** Item nearest the viewport center for the current scroll position, clamped to [0, count - 1]. */
620
1045
  private scrollIndex;
621
- onDocumentClick(event: MouseEvent): void;
1046
+ /** CDK's overlay origin already excludes clicks on the trigger from outside-click dispatch by
1047
+ * design (same note in bk-popover/bk-custom-calendar's onOverlayOutsideClick), so the input's
1048
+ * own (click)="togglePicker()" stays the sole opener/closer via the trigger. */
1049
+ onOverlayOutsideClick(): void;
622
1050
  private previousCloseCounter;
1051
+ /** Handle for the deferred scrollToSelectedTimes() (see togglePicker/ngAfterViewInit). Stored
1052
+ * so a stale timer never fires after the picker has already closed, and so opening/closing in
1053
+ * quick succession never stacks more than one pending call. */
1054
+ private scrollToSelectedTimeoutId;
1055
+ /** rAF handles throttling the active-highlight update to at most once per frame during fast
1056
+ * wheel/trackpad scrolling — same pattern as {@link onViewportChange}. Without this, a scroll
1057
+ * event firing many times per frame drove Angular change detection just as often, which is
1058
+ * what made the wheel feel like it was "sticking" during a fast scroll. */
1059
+ private hourScrollRafId;
1060
+ private minuteScrollRafId;
1061
+ private secondScrollRafId;
1062
+ /** Debounce handles for {@link settleColumn} — row-boundary snapping now happens in JS, only
1063
+ * once scrolling has actually stopped, instead of via CSS scroll-snap-type. CSS snapping on a
1064
+ * list this short fights real-time mouse-wheel input in Chrome/Windows (the browser re-snaps
1065
+ * after every wheel tick), which is what actually caused the "gets stuck" symptom. */
1066
+ private hourSnapTimeoutId;
1067
+ private minuteSnapTimeoutId;
1068
+ private secondSnapTimeoutId;
623
1069
  getHours(): number[];
624
1070
  getAMPMOptions(): string[];
625
1071
  onHourScroll(): void;
626
1072
  onMinuteScroll(): void;
627
1073
  onSecondScroll(): void;
628
- /**
629
- * Resolve the dropdown placement against the current viewport.
630
- * - autoPosition: flip above when there isn't room below, and flip the horizontal
631
- * side when the preferred side would overflow.
632
- * - appendToBody: additionally anchor with `position: fixed` (inline style) so the
633
- * dropdown escapes ancestor overflow and follows the trigger on scroll.
634
- */
635
- private updatePosition;
1074
+ /** Debounces settleColumn() to 120ms after the last scroll event on that column — i.e. it only
1075
+ * runs once the user has actually stopped scrolling, never mid-gesture. */
1076
+ private scheduleSnapSettle;
1077
+ /** Snaps a column to the row boundary nearest its current scroll position — the JS equivalent
1078
+ * of what `scroll-snap-align: center` used to do, minus the mouse-wheel "stuck" bug that came
1079
+ * with doing it via CSS on a list this short (see .time-scroll's CSS comment). */
1080
+ private settleColumn;
1081
+ /** Fires whenever CDK (re)applies a position, including the first one after open. Reads back
1082
+ * which of `positionMeta`'s tagged entries CDK actually used, to drive the CSS flip-direction
1083
+ * class the same value the old manual updatePosition() used to. */
1084
+ onPositionChange(event: ConnectedOverlayPositionChange): void;
636
1085
  private viewportListenersAttached;
637
1086
  private viewportRafId;
638
1087
  private readonly onViewportChange;
1088
+ /**
1089
+ * CDK's own scroll strategy only reacts to real `document`/`window` scroll — it has no way to
1090
+ * know an app shell might scroll a nested container instead. A capture-phase listener on
1091
+ * `document` still sees scroll events fired on any descendant scrollable element (scroll
1092
+ * doesn't bubble, but capture does) — the same trick `bk-custom-calendar` uses. Throttled
1093
+ * through requestAnimationFrame to avoid layout thrash during fast scrolling.
1094
+ */
639
1095
  private handleViewportChange;
640
1096
  /** True when the trigger has scrolled out of the viewport or out of any clipping ancestor. */
641
1097
  private isTriggerOutOfView;
@@ -644,7 +1100,7 @@ declare class BkTimePicker implements OnInit, OnChanges, OnDestroy, AfterViewIni
644
1100
  private detachViewportListeners;
645
1101
  ngOnDestroy(): void;
646
1102
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<BkTimePicker, never>;
647
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<BkTimePicker, "bk-time-picker", never, { "required": { "alias": "required"; "required": false; }; "value": { "alias": "value"; "required": false; }; "label": { "alias": "label"; "required": false; }; "placeholder": { "alias": "placeholder"; "required": false; }; "clearable": { "alias": "clearable"; "required": false; }; "position": { "alias": "position"; "required": false; }; "variation": { "alias": "variation"; "required": false; }; "pickerId": { "alias": "pickerId"; "required": false; }; "closePicker": { "alias": "closePicker"; "required": false; }; "timeFormat": { "alias": "timeFormat"; "required": false; }; "showSeconds": { "alias": "showSeconds"; "required": false; }; "autoPosition": { "alias": "autoPosition"; "required": false; }; "appendToBody": { "alias": "appendToBody"; "required": false; }; "disabled": { "alias": "disabled"; "required": false; }; }, { "change": "change"; "timeChange": "timeChange"; "pickerOpened": "pickerOpened"; "pickerClosed": "pickerClosed"; }, never, never, true, never>;
1103
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<BkTimePicker, "bk-time-picker", never, { "required": { "alias": "required"; "required": false; }; "value": { "alias": "value"; "required": false; }; "label": { "alias": "label"; "required": false; }; "placeholder": { "alias": "placeholder"; "required": false; }; "clearable": { "alias": "clearable"; "required": false; }; "position": { "alias": "position"; "required": false; }; "variation": { "alias": "variation"; "required": false; }; "pickerId": { "alias": "pickerId"; "required": false; }; "closePicker": { "alias": "closePicker"; "required": false; }; "timeFormat": { "alias": "timeFormat"; "required": false; }; "showSeconds": { "alias": "showSeconds"; "required": false; }; "autoPosition": { "alias": "autoPosition"; "required": false; }; "appendToBody": { "alias": "appendToBody"; "required": false; }; "viewportMargin": { "alias": "viewportMargin"; "required": false; }; "panelClass": { "alias": "panelClass"; "required": false; }; "disabled": { "alias": "disabled"; "required": false; }; }, { "change": "change"; "timeChange": "timeChange"; "pickerOpened": "pickerOpened"; "pickerClosed": "pickerClosed"; }, never, never, true, never>;
648
1104
  }
649
1105
 
650
1106
  /**
@@ -4112,7 +4568,7 @@ declare class BkTd extends BkCellBase implements AfterViewInit, OnDestroy {
4112
4568
  */
4113
4569
  protected readonly truncatedTooltip: _angular_core.WritableSignal<string>;
4114
4570
  /** Forwarded to the content wrapper's `bkTooltip`. Override on a column whose default 'right' would run into its neighbour. */
4115
- tooltipPosition: _angular_core.InputSignal<"top" | "bottom" | "left" | "right">;
4571
+ tooltipPosition: _angular_core.InputSignal<"left" | "right" | "top" | "bottom">;
4116
4572
  /** Only present when `ellipsis()` is true — see the `@else` branch in the template. */
4117
4573
  private ellipsisTextRef?;
4118
4574
  checkbox: _angular_core.InputSignal<boolean>;
@@ -4429,5 +4885,5 @@ declare class BkTreeDrag<T = any> extends BkTableDrag implements OnDestroy {
4429
4885
 
4430
4886
  declare const BK_TABLE: readonly [typeof BkTable, typeof BkTableTitle, typeof BkTableFooter, typeof BkVirtualScroll, typeof BkTh, typeof BkTd, typeof BkTrExpand, typeof BkTableSummary, typeof BkTableDrag, typeof BkDragHandle, typeof BkTreeDrag];
4431
4887
 
4432
- export { ARROW_CORNER_GAP, BKTooltipDirective, BK_DEFAULT_DIALOG_CONFIG, BK_DIALOG_DATA, BK_DIALOG_GLOBAL_CONFIG, BK_TABLE, BkAvatar, BkAvatarGroup, BkAvatarUploader, BkBadge, BkBreadcrumb, BkButton, BkButtonGroup, BkCalendarManagerService, BkCheckbox, BkColumnFilterService, BkColumnSelect, BkCustomCalendar, BkDialogActions, BkDialogClose, BkDialogContent, BkDialogModule, BkDialogRef, BkDialogService, BkDialogTitle, BkDragHandle, BkDropdown, BkFileCard, BkFilePicker, BkGrid, BkHierarchicalSelect, BkIconButton, BkInput, BkInputChips, BkLoader, BkMenu, BkPagination, BkPill, BkPopover, BkRadioButton, BkScheduledDatePicker, BkSelect, BkSpinner, BkTable, BkTableDrag, BkTableFooter, BkTableSummary, BkTableTitle, BkTabs, BkTd, BkTextarea, BkTh, BkTimePicker, BkToastr, BkToastrService, BkToggle, BkTooltipInteractionService, BkTrExpand, BkTreeDrag, BkValidator, BkVirtualScroll, BrickclayIcons, BrickclayLib, CalendarModule, CalendarSelection, ColumnFilterOption, DEFAULT_COUNTRY_OPTIONS, NEUTRAL_APPEARANCE, OPPOSITE_SIDE, POPOVER_PLACEMENTS, clamp, containsBkTreeNode, deriveAppearance, flattenBkTreeData, getDialogBackdropAnimation, getDialogPanelAnimation, joinPlacement, moveBkTreeNode, normalizeBkTableSize, parseColor, splitPlacement };
4888
+ export { ARROW_CORNER_GAP, BKTooltipDirective, BK_DEFAULT_DIALOG_CONFIG, BK_DIALOG_DATA, BK_DIALOG_GLOBAL_CONFIG, BK_TABLE, BkAvatar, BkAvatarGroup, BkAvatarUploader, BkBadge, BkBreadcrumb, BkButton, BkButtonGroup, BkCalendarManagerService, BkCheckbox, BkColumnFilterService, BkColumnSelect, BkCustomCalendar, BkDialogActions, BkDialogClose, BkDialogContent, BkDialogModule, BkDialogRef, BkDialogService, BkDialogTitle, BkDragHandle, BkDropdown, BkFileCard, BkFilePicker, BkGrid, BkHierarchicalSelect, BkIconButton, BkInput, BkInputChips, BkLoader, BkMenu, BkPagination, BkPill, BkPopover, BkRadioButton, BkScheduledDatePicker, BkSelect, BkSpinner, BkTable, BkTableDrag, BkTableFooter, BkTableSummary, BkTableTitle, BkTabs, BkTd, BkTextarea, BkTh, BkTimePicker, BkToastr, BkToastrService, BkToggle, BkTooltipInteractionService, BkTrExpand, BkTreeDrag, BkValidator, BkVirtualScroll, BrickclayIcons, BrickclayLib, CalendarModule, CalendarMonth, CalendarSelection, CalendarWeekday, ColumnFilterOption, DEFAULT_COUNTRY_OPTIONS, NEUTRAL_APPEARANCE, OPPOSITE_SIDE, POPOVER_PLACEMENTS, clamp, containsBkTreeNode, deriveAppearance, flattenBkTreeData, getDialogBackdropAnimation, getDialogPanelAnimation, joinPlacement, moveBkTreeNode, normalizeBkTableSize, parseColor, splitPlacement };
4433
4889
  export type { AvatarFallback, AvatarGroupItem, AvatarSize, AvatarVariant, BadgeColor, BadgeSize, BadgeVariant, BkAnimationKeyframes, BkAvatarFallback, BkAvatarSize, BkCellAlign, BkColumnRef, BkColumnSelectPosition, BkDialogAnimation, BkDialogConfig, BkDialogPosition, BkFilterFn, BkFilterOption, BkInputAutoCapitalize, BkInputAutoComplete, BkInputMode, BkInputSize, BkInputType, BkLoaderVariant, BkPageSize, BkPageSizeVisibility, BkSelectGridColumn, BkSortDirection, BkSortFn, BkSortOrder, BkTableNoResult, BkTableQueryParams, BkTableScroll, BkTableSelection, BkTableSize, BkTableSizeToken, BkTextAreaAutoCapitalize, BkTextAreaAutoComplete, BkTextAreaInputMode, BkTooltipDismissible, BkTreeDragScope, BkTreeDropEvent, BkTreeDropPosition, BkTreeRow, BreadcrumbItem, ButtonSize, ButtonVariant, CalendarRange, ColorAppearance, ColorFill, CountryOption, CustomRangesConfig, DotPosition, DotStatus, DropdownItem, DropdownPlacement, DropdownSize, DropdownTrigger, DropdownVariant, FileState, GroupItem, GroupMode, HierarchicalNode, IconButtonSize, IconButtonVariant, IconOrientation, MenuItem, MenuOrientation, MenuPopupSide, MenuSize, MenuSubmenuMode, MenuTrigger, PillColor, PillSize, PillVariant, PopoverAlign, PopoverPlacement, PopoverSide, ScheduledDateSelection, SortDirection, SpinnerSize, TabIconDirection, TabItem, TableAction, TableBadge, TableColumn, TableIcon, TableRows, TabsColors, TabsOrientation, TabsVariant, TimeConfiguration, ToastConfig, ToastMessage, ToastMethodOptions, ToastPosition, ToastSeverity };