@filip.mazev/modal 0.1.27 → 0.1.28

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/README.md CHANGED
@@ -58,12 +58,12 @@ Other themes are also available such as:
58
58
 
59
59
  ### Create a Modal Component
60
60
 
61
- Your modal content components must extend `Modal<TData, TResult>`:
61
+ Your modal content components must extend `IModal<TData, TResult>`:
62
62
 
63
63
  Typescript:
64
64
 
65
65
  ```typescript
66
- import { Modal, MODAL_DATA, ModalHeaderDirective, ModalFooterDirective } from '@filip.mazev/modal';
66
+ import { Modal, ModalHeaderDirective, ModalFooterDirective } from '@filip.mazev/modal';
67
67
 
68
68
  @Component({
69
69
  selector: 'app-my-modal-component',
@@ -75,19 +75,7 @@ import { Modal, MODAL_DATA, ModalHeaderDirective, ModalFooterDirective } from '@
75
75
  styleUrl: './my-modal-component.scss',
76
76
  })
77
77
  export class MyModalComponent extends Modal<MyData, MyResult> {
78
- protected data = inject<MyData>(MODAL_DATA);
79
78
 
80
- constructor() {
81
- super();
82
- }
83
-
84
- override afterModalGet(): void {
85
- // This is called after the modal service has returned the instance of the generated modal to this component. You can access this.modal only after this
86
- }
87
-
88
- override onDestroy(): void {
89
- // Perform onDestroy logic here as this method is called on ngOnDestroy on Modal
90
- }
91
79
  }
92
80
  ```
93
81
 
@@ -98,9 +86,13 @@ HTML (Template):
98
86
  Example Header
99
87
  </div>
100
88
 
101
- <div>
102
- Lorem ipsum dolor sit, amet consectetur adipisicing elit. A, iste voluptatum accusamus facere explicabo impedit exercitationem dolore...
103
- </div>
89
+ <h1>
90
+ {{ data.Title }}
91
+ </h1>
92
+
93
+ <p>
94
+ {{ data.Body }}
95
+ </p>
104
96
 
105
97
  <div *modalFooter>
106
98
  Example Footer
@@ -127,7 +119,6 @@ Accessible via this.modal inside any component that extends Modal. This referenc
127
119
  * `componentRef: (ComponentRef<C>)` The Angular ComponentRef of the content component (your component) inside the modal.
128
120
  * `modalContainerRef: (ComponentRef<ModalComponent>)` The Angular ComponentRef of the wrapper container (the shell responsible for the backdrop, animations, and layout).
129
121
  * `modalContainerElement: (HTMLElement)` The native HTML element of the modal container.
130
- * `selfIdentifier: ({ constructor: Function })` An object identifying the constructor of the component, used internally for instance tracking.
131
122
 
132
123
  #### `IModalCloseResult`
133
124
 
@@ -171,8 +162,9 @@ modalRef.afterClosed().subscribe(result: IModalCloseResult<MyData> => {
171
162
  Controls the behavior and content of the modal container:
172
163
 
173
164
  * `open` |`boolean`|: (optional) Whether the modal should be open or not, will default to true.
174
- * `afterClose` |``Function``|: (optional) The function to run after the modal closes.
175
- * `confirmCloseConfig` |``IModalConfirmCloseConfig<ConfirmModalData, ConfirmModal>``|: (optional) The configuration for the confirm close modal (e.g., a "Discard changes?" prompt), will default to `{ confirmClose: false }`. `ConfirmModalData and ConfirmModal` need to be specified.
165
+ * `afterClose` |`Function`|: (optional) The function to run after the modal closes.
166
+ * `closeGuard` |`ModalCloseGuard`| (optional) The guard that will determine whether the modal can be closed or not
167
+ * `closeGuardOnlyOnCancel` |`boolean`| (optional) Whether the close guard should only be checked on cancel actions, will default to true
176
168
  * `disableClose` |`boolean`|: (optional) Whether the modal should be closable or not, will default to false. This applies to the close button, escape key, and backdrop.
177
169
  * `disableCloseOnBackdropClick` |`boolean`|: (optional) Whether the modal shouldn't be closable specifically when the user clicks on the backdrop, will default to false.
178
170
  * `disableCloseOnNavigation` |`boolean`|: (optional) Whether the modal should remain open when the user navigates away from the current page, will default to false.
@@ -203,18 +195,6 @@ Controls the visual appearance:
203
195
  * `wrapperStyles` |`string`|: (optional) Inline CSS styles to apply to the wrapper of the modal.
204
196
  * `overrideFullHeight` |`boolean`|: (optional) Whether the modal should override the default full-height restriction or not, will default to false.
205
197
 
206
- ### `IModalConfirmCloseConfig`
207
-
208
- Configuration for the confirmation modal triggered when a user attempts to close the parent modal (e.g., "Unsaved Changes"):
209
-
210
- * `confirmModalComponent` |`ComponentType<ConfirmModal>`|: (required) The component class to use for the confirmation modal.
211
- * `confirmClose` |`boolean`|: (required) Whether the confirmation flow should be active. If false, the modal closes immediately without confirmation.
212
- * `confirmOnSubmit` |`boolean`|: (optional) Whether the confirmation should also trigger when the modal is closed via a "submit" (success) action. Defaults to false (meaning confirmation is only required for 'cancel' or backdrop closure).
213
- * `style` |`IModalStyleConfig`|: (optional) The visual style configuration for the confirmation modal instance.
214
- * `data` |`ConfirmModalData`|: (optional) The data to pass to the confirmation component. The component needs to use the @Inject(MODAL_DATA) decorator to receive this.
215
- * `bannerText` |`string`|: (optional) The text to display in the header banner of the confirmation modal.
216
- * `bypassSelfCheck` |`boolean`|: (optional) Whether the modal should bypass the internal check that ensures the confirmation is attached to the specific closing modal. Defaults to false.
217
-
218
198
  ### `IBottomSheetModalConfig`
219
199
 
220
200
  Configuration for the mobile-optimized bottom sheet modal (bottom sheet):
@@ -1,30 +1,11 @@
1
- import { DOCUMENT, NgClass, NgTemplateOutlet } from '@angular/common';
2
1
  import * as i0 from '@angular/core';
3
- import { inject, Injectable, InjectionToken, Injector, ApplicationRef, EnvironmentInjector, RendererFactory2, createComponent, input, output, Component, signal, computed, ViewChild, ViewChildren, effect, ViewContainerRef, TemplateRef, Directive } from '@angular/core';
4
- import { BehaviorSubject, Subject, takeUntil, filter, skip, map, fromEvent, take } from 'rxjs';
2
+ import { InjectionToken, inject, Injector, ApplicationRef, EnvironmentInjector, RendererFactory2, createComponent, Injectable, input, output, Component, signal, computed, ViewChild, ViewChildren, effect, ViewContainerRef, TemplateRef, Directive } from '@angular/core';
3
+ import { BehaviorSubject, Subject, filter, skip, fromEvent, Observable, from, of, take } from 'rxjs';
4
+ import { DOCUMENT, NgClass, NgTemplateOutlet } from '@angular/common';
5
5
  import { Router, NavigationEnd } from '@angular/router';
6
6
  import { uuidv4, WindowDimensionsService, ScrollLockService, DeviceTypeService } from '@filip.mazev/blocks-core';
7
7
  import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
8
8
 
9
- const EMPTY_STRING = '';
10
-
11
- var ModalWarnings;
12
- (function (ModalWarnings) {
13
- //#region Multi-level modals
14
- ModalWarnings["NO_PARENT_PROVIDED"] = "No parent modal provided for multilevel modal. Please provide a parent modal or set openAsMultilevel to false.";
15
- ModalWarnings["MULTILEVEL_INLINE_NOT_SUPPORTED"] = "As of this version, opening a multi-level modal from inline HTML is not possible. Please set openAsMultilevel to false or open your modal through the ModalService! Opening modal as normal modal.";
16
- ModalWarnings["MULTILEVEL_NO_PARENT"] = "Cannot open a multilevel modal with only one modal open. Please open another modal before opening a multilevel modal or set openAsMultilevel to false.";
17
- ModalWarnings["PARENT_MODAL_NOT_SET"] = "Error setting parent modal. Opening modal as normal modal";
18
- //#endregion
19
- //#region Confirm close
20
- ModalWarnings["CONFIRM_MODAL_NESTING_NOT_SUPPORTED"] = "Cannot open a confirm modal from within a confirm modal. If you want to allow this behaviour, set bypassSelfCheck to true in the confirmCloseConfig.";
21
- //#endregion
22
- //#region Directive usage
23
- ModalWarnings["FOOTER_DIRECTIVE_OUTSIDE_MODAL"] = "[ModalFooter] Directive used outside of a ModalComponent.";
24
- ModalWarnings["HEADER_DIRECTIVE_OUTSIDE_MODAL"] = "[ModalHeader] Directive used outside of a ModalComponent.";
25
- //#endregion
26
- })(ModalWarnings || (ModalWarnings = {}));
27
-
28
9
  class ModalStyleConfig {
29
10
  layout;
30
11
  breakpoints;
@@ -46,8 +27,8 @@ class ModalStyleConfig {
46
27
  this.showCloseButton = config?.showCloseButton ?? true;
47
28
  this.mobileConfig = config?.mobileConfig ?? {};
48
29
  this.contentWrapper = config?.contentWrapper ?? true;
49
- this.wrapperClasses = config?.wrapperClasses ?? EMPTY_STRING;
50
- this.wrapperStyles = config?.wrapperStyles ?? EMPTY_STRING;
30
+ this.wrapperClasses = config?.wrapperClasses ?? "";
31
+ this.wrapperStyles = config?.wrapperStyles ?? "";
51
32
  this.overrideFullHeight = config?.overrideFullHeight ?? false;
52
33
  }
53
34
  }
@@ -55,7 +36,8 @@ class ModalStyleConfig {
55
36
  class ModalConfig {
56
37
  open;
57
38
  afterClose;
58
- confirmCloseConfig;
39
+ closeGuard;
40
+ closeGuardOnlyOnCancel;
59
41
  disableClose;
60
42
  disableCloseOnBackdropClick;
61
43
  disableCloseOnNavigation;
@@ -72,7 +54,8 @@ class ModalConfig {
72
54
  constructor(config) {
73
55
  this.open = config?.open ?? true;
74
56
  this.afterClose = config?.afterClose;
75
- this.confirmCloseConfig = config?.confirmCloseConfig;
57
+ this.closeGuard = config?.closeGuard;
58
+ this.closeGuardOnlyOnCancel = config?.closeGuardOnlyOnCancel ?? true;
76
59
  this.disableClose = config?.disableClose ?? false;
77
60
  this.disableCloseOnBackdropClick = config?.disableCloseOnBackdropClick ?? false;
78
61
  this.disableCloseOnNavigation = config?.disableCloseOnNavigation ?? false;
@@ -80,9 +63,9 @@ class ModalConfig {
80
63
  this.webkitOnlyOverflowMobileHandling = config?.webkitOnlyOverflowMobileHandling ?? true;
81
64
  this.data = config?.data ?? null;
82
65
  this.style = new ModalStyleConfig(config?.style);
83
- this.bannerText = config?.bannerText ?? EMPTY_STRING;
84
- this.contentClasses = config?.contentClasses ?? EMPTY_STRING;
85
- this.contentStyles = config?.contentStyles ?? EMPTY_STRING;
66
+ this.bannerText = config?.bannerText ?? "";
67
+ this.contentClasses = config?.contentClasses ?? "";
68
+ this.contentStyles = config?.contentStyles ?? "";
86
69
  this.disableConsoleWarnings = config?.disableConsoleWarnings ?? false;
87
70
  this.disableConsoleInfo = config?.disableConsoleInfo ?? false;
88
71
  this.id = config?.id ?? uuidv4();
@@ -98,6 +81,7 @@ var ModalState;
98
81
  })(ModalState || (ModalState = {}));
99
82
 
100
83
  class ModalRef {
84
+ componentType;
101
85
  modalService;
102
86
  //#region Modal Container
103
87
  _modalContainer = {};
@@ -147,13 +131,6 @@ class ModalRef {
147
131
  getStatus() {
148
132
  return this._modalState;
149
133
  }
150
- _selfIdentifier = {};
151
- set selfIdentifier(selfIdentifier) {
152
- this._selfIdentifier = selfIdentifier;
153
- }
154
- get selfIdentifier() {
155
- return this._selfIdentifier;
156
- }
157
134
  _modalConfig = undefined;
158
135
  set modalConfig(modalConfig) {
159
136
  this._modalConfig = modalConfig;
@@ -171,6 +148,10 @@ class ModalRef {
171
148
  this.backdropClickSubject.next(event);
172
149
  }
173
150
  afterCloseSubject = new Subject();
151
+ /**
152
+ * Observable that emits when the modal has been closed.
153
+ * @returns An Observable that emits an IModalCloseResult<R> when the modal is closed.
154
+ */
174
155
  afterClosed() {
175
156
  return this.afterCloseSubject.asObservable();
176
157
  }
@@ -178,13 +159,13 @@ class ModalRef {
178
159
  this.afterCloseSubject.next(result);
179
160
  }
180
161
  //#endregion
181
- constructor(componentRef, selfIdentifier, modalContainerRef, modalService, modalConfig) {
162
+ constructor(componentRef, componentType, modalContainerRef, modalService, modalConfig) {
163
+ this.componentType = componentType;
182
164
  this.modalService = modalService;
183
165
  this.modalConfig = modalConfig;
184
166
  this.modalContainerRef = modalContainerRef;
185
167
  this.modalContainerElement = modalContainerRef.location.nativeElement;
186
168
  this.componentRef = componentRef;
187
- this.selfIdentifier = selfIdentifier;
188
169
  }
189
170
  //#region Public Methods
190
171
  async open() {
@@ -217,62 +198,11 @@ class ModalRef {
217
198
  this._modalState = ModalState.CLOSED;
218
199
  this.modalState.next(ModalState.CLOSED);
219
200
  this.afterClose(result);
220
- this.modalService?.close(this.selfIdentifier, true);
201
+ this.modalService?.unregister(this);
221
202
  }, this.modalContainerRef.instance.animationDuration);
222
203
  }
223
204
  }
224
205
 
225
- class Modal {
226
- ModalService = inject(ModalService);
227
- modal;
228
- modalGetSubscription$ = new Subject();
229
- constructor() {
230
- this.createModalSubscription();
231
- }
232
- ngOnDestroy() {
233
- this.onDestroy();
234
- this.unsubscribeModalGet();
235
- }
236
- createModalSubscription() {
237
- this.modalGetSubscription$ = new Subject();
238
- this.ModalService
239
- .getSubscribe(this.constructor)
240
- .pipe(takeUntil(this.modalGetSubscription$))
241
- .subscribe((modal) => {
242
- if (modal instanceof ModalRef) {
243
- this.modal = modal;
244
- this.afterModalGet();
245
- this.modalGetSubscription$.next();
246
- this.modalGetSubscription$.complete();
247
- }
248
- });
249
- }
250
- unsubscribeModalGet() {
251
- this.modalGetSubscription$.next();
252
- this.modalGetSubscription$.complete();
253
- }
254
- close() {
255
- this.modal?.close();
256
- }
257
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.1", ngImport: i0, type: Modal, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
258
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.1", ngImport: i0, type: Modal });
259
- }
260
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.1", ngImport: i0, type: Modal, decorators: [{
261
- type: Injectable
262
- }], ctorParameters: () => [] });
263
-
264
- var ModalErrors;
265
- (function (ModalErrors) {
266
- //#region General
267
- ModalErrors["MODAL_DOESNT_MATCH_THE_REQUESTED_TYPES"] = "The modal doesn't match the requested types.";
268
- //#endregion
269
- //#region Multi-level modals
270
- ModalErrors["PARENT_MODAL_CANT_BE_THE_SAME_AS_CHILD"] = "Parent modal cannot be the same as the child modal";
271
- ModalErrors["PARENT_MODAL_NOT_FOUND"] = "Parent modal does not exist";
272
- ModalErrors["PARENT_MODAL_NOT_PROVIDED"] = "No parent modal provided for multilevel modal";
273
- //#endregion
274
- })(ModalErrors || (ModalErrors = {}));
275
-
276
206
  const MODAL_DATA = new InjectionToken("MODAL_DATA");
277
207
 
278
208
  class ModalService {
@@ -284,14 +214,14 @@ class ModalService {
284
214
  renderer;
285
215
  document = inject(DOCUMENT);
286
216
  //#region Properties
287
- modals = new Map();
217
+ modals = new Set();
288
218
  modalsSubject = new BehaviorSubject(this.modals);
289
219
  //#endregion
290
220
  constructor() {
291
221
  this.renderer = this.rendererFactory.createRenderer(null, null);
292
222
  this.createSubscriptions();
293
223
  }
294
- //#region Private Methods
224
+ //#region Subscription Methods
295
225
  createSubscriptions() {
296
226
  this.router.events
297
227
  .pipe(filter((event) => event instanceof NavigationEnd), skip(1), takeUntilDestroyed())
@@ -303,6 +233,12 @@ class ModalService {
303
233
  }
304
234
  //#endregion
305
235
  //#region Public Methods
236
+ /**
237
+ * Opens a modal with the specified component and configuration.
238
+ * @param component The component to be displayed in the modal.
239
+ * @param config Optional configuration for the modal.
240
+ * @returns A ModalRef instance representing the opened modal.
241
+ */
306
242
  open(component, config) {
307
243
  const dataInjector = Injector.create({
308
244
  providers: [{ provide: MODAL_DATA, useValue: config?.data }],
@@ -327,6 +263,10 @@ class ModalService {
327
263
  this.appRef.attachView(wrapperRef.hostView);
328
264
  this.document.body.appendChild(wrapperRef.location.nativeElement);
329
265
  const modal = new ModalRef(contentRef, component, wrapperRef, this, new ModalConfig(config));
266
+ if (this.isIModal(contentRef.instance)) {
267
+ contentRef.instance.modal = modal;
268
+ contentRef.instance.onModalInit();
269
+ }
330
270
  wrapperRef.onDestroy(() => {
331
271
  this.appRef.detachView(wrapperRef.hostView);
332
272
  wrapperRef.location.nativeElement.remove();
@@ -336,66 +276,64 @@ class ModalService {
336
276
  this.renderer.setStyle(modalElement, 'width', '100%');
337
277
  this.renderer.setStyle(modalElement, 'display', 'flex');
338
278
  this.renderer.setStyle(modalElement, 'flex-grow', '1');
339
- this.modals.set(modal.selfIdentifier, modal);
279
+ this.modals.add(modal);
340
280
  this.modalsSubject.next(this.modals);
341
281
  modal.open();
342
282
  return modal;
343
283
  }
344
- close(self, fromCloseFunction = false) {
345
- if (this.modals.has(self)) {
346
- if (fromCloseFunction !== true) {
347
- const modal = this.modals.get(self);
348
- if (modal && modal instanceof ModalRef) {
349
- modal.close();
350
- }
351
- }
352
- this.modals.delete(self);
353
- this.modalsSubject.next(this.modals);
354
- }
284
+ /**
285
+ * Closes the specified modal.
286
+ * @param modal The ModalRef instance representing the modal to be closed.
287
+ */
288
+ close(modal) {
289
+ modal.close();
290
+ }
291
+ /**
292
+ * Unregisters the specified modal from the service.
293
+ * @param modal The ModalRef instance representing the modal to be unregistered.
294
+ */
295
+ unregister(modal) {
296
+ this.modals.delete(modal);
297
+ this.modalsSubject.next(this.modals);
355
298
  }
299
+ /**
300
+ * Closes all open modals.
301
+ * @param onNavigate Indicates if the closeAll is triggered by navigation event.
302
+ */
356
303
  closeAll(onNavigate = false) {
357
304
  this.modals.forEach((modal) => {
358
- if (modal instanceof ModalRef) {
359
- if (modal.modalConfig?.disableCloseOnNavigation !== true || !onNavigate) {
360
- modal.close("cancel", undefined, true);
361
- }
305
+ if (modal.modalConfig?.disableCloseOnNavigation !== true || !onNavigate) {
306
+ modal.close("cancel", undefined, true);
362
307
  }
363
308
  });
364
- this.modals.clear();
365
- this.modalsSubject.next(this.modals);
366
- }
367
- get(self) {
368
- const modal = this.modals.get(self);
369
- this.modalRequestedTypeCheck(modal);
370
- return modal;
371
309
  }
372
- getSubscribe(self) {
373
- return this.modalsSubject.asObservable().pipe(map((modals) => {
374
- const modal = modals.get(self);
375
- this.modalRequestedTypeCheck(modal);
376
- return modal;
377
- }));
310
+ /**
311
+ * Finds if a modal with the specified component type is currently open.
312
+ * @param componentType The component type to search for.
313
+ * @returns True if a modal with the specified component type is open, false otherwise.
314
+ */
315
+ find(componentType) {
316
+ for (const modal of this.modals) {
317
+ if (modal.componentRef.componentType === componentType) {
318
+ return true;
319
+ }
320
+ }
321
+ return false;
378
322
  }
323
+ /**
324
+ * Gets the count of currently open modals.
325
+ * @returns The number of open modals.
326
+ */
379
327
  modalsCount() {
380
328
  return this.modals.size;
381
329
  }
382
- find(self) {
383
- return this.modals.has(self);
384
- }
385
330
  //#endregion
386
331
  //#region Helper Methods
387
- modalRequestedTypeCheck(modal) {
388
- if (modal) {
389
- if (modal instanceof ModalRef) {
390
- if (!(modal.componentRef.instance instanceof Modal)) {
391
- throw new Error(ModalErrors.MODAL_DOESNT_MATCH_THE_REQUESTED_TYPES);
392
- }
393
- }
394
- else if (!(modal instanceof ModalCore)) {
395
- throw new Error(ModalErrors.MODAL_DOESNT_MATCH_THE_REQUESTED_TYPES);
396
- }
397
- }
398
- return true;
332
+ isIModal(component) {
333
+ return (typeof component === 'object' &&
334
+ component !== null &&
335
+ 'onModalInit' in component &&
336
+ typeof component.onModalInit === 'function');
399
337
  }
400
338
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.1", ngImport: i0, type: ModalService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
401
339
  static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.1", ngImport: i0, type: ModalService, providedIn: "root" });
@@ -699,23 +637,27 @@ class ModalCore {
699
637
  deviceTypeService = inject(DeviceTypeService);
700
638
  afterClose = output();
701
639
  animationDuration = MODAL_DEFAULT_ANIM_DURATION;
640
+ /** The component reference (the component that is being displayed in the modal) */
702
641
  componentRef = {};
642
+ /** The configuration for the modal */
703
643
  config;
704
644
  closeFunction;
705
645
  backdropClickSubject = new Subject();
646
+ /* Observable for backdrop clicks */
706
647
  backdropClick = this.backdropClickSubject.asObservable();
648
+ /* Whether the modal is open or not */
707
649
  isOpen = signal(true, ...(ngDevMode ? [{ debugName: "isOpen" }] : []));
708
- isBottomSheetModalActive = signal(false, ...(ngDevMode ? [{ debugName: "isBottomSheetModalActive" }] : []));
709
650
  effectiveLayout = signal('center', ...(ngDevMode ? [{ debugName: "effectiveLayout" }] : []));
710
651
  isCentered = signal(false, ...(ngDevMode ? [{ debugName: "isCentered" }] : []));
711
652
  isSide = signal(false, ...(ngDevMode ? [{ debugName: "isSide" }] : []));
712
653
  headerTemplate = signal(null, ...(ngDevMode ? [{ debugName: "headerTemplate" }] : []));
713
654
  footerTemplate = signal(null, ...(ngDevMode ? [{ debugName: "footerTemplate" }] : []));
655
+ isBottomSheetModalActive = signal(false, ...(ngDevMode ? [{ debugName: "isBottomSheetModalActive" }] : []));
714
656
  isAnimated = false;
715
657
  hasBanner = false;
716
658
  hasDefaultContentWrapperClass = false;
717
659
  isConfirmCloseModalOpen = false;
718
- isSpecialMobileOverflowHandlingEnabled = false;
660
+ isScrollHandlingEnabled = false;
719
661
  isScrollDisabled = false;
720
662
  _sortedBreakpoints = [];
721
663
  windowDimensions = this.windowDimensionsService.dimensions;
@@ -765,7 +707,10 @@ class ModalCore {
765
707
  //#endregion
766
708
  //#region Initialization Methods
767
709
  initParamsFromConfig() {
768
- this.isSpecialMobileOverflowHandlingEnabled = (this.deviceTypeService.getDeviceState().isAppleDevice || this.config?.webkitOnlyOverflowMobileHandling === false);
710
+ this.isScrollHandlingEnabled =
711
+ this.deviceTypeService.getDeviceState().isAppleDevice
712
+ || this.config?.webkitOnlyOverflowMobileHandling === false
713
+ || this.config?.enableExtremeOverflowHandling === true;
769
714
  this.hasBanner =
770
715
  this.config !== undefined &&
771
716
  ((this.config.bannerText !== undefined && this.config.bannerText.length > 0) ||
@@ -795,50 +740,40 @@ class ModalCore {
795
740
  }
796
741
  //#endregion
797
742
  //#region Closing Methods
743
+ /**
744
+ * Closes the modal with the specified state and result.
745
+ * @param {ModalCloseMode} state The state of the modal close (e.g., 'confirm', 'cancel').
746
+ * @param {R | undefined} result The result to be returned when the modal closes.
747
+ * @param {boolean} fromInsideInteraction Whether the close was triggered from inside the modal interaction.
748
+ * @param {boolean} forceClose Whether to force close the modal, bypassing the close guards.
749
+ */
798
750
  close(state = "cancel", result = undefined, fromInsideInteraction = false, forceClose = false) {
799
- if (this.isConfirmCloseModalOpen)
751
+ if (forceClose) {
752
+ this.handleClose(state, result);
800
753
  return;
801
- if (this.isScrollDisabled) {
802
- this.scrollLockService.enableScroll(this.config?.enableExtremeOverflowHandling ?? false);
803
754
  }
804
- if ((this.config && this.config?.disableClose !== true) || !fromInsideInteraction || forceClose) {
805
- if (this.config?.confirmCloseConfig && this.shouldOpenConfirmCloseModal(forceClose, state)) {
806
- if (this.shouldOpenConfirmCloseModalSelfCheck()) {
807
- const modal = this.modalService.open(this.config.confirmCloseConfig.confirmModalComponent, {
808
- style: this.config.confirmCloseConfig.style ?? {
809
- breakpoints: undefined,
810
- },
811
- disableClose: true,
812
- bannerText: this.config.confirmCloseConfig.bannerText ?? EMPTY_STRING,
813
- data: this.config.confirmCloseConfig.data ?? null,
814
- });
815
- this.isConfirmCloseModalOpen = true;
816
- if (this.isBottomSheetModalActive()) {
817
- this.resetBottomSheetModalLayout();
818
- }
819
- modal.afterClosed()
820
- .pipe(take(1))
821
- .subscribe((_result) => {
822
- this.isConfirmCloseModalOpen = false;
823
- if (_result.state === 'confirm') {
824
- this.handleClose(state, result);
825
- }
826
- });
755
+ if ((this.config?.disableClose === true) && fromInsideInteraction) {
756
+ return;
757
+ }
758
+ const shouldCheckCloseGuard = this.config?.closeGuardOnlyOnCancel !== true || state !== "confirm";
759
+ if (shouldCheckCloseGuard && this.config?.closeGuard) {
760
+ const guardResult = this.config.closeGuard.canClose(this.modalService);
761
+ const canClose$ = guardResult instanceof Observable ? guardResult :
762
+ guardResult instanceof Promise ? from(guardResult) :
763
+ of(guardResult);
764
+ canClose$.pipe(take(1)).subscribe((canClose) => {
765
+ if (canClose) {
766
+ this.handleClose(state, result);
827
767
  }
828
768
  else {
829
- if (this.config?.disableConsoleWarnings !== true) {
830
- console.warn(ModalWarnings.CONFIRM_MODAL_NESTING_NOT_SUPPORTED);
769
+ if (this.isBottomSheetModalActive()) {
770
+ this.resetBottomSheetModalLayout();
831
771
  }
832
- this.handleClose(state, result);
833
772
  }
834
- }
835
- else {
836
- this.handleClose(state, result);
837
- }
838
- }
839
- else if (this.isBottomSheetModalActive()) {
840
- this.resetBottomSheetModalLayout();
773
+ });
774
+ return;
841
775
  }
776
+ this.handleClose(state, result);
842
777
  }
843
778
  async handleClose(state, result) {
844
779
  this.isOpen.set(false);
@@ -858,28 +793,6 @@ class ModalCore {
858
793
  }
859
794
  }
860
795
  //#endregion
861
- //#region Logical Assertions
862
- shouldOpenConfirmCloseModal(forceClose, state) {
863
- if (this.config?.confirmCloseConfig) {
864
- const closeNotCalledWithForceClose = !forceClose;
865
- if (closeNotCalledWithForceClose) {
866
- const configuredForConfirmClose = this.config.confirmCloseConfig.confirmClose === true;
867
- const closeCalledWithCancelState = state === "cancel";
868
- const configuredForConfirmCloseOnSubmit = this.config.confirmCloseConfig.confirmOnSubmit === true;
869
- return configuredForConfirmClose && (closeCalledWithCancelState || configuredForConfirmCloseOnSubmit);
870
- }
871
- }
872
- return false;
873
- }
874
- shouldOpenConfirmCloseModalSelfCheck() {
875
- if (this.config?.confirmCloseConfig) {
876
- const hasSelfIdentifier = this.componentRef && this.componentRef.instance && this.componentRef.instance.modal && this.componentRef.instance.modal.selfIdentifier !== EMPTY_STRING;
877
- const bypassSelfCheck = this.config.confirmCloseConfig.bypassSelfCheck === true;
878
- return hasSelfIdentifier || bypassSelfCheck;
879
- }
880
- return false;
881
- }
882
- //#endregion
883
796
  //#region Layout Methods
884
797
  handleLayout(width) {
885
798
  if (!this.config)
@@ -914,18 +827,20 @@ class ModalCore {
914
827
  }
915
828
  handleBottomSheetModalOpened() {
916
829
  this.isBottomSheetModalActive.set(true);
917
- this.scrollLockService.disableScroll({
918
- mainContainer: this.modalContainer?.nativeElement,
919
- handleExtremeOverflow: this.config?.enableExtremeOverflowHandling ?? false,
920
- animationDuration: this.animationDuration,
921
- handleTouchInput: true,
922
- mobileOnlyTouchPrevention: true,
923
- });
924
- this.isScrollDisabled = true;
830
+ if (this.isScrollHandlingEnabled && !this.isScrollDisabled) {
831
+ this.scrollLockService.disableScroll({
832
+ mainContainer: this.modalContainer?.nativeElement,
833
+ handleExtremeOverflow: this.config?.enableExtremeOverflowHandling ?? false,
834
+ animationDuration: this.animationDuration,
835
+ handleTouchInput: true,
836
+ mobileOnlyTouchPrevention: true,
837
+ });
838
+ this.isScrollDisabled = true;
839
+ }
925
840
  }
926
841
  handleBottomSheetModalClosed() {
927
842
  this.isBottomSheetModalActive.set(false);
928
- if (this.isOpen() && this.isSpecialMobileOverflowHandlingEnabled) {
843
+ if (this.isOpen() && this.isScrollHandlingEnabled && this.isScrollDisabled) {
929
844
  this.scrollLockService.enableScroll(this.config?.enableExtremeOverflowHandling ?? false);
930
845
  this.isScrollDisabled = false;
931
846
  }
@@ -976,10 +891,40 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.1", ngImpor
976
891
  args: [ModalCentered]
977
892
  }] } });
978
893
 
979
- const LOWEST_CHARACTER_CAP = 23;
980
- const LOWER_CHARACTER_CAP = 33;
981
- const MID_CHARACTER_CAP = 49;
982
- const UPPER_CHARACTER_CAP = 60;
894
+ class Modal {
895
+ /**
896
+ * Data injected into the modal component.
897
+ */
898
+ data = inject(MODAL_DATA);
899
+ /**
900
+ * Reference to the ModalRef instance associated with this modal.
901
+ */
902
+ modal;
903
+ /**
904
+ * Called when the modal is initialized.
905
+ */
906
+ onModalInit() { }
907
+ /**
908
+ * Closes the modal (with "cancel" reason) with an optional result.
909
+ * @param result The result to be passed when closing the modal.
910
+ */
911
+ close(result) {
912
+ this.modal?.close('cancel', result);
913
+ }
914
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.1", ngImport: i0, type: Modal, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
915
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.1", ngImport: i0, type: Modal });
916
+ }
917
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.1", ngImport: i0, type: Modal, decorators: [{
918
+ type: Injectable
919
+ }] });
920
+
921
+ var ModalWarnings;
922
+ (function (ModalWarnings) {
923
+ //#region Directive usage
924
+ ModalWarnings["FOOTER_DIRECTIVE_OUTSIDE_MODAL"] = "[ModalFooter] Directive used outside of a ModalComponent.";
925
+ ModalWarnings["HEADER_DIRECTIVE_OUTSIDE_MODAL"] = "[ModalHeader] Directive used outside of a ModalComponent.";
926
+ //#endregion
927
+ })(ModalWarnings || (ModalWarnings = {}));
983
928
 
984
929
  class ModalFooterDirective {
985
930
  templateRef = inject(TemplateRef);
@@ -1011,5 +956,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.1", ngImpor
1011
956
  * Generated bundle index. Do not edit.
1012
957
  */
1013
958
 
1014
- export { EMPTY_STRING, LOWER_CHARACTER_CAP, LOWEST_CHARACTER_CAP, MID_CHARACTER_CAP, MODAL_DATA, MODAL_DEFAULT_ANIM_DURATION, MODAL_DEFAULT_ANIM_DURATION_MULTIPLIER_LARGE, MODAL_DEFAULT_ANIM_DURATION_MULTIPLIER_SMALL, MODAL_DEFAULT_INTERNAL_ANIM_DURATION, MODAL_DOWN_SWIPE_LIMIT, MODAL_SWIPE_VELOCITY_THRESHOLD, Modal, ModalBackdrop, ModalBanner, ModalBottomSheet, ModalCentered, ModalConfig, ModalCore, ModalErrors, ModalFooterDirective, ModalRef, ModalService, ModalSide, ModalState, ModalStyleConfig, ModalWarnings, UPPER_CHARACTER_CAP };
959
+ export { MODAL_DATA, MODAL_DEFAULT_ANIM_DURATION, MODAL_DEFAULT_ANIM_DURATION_MULTIPLIER_LARGE, MODAL_DEFAULT_ANIM_DURATION_MULTIPLIER_SMALL, MODAL_DEFAULT_INTERNAL_ANIM_DURATION, MODAL_DOWN_SWIPE_LIMIT, MODAL_SWIPE_VELOCITY_THRESHOLD, Modal, ModalBackdrop, ModalBanner, ModalBottomSheet, ModalCentered, ModalConfig, ModalCore, ModalFooterDirective, ModalRef, ModalService, ModalSide, ModalState, ModalStyleConfig, ModalWarnings };
1015
960
  //# sourceMappingURL=filip.mazev-modal.mjs.map