@tekus/design-system 5.19.0 → 5.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,5 @@
1
1
  import * as i0 from '@angular/core';
2
- import { input, computed, EventEmitter, Component, createComponent, Injectable } from '@angular/core';
3
- import { NgComponentOutlet } from '@angular/common';
2
+ import { viewChild, ViewContainerRef, input, computed, model, EventEmitter, effect, untracked, afterRender, Component, createComponent, Injectable } from '@angular/core';
4
3
  import { ButtonComponent } from '@tekus/design-system/components/button';
5
4
  import * as i1 from 'primeng/dialog';
6
5
  import { DialogModule } from 'primeng/dialog';
@@ -10,99 +9,126 @@ import { Subject } from 'rxjs';
10
9
  /**
11
10
  * @component ModalComponent
12
11
  * @description
13
- * A programmatically controlled modal dialog used for displaying dynamic content, titles, and footer actions.
14
- * The modal is not instantiated via template bindings, but rather opened through a service with a configuration object.
15
- *
16
- * This component supports:
17
- * - Configurable title and content.
18
- * - Optional footer buttons with callbacks and return values.
19
- * - Multiple sizes: `'small' | 'large' | 'full'`.
20
- * - Closable modal and outside-click behavior.
21
- * - Passing arbitrary data to the modal instance.
22
- *
23
- * @usage
24
- * ### Open a modal from TypeScript using the modal service
25
- * ```ts
26
- * this.modalService.open({
27
- * title: 'Demo Modal',
28
- * content: 'This modal is opened from TypeScript using the service.',
29
- * footerButtons: [
30
- * {
31
- * label: 'Accept',
32
- * severity: 'secondary',
33
- * action: () => console.log('Accept clicked'),
34
- * returnValue: true,
35
- * },
36
- * {
37
- * label: 'Cancel',
38
- * severity: 'danger',
39
- * action: () => console.log('Cancel clicked'),
40
- * returnValue: false,
41
- * },
42
- * ],
43
- * size: 'small',
44
- * closable: true,
45
- * closeOnOutsideClick: false,
46
- * }).subscribe((result) => {
47
- * console.log('Modal closed with value:', result);
48
- * });
49
- * ```
12
+ * A programmatically controlled modal dialog used for displaying dynamic content.
13
+ * Modernized for Angular 19 with 100% synchronous Signal-based closing interception.
50
14
  */
51
15
  class ModalComponent {
52
16
  constructor(elementRef) {
17
+ /**
18
+ * @summary Orchestrates the reactive dynamic lifecycle.
19
+ */
53
20
  this.elementRef = elementRef;
21
+ this.contentHost = viewChild('contentHost', {
22
+ read: ViewContainerRef,
23
+ });
54
24
  /** The title displayed at the top of the modal */
55
25
  this.title = input('');
56
- /** The main content of the modal */
26
+ /** The main content of the modal. Can be a string or a Component Type. */
57
27
  this.content = input(null);
58
28
  /** Array of footer buttons with label, callback, and return value */
59
29
  this.footerButtons = input([]);
60
- /** Modal size: 'small', 'large', or 'full' */
30
+ /** Modal size: 'small', 'large', 'medium' or 'full' */
61
31
  this.size = input('small');
62
- /** Whether the modal can be closed by the user */
32
+ /** Whether the modal can be closed by the user via close button */
63
33
  this.closable = input(true);
64
- /** Whether clicking outside closes the modal */
34
+ /** Whether clicking outside the modal mask closes the modal */
65
35
  this.closeOnOutsideClick = input(false);
66
- this.isContentString = computed(() => typeof this.content() === 'string');
67
- /** Computed: whether the modal has footer buttons */
68
- this.hasFooter = computed(() => (this.footerButtons() ?? []).length > 0);
69
36
  /**
70
- * Whether the modal should be responsive on mobile screens.
71
- * If true, the modal width will adapt based on breakpoints.
72
- * @default true
37
+ * Optional data to be passed as inputs to the dynamic component.
73
38
  */
39
+ this.data = input({});
40
+ /**
41
+ * Optional interceptor called before the modal closes.
42
+ * MUST be synchronous. Returns true to allow closing.
43
+ */
44
+ this.interceptor = input(undefined);
45
+ /** Computed: whether the content is a simple string */
46
+ this.isContentString = computed(() => typeof this.content() === 'string');
47
+ /** Computed: whether the modal has footer buttons to display */
48
+ this.hasFooter = computed(() => (this.footerButtons() ?? []).length > 0);
49
+ /** Whether the modal should be responsive on mobile screens */
74
50
  this.responsive = input(true);
51
+ /** Visibility flag as Model Signal (allows two-way binding) */
52
+ this.isOpened = model(false);
53
+ /** Whether the modal content has a scrollbar */
54
+ this.hasScroll = false;
55
+ // --- Internals ---
56
+ /** Emits when the modal closes, passing the return value from footer buttons or null */
57
+ this.onClose = new EventEmitter();
58
+ this.alreadyEmitted = false;
59
+ this.returnValueOnClose = null;
75
60
  /** Computed: calculates modal max-width based on `size` */
76
61
  this.modalMaxWidth = computed(() => {
77
62
  switch (this.size()) {
78
63
  case 'large':
79
- return '67.5rem'; // Large
64
+ return '67.5rem';
80
65
  case 'medium':
81
- return '35rem'; // Medium
66
+ return '35rem';
82
67
  case 'full':
83
- return '98vw'; // Full
68
+ return '98vw';
84
69
  default:
85
- return '25rem'; // Small
70
+ return '25rem';
86
71
  }
87
72
  });
88
- /** Whether the modal content has a scrollbar */
89
- this.hasScroll = false;
90
- /** Visibility flag */
91
- this.isOpened = false;
92
- /** Emits when the modal closes, passing the return value from footer buttons or null */
93
- this.onClose = new EventEmitter();
94
- this.alreadyEmitted = false;
95
- this.returnValueOnClose = null;
73
+ effect(() => {
74
+ const opened = this.isOpened();
75
+ const host = this.contentHost();
76
+ untracked(() => {
77
+ if (opened && host) {
78
+ this.attachDynamicContent();
79
+ }
80
+ else if (!opened) {
81
+ this.detachDynamicContent();
82
+ }
83
+ });
84
+ });
85
+ effect(() => {
86
+ const currentData = this.data();
87
+ untracked(() => this.syncDynamicInputs(currentData));
88
+ });
89
+ afterRender(() => {
90
+ this.checkScroll();
91
+ });
96
92
  }
97
- /** Opens the modal */
98
- open() {
99
- this.isOpened = true;
100
- this.alreadyEmitted = false;
101
- this.returnValueOnClose = null;
93
+ ngOnDestroy() {
94
+ this.detachDynamicContent();
95
+ }
96
+ /**
97
+ * @summary Orchestrates dynamic rendering and destruction based on visibility.
98
+ * @private
99
+ */
100
+ attachDynamicContent() {
101
+ const type = this.content();
102
+ const host = this.contentHost();
103
+ if (!type || typeof type === 'string' || !host)
104
+ return;
105
+ this.detachDynamicContent();
106
+ this.componentRef = host.createComponent(type);
107
+ this.syncDynamicInputs(this.data());
108
+ }
109
+ /**
110
+ * @summary Synchronizes incoming data record with the dynamic instance @Inputs.
111
+ * @private
112
+ */
113
+ syncDynamicInputs(data) {
114
+ if (!this.componentRef)
115
+ return;
116
+ Object.entries(data).forEach(([key, value]) => {
117
+ this.componentRef?.setInput(key, value);
118
+ });
119
+ }
120
+ /**
121
+ * @summary Safely destroys the dynamic component and clears references.
122
+ * @private
123
+ */
124
+ detachDynamicContent() {
125
+ if (this.componentRef) {
126
+ this.componentRef.destroy();
127
+ this.componentRef = undefined;
128
+ }
102
129
  }
103
130
  /**
104
131
  * Checks if the modal content has a scrollbar and updates `hasScroll` state.
105
- * This is called when the modal is shown.
106
132
  */
107
133
  checkScroll() {
108
134
  const contentEl = this.elementRef.nativeElement.querySelector('.p-dialog-content');
@@ -110,54 +136,127 @@ class ModalComponent {
110
136
  this.hasScroll = contentEl.scrollHeight > contentEl.clientHeight;
111
137
  }
112
138
  }
113
- /** Closes the modal and emits onClose with null */
114
- handleClose() {
115
- if (!this.alreadyEmitted) {
116
- this.onClose.emit(null);
139
+ /**
140
+ * Opens the modal dialog.
141
+ */
142
+ open() {
143
+ this.isOpened.set(true);
144
+ this.resetClosureState();
145
+ }
146
+ /**
147
+ * @summary Main entry point for closure requests.
148
+ * Evaluation is 100% synchronous based on current Signal state.
149
+ * @param returnValue (Optional) Value to emit on close.
150
+ */
151
+ tryClose(returnValue = null) {
152
+ if (this.canExecuteClosure()) {
153
+ this.executeClosure(returnValue, arguments.length > 0);
117
154
  }
118
155
  else {
119
- this.onClose.emit(this.returnValueOnClose);
156
+ const instance = this.componentRef?.instance;
157
+ instance?.onBlockedClose?.();
120
158
  }
121
- this.alreadyEmitted = false;
122
- this.returnValueOnClose = null;
123
159
  }
124
- /** Closes the modal without emitting an event */
125
- close() {
126
- this.isOpened = false;
160
+ /**
161
+ * @summary Synchronous evaluator of hierarchical guards.
162
+ * @returns true if closure is allowed.
163
+ * @private
164
+ */
165
+ canExecuteClosure() {
166
+ const instance = this.componentRef?.instance;
167
+ const canClose = instance?.canClose ? instance.canClose() : true;
168
+ if (!canClose)
169
+ return false;
170
+ const configInterceptor = this.interceptor();
171
+ if (configInterceptor && !configInterceptor())
172
+ return false;
173
+ return true;
174
+ }
175
+ /**
176
+ * @summary Unified logic to execute the final closure state change.
177
+ * @private
178
+ */
179
+ executeClosure(returnValue, hasReturnValue) {
180
+ if (hasReturnValue) {
181
+ this.alreadyEmitted = true;
182
+ this.returnValueOnClose = returnValue;
183
+ }
184
+ this.isOpened.set(false);
185
+ }
186
+ /**
187
+ * @summary Handles external visibility changes (from p-dialog 'X' or Escape).
188
+ * Ensures the reactive guard is respected before allowing closure.
189
+ */
190
+ onVisibleChange(visible) {
191
+ if (!visible) {
192
+ this.tryClose();
193
+ }
194
+ }
195
+ /**
196
+ * Handles the close event from the underlying dialog component.
197
+ */
198
+ handleClose() {
199
+ const valueToEmit = this.alreadyEmitted ? this.returnValueOnClose : null;
200
+ this.onClose.emit(valueToEmit);
201
+ this.resetClosureState();
127
202
  }
128
203
  /**
129
- * Handles footer button actions.
130
- * Executes the action callback, emits `onClose` with the provided returnValue, then closes the modal.
131
- * @param action Callback to execute when the button is clicked
132
- * @param returnValue Value emitted on modal close
204
+ * @summary Handles actions triggered by footer buttons.
133
205
  */
134
206
  handleAction(action, returnValue) {
135
207
  if (action)
136
208
  action();
137
- this.alreadyEmitted = true;
138
- this.returnValueOnClose = returnValue;
139
- this.isOpened = false;
209
+ this.tryClose(returnValue);
210
+ }
211
+ /**
212
+ * @summary Safely closes the modal forcefully without checks.
213
+ */
214
+ close() {
215
+ this.tryClose();
216
+ }
217
+ /**
218
+ * @private
219
+ * Encapsulates state cleanup to avoid repetitive assignments
220
+ */
221
+ resetClosureState() {
222
+ this.alreadyEmitted = false;
223
+ this.returnValueOnClose = null;
140
224
  }
141
225
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: ModalComponent, deps: [{ token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Component }); }
142
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.18", type: ModalComponent, isStandalone: true, selector: "tk-modal", inputs: { title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, content: { classPropertyName: "content", publicName: "content", isSignal: true, isRequired: false, transformFunction: null }, footerButtons: { classPropertyName: "footerButtons", publicName: "footerButtons", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, closable: { classPropertyName: "closable", publicName: "closable", isSignal: true, isRequired: false, transformFunction: null }, closeOnOutsideClick: { classPropertyName: "closeOnOutsideClick", publicName: "closeOnOutsideClick", isSignal: true, isRequired: false, transformFunction: null }, responsive: { classPropertyName: "responsive", publicName: "responsive", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<p-dialog\n [modal]=\"true\"\n [(visible)]=\"isOpened\"\n [closable]=\"closable()\"\n [dismissableMask]=\"closeOnOutsideClick()\"\n [draggable]=\"false\"\n [focusOnShow]=\"false\"\n [header]=\"title()\"\n [style]=\"{ 'max-width': modalMaxWidth(), width: 'calc(100% - var(--tk-spacing-base-200))' }\"\n (onHide)=\"handleClose()\"\n (onShow)=\"checkScroll()\"\n class=\"tk-modal\"\n>\n <section class=\"tk-modal__content\">\n @if (content()) {\n @if (isContentString()) {\n <p [innerHTML]=\"content()\" ></p>\n } @else {\n <ng-container *ngComponentOutlet=\"$any(content())\"></ng-container>\n }\n }\n </section>\n\n <ng-template pTemplate=\"footer\">\n @if (hasFooter()) {\n <section class=\"tk-modal__footer\" [class.tk-modal__footer--with-content]=\"hasScroll\">\n @for (btn of footerButtons()!; track $index) {\n <tk-button\n [label]=\"btn.label\"\n [severity]=\"btn.severity\"\n [variant]=\"btn.variant\"\n (clicked)=\"handleAction(btn.action, btn.returnValue)\"\n (keydown.enter)=\"handleAction(btn.action, btn.returnValue)\"\n (keydown.space)=\"handleAction(btn.action, btn.returnValue)\"\n />\n }\n </section>\n }\n </ng-template>\n</p-dialog>\n", styles: [":host ::ng-deep .p-dialog{max-height:90vh;display:flex;flex-direction:column}:host ::ng-deep .p-dialog-content{overflow-y:auto}:host ::ng-deep .p-dialog-title{color:var(--tk-color-text-default, #212121)}:host ::ng-deep .p-dialog-close-button{color:var(--tk-surface-500, #8a8a8b)}:host ::ng-deep .p-dialog-close-button:hover{background:var(--tk-color-base-surface-100, #f0f0f0)!important;color:var(--tk-surface-500, #8a8a8b)}.tk-modal__content{padding-bottom:var(--tk-spacing-paddingY-l, 1.25rem);color:var(--tk-surface-1000, #000000)}.tk-modal__footer{display:flex;flex-direction:row;justify-content:end;align-items:center;gap:var(--tk-spacing-base-50, .5rem)}.tk-modal__footer--with-content{padding-right:var(--tk-spacing-paddingX-l, 1.25rem)}\n"], dependencies: [{ kind: "ngmodule", type: DialogModule }, { kind: "component", type: i1.Dialog, selector: "p-dialog", inputs: ["header", "draggable", "resizable", "positionLeft", "positionTop", "contentStyle", "contentStyleClass", "modal", "closeOnEscape", "dismissableMask", "rtl", "closable", "responsive", "appendTo", "breakpoints", "styleClass", "maskStyleClass", "maskStyle", "showHeader", "breakpoint", "blockScroll", "autoZIndex", "baseZIndex", "minX", "minY", "focusOnShow", "maximizable", "keepInViewport", "focusTrap", "transitionOptions", "closeIcon", "closeAriaLabel", "closeTabindex", "minimizeIcon", "maximizeIcon", "closeButtonProps", "maximizeButtonProps", "visible", "style", "position", "role", "content", "contentTemplate", "footerTemplate", "closeIconTemplate", "maximizeIconTemplate", "minimizeIconTemplate", "headlessTemplate"], outputs: ["onShow", "onHide", "visibleChange", "onResizeInit", "onResizeEnd", "onDragEnd", "onMaximize"] }, { kind: "directive", type: i2.PrimeTemplate, selector: "[pTemplate]", inputs: ["type", "pTemplate"] }, { kind: "component", type: ButtonComponent, selector: "tk-button", inputs: ["label", "disabled", "type", "severity", "variant", "link", "icon", "tooltipText"], outputs: ["clicked"] }, { kind: "directive", type: NgComponentOutlet, selector: "[ngComponentOutlet]", inputs: ["ngComponentOutlet", "ngComponentOutletInputs", "ngComponentOutletInjector", "ngComponentOutletContent", "ngComponentOutletNgModule", "ngComponentOutletNgModuleFactory"], exportAs: ["ngComponentOutlet"] }] }); }
226
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.18", type: ModalComponent, isStandalone: true, selector: "tk-modal", inputs: { title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, content: { classPropertyName: "content", publicName: "content", isSignal: true, isRequired: false, transformFunction: null }, footerButtons: { classPropertyName: "footerButtons", publicName: "footerButtons", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, closable: { classPropertyName: "closable", publicName: "closable", isSignal: true, isRequired: false, transformFunction: null }, closeOnOutsideClick: { classPropertyName: "closeOnOutsideClick", publicName: "closeOnOutsideClick", isSignal: true, isRequired: false, transformFunction: null }, data: { classPropertyName: "data", publicName: "data", isSignal: true, isRequired: false, transformFunction: null }, interceptor: { classPropertyName: "interceptor", publicName: "interceptor", isSignal: true, isRequired: false, transformFunction: null }, responsive: { classPropertyName: "responsive", publicName: "responsive", isSignal: true, isRequired: false, transformFunction: null }, isOpened: { classPropertyName: "isOpened", publicName: "isOpened", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { isOpened: "isOpenedChange" }, viewQueries: [{ propertyName: "contentHost", first: true, predicate: ["contentHost"], descendants: true, read: ViewContainerRef, isSignal: true }], ngImport: i0, template: "<p-dialog\n [modal]=\"true\"\n [visible]=\"isOpened()\"\n (visibleChange)=\"onVisibleChange($event)\"\n [closable]=\"closable()\"\n [dismissableMask]=\"closeOnOutsideClick()\"\n [draggable]=\"false\"\n [focusOnShow]=\"false\"\n [header]=\"title()\"\n [style]=\"{\n 'max-width': modalMaxWidth(),\n width: 'calc(100% - var(--tk-spacing-base-200))',\n }\"\n (onHide)=\"handleClose()\"\n (onShow)=\"checkScroll()\"\n class=\"tk-modal\">\n <section class=\"tk-modal__content\">\n @if (content()) {\n @if (isContentString()) {\n <p [innerHTML]=\"content()\"></p>\n } @else {\n <ng-template #contentHost></ng-template>\n }\n }\n </section>\n\n <ng-template pTemplate=\"footer\">\n @if (hasFooter()) {\n <section\n class=\"tk-modal__footer\"\n [class.tk-modal__footer--with-content]=\"hasScroll\">\n @for (btn of footerButtons()!; track $index) {\n <tk-button\n [label]=\"btn.label\"\n [severity]=\"btn.severity\"\n [variant]=\"btn.variant\"\n (clicked)=\"handleAction(btn.action, btn.returnValue)\"\n (keydown.enter)=\"handleAction(btn.action, btn.returnValue)\"\n (keydown.space)=\"handleAction(btn.action, btn.returnValue)\" />\n }\n </section>\n }\n </ng-template>\n</p-dialog>\n", styles: [":host ::ng-deep .p-dialog{max-height:90vh;display:flex;flex-direction:column}:host ::ng-deep .p-dialog-content{overflow-y:auto}:host ::ng-deep .p-dialog-title{color:var(--tk-color-text-default, #212121)}:host ::ng-deep .p-dialog-close-button{color:var(--tk-surface-500, #8a8a8b)}:host ::ng-deep .p-dialog-close-button:hover{background:var(--tk-color-base-surface-100, #f0f0f0)!important;color:var(--tk-surface-500, #8a8a8b)}.tk-modal__content{padding-bottom:var(--tk-spacing-paddingY-l, 1.25rem);color:var(--tk-surface-1000, #000000)}.tk-modal__footer{display:flex;flex-direction:row;justify-content:end;align-items:center;gap:var(--tk-spacing-base-50, .5rem)}.tk-modal__footer--with-content{padding-right:var(--tk-spacing-paddingX-l, 1.25rem)}\n"], dependencies: [{ kind: "ngmodule", type: DialogModule }, { kind: "component", type: i1.Dialog, selector: "p-dialog", inputs: ["header", "draggable", "resizable", "positionLeft", "positionTop", "contentStyle", "contentStyleClass", "modal", "closeOnEscape", "dismissableMask", "rtl", "closable", "responsive", "appendTo", "breakpoints", "styleClass", "maskStyleClass", "maskStyle", "showHeader", "breakpoint", "blockScroll", "autoZIndex", "baseZIndex", "minX", "minY", "focusOnShow", "maximizable", "keepInViewport", "focusTrap", "transitionOptions", "closeIcon", "closeAriaLabel", "closeTabindex", "minimizeIcon", "maximizeIcon", "closeButtonProps", "maximizeButtonProps", "visible", "style", "position", "role", "content", "contentTemplate", "footerTemplate", "closeIconTemplate", "maximizeIconTemplate", "minimizeIconTemplate", "headlessTemplate"], outputs: ["onShow", "onHide", "visibleChange", "onResizeInit", "onResizeEnd", "onDragEnd", "onMaximize"] }, { kind: "directive", type: i2.PrimeTemplate, selector: "[pTemplate]", inputs: ["type", "pTemplate"] }, { kind: "component", type: ButtonComponent, selector: "tk-button", inputs: ["label", "disabled", "type", "severity", "variant", "link", "icon", "tooltipText"], outputs: ["clicked"] }] }); }
143
227
  }
144
228
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: ModalComponent, decorators: [{
145
229
  type: Component,
146
- args: [{ selector: 'tk-modal', standalone: true, imports: [DialogModule, ButtonComponent, NgComponentOutlet], template: "<p-dialog\n [modal]=\"true\"\n [(visible)]=\"isOpened\"\n [closable]=\"closable()\"\n [dismissableMask]=\"closeOnOutsideClick()\"\n [draggable]=\"false\"\n [focusOnShow]=\"false\"\n [header]=\"title()\"\n [style]=\"{ 'max-width': modalMaxWidth(), width: 'calc(100% - var(--tk-spacing-base-200))' }\"\n (onHide)=\"handleClose()\"\n (onShow)=\"checkScroll()\"\n class=\"tk-modal\"\n>\n <section class=\"tk-modal__content\">\n @if (content()) {\n @if (isContentString()) {\n <p [innerHTML]=\"content()\" ></p>\n } @else {\n <ng-container *ngComponentOutlet=\"$any(content())\"></ng-container>\n }\n }\n </section>\n\n <ng-template pTemplate=\"footer\">\n @if (hasFooter()) {\n <section class=\"tk-modal__footer\" [class.tk-modal__footer--with-content]=\"hasScroll\">\n @for (btn of footerButtons()!; track $index) {\n <tk-button\n [label]=\"btn.label\"\n [severity]=\"btn.severity\"\n [variant]=\"btn.variant\"\n (clicked)=\"handleAction(btn.action, btn.returnValue)\"\n (keydown.enter)=\"handleAction(btn.action, btn.returnValue)\"\n (keydown.space)=\"handleAction(btn.action, btn.returnValue)\"\n />\n }\n </section>\n }\n </ng-template>\n</p-dialog>\n", styles: [":host ::ng-deep .p-dialog{max-height:90vh;display:flex;flex-direction:column}:host ::ng-deep .p-dialog-content{overflow-y:auto}:host ::ng-deep .p-dialog-title{color:var(--tk-color-text-default, #212121)}:host ::ng-deep .p-dialog-close-button{color:var(--tk-surface-500, #8a8a8b)}:host ::ng-deep .p-dialog-close-button:hover{background:var(--tk-color-base-surface-100, #f0f0f0)!important;color:var(--tk-surface-500, #8a8a8b)}.tk-modal__content{padding-bottom:var(--tk-spacing-paddingY-l, 1.25rem);color:var(--tk-surface-1000, #000000)}.tk-modal__footer{display:flex;flex-direction:row;justify-content:end;align-items:center;gap:var(--tk-spacing-base-50, .5rem)}.tk-modal__footer--with-content{padding-right:var(--tk-spacing-paddingX-l, 1.25rem)}\n"] }]
230
+ args: [{ selector: 'tk-modal', standalone: true, imports: [DialogModule, ButtonComponent], template: "<p-dialog\n [modal]=\"true\"\n [visible]=\"isOpened()\"\n (visibleChange)=\"onVisibleChange($event)\"\n [closable]=\"closable()\"\n [dismissableMask]=\"closeOnOutsideClick()\"\n [draggable]=\"false\"\n [focusOnShow]=\"false\"\n [header]=\"title()\"\n [style]=\"{\n 'max-width': modalMaxWidth(),\n width: 'calc(100% - var(--tk-spacing-base-200))',\n }\"\n (onHide)=\"handleClose()\"\n (onShow)=\"checkScroll()\"\n class=\"tk-modal\">\n <section class=\"tk-modal__content\">\n @if (content()) {\n @if (isContentString()) {\n <p [innerHTML]=\"content()\"></p>\n } @else {\n <ng-template #contentHost></ng-template>\n }\n }\n </section>\n\n <ng-template pTemplate=\"footer\">\n @if (hasFooter()) {\n <section\n class=\"tk-modal__footer\"\n [class.tk-modal__footer--with-content]=\"hasScroll\">\n @for (btn of footerButtons()!; track $index) {\n <tk-button\n [label]=\"btn.label\"\n [severity]=\"btn.severity\"\n [variant]=\"btn.variant\"\n (clicked)=\"handleAction(btn.action, btn.returnValue)\"\n (keydown.enter)=\"handleAction(btn.action, btn.returnValue)\"\n (keydown.space)=\"handleAction(btn.action, btn.returnValue)\" />\n }\n </section>\n }\n </ng-template>\n</p-dialog>\n", styles: [":host ::ng-deep .p-dialog{max-height:90vh;display:flex;flex-direction:column}:host ::ng-deep .p-dialog-content{overflow-y:auto}:host ::ng-deep .p-dialog-title{color:var(--tk-color-text-default, #212121)}:host ::ng-deep .p-dialog-close-button{color:var(--tk-surface-500, #8a8a8b)}:host ::ng-deep .p-dialog-close-button:hover{background:var(--tk-color-base-surface-100, #f0f0f0)!important;color:var(--tk-surface-500, #8a8a8b)}.tk-modal__content{padding-bottom:var(--tk-spacing-paddingY-l, 1.25rem);color:var(--tk-surface-1000, #000000)}.tk-modal__footer{display:flex;flex-direction:row;justify-content:end;align-items:center;gap:var(--tk-spacing-base-50, .5rem)}.tk-modal__footer--with-content{padding-right:var(--tk-spacing-paddingX-l, 1.25rem)}\n"] }]
147
231
  }], ctorParameters: () => [{ type: i0.ElementRef }] });
148
232
 
233
+ /**
234
+ * @service ModalService
235
+ * @description
236
+ * Service responsible for programmatically opening and managing modal dialogs.
237
+ * It handles component creation, attachment to the document body, and cleanup.
238
+ */
149
239
  class ModalService {
150
240
  constructor(injector, appRef) {
151
241
  this.injector = injector;
152
242
  this.appRef = appRef;
243
+ /** Reference to the currently open modal component */
153
244
  this.modalRef = null;
154
245
  }
246
+ /** Internal getter for testing purposes */
155
247
  get _modalRefForTesting() {
156
248
  return this.modalRef;
157
249
  }
250
+ /** Internal setter for testing purposes */
158
251
  set _modalRefForTesting(ref) {
159
252
  this.modalRef = ref;
160
253
  }
254
+ /**
255
+ * Opens a modal dialog with the provided configuration.
256
+ * Only one modal can be open at a time.
257
+ * @param config Configuration object for the modal (title, content, buttons, etc.)
258
+ * @returns An observable that emits the modal's return value when it closes.
259
+ */
161
260
  open(config) {
162
261
  if (this.modalRef) {
163
262
  return this.modalRef.instance.onClose.asObservable();
@@ -169,11 +268,13 @@ class ModalService {
169
268
  const domElem = componentRef.hostView.rootNodes[0];
170
269
  document.body.appendChild(domElem);
171
270
  componentRef.setInput('title', config.title);
172
- componentRef.setInput('content', config.content ?? null);
173
- componentRef.setInput('footerButtons', config.footerButtons ?? []);
174
- componentRef.setInput('size', config.size ?? 'small');
271
+ componentRef.setInput('content', config.content);
272
+ componentRef.setInput('footerButtons', config.footerButtons || []);
273
+ componentRef.setInput('size', config.size || 'small');
175
274
  componentRef.setInput('closable', config.closable ?? true);
176
275
  componentRef.setInput('closeOnOutsideClick', config.closeOnOutsideClick ?? false);
276
+ componentRef.setInput('interceptor', config.interceptor);
277
+ componentRef.setInput('data', config.data || {});
177
278
  const close$ = new Subject();
178
279
  componentRef.instance.onClose.subscribe((value) => {
179
280
  close$.next(value);
@@ -1 +1 @@
1
- {"version":3,"file":"tekus-design-system-components-modal.mjs","sources":["../../../projects/design-system/components/modal/src/modal.component.ts","../../../projects/design-system/components/modal/src/modal.component.html","../../../projects/design-system/components/modal/src/services/modal.service.ts","../../../projects/design-system/components/modal/tekus-design-system-components-modal.ts"],"sourcesContent":["import { Component, computed, input, EventEmitter, Type, ElementRef } from '@angular/core';\nimport { NgComponentOutlet } from '@angular/common';\nimport { ButtonComponent } from '@tekus/design-system/components/button';\nimport { DialogModule } from 'primeng/dialog';\nimport { ModalFooterButton, ModalSizeType } from './modal.types';\n\n/**\n * @component ModalComponent\n * @description\n * A programmatically controlled modal dialog used for displaying dynamic content, titles, and footer actions.\n * The modal is not instantiated via template bindings, but rather opened through a service with a configuration object.\n *\n * This component supports:\n * - Configurable title and content.\n * - Optional footer buttons with callbacks and return values.\n * - Multiple sizes: `'small' | 'large' | 'full'`.\n * - Closable modal and outside-click behavior.\n * - Passing arbitrary data to the modal instance.\n *\n * @usage\n * ### Open a modal from TypeScript using the modal service\n * ```ts\n * this.modalService.open({\n * title: 'Demo Modal',\n * content: 'This modal is opened from TypeScript using the service.',\n * footerButtons: [\n * {\n * label: 'Accept',\n * severity: 'secondary',\n * action: () => console.log('Accept clicked'),\n * returnValue: true,\n * },\n * {\n * label: 'Cancel',\n * severity: 'danger',\n * action: () => console.log('Cancel clicked'),\n * returnValue: false,\n * },\n * ],\n * size: 'small',\n * closable: true,\n * closeOnOutsideClick: false,\n * }).subscribe((result) => {\n * console.log('Modal closed with value:', result);\n * });\n * ```\n */\n\n@Component({\n selector: 'tk-modal',\n standalone: true,\n imports: [DialogModule, ButtonComponent, NgComponentOutlet],\n templateUrl: './modal.component.html',\n styleUrls: ['./modal.component.scss']\n})\nexport class ModalComponent {\n\n constructor(private readonly elementRef: ElementRef) {}\n\n /** The title displayed at the top of the modal */\n title = input<string>('');\n /** The main content of the modal */\n content = input<string | Type<unknown> | null>(null);\n /** Array of footer buttons with label, callback, and return value */\n footerButtons = input<ModalFooterButton[]>([]);\n /** Modal size: 'small', 'large', or 'full' */\n size = input<ModalSizeType>('small');\n /** Whether the modal can be closed by the user */\n closable = input<boolean>(true);\n /** Whether clicking outside closes the modal */\n closeOnOutsideClick = input<boolean>(false);\n isContentString = computed(() => typeof this.content() === 'string');\n /** Computed: whether the modal has footer buttons */\n hasFooter = computed(() => (this.footerButtons() ?? []).length > 0);\n\n /**\n * Whether the modal should be responsive on mobile screens.\n * If true, the modal width will adapt based on breakpoints.\n * @default true\n */\n responsive = input<boolean>(true);\n\n /** Computed: calculates modal max-width based on `size` */\n modalMaxWidth = computed(() => {\n switch (this.size()) {\n case 'large':\n return '67.5rem'; // Large\n case 'medium':\n return '35rem'; // Medium\n case 'full':\n return '98vw'; // Full\n default:\n return '25rem'; // Small\n }\n });\n /** Whether the modal content has a scrollbar */\n hasScroll = false;\n\n /** Visibility flag */\n isOpened: boolean = false;\n\n /** Emits when the modal closes, passing the return value from footer buttons or null */\n readonly onClose = new EventEmitter<unknown>();\n private alreadyEmitted = false;\n private returnValueOnClose: unknown = null;\n\n /** Opens the modal */\n open() {\n this.isOpened = true;\n this.alreadyEmitted = false;\n this.returnValueOnClose = null;\n }\n\n /**\n * Checks if the modal content has a scrollbar and updates `hasScroll` state.\n * This is called when the modal is shown.\n */\n checkScroll() {\n const contentEl = this.elementRef.nativeElement.querySelector('.p-dialog-content');\n if (contentEl) {\n this.hasScroll = contentEl.scrollHeight > contentEl.clientHeight;\n }\n }\n\n /** Closes the modal and emits onClose with null */\n handleClose() {\n if (!this.alreadyEmitted) {\n this.onClose.emit(null);\n } else {\n this.onClose.emit(this.returnValueOnClose);\n }\n this.alreadyEmitted = false;\n this.returnValueOnClose = null;\n }\n\n /** Closes the modal without emitting an event */\n close() {\n this.isOpened = false;\n }\n\n /**\n * Handles footer button actions.\n * Executes the action callback, emits `onClose` with the provided returnValue, then closes the modal.\n * @param action Callback to execute when the button is clicked\n * @param returnValue Value emitted on modal close\n */\n handleAction(action: (() => void) | undefined, returnValue: unknown) {\n if (action) action();\n this.alreadyEmitted = true;\n this.returnValueOnClose = returnValue;\n this.isOpened = false;\n }\n}","<p-dialog\n [modal]=\"true\"\n [(visible)]=\"isOpened\"\n [closable]=\"closable()\"\n [dismissableMask]=\"closeOnOutsideClick()\"\n [draggable]=\"false\"\n [focusOnShow]=\"false\"\n [header]=\"title()\"\n [style]=\"{ 'max-width': modalMaxWidth(), width: 'calc(100% - var(--tk-spacing-base-200))' }\"\n (onHide)=\"handleClose()\"\n (onShow)=\"checkScroll()\"\n class=\"tk-modal\"\n>\n <section class=\"tk-modal__content\">\n @if (content()) {\n @if (isContentString()) {\n <p [innerHTML]=\"content()\" ></p>\n } @else {\n <ng-container *ngComponentOutlet=\"$any(content())\"></ng-container>\n }\n }\n </section>\n\n <ng-template pTemplate=\"footer\">\n @if (hasFooter()) {\n <section class=\"tk-modal__footer\" [class.tk-modal__footer--with-content]=\"hasScroll\">\n @for (btn of footerButtons()!; track $index) {\n <tk-button\n [label]=\"btn.label\"\n [severity]=\"btn.severity\"\n [variant]=\"btn.variant\"\n (clicked)=\"handleAction(btn.action, btn.returnValue)\"\n (keydown.enter)=\"handleAction(btn.action, btn.returnValue)\"\n (keydown.space)=\"handleAction(btn.action, btn.returnValue)\"\n />\n }\n </section>\n }\n </ng-template>\n</p-dialog>\n","import {\n Injectable,\n ApplicationRef,\n ComponentRef,\n Injector,\n createComponent,\n EmbeddedViewRef,\n} from '@angular/core';\nimport { ModalComponent } from '../modal.component';\nimport { Observable, Subject } from 'rxjs';\nimport { ModalConfig } from '../modal.types';\n\n@Injectable({ providedIn: 'root' })\nexport class ModalService {\n private modalRef: ComponentRef<ModalComponent> | null = null;\n\n constructor(\n private readonly injector: Injector,\n private readonly appRef: ApplicationRef\n ) {}\n\n get _modalRefForTesting(): ComponentRef<ModalComponent> | null {\n return this.modalRef;\n }\n set _modalRefForTesting(ref: ComponentRef<ModalComponent> | null) {\n this.modalRef = ref;\n }\n\n open(config: ModalConfig): Observable<unknown> {\n if (this.modalRef) {\n return this.modalRef.instance.onClose.asObservable();\n }\n\n const componentRef = createComponent(ModalComponent, {\n environmentInjector: this.appRef.injector,\n });\n\n this.appRef.attachView(componentRef.hostView);\n\n const domElem = (componentRef.hostView as EmbeddedViewRef<unknown>).rootNodes[0] as HTMLElement;\n document.body.appendChild(domElem);\n\n componentRef.setInput('title', config.title);\n componentRef.setInput('content', config.content ?? null);\n componentRef.setInput('footerButtons', config.footerButtons ?? []);\n componentRef.setInput('size', config.size ?? 'small');\n componentRef.setInput('closable', config.closable ?? true);\n componentRef.setInput('closeOnOutsideClick', config.closeOnOutsideClick ?? false);\n\n const close$ = new Subject<unknown>();\n\n componentRef.instance.onClose.subscribe((value) => {\n close$.next(value);\n close$.complete();\n\n this.appRef.detachView(componentRef.hostView);\n componentRef.destroy();\n this.modalRef = null;\n });\n\n componentRef.instance.open();\n this.modalRef = componentRef;\n\n return close$.asObservable();\n }\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"tekus-design-system-components-modal.mjs","sources":["../../../projects/design-system/components/modal/src/modal.component.ts","../../../projects/design-system/components/modal/src/modal.component.html","../../../projects/design-system/components/modal/src/services/modal.service.ts","../../../projects/design-system/components/modal/tekus-design-system-components-modal.ts"],"sourcesContent":["import {\n Component,\n computed,\n input,\n EventEmitter,\n Type,\n ElementRef,\n model,\n viewChild,\n ViewContainerRef,\n ComponentRef,\n effect,\n OnDestroy,\n untracked,\n afterRender,\n} from '@angular/core';\nimport { ButtonComponent } from '@tekus/design-system/components/button';\nimport { DialogModule } from 'primeng/dialog';\nimport { ModalFooterButton, ModalSizeType } from './modal.types';\nimport {\n TkCanClose,\n TkCloseInterceptor,\n} from '@tekus/design-system/core/types';\n\n/**\n * @component ModalComponent\n * @description\n * A programmatically controlled modal dialog used for displaying dynamic content.\n * Modernized for Angular 19 with 100% synchronous Signal-based closing interception.\n */\n@Component({\n selector: 'tk-modal',\n standalone: true,\n imports: [DialogModule, ButtonComponent],\n templateUrl: './modal.component.html',\n styleUrls: ['./modal.component.scss'],\n})\nexport class ModalComponent<T = unknown> implements OnDestroy {\n private readonly contentHost = viewChild('contentHost', {\n read: ViewContainerRef,\n });\n private componentRef?: ComponentRef<T>;\n\n /** The title displayed at the top of the modal */\n title = input<string>('');\n\n /** The main content of the modal. Can be a string or a Component Type. */\n content = input<string | Type<T> | null>(null);\n\n /** Array of footer buttons with label, callback, and return value */\n footerButtons = input<ModalFooterButton[]>([]);\n\n /** Modal size: 'small', 'large', 'medium' or 'full' */\n size = input<ModalSizeType>('small');\n\n /** Whether the modal can be closed by the user via close button */\n closable = input<boolean>(true);\n\n /** Whether clicking outside the modal mask closes the modal */\n closeOnOutsideClick = input<boolean>(false);\n\n /**\n * Optional data to be passed as inputs to the dynamic component.\n */\n data = input<Partial<T>>({});\n\n /**\n * Optional interceptor called before the modal closes.\n * MUST be synchronous. Returns true to allow closing.\n */\n interceptor = input<TkCloseInterceptor | undefined>(undefined);\n\n /** Computed: whether the content is a simple string */\n isContentString = computed(() => typeof this.content() === 'string');\n\n /** Computed: whether the modal has footer buttons to display */\n hasFooter = computed(() => (this.footerButtons() ?? []).length > 0);\n\n /** Whether the modal should be responsive on mobile screens */\n responsive = input<boolean>(true);\n\n /** Visibility flag as Model Signal (allows two-way binding) */\n isOpened = model<boolean>(false);\n\n /** Whether the modal content has a scrollbar */\n hasScroll = false;\n\n // --- Internals ---\n /** Emits when the modal closes, passing the return value from footer buttons or null */\n readonly onClose = new EventEmitter<unknown>();\n private alreadyEmitted = false;\n private returnValueOnClose: unknown = null;\n\n constructor(private readonly elementRef: ElementRef) {\n /**\n * @summary Orchestrates the reactive dynamic lifecycle.\n */\n\n effect(() => {\n const opened = this.isOpened();\n const host = this.contentHost();\n\n untracked(() => {\n if (opened && host) {\n this.attachDynamicContent();\n } else if (!opened) {\n this.detachDynamicContent();\n }\n });\n });\n\n effect(() => {\n const currentData = this.data();\n untracked(() => this.syncDynamicInputs(currentData));\n });\n\n afterRender(() => {\n this.checkScroll();\n });\n }\n\n /** Computed: calculates modal max-width based on `size` */\n modalMaxWidth = computed(() => {\n switch (this.size()) {\n case 'large':\n return '67.5rem';\n case 'medium':\n return '35rem';\n case 'full':\n return '98vw';\n default:\n return '25rem';\n }\n });\n\n ngOnDestroy(): void {\n this.detachDynamicContent();\n }\n\n /**\n * @summary Orchestrates dynamic rendering and destruction based on visibility.\n * @private\n */\n private attachDynamicContent(): void {\n const type = this.content();\n const host = this.contentHost();\n\n if (!type || typeof type === 'string' || !host) return;\n this.detachDynamicContent();\n this.componentRef = host.createComponent(type);\n this.syncDynamicInputs(this.data());\n }\n\n /**\n * @summary Synchronizes incoming data record with the dynamic instance @Inputs.\n * @private\n */\n private syncDynamicInputs(data: Partial<T>): void {\n if (!this.componentRef) return;\n Object.entries(data).forEach(([key, value]) => {\n this.componentRef?.setInput(key, value);\n });\n }\n\n /**\n * @summary Safely destroys the dynamic component and clears references.\n * @private\n */\n private detachDynamicContent(): void {\n if (this.componentRef) {\n this.componentRef.destroy();\n this.componentRef = undefined;\n }\n }\n\n /**\n * Checks if the modal content has a scrollbar and updates `hasScroll` state.\n */\n checkScroll(): void {\n const contentEl =\n this.elementRef.nativeElement.querySelector('.p-dialog-content');\n if (contentEl) {\n this.hasScroll = contentEl.scrollHeight > contentEl.clientHeight;\n }\n }\n\n /**\n * Opens the modal dialog.\n */\n open(): void {\n this.isOpened.set(true);\n this.resetClosureState();\n }\n\n /**\n * @summary Main entry point for closure requests.\n * Evaluation is 100% synchronous based on current Signal state.\n * @param returnValue (Optional) Value to emit on close.\n */\n tryClose(returnValue: unknown = null): void {\n if (this.canExecuteClosure()) {\n this.executeClosure(returnValue, arguments.length > 0);\n } else {\n const instance = this.componentRef?.instance as TkCanClose | undefined;\n instance?.onBlockedClose?.();\n }\n }\n\n /**\n * @summary Synchronous evaluator of hierarchical guards.\n * @returns true if closure is allowed.\n * @private\n */\n private canExecuteClosure(): boolean {\n const instance = this.componentRef?.instance as TkCanClose | undefined;\n const canClose = instance?.canClose ? instance.canClose() : true;\n if (!canClose) return false;\n\n const configInterceptor = this.interceptor();\n if (configInterceptor && !configInterceptor()) return false;\n\n return true;\n }\n\n /**\n * @summary Unified logic to execute the final closure state change.\n * @private\n */\n private executeClosure(returnValue: unknown, hasReturnValue: boolean): void {\n if (hasReturnValue) {\n this.alreadyEmitted = true;\n this.returnValueOnClose = returnValue;\n }\n this.isOpened.set(false);\n }\n\n /**\n * @summary Handles external visibility changes (from p-dialog 'X' or Escape).\n * Ensures the reactive guard is respected before allowing closure.\n */\n onVisibleChange(visible: boolean): void {\n if (!visible) {\n this.tryClose();\n }\n }\n\n /**\n * Handles the close event from the underlying dialog component.\n */\n handleClose(): void {\n const valueToEmit = this.alreadyEmitted ? this.returnValueOnClose : null;\n this.onClose.emit(valueToEmit);\n this.resetClosureState();\n }\n\n /**\n * @summary Handles actions triggered by footer buttons.\n */\n handleAction(action: (() => void) | undefined, returnValue: unknown): void {\n if (action) action();\n this.tryClose(returnValue);\n }\n\n /**\n * @summary Safely closes the modal forcefully without checks.\n */\n close(): void {\n this.tryClose();\n }\n\n /**\n * @private\n * Encapsulates state cleanup to avoid repetitive assignments\n */\n private resetClosureState(): void {\n this.alreadyEmitted = false;\n this.returnValueOnClose = null;\n }\n}\n","<p-dialog\n [modal]=\"true\"\n [visible]=\"isOpened()\"\n (visibleChange)=\"onVisibleChange($event)\"\n [closable]=\"closable()\"\n [dismissableMask]=\"closeOnOutsideClick()\"\n [draggable]=\"false\"\n [focusOnShow]=\"false\"\n [header]=\"title()\"\n [style]=\"{\n 'max-width': modalMaxWidth(),\n width: 'calc(100% - var(--tk-spacing-base-200))',\n }\"\n (onHide)=\"handleClose()\"\n (onShow)=\"checkScroll()\"\n class=\"tk-modal\">\n <section class=\"tk-modal__content\">\n @if (content()) {\n @if (isContentString()) {\n <p [innerHTML]=\"content()\"></p>\n } @else {\n <ng-template #contentHost></ng-template>\n }\n }\n </section>\n\n <ng-template pTemplate=\"footer\">\n @if (hasFooter()) {\n <section\n class=\"tk-modal__footer\"\n [class.tk-modal__footer--with-content]=\"hasScroll\">\n @for (btn of footerButtons()!; track $index) {\n <tk-button\n [label]=\"btn.label\"\n [severity]=\"btn.severity\"\n [variant]=\"btn.variant\"\n (clicked)=\"handleAction(btn.action, btn.returnValue)\"\n (keydown.enter)=\"handleAction(btn.action, btn.returnValue)\"\n (keydown.space)=\"handleAction(btn.action, btn.returnValue)\" />\n }\n </section>\n }\n </ng-template>\n</p-dialog>\n","import {\n Injectable,\n ApplicationRef,\n ComponentRef,\n Injector,\n createComponent,\n EmbeddedViewRef,\n} from '@angular/core';\nimport { ModalComponent } from '../modal.component';\nimport { Observable, Subject } from 'rxjs';\nimport { ModalConfig } from '../modal.types';\n\n/**\n * @service ModalService\n * @description\n * Service responsible for programmatically opening and managing modal dialogs.\n * It handles component creation, attachment to the document body, and cleanup.\n */\n@Injectable({ providedIn: 'root' })\nexport class ModalService {\n /** Reference to the currently open modal component */\n private modalRef: ComponentRef<ModalComponent> | null = null;\n\n constructor(\n private readonly injector: Injector,\n private readonly appRef: ApplicationRef\n ) {}\n\n /** Internal getter for testing purposes */\n get _modalRefForTesting(): ComponentRef<ModalComponent> | null {\n return this.modalRef;\n }\n /** Internal setter for testing purposes */\n set _modalRefForTesting(ref: ComponentRef<ModalComponent> | null) {\n this.modalRef = ref;\n }\n\n /**\n * Opens a modal dialog with the provided configuration.\n * Only one modal can be open at a time.\n * @param config Configuration object for the modal (title, content, buttons, etc.)\n * @returns An observable that emits the modal's return value when it closes.\n */\n open(config: ModalConfig): Observable<unknown> {\n if (this.modalRef) {\n return this.modalRef.instance.onClose.asObservable();\n }\n\n const componentRef = createComponent(ModalComponent, {\n environmentInjector: this.appRef.injector,\n });\n\n this.appRef.attachView(componentRef.hostView);\n\n const domElem = (componentRef.hostView as EmbeddedViewRef<unknown>).rootNodes[0] as HTMLElement;\n document.body.appendChild(domElem);\n\n componentRef.setInput('title', config.title);\n componentRef.setInput('content', config.content);\n componentRef.setInput('footerButtons', config.footerButtons || []);\n componentRef.setInput('size', config.size || 'small');\n componentRef.setInput('closable', config.closable ?? true);\n componentRef.setInput('closeOnOutsideClick', config.closeOnOutsideClick ?? false);\n componentRef.setInput('interceptor', config.interceptor);\n componentRef.setInput('data', config.data || {});\n\n const close$ = new Subject<unknown>();\n\n componentRef.instance.onClose.subscribe((value) => {\n close$.next(value);\n close$.complete();\n\n this.appRef.detachView(componentRef.hostView);\n componentRef.destroy();\n this.modalRef = null;\n });\n\n componentRef.instance.open();\n this.modalRef = componentRef;\n\n return close$.asObservable();\n }\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}