@brickclay-org/ui 0.1.84 → 0.1.86

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,24 +1,25 @@
1
1
  import * as _angular_core from '@angular/core';
2
- import { EventEmitter, OnInit, OnDestroy, OnChanges, ElementRef, Renderer2, SimpleChanges, AfterViewInit, QueryList, ComponentRef, Type, InjectionToken, ApplicationRef, EnvironmentInjector, ChangeDetectorRef, TemplateRef, WritableSignal } from '@angular/core';
2
+ import { EventEmitter, OnInit, OnDestroy, OnChanges, ElementRef, Renderer2, SimpleChanges, AfterViewInit, QueryList, ComponentRef, Type, InjectionToken, NgZone, ApplicationRef, EnvironmentInjector, ChangeDetectorRef, TemplateRef, WritableSignal } from '@angular/core';
3
3
  import { ControlValueAccessor, Validator, AbstractControl, ValidationErrors, NgControl, NgModel } from '@angular/forms';
4
4
  import * as rxjs from 'rxjs';
5
5
  import { Observable } from 'rxjs';
6
6
  import * as i1 from '@angular/common';
7
7
  import * as i1$3 from '@angular/cdk/drag-drop';
8
8
  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';
9
11
  import { NgxMaskDirective } from 'ngx-mask';
10
12
  import * as i1$2 from '@angular/cdk/dialog';
11
13
  import { CdkDialogContainer, DialogRef } from '@angular/cdk/dialog';
12
- import * as i2 from '@angular/cdk/overlay';
13
- import { ScrollStrategy } from '@angular/cdk/overlay';
14
14
  import * as i3 from '@angular/cdk/portal';
15
15
  import { ComponentPortal } from '@angular/cdk/portal';
16
16
  import * as i1$1 from '@angular/cdk/scrolling';
17
- import * as _brickclay_org_ui from '@brickclay-org/ui';
18
17
 
19
18
  declare const BrickclayIcons: {
20
19
  readonly arrowleft: "assets/icons/chevron-left.svg";
21
20
  readonly arrowRight: "assets/icons/chevron-right.svg";
21
+ readonly arrowLeftDouble: "assets/icons/double-chevron-left.svg";
22
+ readonly arrowRightDouble: "assets/icons/double-chevron-right.svg";
22
23
  readonly calenderIcon: "assets/icons/custom-calender.svg";
23
24
  readonly timerIcon: "assets/icons/timer.svg";
24
25
  };
@@ -171,6 +172,8 @@ declare class BkCustomCalendar implements OnInit, OnDestroy, OnChanges, ControlV
171
172
  brickclayIcons: {
172
173
  readonly arrowleft: "assets/icons/chevron-left.svg";
173
174
  readonly arrowRight: "assets/icons/chevron-right.svg";
175
+ readonly arrowLeftDouble: "assets/icons/double-chevron-left.svg";
176
+ readonly arrowRightDouble: "assets/icons/double-chevron-right.svg";
174
177
  readonly calenderIcon: "assets/icons/custom-calender.svg";
175
178
  readonly timerIcon: "assets/icons/timer.svg";
176
179
  };
@@ -562,6 +565,8 @@ declare class BkTimePicker implements OnInit, OnChanges, OnDestroy, AfterViewIni
562
565
  brickclayIcons: {
563
566
  readonly arrowleft: "assets/icons/chevron-left.svg";
564
567
  readonly arrowRight: "assets/icons/chevron-right.svg";
568
+ readonly arrowLeftDouble: "assets/icons/double-chevron-left.svg";
569
+ readonly arrowRightDouble: "assets/icons/double-chevron-right.svg";
565
570
  readonly calenderIcon: "assets/icons/custom-calender.svg";
566
571
  readonly timerIcon: "assets/icons/timer.svg";
567
572
  };
@@ -954,6 +959,8 @@ declare class BkGrid<T = any> {
954
959
  noRecordFoundHeight: string;
955
960
  /** Custom illustration for the empty state. Falls back to a built-in SVG when omitted. */
956
961
  noRecordImgUrl?: string;
962
+ /** Show the empty-state illustration above the message. Set false to show the message only. */
963
+ showNoRecordImg: boolean;
957
964
  noRecordMessage: string;
958
965
  /** Static row class or a function that returns a class per row. Row data may also include `class` via TableRows. */
959
966
  rows?: string | ((row: T) => string | undefined);
@@ -994,7 +1001,7 @@ declare class BkGrid<T = any> {
994
1001
  onDragStart(event: CdkDragStart<any>): void;
995
1002
  get noRecordImagePath(): string;
996
1003
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<BkGrid<any>, never>;
997
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<BkGrid<any>, "bk-grid", never, { "draggable": { "alias": "draggable"; "required": false; }; "columns": { "alias": "columns"; "required": false; }; "result": { "alias": "result"; "required": false; }; "actions": { "alias": "actions"; "required": false; }; "customClass": { "alias": "customClass"; "required": false; }; "actionIconClass": { "alias": "actionIconClass"; "required": false; }; "actionClass": { "alias": "actionClass"; "required": false; }; "showNoRecords": { "alias": "showNoRecords"; "required": false; }; "noRecordFoundHeight": { "alias": "noRecordFoundHeight"; "required": false; }; "noRecordImgUrl": { "alias": "noRecordImgUrl"; "required": false; }; "noRecordMessage": { "alias": "noRecordMessage"; "required": false; }; "rows": { "alias": "rows"; "required": false; }; }, { "change": "change"; "actionClick": "actionClick"; "sortChange": "sortChange"; "dragDropChange": "dragDropChange"; }, never, never, true, never>;
1004
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<BkGrid<any>, "bk-grid", never, { "draggable": { "alias": "draggable"; "required": false; }; "columns": { "alias": "columns"; "required": false; }; "result": { "alias": "result"; "required": false; }; "actions": { "alias": "actions"; "required": false; }; "customClass": { "alias": "customClass"; "required": false; }; "actionIconClass": { "alias": "actionIconClass"; "required": false; }; "actionClass": { "alias": "actionClass"; "required": false; }; "showNoRecords": { "alias": "showNoRecords"; "required": false; }; "noRecordFoundHeight": { "alias": "noRecordFoundHeight"; "required": false; }; "noRecordImgUrl": { "alias": "noRecordImgUrl"; "required": false; }; "showNoRecordImg": { "alias": "showNoRecordImg"; "required": false; }; "noRecordMessage": { "alias": "noRecordMessage"; "required": false; }; "rows": { "alias": "rows"; "required": false; }; }, { "change": "change"; "actionClick": "actionClick"; "sortChange": "sortChange"; "dragDropChange": "dragDropChange"; }, never, never, true, never>;
998
1005
  }
999
1006
 
1000
1007
  /** How an accent colour is applied to a control. */
@@ -1060,7 +1067,7 @@ declare class BkSelect implements ControlValueAccessor, AfterViewInit, OnDestroy
1060
1067
  dropdownView: _angular_core.InputSignal<"list" | "grid">;
1061
1068
  gridColumns: _angular_core.InputSignal<BkSelectGridColumn[]>;
1062
1069
  /** Grid density. `inherit` follows the trigger's existing `variation`. */
1063
- gridVariation: _angular_core.InputSignal<"default" | "sm" | "inherit">;
1070
+ gridVariation: _angular_core.InputSignal<"inherit" | "default" | "sm">;
1064
1071
  /** Stage multi-grid changes until Apply is clicked. */
1065
1072
  gridSelectionActions: _angular_core.InputSignal<boolean>;
1066
1073
  gridMinWidth: _angular_core.InputSignal<string>;
@@ -1110,9 +1117,21 @@ declare class BkSelect implements ControlValueAccessor, AfterViewInit, OnDestroy
1110
1117
  disabled: _angular_core.ModelSignal<boolean>;
1111
1118
  loading: _angular_core.InputSignal<boolean>;
1112
1119
  closeOnSelect: _angular_core.InputSignal<boolean>;
1120
+ /**
1121
+ * When on, the dropdown opens as soon as the control receives keyboard focus
1122
+ * (e.g. tabbing to it) and closes once focus leaves it. Off by default so the
1123
+ * existing click/Enter-to-open behaviour is unchanged.
1124
+ */
1125
+ openOnFocus: _angular_core.InputSignal<boolean>;
1113
1126
  dropdownPosition: _angular_core.InputSignal<"top" | "bottom">;
1114
1127
  hasError: boolean;
1115
1128
  errorMessage: string;
1129
+ /**
1130
+ * @deprecated No-op, kept only so existing `[appendToBody]="true"` bindings don't break.
1131
+ * The dropdown now always positions via Angular CDK Overlay, which portals into the shared
1132
+ * `cdk-overlay-container` unconditionally — the exact clipping/stacking escape this input used
1133
+ * to opt into by hand is now the only behaviour there is. Safe to remove from call sites.
1134
+ */
1116
1135
  appendToBody: _angular_core.InputSignal<boolean>;
1117
1136
  open: _angular_core.OutputEmitterRef<void>;
1118
1137
  close: _angular_core.OutputEmitterRef<void>;
@@ -1130,6 +1149,7 @@ declare class BkSelect implements ControlValueAccessor, AfterViewInit, OnDestroy
1130
1149
  optionsRef: QueryList<ElementRef>;
1131
1150
  controlWrapper: ElementRef<HTMLDivElement>;
1132
1151
  dropdownPanel?: ElementRef<HTMLDivElement>;
1152
+ selectOverlay?: CdkConnectedOverlay;
1133
1153
  chipsViewport?: ElementRef<HTMLDivElement>;
1134
1154
  measureChips?: QueryList<ElementRef<HTMLElement>>;
1135
1155
  measurePlus?: ElementRef<HTMLElement>;
@@ -1143,17 +1163,15 @@ declare class BkSelect implements ControlValueAccessor, AfterViewInit, OnDestroy
1143
1163
  searchTerm: _angular_core.WritableSignal<string>;
1144
1164
  markedIndex: _angular_core.WritableSignal<number>;
1145
1165
  placement: _angular_core.WritableSignal<"top" | "bottom">;
1146
- private originalPanelParent;
1147
- private originalPanelAnchor;
1148
- private panelInBody;
1149
1166
  visibleCount: _angular_core.WritableSignal<number>;
1150
1167
  private resizeObserver?;
1151
- dropdownStyle: _angular_core.WritableSignal<{
1152
- top?: string;
1153
- bottom?: string;
1154
- left: string;
1155
- width: string;
1156
- }>;
1168
+ /**
1169
+ * Panel width, kept equal to the control's — matches the old `dropdownStyle().width`
1170
+ * behaviour. Set on open and re-measured by the same ResizeObserver that already tracks
1171
+ * the control for chip recomputation, so a responsive layout keeps the panel in sync while
1172
+ * it's open.
1173
+ */
1174
+ dropdownWidth: _angular_core.WritableSignal<number | null>;
1157
1175
  filteredItems: _angular_core.Signal<any[]>;
1158
1176
  /**
1159
1177
  * True when the user is allowed to change the selection. Both disabled and
@@ -1164,25 +1182,23 @@ declare class BkSelect implements ControlValueAccessor, AfterViewInit, OnDestroy
1164
1182
  constructor();
1165
1183
  ngAfterViewInit(): void;
1166
1184
  ngOnDestroy(): void;
1167
- private reposition;
1168
1185
  /**
1169
- * True when the control is fully outside the visible area either the
1170
- * viewport or a scrollable/clipping ancestor (e.g. a dialog's scroll body).
1171
- * Used to close the floating (append-to-body) panel once its anchor is no
1172
- * longer on screen, instead of leaving it hanging.
1173
- */
1174
- private isControlClipped;
1175
- /**
1176
- * Relocate the panel node to <body> so it truly escapes any transformed or
1177
- * overflow-clipped ancestor (dialogs, drawers, cards with overflow, etc.).
1178
- * Setting position:fixed alone is not enough when an ancestor has a transform.
1179
- */
1180
- private movePanelToBody;
1181
- /**
1182
- * Return the panel to its original DOM position so Angular's @if can remove
1183
- * it cleanly on close. Must run before isOpen is set to false.
1184
- */
1185
- private restorePanel;
1186
+ * CDK's default `reposition` scroll strategy only reacts to real `document`/`window` scroll
1187
+ * it has no way to know an app shell might scroll a nested container instead (this one
1188
+ * commonly does dashboard layouts with a fixed header/sidebar and a scrollable content pane).
1189
+ * A capture-phase listener on `document` still sees scroll events fired on any descendant
1190
+ * scrollable element (scroll doesn't bubble, but capture does) — same trick `bk-custom-calendar`
1191
+ * and the `bk-input` phone dropdown already use. rAF-throttled so a fast scroll doesn't force
1192
+ * layout on every tick.
1193
+ */
1194
+ private overlayScrollRafId;
1195
+ private readonly onOverlayScroll;
1196
+ private attachOverlayScrollTracking;
1197
+ private detachOverlayScrollTracking;
1198
+ /** Derives `placement` (for the `[data-position="top"]` CSS margin tweak) from which of
1199
+ * `selectDropdownPositions` CDK actually applied replaces the old hand-rolled space
1200
+ * comparison now that the fit/flip decision itself lives in CDK. */
1201
+ onPositionChange(event: ConnectedOverlayPositionChange): void;
1186
1202
  private scheduleRecompute;
1187
1203
  /**
1188
1204
  * Measures the off-screen chip row and works out how many chips fit on a
@@ -1207,15 +1223,12 @@ declare class BkSelect implements ControlValueAccessor, AfterViewInit, OnDestroy
1207
1223
  toggleDropdown(event: Event | null): void;
1208
1224
  openDropdown(): void;
1209
1225
  closeDropdown(): void;
1210
- getTop(): string | null;
1211
- getBottom(): string | null;
1212
1226
  /**
1213
- * Decides which side the panel should open on (auto-flip) and, for
1214
- * append-to-body mode, computes the fixed coordinates. Shared by both modes
1215
- * and re-run on open and on every scroll/resize so the panel snaps to
1216
- * whichever side has room — honouring `dropdownPosition` when it fits.
1227
+ * Preferred position list handed to CDK, in try-order — honours `dropdownPosition` first,
1228
+ * falling back to the other side only when the preferred one genuinely doesn't fit. Replaces
1229
+ * the old hand-rolled `applyPlacement()` space comparison entirely.
1217
1230
  */
1218
- applyPlacement(): void;
1231
+ get selectDropdownPositions(): ConnectedPosition[];
1219
1232
  toggleSelectAll(event: Event): void;
1220
1233
  handleSelection(item: any, event?: Event): void;
1221
1234
  handleGridSelection(item: any, event?: Event): void;
@@ -1225,6 +1238,29 @@ declare class BkSelect implements ControlValueAccessor, AfterViewInit, OnDestroy
1225
1238
  handleClear(event: Event): void;
1226
1239
  private updateModel;
1227
1240
  onSearchInput(event: Event): void;
1241
+ /**
1242
+ * Options in the exact order they are rendered (groups flattened). This is the
1243
+ * space Up/Down navigation and the marked-index operate in — using the flat
1244
+ * `filteredItems()` would desync from the on-screen order once grouping
1245
+ * reshuffles rows. For an ungrouped list this equals `filteredItems()`.
1246
+ */
1247
+ flatOptions: _angular_core.Signal<any[]>;
1248
+ /** The option the marker currently sits on, or null. */
1249
+ private markedOption;
1250
+ /**
1251
+ * Whether an option carries the keyboard/hover marker. Matched by identity
1252
+ * rather than by row position so two groups can't share a highlight just
1253
+ * because the marked item sits at the same index within each group.
1254
+ */
1255
+ isMarked(item: any): boolean;
1256
+ /** Move the marker to a hovered option by its position in the rendered order. */
1257
+ markOption(item: any): void;
1258
+ /**
1259
+ * Rendered-order index that Up/Down navigation starts from when the dropdown
1260
+ * opens: the first already-selected option, or the first option when nothing
1261
+ * is selected. Returns -1 only for an empty list.
1262
+ */
1263
+ private initialMarkedIndex;
1228
1264
  onKeyDown(event: KeyboardEvent): void;
1229
1265
  onTabPress(event: KeyboardEvent): void;
1230
1266
  onScroll(event: Event): void;
@@ -1237,7 +1273,27 @@ declare class BkSelect implements ControlValueAccessor, AfterViewInit, OnDestroy
1237
1273
  setDisabledState(d: boolean): void;
1238
1274
  private resolveSelectedOptions;
1239
1275
  private el;
1240
- onClickOutside(e: Event): void;
1276
+ onOverlayOutsideClick(): void;
1277
+ private pointerFocus;
1278
+ private opening;
1279
+ onPointerDown(): void;
1280
+ /**
1281
+ * Open when *keyboard* focus arrives from outside the control (tabbing to it).
1282
+ * Pointer-initiated focus is ignored — a click opens via its own toggle, so
1283
+ * this stays a keyboard affordance. Focus moves that originate inside the
1284
+ * control or its panel — the panel handing focus to the search field, or focus
1285
+ * falling back to the trigger as the panel closes — must not (re)open it,
1286
+ * which would otherwise trap the user when tabbing backwards out of it.
1287
+ */
1288
+ onFocusIn(event: FocusEvent): void;
1289
+ /**
1290
+ * Close when focus leaves the control entirely — tabbing away, or focus moving
1291
+ * to any element outside the control and its (possibly body-appended) panel.
1292
+ * Internal focus moves keep it open.
1293
+ */
1294
+ onFocusOut(): void;
1295
+ /** True when a node lives inside the control host or its detached panel. */
1296
+ private isFocusInside;
1241
1297
  openFromLabel(event: MouseEvent): void;
1242
1298
  groupedItems: _angular_core.Signal<{
1243
1299
  group: string;
@@ -1285,7 +1341,7 @@ declare class BkSelect implements ControlValueAccessor, AfterViewInit, OnDestroy
1285
1341
  getTooltipIfEllipsed(el: HTMLElement, text: string): string;
1286
1342
  onOptionHover(index: number): void;
1287
1343
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<BkSelect, never>;
1288
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<BkSelect, "bk-select", never, { "items": { "alias": "items"; "required": false; "isSignal": true; }; "bindLabel": { "alias": "bindLabel"; "required": false; "isSignal": true; }; "bindValue": { "alias": "bindValue"; "required": false; "isSignal": true; }; "bindIcon": { "alias": "bindIcon"; "required": false; "isSignal": true; }; "isResponsive": { "alias": "isResponsive"; "required": false; "isSignal": true; }; "placeholder": { "alias": "placeholder"; "required": false; "isSignal": true; }; "notFoundText": { "alias": "notFoundText"; "required": false; "isSignal": true; }; "loadingText": { "alias": "loadingText"; "required": false; "isSignal": true; }; "clearAllText": { "alias": "clearAllText"; "required": false; "isSignal": true; }; "groupBy": { "alias": "groupBy"; "required": false; "isSignal": true; }; "colorKey": { "alias": "colorKey"; "required": false; "isSignal": true; }; "dropdownView": { "alias": "dropdownView"; "required": false; "isSignal": true; }; "gridColumns": { "alias": "gridColumns"; "required": false; "isSignal": true; }; "gridVariation": { "alias": "gridVariation"; "required": false; "isSignal": true; }; "gridSelectionActions": { "alias": "gridSelectionActions"; "required": false; "isSignal": true; }; "gridMinWidth": { "alias": "gridMinWidth"; "required": false; "isSignal": true; }; "gridMaxHeight": { "alias": "gridMaxHeight"; "required": false; "isSignal": true; }; "gridSelectedLabelKeys": { "alias": "gridSelectedLabelKeys"; "required": false; "isSignal": true; }; "gridSelectedLabelSeparator": { "alias": "gridSelectedLabelSeparator"; "required": false; "isSignal": true; }; "gridApplyText": { "alias": "gridApplyText"; "required": false; "isSignal": true; }; "gridClearText": { "alias": "gridClearText"; "required": false; "isSignal": true; }; "showDots": { "alias": "showDots"; "required": false; "isSignal": true; }; "showAvatar": { "alias": "showAvatar"; "required": false; "isSignal": true; }; "avatarKey": { "alias": "avatarKey"; "required": false; "isSignal": true; }; "iconAlt": { "alias": "iconAlt"; "required": false; }; "label": { "alias": "label"; "required": false; }; "required": { "alias": "required"; "required": false; }; "variation": { "alias": "variation"; "required": false; }; "iconSrc": { "alias": "iconSrc"; "required": false; }; "multiple": { "alias": "multiple"; "required": false; "isSignal": true; }; "maxLabels": { "alias": "maxLabels"; "required": false; "isSignal": true; }; "searchable": { "alias": "searchable"; "required": false; "isSignal": true; }; "allSelect": { "alias": "allSelect"; "required": false; "isSignal": true; }; "clearable": { "alias": "clearable"; "required": false; "isSignal": true; }; "readonly": { "alias": "readonly"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "loading": { "alias": "loading"; "required": false; "isSignal": true; }; "closeOnSelect": { "alias": "closeOnSelect"; "required": false; "isSignal": true; }; "dropdownPosition": { "alias": "dropdownPosition"; "required": false; "isSignal": true; }; "hasError": { "alias": "hasError"; "required": false; }; "errorMessage": { "alias": "errorMessage"; "required": false; }; "appendToBody": { "alias": "appendToBody"; "required": false; "isSignal": true; }; "compareWith": { "alias": "compareWith"; "required": false; "isSignal": true; }; }, { "disabled": "disabledChange"; "open": "open"; "close": "close"; "focus": "focus"; "blur": "blur"; "search": "search"; "clear": "clear"; "change": "change"; "scrollToEnd": "scrollToEnd"; }, never, never, true, never>;
1344
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<BkSelect, "bk-select", never, { "items": { "alias": "items"; "required": false; "isSignal": true; }; "bindLabel": { "alias": "bindLabel"; "required": false; "isSignal": true; }; "bindValue": { "alias": "bindValue"; "required": false; "isSignal": true; }; "bindIcon": { "alias": "bindIcon"; "required": false; "isSignal": true; }; "isResponsive": { "alias": "isResponsive"; "required": false; "isSignal": true; }; "placeholder": { "alias": "placeholder"; "required": false; "isSignal": true; }; "notFoundText": { "alias": "notFoundText"; "required": false; "isSignal": true; }; "loadingText": { "alias": "loadingText"; "required": false; "isSignal": true; }; "clearAllText": { "alias": "clearAllText"; "required": false; "isSignal": true; }; "groupBy": { "alias": "groupBy"; "required": false; "isSignal": true; }; "colorKey": { "alias": "colorKey"; "required": false; "isSignal": true; }; "dropdownView": { "alias": "dropdownView"; "required": false; "isSignal": true; }; "gridColumns": { "alias": "gridColumns"; "required": false; "isSignal": true; }; "gridVariation": { "alias": "gridVariation"; "required": false; "isSignal": true; }; "gridSelectionActions": { "alias": "gridSelectionActions"; "required": false; "isSignal": true; }; "gridMinWidth": { "alias": "gridMinWidth"; "required": false; "isSignal": true; }; "gridMaxHeight": { "alias": "gridMaxHeight"; "required": false; "isSignal": true; }; "gridSelectedLabelKeys": { "alias": "gridSelectedLabelKeys"; "required": false; "isSignal": true; }; "gridSelectedLabelSeparator": { "alias": "gridSelectedLabelSeparator"; "required": false; "isSignal": true; }; "gridApplyText": { "alias": "gridApplyText"; "required": false; "isSignal": true; }; "gridClearText": { "alias": "gridClearText"; "required": false; "isSignal": true; }; "showDots": { "alias": "showDots"; "required": false; "isSignal": true; }; "showAvatar": { "alias": "showAvatar"; "required": false; "isSignal": true; }; "avatarKey": { "alias": "avatarKey"; "required": false; "isSignal": true; }; "iconAlt": { "alias": "iconAlt"; "required": false; }; "label": { "alias": "label"; "required": false; }; "required": { "alias": "required"; "required": false; }; "variation": { "alias": "variation"; "required": false; }; "iconSrc": { "alias": "iconSrc"; "required": false; }; "multiple": { "alias": "multiple"; "required": false; "isSignal": true; }; "maxLabels": { "alias": "maxLabels"; "required": false; "isSignal": true; }; "searchable": { "alias": "searchable"; "required": false; "isSignal": true; }; "allSelect": { "alias": "allSelect"; "required": false; "isSignal": true; }; "clearable": { "alias": "clearable"; "required": false; "isSignal": true; }; "readonly": { "alias": "readonly"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "loading": { "alias": "loading"; "required": false; "isSignal": true; }; "closeOnSelect": { "alias": "closeOnSelect"; "required": false; "isSignal": true; }; "openOnFocus": { "alias": "openOnFocus"; "required": false; "isSignal": true; }; "dropdownPosition": { "alias": "dropdownPosition"; "required": false; "isSignal": true; }; "hasError": { "alias": "hasError"; "required": false; }; "errorMessage": { "alias": "errorMessage"; "required": false; }; "appendToBody": { "alias": "appendToBody"; "required": false; "isSignal": true; }; "compareWith": { "alias": "compareWith"; "required": false; "isSignal": true; }; }, { "disabled": "disabledChange"; "open": "open"; "close": "close"; "focus": "focus"; "blur": "blur"; "search": "search"; "clear": "clear"; "change": "change"; "scrollToEnd": "scrollToEnd"; }, never, never, true, never>;
1289
1345
  }
1290
1346
 
1291
1347
  type BkInputType = 'text' | 'email' | 'password' | 'number' | 'url' | 'tel';
@@ -1301,6 +1357,8 @@ interface CountryOption {
1301
1357
  prefix: string;
1302
1358
  placeholder: string;
1303
1359
  }
1360
+ /** Default country list used when a consumer does not provide their own via `[countryOptions]`. */
1361
+ declare const DEFAULT_COUNTRY_OPTIONS: CountryOption[];
1304
1362
  declare class BkInput implements OnInit, OnDestroy, AfterViewInit, ControlValueAccessor {
1305
1363
  id: string;
1306
1364
  name: string;
@@ -1331,7 +1389,14 @@ declare class BkInput implements OnInit, OnDestroy, AfterViewInit, ControlValueA
1331
1389
  currencyDecimals: number;
1332
1390
  allowNegative: boolean;
1333
1391
  countryCode: string;
1334
- countryOptions: CountryOption[];
1392
+ /**
1393
+ * Consumer-supplied country list. When a non-empty list is provided it replaces the
1394
+ * built-in list; when omitted (or given a null/empty value) the DEFAULT_COUNTRY_OPTIONS
1395
+ * are used so the selector always has values to show.
1396
+ */
1397
+ private _countryOptions;
1398
+ set countryOptions(value: CountryOption[] | null | undefined);
1399
+ get countryOptions(): CountryOption[];
1335
1400
  selectedCountry: CountryOption;
1336
1401
  iconOrientation: IconOrientation;
1337
1402
  password: boolean;
@@ -1342,15 +1407,22 @@ declare class BkInput implements OnInit, OnDestroy, AfterViewInit, ControlValueA
1342
1407
  step: number | null;
1343
1408
  maxlength: number | null;
1344
1409
  minlength: number | null;
1345
- dropdownRef?: ElementRef;
1346
- selectRef?: ElementRef;
1347
1410
  inputField?: ElementRef<HTMLInputElement>;
1348
1411
  maskDirective?: NgxMaskDirective;
1412
+ phoneOverlay?: CdkConnectedOverlay;
1349
1413
  isFocused: boolean;
1350
1414
  inputValue: string;
1351
1415
  isDropdownOpen: boolean;
1352
1416
  private pendingMaskedValue;
1353
1417
  private isPropagatingViewValue;
1418
+ /**
1419
+ * Country dropdown panel, positioned via CDK Overlay instead of an inline absolute-positioned
1420
+ * div. This escapes clipping inside dialogs/scroll containers and stacks correctly above CDK
1421
+ * Overlay-based content (e.g. `bk-dialog`) regardless of Angular version — see the
1422
+ * "Dropdown & Calendar Overlay Strategy" doc. Primary: below the trigger, left-aligned (mirrors
1423
+ * the old `top-full left-0 mt-1`). Fallback: above the trigger when there's no room below.
1424
+ */
1425
+ readonly phoneDropdownPositions: ConnectedPosition[];
1354
1426
  input: EventEmitter<Event>;
1355
1427
  change: EventEmitter<Event>;
1356
1428
  focus: EventEmitter<Event>;
@@ -1375,13 +1447,31 @@ declare class BkInput implements OnInit, OnDestroy, AfterViewInit, ControlValueA
1375
1447
  ngOnInit(): void;
1376
1448
  ngAfterViewInit(): void;
1377
1449
  ngOnDestroy(): void;
1378
- onDocumentClick(event: MouseEvent): void;
1379
1450
  handleFocus(event: Event): void;
1380
1451
  handleBlur(event: Event): void;
1381
1452
  handleInput(event: Event): void;
1382
1453
  handleClicked(): void;
1383
1454
  handleChange(event: Event): void;
1384
1455
  toggleDropdown(event?: Event): void;
1456
+ /**
1457
+ * Shared close path for the CDK overlay: outside click, the overlay detaching (e.g. on
1458
+ * destroy), and another `bk-input` phone field opening (see closeAllDropdownsHandler).
1459
+ */
1460
+ closeDropdown(): void;
1461
+ /**
1462
+ * CDK's `reposition` scroll strategy (the default, and what we want — no `block`/`close`
1463
+ * behaviour) only reacts to real `document`/`window` scroll. It has no way to know about an
1464
+ * app shell that scrolls a nested container instead (this one does — see the layout's
1465
+ * `overflow-y-auto` content wrapper), so without this the panel would stay frozen in place
1466
+ * while its trigger scrolls out from under it. A capture-phase listener on `document` still
1467
+ * sees scroll events fired on any descendant scrollable element (scroll doesn't bubble, but
1468
+ * capture does), same trick `bk-custom-calendar` uses for its own popup. rAF-throttled so a
1469
+ * fast scroll doesn't force layout on every tick.
1470
+ */
1471
+ private phoneOverlayScrollRafId;
1472
+ private readonly onPhoneOverlayScroll;
1473
+ private attachPhoneOverlayScrollTracking;
1474
+ private detachPhoneOverlayScrollTracking;
1385
1475
  selectCountry(country: CountryOption): void;
1386
1476
  togglePasswordVisibility(event: Event): void;
1387
1477
  handleIconClick(event: Event): void;
@@ -2156,9 +2246,45 @@ declare function getDialogBackdropAnimation(enterDuration: number, leaveDuration
2156
2246
  leave: BkAnimationKeyframes;
2157
2247
  };
2158
2248
 
2159
- declare class BKTooltipDirective implements OnInit, OnChanges, OnDestroy {
2249
+ /** A tooltip that reacts to interaction/viewport changes while it is on screen. */
2250
+ interface BkTooltipDismissible {
2251
+ hideOnGlobalInteraction(): void;
2252
+ repositionOnViewportChange(): void;
2253
+ }
2254
+ /**
2255
+ * Shared document/window listeners for `bkTooltip`.
2256
+ *
2257
+ * Every directive instance used to declare its own `document:mousedown`,
2258
+ * `window:scroll` and `window:resize` host listeners. On dense screens (the ticket
2259
+ * documents page renders several hundred tooltip hosts) a single click therefore ran
2260
+ * several hundred handlers and — because host listeners mark their view dirty —
2261
+ * forced a full application change-detection pass, which is what made rapid clicking
2262
+ * lock up the page.
2263
+ *
2264
+ * Only a tooltip that is currently visible has anything to do, so tooltips subscribe
2265
+ * while shown and unsubscribe when hidden. The listeners are attached outside the
2266
+ * Angular zone: they only mutate tooltip styles through `Renderer2`, so no change
2267
+ * detection is required.
2268
+ */
2269
+ declare class BkTooltipInteractionService {
2270
+ private readonly zone;
2271
+ private readonly visibleTooltips;
2272
+ private listenersAttached;
2273
+ constructor(zone: NgZone);
2274
+ /** Called when a tooltip becomes visible. */
2275
+ register(tooltip: BkTooltipDismissible): void;
2276
+ /** Called when a tooltip is hidden or destroyed. */
2277
+ unregister(tooltip: BkTooltipDismissible): void;
2278
+ private attachListeners;
2279
+ private forEachVisible;
2280
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<BkTooltipInteractionService, never>;
2281
+ static ɵprov: _angular_core.ɵɵInjectableDeclaration<BkTooltipInteractionService>;
2282
+ }
2283
+
2284
+ declare class BKTooltipDirective implements OnInit, OnChanges, OnDestroy, BkTooltipDismissible {
2160
2285
  private el;
2161
2286
  private renderer;
2287
+ private tooltipInteraction;
2162
2288
  tooltipContent: string | string[];
2163
2289
  tooltipPosition: 'left' | 'right' | 'top' | 'bottom';
2164
2290
  scrollable: boolean;
@@ -2170,7 +2296,8 @@ declare class BKTooltipDirective implements OnInit, OnChanges, OnDestroy {
2170
2296
  private isHoveringTooltip;
2171
2297
  private hideTimeout;
2172
2298
  private tooltipListeners;
2173
- constructor(el: ElementRef, renderer: Renderer2);
2299
+ private isRegisteredWithInteractionService;
2300
+ constructor(el: ElementRef, renderer: Renderer2, tooltipInteraction: BkTooltipInteractionService);
2174
2301
  ngOnInit(): void;
2175
2302
  ngOnChanges(changes: SimpleChanges): void;
2176
2303
  ngOnDestroy(): void;
@@ -2178,8 +2305,12 @@ declare class BKTooltipDirective implements OnInit, OnChanges, OnDestroy {
2178
2305
  appendContent(): void;
2179
2306
  onMouseEnter(): void;
2180
2307
  onMouseLeave(): void;
2181
- onInteract(): void;
2182
- onWindowChange(): void;
2308
+ /** @see BkTooltipInteractionService — shared document/window listeners */
2309
+ hideOnGlobalInteraction(): void;
2310
+ /** @see BkTooltipInteractionService — shared document/window listeners */
2311
+ repositionOnViewportChange(): void;
2312
+ private registerWithInteractionService;
2313
+ private unregisterFromInteractionService;
2183
2314
  private isTooltipContentEmpty;
2184
2315
  /** Snap-hide with no fade: used when something else (a dropdown panel, another
2185
2316
  * tooltip) is opening right now, so the two never visibly overlap mid-fade. */
@@ -2326,7 +2457,12 @@ declare class BkHierarchicalSelect implements ControlValueAccessor, AfterViewIni
2326
2457
  /** Search placeholder. */
2327
2458
  searchPlaceholder: _angular_core.InputSignal<string>;
2328
2459
  searchable: _angular_core.InputSignal<boolean>;
2329
- /** When true, dropdown is positioned fixed and sized to the trigger (use inside modals/popups). */
2460
+ /**
2461
+ * @deprecated No-op, kept only so existing `[appendToBody]="true"` bindings don't break.
2462
+ * The dropdown now always positions via Angular CDK Overlay, which portals into the shared
2463
+ * `cdk-overlay-container` unconditionally — the exact clipping/stacking escape this input used
2464
+ * to opt into by hand is now the only behaviour there is. Safe to remove from call sites.
2465
+ */
2330
2466
  appendToBody: _angular_core.InputSignal<boolean>;
2331
2467
  /** Open above or below the trigger (also used when appendToBody is true for fixed coordinates). */
2332
2468
  dropdownPosition: _angular_core.InputSignal<"top" | "bottom">;
@@ -2357,38 +2493,66 @@ declare class BkHierarchicalSelect implements ControlValueAccessor, AfterViewIni
2357
2493
  searchInput: ElementRef<HTMLInputElement>;
2358
2494
  controlWrapper: ElementRef<HTMLDivElement>;
2359
2495
  dropdownPanel?: ElementRef<HTMLDivElement>;
2496
+ optionsListContainer?: ElementRef<HTMLDivElement>;
2497
+ optionEls?: QueryList<ElementRef<HTMLElement>>;
2498
+ hierarchicalOverlay?: CdkConnectedOverlay;
2499
+ /** Index of the keyboard-highlighted option within the current level (`filteredItems`). */
2500
+ markedIndex: _angular_core.WritableSignal<number>;
2360
2501
  private el;
2361
2502
  placement: _angular_core.WritableSignal<"top" | "bottom">;
2362
- private originalPanelParent;
2363
- private originalPanelAnchor;
2364
- private panelInBody;
2503
+ /**
2504
+ * Panel width, kept equal to the control's — matches the old `dropdownStyle().width`
2505
+ * behaviour. Set on open and re-measured by a ResizeObserver on the control while open, so a
2506
+ * responsive layout keeps the panel in sync.
2507
+ */
2508
+ dropdownWidth: _angular_core.WritableSignal<number | null>;
2509
+ private resizeObserver?;
2365
2510
  constructor();
2366
2511
  ngAfterViewInit(): void;
2367
2512
  ngOnDestroy(): void;
2368
- private reposition;
2369
2513
  /**
2370
- * True when the control is fully outside the visible area either the
2371
- * viewport or a scrollable/clipping ancestor (e.g. a dialog's scroll body).
2514
+ * CDK's default `reposition` scroll strategy only reacts to real `document`/`window` scroll
2515
+ * it has no way to know an app shell might scroll a nested container instead (this one
2516
+ * commonly does — dashboard layouts with a fixed header/sidebar and a scrollable content pane).
2517
+ * A capture-phase listener on `document` still sees scroll events fired on any descendant
2518
+ * scrollable element (scroll doesn't bubble, but capture does) — same trick `bk-custom-calendar`,
2519
+ * `bk-input`'s phone dropdown, and `bk-select` already use. rAF-throttled so a fast scroll
2520
+ * doesn't force layout on every tick.
2521
+ */
2522
+ private overlayScrollRafId;
2523
+ private readonly onOverlayScroll;
2524
+ private attachOverlayScrollTracking;
2525
+ private detachOverlayScrollTracking;
2526
+ /** Derives `placement` (for the `[data-position]` inset CSS) from which of
2527
+ * `hierarchicalDropdownPositions` CDK actually applied — replaces the old hand-rolled space
2528
+ * comparison now that the fit/flip decision itself lives in CDK. */
2529
+ onPositionChange(event: ConnectedOverlayPositionChange): void;
2530
+ /** Runs after the panel has rendered: reset the level highlight + focus. Position/flip is
2531
+ * entirely CDK's job now (see hierarchicalDropdownPositions + onPositionChange). */
2532
+ private afterOpenInit;
2533
+ /**
2534
+ * Set the keyboard highlight for the level now on screen: the selected node if
2535
+ * it's in this level, otherwise the first option. -1 when the level is empty,
2536
+ * which keeps nothing highlighted.
2372
2537
  */
2373
- private isControlClipped;
2538
+ private resetMarked;
2374
2539
  /**
2375
- * Relocate the panel node to <body> so it truly escapes any transformed or
2376
- * overflow-clipped ancestor (dialogs, drawers, overflow cards, etc.).
2540
+ * Keyboard navigation. Up/Down move the highlight within the current level;
2541
+ * Right (or Enter on a parent) drills into children; Left goes back a level;
2542
+ * Enter on a leaf selects it; Escape/Tab close.
2377
2543
  */
2378
- private movePanelToBody;
2379
- /** Return the panel to its original DOM position before Angular removes it. */
2380
- private restorePanel;
2381
- /** Runs after the panel has rendered: relocate (append mode) + position + focus. */
2382
- private afterOpenInit;
2544
+ onKeyDown(event: KeyboardEvent): void;
2545
+ /** Sync the highlight to a hovered option so mouse and keyboard agree. */
2546
+ markOption(index: number): void;
2547
+ /** Scroll the highlighted option into view within the options list (immediate). */
2548
+ private scrollMarkedIntoView;
2549
+ /** Defer a scroll to the marked option (after a keyboard move re-lays the row). */
2550
+ private scrollToMarked;
2551
+ /** Navigate into a node's children and reset the highlight for the new level. */
2552
+ private enterNode;
2383
2553
  isOpen: _angular_core.WritableSignal<boolean>;
2384
2554
  /** Hidden until its real height has been measured and final placement applied. */
2385
2555
  panelReady: _angular_core.WritableSignal<boolean>;
2386
- dropdownStyle: _angular_core.WritableSignal<{
2387
- top?: string;
2388
- bottom?: string;
2389
- left: string;
2390
- width: string;
2391
- }>;
2392
2556
  searchTerm: _angular_core.WritableSignal<string>;
2393
2557
  /** Breadcrumb stack: each entry is the parent node we navigated into. */
2394
2558
  breadcrumb: _angular_core.WritableSignal<HierarchicalNode[]>;
@@ -2432,16 +2596,16 @@ declare class BkHierarchicalSelect implements ControlValueAccessor, AfterViewIni
2432
2596
  private buildPathTo;
2433
2597
  toggleDropdown(event?: Event): void;
2434
2598
  openDropdown(): void;
2599
+ /** Shared tail of openDropdown(): keep the panel exactly as wide as the control (matches the
2600
+ * old dropdownStyle().width), start scroll tracking, and flip on. Position/flip itself is
2601
+ * entirely CDK's job — see hierarchicalDropdownPositions + onPositionChange. */
2602
+ private openPanel;
2435
2603
  /**
2436
- * Decides which side the panel opens on (auto-flip) and, for append-to-body
2437
- * mode, computes the fixed coordinates. Re-run on open and on scroll/resize so
2438
- * the panel snaps to whichever side has room — honouring `dropdownPosition`
2439
- * when it fits. Inline mode is positioned by CSS via the `placement` signal.
2604
+ * Preferred position list handed to CDK, in try-order honours `dropdownPosition` first,
2605
+ * falling back to the other side only when the preferred one genuinely doesn't fit. Replaces
2606
+ * the old hand-rolled `applyPlacement()` space comparison entirely.
2440
2607
  */
2441
- applyPlacement(): void;
2442
- /** Fixed positioning when appendToBody; inset placement uses CSS + .hierarchical-select-field. */
2443
- getTop(): string | null;
2444
- getBottom(): string | null;
2608
+ get hierarchicalDropdownPositions(): ConnectedPosition[];
2445
2609
  closeDropdown(): void;
2446
2610
  goBack(): void;
2447
2611
  onSearchInput(event: Event): void;
@@ -2453,7 +2617,7 @@ declare class BkHierarchicalSelect implements ControlValueAccessor, AfterViewIni
2453
2617
  */
2454
2618
  toggleParentSelection(node: HierarchicalNode, event?: Event): void;
2455
2619
  openFromLabel(event: MouseEvent): void;
2456
- onClickOutside(e: Event): void;
2620
+ onOverlayOutsideClick(): void;
2457
2621
  private _value;
2458
2622
  private valueSignal;
2459
2623
  onChange: (value: any) => void;
@@ -2940,6 +3104,8 @@ declare class BkMenu implements OnChanges, OnDestroy {
2940
3104
  * already sets it apart from the page.
2941
3105
  */
2942
3106
  submenuBackground: boolean;
3107
+ /** Emit parent clicks as selections as well as toggling their inline submenu. */
3108
+ emitParentClick: boolean;
2943
3109
  /** Id of the currently selected leaf item. Two-way bindable. */
2944
3110
  activeItemId: string;
2945
3111
  /** Tint item icons: dark by default, white on the active (dark) row. */
@@ -3028,7 +3194,7 @@ declare class BkMenu implements OnChanges, OnDestroy {
3028
3194
  onDocumentClick(event: MouseEvent): void;
3029
3195
  onViewportChange(): void;
3030
3196
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<BkMenu, never>;
3031
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<BkMenu, "bk-menu", never, { "items": { "alias": "items"; "required": false; }; "orientation": { "alias": "orientation"; "required": false; }; "submenuMode": { "alias": "submenuMode"; "required": false; }; "trigger": { "alias": "trigger"; "required": false; }; "size": { "alias": "size"; "required": false; }; "hoverCloseDelay": { "alias": "hoverCloseDelay"; "required": false; }; "singleOpen": { "alias": "singleOpen"; "required": false; }; "submenuBackground": { "alias": "submenuBackground"; "required": false; }; "activeItemId": { "alias": "activeItemId"; "required": false; }; "tintIcons": { "alias": "tintIcons"; "required": false; }; "maxHeight": { "alias": "maxHeight"; "required": false; }; }, { "activeItemIdChange": "activeItemIdChange"; "itemClick": "itemClick"; }, never, never, true, never>;
3197
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<BkMenu, "bk-menu", never, { "items": { "alias": "items"; "required": false; }; "orientation": { "alias": "orientation"; "required": false; }; "submenuMode": { "alias": "submenuMode"; "required": false; }; "trigger": { "alias": "trigger"; "required": false; }; "size": { "alias": "size"; "required": false; }; "hoverCloseDelay": { "alias": "hoverCloseDelay"; "required": false; }; "singleOpen": { "alias": "singleOpen"; "required": false; }; "submenuBackground": { "alias": "submenuBackground"; "required": false; }; "emitParentClick": { "alias": "emitParentClick"; "required": false; }; "activeItemId": { "alias": "activeItemId"; "required": false; }; "tintIcons": { "alias": "tintIcons"; "required": false; }; "maxHeight": { "alias": "maxHeight"; "required": false; }; }, { "activeItemIdChange": "activeItemIdChange"; "itemClick": "itemClick"; }, never, never, true, never>;
3032
3198
  }
3033
3199
 
3034
3200
  /** Which edge of the anchor the panel sits against. */
@@ -3051,41 +3217,14 @@ type PopoverAlign = 'start' | 'center' | 'end';
3051
3217
  type PopoverPlacement = 'top' | 'top-left' | 'top-right' | 'bottom' | 'bottom-left' | 'bottom-right' | 'left' | 'left-top' | 'left-bottom' | 'right' | 'right-top' | 'right-bottom';
3052
3218
  /** Every placement, in the order a placement picker should show them. */
3053
3219
  declare const POPOVER_PLACEMENTS: readonly PopoverPlacement[];
3054
- interface Rect {
3055
- top: number;
3056
- left: number;
3057
- width: number;
3058
- height: number;
3059
- }
3060
- interface Size {
3061
- width: number;
3062
- height: number;
3063
- }
3064
- interface PopoverPositionOptions {
3065
- /** Gap between the anchor and the panel, in px. */
3066
- offset: number;
3067
- /** Smallest gap to leave between the panel and the viewport edge, in px. */
3068
- viewportPadding: number;
3069
- /** Edge length of the (unrotated) arrow square, in px. */
3070
- arrowSize: number;
3071
- /** Whether a panel that doesn't fit may move to the opposite side. */
3072
- flip: boolean;
3073
- viewport: Size;
3074
- }
3075
- interface PopoverPosition {
3076
- /** Viewport coordinates for the panel (it is positioned `fixed`). */
3077
- top: number;
3078
- left: number;
3079
- /** Where the panel actually landed — not necessarily what was requested. */
3080
- side: PopoverSide;
3081
- align: PopoverAlign;
3082
- placement: PopoverPlacement;
3083
- /**
3084
- * Distance from the panel's leading edge to the arrow's centre, along the
3085
- * panel's cross axis (x for top/bottom, y for left/right).
3086
- */
3087
- arrowOffset: number;
3088
- }
3220
+ /** The side CDK should try when the requested one doesn't fit — only ever the direct
3221
+ * opposite, never a diagonal, so the alignment word stays meaningful after a flip. */
3222
+ declare const OPPOSITE_SIDE: Record<PopoverSide, PopoverSide>;
3223
+ /**
3224
+ * How close the arrow's centre may get to a panel corner. The panel is rounded,
3225
+ * so an arrow any nearer would grow out of the curve instead of a flat edge.
3226
+ */
3227
+ declare const ARROW_CORNER_GAP = 14;
3089
3228
  /** `top-left` → side `top`, align `start`. A bare side means centred. */
3090
3229
  declare function splitPlacement(placement: PopoverPlacement): {
3091
3230
  side: PopoverSide;
@@ -3097,19 +3236,12 @@ declare function splitPlacement(placement: PopoverPlacement): {
3097
3236
  * the same cross axis, so the alignment word stays meaningful.
3098
3237
  */
3099
3238
  declare function joinPlacement(side: PopoverSide, align: PopoverAlign): PopoverPlacement;
3100
- /**
3101
- * Places the panel against the anchor, then keeps it on screen in two ways:
3102
- *
3103
- * - flip: a panel with no room on the requested side moves to the opposite one,
3104
- * but only if it actually fits there — flipping into a second bad fit just
3105
- * trades one clipped edge for another.
3106
- * - shift: the panel slides along its cross axis to stay inside the viewport.
3107
- * The arrow is measured against the *anchor*, not the panel, so it keeps
3108
- * pointing at the trigger however far the panel had to slide.
3109
- */
3110
- declare function computePopoverPosition(anchor: Rect, panel: Size, placement: PopoverPlacement, options: PopoverPositionOptions): PopoverPosition;
3239
+ /** min wins when the range is inverted (arrow limit wider than the panel itself on a very
3240
+ * narrow/maxWidth-constrained panel), which keeps the arrow at the panel's leading edge
3241
+ * rather than pushing it past the trailing one. */
3242
+ declare function clamp(value: number, min: number, max: number): number;
3111
3243
 
3112
- declare class BkPopover implements AfterViewInit, OnDestroy {
3244
+ declare class BkPopover implements OnDestroy {
3113
3245
  /**
3114
3246
  * One of the 12 placements (`top-left`, `right`, `bottom-right`, …). It is a
3115
3247
  * preference, not a guarantee: a panel with no room moves to the opposite
@@ -3141,11 +3273,33 @@ declare class BkPopover implements AfterViewInit, OnDestroy {
3141
3273
  openDelay: _angular_core.InputSignal<number>;
3142
3274
  closeDelay: _angular_core.InputSignal<number>;
3143
3275
  maxWidth: _angular_core.InputSignal<string>;
3276
+ /**
3277
+ * Fixed panel width, e.g. `'320px'`. Unset by default, so the panel sizes to its content
3278
+ * (capped by `maxWidth`) as before. Set this when the content's natural width shouldn't drive
3279
+ * layout — a form, a fixed-column list — so the panel doesn't reflow between opens as content
3280
+ * changes.
3281
+ *
3282
+ * Overrides `maxWidth` entirely while set — a plain CSS `max-width` would otherwise silently
3283
+ * cap a `width` larger than it (280px default), which is never what setting an exact width was
3284
+ * asking for. Raise `maxWidth` too only if you want *both* a starting width and a cap it can
3285
+ * still grow past for some other reason (e.g. a responsive breakpoint override elsewhere).
3286
+ */
3287
+ width: _angular_core.InputSignal<string | null>;
3144
3288
  panelClass: _angular_core.InputSignal<string>;
3145
3289
  /**
3146
- * Relocate the panel to <body> while open. `position: fixed` alone resolves
3147
- * against a transformed ancestor rather than the viewport, so a popover
3148
- * inside an animated dialog or a transformed card needs this to escape.
3290
+ * The max-width actually applied to the panel: whichever of `width`/`maxWidth` is in effect,
3291
+ * additionally capped to the viewport so the panel can never extend past the screen edge — an
3292
+ * oversized `width` (or a placement CDK could only push so far before running out of room)
3293
+ * shrinks to fit instead of overflowing. Mirrors the old hand-rolled positioner's clamp(), which
3294
+ * guaranteed the panel stayed fully on-screen; a bare CSS `max-width` alone doesn't provide that
3295
+ * once the requested size is close to (or exceeds) the viewport itself.
3296
+ */
3297
+ effectiveMaxWidth: _angular_core.Signal<string>;
3298
+ /**
3299
+ * @deprecated No-op, kept only so existing `[appendToBody]="true"` bindings don't break.
3300
+ * The panel now always positions via Angular CDK Overlay, which portals into the shared
3301
+ * `cdk-overlay-container` unconditionally — the exact clipping/stacking escape this input used
3302
+ * to opt into by hand is now the only behaviour there is. Safe to remove from call sites.
3149
3303
  */
3150
3304
  appendToBody: _angular_core.InputSignal<boolean>;
3151
3305
  opened: _angular_core.OutputEmitterRef<void>;
@@ -3153,27 +3307,46 @@ declare class BkPopover implements AfterViewInit, OnDestroy {
3153
3307
  openChange: _angular_core.OutputEmitterRef<boolean>;
3154
3308
  anchorRef: ElementRef<HTMLElement>;
3155
3309
  panelRef?: ElementRef<HTMLElement>;
3310
+ popoverOverlay?: CdkConnectedOverlay;
3156
3311
  private static activeInstance;
3157
- private readonly host;
3158
3312
  isOpen: _angular_core.WritableSignal<boolean>;
3159
- /**
3160
- * Null until the panel has been measured. The template keeps the panel
3161
- * hidden while it is null: the first frame exists only to measure, and
3162
- * showing it at 0,0 first would flash it in the corner.
3163
- */
3164
- position: _angular_core.WritableSignal<PopoverPosition | null>;
3313
+ /** True once CDK has positioned the panel at least once since open() — the template keeps
3314
+ * the panel hidden until then, so the first frame (only there to be measured) never flashes
3315
+ * in the corner. */
3316
+ panelReady: _angular_core.WritableSignal<boolean>;
3317
+ /** Placement actually applied, once known — null before the first (positionChange) fires
3318
+ * (or once closed), in which case activePlacement()/side() fall back to the requested one. */
3319
+ private resolvedPlacement;
3165
3320
  /** Where the panel actually is, once positioned — may differ from `placement`. */
3166
3321
  activePlacement: _angular_core.Signal<PopoverPlacement>;
3167
3322
  /** Side the panel sits on. Drives which edge the arrow hangs off (see CSS). */
3168
- side: _angular_core.Signal<_brickclay_org_ui.PopoverSide>;
3323
+ side: _angular_core.Signal<PopoverSide>;
3324
+ /** Distance from the panel's leading edge to the arrow's centre, along the panel's cross
3325
+ * axis — null until measured. Recomputed from the *actual rendered rects*, not derived from
3326
+ * CDK's position strategy, so it's correct however CDK had to push/clamp the panel. */
3327
+ private arrowOffsetPx;
3328
+ /** Arrow offset along the panel's cross axis — x on top/bottom, y on left/right. */
3329
+ arrowLeft: _angular_core.Signal<number | null>;
3330
+ arrowTop: _angular_core.Signal<number | null>;
3331
+ /**
3332
+ * Requested position first, its opposite side as a fallback when `flip` allows it — CDK tries
3333
+ * these in order and only falls to the second when the first genuinely doesn't fit. Push (on
3334
+ * by default) then keeps whichever one it lands on inside the viewport, matching the old
3335
+ * hand-rolled shift-along-the-edge behaviour. Each entry is tagged with the side/align it came
3336
+ * from (see positionMeta) so onPositionChange can read back which one CDK actually used.
3337
+ *
3338
+ * A stable field, computed once per open() — not a template-bound getter. A getter re-runs on
3339
+ * every change-detection pass (several times before CDK ever reads it), reassigning
3340
+ * `positionMeta` with fresh object references each time; onPositionChange would then be matching
3341
+ * against an array CDK was never actually given.
3342
+ */
3343
+ popoverPositions: ConnectedPosition[];
3344
+ private positionMeta;
3345
+ private computePositions;
3169
3346
  private openTimer;
3170
3347
  private closeTimer;
3171
3348
  private pointerInPanel;
3172
3349
  private resizeObserver?;
3173
- private originalPanelParent;
3174
- private originalPanelAnchor;
3175
- private panelInBody;
3176
- ngAfterViewInit(): void;
3177
3350
  ngOnDestroy(): void;
3178
3351
  open(): void;
3179
3352
  close(): void;
@@ -3183,23 +3356,51 @@ declare class BkPopover implements AfterViewInit, OnDestroy {
3183
3356
  onAnchorLeave(): void;
3184
3357
  onPanelEnter(): void;
3185
3358
  onPanelLeave(): void;
3186
- /** Only attached while open — see open(). Arrow fn so it can be removed. */
3187
- private onDocumentClick;
3359
+ /**
3360
+ * Click-outside-to-close, driven by CDK's document-level outside-pointer-event dispatcher —
3361
+ * it already excludes clicks on the cdkOverlayOrigin (the anchor) by design, so onAnchorClick()
3362
+ * stays the sole opener/toggler. No backdrop involved, so it doesn't block page/nested-scroll-
3363
+ * container scroll the way a modal's would (see the phone-dropdown writeup for why that matters).
3364
+ */
3365
+ onOverlayOutsideClick(): void;
3188
3366
  onEscape(): void;
3189
- private reposition;
3190
- /** Measures the anchor and panel, then hands the maths to the utility. */
3191
- private measure;
3192
- /** Arrow offset along the panel's cross axis x on top/bottom, y on left/right. */
3193
- arrowLeft: _angular_core.Signal<number | null>;
3194
- arrowTop: _angular_core.Signal<number | null>;
3195
- private movePanelToBody;
3196
- private restorePanel;
3367
+ /**
3368
+ * CDK's default `reposition` scroll strategy only reacts to real `document`/`window` scroll
3369
+ * it has no way to know an app shell might scroll a nested container instead (this one
3370
+ * commonly does dashboard layouts with a fixed header/sidebar and a scrollable content pane).
3371
+ * A capture-phase listener on `document` still sees scroll events fired on any descendant
3372
+ * scrollable element (scroll doesn't bubble, but capture does) — same trick `bk-select` and the
3373
+ * `bk-input` phone dropdown already use. Also recomputes the arrow, since `updatePosition()`
3374
+ * sliding the panel along the same side doesn't necessarily fire (positionChange) — that only
3375
+ * fires when the *side* changes, not every pixel of a push/slide. rAF-throttled so a fast
3376
+ * scroll doesn't force layout on every tick.
3377
+ */
3378
+ private overlayScrollRafId;
3379
+ private readonly onOverlayScroll;
3380
+ private attachOverlayScrollTracking;
3381
+ private detachOverlayScrollTracking;
3382
+ /**
3383
+ * Fires whenever CDK (re)applies a position, including the first one after open. Reads back
3384
+ * which of `popoverPositions`' tagged entries CDK actually used — matched by field value
3385
+ * (positionsEqual), not object reference: CDK's position strategy reconstructs its own
3386
+ * ConnectedPosition objects internally, so `event.connectionPair` is never `===` to the object
3387
+ * we handed it even when it's the exact same logical position. Recomputes the arrow from the
3388
+ * real rendered rects, and — on the very first call — starts observing the panel for
3389
+ * content-size changes and reveals it.
3390
+ */
3391
+ onPositionChange(event: ConnectedOverlayPositionChange): void;
3392
+ /**
3393
+ * Arrow offset along the panel's cross axis, measured from the actual rendered anchor/panel
3394
+ * rects rather than reverse-engineered from CDK's position strategy — correct however far CDK
3395
+ * had to push/clamp the panel to stay on screen.
3396
+ */
3397
+ private computeArrowOffset;
3197
3398
  private scheduleClose;
3198
3399
  private cancelOpen;
3199
3400
  private cancelClose;
3200
3401
  private clearTimers;
3201
3402
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<BkPopover, never>;
3202
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<BkPopover, "bk-popover", ["bkPopover"], { "placement": { "alias": "placement"; "required": false; "isSignal": true; }; "trigger": { "alias": "trigger"; "required": false; "isSignal": true; }; "title": { "alias": "title"; "required": false; "isSignal": true; }; "closable": { "alias": "closable"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "showArrow": { "alias": "showArrow"; "required": false; "isSignal": true; }; "offset": { "alias": "offset"; "required": false; "isSignal": true; }; "flip": { "alias": "flip"; "required": false; "isSignal": true; }; "closeOnClickOutside": { "alias": "closeOnClickOutside"; "required": false; "isSignal": true; }; "closeOnEscape": { "alias": "closeOnEscape"; "required": false; "isSignal": true; }; "openDelay": { "alias": "openDelay"; "required": false; "isSignal": true; }; "closeDelay": { "alias": "closeDelay"; "required": false; "isSignal": true; }; "maxWidth": { "alias": "maxWidth"; "required": false; "isSignal": true; }; "panelClass": { "alias": "panelClass"; "required": false; "isSignal": true; }; "appendToBody": { "alias": "appendToBody"; "required": false; "isSignal": true; }; }, { "opened": "opened"; "closed": "closed"; "openChange": "openChange"; }, never, ["[bkPopoverTrigger]", "*"], true, never>;
3403
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<BkPopover, "bk-popover", ["bkPopover"], { "placement": { "alias": "placement"; "required": false; "isSignal": true; }; "trigger": { "alias": "trigger"; "required": false; "isSignal": true; }; "title": { "alias": "title"; "required": false; "isSignal": true; }; "closable": { "alias": "closable"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "showArrow": { "alias": "showArrow"; "required": false; "isSignal": true; }; "offset": { "alias": "offset"; "required": false; "isSignal": true; }; "flip": { "alias": "flip"; "required": false; "isSignal": true; }; "closeOnClickOutside": { "alias": "closeOnClickOutside"; "required": false; "isSignal": true; }; "closeOnEscape": { "alias": "closeOnEscape"; "required": false; "isSignal": true; }; "openDelay": { "alias": "openDelay"; "required": false; "isSignal": true; }; "closeDelay": { "alias": "closeDelay"; "required": false; "isSignal": true; }; "maxWidth": { "alias": "maxWidth"; "required": false; "isSignal": true; }; "width": { "alias": "width"; "required": false; "isSignal": true; }; "panelClass": { "alias": "panelClass"; "required": false; "isSignal": true; }; "appendToBody": { "alias": "appendToBody"; "required": false; "isSignal": true; }; }, { "opened": "opened"; "closed": "closed"; "openChange": "openChange"; }, never, ["[bkPopoverTrigger]", "*"], true, never>;
3203
3404
  }
3204
3405
 
3205
3406
  /** One entry in a stacked avatar group. Mirrors the display inputs of bk-avatar. */
@@ -4217,5 +4418,5 @@ declare class BkTreeDrag<T = any> extends BkTableDrag implements OnDestroy {
4217
4418
 
4218
4419
  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];
4219
4420
 
4220
- export { 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, BkTrExpand, BkTreeDrag, BkValidator, BkVirtualScroll, BrickclayIcons, BrickclayLib, CalendarModule, CalendarSelection, ColumnFilterOption, NEUTRAL_APPEARANCE, POPOVER_PLACEMENTS, computePopoverPosition, containsBkTreeNode, deriveAppearance, flattenBkTreeData, getDialogBackdropAnimation, getDialogPanelAnimation, joinPlacement, moveBkTreeNode, normalizeBkTableSize, parseColor, splitPlacement };
4221
- 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, 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, PopoverPosition, PopoverPositionOptions, PopoverSide, Rect, ScheduledDateSelection, Size, SortDirection, SpinnerSize, TabIconDirection, TabItem, TableAction, TableBadge, TableColumn, TableIcon, TableRows, TabsColors, TabsOrientation, TabsVariant, TimeConfiguration, ToastConfig, ToastMessage, ToastMethodOptions, ToastPosition, ToastSeverity };
4421
+ 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 };
4422
+ 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 };