@brickclay-org/ui 0.0.41 → 0.0.43
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/fesm2022/brickclay-org-ui.mjs +1257 -21
- package/fesm2022/brickclay-org-ui.mjs.map +1 -1
- package/index.d.ts +670 -8
- package/package.json +13 -4
- package/schematics/collection.json +10 -0
- package/schematics/ng-add/schema.json +15 -0
- package/src/lib/dialog/dialog-container.css +102 -0
- package/src/lib/select/select.css +30 -24
- package/src/styles.css +2 -1
- /package/src/lib/{chips/chips.css → input-chips/input-chips.css} +0 -0
|
@@ -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
|
|
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
|
|
@@ -3031,15 +3035,18 @@ class BkSelect {
|
|
|
3031
3035
|
notFoundText = input('No items found', ...(ngDevMode ? [{ debugName: "notFoundText" }] : []));
|
|
3032
3036
|
loadingText = input('Loading...', ...(ngDevMode ? [{ debugName: "loadingText" }] : []));
|
|
3033
3037
|
clearAllText = input('Clear all', ...(ngDevMode ? [{ debugName: "clearAllText" }] : []));
|
|
3038
|
+
groupBy = input('', ...(ngDevMode ? [{ debugName: "groupBy" }] : [])); // NEW
|
|
3039
|
+
colorKey = input('', ...(ngDevMode ? [{ debugName: "colorKey" }] : [])); // e.g. "color"
|
|
3034
3040
|
// iconSrc = input<string>('Clear all');
|
|
3035
3041
|
iconAlt = 'icon';
|
|
3036
|
-
label = '
|
|
3042
|
+
label = '';
|
|
3037
3043
|
required = false;
|
|
3038
3044
|
iconSrc; // optional icon
|
|
3039
3045
|
// Config
|
|
3040
3046
|
multiple = input(false, ...(ngDevMode ? [{ debugName: "multiple" }] : []));
|
|
3041
3047
|
maxLabels = input(2, ...(ngDevMode ? [{ debugName: "maxLabels" }] : []));
|
|
3042
3048
|
searchable = input(true, ...(ngDevMode ? [{ debugName: "searchable" }] : []));
|
|
3049
|
+
allSelect = input(false, ...(ngDevMode ? [{ debugName: "allSelect" }] : []));
|
|
3043
3050
|
clearable = input(true, ...(ngDevMode ? [{ debugName: "clearable" }] : []));
|
|
3044
3051
|
readonly = input(false, ...(ngDevMode ? [{ debugName: "readonly" }] : []));
|
|
3045
3052
|
disabled = model(false, ...(ngDevMode ? [{ debugName: "disabled" }] : []));
|
|
@@ -3136,7 +3143,16 @@ class BkSelect {
|
|
|
3136
3143
|
// this.markedIndex.set(0);
|
|
3137
3144
|
this.open.emit();
|
|
3138
3145
|
this.focus.emit();
|
|
3139
|
-
|
|
3146
|
+
// this.markedIndex.set(0);
|
|
3147
|
+
// setTimeout(() => this.searchInput?.nativeElement.focus());
|
|
3148
|
+
setTimeout(() => {
|
|
3149
|
+
if (this.searchable()) {
|
|
3150
|
+
this.searchInput?.nativeElement.focus();
|
|
3151
|
+
}
|
|
3152
|
+
else {
|
|
3153
|
+
this.controlWrapper?.nativeElement.focus();
|
|
3154
|
+
}
|
|
3155
|
+
});
|
|
3140
3156
|
}
|
|
3141
3157
|
closeDropdown() {
|
|
3142
3158
|
if (!this.isOpen())
|
|
@@ -3241,7 +3257,7 @@ class BkSelect {
|
|
|
3241
3257
|
handleClear(event) {
|
|
3242
3258
|
event.stopPropagation();
|
|
3243
3259
|
this.updateModel([]);
|
|
3244
|
-
this.clear.emit();
|
|
3260
|
+
this.clear.emit(null);
|
|
3245
3261
|
}
|
|
3246
3262
|
updateModel(items) {
|
|
3247
3263
|
this.selectedOptions.set(items);
|
|
@@ -3249,14 +3265,14 @@ class BkSelect {
|
|
|
3249
3265
|
const values = items.map(i => this.resolveValue(i));
|
|
3250
3266
|
this._value = values;
|
|
3251
3267
|
this.onChange(values);
|
|
3252
|
-
this.change.emit(
|
|
3268
|
+
this.change.emit(items);
|
|
3253
3269
|
}
|
|
3254
3270
|
else {
|
|
3255
3271
|
const item = items[0] || null;
|
|
3256
3272
|
const value = item ? this.resolveValue(item) : null;
|
|
3257
3273
|
this._value = value;
|
|
3258
3274
|
this.onChange(value);
|
|
3259
|
-
this.change.emit(
|
|
3275
|
+
this.change.emit(item);
|
|
3260
3276
|
}
|
|
3261
3277
|
}
|
|
3262
3278
|
onSearchInput(event) {
|
|
@@ -3354,14 +3370,38 @@ class BkSelect {
|
|
|
3354
3370
|
this.controlWrapper.nativeElement.focus();
|
|
3355
3371
|
this.openDropdown();
|
|
3356
3372
|
}
|
|
3373
|
+
groupedItems = computed(() => {
|
|
3374
|
+
const groupKey = this.groupBy();
|
|
3375
|
+
const list = this.filteredItems();
|
|
3376
|
+
if (!groupKey) {
|
|
3377
|
+
return [{ group: null, items: list }];
|
|
3378
|
+
}
|
|
3379
|
+
const map = new Map();
|
|
3380
|
+
list.forEach(item => {
|
|
3381
|
+
const key = item[groupKey] ?? 'Others';
|
|
3382
|
+
if (!map.has(key))
|
|
3383
|
+
map.set(key, []);
|
|
3384
|
+
map.get(key).push(item);
|
|
3385
|
+
});
|
|
3386
|
+
return Array.from(map.entries()).map(([group, items]) => ({
|
|
3387
|
+
group,
|
|
3388
|
+
items
|
|
3389
|
+
}));
|
|
3390
|
+
}, ...(ngDevMode ? [{ debugName: "groupedItems" }] : []));
|
|
3391
|
+
resolveColor(item) {
|
|
3392
|
+
if (!item)
|
|
3393
|
+
return null;
|
|
3394
|
+
const key = this.colorKey();
|
|
3395
|
+
return key && typeof item === 'object' ? item[key] : null;
|
|
3396
|
+
}
|
|
3357
3397
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: BkSelect, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
3358
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.16", type: BkSelect, isStandalone: true, selector: "bk-select", inputs: { items: { classPropertyName: "items", publicName: "items", isSignal: true, isRequired: false, transformFunction: null }, bindLabel: { classPropertyName: "bindLabel", publicName: "bindLabel", isSignal: true, isRequired: false, transformFunction: null }, bindValue: { classPropertyName: "bindValue", publicName: "bindValue", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, notFoundText: { classPropertyName: "notFoundText", publicName: "notFoundText", isSignal: true, isRequired: false, transformFunction: null }, loadingText: { classPropertyName: "loadingText", publicName: "loadingText", isSignal: true, isRequired: false, transformFunction: null }, clearAllText: { classPropertyName: "clearAllText", publicName: "clearAllText", isSignal: true, isRequired: false, transformFunction: null }, iconAlt: { classPropertyName: "iconAlt", publicName: "iconAlt", isSignal: false, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: false, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: false, isRequired: false, transformFunction: null }, iconSrc: { classPropertyName: "iconSrc", publicName: "iconSrc", isSignal: false, isRequired: false, transformFunction: null }, multiple: { classPropertyName: "multiple", publicName: "multiple", isSignal: true, isRequired: false, transformFunction: null }, maxLabels: { classPropertyName: "maxLabels", publicName: "maxLabels", isSignal: true, isRequired: false, transformFunction: null }, searchable: { classPropertyName: "searchable", publicName: "searchable", isSignal: true, isRequired: false, transformFunction: null }, clearable: { classPropertyName: "clearable", publicName: "clearable", isSignal: true, isRequired: false, transformFunction: null }, readonly: { classPropertyName: "readonly", publicName: "readonly", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, closeOnSelect: { classPropertyName: "closeOnSelect", publicName: "closeOnSelect", isSignal: true, isRequired: false, transformFunction: null }, dropdownPosition: { classPropertyName: "dropdownPosition", publicName: "dropdownPosition", isSignal: true, isRequired: false, transformFunction: null }, appendToBody: { classPropertyName: "appendToBody", publicName: "appendToBody", isSignal: true, isRequired: false, transformFunction: null }, compareWith: { classPropertyName: "compareWith", publicName: "compareWith", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { disabled: "disabledChange", open: "open", close: "close", focus: "focus", blur: "blur", search: "search", clear: "clear", change: "change", scrollToEnd: "scrollToEnd" }, host: { listeners: { "window:scroll": "onWindowEvents()", "window:resize": "onWindowEvents()", "document:click": "onClickOutside($event)" } }, providers: [
|
|
3398
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.16", type: BkSelect, isStandalone: true, selector: "bk-select", inputs: { items: { classPropertyName: "items", publicName: "items", isSignal: true, isRequired: false, transformFunction: null }, bindLabel: { classPropertyName: "bindLabel", publicName: "bindLabel", isSignal: true, isRequired: false, transformFunction: null }, bindValue: { classPropertyName: "bindValue", publicName: "bindValue", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, notFoundText: { classPropertyName: "notFoundText", publicName: "notFoundText", isSignal: true, isRequired: false, transformFunction: null }, loadingText: { classPropertyName: "loadingText", publicName: "loadingText", isSignal: true, isRequired: false, transformFunction: null }, clearAllText: { classPropertyName: "clearAllText", publicName: "clearAllText", isSignal: true, isRequired: false, transformFunction: null }, groupBy: { classPropertyName: "groupBy", publicName: "groupBy", isSignal: true, isRequired: false, transformFunction: null }, colorKey: { classPropertyName: "colorKey", publicName: "colorKey", isSignal: true, isRequired: false, transformFunction: null }, iconAlt: { classPropertyName: "iconAlt", publicName: "iconAlt", isSignal: false, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: false, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: false, isRequired: false, transformFunction: null }, iconSrc: { classPropertyName: "iconSrc", publicName: "iconSrc", isSignal: false, isRequired: false, transformFunction: null }, multiple: { classPropertyName: "multiple", publicName: "multiple", isSignal: true, isRequired: false, transformFunction: null }, maxLabels: { classPropertyName: "maxLabels", publicName: "maxLabels", isSignal: true, isRequired: false, transformFunction: null }, searchable: { classPropertyName: "searchable", publicName: "searchable", isSignal: true, isRequired: false, transformFunction: null }, allSelect: { classPropertyName: "allSelect", publicName: "allSelect", isSignal: true, isRequired: false, transformFunction: null }, clearable: { classPropertyName: "clearable", publicName: "clearable", isSignal: true, isRequired: false, transformFunction: null }, readonly: { classPropertyName: "readonly", publicName: "readonly", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, closeOnSelect: { classPropertyName: "closeOnSelect", publicName: "closeOnSelect", isSignal: true, isRequired: false, transformFunction: null }, dropdownPosition: { classPropertyName: "dropdownPosition", publicName: "dropdownPosition", isSignal: true, isRequired: false, transformFunction: null }, appendToBody: { classPropertyName: "appendToBody", publicName: "appendToBody", isSignal: true, isRequired: false, transformFunction: null }, compareWith: { classPropertyName: "compareWith", publicName: "compareWith", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { disabled: "disabledChange", open: "open", close: "close", focus: "focus", blur: "blur", search: "search", clear: "clear", change: "change", scrollToEnd: "scrollToEnd" }, host: { listeners: { "window:scroll": "onWindowEvents()", "window:resize": "onWindowEvents()", "document:click": "onClickOutside($event)" } }, providers: [
|
|
3359
3399
|
{
|
|
3360
3400
|
provide: NG_VALUE_ACCESSOR,
|
|
3361
3401
|
useExisting: forwardRef(() => BkSelect),
|
|
3362
3402
|
multi: true
|
|
3363
3403
|
}
|
|
3364
|
-
], viewQueries: [{ propertyName: "searchInput", first: true, predicate: ["searchInput"], descendants: true }, { propertyName: "optionsListContainer", first: true, predicate: ["optionsListContainer"], descendants: true }, { propertyName: "controlWrapper", first: true, predicate: ["controlWrapper"], descendants: true }, { propertyName: "optionsRef", predicate: ["optionsRef"], descendants: true }], ngImport: i0, template: "<div class=\"ng-select-container\">\r\n\r\n <label\r\n class=\"input-label\"\r\n (click)=\"openFromLabel($event)\">\r\n {{ label }}\r\n @if(required){\r\n <span class=\"input-label-required\">*</span>\r\n }\r\n </label>\r\n\r\n <div\r\n #controlWrapper\r\n class=\"ng-select-control\"\r\n [class.focused]=\"isOpen()\"\r\n [class.disabled]=\"disabled()\"\r\n (mousedown)=\"toggleDropdown($event)\"\r\n >\r\n <!-- Icon (Always visible if set) -->\r\n @if(iconSrc){\r\n <img [src]=\"iconSrc\" [alt]=\"iconAlt\" class=\"shrink-0\" />\r\n }\r\n <div class=\"ng-value-container\">\r\n @if (selectedOptions().length === 0)\r\n {\r\n <div class=\"ng-placeholder\">{{ placeholder() }}</div>\r\n }\r\n @if\r\n (multiple() && selectedOptions().length > 0) {\r\n <div class=\"ng-value-chips\">\r\n @for (opt of selectedOptions().slice(0, maxLabels()); track $index) {\r\n <div class=\"ng-value-chip\">\r\n <span class=\"ng-value-label\">{{ resolveLabel(opt) }}</span>\r\n <span class=\"ng-value-icon\" (mousedown)=\"removeOption(opt, $event)\">\u00D7</span>\r\n </div>\r\n }\r\n @if (selectedOptions().length > maxLabels()) {\r\n <div class=\"ng-value-chip remaining-count\"><span class=\"ng-value-label\">+{{ selectedOptions().length - maxLabels() }} more</span></div>\r\n }\r\n </div>\r\n }\r\n @if (!multiple() && selectedOptions().length > 0) {\r\n <div class=\"ng-value-label-single\">{{ resolveLabel(selectedOptions()[0]) }}</div>\r\n }\r\n </div>\r\n <div class=\"ng-actions\">\r\n @if (clearable() && selectedOptions().length > 0 && !disabled()) {\r\n <span class=\"ng-clear-wrapper\" (mousedown)=\"handleClear($event)\" title=\"Clear\">\r\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\"></line><line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\"></line></svg>\r\n </span>\r\n }\r\n <span class=\"ng-arrow-wrapper\" [class.open]=\"isOpen()\">\r\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"m6 9 6 6 6-6\"/></svg>\r\n </span>\r\n </div>\r\n </div>\r\n\r\n @if (isOpen()) {\r\n <div\r\n #dropdownPanel\r\n class=\"custom-ng-dropdown-panel\"\r\n [attr.data-position]=\"dropdownPosition()\"\r\n (scroll)=\"onScroll($event)\"\r\n\r\n [style.position]=\"appendToBody() ? 'fixed' : 'absolute'\"\r\n [style.top]=\"getTop()\"\r\n [style.bottom]=\"getBottom()\"\r\n [style.left]=\"appendToBody() ? dropdownStyle().left : null\"\r\n [style.width]=\"appendToBody() ? dropdownStyle().width : '100%'\"\r\n >\r\n\r\n\r\n @if (searchable()) {\r\n <div class=\"ng-dropdown-search\">\r\n <div class=\"ng-search-wrapper\">\r\n <svg class=\"text-[#BBBDC5] mr-2\" xmlns=\"http://www.w3.org/2000/svg\" width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><circle cx=\"11\" cy=\"11\" r=\"8\"></circle><line x1=\"21\" y1=\"21\" x2=\"16.65\" y2=\"16.65\"></line></svg>\r\n <input #searchInput type=\"text\" class=\"ng-search-input\" [value]=\"searchTerm()\" [placeholder]=\"'Search...'\" (input)=\"onSearchInput($event)\" (keydown)=\"onKeyDown($event)\" (click)=\"$event.stopPropagation()\">\r\n </div>\r\n </div>\r\n }\r\n @if (multiple() && filteredItems().length > 0) {\r\n <div class=\"ng-option select-all-option\" (mousedown)=\"toggleSelectAll($event)\">\r\n <div class=\"mr-2 flex items-center justify-center w-4 h-4 border border-gray-300 rounded bg-white\" [class.bg-blue-600]=\"isAllSelected()\" [class.border-blue-600]=\"isAllSelected()\">\r\n @if(isAllSelected()){ <svg class=\"text-white w-3 h-3\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"4\"><polyline points=\"20 6 9 17 4 12\"/></svg> }\r\n </div>\r\n <span class=\"font-semibold\">Select All</span>\r\n </div>\r\n }\r\n <div #optionsListContainer class=\"ng-options-list\">\r\n @if (loading()) { <div class=\"ng-option-disabled\">{{ loadingText() }}</div> }\r\n @else {\r\n @for (item of filteredItems(); track $index) {\r\n <div #optionsRef class=\"ng-option\" [class.selected]=\"isItemSelected(item)\" [class.marked]=\"$index === markedIndex()\" (click)=\"handleSelection(item, $event)\" (click)=\"markedIndex.set($index)\">\r\n @if (multiple()) {\r\n <div class=\"mr-2 flex items-center justify-center w-4 h-4 border border-gray-300 rounded bg-white\" [class.bg-blue-600]=\"isItemSelected(item)\" [class.border-blue-600]=\"isItemSelected(item)\">\r\n @if(isItemSelected(item)){ <svg class=\"text-white w-3 h-3\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"4\"><polyline points=\"20 6 9 17 4 12\"/></svg> }\r\n </div>\r\n }\r\n <span class=\"flex-1\">{{ resolveLabel(item) }}</span>\r\n @if (!multiple() && isItemSelected(item)) {\r\n <svg class=\"text-[#141414]\" width=\"17\" height=\"17\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\"><polyline points=\"20 6 9 17 4 12\"/></svg>\r\n }\r\n </div>\r\n }\r\n @if (filteredItems().length === 0) { <div class=\"ng-option-disabled\">{{ notFoundText() }}</div> }\r\n }\r\n </div>\r\n\r\n </div>\r\n }\r\n</div>\r\n", styles: [".ng-select-container{@apply relative w-full box-border;}.ng-select-control{@apply flex items-center justify-between gap-2 w-full bg-white border border-[#E3E3E7] rounded transition-all duration-200 px-3 py-[9.2px] cursor-pointer;}.ng-select-control.focused{@apply border-[#6B7080] shadow-none z-10;}.ng-select-control.disabled{@apply bg-[#F4F4F6] cursor-not-allowed opacity-60;}.ng-value-container{@apply flex flex-1 items-center flex-wrap gap-1 relative overflow-hidden h-full;}.ng-placeholder{@apply text-[#6B7080] font-normal text-sm truncate w-full pointer-events-none;}.ng-value-label-single{@apply font-normal text-sm text-[#141414] truncate w-full;}.ng-value-chips{@apply flex flex-wrap gap-1 w-full;}.ng-value-chip{@apply flex items-center bg-gray-100 border border-gray-200 rounded px-2 py-0.5 max-w-full;}.ng-value-chip .ng-value-label{@apply text-[#15191E] truncate max-w-[150px];}.ng-value-chip .ng-value-icon{@apply ml-1 text-gray-500 hover:text-red-500 cursor-pointer text-base font-bold leading-none px-1;}.ng-actions{@apply flex items-center gap-0.5 flex-shrink-0;}.ng-clear-wrapper{@apply text-gray-400 hover:text-red-500 cursor-pointer;}.ng-arrow-wrapper{@apply text-gray-400 transition-transform duration-200;}.ng-arrow-wrapper.open{@apply rotate-180;}.custom-ng-dropdown-panel{@apply absolute left-0 w-full bg-white border border-[#E3E3E7] rounded-xl shadow-lg z-[99] overflow-hidden cursor-default;}.ng-dropdown-search{@apply px-2 pt-2;}.ng-search-wrapper{@apply flex items-center border border-[#E3E3E7] rounded-md px-3 py-[7px] bg-white transition-colors focus-within:border-[#E3E3E7];}.ng-search-input{@apply w-full outline-none font-normal text-sm text-[#141414] placeholder-[#A1A3AE] bg-transparent;}.ng-options-list{@apply max-h-60 overflow-auto relative p-2.5 mb-3.5 flex flex-col gap-0.5;}.ng-option{@apply flex items-center p-2.5 cursor-pointer transition-colors font-normal text-sm text-[#141414];}.ng-option:hover,.ng-option.marked,.ng-option.selected{@apply bg-[#F8F8F8] rounded-md;}.ng-option-disabled{@apply px-3 py-2 text-gray-400 cursor-default;}.ng-value-chip.remaining-count{@apply bg-gray-200 text-gray-600 font-semibold cursor-default;}.select-all-option{@apply sticky top-0 z-20 flex items-center px-3 py-2 cursor-pointer border-b border-[#E3E6EE] bg-gray-50 text-[#15191E];}.select-all-option:hover{@apply bg-gray-100;}.custom-ng-dropdown-panel[data-position=top]{margin-top:0;margin-bottom:4px}.input-label{@apply text-sm font-medium text-[#141414] tracking-[-.28px] mb-1.5 inline-block;}.input-label-required{@apply text-[#E7000B];}::-webkit-scrollbar{width:10px}::-webkit-scrollbar-track{background:transparent;border-radius:8px;width:8px}::-webkit-scrollbar-thumb{background:#d6d7dc;border-radius:8px;transition:.3s ease-in-out}::-webkit-scrollbar-thumb:hover{background:#909090}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }] });
|
|
3404
|
+
], viewQueries: [{ propertyName: "searchInput", first: true, predicate: ["searchInput"], descendants: true }, { propertyName: "optionsListContainer", first: true, predicate: ["optionsListContainer"], descendants: true }, { propertyName: "controlWrapper", first: true, predicate: ["controlWrapper"], descendants: true }, { propertyName: "optionsRef", predicate: ["optionsRef"], descendants: true }], ngImport: i0, template: "<div class=\"ng-select-container\">\r\n\r\n @if(label){\r\n <label\r\n class=\"input-label\"\r\n (click)=\"openFromLabel($event)\">\r\n {{ label }}\r\n @if (required) {\r\n <span class=\"input-label-required\">*</span>\r\n }\r\n </label>\r\n }\r\n\r\n <div\r\n #controlWrapper\r\n class=\"ng-select-control\"\r\n tabindex=\"0\"\r\n (keydown)=\"onKeyDown($event)\"\r\n [class.focused]=\"isOpen()\"\r\n [class.disabled]=\"disabled()\"\r\n (mousedown)=\"toggleDropdown($event)\"\r\n >\r\n <!-- Icon (Always visible if set) -->\r\n @if (iconSrc) {\r\n <img [src]=\"iconSrc\" [alt]=\"iconAlt\" class=\"shrink-0\"/>\r\n }\r\n <div class=\"ng-value-container\">\r\n @if (selectedOptions().length === 0) {\r\n <div class=\"ng-placeholder\">{{ placeholder() }}</div>\r\n }\r\n @if (multiple() && selectedOptions().length > 0) {\r\n <div class=\"ng-value-chips\">\r\n @for (opt of selectedOptions().slice(0, maxLabels()); track $index) {\r\n <div class=\"multi-badge-item\">\r\n <span class=\"multi-badge-item-text\" [style.color]=\"resolveColor(opt)\">{{ resolveLabel(opt) }}</span>\r\n\r\n <button type=\"button\" (mousedown)=\"removeOption(opt, $event)\">\r\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"8\" height=\"8\" viewBox=\"0 0 8 8\" fill=\"none\">\r\n <path d=\"M6.625 0.625L0.625 6.625M0.625 0.625L6.625 6.625\" stroke=\"#BBBDC5\" stroke-width=\"1.25\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\r\n </svg>\r\n </button>\r\n </div>\r\n }\r\n @if (selectedOptions().length > maxLabels()) {\r\n <div class=\"multi-badge-item\"><span\r\n class=\"multi-badge-item-text\">+{{ selectedOptions().length - maxLabels() }}</span></div>\r\n }\r\n </div>\r\n }\r\n @if (!multiple() && selectedOptions().length > 0) {\r\n <div class=\"ng-value-label-single\" [style.color]=\"resolveColor(selectedOptions()[0])\">\r\n {{ resolveLabel(selectedOptions()[0]) }}</div>\r\n }\r\n </div>\r\n <div class=\"ng-actions\">\r\n @if (clearable() && selectedOptions().length > 0 && !disabled()) {\r\n <span class=\"ng-clear-wrapper\" (mousedown)=\"handleClear($event)\" title=\"Clear\">\r\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\"\r\n stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><line x1=\"18\"\r\n y1=\"6\" x2=\"6\"\r\n y2=\"18\"></line><line\r\n x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\"></line></svg>\r\n </span>\r\n }\r\n <span class=\"ng-arrow-wrapper\" [class.open]=\"isOpen()\">\r\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\"\r\n stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path\r\n d=\"m6 9 6 6 6-6\"/></svg>\r\n </span>\r\n </div>\r\n </div>\r\n\r\n @if (isOpen()) {\r\n <div\r\n #dropdownPanel\r\n class=\"custom-ng-dropdown-panel\"\r\n [attr.data-position]=\"dropdownPosition()\"\r\n (scroll)=\"onScroll($event)\"\r\n\r\n [style.position]=\"appendToBody() ? 'fixed' : 'absolute'\"\r\n [style.top]=\"getTop()\"\r\n [style.bottom]=\"getBottom()\"\r\n [style.left]=\"appendToBody() ? dropdownStyle().left : null\"\r\n [style.width]=\"appendToBody() ? dropdownStyle().width : '100%'\"\r\n [class.grouped]=\"groupBy()\"\r\n >\r\n\r\n\r\n @if (searchable()) {\r\n <div class=\"ng-dropdown-search\">\r\n <div class=\"ng-search-wrapper\">\r\n <svg class=\"text-[#BBBDC5] mr-2\" xmlns=\"http://www.w3.org/2000/svg\" width=\"20\" height=\"20\"\r\n viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"\r\n stroke-linejoin=\"round\">\r\n <circle cx=\"11\" cy=\"11\" r=\"8\"></circle>\r\n <line x1=\"21\" y1=\"21\" x2=\"16.65\" y2=\"16.65\"></line>\r\n </svg>\r\n <input #searchInput type=\"text\" class=\"ng-search-input\" [value]=\"searchTerm()\" [placeholder]=\"'Search...'\"\r\n (input)=\"onSearchInput($event)\" (keydown)=\"onKeyDown($event)\" (click)=\"$event.stopPropagation()\">\r\n </div>\r\n </div>\r\n }\r\n <div #optionsListContainer class=\"ng-options-list\">\r\n @if (loading()) {\r\n <div class=\"ng-option-disabled\">{{ loadingText() }}</div>\r\n } @else {\r\n @if (allSelect()){\r\n\r\n @if (multiple() && filteredItems().length > 0) {\r\n <div class=\"ng-option\" (mousedown)=\"toggleSelectAll($event)\" [class.selected]=\"isAllSelected()\">\r\n <div class=\"flex-1 flex justify-between\">\r\n <span>Select All</span>\r\n @if (isAllSelected()) {\r\n <svg class=\"text-[#141414]\" width=\"17\" height=\"17\" viewBox=\"0 0 24 24\" fill=\"none\"\r\n stroke=\"currentColor\" stroke-width=\"2.5\">\r\n <polyline points=\"20 6 9 17 4 12\"/>\r\n </svg>\r\n }\r\n </div>\r\n </div>\r\n }\r\n }\r\n @for (group of groupedItems(); track $index) {\r\n\r\n @if (group.group) {\r\n <div class=\"ng-option-group\">\r\n {{ group.group }}\r\n </div>\r\n }\r\n\r\n @for (item of group.items; track $index) {\r\n <div #optionsRef class=\"ng-option\" [class.selected]=\"isItemSelected(item)\" [class.marked]=\"$index === markedIndex()\" (click)=\"handleSelection(item, $event)\">\r\n <div class=\"flex-1 flex justify-between\">\r\n <span [style.color]=\"resolveColor(item)\">{{ resolveLabel(item) }}</span>\r\n\r\n @if (isItemSelected(item)) {\r\n <svg class=\"text-[#141414]\" width=\"17\" height=\"17\" viewBox=\"0 0 24 24\"\r\n fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\">\r\n <polyline points=\"20 6 9 17 4 12\"/>\r\n </svg>\r\n }\r\n </div>\r\n </div>\r\n }\r\n\r\n }\r\n\r\n @if (filteredItems().length === 0) {\r\n <div class=\"ng-option-disabled\">{{ notFoundText() }}</div>\r\n }\r\n }\r\n </div>\r\n\r\n </div>\r\n }\r\n</div>\r\n", styles: [".ng-select-container{@apply relative w-full box-border;}.ng-select-control{@apply flex items-center justify-between gap-2 w-full bg-white border border-[#E3E3E7] rounded transition-all duration-200 px-3 py-2.5 cursor-pointer;}.ng-select-control.focused{@apply border-[#6B7080] shadow-none z-10;}.ng-select-control.disabled{@apply bg-[#F4F4F6] cursor-not-allowed opacity-60;}.ng-value-container{@apply flex flex-1 items-center flex-wrap gap-1 relative overflow-hidden h-full;}.ng-placeholder{@apply text-[#6B7080] font-normal text-sm truncate w-full pointer-events-none;}.ng-value-label-single{@apply font-normal text-sm leading-[18px] text-[#141414] truncate w-full;}.multi-badge-item{@apply inline-flex items-center gap-1.5 px-1.5 py-0.5 bg-white border border-[#E3E3E7] rounded-[4px];}.multi-badge-item-text{@apply text-[10px] leading-[14px] font-normal text-[#6B7080];}.multi-badge-close{@apply cursor-pointer outline-none w-3 h-3;}.ng-actions{@apply flex items-center gap-0.5 flex-shrink-0;}.ng-clear-wrapper{@apply text-gray-400 hover:text-red-500 cursor-pointer;}.ng-arrow-wrapper{@apply text-gray-400 transition-transform duration-200;}.ng-arrow-wrapper.open{@apply rotate-180;}.custom-ng-dropdown-panel{@apply absolute left-0 w-full bg-white border border-[#E3E3E7] rounded-xl shadow-lg z-[99] overflow-hidden cursor-default p-2.5;}.ng-dropdown-search{@apply px-2 pt-2;}.ng-search-wrapper{@apply flex items-center border border-[#E3E3E7] rounded-md px-3 py-[7px] bg-white transition-colors focus-within:border-[#E3E3E7];}.ng-search-input{@apply w-full outline-none font-normal text-sm text-[#141414] placeholder-[#A1A3AE] bg-transparent;}.ng-options-list{@apply max-h-60 overflow-auto relative flex flex-col gap-0.5;}.ng-option{@apply flex items-center p-2.5 cursor-pointer transition-colors font-normal text-sm text-[#141414];}.ng-option:hover,.ng-option.marked,.ng-option.selected{@apply bg-[#F8F8F8] rounded-md;}.grouped .ng-option{@apply ps-5;}.ng-option-disabled{@apply px-3 py-2 text-gray-400 cursor-default;}.select-all-option{@apply sticky top-0 z-20 flex items-center px-3 py-2 cursor-pointer border-b border-[#E3E6EE] bg-gray-50 text-[#15191E];}.select-all-option:hover{@apply bg-gray-100;}.custom-ng-dropdown-panel[data-position=top]{margin-top:0;margin-bottom:4px}.input-label{@apply text-sm font-medium text-[#141414] tracking-[-.28px] mb-1.5 inline-block;}.input-label-required{@apply text-[#E7000B];}.ng-options-list ::-webkit-scrollbar{width:10px}.ng-options-list ::-webkit-scrollbar-track{background:transparent;border-radius:8px;width:8px}.ng-options-list ::-webkit-scrollbar-thumb{background:#d6d7dc;border-radius:8px;transition:.3s ease-in-out}.ng-options-list ::-webkit-scrollbar-thumb:hover{background:#909090}.ng-option-group{@apply px-2.5 py-1 font-bold text-[13px] leading-5 text-[#141414];}.ng-option-group:not(:first-child){@apply mt-4;}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }] });
|
|
3365
3405
|
}
|
|
3366
3406
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: BkSelect, decorators: [{
|
|
3367
3407
|
type: Component,
|
|
@@ -3371,8 +3411,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
|
|
|
3371
3411
|
useExisting: forwardRef(() => BkSelect),
|
|
3372
3412
|
multi: true
|
|
3373
3413
|
}
|
|
3374
|
-
], template: "<div class=\"ng-select-container\">\r\n\r\n <label\r\n class=\"input-label\"\r\n (click)=\"openFromLabel($event)\">\r\n {{ label }}\r\n @if(required){\r\n <span class=\"input-label-required\">*</span>\r\n }\r\n </label>\r\n\r\n <div\r\n #controlWrapper\r\n class=\"ng-select-control\"\r\n [class.focused]=\"isOpen()\"\r\n [class.disabled]=\"disabled()\"\r\n (mousedown)=\"toggleDropdown($event)\"\r\n >\r\n <!-- Icon (Always visible if set) -->\r\n @if(iconSrc){\r\n <img [src]=\"iconSrc\" [alt]=\"iconAlt\" class=\"shrink-0\"
|
|
3375
|
-
}], ctorParameters: () => [], propDecorators: { items: [{ type: i0.Input, args: [{ isSignal: true, alias: "items", required: false }] }], bindLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "bindLabel", required: false }] }], bindValue: [{ type: i0.Input, args: [{ isSignal: true, alias: "bindValue", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], notFoundText: [{ type: i0.Input, args: [{ isSignal: true, alias: "notFoundText", required: false }] }], loadingText: [{ type: i0.Input, args: [{ isSignal: true, alias: "loadingText", required: false }] }], clearAllText: [{ type: i0.Input, args: [{ isSignal: true, alias: "clearAllText", required: false }] }], iconAlt: [{
|
|
3414
|
+
], template: "<div class=\"ng-select-container\">\r\n\r\n @if(label){\r\n <label\r\n class=\"input-label\"\r\n (click)=\"openFromLabel($event)\">\r\n {{ label }}\r\n @if (required) {\r\n <span class=\"input-label-required\">*</span>\r\n }\r\n </label>\r\n }\r\n\r\n <div\r\n #controlWrapper\r\n class=\"ng-select-control\"\r\n tabindex=\"0\"\r\n (keydown)=\"onKeyDown($event)\"\r\n [class.focused]=\"isOpen()\"\r\n [class.disabled]=\"disabled()\"\r\n (mousedown)=\"toggleDropdown($event)\"\r\n >\r\n <!-- Icon (Always visible if set) -->\r\n @if (iconSrc) {\r\n <img [src]=\"iconSrc\" [alt]=\"iconAlt\" class=\"shrink-0\"/>\r\n }\r\n <div class=\"ng-value-container\">\r\n @if (selectedOptions().length === 0) {\r\n <div class=\"ng-placeholder\">{{ placeholder() }}</div>\r\n }\r\n @if (multiple() && selectedOptions().length > 0) {\r\n <div class=\"ng-value-chips\">\r\n @for (opt of selectedOptions().slice(0, maxLabels()); track $index) {\r\n <div class=\"multi-badge-item\">\r\n <span class=\"multi-badge-item-text\" [style.color]=\"resolveColor(opt)\">{{ resolveLabel(opt) }}</span>\r\n\r\n <button type=\"button\" (mousedown)=\"removeOption(opt, $event)\">\r\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"8\" height=\"8\" viewBox=\"0 0 8 8\" fill=\"none\">\r\n <path d=\"M6.625 0.625L0.625 6.625M0.625 0.625L6.625 6.625\" stroke=\"#BBBDC5\" stroke-width=\"1.25\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\r\n </svg>\r\n </button>\r\n </div>\r\n }\r\n @if (selectedOptions().length > maxLabels()) {\r\n <div class=\"multi-badge-item\"><span\r\n class=\"multi-badge-item-text\">+{{ selectedOptions().length - maxLabels() }}</span></div>\r\n }\r\n </div>\r\n }\r\n @if (!multiple() && selectedOptions().length > 0) {\r\n <div class=\"ng-value-label-single\" [style.color]=\"resolveColor(selectedOptions()[0])\">\r\n {{ resolveLabel(selectedOptions()[0]) }}</div>\r\n }\r\n </div>\r\n <div class=\"ng-actions\">\r\n @if (clearable() && selectedOptions().length > 0 && !disabled()) {\r\n <span class=\"ng-clear-wrapper\" (mousedown)=\"handleClear($event)\" title=\"Clear\">\r\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\"\r\n stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><line x1=\"18\"\r\n y1=\"6\" x2=\"6\"\r\n y2=\"18\"></line><line\r\n x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\"></line></svg>\r\n </span>\r\n }\r\n <span class=\"ng-arrow-wrapper\" [class.open]=\"isOpen()\">\r\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\"\r\n stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path\r\n d=\"m6 9 6 6 6-6\"/></svg>\r\n </span>\r\n </div>\r\n </div>\r\n\r\n @if (isOpen()) {\r\n <div\r\n #dropdownPanel\r\n class=\"custom-ng-dropdown-panel\"\r\n [attr.data-position]=\"dropdownPosition()\"\r\n (scroll)=\"onScroll($event)\"\r\n\r\n [style.position]=\"appendToBody() ? 'fixed' : 'absolute'\"\r\n [style.top]=\"getTop()\"\r\n [style.bottom]=\"getBottom()\"\r\n [style.left]=\"appendToBody() ? dropdownStyle().left : null\"\r\n [style.width]=\"appendToBody() ? dropdownStyle().width : '100%'\"\r\n [class.grouped]=\"groupBy()\"\r\n >\r\n\r\n\r\n @if (searchable()) {\r\n <div class=\"ng-dropdown-search\">\r\n <div class=\"ng-search-wrapper\">\r\n <svg class=\"text-[#BBBDC5] mr-2\" xmlns=\"http://www.w3.org/2000/svg\" width=\"20\" height=\"20\"\r\n viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"\r\n stroke-linejoin=\"round\">\r\n <circle cx=\"11\" cy=\"11\" r=\"8\"></circle>\r\n <line x1=\"21\" y1=\"21\" x2=\"16.65\" y2=\"16.65\"></line>\r\n </svg>\r\n <input #searchInput type=\"text\" class=\"ng-search-input\" [value]=\"searchTerm()\" [placeholder]=\"'Search...'\"\r\n (input)=\"onSearchInput($event)\" (keydown)=\"onKeyDown($event)\" (click)=\"$event.stopPropagation()\">\r\n </div>\r\n </div>\r\n }\r\n <div #optionsListContainer class=\"ng-options-list\">\r\n @if (loading()) {\r\n <div class=\"ng-option-disabled\">{{ loadingText() }}</div>\r\n } @else {\r\n @if (allSelect()){\r\n\r\n @if (multiple() && filteredItems().length > 0) {\r\n <div class=\"ng-option\" (mousedown)=\"toggleSelectAll($event)\" [class.selected]=\"isAllSelected()\">\r\n <div class=\"flex-1 flex justify-between\">\r\n <span>Select All</span>\r\n @if (isAllSelected()) {\r\n <svg class=\"text-[#141414]\" width=\"17\" height=\"17\" viewBox=\"0 0 24 24\" fill=\"none\"\r\n stroke=\"currentColor\" stroke-width=\"2.5\">\r\n <polyline points=\"20 6 9 17 4 12\"/>\r\n </svg>\r\n }\r\n </div>\r\n </div>\r\n }\r\n }\r\n @for (group of groupedItems(); track $index) {\r\n\r\n @if (group.group) {\r\n <div class=\"ng-option-group\">\r\n {{ group.group }}\r\n </div>\r\n }\r\n\r\n @for (item of group.items; track $index) {\r\n <div #optionsRef class=\"ng-option\" [class.selected]=\"isItemSelected(item)\" [class.marked]=\"$index === markedIndex()\" (click)=\"handleSelection(item, $event)\">\r\n <div class=\"flex-1 flex justify-between\">\r\n <span [style.color]=\"resolveColor(item)\">{{ resolveLabel(item) }}</span>\r\n\r\n @if (isItemSelected(item)) {\r\n <svg class=\"text-[#141414]\" width=\"17\" height=\"17\" viewBox=\"0 0 24 24\"\r\n fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\">\r\n <polyline points=\"20 6 9 17 4 12\"/>\r\n </svg>\r\n }\r\n </div>\r\n </div>\r\n }\r\n\r\n }\r\n\r\n @if (filteredItems().length === 0) {\r\n <div class=\"ng-option-disabled\">{{ notFoundText() }}</div>\r\n }\r\n }\r\n </div>\r\n\r\n </div>\r\n }\r\n</div>\r\n", styles: [".ng-select-container{@apply relative w-full box-border;}.ng-select-control{@apply flex items-center justify-between gap-2 w-full bg-white border border-[#E3E3E7] rounded transition-all duration-200 px-3 py-2.5 cursor-pointer;}.ng-select-control.focused{@apply border-[#6B7080] shadow-none z-10;}.ng-select-control.disabled{@apply bg-[#F4F4F6] cursor-not-allowed opacity-60;}.ng-value-container{@apply flex flex-1 items-center flex-wrap gap-1 relative overflow-hidden h-full;}.ng-placeholder{@apply text-[#6B7080] font-normal text-sm truncate w-full pointer-events-none;}.ng-value-label-single{@apply font-normal text-sm leading-[18px] text-[#141414] truncate w-full;}.multi-badge-item{@apply inline-flex items-center gap-1.5 px-1.5 py-0.5 bg-white border border-[#E3E3E7] rounded-[4px];}.multi-badge-item-text{@apply text-[10px] leading-[14px] font-normal text-[#6B7080];}.multi-badge-close{@apply cursor-pointer outline-none w-3 h-3;}.ng-actions{@apply flex items-center gap-0.5 flex-shrink-0;}.ng-clear-wrapper{@apply text-gray-400 hover:text-red-500 cursor-pointer;}.ng-arrow-wrapper{@apply text-gray-400 transition-transform duration-200;}.ng-arrow-wrapper.open{@apply rotate-180;}.custom-ng-dropdown-panel{@apply absolute left-0 w-full bg-white border border-[#E3E3E7] rounded-xl shadow-lg z-[99] overflow-hidden cursor-default p-2.5;}.ng-dropdown-search{@apply px-2 pt-2;}.ng-search-wrapper{@apply flex items-center border border-[#E3E3E7] rounded-md px-3 py-[7px] bg-white transition-colors focus-within:border-[#E3E3E7];}.ng-search-input{@apply w-full outline-none font-normal text-sm text-[#141414] placeholder-[#A1A3AE] bg-transparent;}.ng-options-list{@apply max-h-60 overflow-auto relative flex flex-col gap-0.5;}.ng-option{@apply flex items-center p-2.5 cursor-pointer transition-colors font-normal text-sm text-[#141414];}.ng-option:hover,.ng-option.marked,.ng-option.selected{@apply bg-[#F8F8F8] rounded-md;}.grouped .ng-option{@apply ps-5;}.ng-option-disabled{@apply px-3 py-2 text-gray-400 cursor-default;}.select-all-option{@apply sticky top-0 z-20 flex items-center px-3 py-2 cursor-pointer border-b border-[#E3E6EE] bg-gray-50 text-[#15191E];}.select-all-option:hover{@apply bg-gray-100;}.custom-ng-dropdown-panel[data-position=top]{margin-top:0;margin-bottom:4px}.input-label{@apply text-sm font-medium text-[#141414] tracking-[-.28px] mb-1.5 inline-block;}.input-label-required{@apply text-[#E7000B];}.ng-options-list ::-webkit-scrollbar{width:10px}.ng-options-list ::-webkit-scrollbar-track{background:transparent;border-radius:8px;width:8px}.ng-options-list ::-webkit-scrollbar-thumb{background:#d6d7dc;border-radius:8px;transition:.3s ease-in-out}.ng-options-list ::-webkit-scrollbar-thumb:hover{background:#909090}.ng-option-group{@apply px-2.5 py-1 font-bold text-[13px] leading-5 text-[#141414];}.ng-option-group:not(:first-child){@apply mt-4;}\n"] }]
|
|
3415
|
+
}], ctorParameters: () => [], propDecorators: { items: [{ type: i0.Input, args: [{ isSignal: true, alias: "items", required: false }] }], bindLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "bindLabel", required: false }] }], bindValue: [{ type: i0.Input, args: [{ isSignal: true, alias: "bindValue", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], notFoundText: [{ type: i0.Input, args: [{ isSignal: true, alias: "notFoundText", required: false }] }], loadingText: [{ type: i0.Input, args: [{ isSignal: true, alias: "loadingText", required: false }] }], clearAllText: [{ type: i0.Input, args: [{ isSignal: true, alias: "clearAllText", required: false }] }], groupBy: [{ type: i0.Input, args: [{ isSignal: true, alias: "groupBy", required: false }] }], colorKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "colorKey", required: false }] }], iconAlt: [{
|
|
3376
3416
|
type: Input
|
|
3377
3417
|
}], label: [{
|
|
3378
3418
|
type: Input
|
|
@@ -3380,7 +3420,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
|
|
|
3380
3420
|
type: Input
|
|
3381
3421
|
}], iconSrc: [{
|
|
3382
3422
|
type: Input
|
|
3383
|
-
}], multiple: [{ type: i0.Input, args: [{ isSignal: true, alias: "multiple", required: false }] }], maxLabels: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxLabels", required: false }] }], searchable: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchable", required: false }] }], clearable: [{ type: i0.Input, args: [{ isSignal: true, alias: "clearable", required: false }] }], readonly: [{ type: i0.Input, args: [{ isSignal: true, alias: "readonly", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }, { type: i0.Output, args: ["disabledChange"] }], loading: [{ type: i0.Input, args: [{ isSignal: true, alias: "loading", required: false }] }], closeOnSelect: [{ type: i0.Input, args: [{ isSignal: true, alias: "closeOnSelect", required: false }] }], dropdownPosition: [{ type: i0.Input, args: [{ isSignal: true, alias: "dropdownPosition", required: false }] }], appendToBody: [{ type: i0.Input, args: [{ isSignal: true, alias: "appendToBody", required: false }] }], open: [{ type: i0.Output, args: ["open"] }], close: [{ type: i0.Output, args: ["close"] }], focus: [{ type: i0.Output, args: ["focus"] }], blur: [{ type: i0.Output, args: ["blur"] }], search: [{ type: i0.Output, args: ["search"] }], clear: [{ type: i0.Output, args: ["clear"] }], change: [{ type: i0.Output, args: ["change"] }], scrollToEnd: [{ type: i0.Output, args: ["scrollToEnd"] }], searchInput: [{
|
|
3423
|
+
}], multiple: [{ type: i0.Input, args: [{ isSignal: true, alias: "multiple", required: false }] }], maxLabels: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxLabels", required: false }] }], searchable: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchable", required: false }] }], allSelect: [{ type: i0.Input, args: [{ isSignal: true, alias: "allSelect", required: false }] }], clearable: [{ type: i0.Input, args: [{ isSignal: true, alias: "clearable", required: false }] }], readonly: [{ type: i0.Input, args: [{ isSignal: true, alias: "readonly", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }, { type: i0.Output, args: ["disabledChange"] }], loading: [{ type: i0.Input, args: [{ isSignal: true, alias: "loading", required: false }] }], closeOnSelect: [{ type: i0.Input, args: [{ isSignal: true, alias: "closeOnSelect", required: false }] }], dropdownPosition: [{ type: i0.Input, args: [{ isSignal: true, alias: "dropdownPosition", required: false }] }], appendToBody: [{ type: i0.Input, args: [{ isSignal: true, alias: "appendToBody", required: false }] }], open: [{ type: i0.Output, args: ["open"] }], close: [{ type: i0.Output, args: ["close"] }], focus: [{ type: i0.Output, args: ["focus"] }], blur: [{ type: i0.Output, args: ["blur"] }], search: [{ type: i0.Output, args: ["search"] }], clear: [{ type: i0.Output, args: ["clear"] }], change: [{ type: i0.Output, args: ["change"] }], scrollToEnd: [{ type: i0.Output, args: ["scrollToEnd"] }], searchInput: [{
|
|
3384
3424
|
type: ViewChild,
|
|
3385
3425
|
args: ['searchInput']
|
|
3386
3426
|
}], optionsListContainer: [{
|
|
@@ -3749,7 +3789,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
|
|
|
3749
3789
|
args: ['document:click', ['$event']]
|
|
3750
3790
|
}] } });
|
|
3751
3791
|
|
|
3752
|
-
class
|
|
3792
|
+
class BkInputChips {
|
|
3753
3793
|
badgeInput;
|
|
3754
3794
|
fieldWrapper;
|
|
3755
3795
|
// --- Configuration Inputs ---
|
|
@@ -3928,21 +3968,21 @@ class BkChips {
|
|
|
3928
3968
|
this.onChange([...this.badges]);
|
|
3929
3969
|
this.blur.emit(event);
|
|
3930
3970
|
}
|
|
3931
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type:
|
|
3932
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.16", type:
|
|
3971
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: BkInputChips, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
3972
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.16", type: BkInputChips, isStandalone: true, selector: "bk-input-chips", inputs: { id: "id", name: "name", label: "label", placeholder: "placeholder", hint: "hint", required: "required", disabled: "disabled", readOnly: "readOnly", hasError: "hasError", errorMessage: "errorMessage" }, outputs: { input: "input", change: "change", focus: "focus", blur: "blur" }, providers: [
|
|
3933
3973
|
{
|
|
3934
3974
|
provide: NG_VALUE_ACCESSOR,
|
|
3935
|
-
useExisting: forwardRef(() =>
|
|
3975
|
+
useExisting: forwardRef(() => BkInputChips),
|
|
3936
3976
|
multi: true
|
|
3937
3977
|
}
|
|
3938
3978
|
], viewQueries: [{ propertyName: "badgeInput", first: true, predicate: ["badgeInput"], descendants: true }, { propertyName: "fieldWrapper", first: true, predicate: ["fieldWrapper"], descendants: true }], ngImport: i0, template: "<div class=\"input-badge-container\">\r\n @if (label) {\r\n <label\r\n [for]=\"id\"\r\n class=\"input-badge-label\">\r\n {{ label }}\r\n @if (required) {\r\n <span class=\"input-badge-label-required\">*</span>\r\n }\r\n </label>\r\n }\r\n\r\n <div class=\"input-badge-wrapper\">\r\n <div\r\n #fieldWrapper\r\n class=\"input-badge-field-wrapper\"\r\n [ngClass]=\"{\r\n 'input-badge-field-wrapper--default': inputState === 'default',\r\n 'input-badge-field-wrapper--focused': inputState === 'focused',\r\n 'input-badge-field-wrapper--filled': inputState === 'filled',\r\n 'input-badge-field-wrapper--error': inputState === 'error',\r\n 'input-badge-field-wrapper--disabled': inputState === 'disabled',\r\n 'input-badge-field-wrapper--scrollable': needsScroll\r\n }\"\r\n (click)=\"focusInput()\"\r\n >\r\n <!-- Badges -->\r\n @for (badge of badges; track badge; let i = $index) {\r\n <div class=\"input-badge-item\">\r\n <span class=\"input-badge-item-text\">{{ badge }}</span>\r\n @if (!disabled) {\r\n <button type=\"button\" (click)=\"removeBadge(i); $event.stopPropagation()\"class=\"input-badge-item-close\" >\r\n <img src=\"../../../../assets/images/icons/global/badge-close.svg\" alt=\"Remove\" class=\"input-badge-item-close-icon\"/>\r\n </button>\r\n }\r\n </div>\r\n }\r\n\r\n <!-- Input Field -->\r\n <input\r\n #badgeInput\r\n type=\"text\"\r\n [id]=\"id\"\r\n [name]=\"name\"\r\n [value]=\"inputValue\"\r\n (keydown)=\"onKeyDown($event)\"\r\n (input)=\"handleInput($event)\"\r\n (focus)=\"handleFocus($event)\"\r\n (blur)=\"handleBlur($event)\"\r\n [placeholder]=\"badges.length === 0 ? placeholder : ''\"\r\n [disabled]=\"disabled\"\r\n [readOnly]=\"readOnly\"\r\n class=\"input-badge-input\"\r\n [ngClass]=\"{\r\n 'input-badge-input--default': inputState === 'default',\r\n 'input-badge-input--focused': inputState === 'focused',\r\n 'input-badge-input--filled': inputState === 'filled',\r\n 'input-badge-input--error': inputState === 'error',\r\n 'input-badge-input--disabled': inputState === 'disabled'\r\n }\"\r\n />\r\n </div>\r\n </div>\r\n\r\n <div class=\"input-badge-footer\">\r\n <div class=\"input-badge-footer-content\">\r\n @if (hasError) {\r\n <span class=\"input-badge-error\"> {{ errorMessage }}</span>\r\n } @else if (hint) {\r\n <span class=\"input-badge-hint\"> {{ hint }}</span>\r\n }\r\n </div>\r\n </div>\r\n</div>\r\n", styles: [".input-badge-container{@apply flex flex-col gap-1.5 w-full;}.input-badge-label{@apply text-sm font-medium text-[#141414] block;}.input-badge-label-required{@apply text-[#E7000B] ml-0.5;}.input-badge-wrapper{@apply relative;}.input-badge-field-wrapper{@apply w-full px-3 py-2 text-sm border rounded-[4px] outline-none transition-colors duration-200 bg-white flex flex-wrap gap-2 items-start;height:40px;box-sizing:border-box;overflow-y:hidden;overflow-x:hidden}.input-badge-field-wrapper--scrollable{overflow-y:auto}.input-badge-field-wrapper--default{@apply border-[#E3E3E7] text-[#141414];}.input-badge-field-wrapper--default:focus-within{@apply border-[#6B7080];}.input-badge-field-wrapper--focused{@apply border-[#6B7080] text-[#141414];}.input-badge-field-wrapper--filled{@apply border-[#E3E3E7] text-[#141414] bg-white;}.input-badge-field-wrapper--filled:focus-within{@apply border-[#6B7080];}.input-badge-field-wrapper--error{@apply border-[#FA727A] text-[#141414];}.input-badge-field-wrapper--disabled{@apply bg-[#F4F4F6] text-[#A1A3AE] border-[#E3E3E7] cursor-not-allowed;}.input-badge-item{@apply inline-flex items-center gap-1 px-2 py-[2.5px] bg-white border border-[#E3E3E7] rounded-[4px];}.input-badge-item-text{@apply text-sm leading-[18px] font-normal text-[#6B7080];}.input-badge-item-close{@apply cursor-pointer outline-none w-3 h-3;}.input-badge-input{@apply flex-1 min-w-[120px] outline-none bg-transparent text-[#141414] placeholder:text-[#6B7080] h-auto;flex-basis:120px}.input-badge-input--default,.input-badge-input--focused,.input-badge-input--filled,.input-badge-input--error{@apply text-[#141414];}.input-badge-input--disabled{@apply text-[#A1A3AE] cursor-not-allowed;}.input-badge-footer{@apply flex justify-between items-start font-normal text-sm;}.input-badge-footer-content{@apply flex-1;}.input-badge-hint{@apply text-xs text-[#868997] font-normal;}.input-badge-error{@apply text-xs text-[#F34050] font-normal;}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "ngmodule", type: FormsModule }] });
|
|
3939
3979
|
}
|
|
3940
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type:
|
|
3980
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: BkInputChips, decorators: [{
|
|
3941
3981
|
type: Component,
|
|
3942
|
-
args: [{ selector: 'bk-chips', standalone: true, imports: [CommonModule, FormsModule], providers: [
|
|
3982
|
+
args: [{ selector: 'bk-input-chips', standalone: true, imports: [CommonModule, FormsModule], providers: [
|
|
3943
3983
|
{
|
|
3944
3984
|
provide: NG_VALUE_ACCESSOR,
|
|
3945
|
-
useExisting: forwardRef(() =>
|
|
3985
|
+
useExisting: forwardRef(() => BkInputChips),
|
|
3946
3986
|
multi: true
|
|
3947
3987
|
}
|
|
3948
3988
|
], template: "<div class=\"input-badge-container\">\r\n @if (label) {\r\n <label\r\n [for]=\"id\"\r\n class=\"input-badge-label\">\r\n {{ label }}\r\n @if (required) {\r\n <span class=\"input-badge-label-required\">*</span>\r\n }\r\n </label>\r\n }\r\n\r\n <div class=\"input-badge-wrapper\">\r\n <div\r\n #fieldWrapper\r\n class=\"input-badge-field-wrapper\"\r\n [ngClass]=\"{\r\n 'input-badge-field-wrapper--default': inputState === 'default',\r\n 'input-badge-field-wrapper--focused': inputState === 'focused',\r\n 'input-badge-field-wrapper--filled': inputState === 'filled',\r\n 'input-badge-field-wrapper--error': inputState === 'error',\r\n 'input-badge-field-wrapper--disabled': inputState === 'disabled',\r\n 'input-badge-field-wrapper--scrollable': needsScroll\r\n }\"\r\n (click)=\"focusInput()\"\r\n >\r\n <!-- Badges -->\r\n @for (badge of badges; track badge; let i = $index) {\r\n <div class=\"input-badge-item\">\r\n <span class=\"input-badge-item-text\">{{ badge }}</span>\r\n @if (!disabled) {\r\n <button type=\"button\" (click)=\"removeBadge(i); $event.stopPropagation()\"class=\"input-badge-item-close\" >\r\n <img src=\"../../../../assets/images/icons/global/badge-close.svg\" alt=\"Remove\" class=\"input-badge-item-close-icon\"/>\r\n </button>\r\n }\r\n </div>\r\n }\r\n\r\n <!-- Input Field -->\r\n <input\r\n #badgeInput\r\n type=\"text\"\r\n [id]=\"id\"\r\n [name]=\"name\"\r\n [value]=\"inputValue\"\r\n (keydown)=\"onKeyDown($event)\"\r\n (input)=\"handleInput($event)\"\r\n (focus)=\"handleFocus($event)\"\r\n (blur)=\"handleBlur($event)\"\r\n [placeholder]=\"badges.length === 0 ? placeholder : ''\"\r\n [disabled]=\"disabled\"\r\n [readOnly]=\"readOnly\"\r\n class=\"input-badge-input\"\r\n [ngClass]=\"{\r\n 'input-badge-input--default': inputState === 'default',\r\n 'input-badge-input--focused': inputState === 'focused',\r\n 'input-badge-input--filled': inputState === 'filled',\r\n 'input-badge-input--error': inputState === 'error',\r\n 'input-badge-input--disabled': inputState === 'disabled'\r\n }\"\r\n />\r\n </div>\r\n </div>\r\n\r\n <div class=\"input-badge-footer\">\r\n <div class=\"input-badge-footer-content\">\r\n @if (hasError) {\r\n <span class=\"input-badge-error\"> {{ errorMessage }}</span>\r\n } @else if (hint) {\r\n <span class=\"input-badge-hint\"> {{ hint }}</span>\r\n }\r\n </div>\r\n </div>\r\n</div>\r\n", styles: [".input-badge-container{@apply flex flex-col gap-1.5 w-full;}.input-badge-label{@apply text-sm font-medium text-[#141414] block;}.input-badge-label-required{@apply text-[#E7000B] ml-0.5;}.input-badge-wrapper{@apply relative;}.input-badge-field-wrapper{@apply w-full px-3 py-2 text-sm border rounded-[4px] outline-none transition-colors duration-200 bg-white flex flex-wrap gap-2 items-start;height:40px;box-sizing:border-box;overflow-y:hidden;overflow-x:hidden}.input-badge-field-wrapper--scrollable{overflow-y:auto}.input-badge-field-wrapper--default{@apply border-[#E3E3E7] text-[#141414];}.input-badge-field-wrapper--default:focus-within{@apply border-[#6B7080];}.input-badge-field-wrapper--focused{@apply border-[#6B7080] text-[#141414];}.input-badge-field-wrapper--filled{@apply border-[#E3E3E7] text-[#141414] bg-white;}.input-badge-field-wrapper--filled:focus-within{@apply border-[#6B7080];}.input-badge-field-wrapper--error{@apply border-[#FA727A] text-[#141414];}.input-badge-field-wrapper--disabled{@apply bg-[#F4F4F6] text-[#A1A3AE] border-[#E3E3E7] cursor-not-allowed;}.input-badge-item{@apply inline-flex items-center gap-1 px-2 py-[2.5px] bg-white border border-[#E3E3E7] rounded-[4px];}.input-badge-item-text{@apply text-sm leading-[18px] font-normal text-[#6B7080];}.input-badge-item-close{@apply cursor-pointer outline-none w-3 h-3;}.input-badge-input{@apply flex-1 min-w-[120px] outline-none bg-transparent text-[#141414] placeholder:text-[#6B7080] h-auto;flex-basis:120px}.input-badge-input--default,.input-badge-input--focused,.input-badge-input--filled,.input-badge-input--error{@apply text-[#141414];}.input-badge-input--disabled{@apply text-[#A1A3AE] cursor-not-allowed;}.input-badge-footer{@apply flex justify-between items-start font-normal text-sm;}.input-badge-footer-content{@apply flex-1;}.input-badge-hint{@apply text-xs text-[#868997] font-normal;}.input-badge-error{@apply text-xs text-[#F34050] font-normal;}\n"] }]
|
|
@@ -4022,6 +4062,1202 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
|
|
|
4022
4062
|
type: Output
|
|
4023
4063
|
}] } });
|
|
4024
4064
|
|
|
4065
|
+
/**
|
|
4066
|
+
* Dialog Injection Tokens
|
|
4067
|
+
*
|
|
4068
|
+
* Architecture Decision:
|
|
4069
|
+
* ─────────────────────
|
|
4070
|
+
* • `DIALOG_DATA` — Re-exported from `@angular/cdk/dialog` so that
|
|
4071
|
+
* consumers import from our barrel and the token identity matches
|
|
4072
|
+
* exactly what CDK's `Dialog` service provides in the injector.
|
|
4073
|
+
* This means `@Inject(DIALOG_DATA)` works without any extra wiring.
|
|
4074
|
+
*
|
|
4075
|
+
* • `DIALOG_GLOBAL_CONFIG` — Optional app-level defaults, same pattern
|
|
4076
|
+
* as before.
|
|
4077
|
+
*
|
|
4078
|
+
* • `INTERNAL_DIALOG_CONFIG` — Internal-only token used to pass our
|
|
4079
|
+
* full `DialogConfig` (including animation fields) to the custom
|
|
4080
|
+
* `DialogContainerComponent`. Not part of the public API.
|
|
4081
|
+
*/
|
|
4082
|
+
// ──── Public ─────────────────────────────────────────────────────────────
|
|
4083
|
+
/**
|
|
4084
|
+
* Injection token used to pass arbitrary data into a dialog component.
|
|
4085
|
+
*
|
|
4086
|
+
* This is CDK's own `DIALOG_DATA` token — re-exported so that imports
|
|
4087
|
+
* from our barrel (`shared/components/dialog`) resolve to the exact same
|
|
4088
|
+
* token that CDK's `Dialog` service provides.
|
|
4089
|
+
*
|
|
4090
|
+
* Usage inside a dialog component:
|
|
4091
|
+
* ```ts
|
|
4092
|
+
* constructor(@Inject(DIALOG_DATA) public data: MyDataType) {}
|
|
4093
|
+
* ```
|
|
4094
|
+
*/
|
|
4095
|
+
const DIALOG_DATA = DIALOG_DATA$1;
|
|
4096
|
+
/**
|
|
4097
|
+
* Optional token for providing global dialog defaults at the
|
|
4098
|
+
* application level.
|
|
4099
|
+
*
|
|
4100
|
+
* Usage in `app.config.ts` or a module's `providers` array:
|
|
4101
|
+
* ```ts
|
|
4102
|
+
* providers: [
|
|
4103
|
+
* { provide: DIALOG_GLOBAL_CONFIG, useValue: { animation: 'zoom', width: '600px' } }
|
|
4104
|
+
* ]
|
|
4105
|
+
* ```
|
|
4106
|
+
*/
|
|
4107
|
+
const DIALOG_GLOBAL_CONFIG = new InjectionToken('DIALOG_GLOBAL_CONFIG');
|
|
4108
|
+
// ──── Internal ───────────────────────────────────────────────────────────
|
|
4109
|
+
/**
|
|
4110
|
+
* Internal token that carries our full `DialogConfig` (with animation
|
|
4111
|
+
* settings, position offsets, etc.) into the `DialogContainerComponent`.
|
|
4112
|
+
*
|
|
4113
|
+
* @internal — not exported from the barrel; do not depend on it.
|
|
4114
|
+
*/
|
|
4115
|
+
const INTERNAL_DIALOG_CONFIG = new InjectionToken('INTERNAL_DIALOG_CONFIG');
|
|
4116
|
+
|
|
4117
|
+
/**
|
|
4118
|
+
* Dialog Animations
|
|
4119
|
+
*
|
|
4120
|
+
* Architecture Decision:
|
|
4121
|
+
* ─────────────────────
|
|
4122
|
+
* Animations are implemented via the Web Animations API (WAAPI) instead of
|
|
4123
|
+
* Angular's @angular/animations so that:
|
|
4124
|
+
* 1. The dialog container stays truly standalone — no BrowserAnimationsModule
|
|
4125
|
+
* dependency is required.
|
|
4126
|
+
* 2. We have full programmatic control over timing, easing, and cleanup.
|
|
4127
|
+
* 3. Future presets can be added without touching the component template.
|
|
4128
|
+
*
|
|
4129
|
+
* Each preset exports an `enter` and `leave` keyframe array plus a
|
|
4130
|
+
* recommended timing object. The DialogContainerComponent plays these
|
|
4131
|
+
* via `element.animate()`.
|
|
4132
|
+
*/
|
|
4133
|
+
/**
|
|
4134
|
+
* Build enter/leave WAAPI keyframes for the dialog **panel** element.
|
|
4135
|
+
*/
|
|
4136
|
+
function getDialogPanelAnimation(preset, enterDuration, leaveDuration) {
|
|
4137
|
+
const easeEnter = 'cubic-bezier(0.0, 0.0, 0.2, 1)'; // decelerate
|
|
4138
|
+
const easeLeave = 'cubic-bezier(0.4, 0.0, 1, 1)'; // accelerate
|
|
4139
|
+
switch (preset) {
|
|
4140
|
+
// ─── Fade ────────────────────────────────────────────────────
|
|
4141
|
+
case 'fade':
|
|
4142
|
+
return {
|
|
4143
|
+
enter: {
|
|
4144
|
+
keyframes: [
|
|
4145
|
+
{ opacity: 0, transform: 'scale(0.95)' },
|
|
4146
|
+
{ opacity: 1, transform: 'scale(1)' },
|
|
4147
|
+
],
|
|
4148
|
+
options: { duration: enterDuration, easing: easeEnter, fill: 'forwards' },
|
|
4149
|
+
},
|
|
4150
|
+
leave: {
|
|
4151
|
+
keyframes: [
|
|
4152
|
+
{ opacity: 1, transform: 'scale(1)' },
|
|
4153
|
+
{ opacity: 0, transform: 'scale(0.95)' },
|
|
4154
|
+
],
|
|
4155
|
+
options: { duration: leaveDuration, easing: easeLeave, fill: 'forwards' },
|
|
4156
|
+
},
|
|
4157
|
+
};
|
|
4158
|
+
// ─── Zoom ────────────────────────────────────────────────────
|
|
4159
|
+
case 'zoom':
|
|
4160
|
+
return {
|
|
4161
|
+
enter: {
|
|
4162
|
+
keyframes: [
|
|
4163
|
+
{ opacity: 0, transform: 'scale(0.5)' },
|
|
4164
|
+
{ opacity: 1, transform: 'scale(1)' },
|
|
4165
|
+
],
|
|
4166
|
+
options: { duration: enterDuration, easing: easeEnter, fill: 'forwards' },
|
|
4167
|
+
},
|
|
4168
|
+
leave: {
|
|
4169
|
+
keyframes: [
|
|
4170
|
+
{ opacity: 1, transform: 'scale(1)' },
|
|
4171
|
+
{ opacity: 0, transform: 'scale(0.5)' },
|
|
4172
|
+
],
|
|
4173
|
+
options: { duration: leaveDuration, easing: easeLeave, fill: 'forwards' },
|
|
4174
|
+
},
|
|
4175
|
+
};
|
|
4176
|
+
// ─── Slide Top ───────────────────────────────────────────────
|
|
4177
|
+
case 'slide-top':
|
|
4178
|
+
return {
|
|
4179
|
+
enter: {
|
|
4180
|
+
keyframes: [
|
|
4181
|
+
{ opacity: 0, transform: 'translateY(-40px)' },
|
|
4182
|
+
{ opacity: 1, transform: 'translateY(0)' },
|
|
4183
|
+
],
|
|
4184
|
+
options: { duration: enterDuration, easing: easeEnter, fill: 'forwards' },
|
|
4185
|
+
},
|
|
4186
|
+
leave: {
|
|
4187
|
+
keyframes: [
|
|
4188
|
+
{ opacity: 1, transform: 'translateY(0)' },
|
|
4189
|
+
{ opacity: 0, transform: 'translateY(-40px)' },
|
|
4190
|
+
],
|
|
4191
|
+
options: { duration: leaveDuration, easing: easeLeave, fill: 'forwards' },
|
|
4192
|
+
},
|
|
4193
|
+
};
|
|
4194
|
+
// ─── Slide Bottom ────────────────────────────────────────────
|
|
4195
|
+
case 'slide-bottom':
|
|
4196
|
+
return {
|
|
4197
|
+
enter: {
|
|
4198
|
+
keyframes: [
|
|
4199
|
+
{ opacity: 0, transform: 'translateY(40px)' },
|
|
4200
|
+
{ opacity: 1, transform: 'translateY(0)' },
|
|
4201
|
+
],
|
|
4202
|
+
options: { duration: enterDuration, easing: easeEnter, fill: 'forwards' },
|
|
4203
|
+
},
|
|
4204
|
+
leave: {
|
|
4205
|
+
keyframes: [
|
|
4206
|
+
{ opacity: 1, transform: 'translateY(0)' },
|
|
4207
|
+
{ opacity: 0, transform: 'translateY(40px)' },
|
|
4208
|
+
],
|
|
4209
|
+
options: { duration: leaveDuration, easing: easeLeave, fill: 'forwards' },
|
|
4210
|
+
},
|
|
4211
|
+
};
|
|
4212
|
+
// ─── Slide Left ──────────────────────────────────────────────
|
|
4213
|
+
case 'slide-left':
|
|
4214
|
+
return {
|
|
4215
|
+
enter: {
|
|
4216
|
+
keyframes: [
|
|
4217
|
+
{ opacity: 0, transform: 'translateX(-40px)' },
|
|
4218
|
+
{ opacity: 1, transform: 'translateX(0)' },
|
|
4219
|
+
],
|
|
4220
|
+
options: { duration: enterDuration, easing: easeEnter, fill: 'forwards' },
|
|
4221
|
+
},
|
|
4222
|
+
leave: {
|
|
4223
|
+
keyframes: [
|
|
4224
|
+
{ opacity: 1, transform: 'translateX(0)' },
|
|
4225
|
+
{ opacity: 0, transform: 'translateX(-40px)' },
|
|
4226
|
+
],
|
|
4227
|
+
options: { duration: leaveDuration, easing: easeLeave, fill: 'forwards' },
|
|
4228
|
+
},
|
|
4229
|
+
};
|
|
4230
|
+
// ─── Slide Right ─────────────────────────────────────────────
|
|
4231
|
+
case 'slide-right':
|
|
4232
|
+
return {
|
|
4233
|
+
enter: {
|
|
4234
|
+
keyframes: [
|
|
4235
|
+
{ opacity: 0, transform: 'translateX(40px)' },
|
|
4236
|
+
{ opacity: 1, transform: 'translateX(0)' },
|
|
4237
|
+
],
|
|
4238
|
+
options: { duration: enterDuration, easing: easeEnter, fill: 'forwards' },
|
|
4239
|
+
},
|
|
4240
|
+
leave: {
|
|
4241
|
+
keyframes: [
|
|
4242
|
+
{ opacity: 1, transform: 'translateX(0)' },
|
|
4243
|
+
{ opacity: 0, transform: 'translateX(40px)' },
|
|
4244
|
+
],
|
|
4245
|
+
options: { duration: leaveDuration, easing: easeLeave, fill: 'forwards' },
|
|
4246
|
+
},
|
|
4247
|
+
};
|
|
4248
|
+
// ─── None ────────────────────────────────────────────────────
|
|
4249
|
+
case 'none':
|
|
4250
|
+
default:
|
|
4251
|
+
return {
|
|
4252
|
+
enter: {
|
|
4253
|
+
keyframes: [{ opacity: 1 }],
|
|
4254
|
+
options: { duration: 0, fill: 'forwards' },
|
|
4255
|
+
},
|
|
4256
|
+
leave: {
|
|
4257
|
+
keyframes: [{ opacity: 0 }],
|
|
4258
|
+
options: { duration: 0, fill: 'forwards' },
|
|
4259
|
+
},
|
|
4260
|
+
};
|
|
4261
|
+
}
|
|
4262
|
+
}
|
|
4263
|
+
/**
|
|
4264
|
+
* Build enter/leave WAAPI keyframes for the **backdrop** element.
|
|
4265
|
+
* The backdrop always uses a simple opacity fade regardless of the
|
|
4266
|
+
* panel animation preset.
|
|
4267
|
+
*/
|
|
4268
|
+
function getDialogBackdropAnimation(enterDuration, leaveDuration) {
|
|
4269
|
+
return {
|
|
4270
|
+
enter: {
|
|
4271
|
+
keyframes: [{ opacity: 0 }, { opacity: 1 }],
|
|
4272
|
+
options: { duration: enterDuration, easing: 'ease-out', fill: 'forwards' },
|
|
4273
|
+
},
|
|
4274
|
+
leave: {
|
|
4275
|
+
keyframes: [{ opacity: 1 }, { opacity: 0 }],
|
|
4276
|
+
options: { duration: leaveDuration, easing: 'ease-in', fill: 'forwards' },
|
|
4277
|
+
},
|
|
4278
|
+
};
|
|
4279
|
+
}
|
|
4280
|
+
|
|
4281
|
+
/**
|
|
4282
|
+
* DialogContainerComponent
|
|
4283
|
+
*
|
|
4284
|
+
* Architecture Decision:
|
|
4285
|
+
* ─────────────────────
|
|
4286
|
+
* Extends CDK's `CdkDialogContainer` — the headless base class that
|
|
4287
|
+
* provides:
|
|
4288
|
+
* • Focus-trap management (tab key stays inside the dialog).
|
|
4289
|
+
* • Auto-focus / restore-focus behaviour.
|
|
4290
|
+
* • Portal outlet (`<ng-template cdkPortalOutlet />`) for dynamic
|
|
4291
|
+
* component projection.
|
|
4292
|
+
* • ARIA attribute management on the host element.
|
|
4293
|
+
*
|
|
4294
|
+
* We add:
|
|
4295
|
+
* • WAAPI enter / leave animations on the panel (host element) and
|
|
4296
|
+
* the CDK backdrop sibling.
|
|
4297
|
+
* • Flex-column styles on the dynamically created component host so
|
|
4298
|
+
* `.bk-dialog-content` scrolls and `.bk-dialog-actions` stays pinned.
|
|
4299
|
+
* • Position-offset application via CSS margin.
|
|
4300
|
+
*
|
|
4301
|
+
* This component is **never** used directly — it is created internally
|
|
4302
|
+
* by `DialogService` via CDK's `Dialog.open()`.
|
|
4303
|
+
*/
|
|
4304
|
+
class DialogContainerComponent extends CdkDialogContainer {
|
|
4305
|
+
/**
|
|
4306
|
+
* Our full config (including animation fields).
|
|
4307
|
+
* Provided by DialogService via the INTERNAL_DIALOG_CONFIG token.
|
|
4308
|
+
*/
|
|
4309
|
+
_dialogConfig = inject(INTERNAL_DIALOG_CONFIG);
|
|
4310
|
+
// ──── Opened promise ─────────────────────────────────────────────────
|
|
4311
|
+
/**
|
|
4312
|
+
* Resolves when the enter animation finishes (or immediately for 'none').
|
|
4313
|
+
* `DialogService` subscribes via `.then()` to emit `afterOpened` on the
|
|
4314
|
+
* `DialogRef`.
|
|
4315
|
+
*/
|
|
4316
|
+
_resolveOpened;
|
|
4317
|
+
opened = new Promise(resolve => {
|
|
4318
|
+
this._resolveOpened = resolve;
|
|
4319
|
+
});
|
|
4320
|
+
// ──── Lifecycle ──────────────────────────────────────────────────────
|
|
4321
|
+
ngOnInit() {
|
|
4322
|
+
this._playEnterAnimation();
|
|
4323
|
+
this._applyPositionOffsets();
|
|
4324
|
+
}
|
|
4325
|
+
// ──── Portal override ────────────────────────────────────────────────
|
|
4326
|
+
/**
|
|
4327
|
+
* Override the CDK base to apply flex-column layout on the dynamically
|
|
4328
|
+
* created component's host element.
|
|
4329
|
+
*
|
|
4330
|
+
* Angular's emulated view encapsulation prevents scoped CSS from reaching
|
|
4331
|
+
* dynamically-projected elements, so we set styles programmatically.
|
|
4332
|
+
*/
|
|
4333
|
+
attachComponentPortal(portal) {
|
|
4334
|
+
const ref = super.attachComponentPortal(portal);
|
|
4335
|
+
const el = ref.location.nativeElement;
|
|
4336
|
+
el.style.display = 'flex';
|
|
4337
|
+
el.style.flexDirection = 'column';
|
|
4338
|
+
el.style.flex = '1 1 auto';
|
|
4339
|
+
el.style.minHeight = '0';
|
|
4340
|
+
el.style.overflow = 'hidden';
|
|
4341
|
+
return ref;
|
|
4342
|
+
}
|
|
4343
|
+
// ──── WAAPI Animations ───────────────────────────────────────────────
|
|
4344
|
+
/**
|
|
4345
|
+
* Play the enter animation on the panel (host element) and backdrop.
|
|
4346
|
+
* Resolves the `opened` promise when the panel animation finishes.
|
|
4347
|
+
*/
|
|
4348
|
+
_playEnterAnimation() {
|
|
4349
|
+
const preset = this._dialogConfig.animation ?? 'fade';
|
|
4350
|
+
if (preset === 'none') {
|
|
4351
|
+
this._resolveOpened();
|
|
4352
|
+
return;
|
|
4353
|
+
}
|
|
4354
|
+
const enterDur = this._dialogConfig.animationDurationEnter ?? 200;
|
|
4355
|
+
const leaveDur = this._dialogConfig.animationDurationLeave ?? 150;
|
|
4356
|
+
// ── Panel animation ──
|
|
4357
|
+
const panelAnim = getDialogPanelAnimation(preset, enterDur, leaveDur);
|
|
4358
|
+
const anim = this._elementRef.nativeElement.animate(panelAnim.enter.keyframes, panelAnim.enter.options);
|
|
4359
|
+
// ── Backdrop animation ──
|
|
4360
|
+
const backdropEl = this._getBackdropElement();
|
|
4361
|
+
if (backdropEl) {
|
|
4362
|
+
// Override CDK's CSS transition so our WAAPI timing wins.
|
|
4363
|
+
backdropEl.style.transition = 'none';
|
|
4364
|
+
backdropEl.style.backgroundColor = 'var(--dialog-backdrop-bg, rgba(0, 0, 0, 0.5))';
|
|
4365
|
+
const bdAnim = getDialogBackdropAnimation(enterDur, leaveDur);
|
|
4366
|
+
backdropEl.animate(bdAnim.enter.keyframes, bdAnim.enter.options);
|
|
4367
|
+
}
|
|
4368
|
+
anim.onfinish = () => this._resolveOpened();
|
|
4369
|
+
}
|
|
4370
|
+
/**
|
|
4371
|
+
* Play the leave animation. Returns a Promise that resolves when done.
|
|
4372
|
+
* Called by `DialogRef._runCloseSequence()` before CDK tears down the
|
|
4373
|
+
* overlay.
|
|
4374
|
+
*/
|
|
4375
|
+
playLeaveAnimation() {
|
|
4376
|
+
const preset = this._dialogConfig.animation ?? 'fade';
|
|
4377
|
+
if (preset === 'none')
|
|
4378
|
+
return Promise.resolve();
|
|
4379
|
+
const enterDur = this._dialogConfig.animationDurationEnter ?? 200;
|
|
4380
|
+
const leaveDur = this._dialogConfig.animationDurationLeave ?? 150;
|
|
4381
|
+
return new Promise(resolve => {
|
|
4382
|
+
// ── Panel ──
|
|
4383
|
+
const panelAnim = getDialogPanelAnimation(preset, enterDur, leaveDur);
|
|
4384
|
+
const anim = this._elementRef.nativeElement.animate(panelAnim.leave.keyframes, panelAnim.leave.options);
|
|
4385
|
+
// ── Backdrop ──
|
|
4386
|
+
const backdropEl = this._getBackdropElement();
|
|
4387
|
+
if (backdropEl) {
|
|
4388
|
+
const bdAnim = getDialogBackdropAnimation(enterDur, leaveDur);
|
|
4389
|
+
backdropEl.animate(bdAnim.leave.keyframes, bdAnim.leave.options);
|
|
4390
|
+
}
|
|
4391
|
+
anim.onfinish = () => resolve();
|
|
4392
|
+
// Safety net — resolve even if onfinish never fires (SSR, etc.).
|
|
4393
|
+
setTimeout(() => resolve(), leaveDur + 50);
|
|
4394
|
+
});
|
|
4395
|
+
}
|
|
4396
|
+
// ──── Position Offsets ───────────────────────────────────────────────
|
|
4397
|
+
/**
|
|
4398
|
+
* Apply explicit position offsets via CSS margin.
|
|
4399
|
+
* These are additive to CDK's `GlobalPositionStrategy` alignment.
|
|
4400
|
+
*/
|
|
4401
|
+
_applyPositionOffsets() {
|
|
4402
|
+
const pos = this._dialogConfig.position;
|
|
4403
|
+
if (!pos)
|
|
4404
|
+
return;
|
|
4405
|
+
const el = this._elementRef.nativeElement;
|
|
4406
|
+
if (pos.top)
|
|
4407
|
+
el.style.marginTop = pos.top;
|
|
4408
|
+
if (pos.bottom)
|
|
4409
|
+
el.style.marginBottom = pos.bottom;
|
|
4410
|
+
if (pos.left)
|
|
4411
|
+
el.style.marginLeft = pos.left;
|
|
4412
|
+
if (pos.right)
|
|
4413
|
+
el.style.marginRight = pos.right;
|
|
4414
|
+
}
|
|
4415
|
+
// ──── Helpers ────────────────────────────────────────────────────────
|
|
4416
|
+
/**
|
|
4417
|
+
* CDK places the backdrop as a sibling of `.cdk-overlay-pane`.
|
|
4418
|
+
* Walk up from our host → pane → wrapper, then query for the backdrop.
|
|
4419
|
+
*/
|
|
4420
|
+
_getBackdropElement() {
|
|
4421
|
+
const pane = this._elementRef.nativeElement.closest('.cdk-overlay-pane');
|
|
4422
|
+
return pane?.parentElement?.querySelector('.cdk-overlay-backdrop') ?? null;
|
|
4423
|
+
}
|
|
4424
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: DialogContainerComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
|
|
4425
|
+
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"] }] });
|
|
4426
|
+
}
|
|
4427
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: DialogContainerComponent, decorators: [{
|
|
4428
|
+
type: Component,
|
|
4429
|
+
args: [{ selector: 'bk-dialog-container', standalone: true, imports: [CdkPortalOutlet], host: {
|
|
4430
|
+
'class': 'bk-dialog-container',
|
|
4431
|
+
}, 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"] }]
|
|
4432
|
+
}] });
|
|
4433
|
+
|
|
4434
|
+
/**
|
|
4435
|
+
* DialogRef — Handle returned to callers by `DialogService.open()`.
|
|
4436
|
+
*
|
|
4437
|
+
* Architecture Decision:
|
|
4438
|
+
* ─────────────────────
|
|
4439
|
+
* Wraps CDK's `DialogRef` to add:
|
|
4440
|
+
* • WAAPI leave-animation before CDK tears down the overlay.
|
|
4441
|
+
* • Per-instance `afterOpened()` observable (CDK only has a service-level
|
|
4442
|
+
* `afterOpened` Subject).
|
|
4443
|
+
* • The same familiar API shape used by callers (`close`, `afterClosed`,
|
|
4444
|
+
* `backdropClick`, `keydownEvents`, `componentInstance`).
|
|
4445
|
+
*
|
|
4446
|
+
* CDK's `DialogRef<R, C>` has Result first, Component second.
|
|
4447
|
+
* Ours keeps `<T, R>` (Component first, Result second) to match the
|
|
4448
|
+
* convention consumers already use.
|
|
4449
|
+
*/
|
|
4450
|
+
class DialogRef {
|
|
4451
|
+
_cdkRef;
|
|
4452
|
+
/** Unique dialog identifier (managed by CDK). */
|
|
4453
|
+
id;
|
|
4454
|
+
/** Instance of the component rendered inside the dialog. */
|
|
4455
|
+
componentInstance;
|
|
4456
|
+
// ──── Internal wiring (set by DialogService) ──────────────────────────
|
|
4457
|
+
/** @internal Container reference for leave animation. */
|
|
4458
|
+
_containerInstance;
|
|
4459
|
+
/** @internal Prevent double-close. */
|
|
4460
|
+
_closing = false;
|
|
4461
|
+
/** @internal Per-instance afterOpened Subject. */
|
|
4462
|
+
_afterOpened$ = new Subject();
|
|
4463
|
+
constructor(
|
|
4464
|
+
/**
|
|
4465
|
+
* @internal Wrapped CDK ref.
|
|
4466
|
+
* Typed as `CdkDialogRef<any>` to avoid deep generic-variance issues
|
|
4467
|
+
* that arise from CDK's `config.providers` callback signature.
|
|
4468
|
+
* The public API (`afterClosed`, etc.) re-types the observables.
|
|
4469
|
+
*/
|
|
4470
|
+
_cdkRef) {
|
|
4471
|
+
this._cdkRef = _cdkRef;
|
|
4472
|
+
this.id = _cdkRef.id;
|
|
4473
|
+
}
|
|
4474
|
+
// ──── Public API ──────────────────────────────────────────────────────
|
|
4475
|
+
/**
|
|
4476
|
+
* Close the dialog, optionally returning a result.
|
|
4477
|
+
* Plays the WAAPI leave animation, then delegates to CDK for cleanup.
|
|
4478
|
+
*/
|
|
4479
|
+
close(result) {
|
|
4480
|
+
if (this._closing)
|
|
4481
|
+
return;
|
|
4482
|
+
this._closing = true;
|
|
4483
|
+
this._runCloseSequence(result);
|
|
4484
|
+
}
|
|
4485
|
+
/**
|
|
4486
|
+
* Observable that emits (and completes) once after the dialog has been
|
|
4487
|
+
* removed from the DOM and all cleanup is done.
|
|
4488
|
+
*/
|
|
4489
|
+
afterClosed() {
|
|
4490
|
+
return this._cdkRef.closed;
|
|
4491
|
+
}
|
|
4492
|
+
/**
|
|
4493
|
+
* Observable that emits (and completes) when the enter animation
|
|
4494
|
+
* finishes and the dialog is fully visible.
|
|
4495
|
+
*/
|
|
4496
|
+
afterOpened() {
|
|
4497
|
+
return this._afterOpened$.asObservable();
|
|
4498
|
+
}
|
|
4499
|
+
/**
|
|
4500
|
+
* Observable of backdrop / outside-pointer click events.
|
|
4501
|
+
*/
|
|
4502
|
+
backdropClick() {
|
|
4503
|
+
return this._cdkRef.outsidePointerEvents;
|
|
4504
|
+
}
|
|
4505
|
+
/**
|
|
4506
|
+
* Observable of keyboard events dispatched while this dialog is open.
|
|
4507
|
+
*/
|
|
4508
|
+
keydownEvents() {
|
|
4509
|
+
return this._cdkRef.keydownEvents;
|
|
4510
|
+
}
|
|
4511
|
+
// ──── Internal helpers ────────────────────────────────────────────────
|
|
4512
|
+
/** @internal Called by the container once the enter animation finishes. */
|
|
4513
|
+
_emitOpened() {
|
|
4514
|
+
this._afterOpened$.next();
|
|
4515
|
+
this._afterOpened$.complete();
|
|
4516
|
+
}
|
|
4517
|
+
/** Play leave animation, then close via CDK. */
|
|
4518
|
+
async _runCloseSequence(result) {
|
|
4519
|
+
try {
|
|
4520
|
+
await this._containerInstance.playLeaveAnimation();
|
|
4521
|
+
}
|
|
4522
|
+
catch {
|
|
4523
|
+
// Animation may fail in SSR / test — proceed anyway.
|
|
4524
|
+
}
|
|
4525
|
+
this._cdkRef.close(result);
|
|
4526
|
+
}
|
|
4527
|
+
}
|
|
4528
|
+
|
|
4529
|
+
/**
|
|
4530
|
+
* Dialog Configuration
|
|
4531
|
+
*
|
|
4532
|
+
* Architecture Decision:
|
|
4533
|
+
* ─────────────────────
|
|
4534
|
+
* Our `DialogConfig` mirrors the fields from CDK's `DialogConfig` that we
|
|
4535
|
+
* expose, plus adds custom animation properties powered by WAAPI.
|
|
4536
|
+
*
|
|
4537
|
+
* When `DialogService.open()` is called these values are:
|
|
4538
|
+
* 1. Merged with global defaults and per-call overrides.
|
|
4539
|
+
* 2. Mapped onto CDK's native `DialogConfig` for overlay, backdrop, scroll,
|
|
4540
|
+
* position, ARIA, and focus-trap management.
|
|
4541
|
+
* 3. Passed to `DialogContainerComponent` via an internal token for
|
|
4542
|
+
* animation playback and position offsets.
|
|
4543
|
+
*
|
|
4544
|
+
* CDK handles: unique IDs, z-index stacking, scroll blocking, focus trap,
|
|
4545
|
+
* backdrop rendering, overlay positioning, keyboard events.
|
|
4546
|
+
* We handle: WAAPI animations, panel appearance, convenience config sugar.
|
|
4547
|
+
*/
|
|
4548
|
+
// ──── Defaults ───────────────────────────────────────────────────────────
|
|
4549
|
+
const DEFAULT_DIALOG_CONFIG = {
|
|
4550
|
+
id: undefined,
|
|
4551
|
+
width: undefined,
|
|
4552
|
+
height: undefined,
|
|
4553
|
+
minWidth: undefined,
|
|
4554
|
+
minHeight: undefined,
|
|
4555
|
+
maxWidth: '90vw',
|
|
4556
|
+
maxHeight: '90vh',
|
|
4557
|
+
disableClose: false,
|
|
4558
|
+
closeOnBackdrop: true,
|
|
4559
|
+
closeOnEsc: true,
|
|
4560
|
+
hasBackdrop: true,
|
|
4561
|
+
lockScroll: true,
|
|
4562
|
+
panelClass: '',
|
|
4563
|
+
backdropClass: '',
|
|
4564
|
+
data: undefined,
|
|
4565
|
+
animation: 'fade',
|
|
4566
|
+
animationDurationEnter: 200,
|
|
4567
|
+
animationDurationLeave: 150,
|
|
4568
|
+
position: undefined,
|
|
4569
|
+
role: 'dialog',
|
|
4570
|
+
ariaLabel: undefined,
|
|
4571
|
+
ariaLabelledBy: undefined,
|
|
4572
|
+
ariaDescribedBy: undefined,
|
|
4573
|
+
autoFocus: true,
|
|
4574
|
+
restoreFocus: true,
|
|
4575
|
+
scrollStrategy: undefined,
|
|
4576
|
+
};
|
|
4577
|
+
|
|
4578
|
+
/**
|
|
4579
|
+
* DialogService — The core engine of the custom dialog system.
|
|
4580
|
+
*
|
|
4581
|
+
* Architecture Decision:
|
|
4582
|
+
* ─────────────────────
|
|
4583
|
+
* Built on top of Angular CDK's `Dialog` service (the same foundation
|
|
4584
|
+
* that `MatDialog` uses internally):
|
|
4585
|
+
*
|
|
4586
|
+
* 1. CDK creates the overlay, backdrop, scroll-blocking, focus-trap,
|
|
4587
|
+
* z-index stacking, and unique ID management — battle-tested infra
|
|
4588
|
+
* shared with every Angular Material dialog in the ecosystem.
|
|
4589
|
+
*
|
|
4590
|
+
* 2. We provide our own `DialogContainerComponent` (extending
|
|
4591
|
+
* `CdkDialogContainer`) for WAAPI animations and panel styling.
|
|
4592
|
+
*
|
|
4593
|
+
* 3. We wrap CDK's `DialogRef` in our own `DialogRef` to add the
|
|
4594
|
+
* leave-animation step before CDK tears down the overlay, and to
|
|
4595
|
+
* expose the same familiar API shape (`afterClosed()`, etc.).
|
|
4596
|
+
*
|
|
4597
|
+
* 4. Configuration is merged DEFAULT → GLOBAL → per-call, then mapped
|
|
4598
|
+
* to CDK's native config with our extras carried via an internal
|
|
4599
|
+
* injection token.
|
|
4600
|
+
*
|
|
4601
|
+
* What CDK handles for us (no custom code needed):
|
|
4602
|
+
* ─────────────────────────────────────────────────
|
|
4603
|
+
* ✓ Unique dialog IDs (throws on collision)
|
|
4604
|
+
* ✓ Z-index stacking via `.cdk-overlay-container`
|
|
4605
|
+
* ✓ Focus trap (tab key stays inside dialog)
|
|
4606
|
+
* ✓ Auto-focus / restore-focus
|
|
4607
|
+
* ✓ Scroll blocking (BlockScrollStrategy)
|
|
4608
|
+
* ✓ Backdrop rendering and click detection
|
|
4609
|
+
* ✓ Keyboard event forwarding
|
|
4610
|
+
* ✓ Overlay DOM lifecycle (create → attach → detach → dispose)
|
|
4611
|
+
*
|
|
4612
|
+
* Memory safety:
|
|
4613
|
+
* ─────────────
|
|
4614
|
+
* CDK manages the full lifecycle: on close it detaches the overlay,
|
|
4615
|
+
* destroys the container, and disposes the overlay ref.
|
|
4616
|
+
* Our DialogRef subjects complete via CDK's `closed` observable,
|
|
4617
|
+
* preventing lingering subscriptions.
|
|
4618
|
+
*/
|
|
4619
|
+
class DialogService {
|
|
4620
|
+
_cdkDialog = inject(Dialog);
|
|
4621
|
+
_overlay = inject(Overlay);
|
|
4622
|
+
_globalConfig = inject(DIALOG_GLOBAL_CONFIG, { optional: true });
|
|
4623
|
+
/** Stack of currently open dialog refs (most recent = last). */
|
|
4624
|
+
_openDialogRefs = [];
|
|
4625
|
+
// ════════════════════════════════════════════════════════════════════
|
|
4626
|
+
// Public API
|
|
4627
|
+
// ════════════════════════════════════════════════════════════════════
|
|
4628
|
+
/**
|
|
4629
|
+
* Open a dialog containing the given component.
|
|
4630
|
+
*
|
|
4631
|
+
* @param component The component class to render inside the dialog.
|
|
4632
|
+
* @param config Optional per-dialog configuration (merged on top
|
|
4633
|
+
* of global and default settings).
|
|
4634
|
+
* @returns A strongly-typed `DialogRef` to interact with.
|
|
4635
|
+
*
|
|
4636
|
+
* @example
|
|
4637
|
+
* ```ts
|
|
4638
|
+
* const ref = this.dialog.open(UserFormComponent, {
|
|
4639
|
+
* width: '500px',
|
|
4640
|
+
* data: { userId: 10 },
|
|
4641
|
+
* });
|
|
4642
|
+
* ref.afterClosed().subscribe(result => console.log(result));
|
|
4643
|
+
* ```
|
|
4644
|
+
*/
|
|
4645
|
+
open(component, config) {
|
|
4646
|
+
// ── 1. Merge configs: DEFAULT ← GLOBAL ← per-call ──────────────
|
|
4647
|
+
const mergedConfig = {
|
|
4648
|
+
...DEFAULT_DIALOG_CONFIG,
|
|
4649
|
+
...(this._globalConfig ?? {}),
|
|
4650
|
+
...(config ?? {}),
|
|
4651
|
+
};
|
|
4652
|
+
// ── 2. Prepare the return ref (set inside providers callback) ───
|
|
4653
|
+
let ourRef;
|
|
4654
|
+
// ── 3. Build CDK-native config ──────────────────────────────────
|
|
4655
|
+
const cdkConfig = {
|
|
4656
|
+
// Identity
|
|
4657
|
+
id: mergedConfig.id,
|
|
4658
|
+
// Sizing (applied on .cdk-overlay-pane)
|
|
4659
|
+
width: mergedConfig.width,
|
|
4660
|
+
height: mergedConfig.height,
|
|
4661
|
+
minWidth: mergedConfig.minWidth,
|
|
4662
|
+
minHeight: mergedConfig.minHeight,
|
|
4663
|
+
maxWidth: mergedConfig.maxWidth ?? '90vw',
|
|
4664
|
+
maxHeight: mergedConfig.maxHeight ?? '90vh',
|
|
4665
|
+
// Backdrop
|
|
4666
|
+
hasBackdrop: mergedConfig.hasBackdrop !== false,
|
|
4667
|
+
backdropClass: mergedConfig.backdropClass || 'cdk-overlay-dark-backdrop',
|
|
4668
|
+
// Panel class (always include our base class)
|
|
4669
|
+
panelClass: this._buildPanelClasses(mergedConfig),
|
|
4670
|
+
// Data (CDK provides this as DIALOG_DATA automatically)
|
|
4671
|
+
data: mergedConfig.data,
|
|
4672
|
+
// We manage close ourselves for fine-grained closeOnBackdrop / closeOnEsc.
|
|
4673
|
+
disableClose: true,
|
|
4674
|
+
// Accessibility
|
|
4675
|
+
autoFocus: mergedConfig.autoFocus ?? true,
|
|
4676
|
+
restoreFocus: mergedConfig.restoreFocus ?? true,
|
|
4677
|
+
role: mergedConfig.role ?? 'dialog',
|
|
4678
|
+
ariaLabel: mergedConfig.ariaLabel ?? null,
|
|
4679
|
+
ariaLabelledBy: mergedConfig.ariaLabelledBy ?? null,
|
|
4680
|
+
ariaDescribedBy: mergedConfig.ariaDescribedBy ?? null,
|
|
4681
|
+
// Scroll strategy
|
|
4682
|
+
scrollStrategy: mergedConfig.scrollStrategy ??
|
|
4683
|
+
(mergedConfig.lockScroll !== false
|
|
4684
|
+
? this._overlay.scrollStrategies.block()
|
|
4685
|
+
: this._overlay.scrollStrategies.noop()),
|
|
4686
|
+
// Position strategy (centered by default, shifted by offsets)
|
|
4687
|
+
positionStrategy: this._buildPositionStrategy(mergedConfig),
|
|
4688
|
+
// ── Custom container ──────────────────────────────────────────
|
|
4689
|
+
container: {
|
|
4690
|
+
type: DialogContainerComponent,
|
|
4691
|
+
providers: () => [
|
|
4692
|
+
{ provide: INTERNAL_DIALOG_CONFIG, useValue: mergedConfig },
|
|
4693
|
+
],
|
|
4694
|
+
},
|
|
4695
|
+
// ── Provider callback ─────────────────────────────────────────
|
|
4696
|
+
// Runs after the container is created but before the user
|
|
4697
|
+
// component. We create our `DialogRef` wrapper here and make
|
|
4698
|
+
// it available for injection in the user component.
|
|
4699
|
+
providers: (cdkRef, _cdkConfig, containerInstance) => {
|
|
4700
|
+
ourRef = new DialogRef(cdkRef);
|
|
4701
|
+
ourRef._containerInstance = containerInstance;
|
|
4702
|
+
// Wire up afterOpened emission.
|
|
4703
|
+
containerInstance.opened
|
|
4704
|
+
.then(() => ourRef._emitOpened());
|
|
4705
|
+
return [
|
|
4706
|
+
{ provide: DialogRef, useValue: ourRef },
|
|
4707
|
+
];
|
|
4708
|
+
},
|
|
4709
|
+
};
|
|
4710
|
+
// ── 4. Open via CDK ─────────────────────────────────────────────
|
|
4711
|
+
const cdkRef = this._cdkDialog.open(component, cdkConfig);
|
|
4712
|
+
// ── 5. Link componentInstance ───────────────────────────────────
|
|
4713
|
+
ourRef.componentInstance = cdkRef.componentInstance;
|
|
4714
|
+
// ── 6. Set up close-on-backdrop / close-on-ESC ──────────────────
|
|
4715
|
+
this._setupCloseListeners(ourRef, mergedConfig);
|
|
4716
|
+
// ── 7. Track in stack ───────────────────────────────────────────
|
|
4717
|
+
this._openDialogRefs.push(ourRef);
|
|
4718
|
+
// ── 8. Cleanup on close ─────────────────────────────────────────
|
|
4719
|
+
ourRef.afterClosed().subscribe(() => {
|
|
4720
|
+
const idx = this._openDialogRefs.indexOf(ourRef);
|
|
4721
|
+
if (idx !== -1)
|
|
4722
|
+
this._openDialogRefs.splice(idx, 1);
|
|
4723
|
+
});
|
|
4724
|
+
return ourRef;
|
|
4725
|
+
}
|
|
4726
|
+
/**
|
|
4727
|
+
* Close all currently open dialogs (most recent first).
|
|
4728
|
+
*/
|
|
4729
|
+
closeAll() {
|
|
4730
|
+
for (let i = this._openDialogRefs.length - 1; i >= 0; i--) {
|
|
4731
|
+
this._openDialogRefs[i].close();
|
|
4732
|
+
}
|
|
4733
|
+
}
|
|
4734
|
+
/**
|
|
4735
|
+
* Returns the `DialogRef` of the most recently opened dialog,
|
|
4736
|
+
* or `undefined` if none are open.
|
|
4737
|
+
*/
|
|
4738
|
+
getTopDialog() {
|
|
4739
|
+
return this._openDialogRefs[this._openDialogRefs.length - 1];
|
|
4740
|
+
}
|
|
4741
|
+
/**
|
|
4742
|
+
* Get a dialog by its CDK-managed unique ID.
|
|
4743
|
+
*/
|
|
4744
|
+
getDialogById(id) {
|
|
4745
|
+
return this._openDialogRefs.find(r => r.id === id);
|
|
4746
|
+
}
|
|
4747
|
+
/**
|
|
4748
|
+
* Number of currently open dialogs.
|
|
4749
|
+
*/
|
|
4750
|
+
get openDialogCount() {
|
|
4751
|
+
return this._openDialogRefs.length;
|
|
4752
|
+
}
|
|
4753
|
+
/**
|
|
4754
|
+
* Read-only snapshot of currently open dialog refs.
|
|
4755
|
+
* Used internally by content directives (`BkDialogTitle`, `BkDialogActions`,
|
|
4756
|
+
* `BkDialogClose`) for the DOM-walk fallback when `DialogRef` is not
|
|
4757
|
+
* available via injection (e.g. inside `<ng-template>`).
|
|
4758
|
+
*/
|
|
4759
|
+
get openDialogsRef() {
|
|
4760
|
+
return this._openDialogRefs;
|
|
4761
|
+
}
|
|
4762
|
+
// ════════════════════════════════════════════════════════════════════
|
|
4763
|
+
// Convenience Helpers
|
|
4764
|
+
// ════════════════════════════════════════════════════════════════════
|
|
4765
|
+
/**
|
|
4766
|
+
* Open a simple confirmation dialog.
|
|
4767
|
+
* Resolves `true` if the user confirms, `false` otherwise.
|
|
4768
|
+
*
|
|
4769
|
+
* @example
|
|
4770
|
+
* ```ts
|
|
4771
|
+
* const ref = this.dialog.confirm({
|
|
4772
|
+
* message: 'Delete this item?',
|
|
4773
|
+
* component: ConfirmDialogComponent,
|
|
4774
|
+
* });
|
|
4775
|
+
* ref.afterClosed().subscribe(yes => { if (yes) ... });
|
|
4776
|
+
* ```
|
|
4777
|
+
*/
|
|
4778
|
+
confirm(options) {
|
|
4779
|
+
return this.open(options.component, {
|
|
4780
|
+
width: options.width ?? '400px',
|
|
4781
|
+
disableClose: true,
|
|
4782
|
+
closeOnBackdrop: false,
|
|
4783
|
+
animation: 'fade',
|
|
4784
|
+
data: {
|
|
4785
|
+
title: options.title ?? 'Confirm',
|
|
4786
|
+
message: options.message,
|
|
4787
|
+
btnOkText: options.btnOkText ?? 'Yes',
|
|
4788
|
+
btnCancelText: options.btnCancelText ?? 'No',
|
|
4789
|
+
},
|
|
4790
|
+
});
|
|
4791
|
+
}
|
|
4792
|
+
/**
|
|
4793
|
+
* Open a simple alert dialog.
|
|
4794
|
+
*/
|
|
4795
|
+
alert(options) {
|
|
4796
|
+
return this.open(options.component, {
|
|
4797
|
+
width: options.width ?? '400px',
|
|
4798
|
+
disableClose: true,
|
|
4799
|
+
closeOnBackdrop: false,
|
|
4800
|
+
animation: 'fade',
|
|
4801
|
+
data: {
|
|
4802
|
+
title: options.title ?? 'Alert',
|
|
4803
|
+
message: options.message,
|
|
4804
|
+
btnOkText: options.btnOkText ?? 'OK',
|
|
4805
|
+
},
|
|
4806
|
+
});
|
|
4807
|
+
}
|
|
4808
|
+
// ════════════════════════════════════════════════════════════════════
|
|
4809
|
+
// Private
|
|
4810
|
+
// ════════════════════════════════════════════════════════════════════
|
|
4811
|
+
/**
|
|
4812
|
+
* Subscribe to backdrop-click and ESC events on the CDK ref,
|
|
4813
|
+
* closing the dialog based on our config flags.
|
|
4814
|
+
*
|
|
4815
|
+
* We always set CDK `disableClose: true` so that CDK never auto-closes;
|
|
4816
|
+
* this gives us fine-grained control over `closeOnBackdrop` and
|
|
4817
|
+
* `closeOnEsc` independently.
|
|
4818
|
+
*/
|
|
4819
|
+
_setupCloseListeners(ref, config) {
|
|
4820
|
+
if (config.disableClose)
|
|
4821
|
+
return;
|
|
4822
|
+
// Backdrop click
|
|
4823
|
+
if (config.closeOnBackdrop !== false) {
|
|
4824
|
+
ref.backdropClick().subscribe(() => ref.close());
|
|
4825
|
+
}
|
|
4826
|
+
// ESC key
|
|
4827
|
+
if (config.closeOnEsc !== false) {
|
|
4828
|
+
ref.keydownEvents()
|
|
4829
|
+
.pipe(filter((e) => e.key === 'Escape' || e.key === 'Esc'))
|
|
4830
|
+
.subscribe((e) => {
|
|
4831
|
+
e.preventDefault();
|
|
4832
|
+
ref.close();
|
|
4833
|
+
});
|
|
4834
|
+
}
|
|
4835
|
+
}
|
|
4836
|
+
/**
|
|
4837
|
+
* Build the CSS classes for the CDK overlay pane.
|
|
4838
|
+
* Always includes our base class; adds user-provided classes on top.
|
|
4839
|
+
*/
|
|
4840
|
+
_buildPanelClasses(config) {
|
|
4841
|
+
const classes = ['bk-dialog-pane'];
|
|
4842
|
+
if (config.panelClass) {
|
|
4843
|
+
if (Array.isArray(config.panelClass)) {
|
|
4844
|
+
classes.push(...config.panelClass);
|
|
4845
|
+
}
|
|
4846
|
+
else {
|
|
4847
|
+
classes.push(config.panelClass);
|
|
4848
|
+
}
|
|
4849
|
+
}
|
|
4850
|
+
return classes;
|
|
4851
|
+
}
|
|
4852
|
+
/**
|
|
4853
|
+
* Build CDK's `GlobalPositionStrategy` from our position config.
|
|
4854
|
+
* Falls back to centred (both axes) when no position is specified.
|
|
4855
|
+
*/
|
|
4856
|
+
_buildPositionStrategy(config) {
|
|
4857
|
+
const strategy = this._overlay.position().global();
|
|
4858
|
+
if (config.position?.top) {
|
|
4859
|
+
strategy.top(config.position.top);
|
|
4860
|
+
}
|
|
4861
|
+
else if (config.position?.bottom) {
|
|
4862
|
+
strategy.bottom(config.position.bottom);
|
|
4863
|
+
}
|
|
4864
|
+
else {
|
|
4865
|
+
strategy.centerVertically();
|
|
4866
|
+
}
|
|
4867
|
+
if (config.position?.left) {
|
|
4868
|
+
strategy.left(config.position.left);
|
|
4869
|
+
}
|
|
4870
|
+
else if (config.position?.right) {
|
|
4871
|
+
strategy.right(config.position.right);
|
|
4872
|
+
}
|
|
4873
|
+
else {
|
|
4874
|
+
strategy.centerHorizontally();
|
|
4875
|
+
}
|
|
4876
|
+
return strategy;
|
|
4877
|
+
}
|
|
4878
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: DialogService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
4879
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: DialogService, providedIn: 'root' });
|
|
4880
|
+
}
|
|
4881
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: DialogService, decorators: [{
|
|
4882
|
+
type: Injectable,
|
|
4883
|
+
args: [{ providedIn: 'root' }]
|
|
4884
|
+
}] });
|
|
4885
|
+
|
|
4886
|
+
/**
|
|
4887
|
+
* Dialog Content Directives
|
|
4888
|
+
*
|
|
4889
|
+
* Architecture Decision:
|
|
4890
|
+
* ─────────────────────
|
|
4891
|
+
* These directives mirror Angular Material's dialog content directives:
|
|
4892
|
+
*
|
|
4893
|
+
* MatDialogTitle → BkDialogTitle
|
|
4894
|
+
* MatDialogContent → BkDialogContent
|
|
4895
|
+
* MatDialogActions → BkDialogActions
|
|
4896
|
+
* MatDialogClose → BkDialogClose
|
|
4897
|
+
*
|
|
4898
|
+
* Each directive:
|
|
4899
|
+
* 1. Applies the corresponding CSS host class (e.g. `bk-dialog-content`)
|
|
4900
|
+
* that is styled in `dialog-container.css` via `::ng-deep`.
|
|
4901
|
+
* 2. Provides extra accessibility or behavioural features on top of the
|
|
4902
|
+
* plain CSS class.
|
|
4903
|
+
*
|
|
4904
|
+
* Consumers can still use the raw CSS classes directly (without importing
|
|
4905
|
+
* these directives) for backward compatibility, but the directives are
|
|
4906
|
+
* preferred because they add CdkScrollable, ARIA integration, and one-click
|
|
4907
|
+
* close behaviour.
|
|
4908
|
+
*
|
|
4909
|
+
* @see https://github.com/angular/components/blob/main/src/material/dialog/dialog-content-directives.ts
|
|
4910
|
+
*/
|
|
4911
|
+
// ──── ID Generation ──────────────────────────────────────────────────────
|
|
4912
|
+
let nextTitleId = 0;
|
|
4913
|
+
// ──── Helper ─────────────────────────────────────────────────────────────
|
|
4914
|
+
/**
|
|
4915
|
+
* Finds the closest `DialogRef` by walking up the DOM from the given
|
|
4916
|
+
* element to the nearest `bk-dialog-container` host, then matching its
|
|
4917
|
+
* `id` attribute against the currently open dialogs.
|
|
4918
|
+
*
|
|
4919
|
+
* This fallback is necessary when a directive lives inside an
|
|
4920
|
+
* `<ng-template>` (TemplateRef), where the dialog's custom injector is
|
|
4921
|
+
* not available.
|
|
4922
|
+
*
|
|
4923
|
+
* @see Material's `getClosestDialog` in `dialog-content-directives.ts`
|
|
4924
|
+
*/
|
|
4925
|
+
function getClosestDialog(element, openDialogs) {
|
|
4926
|
+
let parent = element.nativeElement.parentElement;
|
|
4927
|
+
while (parent && !parent.classList.contains('bk-dialog-container')) {
|
|
4928
|
+
parent = parent.parentElement;
|
|
4929
|
+
}
|
|
4930
|
+
return parent
|
|
4931
|
+
? openDialogs.find(ref => ref.id === parent.id) ?? null
|
|
4932
|
+
: null;
|
|
4933
|
+
}
|
|
4934
|
+
// ──── Base for Title / Actions ───────────────────────────────────────────
|
|
4935
|
+
/**
|
|
4936
|
+
* Shared abstract base that resolves the owning `DialogRef` (via DI or
|
|
4937
|
+
* DOM walk) and invokes `_onAdd()` / `_onRemove()` lifecycle hooks.
|
|
4938
|
+
*
|
|
4939
|
+
* Same pattern as Material's internal `MatDialogLayoutSection`.
|
|
4940
|
+
*/
|
|
4941
|
+
class BkDialogLayoutSection {
|
|
4942
|
+
_dialogRef = inject(DialogRef, { optional: true });
|
|
4943
|
+
_elementRef = inject((ElementRef));
|
|
4944
|
+
_dialogService = inject(DialogService);
|
|
4945
|
+
ngOnInit() {
|
|
4946
|
+
if (!this._dialogRef) {
|
|
4947
|
+
this._dialogRef = getClosestDialog(this._elementRef, this._dialogService.openDialogsRef);
|
|
4948
|
+
}
|
|
4949
|
+
if (this._dialogRef) {
|
|
4950
|
+
Promise.resolve().then(() => this._onAdd());
|
|
4951
|
+
}
|
|
4952
|
+
}
|
|
4953
|
+
ngOnDestroy() {
|
|
4954
|
+
if (this._dialogRef) {
|
|
4955
|
+
Promise.resolve().then(() => this._onRemove());
|
|
4956
|
+
}
|
|
4957
|
+
}
|
|
4958
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: BkDialogLayoutSection, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
4959
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "20.3.16", type: BkDialogLayoutSection, isStandalone: true, ngImport: i0 });
|
|
4960
|
+
}
|
|
4961
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: BkDialogLayoutSection, decorators: [{
|
|
4962
|
+
type: Directive,
|
|
4963
|
+
args: [{ standalone: true }]
|
|
4964
|
+
}] });
|
|
4965
|
+
// ════════════════════════════════════════════════════════════════════════
|
|
4966
|
+
// BkDialogTitle
|
|
4967
|
+
// ════════════════════════════════════════════════════════════════════════
|
|
4968
|
+
/**
|
|
4969
|
+
* Marks an element as the dialog title.
|
|
4970
|
+
*
|
|
4971
|
+
* • Generates a unique `id` and binds it to the host element.
|
|
4972
|
+
* • Registers the `id` with the CDK container's `aria-labelledby` queue
|
|
4973
|
+
* so screen readers announce the dialog title automatically.
|
|
4974
|
+
*
|
|
4975
|
+
* Usage:
|
|
4976
|
+
* ```html
|
|
4977
|
+
* <h2 bk-dialog-title>Edit User</h2>
|
|
4978
|
+
* ```
|
|
4979
|
+
*/
|
|
4980
|
+
class BkDialogTitle extends BkDialogLayoutSection {
|
|
4981
|
+
/** Unique element `id`. Auto-generated but can be overridden. */
|
|
4982
|
+
id = `bk-dialog-title-${nextTitleId++}`;
|
|
4983
|
+
_onAdd() {
|
|
4984
|
+
// Register with the CdkDialogContainer's aria-labelledby queue.
|
|
4985
|
+
// _containerInstance extends CdkDialogContainer which has _addAriaLabelledBy.
|
|
4986
|
+
this._dialogRef?._containerInstance?._addAriaLabelledBy?.(this.id);
|
|
4987
|
+
}
|
|
4988
|
+
_onRemove() {
|
|
4989
|
+
this._dialogRef?._containerInstance?._removeAriaLabelledBy?.(this.id);
|
|
4990
|
+
}
|
|
4991
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: BkDialogTitle, deps: null, target: i0.ɵɵFactoryTarget.Directive });
|
|
4992
|
+
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 });
|
|
4993
|
+
}
|
|
4994
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: BkDialogTitle, decorators: [{
|
|
4995
|
+
type: Directive,
|
|
4996
|
+
args: [{
|
|
4997
|
+
selector: '[bk-dialog-title], [bkDialogTitle]',
|
|
4998
|
+
standalone: true,
|
|
4999
|
+
exportAs: 'bkDialogTitle',
|
|
5000
|
+
host: {
|
|
5001
|
+
'class': 'bk-dialog-title',
|
|
5002
|
+
'[id]': 'id',
|
|
5003
|
+
},
|
|
5004
|
+
}]
|
|
5005
|
+
}], propDecorators: { id: [{
|
|
5006
|
+
type: Input
|
|
5007
|
+
}] } });
|
|
5008
|
+
// ════════════════════════════════════════════════════════════════════════
|
|
5009
|
+
// BkDialogContent
|
|
5010
|
+
// ════════════════════════════════════════════════════════════════════════
|
|
5011
|
+
/**
|
|
5012
|
+
* Scrollable content area of a dialog.
|
|
5013
|
+
*
|
|
5014
|
+
* • Applies the `bk-dialog-content` CSS class → `flex: 1 1 auto; overflow: auto`.
|
|
5015
|
+
* • Composes CDK's `CdkScrollable` for scroll-position monitoring and
|
|
5016
|
+
* integration with CDK's scroll dispatcher.
|
|
5017
|
+
*
|
|
5018
|
+
* Usage (attribute):
|
|
5019
|
+
* ```html
|
|
5020
|
+
* <div bk-dialog-content>
|
|
5021
|
+
* <!-- body content — this area scrolls -->
|
|
5022
|
+
* </div>
|
|
5023
|
+
* ```
|
|
5024
|
+
*
|
|
5025
|
+
* Usage (element):
|
|
5026
|
+
* ```html
|
|
5027
|
+
* <bk-dialog-content>...</bk-dialog-content>
|
|
5028
|
+
* ```
|
|
5029
|
+
*/
|
|
5030
|
+
class BkDialogContent {
|
|
5031
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: BkDialogContent, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
5032
|
+
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 });
|
|
5033
|
+
}
|
|
5034
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: BkDialogContent, decorators: [{
|
|
5035
|
+
type: Directive,
|
|
5036
|
+
args: [{
|
|
5037
|
+
selector: '[bk-dialog-content], bk-dialog-content, [bkDialogContent]',
|
|
5038
|
+
standalone: true,
|
|
5039
|
+
exportAs: 'bkDialogContent',
|
|
5040
|
+
host: {
|
|
5041
|
+
'class': 'bk-dialog-content',
|
|
5042
|
+
},
|
|
5043
|
+
hostDirectives: [CdkScrollable],
|
|
5044
|
+
}]
|
|
5045
|
+
}] });
|
|
5046
|
+
// ════════════════════════════════════════════════════════════════════════
|
|
5047
|
+
// BkDialogActions
|
|
5048
|
+
// ════════════════════════════════════════════════════════════════════════
|
|
5049
|
+
/**
|
|
5050
|
+
* Container for action buttons at the bottom of a dialog.
|
|
5051
|
+
* Stays pinned when the content area scrolls.
|
|
5052
|
+
*
|
|
5053
|
+
* • Applies `bk-dialog-actions` class → `flex: 0 0 auto` (never shrinks).
|
|
5054
|
+
* • Optional `align` input controls horizontal alignment.
|
|
5055
|
+
*
|
|
5056
|
+
* Usage:
|
|
5057
|
+
* ```html
|
|
5058
|
+
* <div bk-dialog-actions>
|
|
5059
|
+
* <button (click)="cancel()">Cancel</button>
|
|
5060
|
+
* <button (click)="save()">Save</button>
|
|
5061
|
+
* </div>
|
|
5062
|
+
*
|
|
5063
|
+
* <!-- With alignment -->
|
|
5064
|
+
* <div bk-dialog-actions align="center">...</div>
|
|
5065
|
+
* ```
|
|
5066
|
+
*/
|
|
5067
|
+
class BkDialogActions extends BkDialogLayoutSection {
|
|
5068
|
+
/** Horizontal alignment of action buttons. */
|
|
5069
|
+
align;
|
|
5070
|
+
_onAdd() {
|
|
5071
|
+
// Future: notify container for action section count (like Material).
|
|
5072
|
+
}
|
|
5073
|
+
_onRemove() {
|
|
5074
|
+
// No-op.
|
|
5075
|
+
}
|
|
5076
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: BkDialogActions, deps: null, target: i0.ɵɵFactoryTarget.Directive });
|
|
5077
|
+
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 });
|
|
5078
|
+
}
|
|
5079
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: BkDialogActions, decorators: [{
|
|
5080
|
+
type: Directive,
|
|
5081
|
+
args: [{
|
|
5082
|
+
selector: '[bk-dialog-actions], bk-dialog-actions, [bkDialogActions]',
|
|
5083
|
+
standalone: true,
|
|
5084
|
+
exportAs: 'bkDialogActions',
|
|
5085
|
+
host: {
|
|
5086
|
+
'class': 'bk-dialog-actions',
|
|
5087
|
+
'[class.bk-dialog-actions-align-start]': 'align === "start"',
|
|
5088
|
+
'[class.bk-dialog-actions-align-center]': 'align === "center"',
|
|
5089
|
+
'[class.bk-dialog-actions-align-end]': 'align === "end"',
|
|
5090
|
+
},
|
|
5091
|
+
}]
|
|
5092
|
+
}], propDecorators: { align: [{
|
|
5093
|
+
type: Input
|
|
5094
|
+
}] } });
|
|
5095
|
+
// ════════════════════════════════════════════════════════════════════════
|
|
5096
|
+
// BkDialogClose
|
|
5097
|
+
// ════════════════════════════════════════════════════════════════════════
|
|
5098
|
+
/**
|
|
5099
|
+
* Closes the dialog when the host element is clicked.
|
|
5100
|
+
* Optionally returns a result value to the opener.
|
|
5101
|
+
*
|
|
5102
|
+
* • Sets `type="button"` by default to prevent accidental form submissions.
|
|
5103
|
+
* • Supports an optional `aria-label` for screen readers.
|
|
5104
|
+
*
|
|
5105
|
+
* Usage:
|
|
5106
|
+
* ```html
|
|
5107
|
+
* <!-- Close with no result -->
|
|
5108
|
+
* <button bk-dialog-close>Cancel</button>
|
|
5109
|
+
*
|
|
5110
|
+
* <!-- Close with a result value -->
|
|
5111
|
+
* <button [bk-dialog-close]="'confirmed'">OK</button>
|
|
5112
|
+
*
|
|
5113
|
+
* <!-- Alternative camelCase binding -->
|
|
5114
|
+
* <button [bkDialogClose]="myResult">Done</button>
|
|
5115
|
+
* ```
|
|
5116
|
+
*/
|
|
5117
|
+
class BkDialogClose {
|
|
5118
|
+
_dialogRef = inject(DialogRef, { optional: true });
|
|
5119
|
+
_elementRef = inject((ElementRef));
|
|
5120
|
+
_dialogService = inject(DialogService);
|
|
5121
|
+
/** Screen-reader label for the button. */
|
|
5122
|
+
ariaLabel;
|
|
5123
|
+
/** Prevents accidental form submits. Default: `'button'`. */
|
|
5124
|
+
type = 'button';
|
|
5125
|
+
/** Dialog result — set via `[bk-dialog-close]="value"`. */
|
|
5126
|
+
dialogResult;
|
|
5127
|
+
/** Alternative camelCase binding: `[bkDialogClose]="value"`. */
|
|
5128
|
+
_bkDialogClose;
|
|
5129
|
+
ngOnInit() {
|
|
5130
|
+
if (!this._dialogRef) {
|
|
5131
|
+
this._dialogRef = getClosestDialog(this._elementRef, this._dialogService.openDialogsRef);
|
|
5132
|
+
}
|
|
5133
|
+
}
|
|
5134
|
+
ngOnChanges(changes) {
|
|
5135
|
+
const proxiedChange = changes['_bkDialogClose'] || changes['dialogResult'];
|
|
5136
|
+
if (proxiedChange) {
|
|
5137
|
+
this.dialogResult = proxiedChange.currentValue;
|
|
5138
|
+
}
|
|
5139
|
+
}
|
|
5140
|
+
_onButtonClick(_event) {
|
|
5141
|
+
this._dialogRef?.close(this.dialogResult);
|
|
5142
|
+
}
|
|
5143
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: BkDialogClose, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
5144
|
+
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 });
|
|
5145
|
+
}
|
|
5146
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: BkDialogClose, decorators: [{
|
|
5147
|
+
type: Directive,
|
|
5148
|
+
args: [{
|
|
5149
|
+
selector: '[bk-dialog-close], [bkDialogClose]',
|
|
5150
|
+
standalone: true,
|
|
5151
|
+
exportAs: 'bkDialogClose',
|
|
5152
|
+
host: {
|
|
5153
|
+
'(click)': '_onButtonClick($event)',
|
|
5154
|
+
'[attr.aria-label]': 'ariaLabel || null',
|
|
5155
|
+
'[attr.type]': 'type',
|
|
5156
|
+
},
|
|
5157
|
+
}]
|
|
5158
|
+
}], propDecorators: { ariaLabel: [{
|
|
5159
|
+
type: Input,
|
|
5160
|
+
args: ['aria-label']
|
|
5161
|
+
}], type: [{
|
|
5162
|
+
type: Input
|
|
5163
|
+
}], dialogResult: [{
|
|
5164
|
+
type: Input,
|
|
5165
|
+
args: ['bk-dialog-close']
|
|
5166
|
+
}], _bkDialogClose: [{
|
|
5167
|
+
type: Input,
|
|
5168
|
+
args: ['bkDialogClose']
|
|
5169
|
+
}] } });
|
|
5170
|
+
|
|
5171
|
+
/**
|
|
5172
|
+
* BkDialogModule
|
|
5173
|
+
*
|
|
5174
|
+
* Optional NgModule wrapper for projects that prefer module-based usage.
|
|
5175
|
+
*
|
|
5176
|
+
* Architecture Decision:
|
|
5177
|
+
* ─────────────────────
|
|
5178
|
+
* Follows the exact same pattern as Angular Material's `MatDialogModule`
|
|
5179
|
+
* and `@brickclay-org/ui`'s `CalendarModule`:
|
|
5180
|
+
*
|
|
5181
|
+
* • All components/directives are **standalone**.
|
|
5182
|
+
* • This module simply imports + re-exports them for convenience.
|
|
5183
|
+
* • `DialogService` is `providedIn: 'root'`, so it does NOT need to
|
|
5184
|
+
* be listed in `providers` here — it is tree-shakeable and available
|
|
5185
|
+
* app-wide automatically.
|
|
5186
|
+
*
|
|
5187
|
+
* Two usage styles:
|
|
5188
|
+
*
|
|
5189
|
+
* ───────── Module-based (NgModule) ─────────
|
|
5190
|
+
* ```ts
|
|
5191
|
+
* import { BkDialogModule } from '@shared/components/dialog';
|
|
5192
|
+
*
|
|
5193
|
+
* @NgModule({ imports: [BkDialogModule] })
|
|
5194
|
+
* export class FuelCostModule {}
|
|
5195
|
+
* ```
|
|
5196
|
+
*
|
|
5197
|
+
* ───────── Standalone ──────────────────────
|
|
5198
|
+
* ```ts
|
|
5199
|
+
* @Component({
|
|
5200
|
+
* standalone: true,
|
|
5201
|
+
* imports: [BkDialogContent, BkDialogActions, BkDialogClose],
|
|
5202
|
+
* })
|
|
5203
|
+
* export class MyDialog {}
|
|
5204
|
+
* ```
|
|
5205
|
+
*
|
|
5206
|
+
* @see https://github.com/angular/components/blob/main/src/material/dialog/dialog-module.ts
|
|
5207
|
+
*/
|
|
5208
|
+
class BkDialogModule {
|
|
5209
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: BkDialogModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
|
|
5210
|
+
static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "20.3.16", ngImport: i0, type: BkDialogModule, imports: [
|
|
5211
|
+
// ── CDK foundations ──────────────────────────────────────────────
|
|
5212
|
+
DialogModule,
|
|
5213
|
+
OverlayModule,
|
|
5214
|
+
PortalModule,
|
|
5215
|
+
// ── Our standalone pieces ───────────────────────────────────────
|
|
5216
|
+
DialogContainerComponent,
|
|
5217
|
+
BkDialogTitle,
|
|
5218
|
+
BkDialogContent,
|
|
5219
|
+
BkDialogActions,
|
|
5220
|
+
BkDialogClose], exports: [
|
|
5221
|
+
// ── Public API for template usage ───────────────────────────────
|
|
5222
|
+
// Consumers import BkDialogModule and get these directives in
|
|
5223
|
+
// their templates automatically — just like MatDialogModule.
|
|
5224
|
+
BkDialogTitle,
|
|
5225
|
+
BkDialogContent,
|
|
5226
|
+
BkDialogActions,
|
|
5227
|
+
BkDialogClose] });
|
|
5228
|
+
static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: BkDialogModule, imports: [
|
|
5229
|
+
// ── CDK foundations ──────────────────────────────────────────────
|
|
5230
|
+
DialogModule,
|
|
5231
|
+
OverlayModule,
|
|
5232
|
+
PortalModule] });
|
|
5233
|
+
}
|
|
5234
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: BkDialogModule, decorators: [{
|
|
5235
|
+
type: NgModule,
|
|
5236
|
+
args: [{
|
|
5237
|
+
imports: [
|
|
5238
|
+
// ── CDK foundations ──────────────────────────────────────────────
|
|
5239
|
+
DialogModule,
|
|
5240
|
+
OverlayModule,
|
|
5241
|
+
PortalModule,
|
|
5242
|
+
// ── Our standalone pieces ───────────────────────────────────────
|
|
5243
|
+
DialogContainerComponent,
|
|
5244
|
+
BkDialogTitle,
|
|
5245
|
+
BkDialogContent,
|
|
5246
|
+
BkDialogActions,
|
|
5247
|
+
BkDialogClose,
|
|
5248
|
+
],
|
|
5249
|
+
exports: [
|
|
5250
|
+
// ── Public API for template usage ───────────────────────────────
|
|
5251
|
+
// Consumers import BkDialogModule and get these directives in
|
|
5252
|
+
// their templates automatically — just like MatDialogModule.
|
|
5253
|
+
BkDialogTitle,
|
|
5254
|
+
BkDialogContent,
|
|
5255
|
+
BkDialogActions,
|
|
5256
|
+
BkDialogClose,
|
|
5257
|
+
],
|
|
5258
|
+
}]
|
|
5259
|
+
}] });
|
|
5260
|
+
|
|
4025
5261
|
/*
|
|
4026
5262
|
* Public API Surface of brickclay-lib
|
|
4027
5263
|
*/
|
|
@@ -4031,5 +5267,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
|
|
|
4031
5267
|
* Generated bundle index. Do not edit.
|
|
4032
5268
|
*/
|
|
4033
5269
|
|
|
4034
|
-
export { BkBadge, BkButton, BkButtonGroup, BkCheckbox,
|
|
5270
|
+
export { BkBadge, BkButton, BkButtonGroup, BkCheckbox, BkCustomCalendar, BkDialogActions, BkDialogClose, BkDialogContent, BkDialogModule, BkDialogTitle, BkGrid, BkIconButton, BkInput, BkInputChips, 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
5271
|
//# sourceMappingURL=brickclay-org-ui.mjs.map
|