ng2-multi-modal
Version:
Multi Modal Component for Angular 19+
944 lines (937 loc) • 62.5 kB
JavaScript
import * as i0 from '@angular/core';
import { Component, input, effect, untracked, TemplateRef, Directive, signal, model, output, computed, HostListener, ViewChild, createComponent, Injectable } from '@angular/core';
import * as i1 from '@angular/router';
import { NavigationStart } from '@angular/router';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import * as i2 from '@angular/common';
import { CommonModule } from '@angular/common';
import { trigger, transition, style, animate } from '@angular/animations';
class LoadingIcon {
constructor() {
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.2", ngImport: i0, type: LoadingIcon, deps: [], target: i0.ɵɵFactoryTarget.Component });
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.2", type: LoadingIcon, isStandalone: true, selector: "loading-icon", ngImport: i0, template: `
<svg class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" width="50" height="50">
<path d="M512.056 908.647c-84.516 0-166.416-27.084-235.266-78.637-84.15-63.028-138.741-155.109-153.675-259.2-14.934-104.119 11.559-207.816 74.588-291.994 130.162-173.812 377.438-209.25 551.194-79.172 72.844 54.562 124.819 133.228 146.391 221.484 3.684 15.103-5.569 30.319-20.644 34.003-15.075 3.572-30.319-5.541-34.003-20.644-18.45-75.628-63-143.044-125.466-189.816-148.866-111.516-360.844-81.112-472.444 67.866-54.028 72.141-76.725 161.016-63.9 250.256 12.797 89.241 59.597 168.131 131.737 222.131 149.006 111.656 360.956 81.197 472.5-67.781 29.194-39.009 49.219-82.716 59.456-129.938 3.319-15.188 18.366-24.834 33.441-21.544 15.188 3.291 24.834 18.281 21.544 33.441-12.009 55.181-35.353 106.2-69.413 151.762-63.028 84.15-155.109 138.769-259.256 153.675-18.984 2.756-37.941 4.106-56.784 4.106z"
fill="#272636"></path>
<animateTransform attributeName="transform" type="rotate" from="0 0 0"
to="360 0 0" dur="1s" repeatCount="indefinite"></animateTransform>
</svg>
`, isInline: true });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.2", ngImport: i0, type: LoadingIcon, decorators: [{
type: Component,
args: [{ selector: 'loading-icon', template: `
<svg class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" width="50" height="50">
<path d="M512.056 908.647c-84.516 0-166.416-27.084-235.266-78.637-84.15-63.028-138.741-155.109-153.675-259.2-14.934-104.119 11.559-207.816 74.588-291.994 130.162-173.812 377.438-209.25 551.194-79.172 72.844 54.562 124.819 133.228 146.391 221.484 3.684 15.103-5.569 30.319-20.644 34.003-15.075 3.572-30.319-5.541-34.003-20.644-18.45-75.628-63-143.044-125.466-189.816-148.866-111.516-360.844-81.112-472.444 67.866-54.028 72.141-76.725 161.016-63.9 250.256 12.797 89.241 59.597 168.131 131.737 222.131 149.006 111.656 360.956 81.197 472.5-67.781 29.194-39.009 49.219-82.716 59.456-129.938 3.319-15.188 18.366-24.834 33.441-21.544 15.188 3.291 24.834 18.281 21.544 33.441-12.009 55.181-35.353 106.2-69.413 151.762-63.028 84.15-155.109 138.769-259.256 153.675-18.984 2.756-37.941 4.106-56.784 4.106z"
fill="#272636"></path>
<animateTransform attributeName="transform" type="rotate" from="0 0 0"
to="360 0 0" dur="1s" repeatCount="indefinite"></animateTransform>
</svg>
`, standalone: true }]
}], ctorParameters: () => [] });
class CloseIcon {
constructor() {
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.2", ngImport: i0, type: CloseIcon, deps: [], target: i0.ɵɵFactoryTarget.Component });
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.2", type: CloseIcon, isStandalone: true, selector: "close-icon", ngImport: i0, template: `
<svg
xmlns="http://www.w3.org/2000/svg"
width="10"
height="10"
viewBox="0 0 10 10"
>
<path
class="color-path"
fill-rule="evenodd"
d="M5.707 5l3.647-3.646a.5.5 0 0 0-.708-.708L5 4.293 1.354.646a.5.5 0 0 0-.708.708L4.293 5 .646 8.646a.5.5 0 0 0 .708.708L5 5.707l3.646 3.647a.5.5 0 0 0 .708-.708L5.707 5z"
/>
</svg>
`, isInline: true });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.2", ngImport: i0, type: CloseIcon, decorators: [{
type: Component,
args: [{ selector: 'close-icon', template: `
<svg
xmlns="http://www.w3.org/2000/svg"
width="10"
height="10"
viewBox="0 0 10 10"
>
<path
class="color-path"
fill-rule="evenodd"
d="M5.707 5l3.647-3.646a.5.5 0 0 0-.708-.708L5 4.293 1.354.646a.5.5 0 0 0-.708.708L4.293 5 .646 8.646a.5.5 0 0 0 .708.708L5 5.707l3.646 3.647a.5.5 0 0 0 .708-.708L5.707 5z"
/>
</svg>
`, standalone: true }]
}], ctorParameters: () => [] });
class MaximizeIcon {
constructor() { }
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.2", ngImport: i0, type: MaximizeIcon, deps: [], target: i0.ɵɵFactoryTarget.Component });
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.2", type: MaximizeIcon, isStandalone: true, selector: "maximize-icon", ngImport: i0, template: `
<svg
width="10"
height="10"
viewBox="0 0 10 10"
xmlns="http://www.w3.org/2000/svg">
<polygon
class="apple-theme color-path"
points="5,0 0,0 0,5"
/>
<polygon
class="apple-theme color-path"
points="10,10 10,5 5,10"
/>
<rect
class="win-theme color-rect"
x="0"
y="0"
width="10"
height="10"
stroke="#444"
stroke-width="1"
fill="none"
/>
<rect
class="win-theme color-rect"
x="1"
y="1"
width="8"
height="1"
stroke="#444"
stroke-width="1"
/>
</svg>
`, isInline: true });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.2", ngImport: i0, type: MaximizeIcon, decorators: [{
type: Component,
args: [{ selector: 'maximize-icon', template: `
<svg
width="10"
height="10"
viewBox="0 0 10 10"
xmlns="http://www.w3.org/2000/svg">
<polygon
class="apple-theme color-path"
points="5,0 0,0 0,5"
/>
<polygon
class="apple-theme color-path"
points="10,10 10,5 5,10"
/>
<rect
class="win-theme color-rect"
x="0"
y="0"
width="10"
height="10"
stroke="#444"
stroke-width="1"
fill="none"
/>
<rect
class="win-theme color-rect"
x="1"
y="1"
width="8"
height="1"
stroke="#444"
stroke-width="1"
/>
</svg>
`, standalone: true }]
}], ctorParameters: () => [] });
class MinimizeIcon {
constructor() { }
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.2", ngImport: i0, type: MinimizeIcon, deps: [], target: i0.ɵɵFactoryTarget.Component });
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.2", type: MinimizeIcon, isStandalone: true, selector: "minimize-icon", ngImport: i0, template: `
<svg
xmlns="http://www.w3.org/2000/svg"
width="10"
height="10"
viewBox="0 0 10 10"
>
<path
class="color-path"
fill-rule="evenodd"
d="M0 5h10v1H0z"
/>
</svg>
`, isInline: true });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.2", ngImport: i0, type: MinimizeIcon, decorators: [{
type: Component,
args: [{ selector: 'minimize-icon', template: `
<svg
xmlns="http://www.w3.org/2000/svg"
width="10"
height="10"
viewBox="0 0 10 10"
>
<path
class="color-path"
fill-rule="evenodd"
d="M0 5h10v1H0z"
/>
</svg>
`, standalone: true }]
}], ctorParameters: () => [] });
class MaximizeDIcon {
constructor() { }
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.2", ngImport: i0, type: MaximizeDIcon, deps: [], target: i0.ɵɵFactoryTarget.Component });
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.2", type: MaximizeDIcon, isStandalone: true, selector: "maximized-icon", ngImport: i0, template: `
<svg width="100"
height="100"
viewBox="-2 -2 14 14"
xmlns="http://www.w3.org/2000/svg">
<polygon
class="apple-theme color-path"
points="-2,4.6 4.6,4.6 4.6,-2"
></polygon>
<polygon
class="apple-theme color-path"
points="5.4,5.4 12,5.4 5.4,12"
></polygon>
<!-- Two rectangles, one is under the other, one in the top right corner, one in the bottom left corner, and the one in the bottom left corner -->
<rect
class="win-theme color-path"
x="2"
y="-1"
width="9"
height="9"
stroke="#444"
stroke-width="1"
fill="none"
></rect>
<rect
class="win-theme color-path"
x="-1"
y="2"
width="9"
height="9"
stroke="#444"
stroke-width="1"
></rect>
</svg>
`, isInline: true });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.2", ngImport: i0, type: MaximizeDIcon, decorators: [{
type: Component,
args: [{ selector: 'maximized-icon', template: `
<svg width="100"
height="100"
viewBox="-2 -2 14 14"
xmlns="http://www.w3.org/2000/svg">
<polygon
class="apple-theme color-path"
points="-2,4.6 4.6,4.6 4.6,-2"
></polygon>
<polygon
class="apple-theme color-path"
points="5.4,5.4 12,5.4 5.4,12"
></polygon>
<!-- Two rectangles, one is under the other, one in the top right corner, one in the bottom left corner, and the one in the bottom left corner -->
<rect
class="win-theme color-path"
x="2"
y="-1"
width="9"
height="9"
stroke="#444"
stroke-width="1"
fill="none"
></rect>
<rect
class="win-theme color-path"
x="-1"
y="2"
width="9"
height="9"
stroke="#444"
stroke-width="1"
></rect>
</svg>
`, standalone: true }]
}], ctorParameters: () => [] });
class StringTemplateOutletDirective {
viewContainer;
templateRef;
embeddedViewRef = null;
context = new StringTemplateOutletContext();
// Using Angular 19's input() function instead of @Input decorator
stringTemplateOutletContext = input(null);
stringTemplateOutlet = input(null);
static ngTemplateContextGuard(_dir, _ctx) {
return true;
}
constructor(viewContainer, templateRef) {
this.viewContainer = viewContainer;
this.templateRef = templateRef;
// Effect that reacts to changes in inputs
effect(() => {
// Read the current values of inputs
const outlet = this.stringTemplateOutlet();
const _ = this.stringTemplateOutletContext();
// Update the implicit context when outlet changes
if (outlet !== null) {
this.context.$implicit = outlet;
}
// Determine if we should recreate the view
untracked(() => {
const isNewOutletTemplate = outlet instanceof TemplateRef;
// Check if the embedded view exists and if the template type changed
const shouldRecreate = !this.embeddedViewRef ||
(this.embeddedViewRef && isNewOutletTemplate);
if (shouldRecreate) {
this.recreateView();
}
else if (this.embeddedViewRef) {
this.updateContext();
}
});
});
}
recreateView() {
this.viewContainer.clear();
const isTemplateRef = this.stringTemplateOutlet() instanceof TemplateRef;
const templateRef = (isTemplateRef ? this.stringTemplateOutlet() : this.templateRef);
this.embeddedViewRef = this.viewContainer.createEmbeddedView(templateRef, isTemplateRef ? this.stringTemplateOutletContext() : this.context);
}
updateContext() {
if (!this.embeddedViewRef)
return;
const isTemplateRef = this.stringTemplateOutlet() instanceof TemplateRef;
const newCtx = isTemplateRef ? this.stringTemplateOutletContext() : this.context;
const oldCtx = this.embeddedViewRef.context;
if (newCtx) {
for (const propName of Object.keys(newCtx)) {
oldCtx[propName] = newCtx[propName];
}
}
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.2", ngImport: i0, type: StringTemplateOutletDirective, deps: [{ token: i0.ViewContainerRef }, { token: i0.TemplateRef }], target: i0.ɵɵFactoryTarget.Directive });
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "19.2.2", type: StringTemplateOutletDirective, isStandalone: true, selector: "[stringTemplateOutlet]", inputs: { stringTemplateOutletContext: { classPropertyName: "stringTemplateOutletContext", publicName: "stringTemplateOutletContext", isSignal: true, isRequired: false, transformFunction: null }, stringTemplateOutlet: { classPropertyName: "stringTemplateOutlet", publicName: "stringTemplateOutlet", isSignal: true, isRequired: false, transformFunction: null } }, exportAs: ["stringTemplateOutlet"], ngImport: i0 });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.2", ngImport: i0, type: StringTemplateOutletDirective, decorators: [{
type: Directive,
args: [{
selector: '[stringTemplateOutlet]',
exportAs: 'stringTemplateOutlet',
standalone: true
}]
}], ctorParameters: () => [{ type: i0.ViewContainerRef }, { type: i0.TemplateRef }] });
class StringTemplateOutletContext {
$implicit;
}
class Ng2MultiModalComponent {
modalService;
modalId = signal('window' + Math.floor(Math.random() * 1000000));
titleHeight = signal(0);
position = signal({});
dragging = signal(false);
windowMouseEnterFlag = signal(false);
windowMouseDownFlag = signal(false);
windowMouseLeaveFlag = signal(true);
clickedX = signal(0);
clickedY = signal(0);
mouseEventSignal = signal(null);
mouseEnteredSignal = signal(null);
borderWidth = signal(4);
cursorStyle = signal('default');
display = signal('none');
border = signal({
isLeft: false,
isRight: false,
isTop: false,
isBottom: false
});
propertyBeforeMaximize = signal(null);
// Input signals
title = input('Modal Name');
icon = input(null);
align = input('leftTop');
bodyStyle = input({});
closeOnNavigation = input(false);
closable = input(true);
content = input();
minHeight = input(100);
minWidth = input(175);
maximizable = input(true);
minimizable = input(true);
resizable = input(true);
outOfBounds = input(false);
loadingTip = input(this.getLocaleText('loading'));
// Model signals
height = model(300);
width = model(300);
zIndex = model(0);
offsetY = model(200);
offsetX = model(200);
loading = model(true);
theme = model('light');
draggable = model(true);
contentScrollable = model(false);
minimized = model(false);
maximized = model(false);
// Output events
onReady = output();
onClose = output();
onResize = output();
onMaximize = output();
onMaximizeRestore = output();
onMinimize = output();
onMinimizeRestore = output();
onSelected = output();
onMove = output();
constructor(modalService) {
this.modalService = modalService;
}
get language() {
return this.modalService?.language() || 'en';
}
windowSizeSignal = computed(() => ({
offsetX: this.offsetX(),
offsetY: this.offsetY(),
align: this.align(),
width: this.width(),
height: this.height()
}));
leftSignal = computed(() => (this.align() === 'leftTop' || this.align() === 'leftBottom') ?
this.offsetX() : window.innerWidth - this.width() - this.offsetX());
rightSignal = computed(() => (this.align() === 'rightTop' || this.align() === 'rightBottom') ?
window.innerWidth - this.offsetX() : this.width() + this.offsetX());
topSignal = computed(() => (this.align() === 'leftTop' || this.align() === 'rightTop') ?
this.offsetY() : window.innerHeight - this.height() - this.offsetY());
bottomSignal = computed(() => (this.align() === 'leftBottom' || this.align() === 'rightBottom') ?
window.innerHeight - this.offsetY() : this.height() + this.offsetY());
selectedSignal = computed(() => this.modalService.selectedWindow() === this.modalId());
// Create a signal for the element
titleBarElement = signal(null);
// Update the setter
set titleBar(titleBar) {
this.titleBarElement.set(titleBar);
if (titleBar) {
this.titleHeight.set(titleBar.nativeElement.offsetHeight);
}
}
getLocaleText(text) {
const dictionary = {
'es': {
loading: 'Loading...',
close: 'Close Window',
maximize: 'Maximize',
minimize: 'Minimize',
windowMode: 'Window Mode',
},
'en': {
loading: 'Loading...',
close: 'Close Window',
maximize: 'Maximize',
minimize: 'Minimize',
windowMode: 'Window Mode',
}
};
return dictionary[this.language][text];
}
updateOffsetX(offsetX) {
this.offsetX.set(offsetX);
if (['leftTop', 'leftBottom'].includes(this.align())) {
this.position.update((oldValue) => {
return {
...oldValue,
left: offsetX + 'px',
right: null
};
});
}
else {
this.position.update((oldValue) => {
return {
...oldValue,
left: null,
right: offsetX + 'px'
};
});
}
}
updateOffsetY(offsetY) {
this.offsetY.set(offsetY);
if (['leftTop', 'rightTop'].includes(this.align())) {
this.position.update((oldValue) => {
return {
...oldValue,
top: offsetY + 'px',
bottom: null
};
});
}
else {
this.position.update((oldValue) => {
return {
...oldValue,
top: null,
bottom: offsetY + 'px'
};
});
}
}
preventTextSelection(prevent) {
if (prevent) {
// Create a temporary overlay div that covers the entire viewport
// This catches all mouse events during resize/drag without affecting actual content
if (!document.getElementById('ng2-modal-overlay')) {
const overlay = document.createElement('div');
overlay.id = 'ng2-modal-overlay';
overlay.style.position = 'fixed';
overlay.style.top = '0';
overlay.style.left = '0';
overlay.style.width = '100%';
overlay.style.height = '100%';
overlay.style.zIndex = '999999';
overlay.style.pointerEvents = 'none'; // Allow events to pass through
document.body.appendChild(overlay);
}
// Better to use a class to avoid direct style manipulation
document.body.classList.add('ng2-modal-no-select');
// Add style if it doesn't exist
if (!document.getElementById('ng2-modal-style')) {
const style = document.createElement('style');
style.id = 'ng2-modal-style';
style.innerHTML = `
.ng2-modal-no-select {
cursor: inherit !important;
}
.ng2-modal-no-select ::selection {
background: transparent !important;
}
.ng2-modal-no-select ::-moz-selection {
background: transparent !important;
}
`;
document.head.appendChild(style);
}
}
else {
// Remove all elements we added
document.body.classList.remove('ng2-modal-no-select');
const overlay = document.getElementById('ng2-modal-overlay');
if (overlay) {
overlay.remove();
}
}
}
documentMouseLeave() {
// Ensure text selection is re-enabled when the mouse leaves the document
this.preventTextSelection(false);
this.dragging.set(false);
this.windowMouseDownFlag.set(false);
}
onMouseMove(event) {
if (!this.draggable() || this.maximized()) {
return;
}
this.mouseEventSignal.set(event);
// Only prevent text selection during actual dragging or resizing
if (this.dragging() || (this.windowMouseDownFlag() && this.resizable() &&
Object.values(this.border()).some(value => value === true))) {
this.preventTextSelection(true);
}
if (this.dragging()) {
// Replace when(this.align) with switch or if/else
let newOffsetX = 0;
if (this.align() === 'leftTop' || this.align() === 'leftBottom') {
if (!this.outOfBounds) {
let offsetX = Math.max(event.clientX - this.clickedX(), 0);
if (offsetX + this.width() > window.innerWidth) {
offsetX = window.innerWidth - this.width();
}
newOffsetX = offsetX;
}
else {
newOffsetX = event.clientX - this.clickedX();
}
}
else if (this.align() === 'rightTop' || this.align() === 'rightBottom') {
if (!this.outOfBounds) {
let offsetX = Math.max(window.innerWidth - event.clientX + this.clickedX() - this.width(), 0);
if (offsetX + this.width() > window.innerWidth) {
offsetX = window.innerWidth - this.width();
}
newOffsetX = offsetX;
}
else {
newOffsetX = window.innerWidth - event.clientX + this.clickedX() - this.width();
}
}
this.updateOffsetX(newOffsetX);
// Handle Y positioning
let newOffsetY = 0;
if (this.align() === 'leftTop' || this.align() === 'rightTop') {
if (!this.outOfBounds) {
let offsetY = Math.max(event.clientY - this.clickedY(), 0);
if (offsetY + this.height() > window.innerHeight) {
offsetY = window.innerHeight - this.height();
}
newOffsetY = offsetY;
}
else {
newOffsetY = event.clientY - this.clickedY();
}
}
else if (this.align() === 'leftBottom' || this.align() === 'rightBottom') {
if (!this.outOfBounds) {
let offsetY = Math.max(window.innerHeight - event.clientY + this.clickedY() - this.height(), 0);
if (offsetY + this.height() > window.innerHeight) {
offsetY = window.innerHeight - this.height();
}
newOffsetY = offsetY;
}
else {
newOffsetY = window.innerHeight - event.clientY + this.clickedY() - this.height();
}
}
this.updateOffsetY(newOffsetY);
}
if (this.windowMouseDownFlag() && this.resizable()) {
this.resizeWindow(event);
}
else {
this.onMove.emit({ ...this });
}
let x = event.clientX;
let y = event.clientY;
let leftBorderX = Math.abs(this.leftSignal() - x) <= this.borderWidth();
let rightBorderX = Math.abs(this.leftSignal() + this.width() - x) <= this.borderWidth();
let rightLeftBorderY = (y > this.topSignal()) && (y < (this.topSignal() + this.height()));
let topBorderY = Math.abs(this.topSignal() - y) <= this.borderWidth();
let bottomBorderY = Math.abs(this.topSignal() + this.height() - y) <= this.borderWidth();
let topBottomBorderX = x > this.leftSignal() && x < this.leftSignal() + this.width();
if (this.resizable()) {
if (leftBorderX && bottomBorderY) {
this.cursorStyle.set('sw-resize');
this.border.set({
isLeft: true,
isBottom: true,
});
this.contentScrollable.set(true);
}
else if (rightBorderX && bottomBorderY) {
this.cursorStyle.set('se-resize');
this.border.set({
isRight: true,
isBottom: true,
});
this.contentScrollable.set(true);
}
else if (leftBorderX && topBorderY) {
this.cursorStyle.set('nw-resize');
this.border.set({
isLeft: true,
isTop: true,
});
this.contentScrollable.set(true);
}
else if (rightBorderX && topBorderY) {
this.cursorStyle.set('ne-resize');
this.border.set({
isRight: true,
isTop: true,
});
this.contentScrollable.set(true);
}
else if (leftBorderX && rightLeftBorderY) {
this.cursorStyle.set('w-resize');
this.border.set({
isLeft: true,
});
this.contentScrollable.set(true);
}
else if (rightBorderX && rightLeftBorderY) {
this.cursorStyle.set('e-resize');
this.border.set({
isRight: true,
});
this.contentScrollable.set(true);
}
else if (topBorderY && topBottomBorderX) {
this.cursorStyle.set('n-resize');
this.border.set({
isTop: true,
});
this.contentScrollable.set(true);
}
else if (bottomBorderY && topBottomBorderX) {
this.cursorStyle.set('s-resize');
this.border.set({
isBottom: true,
});
this.contentScrollable.set(true);
}
else {
this.border.set({});
this.cursorStyle.set('auto');
this.contentScrollable.set(false);
}
}
}
resizeWindow(event) {
if (!this.draggable()) {
return;
}
if (this.dragging()) {
return;
}
if (!this.border().isLeft && !this.border().isRight && !this.border().isTop && !this.border().isBottom) {
return;
}
if (this.border().isLeft) {
if (this.align().toLocaleLowerCase().includes('left')) {
let r = this.rightSignal();
this.updateOffsetX(event.clientX);
this.width.set(r - this.leftSignal());
}
else {
this.width.set(this.rightSignal() - event.clientX);
}
}
if (this.border().isRight) {
if (this.align().toLocaleLowerCase().includes('left')) {
this.width.update(prev => prev + event.clientX - this.rightSignal());
}
else {
this.width.update(prev => prev + event.clientX - this.rightSignal());
;
this.updateOffsetX(Math.max(window.innerWidth - event.clientX, 0));
}
}
if (this.border().isTop) {
if (this.align().toLocaleLowerCase().includes('top')) {
let b = this.bottomSignal();
this.updateOffsetY(Math.max(event.clientY, 0));
this.height.set(b - this.topSignal());
}
else {
this.height.set(this.bottomSignal() - event.clientY);
}
}
if (this.border().isBottom) {
if (this.align().toLocaleLowerCase().includes('top')) {
this.height.update(prev => prev + event.clientY - this.bottomSignal());
}
else {
this.height.update(prev => prev + event.clientY - this.bottomSignal());
this.updateOffsetY(Math.max(window.innerHeight - event.clientY, 0));
}
}
if (this.height() < this.minHeight()) {
this.height.set(this.minHeight());
}
if (this.width() < this.minWidth()) {
this.width.set(this.minWidth());
}
if (!this.outOfBounds() && this.height() + this.offsetY() > window.innerHeight) {
this.updateOffsetY(Math.max(window.innerHeight - this.height(), 0));
}
if (!this.outOfBounds && this.width() + this.offsetX() > window.innerWidth) {
this.updateOffsetX(Math.max(window.innerWidth - this.width(), 0));
}
if (this.offsetY() < 0) {
this.updateOffsetY(0);
}
this.onResize.emit(this.windowSizeSignal());
}
titleBarMouseDown(event) {
if (event.button === 2) {
return;
}
// Prevent default action to avoid text selection
event.preventDefault();
// Enable text selection prevention
this.preventTextSelection(true);
this.dragging.set(true);
this.clickedX.set(event.clientX - this.leftSignal());
this.clickedY.set(event.clientY - this.topSignal());
}
titleBarMouseUp(event) {
// We should always re-enable text selection on mouse up
this.preventTextSelection(false);
this.dragging.set(false);
this.windowMouseDownFlag.set(false);
}
// Add a key event listener to ensure we clean up if Escape is pressed
handleEscapeKey(event) {
this.preventTextSelection(false);
}
windowMouseEnter(event) {
if (!this.draggable()) {
return;
}
this.mouseEnteredSignal.set(event);
this.windowMouseEnterFlag.set(true);
this.windowMouseLeaveFlag.set(false);
}
windowMouseDown(event) {
this.modalService.selectedWindow.set(this.modalId());
if (!this.draggable() || event.button === 2) {
return;
}
// Only prevent selection when clicking on a border for resizing
const onBorder = Object.values(this.border()).some(value => value === true);
if (onBorder && this.resizable()) {
event.preventDefault();
this.preventTextSelection(true);
}
this.windowMouseDownFlag.set(true);
this.onSelected.emit(this.modalId());
if (this.windowMouseDownFlag() && this.windowMouseEnterFlag()) {
const idx = this.modalService.maxZIndex++;
this.zIndex.set(idx);
}
}
windowMouseLeave(event) {
if (!this.draggable()) {
return;
}
this.windowMouseLeaveFlag.set(true);
this.windowMouseEnterFlag.set(false);
}
close() {
if (this.closable()) {
this.height.set(0);
window.onresize = null;
this.toggleBodyScrollable(true);
this.display.set('none');
this.onClose.emit(this.modalId());
}
}
minimize() {
window.onresize = null;
if (this.minimized()) {
this.minimized.set(false);
this.display.set('block');
// this.modalService.dockComponentRef!.instance.docks =
// this.modalService.dockComponentRef!.instance.docks.filter(win => win != this);
this.modalService.dockComponentRef.instance.docks.update(prev => prev.filter(win => win !== this));
this.onMinimizeRestore.emit(this.windowSizeSignal());
}
else {
this.toggleBodyScrollable(true);
this.minimized.set(true);
setTimeout(() => {
this.display.set('none');
}, 200);
this.modalService.addMinimizeItem(this);
this.onMinimize.emit(this.windowSizeSignal());
}
this.onResize.emit(this.windowSizeSignal());
}
maximize() {
return new Promise(resolve => {
const isCurrentlyMaximized = this.maximized();
const hasStoredProperties = !!this.propertyBeforeMaximize();
if (isCurrentlyMaximized && hasStoredProperties) {
// Restore from maximized state
this.toggleBodyScrollable(true);
window.onresize = null;
this.maximized.set(false);
const { width, height, offsetX, offsetY } = this.propertyBeforeMaximize();
this.width.set(width);
this.height.set(height);
this.updateOffsetX(offsetX);
this.updateOffsetY(offsetY);
this.draggable.set(true);
this.onResize.emit(this.windowSizeSignal());
this.onMaximizeRestore.emit(this.windowSizeSignal());
resolve(true);
}
else {
// Save current state before maximizing
this.propertyBeforeMaximize.set({
width: this.width(),
height: this.height(),
offsetX: this.offsetX(),
offsetY: this.offsetY(),
align: this.align()
});
// Maximize the window
this.toggleBodyScrollable(false);
this.maximized.set(true);
this.draggable.set(false);
// Set dimensions and position
this.updateOffsetX(0);
this.updateOffsetY(0);
this.height.set(window.innerHeight);
this.width.set(window.innerWidth);
// Handle window resize when maximized
window.onresize = () => {
this.width.set(window.innerWidth);
this.height.set(window.innerHeight);
this.updateOffsetX(0);
this.updateOffsetY(0);
};
this.onResize.emit(this.windowSizeSignal());
this.onMaximize.emit(this.windowSizeSignal());
resolve(true);
}
});
}
//if html window resized, judge if window is out of screen, if so, move it to the screen
resizeListener(event) {
if (this.offsetY() + this.height() > window.innerHeight) {
this.updateOffsetY(Math.max(window.innerHeight - this.height(), 0));
}
if (this.offsetX() + this.width() > window.innerWidth) {
this.updateOffsetX(Math.max(window.innerWidth - this.width(), 0));
}
this.onResize.emit(this.windowSizeSignal());
}
toggleBodyScrollable(scrollable = true) {
setTimeout(() => {
if (scrollable) {
document.body.style.overflow = 'auto';
}
else {
document.body.style.overflow = 'hidden';
}
}, 200);
}
ngOnDestroy() {
// Clean up any remaining text selection prevention
this.preventTextSelection(false);
// Remove any window resize handlers
window.onresize = null;
}
async ngAfterViewInit() {
if (this.maximized()) {
this.display.set('none');
this.maximized.set(false);
await this.maximize();
}
this.display.set('block');
this.loading.set(false);
this.onReady.emit(this);
this.updateOffsetX(this.offsetX());
this.updateOffsetY(this.offsetY());
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.2", ngImport: i0, type: Ng2MultiModalComponent, deps: [{ token: Ng2MultiModalService }], target: i0.ɵɵFactoryTarget.Component });
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.2", type: Ng2MultiModalComponent, isStandalone: true, selector: "ng2-multi-modal", inputs: { title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, icon: { classPropertyName: "icon", publicName: "icon", isSignal: true, isRequired: false, transformFunction: null }, align: { classPropertyName: "align", publicName: "align", isSignal: true, isRequired: false, transformFunction: null }, bodyStyle: { classPropertyName: "bodyStyle", publicName: "bodyStyle", isSignal: true, isRequired: false, transformFunction: null }, closeOnNavigation: { classPropertyName: "closeOnNavigation", publicName: "closeOnNavigation", isSignal: true, isRequired: false, transformFunction: null }, closable: { classPropertyName: "closable", publicName: "closable", isSignal: true, isRequired: false, transformFunction: null }, content: { classPropertyName: "content", publicName: "content", isSignal: true, isRequired: false, transformFunction: null }, minHeight: { classPropertyName: "minHeight", publicName: "minHeight", isSignal: true, isRequired: false, transformFunction: null }, minWidth: { classPropertyName: "minWidth", publicName: "minWidth", isSignal: true, isRequired: false, transformFunction: null }, maximizable: { classPropertyName: "maximizable", publicName: "maximizable", isSignal: true, isRequired: false, transformFunction: null }, minimizable: { classPropertyName: "minimizable", publicName: "minimizable", isSignal: true, isRequired: false, transformFunction: null }, resizable: { classPropertyName: "resizable", publicName: "resizable", isSignal: true, isRequired: false, transformFunction: null }, outOfBounds: { classPropertyName: "outOfBounds", publicName: "outOfBounds", isSignal: true, isRequired: false, transformFunction: null }, loadingTip: { classPropertyName: "loadingTip", publicName: "loadingTip", isSignal: true, isRequired: false, transformFunction: null }, height: { classPropertyName: "height", publicName: "height", isSignal: true, isRequired: false, transformFunction: null }, width: { classPropertyName: "width", publicName: "width", isSignal: true, isRequired: false, transformFunction: null }, zIndex: { classPropertyName: "zIndex", publicName: "zIndex", isSignal: true, isRequired: false, transformFunction: null }, offsetY: { classPropertyName: "offsetY", publicName: "offsetY", isSignal: true, isRequired: false, transformFunction: null }, offsetX: { classPropertyName: "offsetX", publicName: "offsetX", isSignal: true, isRequired: false, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, theme: { classPropertyName: "theme", publicName: "theme", isSignal: true, isRequired: false, transformFunction: null }, draggable: { classPropertyName: "draggable", publicName: "draggable", isSignal: true, isRequired: false, transformFunction: null }, contentScrollable: { classPropertyName: "contentScrollable", publicName: "contentScrollable", isSignal: true, isRequired: false, transformFunction: null }, minimized: { classPropertyName: "minimized", publicName: "minimized", isSignal: true, isRequired: false, transformFunction: null }, maximized: { classPropertyName: "maximized", publicName: "maximized", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { height: "heightChange", width: "widthChange", zIndex: "zIndexChange", offsetY: "offsetYChange", offsetX: "offsetXChange", loading: "loadingChange", theme: "themeChange", draggable: "draggableChange", contentScrollable: "contentScrollableChange", minimized: "minimizedChange", maximized: "maximizedChange", onReady: "onReady", onClose: "onClose", onResize: "onResize", onMaximize: "onMaximize", onMaximizeRestore: "onMaximizeRestore", onMinimize: "onMinimize", onMinimizeRestore: "onMinimizeRestore", onSelected: "onSelected", onMove: "onMove" }, host: { listeners: { "document:mouseleave": "documentMouseLeave()", "document:mousemove": "onMouseMove($event)", "document:mouseup": "titleBarMouseUp($event)", "document:keydown.escape": "handleEscapeKey($event)", "window: resize": "resizeListener($event)" } }, viewQueries: [{ propertyName: "titleBar", first: true, predicate: ["titleBar"], descendants: true }], ngImport: i0, template: "<div\r\n [ngClass]=\"[\r\n 'ng-modal',\r\n 'ng-modal-theme' + (this.theme() === 'dark' ? '-dark' : '')\r\n ]\"\r\n [hidden]=\"loading()\"\r\n [ngStyle]=\"position()\"\r\n [style.width.px]=\"width()\"\r\n [style.height.px]=\"height()\"\r\n [style.cursor]=\"cursorStyle()\"\r\n [style.z-index]=\"zIndex()\"\r\n [style.display]=\"display()\"\r\n></div>\r\n<div\r\n [class]=\"'ng-modal ng-modal-theme' + (this.theme() === 'dark' ? '-dark' : '')\"\r\n [hidden]=\"loading()\"\r\n [ngClass]=\"{\r\n selected: selectedSignal(),\r\n minimized: minimized(),\r\n maximized: maximized()\r\n }\"\r\n [ngStyle]=\"position()\"\r\n [style.width.px]=\"width()\"\r\n [style.height.px]=\"height()\"\r\n [style.cursor]=\"cursorStyle()\"\r\n [style.z-index]=\"zIndex()\"\r\n [style.display]=\"display()\"\r\n (mouseenter)=\"windowMouseEnter($event)\"\r\n (mousedown)=\"windowMouseDown($event)\"\r\n (mouseleave)=\"windowMouseLeave($event)\"\r\n>\r\n <div\r\n #titleBar\r\n class=\"win-title-bar\"\r\n *ngIf=\"!!title()\"\r\n (dblclick)=\"maximize()\"\r\n [ngClass]=\"{ 'no-drag': !draggable() }\"\r\n (mousedown)=\"titleBarMouseDown($event)\"\r\n >\r\n <div class=\"title-name\" [title]=\"title()\">\r\n <ng-container *stringTemplateOutlet=\"icon()\">\r\n @if (icon()) {\r\n <img class=\"icon\" draggable=\"false\" [src]=\"icon()\" alt=\"icon\" />\r\n }\r\n </ng-container>\r\n <ng-container *stringTemplateOutlet=\"title()\">{{ title() }}</ng-container>\r\n </div>\r\n <div class=\"win-icons\">\r\n <close-icon\r\n class=\"win-icon\"\r\n [title]=\"getLocaleText('close')\"\r\n (click)=\"close()\"\r\n />\r\n <minimize-icon\r\n class=\"win-icon\"\r\n [title]=\"getLocaleText('minimize')\"\r\n (click)=\"minimize()\"\r\n />\r\n <maximize-icon\r\n class=\"win-icon\"\r\n [title]=\"getLocaleText('maximize')\"\r\n *ngIf=\"!maximized()\"\r\n (click)=\"maximize()\"\r\n />\r\n <maximized-icon\r\n class=\"win-icon\"\r\n [title]=\"getLocaleText('windowMode')\"\r\n *ngIf=\"maximized()\"\r\n (click)=\"maximize()\"\r\n />\r\n </div>\r\n </div>\r\n <div\r\n class=\"body\"\r\n [ngClass]=\"contentScrollable() ? 'no-scroll' : ''\"\r\n [ngStyle]=\"bodyStyle()\"\r\n >\r\n <ng-container *stringTemplateOutlet=\"content\">{{ content() }}</ng-container>\r\n <ng-content></ng-content>\r\n </div>\r\n</div>\r\n\r\n<ng-container *stringTemplateOutlet=\"loadingTip()\">\r\n <div\r\n [class]=\"'window-loading' + (this.theme() === 'dark' ? '-dark' : '')\"\r\n [ngStyle]=\"position()\"\r\n *ngIf=\"loading()\"\r\n >\r\n <div>\r\n <loading-icon />\r\n </div>\r\n <ng-container *stringTemplateOutlet=\"loadingTip()\">{{\r\n loadingTip()\r\n }}</ng-container>\r\n </div>\r\n</ng-container>\r\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i2.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "component", type: CloseIcon, selector: "close-icon" }, { kind: "component", type: LoadingIcon, selector: "loading-icon" }, { kind: "component", type: MaximizeIcon, selector: "maximize-icon" }, { kind: "component", type: MinimizeIcon, selector: "minimize-icon" }, { kind: "component", type: MaximizeDIcon, selector: "maximized-icon" }, { kind: "directive", type: StringTemplateOutletDirective, selector: "[stringTemplateOutlet]", inputs: ["stringTemplateOutletContext", "stringTemplateOutlet"], exportAs: ["stringTemplateOutlet"] }] });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.2", ngImport: i0, type: Ng2MultiModalComponent, decorators: [{
type: Component,
args: [{ selector: 'ng2-multi-modal', imports: [CommonModule, CloseIcon, LoadingIcon, MaximizeIcon, MinimizeIcon, MaximizeDIcon, StringTemplateOutletDirective], standalone: true, template: "<div\r\n [ngClass]=\"[\r\n 'ng-modal',\r\n 'ng-modal-theme' + (this.theme() === 'dark' ? '-dark' : '')\r\n ]\"\r\n [hidden]=\"loading()\"\r\n [ngStyle]=\"position()\"\r\n [style.width.px]=\"width()\"\r\n [style.height.px]=\"height()\"\r\n [style.cursor]=\"cursorStyle()\"\r\n [style.z-index]=\"zIndex()\"\r\n [style.display]=\"display()\"\r\n></div>\r\n<div\r\n [class]=\"'ng-modal ng-modal-theme' + (this.theme() === 'dark' ? '-dark' : '')\"\r\n [hidden]=\"loading()\"\r\n [ngClass]=\"{\r\n selected: selectedSignal(),\r\n minimized: minimized(),\r\n maximized: maximized()\r\n }\"\r\n [ngStyle]=\"position()\"\r\n [style.width.px]=\"width()\"\r\n [style.height.px]=\"height()\"\r\n [style.cursor]=\"cursorStyle()\"\r\n [style.z-index]=\"zIndex()\"\r\n [style.display]=\"display()\"\r\n (mouseenter)=\"windowMouseEnter($event)\"\r\n (mousedown)=\"windowMouseDown($event)\"\r\n (mouseleave)=\"windowMouseLeave($event)\"\r\n>\r\n <div\r\n #titleBar\r\n class=\"win-title-bar\"\r\n *ngIf=\"!!title()\"\r\n (dblclick)=\"maximize()\"\r\n [ngClass]=\"{ 'no-drag': !draggable() }\"\r\n (mousedown)=\"titleBarMouseDown($event)\"\r\n >\r\n <div class=\"title-name\" [title]=\"title()\">\r\n <ng-container *stringTemplateOutlet=\"icon()\">\r\n @if (icon()) {\r\n <img class=\"icon\" draggable=\"false\" [src]=\"icon()\" alt=\"icon\" />\r\n }\r\n </ng-container>\r\n <ng-container *stringTemplateOutlet=\"title()\">{{ title() }}</ng-container>\r\n </div>\r\n <div class=\"win-icons\">\r\n <close-icon\r\n class=\"win-icon\"\r\n [title]=\"getLocaleText('close')\"\r\n (click)=\"close()\"\r\n />\r\n <minimize-icon\r\n class=\"win-icon\"\r\n [title]=\"getLocaleText('minimize')\"\r\n (click)=\"minimize()\"\r\n />\r\n <maximize-icon\r\n class=\"win-icon\"\r\n [title]=\"getLocaleText('maximize')\"\r\n *ngIf=\"!maximized()\"\r\n (click)=\"maximize()\"\r\n />\r\n <maximized-icon\r\n class=\"win-icon\"\r\n [title]=\"getLocaleText('windowMode')\"\r\n *ngIf=\"maximized()\"\r\n (click)=\"maximize()\"\r\n />\r\n </div>\r\n </div>\r\n <div\r\n class=\"body\"\r\n [ngClass]=\"contentScrollable() ? 'no-scroll' : ''\"\r\n [ngStyle]=\"bodyStyle()\"\r\n >\r\n <ng-container *stringTemplateOutlet=\"content\">{{ content() }}</ng-container>\r\n <ng-content></ng-content>\r\n </div>\r\n</div>\r\n\r\n<ng-container *stringTemplateOutlet=\"loadingTip()\">\r\n <div\r\n [class]=\"'window-loading' + (this.theme() === 'dark' ? '-dark' : '')\"\r\n [ngStyle]=\"position()\"\r\n *ngIf=\"loading()\"\r\n >\r\n <div>\r\n <loading-icon />\r\n </div>\r\n <ng-container *stringTemplateOutlet=\"loadingTip()\">{{\r\n loadingTip()\r\n }}</ng-container>\r\n </div>\r\n</ng-container>\r\n" }]
}], ctorParameters: () => [{ type: Ng2MultiModalService }], propDecorators: { titleBar: [{
type: ViewChild,
args: ['titleBar', { static: false }]
}], documentMouseLeave: [{
type: HostListener,
args: ['document:mouseleave']
}], onMouseMove: [{
type: HostListener,
args: ['document:mousemove', ['$event']]
}], titleBarMouseUp: [{
type: HostListener,
args: ['document:mouseup', ['$event']]
}], handleEscapeKey: