@fuse_ui/modal 0.0.1
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/fuse_ui-modal.mjs +202 -0
- package/fesm2022/fuse_ui-modal.mjs.map +1 -0
- package/package.json +60 -0
- package/types/fuse_ui-modal.d.ts +94 -0
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import * as i0 from '@angular/core';
|
|
2
|
+
import { InjectionToken, inject, DestroyRef, ElementRef, viewChild, ViewContainerRef, ChangeDetectionStrategy, Component, signal, Injector, Injectable } from '@angular/core';
|
|
3
|
+
import { Overlay, OverlayConfig } from '@angular/cdk/overlay';
|
|
4
|
+
import { ComponentPortal } from '@angular/cdk/portal';
|
|
5
|
+
import { filter, take } from 'rxjs/operators';
|
|
6
|
+
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
|
7
|
+
import { fromEvent, Subject } from 'rxjs';
|
|
8
|
+
import { FocusTrapFactory } from '@angular/cdk/a11y';
|
|
9
|
+
|
|
10
|
+
/** The dynamic component type to render inside the modal shell. */
|
|
11
|
+
const FUSE_MODAL_COMPONENT = new InjectionToken('FUSE_MODAL_COMPONENT');
|
|
12
|
+
/** Resolved configuration for the open modal. */
|
|
13
|
+
const FUSE_MODAL_CONFIG = new InjectionToken('FUSE_MODAL_CONFIG');
|
|
14
|
+
/** Reference to the open modal; injectable inside the hosted component. */
|
|
15
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
16
|
+
const FUSE_MODAL_REF = new InjectionToken('FUSE_MODAL_REF');
|
|
17
|
+
/**
|
|
18
|
+
* Arbitrary data passed via FuseModalConfig.data.
|
|
19
|
+
* Inject this token in the hosted component to receive it.
|
|
20
|
+
*/
|
|
21
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
22
|
+
const FUSE_MODAL_DATA = new InjectionToken('FUSE_MODAL_DATA');
|
|
23
|
+
|
|
24
|
+
class FuseModalComponent {
|
|
25
|
+
// ─── Injected tokens ───────────────────────────────────────────────────────
|
|
26
|
+
config = inject(FUSE_MODAL_CONFIG);
|
|
27
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
28
|
+
modalRef = inject(FUSE_MODAL_REF);
|
|
29
|
+
componentType = inject(FUSE_MODAL_COMPONENT);
|
|
30
|
+
destroyRef = inject(DestroyRef);
|
|
31
|
+
el = inject(ElementRef);
|
|
32
|
+
focusTrapFactory = inject(FocusTrapFactory);
|
|
33
|
+
// ─── View refs ─────────────────────────────────────────────────────────────
|
|
34
|
+
contentRef = viewChild.required('contentRef', { read: ViewContainerRef });
|
|
35
|
+
// ─── Private state ─────────────────────────────────────────────────────────
|
|
36
|
+
focusTrap;
|
|
37
|
+
// ─── Lifecycle ─────────────────────────────────────────────────────────────
|
|
38
|
+
ngAfterViewInit() {
|
|
39
|
+
// Render the hosted component into the <ng-template #contentRef>
|
|
40
|
+
this.contentRef().createComponent(this.componentType);
|
|
41
|
+
// Trap focus inside the modal panel
|
|
42
|
+
this.focusTrap = this.focusTrapFactory.create(this.el.nativeElement);
|
|
43
|
+
this.focusTrap.focusInitialElementWhenReady();
|
|
44
|
+
// ESC key → close (only when closable)
|
|
45
|
+
fromEvent(document, 'keydown')
|
|
46
|
+
.pipe(filter((e) => e.key === 'Escape'), takeUntilDestroyed(this.destroyRef))
|
|
47
|
+
.subscribe(() => {
|
|
48
|
+
if (this.config.closable) {
|
|
49
|
+
this.modalRef.close();
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
ngOnDestroy() {
|
|
54
|
+
this.focusTrap?.destroy();
|
|
55
|
+
}
|
|
56
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.6", ngImport: i0, type: FuseModalComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
57
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.6", type: FuseModalComponent, isStandalone: true, selector: "fuse-modal", viewQueries: [{ propertyName: "contentRef", first: true, predicate: ["contentRef"], descendants: true, read: ViewContainerRef, isSignal: true }], ngImport: i0, template: "<div\n class=\"fuse-modal__panel fuse-modal__panel--{{ config.size }}\"\n role=\"dialog\"\n aria-modal=\"true\"\n>\n <!-- Close button -->\n @if (config.closable) {\n <button\n type=\"button\"\n class=\"fuse-modal__close\"\n aria-label=\"Close modal\"\n (click)=\"modalRef.close()\"\n >\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 20 20\"\n fill=\"currentColor\" width=\"20\" height=\"20\">\n <path d=\"M6.28 5.22a.75.75 0 00-1.06 1.06L8.94 10l-3.72 3.72a.75.75\n 0 101.06 1.06L10 11.06l3.72 3.72a.75.75 0 101.06-1.06L11.06\n 10l3.72-3.72a.75.75 0 00-1.06-1.06L10 8.94 6.28 5.22z\" />\n </svg>\n </button>\n }\n\n <!-- Dynamic content -->\n <ng-template #contentRef></ng-template>\n</div>\n", styles: [":host{display:block}.fuse-modal__panel{--fuse-modal-radius: var(--fuse-radius-lg, 12px);--fuse-modal-bg: var(--fuse-color-surface, #ffffff);--fuse-modal-shadow: var(--fuse-shadow-xl, 0 20px 60px rgba(0, 0, 0, .25));--fuse-modal-padding: var(--fuse-space-6, 24px);position:relative;background:var(--fuse-modal-bg);border-radius:var(--fuse-modal-radius);box-shadow:var(--fuse-modal-shadow);padding:var(--fuse-modal-padding);overflow:auto;outline:none}.fuse-modal__panel--sm{width:min(90vw,360px);max-height:90vh}.fuse-modal__panel--md{width:min(90vw,520px);max-height:90vh}.fuse-modal__panel--lg{width:min(90vw,760px);max-height:90vh}.fuse-modal__panel--fullscreen{width:100vw;height:100vh;max-height:100vh;border-radius:0}.fuse-modal__close{position:absolute;top:var(--fuse-space-3, 12px);right:var(--fuse-space-3, 12px);display:inline-flex;align-items:center;justify-content:center;width:32px;height:32px;background:transparent;border:none;border-radius:var(--fuse-radius-sm, 4px);color:var(--fuse-color-text-muted, #6b7280);cursor:pointer;transition:color .15s ease,background-color .15s ease}.fuse-modal__close:hover{background-color:var(--fuse-color-neutral-100, #f3f4f6);color:var(--fuse-color-text, #111827)}.fuse-modal__close:focus-visible{outline:2px solid var(--fuse-color-primary, #4f46e5);outline-offset:2px}:host-context(.ios) .fuse-modal__panel{--fuse-modal-radius: var(--fuse-radius-xl, 16px)}:host-context(.md) .fuse-modal__panel{--fuse-modal-radius: var(--fuse-radius-md, 8px)}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
58
|
+
}
|
|
59
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.6", ngImport: i0, type: FuseModalComponent, decorators: [{
|
|
60
|
+
type: Component,
|
|
61
|
+
args: [{ selector: 'fuse-modal', standalone: true, imports: [], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div\n class=\"fuse-modal__panel fuse-modal__panel--{{ config.size }}\"\n role=\"dialog\"\n aria-modal=\"true\"\n>\n <!-- Close button -->\n @if (config.closable) {\n <button\n type=\"button\"\n class=\"fuse-modal__close\"\n aria-label=\"Close modal\"\n (click)=\"modalRef.close()\"\n >\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 20 20\"\n fill=\"currentColor\" width=\"20\" height=\"20\">\n <path d=\"M6.28 5.22a.75.75 0 00-1.06 1.06L8.94 10l-3.72 3.72a.75.75\n 0 101.06 1.06L10 11.06l3.72 3.72a.75.75 0 101.06-1.06L11.06\n 10l3.72-3.72a.75.75 0 00-1.06-1.06L10 8.94 6.28 5.22z\" />\n </svg>\n </button>\n }\n\n <!-- Dynamic content -->\n <ng-template #contentRef></ng-template>\n</div>\n", styles: [":host{display:block}.fuse-modal__panel{--fuse-modal-radius: var(--fuse-radius-lg, 12px);--fuse-modal-bg: var(--fuse-color-surface, #ffffff);--fuse-modal-shadow: var(--fuse-shadow-xl, 0 20px 60px rgba(0, 0, 0, .25));--fuse-modal-padding: var(--fuse-space-6, 24px);position:relative;background:var(--fuse-modal-bg);border-radius:var(--fuse-modal-radius);box-shadow:var(--fuse-modal-shadow);padding:var(--fuse-modal-padding);overflow:auto;outline:none}.fuse-modal__panel--sm{width:min(90vw,360px);max-height:90vh}.fuse-modal__panel--md{width:min(90vw,520px);max-height:90vh}.fuse-modal__panel--lg{width:min(90vw,760px);max-height:90vh}.fuse-modal__panel--fullscreen{width:100vw;height:100vh;max-height:100vh;border-radius:0}.fuse-modal__close{position:absolute;top:var(--fuse-space-3, 12px);right:var(--fuse-space-3, 12px);display:inline-flex;align-items:center;justify-content:center;width:32px;height:32px;background:transparent;border:none;border-radius:var(--fuse-radius-sm, 4px);color:var(--fuse-color-text-muted, #6b7280);cursor:pointer;transition:color .15s ease,background-color .15s ease}.fuse-modal__close:hover{background-color:var(--fuse-color-neutral-100, #f3f4f6);color:var(--fuse-color-text, #111827)}.fuse-modal__close:focus-visible{outline:2px solid var(--fuse-color-primary, #4f46e5);outline-offset:2px}:host-context(.ios) .fuse-modal__panel{--fuse-modal-radius: var(--fuse-radius-xl, 16px)}:host-context(.md) .fuse-modal__panel{--fuse-modal-radius: var(--fuse-radius-md, 8px)}\n"] }]
|
|
62
|
+
}], propDecorators: { contentRef: [{ type: i0.ViewChild, args: ['contentRef', { ...{ read: ViewContainerRef }, isSignal: true }] }] } });
|
|
63
|
+
|
|
64
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
65
|
+
class FuseModalRef {
|
|
66
|
+
overlayRef;
|
|
67
|
+
/** Internal signal carrying the close result. */
|
|
68
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
69
|
+
_result = signal(undefined, ...(ngDevMode ? [{ debugName: "_result" }] : /* istanbul ignore next */ []));
|
|
70
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
71
|
+
_closed$ = new Subject();
|
|
72
|
+
_disposed = false;
|
|
73
|
+
constructor(overlayRef) {
|
|
74
|
+
this.overlayRef = overlayRef;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Close the modal and pass an optional result to `afterClosed()`.
|
|
78
|
+
* Safe to call multiple times — subsequent calls are no-ops.
|
|
79
|
+
*/
|
|
80
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
81
|
+
close(result) {
|
|
82
|
+
if (this._disposed)
|
|
83
|
+
return;
|
|
84
|
+
this._disposed = true;
|
|
85
|
+
this._result.set(result);
|
|
86
|
+
this.overlayRef.dispose();
|
|
87
|
+
this._closed$.next(result);
|
|
88
|
+
this._closed$.complete();
|
|
89
|
+
}
|
|
90
|
+
/** Emits once with the close result then completes. */
|
|
91
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
92
|
+
afterClosed() {
|
|
93
|
+
return this._closed$.asObservable();
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
class FuseConfirmDialogComponent {
|
|
98
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
99
|
+
modalRef = inject(FUSE_MODAL_REF);
|
|
100
|
+
options = inject(FUSE_MODAL_DATA);
|
|
101
|
+
confirm() {
|
|
102
|
+
this.modalRef.close(true);
|
|
103
|
+
}
|
|
104
|
+
cancel() {
|
|
105
|
+
this.modalRef.close(false);
|
|
106
|
+
}
|
|
107
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.6", ngImport: i0, type: FuseConfirmDialogComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
108
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.6", type: FuseConfirmDialogComponent, isStandalone: true, selector: "fuse-confirm-dialog", ngImport: i0, template: "<div class=\"fuse-confirm\" [class]=\"'fuse-confirm--' + (options.type ?? 'info')\">\n <!-- Icon -->\n <div class=\"fuse-confirm__icon\" aria-hidden=\"true\">\n @switch (options.type ?? 'info') {\n @case ('danger') {\n <svg xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\" fill=\"currentColor\" width=\"24\" height=\"24\">\n <path fill-rule=\"evenodd\"\n d=\"M9.401 3.003c1.155-2 4.043-2 5.197 0l7.355 12.748c1.154 2-.29 4.5-2.599 4.5H4.645c-2.309 0-3.752-2.5-2.598-4.5L9.4 3.003zM12 8.25a.75.75 0 01.75.75v3.75a.75.75 0 01-1.5 0V9a.75.75 0 01.75-.75zm0 8.25a.75.75 0 100-1.5.75.75 0 000 1.5z\"\n clip-rule=\"evenodd\" />\n </svg>\n }\n @case ('warning') {\n <svg xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\" fill=\"currentColor\" width=\"24\" height=\"24\">\n <path fill-rule=\"evenodd\"\n d=\"M9.401 3.003c1.155-2 4.043-2 5.197 0l7.355 12.748c1.154 2-.29 4.5-2.599 4.5H4.645c-2.309 0-3.752-2.5-2.598-4.5L9.4 3.003zM12 8.25a.75.75 0 01.75.75v3.75a.75.75 0 01-1.5 0V9a.75.75 0 01.75-.75zm0 8.25a.75.75 0 100-1.5.75.75 0 000 1.5z\"\n clip-rule=\"evenodd\" />\n </svg>\n }\n @default {\n <!-- info (default) -->\n <svg xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\" fill=\"currentColor\" width=\"24\" height=\"24\">\n <path fill-rule=\"evenodd\"\n d=\"M2.25 12c0-5.385 4.365-9.75 9.75-9.75s9.75 4.365 9.75 9.75-4.365 9.75-9.75 9.75S2.25 17.385 2.25 12zm8.706-1.442c1.146-.573 2.437.463 2.126 1.706l-.709 2.836.042-.02a.75.75 0 01.67 1.34l-.04.022c-1.147.573-2.438-.463-2.127-1.706l.71-2.836-.042.02a.75.75 0 11-.671-1.34l.041-.022zM12 9a.75.75 0 100-1.5A.75.75 0 0012 9z\"\n clip-rule=\"evenodd\" />\n </svg>\n }\n }\n </div>\n\n <!-- Body -->\n <div class=\"fuse-confirm__body\">\n <h2 class=\"fuse-confirm__title\">{{ options.title }}</h2>\n <p class=\"fuse-confirm__message\">{{ options.message }}</p>\n </div>\n\n <!-- Actions -->\n <div class=\"fuse-confirm__actions\">\n <button\n type=\"button\"\n class=\"fuse-confirm__btn fuse-confirm__btn--cancel\"\n (click)=\"cancel()\"\n >\n {{ options.cancelLabel ?? 'Cancel' }}\n </button>\n <button\n type=\"button\"\n class=\"fuse-confirm__btn fuse-confirm__btn--confirm\"\n (click)=\"confirm()\"\n >\n {{ options.confirmLabel ?? 'Confirm' }}\n </button>\n </div>\n</div>\n", styles: [":host{display:block}.fuse-confirm{display:flex;flex-direction:column;gap:var(--fuse-space-4, 16px)}.fuse-confirm__icon{display:flex;align-items:center;justify-content:center;width:48px;height:48px;border-radius:var(--fuse-radius-full, 9999px);align-self:flex-start}.fuse-confirm__icon svg{flex-shrink:0}:host(.fuse-confirm--danger) .fuse-confirm__icon{background-color:var(--fuse-color-danger-100, #fee2e2);color:var(--fuse-color-danger-600, #dc2626)}:host(.fuse-confirm--warning) .fuse-confirm__icon{background-color:var(--fuse-color-warning-100, #fef3c7);color:var(--fuse-color-warning-600, #d97706)}:host(.fuse-confirm--info) .fuse-confirm__icon,:host(:not(.fuse-confirm--danger):not(.fuse-confirm--warning)) .fuse-confirm__icon{background-color:var(--fuse-color-info-100, #dbeafe);color:var(--fuse-color-info-600, #2563eb)}.fuse-confirm__body{display:flex;flex-direction:column;gap:var(--fuse-space-2, 8px)}.fuse-confirm__title{margin:0;font-size:var(--fuse-font-size-lg, 1.125rem);font-weight:var(--fuse-font-weight-semibold, 600);color:var(--fuse-color-text, #111827);line-height:1.4}.fuse-confirm__message{margin:0;font-size:var(--fuse-font-size-sm, .875rem);color:var(--fuse-color-text-muted, #6b7280);line-height:1.5}.fuse-confirm__actions{display:flex;justify-content:flex-end;gap:var(--fuse-space-3, 12px);padding-top:var(--fuse-space-2, 8px)}.fuse-confirm__btn{display:inline-flex;align-items:center;justify-content:center;padding:var(--fuse-space-2, 8px) var(--fuse-space-4, 16px);font-size:var(--fuse-font-size-sm, .875rem);font-weight:var(--fuse-font-weight-medium, 500);border-radius:var(--fuse-radius-md, 8px);border:1px solid transparent;cursor:pointer;transition:background-color .15s ease,color .15s ease,border-color .15s ease}.fuse-confirm__btn:focus-visible{outline:2px solid var(--fuse-color-primary, #4f46e5);outline-offset:2px}.fuse-confirm__btn--cancel{background-color:var(--fuse-color-neutral-100, #f3f4f6);color:var(--fuse-color-text, #111827);border-color:var(--fuse-color-neutral-200, #e5e7eb)}.fuse-confirm__btn--cancel:hover{background-color:var(--fuse-color-neutral-200, #e5e7eb)}.fuse-confirm__btn--confirm{background-color:var(--fuse-color-primary, #4f46e5);color:var(--fuse-color-primary-contrast, #ffffff)}.fuse-confirm__btn--confirm:hover{background-color:var(--fuse-color-primary-700, #4338ca)}:host(.fuse-confirm--danger) .fuse-confirm__btn--confirm{background-color:var(--fuse-color-danger-600, #dc2626);color:var(--fuse-color-danger-contrast, #ffffff)}:host(.fuse-confirm--danger) .fuse-confirm__btn--confirm:hover{background-color:var(--fuse-color-danger-700, #b91c1c)}:host(.fuse-confirm--warning) .fuse-confirm__btn--confirm{background-color:var(--fuse-color-warning-600, #d97706);color:var(--fuse-color-warning-contrast, #ffffff)}:host(.fuse-confirm--warning) .fuse-confirm__btn--confirm:hover{background-color:var(--fuse-color-warning-700, #b45309)}:host-context(.ios) .fuse-confirm__btn{border-radius:var(--fuse-radius-lg, 12px)}:host-context(.ios) .fuse-confirm__title{font-size:var(--fuse-font-size-base, 1rem)}:host-context(.md) .fuse-confirm__btn{border-radius:var(--fuse-radius-sm, 4px);text-transform:uppercase;letter-spacing:.05em;font-size:var(--fuse-font-size-xs, .75rem)}:host-context(.md) .fuse-confirm__actions{justify-content:flex-end}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
109
|
+
}
|
|
110
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.6", ngImport: i0, type: FuseConfirmDialogComponent, decorators: [{
|
|
111
|
+
type: Component,
|
|
112
|
+
args: [{ selector: 'fuse-confirm-dialog', standalone: true, imports: [], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"fuse-confirm\" [class]=\"'fuse-confirm--' + (options.type ?? 'info')\">\n <!-- Icon -->\n <div class=\"fuse-confirm__icon\" aria-hidden=\"true\">\n @switch (options.type ?? 'info') {\n @case ('danger') {\n <svg xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\" fill=\"currentColor\" width=\"24\" height=\"24\">\n <path fill-rule=\"evenodd\"\n d=\"M9.401 3.003c1.155-2 4.043-2 5.197 0l7.355 12.748c1.154 2-.29 4.5-2.599 4.5H4.645c-2.309 0-3.752-2.5-2.598-4.5L9.4 3.003zM12 8.25a.75.75 0 01.75.75v3.75a.75.75 0 01-1.5 0V9a.75.75 0 01.75-.75zm0 8.25a.75.75 0 100-1.5.75.75 0 000 1.5z\"\n clip-rule=\"evenodd\" />\n </svg>\n }\n @case ('warning') {\n <svg xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\" fill=\"currentColor\" width=\"24\" height=\"24\">\n <path fill-rule=\"evenodd\"\n d=\"M9.401 3.003c1.155-2 4.043-2 5.197 0l7.355 12.748c1.154 2-.29 4.5-2.599 4.5H4.645c-2.309 0-3.752-2.5-2.598-4.5L9.4 3.003zM12 8.25a.75.75 0 01.75.75v3.75a.75.75 0 01-1.5 0V9a.75.75 0 01.75-.75zm0 8.25a.75.75 0 100-1.5.75.75 0 000 1.5z\"\n clip-rule=\"evenodd\" />\n </svg>\n }\n @default {\n <!-- info (default) -->\n <svg xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\" fill=\"currentColor\" width=\"24\" height=\"24\">\n <path fill-rule=\"evenodd\"\n d=\"M2.25 12c0-5.385 4.365-9.75 9.75-9.75s9.75 4.365 9.75 9.75-4.365 9.75-9.75 9.75S2.25 17.385 2.25 12zm8.706-1.442c1.146-.573 2.437.463 2.126 1.706l-.709 2.836.042-.02a.75.75 0 01.67 1.34l-.04.022c-1.147.573-2.438-.463-2.127-1.706l.71-2.836-.042.02a.75.75 0 11-.671-1.34l.041-.022zM12 9a.75.75 0 100-1.5A.75.75 0 0012 9z\"\n clip-rule=\"evenodd\" />\n </svg>\n }\n }\n </div>\n\n <!-- Body -->\n <div class=\"fuse-confirm__body\">\n <h2 class=\"fuse-confirm__title\">{{ options.title }}</h2>\n <p class=\"fuse-confirm__message\">{{ options.message }}</p>\n </div>\n\n <!-- Actions -->\n <div class=\"fuse-confirm__actions\">\n <button\n type=\"button\"\n class=\"fuse-confirm__btn fuse-confirm__btn--cancel\"\n (click)=\"cancel()\"\n >\n {{ options.cancelLabel ?? 'Cancel' }}\n </button>\n <button\n type=\"button\"\n class=\"fuse-confirm__btn fuse-confirm__btn--confirm\"\n (click)=\"confirm()\"\n >\n {{ options.confirmLabel ?? 'Confirm' }}\n </button>\n </div>\n</div>\n", styles: [":host{display:block}.fuse-confirm{display:flex;flex-direction:column;gap:var(--fuse-space-4, 16px)}.fuse-confirm__icon{display:flex;align-items:center;justify-content:center;width:48px;height:48px;border-radius:var(--fuse-radius-full, 9999px);align-self:flex-start}.fuse-confirm__icon svg{flex-shrink:0}:host(.fuse-confirm--danger) .fuse-confirm__icon{background-color:var(--fuse-color-danger-100, #fee2e2);color:var(--fuse-color-danger-600, #dc2626)}:host(.fuse-confirm--warning) .fuse-confirm__icon{background-color:var(--fuse-color-warning-100, #fef3c7);color:var(--fuse-color-warning-600, #d97706)}:host(.fuse-confirm--info) .fuse-confirm__icon,:host(:not(.fuse-confirm--danger):not(.fuse-confirm--warning)) .fuse-confirm__icon{background-color:var(--fuse-color-info-100, #dbeafe);color:var(--fuse-color-info-600, #2563eb)}.fuse-confirm__body{display:flex;flex-direction:column;gap:var(--fuse-space-2, 8px)}.fuse-confirm__title{margin:0;font-size:var(--fuse-font-size-lg, 1.125rem);font-weight:var(--fuse-font-weight-semibold, 600);color:var(--fuse-color-text, #111827);line-height:1.4}.fuse-confirm__message{margin:0;font-size:var(--fuse-font-size-sm, .875rem);color:var(--fuse-color-text-muted, #6b7280);line-height:1.5}.fuse-confirm__actions{display:flex;justify-content:flex-end;gap:var(--fuse-space-3, 12px);padding-top:var(--fuse-space-2, 8px)}.fuse-confirm__btn{display:inline-flex;align-items:center;justify-content:center;padding:var(--fuse-space-2, 8px) var(--fuse-space-4, 16px);font-size:var(--fuse-font-size-sm, .875rem);font-weight:var(--fuse-font-weight-medium, 500);border-radius:var(--fuse-radius-md, 8px);border:1px solid transparent;cursor:pointer;transition:background-color .15s ease,color .15s ease,border-color .15s ease}.fuse-confirm__btn:focus-visible{outline:2px solid var(--fuse-color-primary, #4f46e5);outline-offset:2px}.fuse-confirm__btn--cancel{background-color:var(--fuse-color-neutral-100, #f3f4f6);color:var(--fuse-color-text, #111827);border-color:var(--fuse-color-neutral-200, #e5e7eb)}.fuse-confirm__btn--cancel:hover{background-color:var(--fuse-color-neutral-200, #e5e7eb)}.fuse-confirm__btn--confirm{background-color:var(--fuse-color-primary, #4f46e5);color:var(--fuse-color-primary-contrast, #ffffff)}.fuse-confirm__btn--confirm:hover{background-color:var(--fuse-color-primary-700, #4338ca)}:host(.fuse-confirm--danger) .fuse-confirm__btn--confirm{background-color:var(--fuse-color-danger-600, #dc2626);color:var(--fuse-color-danger-contrast, #ffffff)}:host(.fuse-confirm--danger) .fuse-confirm__btn--confirm:hover{background-color:var(--fuse-color-danger-700, #b91c1c)}:host(.fuse-confirm--warning) .fuse-confirm__btn--confirm{background-color:var(--fuse-color-warning-600, #d97706);color:var(--fuse-color-warning-contrast, #ffffff)}:host(.fuse-confirm--warning) .fuse-confirm__btn--confirm:hover{background-color:var(--fuse-color-warning-700, #b45309)}:host-context(.ios) .fuse-confirm__btn{border-radius:var(--fuse-radius-lg, 12px)}:host-context(.ios) .fuse-confirm__title{font-size:var(--fuse-font-size-base, 1rem)}:host-context(.md) .fuse-confirm__btn{border-radius:var(--fuse-radius-sm, 4px);text-transform:uppercase;letter-spacing:.05em;font-size:var(--fuse-font-size-xs, .75rem)}:host-context(.md) .fuse-confirm__actions{justify-content:flex-end}\n"] }]
|
|
113
|
+
}] });
|
|
114
|
+
|
|
115
|
+
const SIZE_ORDER = ['sm', 'md', 'lg', 'fullscreen'];
|
|
116
|
+
class FuseModalService {
|
|
117
|
+
overlay = inject(Overlay);
|
|
118
|
+
injector = inject(Injector);
|
|
119
|
+
/**
|
|
120
|
+
* Open any standalone component in a modal panel.
|
|
121
|
+
* The component can inject FUSE_MODAL_REF and FUSE_MODAL_DATA.
|
|
122
|
+
*/
|
|
123
|
+
open(component, config) {
|
|
124
|
+
const resolved = this.resolveConfig(config);
|
|
125
|
+
const overlayRef = this.overlay.create(this.buildOverlayConfig(resolved));
|
|
126
|
+
const modalRef = new FuseModalRef(overlayRef);
|
|
127
|
+
const childInjector = Injector.create({
|
|
128
|
+
providers: [
|
|
129
|
+
{ provide: FUSE_MODAL_COMPONENT, useValue: component },
|
|
130
|
+
{ provide: FUSE_MODAL_CONFIG, useValue: resolved },
|
|
131
|
+
{ provide: FUSE_MODAL_REF, useValue: modalRef },
|
|
132
|
+
{ provide: FUSE_MODAL_DATA, useValue: resolved.data ?? null },
|
|
133
|
+
],
|
|
134
|
+
parent: this.injector,
|
|
135
|
+
});
|
|
136
|
+
const portal = new ComponentPortal(FuseModalComponent, null, childInjector);
|
|
137
|
+
overlayRef.attach(portal);
|
|
138
|
+
if (resolved.backdropDismiss) {
|
|
139
|
+
overlayRef
|
|
140
|
+
.backdropClick()
|
|
141
|
+
.pipe(take(1))
|
|
142
|
+
.subscribe(() => modalRef.close());
|
|
143
|
+
}
|
|
144
|
+
return modalRef;
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Show a pre-built confirmation dialog.
|
|
148
|
+
* Resolves `true` if the user confirms, `false` otherwise.
|
|
149
|
+
*/
|
|
150
|
+
confirm(options) {
|
|
151
|
+
return new Promise((resolve) => {
|
|
152
|
+
const ref = this.open(FuseConfirmDialogComponent, {
|
|
153
|
+
size: 'sm',
|
|
154
|
+
closable: false,
|
|
155
|
+
backdropDismiss: false,
|
|
156
|
+
data: options,
|
|
157
|
+
});
|
|
158
|
+
ref.afterClosed().subscribe((result) => resolve(!!result));
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
// ─── Helpers ───────────────────────────────────────────────────────────────
|
|
162
|
+
resolveConfig(config) {
|
|
163
|
+
return {
|
|
164
|
+
size: config?.size ?? 'md',
|
|
165
|
+
closable: config?.closable !== false,
|
|
166
|
+
backdropDismiss: config?.backdropDismiss !== false,
|
|
167
|
+
data: config?.data ?? null,
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
buildOverlayConfig(config) {
|
|
171
|
+
const positionStrategy = this.overlay
|
|
172
|
+
.position()
|
|
173
|
+
.global()
|
|
174
|
+
.centerHorizontally()
|
|
175
|
+
.centerVertically();
|
|
176
|
+
return new OverlayConfig({
|
|
177
|
+
hasBackdrop: true,
|
|
178
|
+
backdropClass: 'fuse-modal-backdrop',
|
|
179
|
+
panelClass: [
|
|
180
|
+
'fuse-modal-overlay',
|
|
181
|
+
`fuse-modal-overlay--${config.size}`,
|
|
182
|
+
],
|
|
183
|
+
positionStrategy,
|
|
184
|
+
scrollStrategy: this.overlay.scrollStrategies.block(),
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.6", ngImport: i0, type: FuseModalService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
188
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.6", ngImport: i0, type: FuseModalService, providedIn: 'root' });
|
|
189
|
+
}
|
|
190
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.6", ngImport: i0, type: FuseModalService, decorators: [{
|
|
191
|
+
type: Injectable,
|
|
192
|
+
args: [{ providedIn: 'root' }]
|
|
193
|
+
}] });
|
|
194
|
+
// Unused import guard — ensures the SIZE_ORDER tuple is reachable.
|
|
195
|
+
void SIZE_ORDER;
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Generated bundle index. Do not edit.
|
|
199
|
+
*/
|
|
200
|
+
|
|
201
|
+
export { FUSE_MODAL_DATA, FUSE_MODAL_REF, FuseConfirmDialogComponent, FuseModalComponent, FuseModalRef, FuseModalService };
|
|
202
|
+
//# sourceMappingURL=fuse_ui-modal.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fuse_ui-modal.mjs","sources":["../../../../packages/modal/src/lib/modal/fuse-modal.tokens.ts","../../../../packages/modal/src/lib/modal/fuse-modal.component.ts","../../../../packages/modal/src/lib/modal/fuse-modal.component.html","../../../../packages/modal/src/lib/modal/fuse-modal-ref.ts","../../../../packages/modal/src/lib/confirm/fuse-confirm-dialog.component.ts","../../../../packages/modal/src/lib/confirm/fuse-confirm-dialog.component.html","../../../../packages/modal/src/lib/modal/fuse-modal.service.ts","../../../../packages/modal/src/fuse_ui-modal.ts"],"sourcesContent":["import { InjectionToken, Type } from '@angular/core';\nimport type { FuseModalConfig } from './fuse-modal.config';\nimport type { FuseModalRef } from './fuse-modal-ref';\n\n/** The dynamic component type to render inside the modal shell. */\nexport const FUSE_MODAL_COMPONENT = new InjectionToken<Type<unknown>>(\n 'FUSE_MODAL_COMPONENT',\n);\n\n/** Resolved configuration for the open modal. */\nexport const FUSE_MODAL_CONFIG = new InjectionToken<Required<FuseModalConfig>>(\n 'FUSE_MODAL_CONFIG',\n);\n\n/** Reference to the open modal; injectable inside the hosted component. */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport const FUSE_MODAL_REF = new InjectionToken<FuseModalRef<any>>(\n 'FUSE_MODAL_REF',\n);\n\n/**\n * Arbitrary data passed via FuseModalConfig.data.\n * Inject this token in the hosted component to receive it.\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport const FUSE_MODAL_DATA = new InjectionToken<any>('FUSE_MODAL_DATA');\n","import {\n AfterViewInit,\n ChangeDetectionStrategy,\n Component,\n DestroyRef,\n ElementRef,\n OnDestroy,\n Type,\n ViewContainerRef,\n inject,\n viewChild,\n} from '@angular/core';\nimport { takeUntilDestroyed } from '@angular/core/rxjs-interop';\nimport { fromEvent } from 'rxjs';\nimport { filter } from 'rxjs/operators';\nimport { FocusTrap, FocusTrapFactory } from '@angular/cdk/a11y';\nimport type { FuseModalConfig } from './fuse-modal.config';\nimport type { FuseModalRef } from './fuse-modal-ref';\nimport {\n FUSE_MODAL_COMPONENT,\n FUSE_MODAL_CONFIG,\n FUSE_MODAL_REF,\n} from './fuse-modal.tokens';\n\n@Component({\n selector: 'fuse-modal',\n standalone: true,\n imports: [],\n changeDetection: ChangeDetectionStrategy.OnPush,\n templateUrl: './fuse-modal.component.html',\n styleUrl: './fuse-modal.component.scss',\n})\nexport class FuseModalComponent implements AfterViewInit, OnDestroy {\n // ─── Injected tokens ───────────────────────────────────────────────────────\n\n \n protected readonly config = inject<Required<FuseModalConfig>>(FUSE_MODAL_CONFIG);\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n protected readonly modalRef = inject<FuseModalRef<any>>(FUSE_MODAL_REF);\n private readonly componentType = inject<Type<unknown>>(FUSE_MODAL_COMPONENT);\n private readonly destroyRef = inject(DestroyRef);\n private readonly el = inject<ElementRef<HTMLElement>>(ElementRef);\n private readonly focusTrapFactory = inject(FocusTrapFactory);\n\n // ─── View refs ─────────────────────────────────────────────────────────────\n\n private readonly contentRef = viewChild.required('contentRef', { read: ViewContainerRef });\n\n // ─── Private state ─────────────────────────────────────────────────────────\n\n private focusTrap!: FocusTrap;\n\n // ─── Lifecycle ─────────────────────────────────────────────────────────────\n\n ngAfterViewInit(): void {\n // Render the hosted component into the <ng-template #contentRef>\n this.contentRef().createComponent(this.componentType);\n\n // Trap focus inside the modal panel\n this.focusTrap = this.focusTrapFactory.create(this.el.nativeElement);\n this.focusTrap.focusInitialElementWhenReady();\n\n // ESC key → close (only when closable)\n fromEvent<KeyboardEvent>(document, 'keydown')\n .pipe(\n filter((e) => e.key === 'Escape'),\n takeUntilDestroyed(this.destroyRef),\n )\n .subscribe(() => {\n if (this.config.closable) {\n this.modalRef.close();\n }\n });\n }\n\n ngOnDestroy(): void {\n this.focusTrap?.destroy();\n }\n}\n","<div\n class=\"fuse-modal__panel fuse-modal__panel--{{ config.size }}\"\n role=\"dialog\"\n aria-modal=\"true\"\n>\n <!-- Close button -->\n @if (config.closable) {\n <button\n type=\"button\"\n class=\"fuse-modal__close\"\n aria-label=\"Close modal\"\n (click)=\"modalRef.close()\"\n >\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 20 20\"\n fill=\"currentColor\" width=\"20\" height=\"20\">\n <path d=\"M6.28 5.22a.75.75 0 00-1.06 1.06L8.94 10l-3.72 3.72a.75.75\n 0 101.06 1.06L10 11.06l3.72 3.72a.75.75 0 101.06-1.06L11.06\n 10l3.72-3.72a.75.75 0 00-1.06-1.06L10 8.94 6.28 5.22z\" />\n </svg>\n </button>\n }\n\n <!-- Dynamic content -->\n <ng-template #contentRef></ng-template>\n</div>\n","import { signal } from '@angular/core';\nimport { Observable, Subject } from 'rxjs';\nimport type { OverlayRef } from '@angular/cdk/overlay';\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport class FuseModalRef<T = any> {\n /** Internal signal carrying the close result. */\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n private readonly _result = signal<any>(undefined);\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n private readonly _closed$ = new Subject<any>();\n\n private _disposed = false;\n\n constructor(private readonly overlayRef: OverlayRef) {}\n\n /**\n * Close the modal and pass an optional result to `afterClosed()`.\n * Safe to call multiple times — subsequent calls are no-ops.\n */\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n close(result?: any): void {\n if (this._disposed) return;\n this._disposed = true;\n this._result.set(result);\n this.overlayRef.dispose();\n this._closed$.next(result);\n this._closed$.complete();\n }\n\n /** Emits once with the close result then completes. */\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n afterClosed(): Observable<any> {\n return this._closed$.asObservable();\n }\n}\n","import {\n ChangeDetectionStrategy,\n Component,\n inject,\n} from '@angular/core';\nimport type { FuseConfirmOptions } from '../modal/fuse-modal.config';\nimport type { FuseModalRef } from '../modal/fuse-modal-ref';\nimport {\n FUSE_MODAL_DATA,\n FUSE_MODAL_REF,\n} from '../modal/fuse-modal.tokens';\n\n@Component({\n selector: 'fuse-confirm-dialog',\n standalone: true,\n imports: [],\n changeDetection: ChangeDetectionStrategy.OnPush,\n templateUrl: './fuse-confirm-dialog.component.html',\n styleUrl: './fuse-confirm-dialog.component.scss',\n})\nexport class FuseConfirmDialogComponent {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n protected readonly modalRef = inject<FuseModalRef<any>>(FUSE_MODAL_REF);\n protected readonly options = inject<FuseConfirmOptions>(FUSE_MODAL_DATA);\n\n protected confirm(): void {\n this.modalRef.close(true);\n }\n\n protected cancel(): void {\n this.modalRef.close(false);\n }\n}\n","<div class=\"fuse-confirm\" [class]=\"'fuse-confirm--' + (options.type ?? 'info')\">\n <!-- Icon -->\n <div class=\"fuse-confirm__icon\" aria-hidden=\"true\">\n @switch (options.type ?? 'info') {\n @case ('danger') {\n <svg xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\" fill=\"currentColor\" width=\"24\" height=\"24\">\n <path fill-rule=\"evenodd\"\n d=\"M9.401 3.003c1.155-2 4.043-2 5.197 0l7.355 12.748c1.154 2-.29 4.5-2.599 4.5H4.645c-2.309 0-3.752-2.5-2.598-4.5L9.4 3.003zM12 8.25a.75.75 0 01.75.75v3.75a.75.75 0 01-1.5 0V9a.75.75 0 01.75-.75zm0 8.25a.75.75 0 100-1.5.75.75 0 000 1.5z\"\n clip-rule=\"evenodd\" />\n </svg>\n }\n @case ('warning') {\n <svg xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\" fill=\"currentColor\" width=\"24\" height=\"24\">\n <path fill-rule=\"evenodd\"\n d=\"M9.401 3.003c1.155-2 4.043-2 5.197 0l7.355 12.748c1.154 2-.29 4.5-2.599 4.5H4.645c-2.309 0-3.752-2.5-2.598-4.5L9.4 3.003zM12 8.25a.75.75 0 01.75.75v3.75a.75.75 0 01-1.5 0V9a.75.75 0 01.75-.75zm0 8.25a.75.75 0 100-1.5.75.75 0 000 1.5z\"\n clip-rule=\"evenodd\" />\n </svg>\n }\n @default {\n <!-- info (default) -->\n <svg xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\" fill=\"currentColor\" width=\"24\" height=\"24\">\n <path fill-rule=\"evenodd\"\n d=\"M2.25 12c0-5.385 4.365-9.75 9.75-9.75s9.75 4.365 9.75 9.75-4.365 9.75-9.75 9.75S2.25 17.385 2.25 12zm8.706-1.442c1.146-.573 2.437.463 2.126 1.706l-.709 2.836.042-.02a.75.75 0 01.67 1.34l-.04.022c-1.147.573-2.438-.463-2.127-1.706l.71-2.836-.042.02a.75.75 0 11-.671-1.34l.041-.022zM12 9a.75.75 0 100-1.5A.75.75 0 0012 9z\"\n clip-rule=\"evenodd\" />\n </svg>\n }\n }\n </div>\n\n <!-- Body -->\n <div class=\"fuse-confirm__body\">\n <h2 class=\"fuse-confirm__title\">{{ options.title }}</h2>\n <p class=\"fuse-confirm__message\">{{ options.message }}</p>\n </div>\n\n <!-- Actions -->\n <div class=\"fuse-confirm__actions\">\n <button\n type=\"button\"\n class=\"fuse-confirm__btn fuse-confirm__btn--cancel\"\n (click)=\"cancel()\"\n >\n {{ options.cancelLabel ?? 'Cancel' }}\n </button>\n <button\n type=\"button\"\n class=\"fuse-confirm__btn fuse-confirm__btn--confirm\"\n (click)=\"confirm()\"\n >\n {{ options.confirmLabel ?? 'Confirm' }}\n </button>\n </div>\n</div>\n","import {\n Injectable,\n Injector,\n Type,\n inject,\n} from '@angular/core';\nimport { Overlay, OverlayConfig } from '@angular/cdk/overlay';\nimport { ComponentPortal } from '@angular/cdk/portal';\nimport { take } from 'rxjs/operators';\nimport { FuseModalComponent } from './fuse-modal.component';\nimport { FuseModalRef } from './fuse-modal-ref';\nimport type { FuseConfirmOptions, FuseModalConfig, FuseModalSize } from './fuse-modal.config';\nimport {\n FUSE_MODAL_COMPONENT,\n FUSE_MODAL_CONFIG,\n FUSE_MODAL_DATA,\n FUSE_MODAL_REF,\n} from './fuse-modal.tokens';\nimport { FuseConfirmDialogComponent } from '../confirm/fuse-confirm-dialog.component';\n\nconst SIZE_ORDER: FuseModalSize[] = ['sm', 'md', 'lg', 'fullscreen'];\n\n@Injectable({ providedIn: 'root' })\nexport class FuseModalService {\n private readonly overlay = inject(Overlay);\n private readonly injector = inject(Injector);\n\n /**\n * Open any standalone component in a modal panel.\n * The component can inject FUSE_MODAL_REF and FUSE_MODAL_DATA.\n */\n open<T>(component: Type<T>, config?: FuseModalConfig): FuseModalRef<T> {\n const resolved = this.resolveConfig(config);\n\n const overlayRef = this.overlay.create(this.buildOverlayConfig(resolved));\n const modalRef = new FuseModalRef<T>(overlayRef);\n\n const childInjector = Injector.create({\n providers: [\n { provide: FUSE_MODAL_COMPONENT, useValue: component },\n { provide: FUSE_MODAL_CONFIG, useValue: resolved },\n { provide: FUSE_MODAL_REF, useValue: modalRef },\n { provide: FUSE_MODAL_DATA, useValue: resolved.data ?? null },\n ],\n parent: this.injector,\n });\n\n const portal = new ComponentPortal(FuseModalComponent, null, childInjector);\n overlayRef.attach(portal);\n\n if (resolved.backdropDismiss) {\n overlayRef\n .backdropClick()\n .pipe(take(1))\n .subscribe(() => modalRef.close());\n }\n\n return modalRef;\n }\n\n /**\n * Show a pre-built confirmation dialog.\n * Resolves `true` if the user confirms, `false` otherwise.\n */\n confirm(options: FuseConfirmOptions): Promise<boolean> {\n return new Promise<boolean>((resolve) => {\n const ref = this.open(FuseConfirmDialogComponent, {\n size: 'sm',\n closable: false,\n backdropDismiss: false,\n data: options,\n });\n ref.afterClosed().subscribe((result) => resolve(!!result));\n });\n }\n\n // ─── Helpers ───────────────────────────────────────────────────────────────\n\n private resolveConfig(config?: FuseModalConfig): Required<FuseModalConfig> {\n return {\n size: config?.size ?? 'md',\n closable: config?.closable !== false,\n backdropDismiss: config?.backdropDismiss !== false,\n data: config?.data ?? null,\n };\n }\n\n private buildOverlayConfig(config: Required<FuseModalConfig>): OverlayConfig {\n const positionStrategy = this.overlay\n .position()\n .global()\n .centerHorizontally()\n .centerVertically();\n\n return new OverlayConfig({\n hasBackdrop: true,\n backdropClass: 'fuse-modal-backdrop',\n panelClass: [\n 'fuse-modal-overlay',\n `fuse-modal-overlay--${config.size}`,\n ],\n positionStrategy,\n scrollStrategy: this.overlay.scrollStrategies.block(),\n });\n }\n}\n\n// Unused import guard — ensures the SIZE_ORDER tuple is reachable.\nvoid SIZE_ORDER;\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@fuse_ui/modal",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"license": "MIT",
|
|
5
|
+
"publishConfig": {
|
|
6
|
+
"access": "public"
|
|
7
|
+
},
|
|
8
|
+
"keywords": [
|
|
9
|
+
"fuse-ui",
|
|
10
|
+
"angular",
|
|
11
|
+
"ionic",
|
|
12
|
+
"ionic8",
|
|
13
|
+
"angular18",
|
|
14
|
+
"angular19",
|
|
15
|
+
"angular20",
|
|
16
|
+
"angular21",
|
|
17
|
+
"ui-components",
|
|
18
|
+
"design-system",
|
|
19
|
+
"css-variables",
|
|
20
|
+
"signals",
|
|
21
|
+
"standalone",
|
|
22
|
+
"multi-theme",
|
|
23
|
+
"dark-mode",
|
|
24
|
+
"fluid-typography",
|
|
25
|
+
"animated"
|
|
26
|
+
],
|
|
27
|
+
"peerDependencies": {
|
|
28
|
+
"@angular/core": ">=18.0.0",
|
|
29
|
+
"@angular/common": ">=18.0.0",
|
|
30
|
+
"rxjs": ">=7.4.0",
|
|
31
|
+
"@angular/cdk": ">=18.0.0"
|
|
32
|
+
},
|
|
33
|
+
"peerDependenciesMeta": {
|
|
34
|
+
"@ionic/angular": {
|
|
35
|
+
"optional": true
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
"sideEffects": [
|
|
39
|
+
"*.css",
|
|
40
|
+
"**/*.scss"
|
|
41
|
+
],
|
|
42
|
+
"engines": {
|
|
43
|
+
"node": ">=20.0.0"
|
|
44
|
+
},
|
|
45
|
+
"module": "fesm2022/fuse_ui-modal.mjs",
|
|
46
|
+
"typings": "types/fuse_ui-modal.d.ts",
|
|
47
|
+
"exports": {
|
|
48
|
+
"./package.json": {
|
|
49
|
+
"default": "./package.json"
|
|
50
|
+
},
|
|
51
|
+
".": {
|
|
52
|
+
"types": "./types/fuse_ui-modal.d.ts",
|
|
53
|
+
"default": "./fesm2022/fuse_ui-modal.mjs"
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
"type": "module",
|
|
57
|
+
"dependencies": {
|
|
58
|
+
"tslib": "^2.3.0"
|
|
59
|
+
}
|
|
60
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import * as i0 from '@angular/core';
|
|
2
|
+
import { Type, AfterViewInit, OnDestroy, InjectionToken } from '@angular/core';
|
|
3
|
+
import { Observable } from 'rxjs';
|
|
4
|
+
import { OverlayRef } from '@angular/cdk/overlay';
|
|
5
|
+
|
|
6
|
+
declare class FuseModalRef<T = any> {
|
|
7
|
+
private readonly overlayRef;
|
|
8
|
+
/** Internal signal carrying the close result. */
|
|
9
|
+
private readonly _result;
|
|
10
|
+
private readonly _closed$;
|
|
11
|
+
private _disposed;
|
|
12
|
+
constructor(overlayRef: OverlayRef);
|
|
13
|
+
/**
|
|
14
|
+
* Close the modal and pass an optional result to `afterClosed()`.
|
|
15
|
+
* Safe to call multiple times — subsequent calls are no-ops.
|
|
16
|
+
*/
|
|
17
|
+
close(result?: any): void;
|
|
18
|
+
/** Emits once with the close result then completes. */
|
|
19
|
+
afterClosed(): Observable<any>;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
type FuseModalSize = 'sm' | 'md' | 'lg' | 'fullscreen';
|
|
23
|
+
type FuseConfirmType = 'danger' | 'warning' | 'info';
|
|
24
|
+
interface FuseModalConfig {
|
|
25
|
+
/** Panel size. Default: 'md'. */
|
|
26
|
+
size?: FuseModalSize;
|
|
27
|
+
/** Show the built-in × close button. Default: true. */
|
|
28
|
+
closable?: boolean;
|
|
29
|
+
/** Close when the backdrop is clicked. Default: true. */
|
|
30
|
+
backdropDismiss?: boolean;
|
|
31
|
+
/** Arbitrary data injected as FUSE_MODAL_DATA in the hosted component. */
|
|
32
|
+
data?: any;
|
|
33
|
+
}
|
|
34
|
+
interface FuseConfirmOptions {
|
|
35
|
+
title: string;
|
|
36
|
+
message: string;
|
|
37
|
+
confirmLabel?: string;
|
|
38
|
+
cancelLabel?: string;
|
|
39
|
+
type?: FuseConfirmType;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
declare class FuseModalService {
|
|
43
|
+
private readonly overlay;
|
|
44
|
+
private readonly injector;
|
|
45
|
+
/**
|
|
46
|
+
* Open any standalone component in a modal panel.
|
|
47
|
+
* The component can inject FUSE_MODAL_REF and FUSE_MODAL_DATA.
|
|
48
|
+
*/
|
|
49
|
+
open<T>(component: Type<T>, config?: FuseModalConfig): FuseModalRef<T>;
|
|
50
|
+
/**
|
|
51
|
+
* Show a pre-built confirmation dialog.
|
|
52
|
+
* Resolves `true` if the user confirms, `false` otherwise.
|
|
53
|
+
*/
|
|
54
|
+
confirm(options: FuseConfirmOptions): Promise<boolean>;
|
|
55
|
+
private resolveConfig;
|
|
56
|
+
private buildOverlayConfig;
|
|
57
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<FuseModalService, never>;
|
|
58
|
+
static ɵprov: i0.ɵɵInjectableDeclaration<FuseModalService>;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
declare class FuseModalComponent implements AfterViewInit, OnDestroy {
|
|
62
|
+
protected readonly config: Required<FuseModalConfig>;
|
|
63
|
+
protected readonly modalRef: FuseModalRef<any>;
|
|
64
|
+
private readonly componentType;
|
|
65
|
+
private readonly destroyRef;
|
|
66
|
+
private readonly el;
|
|
67
|
+
private readonly focusTrapFactory;
|
|
68
|
+
private readonly contentRef;
|
|
69
|
+
private focusTrap;
|
|
70
|
+
ngAfterViewInit(): void;
|
|
71
|
+
ngOnDestroy(): void;
|
|
72
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<FuseModalComponent, never>;
|
|
73
|
+
static ɵcmp: i0.ɵɵComponentDeclaration<FuseModalComponent, "fuse-modal", never, {}, {}, never, never, true, never>;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
declare class FuseConfirmDialogComponent {
|
|
77
|
+
protected readonly modalRef: FuseModalRef<any>;
|
|
78
|
+
protected readonly options: FuseConfirmOptions;
|
|
79
|
+
protected confirm(): void;
|
|
80
|
+
protected cancel(): void;
|
|
81
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<FuseConfirmDialogComponent, never>;
|
|
82
|
+
static ɵcmp: i0.ɵɵComponentDeclaration<FuseConfirmDialogComponent, "fuse-confirm-dialog", never, {}, {}, never, never, true, never>;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** Reference to the open modal; injectable inside the hosted component. */
|
|
86
|
+
declare const FUSE_MODAL_REF: InjectionToken<FuseModalRef<any>>;
|
|
87
|
+
/**
|
|
88
|
+
* Arbitrary data passed via FuseModalConfig.data.
|
|
89
|
+
* Inject this token in the hosted component to receive it.
|
|
90
|
+
*/
|
|
91
|
+
declare const FUSE_MODAL_DATA: InjectionToken<any>;
|
|
92
|
+
|
|
93
|
+
export { FUSE_MODAL_DATA, FUSE_MODAL_REF, FuseConfirmDialogComponent, FuseModalComponent, FuseModalRef, FuseModalService };
|
|
94
|
+
export type { FuseConfirmOptions, FuseConfirmType, FuseModalConfig, FuseModalSize };
|