@brickclay-org/ui 0.0.41 → 0.0.42

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.
@@ -1,15 +1,19 @@
1
1
  import * as i0 from '@angular/core';
2
- import { Component, EventEmitter, HostListener, ViewChildren, Output, Input, Injectable, NgModule, forwardRef, ViewEncapsulation, Optional, Self, ViewChild, input, model, output, signal, computed, effect, inject, ElementRef } from '@angular/core';
2
+ import { Component, EventEmitter, HostListener, ViewChildren, Output, Input, Injectable, NgModule, forwardRef, ViewEncapsulation, Optional, Self, ViewChild, input, model, output, signal, computed, effect, inject, ElementRef, InjectionToken, Directive } from '@angular/core';
3
3
  import * as i1 from '@angular/common';
4
4
  import { CommonModule } from '@angular/common';
5
5
  import * as i1$1 from '@angular/forms';
6
6
  import { FormsModule, NG_VALUE_ACCESSOR } from '@angular/forms';
7
7
  import moment from 'moment';
8
- import { Subject } from 'rxjs';
8
+ import { Subject, filter } from 'rxjs';
9
9
  import * as i2 from '@angular/cdk/drag-drop';
10
10
  import { moveItemInArray, DragDropModule } from '@angular/cdk/drag-drop';
11
- import { ScrollingModule } from '@angular/cdk/scrolling';
11
+ import * as i1$2 from '@angular/cdk/scrolling';
12
+ import { ScrollingModule, CdkScrollable } from '@angular/cdk/scrolling';
12
13
  import { NgxMaskDirective, provideNgxMask } from 'ngx-mask';
14
+ import { DIALOG_DATA as DIALOG_DATA$1, CdkDialogContainer, Dialog, DialogModule } from '@angular/cdk/dialog';
15
+ import { Overlay, OverlayModule } from '@angular/cdk/overlay';
16
+ import { CdkPortalOutlet, PortalModule } from '@angular/cdk/portal';
13
17
 
14
18
  // Icon paths that will be resolved relative to the library's assets folder
15
19
  // When published to npm, users will need to configure their angular.json to include these assets
@@ -4022,6 +4026,1202 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
4022
4026
  type: Output
4023
4027
  }] } });
4024
4028
 
4029
+ /**
4030
+ * Dialog Injection Tokens
4031
+ *
4032
+ * Architecture Decision:
4033
+ * ─────────────────────
4034
+ * • `DIALOG_DATA` — Re-exported from `@angular/cdk/dialog` so that
4035
+ * consumers import from our barrel and the token identity matches
4036
+ * exactly what CDK's `Dialog` service provides in the injector.
4037
+ * This means `@Inject(DIALOG_DATA)` works without any extra wiring.
4038
+ *
4039
+ * • `DIALOG_GLOBAL_CONFIG` — Optional app-level defaults, same pattern
4040
+ * as before.
4041
+ *
4042
+ * • `INTERNAL_DIALOG_CONFIG` — Internal-only token used to pass our
4043
+ * full `DialogConfig` (including animation fields) to the custom
4044
+ * `DialogContainerComponent`. Not part of the public API.
4045
+ */
4046
+ // ──── Public ─────────────────────────────────────────────────────────────
4047
+ /**
4048
+ * Injection token used to pass arbitrary data into a dialog component.
4049
+ *
4050
+ * This is CDK's own `DIALOG_DATA` token — re-exported so that imports
4051
+ * from our barrel (`shared/components/dialog`) resolve to the exact same
4052
+ * token that CDK's `Dialog` service provides.
4053
+ *
4054
+ * Usage inside a dialog component:
4055
+ * ```ts
4056
+ * constructor(@Inject(DIALOG_DATA) public data: MyDataType) {}
4057
+ * ```
4058
+ */
4059
+ const DIALOG_DATA = DIALOG_DATA$1;
4060
+ /**
4061
+ * Optional token for providing global dialog defaults at the
4062
+ * application level.
4063
+ *
4064
+ * Usage in `app.config.ts` or a module's `providers` array:
4065
+ * ```ts
4066
+ * providers: [
4067
+ * { provide: DIALOG_GLOBAL_CONFIG, useValue: { animation: 'zoom', width: '600px' } }
4068
+ * ]
4069
+ * ```
4070
+ */
4071
+ const DIALOG_GLOBAL_CONFIG = new InjectionToken('DIALOG_GLOBAL_CONFIG');
4072
+ // ──── Internal ───────────────────────────────────────────────────────────
4073
+ /**
4074
+ * Internal token that carries our full `DialogConfig` (with animation
4075
+ * settings, position offsets, etc.) into the `DialogContainerComponent`.
4076
+ *
4077
+ * @internal — not exported from the barrel; do not depend on it.
4078
+ */
4079
+ const INTERNAL_DIALOG_CONFIG = new InjectionToken('INTERNAL_DIALOG_CONFIG');
4080
+
4081
+ /**
4082
+ * Dialog Animations
4083
+ *
4084
+ * Architecture Decision:
4085
+ * ─────────────────────
4086
+ * Animations are implemented via the Web Animations API (WAAPI) instead of
4087
+ * Angular's @angular/animations so that:
4088
+ * 1. The dialog container stays truly standalone — no BrowserAnimationsModule
4089
+ * dependency is required.
4090
+ * 2. We have full programmatic control over timing, easing, and cleanup.
4091
+ * 3. Future presets can be added without touching the component template.
4092
+ *
4093
+ * Each preset exports an `enter` and `leave` keyframe array plus a
4094
+ * recommended timing object. The DialogContainerComponent plays these
4095
+ * via `element.animate()`.
4096
+ */
4097
+ /**
4098
+ * Build enter/leave WAAPI keyframes for the dialog **panel** element.
4099
+ */
4100
+ function getDialogPanelAnimation(preset, enterDuration, leaveDuration) {
4101
+ const easeEnter = 'cubic-bezier(0.0, 0.0, 0.2, 1)'; // decelerate
4102
+ const easeLeave = 'cubic-bezier(0.4, 0.0, 1, 1)'; // accelerate
4103
+ switch (preset) {
4104
+ // ─── Fade ────────────────────────────────────────────────────
4105
+ case 'fade':
4106
+ return {
4107
+ enter: {
4108
+ keyframes: [
4109
+ { opacity: 0, transform: 'scale(0.95)' },
4110
+ { opacity: 1, transform: 'scale(1)' },
4111
+ ],
4112
+ options: { duration: enterDuration, easing: easeEnter, fill: 'forwards' },
4113
+ },
4114
+ leave: {
4115
+ keyframes: [
4116
+ { opacity: 1, transform: 'scale(1)' },
4117
+ { opacity: 0, transform: 'scale(0.95)' },
4118
+ ],
4119
+ options: { duration: leaveDuration, easing: easeLeave, fill: 'forwards' },
4120
+ },
4121
+ };
4122
+ // ─── Zoom ────────────────────────────────────────────────────
4123
+ case 'zoom':
4124
+ return {
4125
+ enter: {
4126
+ keyframes: [
4127
+ { opacity: 0, transform: 'scale(0.5)' },
4128
+ { opacity: 1, transform: 'scale(1)' },
4129
+ ],
4130
+ options: { duration: enterDuration, easing: easeEnter, fill: 'forwards' },
4131
+ },
4132
+ leave: {
4133
+ keyframes: [
4134
+ { opacity: 1, transform: 'scale(1)' },
4135
+ { opacity: 0, transform: 'scale(0.5)' },
4136
+ ],
4137
+ options: { duration: leaveDuration, easing: easeLeave, fill: 'forwards' },
4138
+ },
4139
+ };
4140
+ // ─── Slide Top ───────────────────────────────────────────────
4141
+ case 'slide-top':
4142
+ return {
4143
+ enter: {
4144
+ keyframes: [
4145
+ { opacity: 0, transform: 'translateY(-40px)' },
4146
+ { opacity: 1, transform: 'translateY(0)' },
4147
+ ],
4148
+ options: { duration: enterDuration, easing: easeEnter, fill: 'forwards' },
4149
+ },
4150
+ leave: {
4151
+ keyframes: [
4152
+ { opacity: 1, transform: 'translateY(0)' },
4153
+ { opacity: 0, transform: 'translateY(-40px)' },
4154
+ ],
4155
+ options: { duration: leaveDuration, easing: easeLeave, fill: 'forwards' },
4156
+ },
4157
+ };
4158
+ // ─── Slide Bottom ────────────────────────────────────────────
4159
+ case 'slide-bottom':
4160
+ return {
4161
+ enter: {
4162
+ keyframes: [
4163
+ { opacity: 0, transform: 'translateY(40px)' },
4164
+ { opacity: 1, transform: 'translateY(0)' },
4165
+ ],
4166
+ options: { duration: enterDuration, easing: easeEnter, fill: 'forwards' },
4167
+ },
4168
+ leave: {
4169
+ keyframes: [
4170
+ { opacity: 1, transform: 'translateY(0)' },
4171
+ { opacity: 0, transform: 'translateY(40px)' },
4172
+ ],
4173
+ options: { duration: leaveDuration, easing: easeLeave, fill: 'forwards' },
4174
+ },
4175
+ };
4176
+ // ─── Slide Left ──────────────────────────────────────────────
4177
+ case 'slide-left':
4178
+ return {
4179
+ enter: {
4180
+ keyframes: [
4181
+ { opacity: 0, transform: 'translateX(-40px)' },
4182
+ { opacity: 1, transform: 'translateX(0)' },
4183
+ ],
4184
+ options: { duration: enterDuration, easing: easeEnter, fill: 'forwards' },
4185
+ },
4186
+ leave: {
4187
+ keyframes: [
4188
+ { opacity: 1, transform: 'translateX(0)' },
4189
+ { opacity: 0, transform: 'translateX(-40px)' },
4190
+ ],
4191
+ options: { duration: leaveDuration, easing: easeLeave, fill: 'forwards' },
4192
+ },
4193
+ };
4194
+ // ─── Slide Right ─────────────────────────────────────────────
4195
+ case 'slide-right':
4196
+ return {
4197
+ enter: {
4198
+ keyframes: [
4199
+ { opacity: 0, transform: 'translateX(40px)' },
4200
+ { opacity: 1, transform: 'translateX(0)' },
4201
+ ],
4202
+ options: { duration: enterDuration, easing: easeEnter, fill: 'forwards' },
4203
+ },
4204
+ leave: {
4205
+ keyframes: [
4206
+ { opacity: 1, transform: 'translateX(0)' },
4207
+ { opacity: 0, transform: 'translateX(40px)' },
4208
+ ],
4209
+ options: { duration: leaveDuration, easing: easeLeave, fill: 'forwards' },
4210
+ },
4211
+ };
4212
+ // ─── None ────────────────────────────────────────────────────
4213
+ case 'none':
4214
+ default:
4215
+ return {
4216
+ enter: {
4217
+ keyframes: [{ opacity: 1 }],
4218
+ options: { duration: 0, fill: 'forwards' },
4219
+ },
4220
+ leave: {
4221
+ keyframes: [{ opacity: 0 }],
4222
+ options: { duration: 0, fill: 'forwards' },
4223
+ },
4224
+ };
4225
+ }
4226
+ }
4227
+ /**
4228
+ * Build enter/leave WAAPI keyframes for the **backdrop** element.
4229
+ * The backdrop always uses a simple opacity fade regardless of the
4230
+ * panel animation preset.
4231
+ */
4232
+ function getDialogBackdropAnimation(enterDuration, leaveDuration) {
4233
+ return {
4234
+ enter: {
4235
+ keyframes: [{ opacity: 0 }, { opacity: 1 }],
4236
+ options: { duration: enterDuration, easing: 'ease-out', fill: 'forwards' },
4237
+ },
4238
+ leave: {
4239
+ keyframes: [{ opacity: 1 }, { opacity: 0 }],
4240
+ options: { duration: leaveDuration, easing: 'ease-in', fill: 'forwards' },
4241
+ },
4242
+ };
4243
+ }
4244
+
4245
+ /**
4246
+ * DialogContainerComponent
4247
+ *
4248
+ * Architecture Decision:
4249
+ * ─────────────────────
4250
+ * Extends CDK's `CdkDialogContainer` — the headless base class that
4251
+ * provides:
4252
+ * • Focus-trap management (tab key stays inside the dialog).
4253
+ * • Auto-focus / restore-focus behaviour.
4254
+ * • Portal outlet (`<ng-template cdkPortalOutlet />`) for dynamic
4255
+ * component projection.
4256
+ * • ARIA attribute management on the host element.
4257
+ *
4258
+ * We add:
4259
+ * • WAAPI enter / leave animations on the panel (host element) and
4260
+ * the CDK backdrop sibling.
4261
+ * • Flex-column styles on the dynamically created component host so
4262
+ * `.bk-dialog-content` scrolls and `.bk-dialog-actions` stays pinned.
4263
+ * • Position-offset application via CSS margin.
4264
+ *
4265
+ * This component is **never** used directly — it is created internally
4266
+ * by `DialogService` via CDK's `Dialog.open()`.
4267
+ */
4268
+ class DialogContainerComponent extends CdkDialogContainer {
4269
+ /**
4270
+ * Our full config (including animation fields).
4271
+ * Provided by DialogService via the INTERNAL_DIALOG_CONFIG token.
4272
+ */
4273
+ _dialogConfig = inject(INTERNAL_DIALOG_CONFIG);
4274
+ // ──── Opened promise ─────────────────────────────────────────────────
4275
+ /**
4276
+ * Resolves when the enter animation finishes (or immediately for 'none').
4277
+ * `DialogService` subscribes via `.then()` to emit `afterOpened` on the
4278
+ * `DialogRef`.
4279
+ */
4280
+ _resolveOpened;
4281
+ opened = new Promise(resolve => {
4282
+ this._resolveOpened = resolve;
4283
+ });
4284
+ // ──── Lifecycle ──────────────────────────────────────────────────────
4285
+ ngOnInit() {
4286
+ this._playEnterAnimation();
4287
+ this._applyPositionOffsets();
4288
+ }
4289
+ // ──── Portal override ────────────────────────────────────────────────
4290
+ /**
4291
+ * Override the CDK base to apply flex-column layout on the dynamically
4292
+ * created component's host element.
4293
+ *
4294
+ * Angular's emulated view encapsulation prevents scoped CSS from reaching
4295
+ * dynamically-projected elements, so we set styles programmatically.
4296
+ */
4297
+ attachComponentPortal(portal) {
4298
+ const ref = super.attachComponentPortal(portal);
4299
+ const el = ref.location.nativeElement;
4300
+ el.style.display = 'flex';
4301
+ el.style.flexDirection = 'column';
4302
+ el.style.flex = '1 1 auto';
4303
+ el.style.minHeight = '0';
4304
+ el.style.overflow = 'hidden';
4305
+ return ref;
4306
+ }
4307
+ // ──── WAAPI Animations ───────────────────────────────────────────────
4308
+ /**
4309
+ * Play the enter animation on the panel (host element) and backdrop.
4310
+ * Resolves the `opened` promise when the panel animation finishes.
4311
+ */
4312
+ _playEnterAnimation() {
4313
+ const preset = this._dialogConfig.animation ?? 'fade';
4314
+ if (preset === 'none') {
4315
+ this._resolveOpened();
4316
+ return;
4317
+ }
4318
+ const enterDur = this._dialogConfig.animationDurationEnter ?? 200;
4319
+ const leaveDur = this._dialogConfig.animationDurationLeave ?? 150;
4320
+ // ── Panel animation ──
4321
+ const panelAnim = getDialogPanelAnimation(preset, enterDur, leaveDur);
4322
+ const anim = this._elementRef.nativeElement.animate(panelAnim.enter.keyframes, panelAnim.enter.options);
4323
+ // ── Backdrop animation ──
4324
+ const backdropEl = this._getBackdropElement();
4325
+ if (backdropEl) {
4326
+ // Override CDK's CSS transition so our WAAPI timing wins.
4327
+ backdropEl.style.transition = 'none';
4328
+ backdropEl.style.backgroundColor = 'var(--dialog-backdrop-bg, rgba(0, 0, 0, 0.5))';
4329
+ const bdAnim = getDialogBackdropAnimation(enterDur, leaveDur);
4330
+ backdropEl.animate(bdAnim.enter.keyframes, bdAnim.enter.options);
4331
+ }
4332
+ anim.onfinish = () => this._resolveOpened();
4333
+ }
4334
+ /**
4335
+ * Play the leave animation. Returns a Promise that resolves when done.
4336
+ * Called by `DialogRef._runCloseSequence()` before CDK tears down the
4337
+ * overlay.
4338
+ */
4339
+ playLeaveAnimation() {
4340
+ const preset = this._dialogConfig.animation ?? 'fade';
4341
+ if (preset === 'none')
4342
+ return Promise.resolve();
4343
+ const enterDur = this._dialogConfig.animationDurationEnter ?? 200;
4344
+ const leaveDur = this._dialogConfig.animationDurationLeave ?? 150;
4345
+ return new Promise(resolve => {
4346
+ // ── Panel ──
4347
+ const panelAnim = getDialogPanelAnimation(preset, enterDur, leaveDur);
4348
+ const anim = this._elementRef.nativeElement.animate(panelAnim.leave.keyframes, panelAnim.leave.options);
4349
+ // ── Backdrop ──
4350
+ const backdropEl = this._getBackdropElement();
4351
+ if (backdropEl) {
4352
+ const bdAnim = getDialogBackdropAnimation(enterDur, leaveDur);
4353
+ backdropEl.animate(bdAnim.leave.keyframes, bdAnim.leave.options);
4354
+ }
4355
+ anim.onfinish = () => resolve();
4356
+ // Safety net — resolve even if onfinish never fires (SSR, etc.).
4357
+ setTimeout(() => resolve(), leaveDur + 50);
4358
+ });
4359
+ }
4360
+ // ──── Position Offsets ───────────────────────────────────────────────
4361
+ /**
4362
+ * Apply explicit position offsets via CSS margin.
4363
+ * These are additive to CDK's `GlobalPositionStrategy` alignment.
4364
+ */
4365
+ _applyPositionOffsets() {
4366
+ const pos = this._dialogConfig.position;
4367
+ if (!pos)
4368
+ return;
4369
+ const el = this._elementRef.nativeElement;
4370
+ if (pos.top)
4371
+ el.style.marginTop = pos.top;
4372
+ if (pos.bottom)
4373
+ el.style.marginBottom = pos.bottom;
4374
+ if (pos.left)
4375
+ el.style.marginLeft = pos.left;
4376
+ if (pos.right)
4377
+ el.style.marginRight = pos.right;
4378
+ }
4379
+ // ──── Helpers ────────────────────────────────────────────────────────
4380
+ /**
4381
+ * CDK places the backdrop as a sibling of `.cdk-overlay-pane`.
4382
+ * Walk up from our host → pane → wrapper, then query for the backdrop.
4383
+ */
4384
+ _getBackdropElement() {
4385
+ const pane = this._elementRef.nativeElement.closest('.cdk-overlay-pane');
4386
+ return pane?.parentElement?.querySelector('.cdk-overlay-backdrop') ?? null;
4387
+ }
4388
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: DialogContainerComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
4389
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.16", type: DialogContainerComponent, isStandalone: true, selector: "bk-dialog-container", host: { classAttribute: "bk-dialog-container" }, usesInheritance: true, ngImport: i0, template: "<!--\r\n Dialog Container Template (CDK-based)\r\n\r\n Architecture:\r\n \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n CDK Overlay manages the full overlay stack:\r\n\r\n \u250C\u2500 .cdk-overlay-container (fixed, z-index managed by CDK) \u2500\u2500\u2500\u2500\u2500\u2500\u2510\r\n \u2502 \u250C\u2500 .cdk-global-overlay-wrapper (flex-center) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u2502\r\n \u2502 \u2502 .cdk-overlay-backdrop (click \u2192 close, CDK transition) \u2502 \u2502\r\n \u2502 \u2502 \u250C\u2500 .cdk-overlay-pane (width/height from config) \u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u2502 \u2502\r\n \u2502 \u2502 \u2502 <bk-dialog-container> \u2190 this component (panel) \u2502 \u2502 \u2502\r\n \u2502 \u2502 \u2502 <ng-template cdkPortalOutlet /> \u2190 user component \u2502 \u2502 \u2502\r\n \u2502 \u2502 \u2502 <UserDialogComponent> \u2502 \u2502 \u2502\r\n \u2502 \u2502 \u2502 <h2 bk-dialog-title>Title</h2> \u2502 \u2502 \u2502\r\n \u2502 \u2502 \u2502 <div bk-dialog-content>Scrollable body</div> \u2502 \u2502 \u2502\r\n \u2502 \u2502 \u2502 <div bk-dialog-actions>Pinned buttons</div> \u2502 \u2502 \u2502\r\n \u2502 \u2502 \u2502 </UserDialogComponent> \u2502 \u2502 \u2502\r\n \u2502 \u2502 \u2502 </bk-dialog-container> \u2502 \u2502 \u2502\r\n \u2502 \u2502 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2502 \u2502\r\n \u2502 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2502\r\n \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\r\n\r\n The host element IS the visible panel (background, radius, shadow).\r\n CDK Portal projects the caller's component as a direct child.\r\n\r\n Directives (BkDialogTitle, BkDialogContent, BkDialogActions, BkDialogClose)\r\n apply the corresponding CSS classes and add accessibility features.\r\n-->\r\n\r\n<ng-template cdkPortalOutlet />\r\n", styles: [":host{display:flex;flex-direction:column;flex:1;min-height:0;max-height:inherit;background:var(--bk-dialog-panel-bg, #ffffff);border-radius:var(--bk-dialog-panel-radius, 8px);box-shadow:var( --bk-dialog-panel-shadow, 0 11px 15px -7px rgba(0, 0, 0, .2), 0 24px 38px 3px rgba(0, 0, 0, .14), 0 9px 46px 8px rgba(0, 0, 0, .12) );outline:0;box-sizing:border-box;overflow:hidden;will-change:transform,opacity}:host ::ng-deep>:first-child{display:flex;flex-direction:column;flex:1 1 auto;min-height:0;overflow:hidden}:host ::ng-deep .bk-dialog-content{flex:1 1 auto;overflow:auto;min-height:0;display:block;-webkit-overflow-scrolling:touch}:host ::ng-deep .bk-dialog-actions{flex:0 0 auto;display:flex;align-items:center;justify-content:flex-end;flex-wrap:wrap;min-height:52px}:host ::ng-deep .bk-dialog-title{flex:0 0 auto;margin:0}:host ::ng-deep .bk-dialog-actions-align-start{justify-content:flex-start}:host ::ng-deep .bk-dialog-actions-align-center{justify-content:center}\n"], dependencies: [{ kind: "directive", type: CdkPortalOutlet, selector: "[cdkPortalOutlet]", inputs: ["cdkPortalOutlet"], outputs: ["attached"], exportAs: ["cdkPortalOutlet"] }] });
4390
+ }
4391
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: DialogContainerComponent, decorators: [{
4392
+ type: Component,
4393
+ args: [{ selector: 'bk-dialog-container', standalone: true, imports: [CdkPortalOutlet], host: {
4394
+ 'class': 'bk-dialog-container',
4395
+ }, template: "<!--\r\n Dialog Container Template (CDK-based)\r\n\r\n Architecture:\r\n \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n CDK Overlay manages the full overlay stack:\r\n\r\n \u250C\u2500 .cdk-overlay-container (fixed, z-index managed by CDK) \u2500\u2500\u2500\u2500\u2500\u2500\u2510\r\n \u2502 \u250C\u2500 .cdk-global-overlay-wrapper (flex-center) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u2502\r\n \u2502 \u2502 .cdk-overlay-backdrop (click \u2192 close, CDK transition) \u2502 \u2502\r\n \u2502 \u2502 \u250C\u2500 .cdk-overlay-pane (width/height from config) \u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u2502 \u2502\r\n \u2502 \u2502 \u2502 <bk-dialog-container> \u2190 this component (panel) \u2502 \u2502 \u2502\r\n \u2502 \u2502 \u2502 <ng-template cdkPortalOutlet /> \u2190 user component \u2502 \u2502 \u2502\r\n \u2502 \u2502 \u2502 <UserDialogComponent> \u2502 \u2502 \u2502\r\n \u2502 \u2502 \u2502 <h2 bk-dialog-title>Title</h2> \u2502 \u2502 \u2502\r\n \u2502 \u2502 \u2502 <div bk-dialog-content>Scrollable body</div> \u2502 \u2502 \u2502\r\n \u2502 \u2502 \u2502 <div bk-dialog-actions>Pinned buttons</div> \u2502 \u2502 \u2502\r\n \u2502 \u2502 \u2502 </UserDialogComponent> \u2502 \u2502 \u2502\r\n \u2502 \u2502 \u2502 </bk-dialog-container> \u2502 \u2502 \u2502\r\n \u2502 \u2502 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2502 \u2502\r\n \u2502 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2502\r\n \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\r\n\r\n The host element IS the visible panel (background, radius, shadow).\r\n CDK Portal projects the caller's component as a direct child.\r\n\r\n Directives (BkDialogTitle, BkDialogContent, BkDialogActions, BkDialogClose)\r\n apply the corresponding CSS classes and add accessibility features.\r\n-->\r\n\r\n<ng-template cdkPortalOutlet />\r\n", styles: [":host{display:flex;flex-direction:column;flex:1;min-height:0;max-height:inherit;background:var(--bk-dialog-panel-bg, #ffffff);border-radius:var(--bk-dialog-panel-radius, 8px);box-shadow:var( --bk-dialog-panel-shadow, 0 11px 15px -7px rgba(0, 0, 0, .2), 0 24px 38px 3px rgba(0, 0, 0, .14), 0 9px 46px 8px rgba(0, 0, 0, .12) );outline:0;box-sizing:border-box;overflow:hidden;will-change:transform,opacity}:host ::ng-deep>:first-child{display:flex;flex-direction:column;flex:1 1 auto;min-height:0;overflow:hidden}:host ::ng-deep .bk-dialog-content{flex:1 1 auto;overflow:auto;min-height:0;display:block;-webkit-overflow-scrolling:touch}:host ::ng-deep .bk-dialog-actions{flex:0 0 auto;display:flex;align-items:center;justify-content:flex-end;flex-wrap:wrap;min-height:52px}:host ::ng-deep .bk-dialog-title{flex:0 0 auto;margin:0}:host ::ng-deep .bk-dialog-actions-align-start{justify-content:flex-start}:host ::ng-deep .bk-dialog-actions-align-center{justify-content:center}\n"] }]
4396
+ }] });
4397
+
4398
+ /**
4399
+ * DialogRef — Handle returned to callers by `DialogService.open()`.
4400
+ *
4401
+ * Architecture Decision:
4402
+ * ─────────────────────
4403
+ * Wraps CDK's `DialogRef` to add:
4404
+ * • WAAPI leave-animation before CDK tears down the overlay.
4405
+ * • Per-instance `afterOpened()` observable (CDK only has a service-level
4406
+ * `afterOpened` Subject).
4407
+ * • The same familiar API shape used by callers (`close`, `afterClosed`,
4408
+ * `backdropClick`, `keydownEvents`, `componentInstance`).
4409
+ *
4410
+ * CDK's `DialogRef<R, C>` has Result first, Component second.
4411
+ * Ours keeps `<T, R>` (Component first, Result second) to match the
4412
+ * convention consumers already use.
4413
+ */
4414
+ class DialogRef {
4415
+ _cdkRef;
4416
+ /** Unique dialog identifier (managed by CDK). */
4417
+ id;
4418
+ /** Instance of the component rendered inside the dialog. */
4419
+ componentInstance;
4420
+ // ──── Internal wiring (set by DialogService) ──────────────────────────
4421
+ /** @internal Container reference for leave animation. */
4422
+ _containerInstance;
4423
+ /** @internal Prevent double-close. */
4424
+ _closing = false;
4425
+ /** @internal Per-instance afterOpened Subject. */
4426
+ _afterOpened$ = new Subject();
4427
+ constructor(
4428
+ /**
4429
+ * @internal Wrapped CDK ref.
4430
+ * Typed as `CdkDialogRef<any>` to avoid deep generic-variance issues
4431
+ * that arise from CDK's `config.providers` callback signature.
4432
+ * The public API (`afterClosed`, etc.) re-types the observables.
4433
+ */
4434
+ _cdkRef) {
4435
+ this._cdkRef = _cdkRef;
4436
+ this.id = _cdkRef.id;
4437
+ }
4438
+ // ──── Public API ──────────────────────────────────────────────────────
4439
+ /**
4440
+ * Close the dialog, optionally returning a result.
4441
+ * Plays the WAAPI leave animation, then delegates to CDK for cleanup.
4442
+ */
4443
+ close(result) {
4444
+ if (this._closing)
4445
+ return;
4446
+ this._closing = true;
4447
+ this._runCloseSequence(result);
4448
+ }
4449
+ /**
4450
+ * Observable that emits (and completes) once after the dialog has been
4451
+ * removed from the DOM and all cleanup is done.
4452
+ */
4453
+ afterClosed() {
4454
+ return this._cdkRef.closed;
4455
+ }
4456
+ /**
4457
+ * Observable that emits (and completes) when the enter animation
4458
+ * finishes and the dialog is fully visible.
4459
+ */
4460
+ afterOpened() {
4461
+ return this._afterOpened$.asObservable();
4462
+ }
4463
+ /**
4464
+ * Observable of backdrop / outside-pointer click events.
4465
+ */
4466
+ backdropClick() {
4467
+ return this._cdkRef.outsidePointerEvents;
4468
+ }
4469
+ /**
4470
+ * Observable of keyboard events dispatched while this dialog is open.
4471
+ */
4472
+ keydownEvents() {
4473
+ return this._cdkRef.keydownEvents;
4474
+ }
4475
+ // ──── Internal helpers ────────────────────────────────────────────────
4476
+ /** @internal Called by the container once the enter animation finishes. */
4477
+ _emitOpened() {
4478
+ this._afterOpened$.next();
4479
+ this._afterOpened$.complete();
4480
+ }
4481
+ /** Play leave animation, then close via CDK. */
4482
+ async _runCloseSequence(result) {
4483
+ try {
4484
+ await this._containerInstance.playLeaveAnimation();
4485
+ }
4486
+ catch {
4487
+ // Animation may fail in SSR / test — proceed anyway.
4488
+ }
4489
+ this._cdkRef.close(result);
4490
+ }
4491
+ }
4492
+
4493
+ /**
4494
+ * Dialog Configuration
4495
+ *
4496
+ * Architecture Decision:
4497
+ * ─────────────────────
4498
+ * Our `DialogConfig` mirrors the fields from CDK's `DialogConfig` that we
4499
+ * expose, plus adds custom animation properties powered by WAAPI.
4500
+ *
4501
+ * When `DialogService.open()` is called these values are:
4502
+ * 1. Merged with global defaults and per-call overrides.
4503
+ * 2. Mapped onto CDK's native `DialogConfig` for overlay, backdrop, scroll,
4504
+ * position, ARIA, and focus-trap management.
4505
+ * 3. Passed to `DialogContainerComponent` via an internal token for
4506
+ * animation playback and position offsets.
4507
+ *
4508
+ * CDK handles: unique IDs, z-index stacking, scroll blocking, focus trap,
4509
+ * backdrop rendering, overlay positioning, keyboard events.
4510
+ * We handle: WAAPI animations, panel appearance, convenience config sugar.
4511
+ */
4512
+ // ──── Defaults ───────────────────────────────────────────────────────────
4513
+ const DEFAULT_DIALOG_CONFIG = {
4514
+ id: undefined,
4515
+ width: undefined,
4516
+ height: undefined,
4517
+ minWidth: undefined,
4518
+ minHeight: undefined,
4519
+ maxWidth: '90vw',
4520
+ maxHeight: '90vh',
4521
+ disableClose: false,
4522
+ closeOnBackdrop: true,
4523
+ closeOnEsc: true,
4524
+ hasBackdrop: true,
4525
+ lockScroll: true,
4526
+ panelClass: '',
4527
+ backdropClass: '',
4528
+ data: undefined,
4529
+ animation: 'fade',
4530
+ animationDurationEnter: 200,
4531
+ animationDurationLeave: 150,
4532
+ position: undefined,
4533
+ role: 'dialog',
4534
+ ariaLabel: undefined,
4535
+ ariaLabelledBy: undefined,
4536
+ ariaDescribedBy: undefined,
4537
+ autoFocus: true,
4538
+ restoreFocus: true,
4539
+ scrollStrategy: undefined,
4540
+ };
4541
+
4542
+ /**
4543
+ * DialogService — The core engine of the custom dialog system.
4544
+ *
4545
+ * Architecture Decision:
4546
+ * ─────────────────────
4547
+ * Built on top of Angular CDK's `Dialog` service (the same foundation
4548
+ * that `MatDialog` uses internally):
4549
+ *
4550
+ * 1. CDK creates the overlay, backdrop, scroll-blocking, focus-trap,
4551
+ * z-index stacking, and unique ID management — battle-tested infra
4552
+ * shared with every Angular Material dialog in the ecosystem.
4553
+ *
4554
+ * 2. We provide our own `DialogContainerComponent` (extending
4555
+ * `CdkDialogContainer`) for WAAPI animations and panel styling.
4556
+ *
4557
+ * 3. We wrap CDK's `DialogRef` in our own `DialogRef` to add the
4558
+ * leave-animation step before CDK tears down the overlay, and to
4559
+ * expose the same familiar API shape (`afterClosed()`, etc.).
4560
+ *
4561
+ * 4. Configuration is merged DEFAULT → GLOBAL → per-call, then mapped
4562
+ * to CDK's native config with our extras carried via an internal
4563
+ * injection token.
4564
+ *
4565
+ * What CDK handles for us (no custom code needed):
4566
+ * ─────────────────────────────────────────────────
4567
+ * ✓ Unique dialog IDs (throws on collision)
4568
+ * ✓ Z-index stacking via `.cdk-overlay-container`
4569
+ * ✓ Focus trap (tab key stays inside dialog)
4570
+ * ✓ Auto-focus / restore-focus
4571
+ * ✓ Scroll blocking (BlockScrollStrategy)
4572
+ * ✓ Backdrop rendering and click detection
4573
+ * ✓ Keyboard event forwarding
4574
+ * ✓ Overlay DOM lifecycle (create → attach → detach → dispose)
4575
+ *
4576
+ * Memory safety:
4577
+ * ─────────────
4578
+ * CDK manages the full lifecycle: on close it detaches the overlay,
4579
+ * destroys the container, and disposes the overlay ref.
4580
+ * Our DialogRef subjects complete via CDK's `closed` observable,
4581
+ * preventing lingering subscriptions.
4582
+ */
4583
+ class DialogService {
4584
+ _cdkDialog = inject(Dialog);
4585
+ _overlay = inject(Overlay);
4586
+ _globalConfig = inject(DIALOG_GLOBAL_CONFIG, { optional: true });
4587
+ /** Stack of currently open dialog refs (most recent = last). */
4588
+ _openDialogRefs = [];
4589
+ // ════════════════════════════════════════════════════════════════════
4590
+ // Public API
4591
+ // ════════════════════════════════════════════════════════════════════
4592
+ /**
4593
+ * Open a dialog containing the given component.
4594
+ *
4595
+ * @param component The component class to render inside the dialog.
4596
+ * @param config Optional per-dialog configuration (merged on top
4597
+ * of global and default settings).
4598
+ * @returns A strongly-typed `DialogRef` to interact with.
4599
+ *
4600
+ * @example
4601
+ * ```ts
4602
+ * const ref = this.dialog.open(UserFormComponent, {
4603
+ * width: '500px',
4604
+ * data: { userId: 10 },
4605
+ * });
4606
+ * ref.afterClosed().subscribe(result => console.log(result));
4607
+ * ```
4608
+ */
4609
+ open(component, config) {
4610
+ // ── 1. Merge configs: DEFAULT ← GLOBAL ← per-call ──────────────
4611
+ const mergedConfig = {
4612
+ ...DEFAULT_DIALOG_CONFIG,
4613
+ ...(this._globalConfig ?? {}),
4614
+ ...(config ?? {}),
4615
+ };
4616
+ // ── 2. Prepare the return ref (set inside providers callback) ───
4617
+ let ourRef;
4618
+ // ── 3. Build CDK-native config ──────────────────────────────────
4619
+ const cdkConfig = {
4620
+ // Identity
4621
+ id: mergedConfig.id,
4622
+ // Sizing (applied on .cdk-overlay-pane)
4623
+ width: mergedConfig.width,
4624
+ height: mergedConfig.height,
4625
+ minWidth: mergedConfig.minWidth,
4626
+ minHeight: mergedConfig.minHeight,
4627
+ maxWidth: mergedConfig.maxWidth ?? '90vw',
4628
+ maxHeight: mergedConfig.maxHeight ?? '90vh',
4629
+ // Backdrop
4630
+ hasBackdrop: mergedConfig.hasBackdrop !== false,
4631
+ backdropClass: mergedConfig.backdropClass || 'cdk-overlay-dark-backdrop',
4632
+ // Panel class (always include our base class)
4633
+ panelClass: this._buildPanelClasses(mergedConfig),
4634
+ // Data (CDK provides this as DIALOG_DATA automatically)
4635
+ data: mergedConfig.data,
4636
+ // We manage close ourselves for fine-grained closeOnBackdrop / closeOnEsc.
4637
+ disableClose: true,
4638
+ // Accessibility
4639
+ autoFocus: mergedConfig.autoFocus ?? true,
4640
+ restoreFocus: mergedConfig.restoreFocus ?? true,
4641
+ role: mergedConfig.role ?? 'dialog',
4642
+ ariaLabel: mergedConfig.ariaLabel ?? null,
4643
+ ariaLabelledBy: mergedConfig.ariaLabelledBy ?? null,
4644
+ ariaDescribedBy: mergedConfig.ariaDescribedBy ?? null,
4645
+ // Scroll strategy
4646
+ scrollStrategy: mergedConfig.scrollStrategy ??
4647
+ (mergedConfig.lockScroll !== false
4648
+ ? this._overlay.scrollStrategies.block()
4649
+ : this._overlay.scrollStrategies.noop()),
4650
+ // Position strategy (centered by default, shifted by offsets)
4651
+ positionStrategy: this._buildPositionStrategy(mergedConfig),
4652
+ // ── Custom container ──────────────────────────────────────────
4653
+ container: {
4654
+ type: DialogContainerComponent,
4655
+ providers: () => [
4656
+ { provide: INTERNAL_DIALOG_CONFIG, useValue: mergedConfig },
4657
+ ],
4658
+ },
4659
+ // ── Provider callback ─────────────────────────────────────────
4660
+ // Runs after the container is created but before the user
4661
+ // component. We create our `DialogRef` wrapper here and make
4662
+ // it available for injection in the user component.
4663
+ providers: (cdkRef, _cdkConfig, containerInstance) => {
4664
+ ourRef = new DialogRef(cdkRef);
4665
+ ourRef._containerInstance = containerInstance;
4666
+ // Wire up afterOpened emission.
4667
+ containerInstance.opened
4668
+ .then(() => ourRef._emitOpened());
4669
+ return [
4670
+ { provide: DialogRef, useValue: ourRef },
4671
+ ];
4672
+ },
4673
+ };
4674
+ // ── 4. Open via CDK ─────────────────────────────────────────────
4675
+ const cdkRef = this._cdkDialog.open(component, cdkConfig);
4676
+ // ── 5. Link componentInstance ───────────────────────────────────
4677
+ ourRef.componentInstance = cdkRef.componentInstance;
4678
+ // ── 6. Set up close-on-backdrop / close-on-ESC ──────────────────
4679
+ this._setupCloseListeners(ourRef, mergedConfig);
4680
+ // ── 7. Track in stack ───────────────────────────────────────────
4681
+ this._openDialogRefs.push(ourRef);
4682
+ // ── 8. Cleanup on close ─────────────────────────────────────────
4683
+ ourRef.afterClosed().subscribe(() => {
4684
+ const idx = this._openDialogRefs.indexOf(ourRef);
4685
+ if (idx !== -1)
4686
+ this._openDialogRefs.splice(idx, 1);
4687
+ });
4688
+ return ourRef;
4689
+ }
4690
+ /**
4691
+ * Close all currently open dialogs (most recent first).
4692
+ */
4693
+ closeAll() {
4694
+ for (let i = this._openDialogRefs.length - 1; i >= 0; i--) {
4695
+ this._openDialogRefs[i].close();
4696
+ }
4697
+ }
4698
+ /**
4699
+ * Returns the `DialogRef` of the most recently opened dialog,
4700
+ * or `undefined` if none are open.
4701
+ */
4702
+ getTopDialog() {
4703
+ return this._openDialogRefs[this._openDialogRefs.length - 1];
4704
+ }
4705
+ /**
4706
+ * Get a dialog by its CDK-managed unique ID.
4707
+ */
4708
+ getDialogById(id) {
4709
+ return this._openDialogRefs.find(r => r.id === id);
4710
+ }
4711
+ /**
4712
+ * Number of currently open dialogs.
4713
+ */
4714
+ get openDialogCount() {
4715
+ return this._openDialogRefs.length;
4716
+ }
4717
+ /**
4718
+ * Read-only snapshot of currently open dialog refs.
4719
+ * Used internally by content directives (`BkDialogTitle`, `BkDialogActions`,
4720
+ * `BkDialogClose`) for the DOM-walk fallback when `DialogRef` is not
4721
+ * available via injection (e.g. inside `<ng-template>`).
4722
+ */
4723
+ get openDialogsRef() {
4724
+ return this._openDialogRefs;
4725
+ }
4726
+ // ════════════════════════════════════════════════════════════════════
4727
+ // Convenience Helpers
4728
+ // ════════════════════════════════════════════════════════════════════
4729
+ /**
4730
+ * Open a simple confirmation dialog.
4731
+ * Resolves `true` if the user confirms, `false` otherwise.
4732
+ *
4733
+ * @example
4734
+ * ```ts
4735
+ * const ref = this.dialog.confirm({
4736
+ * message: 'Delete this item?',
4737
+ * component: ConfirmDialogComponent,
4738
+ * });
4739
+ * ref.afterClosed().subscribe(yes => { if (yes) ... });
4740
+ * ```
4741
+ */
4742
+ confirm(options) {
4743
+ return this.open(options.component, {
4744
+ width: options.width ?? '400px',
4745
+ disableClose: true,
4746
+ closeOnBackdrop: false,
4747
+ animation: 'fade',
4748
+ data: {
4749
+ title: options.title ?? 'Confirm',
4750
+ message: options.message,
4751
+ btnOkText: options.btnOkText ?? 'Yes',
4752
+ btnCancelText: options.btnCancelText ?? 'No',
4753
+ },
4754
+ });
4755
+ }
4756
+ /**
4757
+ * Open a simple alert dialog.
4758
+ */
4759
+ alert(options) {
4760
+ return this.open(options.component, {
4761
+ width: options.width ?? '400px',
4762
+ disableClose: true,
4763
+ closeOnBackdrop: false,
4764
+ animation: 'fade',
4765
+ data: {
4766
+ title: options.title ?? 'Alert',
4767
+ message: options.message,
4768
+ btnOkText: options.btnOkText ?? 'OK',
4769
+ },
4770
+ });
4771
+ }
4772
+ // ════════════════════════════════════════════════════════════════════
4773
+ // Private
4774
+ // ════════════════════════════════════════════════════════════════════
4775
+ /**
4776
+ * Subscribe to backdrop-click and ESC events on the CDK ref,
4777
+ * closing the dialog based on our config flags.
4778
+ *
4779
+ * We always set CDK `disableClose: true` so that CDK never auto-closes;
4780
+ * this gives us fine-grained control over `closeOnBackdrop` and
4781
+ * `closeOnEsc` independently.
4782
+ */
4783
+ _setupCloseListeners(ref, config) {
4784
+ if (config.disableClose)
4785
+ return;
4786
+ // Backdrop click
4787
+ if (config.closeOnBackdrop !== false) {
4788
+ ref.backdropClick().subscribe(() => ref.close());
4789
+ }
4790
+ // ESC key
4791
+ if (config.closeOnEsc !== false) {
4792
+ ref.keydownEvents()
4793
+ .pipe(filter((e) => e.key === 'Escape' || e.key === 'Esc'))
4794
+ .subscribe((e) => {
4795
+ e.preventDefault();
4796
+ ref.close();
4797
+ });
4798
+ }
4799
+ }
4800
+ /**
4801
+ * Build the CSS classes for the CDK overlay pane.
4802
+ * Always includes our base class; adds user-provided classes on top.
4803
+ */
4804
+ _buildPanelClasses(config) {
4805
+ const classes = ['bk-dialog-pane'];
4806
+ if (config.panelClass) {
4807
+ if (Array.isArray(config.panelClass)) {
4808
+ classes.push(...config.panelClass);
4809
+ }
4810
+ else {
4811
+ classes.push(config.panelClass);
4812
+ }
4813
+ }
4814
+ return classes;
4815
+ }
4816
+ /**
4817
+ * Build CDK's `GlobalPositionStrategy` from our position config.
4818
+ * Falls back to centred (both axes) when no position is specified.
4819
+ */
4820
+ _buildPositionStrategy(config) {
4821
+ const strategy = this._overlay.position().global();
4822
+ if (config.position?.top) {
4823
+ strategy.top(config.position.top);
4824
+ }
4825
+ else if (config.position?.bottom) {
4826
+ strategy.bottom(config.position.bottom);
4827
+ }
4828
+ else {
4829
+ strategy.centerVertically();
4830
+ }
4831
+ if (config.position?.left) {
4832
+ strategy.left(config.position.left);
4833
+ }
4834
+ else if (config.position?.right) {
4835
+ strategy.right(config.position.right);
4836
+ }
4837
+ else {
4838
+ strategy.centerHorizontally();
4839
+ }
4840
+ return strategy;
4841
+ }
4842
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: DialogService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
4843
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: DialogService, providedIn: 'root' });
4844
+ }
4845
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: DialogService, decorators: [{
4846
+ type: Injectable,
4847
+ args: [{ providedIn: 'root' }]
4848
+ }] });
4849
+
4850
+ /**
4851
+ * Dialog Content Directives
4852
+ *
4853
+ * Architecture Decision:
4854
+ * ─────────────────────
4855
+ * These directives mirror Angular Material's dialog content directives:
4856
+ *
4857
+ * MatDialogTitle → BkDialogTitle
4858
+ * MatDialogContent → BkDialogContent
4859
+ * MatDialogActions → BkDialogActions
4860
+ * MatDialogClose → BkDialogClose
4861
+ *
4862
+ * Each directive:
4863
+ * 1. Applies the corresponding CSS host class (e.g. `bk-dialog-content`)
4864
+ * that is styled in `dialog-container.css` via `::ng-deep`.
4865
+ * 2. Provides extra accessibility or behavioural features on top of the
4866
+ * plain CSS class.
4867
+ *
4868
+ * Consumers can still use the raw CSS classes directly (without importing
4869
+ * these directives) for backward compatibility, but the directives are
4870
+ * preferred because they add CdkScrollable, ARIA integration, and one-click
4871
+ * close behaviour.
4872
+ *
4873
+ * @see https://github.com/angular/components/blob/main/src/material/dialog/dialog-content-directives.ts
4874
+ */
4875
+ // ──── ID Generation ──────────────────────────────────────────────────────
4876
+ let nextTitleId = 0;
4877
+ // ──── Helper ─────────────────────────────────────────────────────────────
4878
+ /**
4879
+ * Finds the closest `DialogRef` by walking up the DOM from the given
4880
+ * element to the nearest `bk-dialog-container` host, then matching its
4881
+ * `id` attribute against the currently open dialogs.
4882
+ *
4883
+ * This fallback is necessary when a directive lives inside an
4884
+ * `<ng-template>` (TemplateRef), where the dialog's custom injector is
4885
+ * not available.
4886
+ *
4887
+ * @see Material's `getClosestDialog` in `dialog-content-directives.ts`
4888
+ */
4889
+ function getClosestDialog(element, openDialogs) {
4890
+ let parent = element.nativeElement.parentElement;
4891
+ while (parent && !parent.classList.contains('bk-dialog-container')) {
4892
+ parent = parent.parentElement;
4893
+ }
4894
+ return parent
4895
+ ? openDialogs.find(ref => ref.id === parent.id) ?? null
4896
+ : null;
4897
+ }
4898
+ // ──── Base for Title / Actions ───────────────────────────────────────────
4899
+ /**
4900
+ * Shared abstract base that resolves the owning `DialogRef` (via DI or
4901
+ * DOM walk) and invokes `_onAdd()` / `_onRemove()` lifecycle hooks.
4902
+ *
4903
+ * Same pattern as Material's internal `MatDialogLayoutSection`.
4904
+ */
4905
+ class BkDialogLayoutSection {
4906
+ _dialogRef = inject(DialogRef, { optional: true });
4907
+ _elementRef = inject((ElementRef));
4908
+ _dialogService = inject(DialogService);
4909
+ ngOnInit() {
4910
+ if (!this._dialogRef) {
4911
+ this._dialogRef = getClosestDialog(this._elementRef, this._dialogService.openDialogsRef);
4912
+ }
4913
+ if (this._dialogRef) {
4914
+ Promise.resolve().then(() => this._onAdd());
4915
+ }
4916
+ }
4917
+ ngOnDestroy() {
4918
+ if (this._dialogRef) {
4919
+ Promise.resolve().then(() => this._onRemove());
4920
+ }
4921
+ }
4922
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: BkDialogLayoutSection, deps: [], target: i0.ɵɵFactoryTarget.Directive });
4923
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "20.3.16", type: BkDialogLayoutSection, isStandalone: true, ngImport: i0 });
4924
+ }
4925
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: BkDialogLayoutSection, decorators: [{
4926
+ type: Directive,
4927
+ args: [{ standalone: true }]
4928
+ }] });
4929
+ // ════════════════════════════════════════════════════════════════════════
4930
+ // BkDialogTitle
4931
+ // ════════════════════════════════════════════════════════════════════════
4932
+ /**
4933
+ * Marks an element as the dialog title.
4934
+ *
4935
+ * • Generates a unique `id` and binds it to the host element.
4936
+ * • Registers the `id` with the CDK container's `aria-labelledby` queue
4937
+ * so screen readers announce the dialog title automatically.
4938
+ *
4939
+ * Usage:
4940
+ * ```html
4941
+ * <h2 bk-dialog-title>Edit User</h2>
4942
+ * ```
4943
+ */
4944
+ class BkDialogTitle extends BkDialogLayoutSection {
4945
+ /** Unique element `id`. Auto-generated but can be overridden. */
4946
+ id = `bk-dialog-title-${nextTitleId++}`;
4947
+ _onAdd() {
4948
+ // Register with the CdkDialogContainer's aria-labelledby queue.
4949
+ // _containerInstance extends CdkDialogContainer which has _addAriaLabelledBy.
4950
+ this._dialogRef?._containerInstance?._addAriaLabelledBy?.(this.id);
4951
+ }
4952
+ _onRemove() {
4953
+ this._dialogRef?._containerInstance?._removeAriaLabelledBy?.(this.id);
4954
+ }
4955
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: BkDialogTitle, deps: null, target: i0.ɵɵFactoryTarget.Directive });
4956
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "20.3.16", type: BkDialogTitle, isStandalone: true, selector: "[bk-dialog-title], [bkDialogTitle]", inputs: { id: "id" }, host: { properties: { "id": "id" }, classAttribute: "bk-dialog-title" }, exportAs: ["bkDialogTitle"], usesInheritance: true, ngImport: i0 });
4957
+ }
4958
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: BkDialogTitle, decorators: [{
4959
+ type: Directive,
4960
+ args: [{
4961
+ selector: '[bk-dialog-title], [bkDialogTitle]',
4962
+ standalone: true,
4963
+ exportAs: 'bkDialogTitle',
4964
+ host: {
4965
+ 'class': 'bk-dialog-title',
4966
+ '[id]': 'id',
4967
+ },
4968
+ }]
4969
+ }], propDecorators: { id: [{
4970
+ type: Input
4971
+ }] } });
4972
+ // ════════════════════════════════════════════════════════════════════════
4973
+ // BkDialogContent
4974
+ // ════════════════════════════════════════════════════════════════════════
4975
+ /**
4976
+ * Scrollable content area of a dialog.
4977
+ *
4978
+ * • Applies the `bk-dialog-content` CSS class → `flex: 1 1 auto; overflow: auto`.
4979
+ * • Composes CDK's `CdkScrollable` for scroll-position monitoring and
4980
+ * integration with CDK's scroll dispatcher.
4981
+ *
4982
+ * Usage (attribute):
4983
+ * ```html
4984
+ * <div bk-dialog-content>
4985
+ * <!-- body content — this area scrolls -->
4986
+ * </div>
4987
+ * ```
4988
+ *
4989
+ * Usage (element):
4990
+ * ```html
4991
+ * <bk-dialog-content>...</bk-dialog-content>
4992
+ * ```
4993
+ */
4994
+ class BkDialogContent {
4995
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: BkDialogContent, deps: [], target: i0.ɵɵFactoryTarget.Directive });
4996
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "20.3.16", type: BkDialogContent, isStandalone: true, selector: "[bk-dialog-content], bk-dialog-content, [bkDialogContent]", host: { classAttribute: "bk-dialog-content" }, exportAs: ["bkDialogContent"], hostDirectives: [{ directive: i1$2.CdkScrollable }], ngImport: i0 });
4997
+ }
4998
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: BkDialogContent, decorators: [{
4999
+ type: Directive,
5000
+ args: [{
5001
+ selector: '[bk-dialog-content], bk-dialog-content, [bkDialogContent]',
5002
+ standalone: true,
5003
+ exportAs: 'bkDialogContent',
5004
+ host: {
5005
+ 'class': 'bk-dialog-content',
5006
+ },
5007
+ hostDirectives: [CdkScrollable],
5008
+ }]
5009
+ }] });
5010
+ // ════════════════════════════════════════════════════════════════════════
5011
+ // BkDialogActions
5012
+ // ════════════════════════════════════════════════════════════════════════
5013
+ /**
5014
+ * Container for action buttons at the bottom of a dialog.
5015
+ * Stays pinned when the content area scrolls.
5016
+ *
5017
+ * • Applies `bk-dialog-actions` class → `flex: 0 0 auto` (never shrinks).
5018
+ * • Optional `align` input controls horizontal alignment.
5019
+ *
5020
+ * Usage:
5021
+ * ```html
5022
+ * <div bk-dialog-actions>
5023
+ * <button (click)="cancel()">Cancel</button>
5024
+ * <button (click)="save()">Save</button>
5025
+ * </div>
5026
+ *
5027
+ * <!-- With alignment -->
5028
+ * <div bk-dialog-actions align="center">...</div>
5029
+ * ```
5030
+ */
5031
+ class BkDialogActions extends BkDialogLayoutSection {
5032
+ /** Horizontal alignment of action buttons. */
5033
+ align;
5034
+ _onAdd() {
5035
+ // Future: notify container for action section count (like Material).
5036
+ }
5037
+ _onRemove() {
5038
+ // No-op.
5039
+ }
5040
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: BkDialogActions, deps: null, target: i0.ɵɵFactoryTarget.Directive });
5041
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "20.3.16", type: BkDialogActions, isStandalone: true, selector: "[bk-dialog-actions], bk-dialog-actions, [bkDialogActions]", inputs: { align: "align" }, host: { properties: { "class.bk-dialog-actions-align-start": "align === \"start\"", "class.bk-dialog-actions-align-center": "align === \"center\"", "class.bk-dialog-actions-align-end": "align === \"end\"" }, classAttribute: "bk-dialog-actions" }, exportAs: ["bkDialogActions"], usesInheritance: true, ngImport: i0 });
5042
+ }
5043
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: BkDialogActions, decorators: [{
5044
+ type: Directive,
5045
+ args: [{
5046
+ selector: '[bk-dialog-actions], bk-dialog-actions, [bkDialogActions]',
5047
+ standalone: true,
5048
+ exportAs: 'bkDialogActions',
5049
+ host: {
5050
+ 'class': 'bk-dialog-actions',
5051
+ '[class.bk-dialog-actions-align-start]': 'align === "start"',
5052
+ '[class.bk-dialog-actions-align-center]': 'align === "center"',
5053
+ '[class.bk-dialog-actions-align-end]': 'align === "end"',
5054
+ },
5055
+ }]
5056
+ }], propDecorators: { align: [{
5057
+ type: Input
5058
+ }] } });
5059
+ // ════════════════════════════════════════════════════════════════════════
5060
+ // BkDialogClose
5061
+ // ════════════════════════════════════════════════════════════════════════
5062
+ /**
5063
+ * Closes the dialog when the host element is clicked.
5064
+ * Optionally returns a result value to the opener.
5065
+ *
5066
+ * • Sets `type="button"` by default to prevent accidental form submissions.
5067
+ * • Supports an optional `aria-label` for screen readers.
5068
+ *
5069
+ * Usage:
5070
+ * ```html
5071
+ * <!-- Close with no result -->
5072
+ * <button bk-dialog-close>Cancel</button>
5073
+ *
5074
+ * <!-- Close with a result value -->
5075
+ * <button [bk-dialog-close]="'confirmed'">OK</button>
5076
+ *
5077
+ * <!-- Alternative camelCase binding -->
5078
+ * <button [bkDialogClose]="myResult">Done</button>
5079
+ * ```
5080
+ */
5081
+ class BkDialogClose {
5082
+ _dialogRef = inject(DialogRef, { optional: true });
5083
+ _elementRef = inject((ElementRef));
5084
+ _dialogService = inject(DialogService);
5085
+ /** Screen-reader label for the button. */
5086
+ ariaLabel;
5087
+ /** Prevents accidental form submits. Default: `'button'`. */
5088
+ type = 'button';
5089
+ /** Dialog result — set via `[bk-dialog-close]="value"`. */
5090
+ dialogResult;
5091
+ /** Alternative camelCase binding: `[bkDialogClose]="value"`. */
5092
+ _bkDialogClose;
5093
+ ngOnInit() {
5094
+ if (!this._dialogRef) {
5095
+ this._dialogRef = getClosestDialog(this._elementRef, this._dialogService.openDialogsRef);
5096
+ }
5097
+ }
5098
+ ngOnChanges(changes) {
5099
+ const proxiedChange = changes['_bkDialogClose'] || changes['dialogResult'];
5100
+ if (proxiedChange) {
5101
+ this.dialogResult = proxiedChange.currentValue;
5102
+ }
5103
+ }
5104
+ _onButtonClick(_event) {
5105
+ this._dialogRef?.close(this.dialogResult);
5106
+ }
5107
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: BkDialogClose, deps: [], target: i0.ɵɵFactoryTarget.Directive });
5108
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "20.3.16", type: BkDialogClose, isStandalone: true, selector: "[bk-dialog-close], [bkDialogClose]", inputs: { ariaLabel: ["aria-label", "ariaLabel"], type: "type", dialogResult: ["bk-dialog-close", "dialogResult"], _bkDialogClose: ["bkDialogClose", "_bkDialogClose"] }, host: { listeners: { "click": "_onButtonClick($event)" }, properties: { "attr.aria-label": "ariaLabel || null", "attr.type": "type" } }, exportAs: ["bkDialogClose"], usesOnChanges: true, ngImport: i0 });
5109
+ }
5110
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: BkDialogClose, decorators: [{
5111
+ type: Directive,
5112
+ args: [{
5113
+ selector: '[bk-dialog-close], [bkDialogClose]',
5114
+ standalone: true,
5115
+ exportAs: 'bkDialogClose',
5116
+ host: {
5117
+ '(click)': '_onButtonClick($event)',
5118
+ '[attr.aria-label]': 'ariaLabel || null',
5119
+ '[attr.type]': 'type',
5120
+ },
5121
+ }]
5122
+ }], propDecorators: { ariaLabel: [{
5123
+ type: Input,
5124
+ args: ['aria-label']
5125
+ }], type: [{
5126
+ type: Input
5127
+ }], dialogResult: [{
5128
+ type: Input,
5129
+ args: ['bk-dialog-close']
5130
+ }], _bkDialogClose: [{
5131
+ type: Input,
5132
+ args: ['bkDialogClose']
5133
+ }] } });
5134
+
5135
+ /**
5136
+ * BkDialogModule
5137
+ *
5138
+ * Optional NgModule wrapper for projects that prefer module-based usage.
5139
+ *
5140
+ * Architecture Decision:
5141
+ * ─────────────────────
5142
+ * Follows the exact same pattern as Angular Material's `MatDialogModule`
5143
+ * and `@brickclay-org/ui`'s `CalendarModule`:
5144
+ *
5145
+ * • All components/directives are **standalone**.
5146
+ * • This module simply imports + re-exports them for convenience.
5147
+ * • `DialogService` is `providedIn: 'root'`, so it does NOT need to
5148
+ * be listed in `providers` here — it is tree-shakeable and available
5149
+ * app-wide automatically.
5150
+ *
5151
+ * Two usage styles:
5152
+ *
5153
+ * ───────── Module-based (NgModule) ─────────
5154
+ * ```ts
5155
+ * import { BkDialogModule } from '@shared/components/dialog';
5156
+ *
5157
+ * @NgModule({ imports: [BkDialogModule] })
5158
+ * export class FuelCostModule {}
5159
+ * ```
5160
+ *
5161
+ * ───────── Standalone ──────────────────────
5162
+ * ```ts
5163
+ * @Component({
5164
+ * standalone: true,
5165
+ * imports: [BkDialogContent, BkDialogActions, BkDialogClose],
5166
+ * })
5167
+ * export class MyDialog {}
5168
+ * ```
5169
+ *
5170
+ * @see https://github.com/angular/components/blob/main/src/material/dialog/dialog-module.ts
5171
+ */
5172
+ class BkDialogModule {
5173
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: BkDialogModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
5174
+ static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "20.3.16", ngImport: i0, type: BkDialogModule, imports: [
5175
+ // ── CDK foundations ──────────────────────────────────────────────
5176
+ DialogModule,
5177
+ OverlayModule,
5178
+ PortalModule,
5179
+ // ── Our standalone pieces ───────────────────────────────────────
5180
+ DialogContainerComponent,
5181
+ BkDialogTitle,
5182
+ BkDialogContent,
5183
+ BkDialogActions,
5184
+ BkDialogClose], exports: [
5185
+ // ── Public API for template usage ───────────────────────────────
5186
+ // Consumers import BkDialogModule and get these directives in
5187
+ // their templates automatically — just like MatDialogModule.
5188
+ BkDialogTitle,
5189
+ BkDialogContent,
5190
+ BkDialogActions,
5191
+ BkDialogClose] });
5192
+ static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: BkDialogModule, imports: [
5193
+ // ── CDK foundations ──────────────────────────────────────────────
5194
+ DialogModule,
5195
+ OverlayModule,
5196
+ PortalModule] });
5197
+ }
5198
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: BkDialogModule, decorators: [{
5199
+ type: NgModule,
5200
+ args: [{
5201
+ imports: [
5202
+ // ── CDK foundations ──────────────────────────────────────────────
5203
+ DialogModule,
5204
+ OverlayModule,
5205
+ PortalModule,
5206
+ // ── Our standalone pieces ───────────────────────────────────────
5207
+ DialogContainerComponent,
5208
+ BkDialogTitle,
5209
+ BkDialogContent,
5210
+ BkDialogActions,
5211
+ BkDialogClose,
5212
+ ],
5213
+ exports: [
5214
+ // ── Public API for template usage ───────────────────────────────
5215
+ // Consumers import BkDialogModule and get these directives in
5216
+ // their templates automatically — just like MatDialogModule.
5217
+ BkDialogTitle,
5218
+ BkDialogContent,
5219
+ BkDialogActions,
5220
+ BkDialogClose,
5221
+ ],
5222
+ }]
5223
+ }] });
5224
+
4025
5225
  /*
4026
5226
  * Public API Surface of brickclay-lib
4027
5227
  */
@@ -4031,5 +5231,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
4031
5231
  * Generated bundle index. Do not edit.
4032
5232
  */
4033
5233
 
4034
- export { BkBadge, BkButton, BkButtonGroup, BkCheckbox, BkChips, BkCustomCalendar, BkGrid, BkIconButton, BkInput, BkPill, BkRadioButton, BkScheduledDatePicker, BkSelect, BkSpinner, BkTabs, BkTextarea, BkTimePicker, BkToggle, BrickclayIcons, BrickclayLib, CalendarManagerService, CalendarModule };
5234
+ export { BkBadge, BkButton, BkButtonGroup, BkCheckbox, BkChips, BkCustomCalendar, BkDialogActions, BkDialogClose, BkDialogContent, BkDialogModule, BkDialogTitle, BkGrid, BkIconButton, BkInput, BkPill, BkRadioButton, BkScheduledDatePicker, BkSelect, BkSpinner, BkTabs, BkTextarea, BkTimePicker, BkToggle, BrickclayIcons, BrickclayLib, CalendarManagerService, CalendarModule, DEFAULT_DIALOG_CONFIG, DIALOG_DATA, DIALOG_GLOBAL_CONFIG, DialogRef, DialogService, getDialogBackdropAnimation, getDialogPanelAnimation };
4035
5235
  //# sourceMappingURL=brickclay-org-ui.mjs.map